From 5a018dc77f4bb3119da9bd374112082eae7c027d Mon Sep 17 00:00:00 2001 From: falkTX Date: Tue, 1 Mar 2016 23:43:53 +0100 Subject: [PATCH] Update juce --- .../buffers/juce_FloatVectorOperations.cpp | 31 +- .../buffers/juce_FloatVectorOperations.h | 6 + .../effects/juce_CatmullRomInterpolator.cpp | 63 +++ .../effects/juce_CatmullRomInterpolator.h | 89 ++++ .../effects/juce_LagrangeInterpolator.cpp | 272 ++++++------ .../effects/juce_LagrangeInterpolator.h | 13 +- .../effects/juce_LinearSmoothedValue.h | 8 +- .../juce_audio_basics/juce_audio_basics.cpp | 3 +- .../juce_audio_basics/juce_audio_basics.h | 3 +- .../midi/juce_MidiMessage.cpp | 2 +- .../midi/juce_MidiMessageSequence.cpp | 21 +- .../midi/juce_MidiMessageSequence.h | 11 +- .../juce_audio_basics/midi/juce_MidiRPN.cpp | 4 +- .../juce_audio_basics/midi/juce_MidiRPN.h | 12 +- .../mpe/juce_MPEInstrument.cpp | 18 +- .../mpe/juce_MPEInstrument.h | 31 +- .../mpe/juce_MPEMessages.cpp | 4 +- .../juce_audio_basics/mpe/juce_MPENote.cpp | 4 +- .../juce_audio_basics/mpe/juce_MPENote.h | 12 +- .../mpe/juce_MPESynthesiser.h | 18 +- .../mpe/juce_MPESynthesiserBase.h | 20 +- .../mpe/juce_MPESynthesiserVoice.h | 8 +- .../juce_audio_basics/mpe/juce_MPEValue.cpp | 4 +- .../juce_audio_basics/mpe/juce_MPEValue.h | 4 +- .../juce_audio_basics/mpe/juce_MPEZone.cpp | 6 +- .../juce_audio_basics/mpe/juce_MPEZone.h | 2 +- .../mpe/juce_MPEZoneLayout.cpp | 8 - .../mpe/juce_MPEZoneLayout.h | 11 +- .../sources/juce_BufferingAudioSource.h | 2 +- .../audio_io/juce_AudioDeviceManager.cpp | 19 +- .../audio_io/juce_AudioDeviceManager.h | 9 +- .../juce_audio_devices/juce_audio_devices.cpp | 11 +- .../juce_audio_devices/juce_audio_devices.h | 6 +- .../native/juce_android_Audio.cpp | 52 ++- .../native/juce_android_OpenSL.cpp | 42 +- .../native/juce_ios_Audio.cpp | 49 ++- .../native/juce_mac_CoreAudio.cpp | 102 +++-- .../native/juce_win32_ASIO.cpp | 63 +-- .../native/juce_win32_WASAPI.cpp | 5 + .../flac/libFLAC/include/private/metadata.h | 2 +- .../codecs/juce_CoreAudioFormat.cpp | 27 +- .../codecs/juce_QuickTimeAudioFormat.cpp | 4 - .../juce_audio_formats/juce_audio_formats.cpp | 14 +- .../juce_audio_formats/juce_audio_formats.h | 4 +- .../juce_AudioUnitPluginFormat.mm | 5 - .../format_types/juce_LADSPAPluginFormat.cpp | 1 - .../format_types/juce_VST3Common.h | 78 ++-- .../format_types/juce_VST3Headers.h | 8 - .../format_types/juce_VST3PluginFormat.cpp | 14 +- .../format_types/juce_VSTPluginFormat.cpp | 15 +- .../juce_audio_processors.cpp | 7 +- .../juce_audio_processors.h | 6 +- .../processors/juce_AudioChannelSet.cpp | 30 +- .../processors/juce_AudioChannelSet.h | 4 +- .../processors/juce_AudioProcessor.cpp | 26 +- .../processors/juce_AudioProcessor.h | 24 +- .../processors/juce_AudioProcessorGraph.cpp | 6 - .../processors/juce_AudioProcessorGraph.h | 2 - .../juce_AudioProcessorParameters.cpp | 31 +- .../juce_AudioProcessorValueTreeState.cpp | 12 +- .../juce_core/containers/juce_HashMap.h | 7 + source/modules/juce_core/files/juce_File.cpp | 84 +++- source/modules/juce_core/files/juce_File.h | 2 +- .../juce_core/files/juce_WildcardFileFilter.h | 4 +- .../juce_core/javascript/juce_Javascript.cpp | 65 ++- source/modules/juce_core/juce_core.cpp | 13 +- source/modules/juce_core/juce_core.h | 31 +- .../juce_core/maths/juce_BigInteger.cpp | 2 +- .../juce_core/maths/juce_MathsFunctions.h | 16 +- .../maths/juce_StatisticsAccumulator.h | 145 +++++++ source/modules/juce_core/memory/juce_Atomic.h | 2 +- .../modules/juce_core/memory/juce_ByteOrder.h | 2 +- .../memory/juce_OptionalScopedPointer.h | 6 + .../misc/juce_RuntimePermissions.cpp | 48 +++ .../juce_core/misc/juce_RuntimePermissions.h | 131 ++++++ .../java/AndroidRuntimePermissions.java | 14 + .../native/java/JuceAppActivity.java | 168 +++++++- .../native/juce_BasicNativeHeaders.h | 5 - .../juce_core/native/juce_android_Files.cpp | 18 +- .../native/juce_android_JNIHelpers.h | 11 +- .../juce_android_RuntimePermissions.cpp | 90 ++++ .../juce_core/native/juce_android_Threads.cpp | 15 +- .../native/juce_linux_CommonFile.cpp | 2 +- .../native/juce_linux_SystemStats.cpp | 3 + .../juce_core/native/juce_mac_Strings.mm | 10 +- .../juce_core/native/juce_mac_SystemStats.mm | 20 +- .../juce_core/native/juce_posix_SharedCode.h | 68 ++- .../juce_core/native/juce_win32_Registry.cpp | 23 +- .../native/juce_win32_SystemStats.cpp | 10 +- .../juce_core/native/juce_win32_Threads.cpp | 3 +- .../modules/juce_core/network/juce_Socket.cpp | 84 ++-- .../juce_core/system/juce_PlatformDefs.h | 15 +- .../juce_core/system/juce_SystemStats.cpp | 8 +- .../juce_core/system/juce_SystemStats.h | 17 +- .../juce_core/system/juce_TargetPlatform.h | 7 +- .../juce_core/text/juce_CharPointer_UTF8.h | 3 + .../modules/juce_core/threads/juce_Process.h | 9 + .../modules/juce_core/threads/juce_Thread.cpp | 3 +- .../modules/juce_core/threads/juce_Thread.h | 9 +- source/modules/juce_core/time/juce_Time.cpp | 406 ++++++++++++++---- source/modules/juce_core/time/juce_Time.h | 123 +++--- source/modules/juce_core/zip/juce_ZipFile.cpp | 82 ++-- source/modules/juce_core/zip/juce_ZipFile.h | 2 +- .../juce_data_structures.h | 2 +- .../broadcasters/juce_AsyncUpdater.cpp | 12 +- source/modules/juce_events/juce_events.cpp | 10 +- source/modules/juce_events/juce_events.h | 6 +- .../native/juce_osx_MessageQueue.h | 20 +- .../native/juce_win32_Messaging.cpp | 108 +++-- .../modules/juce_events/timers/juce_Timer.cpp | 10 + .../juce_graphics/colour/juce_PixelFormats.h | 2 +- .../juce_graphics/geometry/juce_Line.h | 3 + .../geometry/juce_PathStrokeType.cpp | 50 ++- .../juce_graphics/geometry/juce_Point.h | 16 +- .../juce_graphics/geometry/juce_Rectangle.h | 13 +- .../geometry/juce_RectangleList.h | 2 +- .../image_formats/jpglib/jconfig.h | 4 +- .../juce_graphics/images/juce_Image.cpp | 15 +- .../modules/juce_graphics/juce_graphics.cpp | 11 +- source/modules/juce_graphics/juce_graphics.h | 9 +- .../native/juce_mac_CoreGraphicsContext.h | 2 +- .../juce_graphics/native/juce_mac_Fonts.mm | 81 ++-- .../juce_win32_DirectWriteTypeLayout.cpp | 2 +- .../native/juce_win32_DirectWriteTypeface.cpp | 2 +- .../juce_gui_basics/buttons/juce_Button.cpp | 7 + .../juce_gui_basics/buttons/juce_Button.h | 2 +- .../components/juce_Component.cpp | 57 ++- .../components/juce_Component.h | 8 +- .../components/juce_Desktop.cpp | 10 +- .../juce_gui_basics/components/juce_Desktop.h | 7 + .../drawables/juce_DrawableShape.cpp | 18 +- .../drawables/juce_DrawableShape.h | 7 + .../drawables/juce_SVGParser.cpp | 290 +++++++++---- .../filebrowser/juce_FileBrowserComponent.cpp | 2 +- .../filebrowser/juce_FileChooser.cpp | 9 +- .../filebrowser/juce_FileChooser.h | 38 +- .../juce_gui_basics/juce_gui_basics.cpp | 14 +- .../modules/juce_gui_basics/juce_gui_basics.h | 4 +- .../layout/juce_AnimatedPositionBehaviours.h | 2 +- .../layout/juce_ComponentBuilder.cpp | 2 +- .../layout/juce_ComponentBuilder.h | 6 +- .../juce_gui_basics/layout/juce_Viewport.cpp | 31 +- .../juce_gui_basics/layout/juce_Viewport.h | 2 +- .../juce_gui_basics/menus/juce_PopupMenu.cpp | 14 +- .../juce_gui_basics/menus/juce_PopupMenu.h | 15 +- .../misc/juce_BubbleComponent.cpp | 28 +- .../misc/juce_BubbleComponent.h | 11 +- .../juce_gui_basics/mouse/juce_MouseEvent.cpp | 7 +- .../juce_gui_basics/mouse/juce_MouseEvent.h | 30 +- .../mouse/juce_MouseInputSource.cpp | 2 +- .../native/juce_android_FileChooser.cpp | 1 + .../native/juce_android_Windowing.cpp | 107 ++++- .../native/juce_ios_UIViewComponentPeer.mm | 33 +- .../native/juce_ios_Windowing.mm | 108 +++-- .../native/juce_linux_FileChooser.cpp | 1 + .../native/juce_linux_Windowing.cpp | 153 +++---- .../native/juce_mac_FileChooser.mm | 5 + .../native/juce_mac_NSViewComponentPeer.mm | 218 +++++----- .../native/juce_mac_Windowing.mm | 68 ++- .../native/juce_win32_FileChooser.cpp | 3 +- .../native/juce_win32_Windowing.cpp | 23 +- .../juce_gui_basics/widgets/juce_ComboBox.cpp | 2 +- .../juce_gui_basics/widgets/juce_Label.cpp | 3 +- .../juce_gui_basics/widgets/juce_ListBox.cpp | 2 +- .../juce_gui_basics/widgets/juce_Slider.cpp | 73 ++-- .../juce_gui_basics/widgets/juce_Slider.h | 41 +- .../widgets/juce_TableHeaderComponent.cpp | 6 +- .../widgets/juce_TableListBox.cpp | 24 +- .../juce_gui_basics/widgets/juce_TextEditor.h | 4 +- .../widgets/juce_ToolbarItemComponent.cpp | 2 +- .../windows/juce_DialogWindow.cpp | 12 +- .../windows/juce_DialogWindow.h | 6 + .../windows/juce_ResizableWindow.cpp | 18 +- .../windows/juce_ResizableWindow.h | 1 + .../code_editor/juce_XMLCodeTokeniser.cpp | 2 +- .../modules/juce_gui_extra/juce_gui_extra.cpp | 16 +- .../modules/juce_gui_extra/juce_gui_extra.h | 4 +- .../juce_mac_CarbonViewWrapperComponent.h | 25 +- .../native/juce_mac_WebBrowserComponent.mm | 4 + source/utils/CarlaSemUtils.hpp | 6 +- 180 files changed, 3455 insertions(+), 1673 deletions(-) create mode 100644 source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.cpp create mode 100644 source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h create mode 100644 source/modules/juce_core/maths/juce_StatisticsAccumulator.h create mode 100644 source/modules/juce_core/misc/juce_RuntimePermissions.cpp create mode 100644 source/modules/juce_core/misc/juce_RuntimePermissions.h create mode 100644 source/modules/juce_core/native/java/AndroidRuntimePermissions.java create mode 100644 source/modules/juce_core/native/juce_android_RuntimePermissions.cpp diff --git a/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp b/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp index f6548ce64..881965e25 100644 --- a/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp +++ b/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp @@ -29,17 +29,6 @@ namespace FloatVectorHelpers #define JUCE_INCREMENT_DEST dest += (16 / sizeof (*dest)); #if JUCE_USE_SSE_INTRINSICS - static bool sse2Present = false; - - static bool isSSE2Available() noexcept - { - if (sse2Present) - return true; - - sse2Present = SystemStats::hasSSE2(); - return sse2Present; - } - inline static bool isAligned (const void* p) noexcept { return (((pointer_sized_int) p) & 15) == 0; @@ -113,7 +102,6 @@ namespace FloatVectorHelpers #define JUCE_BEGIN_VEC_OP \ typedef FloatVectorHelpers::ModeType::Mode Mode; \ - if (FloatVectorHelpers::isSSE2Available()) \ { \ const int numLongOps = num / Mode::numParallel; @@ -372,11 +360,7 @@ namespace FloatVectorHelpers { int numLongOps = num / Mode::numParallel; - #if JUCE_USE_SSE_INTRINSICS - if (numLongOps > 1 && isSSE2Available()) - #else if (numLongOps > 1) - #endif { ParallelType val; @@ -446,11 +430,7 @@ namespace FloatVectorHelpers { int numLongOps = num / Mode::numParallel; - #if JUCE_USE_SSE_INTRINSICS - if (numLongOps > 1 && isSSE2Available()) - #else if (numLongOps > 1) - #endif { ParallelType mn, mx; @@ -1002,12 +982,19 @@ double JUCE_CALLTYPE FloatVectorOperations::findMaximum (const double* src, int void JUCE_CALLTYPE FloatVectorOperations::enableFlushToZeroMode (bool shouldEnable) noexcept { #if JUCE_USE_SSE_INTRINSICS - if (FloatVectorHelpers::isSSE2Available()) - _MM_SET_FLUSH_ZERO_MODE (shouldEnable ? _MM_FLUSH_ZERO_ON : _MM_FLUSH_ZERO_OFF); + _MM_SET_FLUSH_ZERO_MODE (shouldEnable ? _MM_FLUSH_ZERO_ON : _MM_FLUSH_ZERO_OFF); #endif ignoreUnused (shouldEnable); } +void JUCE_CALLTYPE FloatVectorOperations::disableDenormalisedNumberSupport() noexcept +{ + #if JUCE_USE_SSE_INTRINSICS + const int mxcsr = _mm_getcsr(); + _mm_setcsr (mxcsr | 0x8040); // add the DAZ and FZ bits + #endif +} + //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS diff --git a/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h b/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h index 0c85e60f4..f1a8d13b4 100644 --- a/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h +++ b/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h @@ -198,6 +198,12 @@ public: Effectively, this is a wrapper around a call to _MM_SET_FLUSH_ZERO_MODE */ static void JUCE_CALLTYPE enableFlushToZeroMode (bool shouldEnable) noexcept; + + /** On Intel CPUs, this method enables the SSE flush-to-zero and denormalised-are-zero modes. + This effectively sets the DAZ and FZ bits of the MXCSR register. It's a convenient thing to + call before audio processing code where you really want to avoid denormalisation performance hits. + */ + static void JUCE_CALLTYPE disableDenormalisedNumberSupport() noexcept; }; diff --git a/source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.cpp b/source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.cpp new file mode 100644 index 000000000..ec91ac15f --- /dev/null +++ b/source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.cpp @@ -0,0 +1,63 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI 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. + + ============================================================================== +*/ + + +struct CatmullRomAlgorithm +{ + static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept + { + const float y0 = inputs[3]; + const float y1 = inputs[2]; + const float y2 = inputs[1]; + const float y3 = inputs[0]; + + const float halfY0 = 0.5f * y0; + const float halfY3 = 0.5f * y3; + + return y1 + offset * ((0.5f * y2 - halfY0) + + (offset * (((y0 + 2.0f * y2) - (halfY3 + 2.5f * y1)) + + (offset * ((halfY3 + 1.5f * y1) - (halfY0 + 1.5f * y2)))))); + } +}; + +CatmullRomInterpolator::CatmullRomInterpolator() noexcept { reset(); } +CatmullRomInterpolator::~CatmullRomInterpolator() noexcept {} + +void CatmullRomInterpolator::reset() noexcept +{ + subSamplePos = 1.0; + + for (int i = 0; i < numElementsInArray (lastInputSamples); ++i) + lastInputSamples[i] = 0; +} + +int CatmullRomInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept +{ + return interpolate (lastInputSamples, subSamplePos, actualRatio, in, out, numOut); +} + +int CatmullRomInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept +{ + return interpolateAdding (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain); +} diff --git a/source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h b/source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h new file mode 100644 index 000000000..466c2278d --- /dev/null +++ b/source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h @@ -0,0 +1,89 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI 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. + + ============================================================================== +*/ + + +/** + Interpolator for resampling a stream of floats using Catmull-Rom interpolation. + + Note that the resampler is stateful, so when there's a break in the continuity + of the input stream you're feeding it, you should call reset() before feeding + it any new data. And like with any other stateful filter, if you're resampling + multiple channels, make sure each one uses its own CatmullRomInterpolator + object. + + @see LagrangeInterpolator +*/ +class JUCE_API CatmullRomInterpolator +{ +public: + CatmullRomInterpolator() noexcept; + ~CatmullRomInterpolator() noexcept; + + /** Resets the state of the interpolator. + Call this when there's a break in the continuity of the input data stream. + */ + void reset() noexcept; + + /** Resamples a stream of samples. + + @param speedRatio the number of input samples to use for each output sample + @param inputSamples the source data to read from. This must contain at + least (speedRatio * numOutputSamplesToProduce) samples. + @param outputSamples the buffer to write the results into + @param numOutputSamplesToProduce the number of output samples that should be created + + @returns the actual number of input samples that were used + */ + int process (double speedRatio, + const float* inputSamples, + float* outputSamples, + int numOutputSamplesToProduce) noexcept; + + /** Resamples a stream of samples, adding the results to the output data + with a gain. + + @param speedRatio the number of input samples to use for each output sample + @param inputSamples the source data to read from. This must contain at + least (speedRatio * numOutputSamplesToProduce) samples. + @param outputSamples the buffer to write the results to - the result values will be added + to any pre-existing data in this buffer after being multiplied by + the gain factor + @param numOutputSamplesToProduce the number of output samples that should be created + @param gain a gain factor to multiply the resulting samples by before + adding them to the destination buffer + + @returns the actual number of input samples that were used + */ + int processAdding (double speedRatio, + const float* inputSamples, + float* outputSamples, + int numOutputSamplesToProduce, + float gain) noexcept; + +private: + float lastInputSamples[5]; + double subSamplePos; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CatmullRomInterpolator) +}; diff --git a/source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp b/source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp index aa4eccc0d..03e568025 100644 --- a/source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp +++ b/source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp @@ -22,185 +22,179 @@ ============================================================================== */ -namespace LagrangeHelpers +namespace { - template - struct ResampleHelper - { - static forcedinline void calc (float& a, float b) { a *= b * (1.0f / k); } - }; - - template<> - struct ResampleHelper <0> - { - static forcedinline void calc (float&, float) {} - }; - - template - static forcedinline float calcCoefficient (float input, const float offset) noexcept + static forcedinline void pushInterpolationSample (float* lastInputSamples, const float newValue) noexcept { - ResampleHelper <0 - k>::calc (input, -2.0f - offset); - ResampleHelper <1 - k>::calc (input, -1.0f - offset); - ResampleHelper <2 - k>::calc (input, 0.0f - offset); - ResampleHelper <3 - k>::calc (input, 1.0f - offset); - ResampleHelper <4 - k>::calc (input, 2.0f - offset); - return input; + lastInputSamples[4] = lastInputSamples[3]; + lastInputSamples[3] = lastInputSamples[2]; + lastInputSamples[2] = lastInputSamples[1]; + lastInputSamples[1] = lastInputSamples[0]; + lastInputSamples[0] = newValue; } - static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept - { - return calcCoefficient<0> (inputs[4], offset) - + calcCoefficient<1> (inputs[3], offset) - + calcCoefficient<2> (inputs[2], offset) - + calcCoefficient<3> (inputs[1], offset) - + calcCoefficient<4> (inputs[0], offset); - } - - static forcedinline void push (float* inputs, const float newValue) noexcept - { - inputs[4] = inputs[3]; - inputs[3] = inputs[2]; - inputs[2] = inputs[1]; - inputs[1] = inputs[0]; - inputs[0] = newValue; - } -} - -//============================================================================== -LagrangeInterpolator::LagrangeInterpolator() { reset(); } -LagrangeInterpolator::~LagrangeInterpolator() {} - -void LagrangeInterpolator::reset() noexcept -{ - subSamplePos = 1.0; - - for (int i = 0; i < numElementsInArray (lastInputSamples); ++i) - lastInputSamples[i] = 0; -} - -int LagrangeInterpolator::process (const double actualRatio, const float* in, - float* out, const int numOut) noexcept -{ - if (actualRatio == 1.0) + static forcedinline void pushInterpolationSamples (float* lastInputSamples, const float* input, int numOut) noexcept { - memcpy (out, in, (size_t) numOut * sizeof (float)); - - if (numOut >= 4) + if (numOut >= 5) { - const float* end = in + numOut; - - for (int i = 0; i < 4; ++i) - lastInputSamples[i] = *--end; + for (int i = 0; i < 5; ++i) + lastInputSamples[i] = input[--numOut]; } else { for (int i = 0; i < numOut; ++i) - LagrangeHelpers::push (lastInputSamples, in[i]); + pushInterpolationSample (lastInputSamples, input[i]); } - - return numOut; } - const float* const originalIn = in; - double pos = subSamplePos; - - if (actualRatio < 1.0) + template + static int interpolate (float* lastInputSamples, double& subSamplePos, const double actualRatio, + const float* in, float* out, const int numOut) noexcept { - for (int i = numOut; --i >= 0;) + if (actualRatio == 1.0) { - if (pos >= 1.0) + memcpy (out, in, (size_t) numOut * sizeof (float)); + pushInterpolationSamples (lastInputSamples, in, numOut); + return numOut; + } + + const float* const originalIn = in; + double pos = subSamplePos; + + if (actualRatio < 1.0) + { + for (int i = numOut; --i >= 0;) { - LagrangeHelpers::push (lastInputSamples, *in++); - pos -= 1.0; + if (pos >= 1.0) + { + pushInterpolationSample (lastInputSamples, *in++); + pos -= 1.0; + } + + *out++ = InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); + pos += actualRatio; } - - *out++ = LagrangeHelpers::valueAtOffset (lastInputSamples, (float) pos); - pos += actualRatio; } - } - else - { - for (int i = numOut; --i >= 0;) + else { - while (pos < actualRatio) + for (int i = numOut; --i >= 0;) { - LagrangeHelpers::push (lastInputSamples, *in++); - pos += 1.0; + while (pos < actualRatio) + { + pushInterpolationSample (lastInputSamples, *in++); + pos += 1.0; + } + + pos -= actualRatio; + *out++ = InterpolatorType::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos)); } - - pos -= actualRatio; - *out++ = LagrangeHelpers::valueAtOffset (lastInputSamples, 1.0f - (float) pos); } - } - subSamplePos = pos; - return (int) (in - originalIn); -} + subSamplePos = pos; + return (int) (in - originalIn); + } -int LagrangeInterpolator::processAdding (const double actualRatio, const float* in, - float* out, const int numOut, const float gain) noexcept -{ - if (actualRatio == 1.0) + template + static int interpolateAdding (float* lastInputSamples, double& subSamplePos, const double actualRatio, + const float* in, float* out, const int numOut, const float gain) noexcept { - if (gain != 1.0f) - { - for (int i = 0; i < numOut; ++i) - out[i] += in[i] * gain; - } - else + if (actualRatio == 1.0) { - for (int i = 0; i < numOut; ++i) - out[i] += in[i]; + FloatVectorOperations::addWithMultiply (out, in, gain, numOut); + pushInterpolationSamples (lastInputSamples, in, numOut); + return numOut; } - if (numOut >= 4) - { - const float* end = in + numOut; + const float* const originalIn = in; + double pos = subSamplePos; - for (int i = 0; i < 4; ++i) - lastInputSamples[i] = *--end; + if (actualRatio < 1.0) + { + for (int i = numOut; --i >= 0;) + { + if (pos >= 1.0) + { + pushInterpolationSample (lastInputSamples, *in++); + pos -= 1.0; + } + + *out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); + pos += actualRatio; + } } else { - for (int i = 0; i < numOut; ++i) - LagrangeHelpers::push (lastInputSamples, in[i]); + for (int i = numOut; --i >= 0;) + { + while (pos < actualRatio) + { + pushInterpolationSample (lastInputSamples, *in++); + pos += 1.0; + } + + pos -= actualRatio; + *out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos)); + } } - return numOut; + subSamplePos = pos; + return (int) (in - originalIn); } +} - const float* const originalIn = in; - double pos = subSamplePos; +//============================================================================== +template +struct LagrangeResampleHelper +{ + static forcedinline void calc (float& a, float b) noexcept { a *= b * (1.0f / k); } +}; - if (actualRatio < 1.0) - { - for (int i = numOut; --i >= 0;) - { - if (pos >= 1.0) - { - LagrangeHelpers::push (lastInputSamples, *in++); - pos -= 1.0; - } +template<> +struct LagrangeResampleHelper<0> +{ + static forcedinline void calc (float&, float) noexcept {} +}; - *out++ += gain * LagrangeHelpers::valueAtOffset (lastInputSamples, (float) pos); - pos += actualRatio; - } - } - else +struct LagrangeAlgorithm +{ + static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept { - for (int i = numOut; --i >= 0;) - { - while (pos < actualRatio) - { - LagrangeHelpers::push (lastInputSamples, *in++); - pos += 1.0; - } + return calcCoefficient<0> (inputs[4], offset) + + calcCoefficient<1> (inputs[3], offset) + + calcCoefficient<2> (inputs[2], offset) + + calcCoefficient<3> (inputs[1], offset) + + calcCoefficient<4> (inputs[0], offset); + } - pos -= actualRatio; - *out++ += gain * LagrangeHelpers::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos)); - } + template + static forcedinline float calcCoefficient (float input, const float offset) noexcept + { + LagrangeResampleHelper<0 - k>::calc (input, -2.0f - offset); + LagrangeResampleHelper<1 - k>::calc (input, -1.0f - offset); + LagrangeResampleHelper<2 - k>::calc (input, 0.0f - offset); + LagrangeResampleHelper<3 - k>::calc (input, 1.0f - offset); + LagrangeResampleHelper<4 - k>::calc (input, 2.0f - offset); + return input; } +}; + +LagrangeInterpolator::LagrangeInterpolator() noexcept { reset(); } +LagrangeInterpolator::~LagrangeInterpolator() noexcept {} - subSamplePos = pos; - return (int) (in - originalIn); +void LagrangeInterpolator::reset() noexcept +{ + subSamplePos = 1.0; + + for (int i = 0; i < numElementsInArray (lastInputSamples); ++i) + lastInputSamples[i] = 0; +} + +int LagrangeInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept +{ + return interpolate (lastInputSamples, subSamplePos, actualRatio, in, out, numOut); +} + +int LagrangeInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept +{ + return interpolateAdding (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain); } diff --git a/source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h b/source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h index c3f7fd298..d3231556e 100644 --- a/source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h +++ b/source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h @@ -22,11 +22,7 @@ ============================================================================== */ -#ifndef JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED -#define JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED - -//============================================================================== /** Interpolator for resampling a stream of floats using 4-point lagrange interpolation. @@ -35,12 +31,14 @@ it any new data. And like with any other stateful filter, if you're resampling multiple channels, make sure each one uses its own LagrangeInterpolator object. + + @see CatmullRomInterpolator */ class JUCE_API LagrangeInterpolator { public: - LagrangeInterpolator(); - ~LagrangeInterpolator(); + LagrangeInterpolator() noexcept; + ~LagrangeInterpolator() noexcept; /** Resets the state of the interpolator. Call this when there's a break in the continuity of the input data stream. @@ -89,6 +87,3 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LagrangeInterpolator) }; - - -#endif // JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED diff --git a/source/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h b/source/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h index 44fa1e832..818ddb8e5 100644 --- a/source/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h +++ b/source/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h @@ -49,7 +49,7 @@ public: { } - //========================================================================== + //============================================================================== /** Reset to a new sample rate and ramp length. */ void reset (double sampleRate, double rampLengthInSeconds) noexcept { @@ -59,7 +59,7 @@ public: countdown = 0; } - //========================================================================== + //============================================================================== /** Set a new target value. */ void setValue (FloatType newValue) noexcept { @@ -75,7 +75,7 @@ public: } } - //========================================================================== + //============================================================================== /** Compute the next value. */ FloatType getNextValue() noexcept { @@ -88,7 +88,7 @@ public: } private: - //========================================================================== + //============================================================================== FloatType currentValue, target, step; int countdown, stepsToTarget; }; diff --git a/source/modules/juce_audio_basics/juce_audio_basics.cpp b/source/modules/juce_audio_basics/juce_audio_basics.cpp index 5af080848..6f37f2dfb 100644 --- a/source/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/source/modules/juce_audio_basics/juce_audio_basics.cpp @@ -58,9 +58,7 @@ #endif #if (JUCE_MAC || JUCE_IOS) && JUCE_USE_VDSP_FRAMEWORK - #define Point CarbonDummyPointName // (workaround to avoid definition of "Point" by old Carbon headers) #include - #undef Point #else #undef JUCE_USE_VDSP_FRAMEWORK #endif @@ -81,6 +79,7 @@ namespace juce #include "effects/juce_IIRFilter.cpp" #include "effects/juce_IIRFilterOld.cpp" #include "effects/juce_LagrangeInterpolator.cpp" +#include "effects/juce_CatmullRomInterpolator.cpp" #include "effects/juce_FFT.cpp" #include "midi/juce_MidiBuffer.cpp" #include "midi/juce_MidiFile.cpp" diff --git a/source/modules/juce_audio_basics/juce_audio_basics.h b/source/modules/juce_audio_basics/juce_audio_basics.h index 69c398c42..09b4760fb 100644 --- a/source/modules/juce_audio_basics/juce_audio_basics.h +++ b/source/modules/juce_audio_basics/juce_audio_basics.h @@ -27,7 +27,7 @@ #include "../juce_core/juce_core.h" -//============================================================================= +//============================================================================== namespace juce { @@ -41,6 +41,7 @@ namespace juce #include "effects/juce_IIRFilter.h" #include "effects/juce_IIRFilterOld.h" #include "effects/juce_LagrangeInterpolator.h" +#include "effects/juce_CatmullRomInterpolator.h" #include "effects/juce_FFT.h" #include "effects/juce_LinearSmoothedValue.h" #include "effects/juce_Reverb.h" diff --git a/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp b/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp index 749fe654c..a3867a289 100644 --- a/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp +++ b/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp @@ -371,7 +371,7 @@ int MidiMessage::getNoteNumber() const noexcept void MidiMessage::setNoteNumber (const int newNoteNumber) noexcept { - if (isNoteOnOrOff()) + if (isNoteOnOrOff() || isAftertouch()) getData()[1] = (uint8) (newNoteNumber & 127); } diff --git a/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp b/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp index dcac30e77..7b2ab85ca 100644 --- a/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp +++ b/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp @@ -156,23 +156,34 @@ struct MidiMessageSequenceSorter } }; +void MidiMessageSequence::addSequence (const MidiMessageSequence& other, double timeAdjustment) +{ + for (int i = 0; i < other.list.size(); ++i) + { + const MidiMessage& m = other.list.getUnchecked(i)->message; + + MidiEventHolder* const newOne = new MidiEventHolder (m); + newOne->message.addToTimeStamp (timeAdjustment); + list.add (newOne); + } + + sort(); +} + void MidiMessageSequence::addSequence (const MidiMessageSequence& other, double timeAdjustment, double firstAllowableTime, double endOfAllowableDestTimes) { - firstAllowableTime -= timeAdjustment; - endOfAllowableDestTimes -= timeAdjustment; - for (int i = 0; i < other.list.size(); ++i) { const MidiMessage& m = other.list.getUnchecked(i)->message; - const double t = m.getTimeStamp(); + const double t = m.getTimeStamp() + timeAdjustment; if (t >= firstAllowableTime && t < endOfAllowableDestTimes) { MidiEventHolder* const newOne = new MidiEventHolder (m); - newOne->message.setTimeStamp (timeAdjustment + t); + newOne->message.setTimeStamp (t); list.add (newOne); } diff --git a/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h b/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h index 5d2f977a5..2f55e2dab 100644 --- a/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h +++ b/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h @@ -160,7 +160,6 @@ public: void deleteEvent (int index, bool deleteMatchingNoteUp); /** Merges another sequence into this one. - Remember to call updateMatchedPairs() after using this method. @param other the sequence to add from @@ -178,6 +177,16 @@ public: double firstAllowableDestTime, double endOfAllowableDestTimes); + /** Merges another sequence into this one. + Remember to call updateMatchedPairs() after using this method. + + @param other the sequence to add from + @param timeAdjustmentDelta an amount to add to the timestamps of the midi events + as they are read from the other sequence + */ + void addSequence (const MidiMessageSequence& other, + double timeAdjustmentDelta); + //============================================================================== /** Makes sure all the note-on and note-off pairs are up-to-date. diff --git a/source/modules/juce_audio_basics/midi/juce_MidiRPN.cpp b/source/modules/juce_audio_basics/midi/juce_MidiRPN.cpp index 7d7335e2c..1819624e3 100644 --- a/source/modules/juce_audio_basics/midi/juce_MidiRPN.cpp +++ b/source/modules/juce_audio_basics/midi/juce_MidiRPN.cpp @@ -331,7 +331,7 @@ public: } private: - //========================================================================== + //============================================================================== void expectContainsRPN (const MidiBuffer& midiBuffer, int channel, int parameterNumber, @@ -343,7 +343,7 @@ private: expectContainsRPN (midiBuffer, expected); } - //========================================================================== + //============================================================================== void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected) { MidiBuffer::Iterator iter (midiBuffer); diff --git a/source/modules/juce_audio_basics/midi/juce_MidiRPN.h b/source/modules/juce_audio_basics/midi/juce_MidiRPN.h index 9199860f1..e8f7376a4 100644 --- a/source/modules/juce_audio_basics/midi/juce_MidiRPN.h +++ b/source/modules/juce_audio_basics/midi/juce_MidiRPN.h @@ -26,7 +26,7 @@ #define JUCE_MIDIRPNDETECTOR_H_INCLUDED -//========================================================================== +//============================================================================== /** Represents a MIDI RPN (registered parameter number) or NRPN (non-registered parameter number) message. */ @@ -77,7 +77,7 @@ public: */ void reset() noexcept; - //========================================================================== + //============================================================================== /** Takes the next in a stream of incoming MIDI CC messages and returns true if it forms the last of a sequence that makes an RPN or NPRN. @@ -91,7 +91,7 @@ public: MidiRPNMessage& result) noexcept; private: - //========================================================================== + //============================================================================== struct ChannelState { ChannelState() noexcept; @@ -104,7 +104,7 @@ private: bool isNRPN; }; - //========================================================================== + //============================================================================== ChannelState states[16]; JUCE_LEAK_DETECTOR (MidiRPNDetector) @@ -120,11 +120,11 @@ private: class JUCE_API MidiRPNGenerator { public: - //========================================================================== + //============================================================================== /** Generates a MIDI sequence representing the given RPN or NRPN message. */ static MidiBuffer generate (MidiRPNMessage message); - //========================================================================== + //============================================================================== /** Generates a MIDI sequence representing an RPN or NRPN message with the given parameters. diff --git a/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp b/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp index cabee295d..9c88fc7c1 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp +++ b/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp @@ -135,14 +135,6 @@ void MPEInstrument::removeListener (Listener* const listenerToRemove) noexcept listeners.remove (listenerToRemove); } -MPEInstrument::Listener::Listener() -{ -} - -MPEInstrument::Listener::~Listener() -{ -} - //============================================================================== void MPEInstrument::processNextMidiEvent (const MidiMessage& message) { @@ -1976,7 +1968,7 @@ public: } private: - //========================================================================== + //============================================================================== /* This mock class is used for unit testing whether the methods of MPEInstrument are called correctly. */ @@ -2074,7 +2066,7 @@ private: ScopedPointer lastNoteFinished; private: - //====================================================================== + //============================================================================== void noteAdded (MPENote) override { noteAddedCallCounter++; } void notePressureChanged (MPENote) override { notePressureChangedCallCounter++; } @@ -2089,7 +2081,7 @@ private: } }; - //========================================================================== + //============================================================================== template class CustomInitialValuesTest : public MPEInstrument { @@ -2109,7 +2101,7 @@ private: } }; - //========================================================================== + //============================================================================== void expectNote (MPENote noteToTest, int noteOnVelocity7Bit, int pressure7Bit, @@ -2141,7 +2133,7 @@ private: expect (std::fabs (expected - actual) < maxAbsoluteError); } - //========================================================================== + //============================================================================== MPEZoneLayout testLayout; }; diff --git a/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.h b/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.h index db7257098..ea895baab 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.h +++ b/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.h @@ -68,7 +68,7 @@ public: /** Destructor. */ virtual ~MPEInstrument(); - //========================================================================== + //============================================================================== /** Returns the current zone layout of the instrument. This happens by value, to enforce thread-safety and class invariants. @@ -98,7 +98,7 @@ public: */ bool isMasterChannel (int midiChannel) const noexcept; - //========================================================================== + //============================================================================== /** The MPE note tracking mode. In case there is more than one note playing simultaneously on the same MIDI channel, this determines which of these notes will be modulated by an incoming MPE message on that channel @@ -123,7 +123,7 @@ public: /** Set the MPE tracking mode for the timbre dimension. */ void setTimbreTrackingMode (TrackingMode modeToUse); - //========================================================================== + //============================================================================== /** Process a MIDI message and trigger the appropriate method calls (noteOn, noteOff etc.) @@ -132,7 +132,7 @@ public: */ virtual void processNextMidiEvent (const MidiMessage& message); - //========================================================================== + //============================================================================== /** Request a note-on on the given channel, with the given initial note number and velocity. If the message arrives on a valid note channel, this will create a @@ -187,7 +187,7 @@ public: */ void releaseAllNotes(); - //========================================================================== + //============================================================================== /** Returns the number of MPE notes currently played by the instrument. */ @@ -221,7 +221,7 @@ public: */ MPENote getMostRecentNoteOtherThan (MPENote otherThanThisNote) const noexcept; - //========================================================================== + //============================================================================== /** Derive from this class to be informed about any changes in the expressive MIDI notes played by this instrument. @@ -230,14 +230,11 @@ public: Therefore you should never do heavy work such as graphics rendering etc. inside those callbacks. */ - class Listener + class JUCE_API Listener { public: - /** Constructor. */ - Listener(); - /** Destructor. */ - virtual ~Listener(); + virtual ~Listener() {} /** Implement this callback to be informed whenever a new expressive MIDI note is triggered. @@ -278,14 +275,14 @@ public: virtual void noteReleased (MPENote finishedNote) = 0; }; - //========================================================================== + //============================================================================== /** Adds a listener. */ - void addListener (Listener* const listenerToAdd) noexcept; + void addListener (Listener* listenerToAdd) noexcept; /** Removes a listener. */ - void removeListener (Listener* const listenerToRemove) noexcept; + void removeListener (Listener* listenerToRemove) noexcept; - //========================================================================== + //============================================================================== /** Puts the instrument into legacy mode. As a side effect, this will discard all currently playing notes, and call noteReleased for all of them. @@ -324,7 +321,7 @@ public: void setLegacyModePitchbendRange (int pitchbendRange); protected: - //========================================================================== + //============================================================================== /** This method defines what initial pitchbend value should be used for newly triggered notes. The default is to use the last pitchbend value that has been received on the same MIDI channel (or no pitchbend @@ -354,7 +351,7 @@ protected: MPEValue midiNoteOnVelocity) const; private: - //========================================================================== + //============================================================================== CriticalSection lock; Array notes; MPEZoneLayout zoneLayout; diff --git a/source/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp b/source/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp index 5102e5505..fcfe2f580 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp +++ b/source/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp @@ -162,7 +162,7 @@ public: } private: - //========================================================================== + //============================================================================== void testMidiBuffer (MidiBuffer& buffer, const uint8* expectedBytes, int expectedBytesSize) { uint8 actualBytes[128] = { 0 }; @@ -171,7 +171,7 @@ private: expectEquals (std::memcmp (actualBytes, expectedBytes, (std::size_t) expectedBytesSize), 0); } - //========================================================================== + //============================================================================== void extractRawBinaryData (const MidiBuffer& midiBuffer, const uint8* bufferToCopyTo, std::size_t maxBytes) { std::size_t pos = 0; diff --git a/source/modules/juce_audio_basics/mpe/juce_MPENote.cpp b/source/modules/juce_audio_basics/mpe/juce_MPENote.cpp index 80516cb0a..9b62cc892 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPENote.cpp +++ b/source/modules/juce_audio_basics/mpe/juce_MPENote.cpp @@ -103,7 +103,7 @@ class MPENoteTests : public UnitTest public: MPENoteTests() : UnitTest ("MPENote class") {} - //========================================================================== + //============================================================================== void runTest() override { beginTest ("getFrequencyInHertz"); @@ -116,7 +116,7 @@ public: } private: - //========================================================================== + //============================================================================== void expectEqualsWithinOneCent (double frequencyInHertzActual, double frequencyInHertzExpected) { diff --git a/source/modules/juce_audio_basics/mpe/juce_MPENote.h b/source/modules/juce_audio_basics/mpe/juce_MPENote.h index 9e979a41e..7887e5ae0 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPENote.h +++ b/source/modules/juce_audio_basics/mpe/juce_MPENote.h @@ -39,7 +39,7 @@ */ struct JUCE_API MPENote { - //========================================================================== + //============================================================================== enum KeyState { off = 0, @@ -48,7 +48,7 @@ struct JUCE_API MPENote keyDownAndSustained = 3 }; - //========================================================================== + //============================================================================== /** Constructor. @param midiChannel The MIDI channel of the note, between 2 and 16. @@ -88,7 +88,7 @@ struct JUCE_API MPENote /** Checks whether the MPE note is valid. */ bool isValid() const noexcept; - //========================================================================== + //============================================================================== // Invariants that define the note. /** A unique ID. Useful to distinguish the note from other simultaneously @@ -107,7 +107,7 @@ struct JUCE_API MPENote */ uint8 initialNote; - //========================================================================== + //============================================================================== // The five dimensions of continuous expressive control /** The velocity ("strike") of the note-on. @@ -146,7 +146,7 @@ struct JUCE_API MPENote */ MPEValue noteOffVelocity; - //========================================================================== + //============================================================================== /** Current effective pitchbend of the note in units of semitones, relative to initialNote. You should use this to compute the actual effective pitch of the note. This value is computed and set by an MPEInstrument to the @@ -163,7 +163,7 @@ struct JUCE_API MPENote */ KeyState keyState; - //========================================================================== + //============================================================================== /** Returns the current frequency of the note in Hertz. This is the a sum of the initialNote and the totalPitchbendInSemitones, converted to Hertz. */ diff --git a/source/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h b/source/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h index cbca499e8..151b21a52 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h +++ b/source/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h @@ -55,7 +55,7 @@ class JUCE_API MPESynthesiser : public MPESynthesiserBase { public: - //========================================================================== + //============================================================================== /** Constructor. You'll need to add some voices before it'll make any sound. @@ -75,7 +75,7 @@ public: /** Destructor. */ ~MPESynthesiser(); - //========================================================================== + //============================================================================== /** Deletes all voices. */ void clearVoices(); @@ -116,7 +116,7 @@ public: */ virtual void turnOffAllVoices (bool allowTailOff); - //========================================================================== + //============================================================================== /** If set to true, then the synth will try to take over an existing voice if it runs out and needs to play another note. @@ -128,7 +128,7 @@ public: /** Returns true if note-stealing is enabled. */ bool isVoiceStealingEnabled() const noexcept { return shouldStealVoices; } - //========================================================================== + //============================================================================== /** Tells the synthesiser what the sample rate is for the audio it's being used to render. This overrides the implementation in MPESynthesiserBase, to additionally @@ -137,7 +137,7 @@ public: */ void setCurrentPlaybackSampleRate (double newRate) override; - //========================================================================== + //============================================================================== /** Handle incoming MIDI events. This method will be called automatically according to the MIDI data passed @@ -238,7 +238,7 @@ protected: */ virtual void noteKeyStateChanged (MPENote changedNote) override; - //========================================================================== + //============================================================================== /** This will simply call renderNextBlock for each currently active voice and fill the buffer with the sum. Override this method if you need to do more work to render your audio. @@ -255,7 +255,7 @@ protected: int startSample, int numSamples) override; - //========================================================================== + //============================================================================== /** Searches through the voices to find one that's not currently playing, and which can play the given MPE note. @@ -298,11 +298,11 @@ protected: */ void stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff); - //========================================================================== + //============================================================================== OwnedArray voices; private: - //========================================================================== + //============================================================================== bool shouldStealVoices; CriticalSection voicesLock; diff --git a/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h b/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h index 7ea8abda8..898b28143 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h +++ b/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h @@ -47,7 +47,7 @@ struct JUCE_API MPESynthesiserBase : public MPEInstrument::Listener { public: - //========================================================================== + //============================================================================== /** Constructor. */ MPESynthesiserBase(); @@ -61,7 +61,7 @@ public: */ MPESynthesiserBase (MPEInstrument* instrument); - //========================================================================== + //============================================================================== /** Returns the synthesiser's internal MPE zone layout. This happens by value, to enforce thread-safety and class invariants. */ @@ -73,7 +73,7 @@ public: */ void setZoneLayout (MPEZoneLayout newLayout); - //========================================================================== + //============================================================================== /** Tells the synthesiser what the sample rate is for the audio it's being used to render. */ @@ -84,7 +84,7 @@ public: */ double getSampleRate() const noexcept { return sampleRate; } - //========================================================================== + //============================================================================== /** Creates the next block of audio output. Call this to make sound. This will chop up the AudioBuffer into subBlock @@ -99,7 +99,7 @@ public: int startSample, int numSamples); - //========================================================================== + //============================================================================== /** Handle incoming MIDI events (called from renderNextBlock). The default implementation provided here simply forwards everything @@ -113,7 +113,7 @@ public: */ virtual void handleMidiEvent (const MidiMessage&); - //========================================================================== + //============================================================================== /** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering. When rendering, the audio blocks that are passed into renderNextBlock() will be split up @@ -130,7 +130,7 @@ public: */ void setMinimumRenderingSubdivisionSize (int numSamples) noexcept; - //========================================================================== + //============================================================================== /** Puts the synthesiser into legacy mode. @param pitchbendRange The note pitchbend range in semitones to use when in legacy mode. @@ -160,7 +160,7 @@ public: void setLegacyModePitchbendRange (int pitchbendRange); protected: - //========================================================================== + //============================================================================== /** Implement this method to render your audio inside. @see renderNextBlock */ @@ -176,14 +176,14 @@ protected: int /*numSamples*/) {} protected: - //========================================================================== + //============================================================================== /** @internal */ ScopedPointer instrument; /** @internal */ CriticalSection renderAudioLock; private: - //========================================================================== + //============================================================================== double sampleRate; int minimumSubBlockSize; diff --git a/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h b/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h index 8fa3e1262..ce3149af2 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h +++ b/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h @@ -37,7 +37,7 @@ class JUCE_API MPESynthesiserVoice { public: - //======================================================================== + //============================================================================== /** Constructor. */ MPESynthesiserVoice(); @@ -160,7 +160,7 @@ public: bool wasStartedBefore (const MPESynthesiserVoice& other) const noexcept; protected: - //========================================================================== + //============================================================================== /** 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 @@ -175,12 +175,12 @@ protected: */ void clearCurrentNote() noexcept; - //========================================================================== + //============================================================================== double currentSampleRate; MPENote currentlyPlayingNote; private: - //========================================================================== + //============================================================================== friend class MPESynthesiser; uint32 noteStartTime; diff --git a/source/modules/juce_audio_basics/mpe/juce_MPEValue.cpp b/source/modules/juce_audio_basics/mpe/juce_MPEValue.cpp index c1981fa75..c6cc0efbe 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPEValue.cpp +++ b/source/modules/juce_audio_basics/mpe/juce_MPEValue.cpp @@ -144,7 +144,7 @@ public: } private: - //========================================================================== + //============================================================================== void expectValuesConsistent (MPEValue value, int expectedValueAs7BitInt, int expectedValueAs14BitInt, @@ -157,7 +157,7 @@ private: expectFloatWithinRelativeError (value.asUnsignedFloat(), expectedValueAsUnsignedFloat, 0.0001f); } - //========================================================================== + //============================================================================== void expectFloatWithinRelativeError (float actualValue, float expectedValue, float maxRelativeError) { const float maxAbsoluteError = jmax (1.0f, std::fabs (expectedValue)) * maxRelativeError; diff --git a/source/modules/juce_audio_basics/mpe/juce_MPEValue.h b/source/modules/juce_audio_basics/mpe/juce_MPEValue.h index e137c46e2..fc6304fca 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPEValue.h +++ b/source/modules/juce_audio_basics/mpe/juce_MPEValue.h @@ -37,7 +37,7 @@ class JUCE_API MPEValue { public: - //========================================================================== + //============================================================================== /** Default constructor. Constructs an MPEValue corresponding to the centre value. */ @@ -87,7 +87,7 @@ public: bool operator!= (const MPEValue& other) const noexcept; private: - //========================================================================== + //============================================================================== MPEValue (int normalisedValue); int normalisedValue; }; diff --git a/source/modules/juce_audio_basics/mpe/juce_MPEZone.cpp b/source/modules/juce_audio_basics/mpe/juce_MPEZone.cpp index e2f6030df..ec8e00fbf 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPEZone.cpp +++ b/source/modules/juce_audio_basics/mpe/juce_MPEZone.cpp @@ -144,7 +144,7 @@ bool MPEZone::truncateToFit (MPEZone other) noexcept return true; } -//========================================================================== +//============================================================================== bool MPEZone::operator== (const MPEZone& other) const noexcept { return masterChannel == other.masterChannel @@ -284,7 +284,7 @@ public: } private: - //========================================================================== + //============================================================================== void testOverlapsWith (int masterChannelFirst, int numNoteChannelsFirst, int masterChannelSecond, int numNoteChannelsSecond, bool expectedRetVal) @@ -296,7 +296,7 @@ private: expect (second.overlapsWith (first) == expectedRetVal); } - //========================================================================== + //============================================================================== void testTruncateToFit (int masterChannelFirst, int numNoteChannelsFirst, int masterChannelSecond, int numNoteChannelsSecond, bool expectedRetVal, diff --git a/source/modules/juce_audio_basics/mpe/juce_MPEZone.h b/source/modules/juce_audio_basics/mpe/juce_MPEZone.h index ebd1a2f64..d9d6c3ec8 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPEZone.h +++ b/source/modules/juce_audio_basics/mpe/juce_MPEZone.h @@ -127,7 +127,7 @@ struct JUCE_API MPEZone bool operator!= (const MPEZone& other) const noexcept; private: - //========================================================================== + //============================================================================== int masterChannel; int numNoteChannels; int perNotePitchbendRange; diff --git a/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp b/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp index 3a1af4de7..f105da975 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp +++ b/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp @@ -197,14 +197,6 @@ void MPEZoneLayout::removeListener (Listener* const listenerToRemove) noexcept listeners.remove (listenerToRemove); } -MPEZoneLayout::Listener::Listener() -{ -} - -MPEZoneLayout::Listener::~Listener() -{ -} - //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS diff --git a/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h b/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h index 021a07f61..f4a1cf2a1 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h +++ b/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h @@ -125,18 +125,15 @@ public: */ MPEZone* getZoneByNoteChannel (int midiChannel) const noexcept; - //========================================================================== + //============================================================================== /** Listener class. Derive from this class to allow your class to be notified about changes to the zone layout. */ class Listener { public: - /** Constructor. */ - Listener(); - /** Destructor. */ - virtual ~Listener(); + virtual ~Listener() {} /** Implement this callback to be notified about any changes to this MPEZoneLayout. Will be called whenever a zone is added, zones are @@ -145,7 +142,7 @@ public: virtual void zoneLayoutChanged (const MPEZoneLayout& layout) = 0; }; - //========================================================================== + //============================================================================== /** Adds a listener. */ void addListener (Listener* const listenerToAdd) noexcept; @@ -153,7 +150,7 @@ public: void removeListener (Listener* const listenerToRemove) noexcept; private: - //========================================================================== + //============================================================================== Array zones; MidiRPNDetector rpnDetector; ListenerList listeners; diff --git a/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h b/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h index 4d507d713..88116207c 100644 --- a/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h +++ b/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h @@ -46,7 +46,7 @@ public: @param source the input source to read from @param backgroundThread a background thread that will be used for the background read-ahead. This object must not be deleted - until after any BufferedAudioSources that are using it + until after any BufferingAudioSources that are using it have been deleted! @param deleteSourceWhenDeleted if true, then the input source object will be deleted when this object is deleted diff --git a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp index 4dc948bec..9c1e21a07 100644 --- a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp @@ -993,9 +993,9 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) class AudioSampleBufferSource : public PositionableAudioSource { public: - AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool ownBuffer) + AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool ownBuffer, bool playOnAllChannels) : buffer (audioBuffer, ownBuffer), - position (0), looping (false) + position (0), looping (false), playAcrossAllChannels (playOnAllChannels) {} //============================================================================== @@ -1029,8 +1029,11 @@ public: if (samplesToCopy > 0) { - const int maxInChannels = buffer->getNumChannels(); - const int maxOutChannels = jmin (bufferToFill.buffer->getNumChannels(), jmax (maxInChannels, 2)); + int maxInChannels = buffer->getNumChannels(); + int maxOutChannels = bufferToFill.buffer->getNumChannels(); + + if (! playAcrossAllChannels) + maxOutChannels = jmin (maxOutChannels, maxInChannels); for (int i = 0; i < maxOutChannels; ++i) bufferToFill.buffer->copyFrom (i, bufferToFill.startSample, *buffer, @@ -1047,7 +1050,7 @@ private: //============================================================================== OptionalScopedPointer buffer; int position; - bool looping; + bool looping, playAcrossAllChannels; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSampleBufferSource) }; @@ -1080,10 +1083,10 @@ void AudioDeviceManager::playSound (AudioFormatReader* reader, bool deleteWhenFi playSound (new AudioFormatReaderSource (reader, deleteWhenFinished), true); } -void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished) +void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished, bool playOnAllOutputChannels) { if (buffer != nullptr) - playSound (new AudioSampleBufferSource (buffer, deleteWhenFinished), true); + playSound (new AudioSampleBufferSource (buffer, deleteWhenFinished, playOnAllOutputChannels), true); } void AudioDeviceManager::playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished) @@ -1134,7 +1137,7 @@ void AudioDeviceManager::playTestSound() newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); - playSound (newSound, true); + playSound (newSound, true, true); } //============================================================================== diff --git a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h b/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h index 5c97e1c46..4d94b53b5 100644 --- a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h +++ b/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h @@ -445,8 +445,15 @@ public: This will output the sound contained in an audio sample buffer. If deleteWhenFinished is true then the audio sample buffer will be automatically deleted once the sound has finished playing. + + If playOnAllOutputChannels is true, then if there are more output channels + than buffer channels, then the ones that are available will be re-used on + multiple outputs so that something is sent to all output channels. If it + is false, then the buffer will just be played on the first output channels. */ - void playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished = false); + void playSound (AudioSampleBuffer* buffer, + bool deleteWhenFinished = false, + bool playOnAllOutputChannels = false); //============================================================================== /** Turns on level-measuring. diff --git a/source/modules/juce_audio_devices/juce_audio_devices.cpp b/source/modules/juce_audio_devices/juce_audio_devices.cpp index 172b0e557..e5717830e 100644 --- a/source/modules/juce_audio_devices/juce_audio_devices.cpp +++ b/source/modules/juce_audio_devices/juce_audio_devices.cpp @@ -31,7 +31,12 @@ #error "Incorrect use of JUCE cpp file" #endif -#include "../juce_core/native/juce_BasicNativeHeaders.h" +#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 +#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 +#define JUCE_CORE_INCLUDE_JNI_HELPERS 1 +#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 +#define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 + #include "juce_audio_devices.h" //============================================================================== @@ -141,7 +146,6 @@ namespace juce //============================================================================== #if JUCE_MAC - #include "../juce_core/native/juce_osx_ObjCHelpers.h" #include "native/juce_mac_CoreAudio.cpp" #include "native/juce_mac_CoreMidi.cpp" @@ -160,8 +164,6 @@ namespace juce //============================================================================== #elif JUCE_WINDOWS - #include "../juce_core/native/juce_win32_ComSmartPtr.h" - #include "../juce_events/native/juce_win32_HiddenMessageWindow.h" #if JUCE_WASAPI #include "native/juce_win32_WASAPI.cpp" @@ -203,7 +205,6 @@ namespace juce //============================================================================== #elif JUCE_ANDROID - #include "../juce_core/native/juce_android_JNIHelpers.h" #include "native/juce_android_Audio.cpp" #include "native/juce_android_Midi.cpp" diff --git a/source/modules/juce_audio_devices/juce_audio_devices.h b/source/modules/juce_audio_devices/juce_audio_devices.h index 249f6b2d3..9db9e4730 100644 --- a/source/modules/juce_audio_devices/juce_audio_devices.h +++ b/source/modules/juce_audio_devices/juce_audio_devices.h @@ -29,7 +29,7 @@ #include "../juce_audio_basics/juce_audio_basics.h" #include "../juce_audio_formats/juce_audio_formats.h" -//============================================================================= +//============================================================================== /** Config: JUCE_ASIO Enables ASIO audio devices (MS Windows only). Turning this on means that you'll need to have the Steinberg ASIO SDK installed @@ -90,7 +90,7 @@ #endif #endif -//============================================================================= +//============================================================================== /** Config: JUCE_USE_CDREADER Enables the AudioCDReader class (on supported platforms). */ @@ -105,7 +105,7 @@ #define JUCE_USE_CDBURNER 0 #endif -//============================================================================= +//============================================================================== namespace juce { diff --git a/source/modules/juce_audio_devices/native/juce_android_Audio.cpp b/source/modules/juce_audio_devices/native/juce_android_Audio.cpp index d32f5cf70..5714d6235 100644 --- a/source/modules/juce_audio_devices/native/juce_android_Audio.cpp +++ b/source/modules/juce_audio_devices/native/juce_android_Audio.cpp @@ -195,25 +195,51 @@ public: STREAM_MUSIC, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT, (jint) (minBufferSizeOut * numDeviceOutputChannels * sizeof (int16)), MODE_STREAM)); - if (env->CallIntMethod (outputDevice, AudioTrack.getState) != STATE_UNINITIALIZED) + int outputDeviceState = env->CallIntMethod (outputDevice, AudioTrack.getState); + if (outputDeviceState > 0) + { isRunning = true; + } else - outputDevice.clear(); // failed to open the device + { + // failed to open the device + outputDevice.clear(); + lastError = "Error opening audio output device: android.media.AudioTrack failed with state = " + String (outputDeviceState); + } } if (numClientInputChannels > 0 && numDeviceInputChannelsAvailable > 0) { - numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable); - inputDevice = GlobalRef (env->NewObject (AudioRecord, AudioRecord.constructor, - 0 /* (default audio source) */, sampleRate, - numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO, - ENCODING_PCM_16BIT, - (jint) (minBufferSizeIn * numDeviceInputChannels * sizeof (int16)))); - - if (env->CallIntMethod (inputDevice, AudioRecord.getState) != STATE_UNINITIALIZED) - isRunning = true; + if (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio)) + { + // If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio + // before trying to open an audio input device. This is not going to work! + jassertfalse; + + inputDevice.clear(); + lastError = "Error opening audio input device: the app was not granted android.permission.RECORD_AUDIO"; + } else - inputDevice.clear(); // failed to open the device + { + numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable); + inputDevice = GlobalRef (env->NewObject (AudioRecord, AudioRecord.constructor, + 0 /* (default audio source) */, sampleRate, + numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO, + ENCODING_PCM_16BIT, + (jint) (minBufferSizeIn * numDeviceInputChannels * sizeof (int16)))); + + int inputDeviceState = env->CallIntMethod (inputDevice, AudioRecord.getState); + if (inputDeviceState > 0) + { + isRunning = true; + } + else + { + // failed to open the device + inputDevice.clear(); + lastError = "Error opening audio input device: android.media.AudioRecord failed with state = " + String (inputDeviceState); + } + } } if (isRunning) @@ -368,7 +394,7 @@ public: int minBufferSizeOut, minBufferSizeIn; private: - //================================================================================================== + //============================================================================== CriticalSection callbackLock; AudioIODeviceCallback* callback; jint sampleRate; diff --git a/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp b/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp index 08580aa08..7102afeee 100644 --- a/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp +++ b/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp @@ -145,12 +145,32 @@ public: << ", sampleRate = " << sampleRate); if (numInputChannels > 0) - recorder = engine.createRecorder (numInputChannels, sampleRate, - audioBuffersToEnqueue, actualBufferSize); + { + if (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio)) + { + // If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio + // before trying to open an audio input device. This is not going to work! + jassertfalse; + lastError = "Error opening OpenSL input device: the app was not granted android.permission.RECORD_AUDIO"; + } + else + { + recorder = engine.createRecorder (numInputChannels, sampleRate, + audioBuffersToEnqueue, actualBufferSize); + + if (recorder == nullptr) + lastError = "Error opening OpenSL input device: creating Recorder failed."; + } + } if (numOutputChannels > 0) - player = engine.createPlayer (numOutputChannels, sampleRate, - audioBuffersToEnqueue, actualBufferSize); + { + player = engine.createPlayer (numOutputChannels, sampleRate, + audioBuffersToEnqueue, actualBufferSize); + + if (player == nullptr) + lastError = "Error opening OpenSL input device: creating Player failed."; + } // pre-fill buffers for (int i = 0; i < audioBuffersToEnqueue; ++i) @@ -220,7 +240,7 @@ public: } private: - //================================================================================================== + //============================================================================== CriticalSection callbackLock; AudioIODeviceCallback* callback; int actualBufferSize, sampleRate; @@ -242,7 +262,7 @@ private: defaultBufferSizeIsMultipleOfNative = 1 }; - //================================================================================================== + //============================================================================== static String audioManagerGetProperty (const String& property) { const LocalRef jProperty (javaString (property)); @@ -281,7 +301,7 @@ private: return androidHasSystemFeature ("android.hardware.audio.low_latency"); } - //================================================================================================== + //============================================================================== AudioIODeviceCallback* setCallback (AudioIODeviceCallback* const newCallback) { const ScopedLock sl (callbackLock); @@ -331,7 +351,7 @@ private: DBG ("Unable to set audio thread priority: priority is still " << priority); } - //================================================================================================== + //============================================================================== struct Engine { Engine() @@ -400,7 +420,7 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Engine) }; - //================================================================================================== + //============================================================================== struct BufferList { BufferList (const int numChannels_, const int numBuffers_, const int numSamples_) @@ -444,7 +464,7 @@ private: WaitableEvent dataArrived; }; - //================================================================================================== + //============================================================================== struct Player { Player (int numChannels, int sampleRate, Engine& engine, int playerNumBuffers, int playerBufferSize) @@ -559,7 +579,7 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Player) }; - //================================================================================================== + //============================================================================== struct Recorder { Recorder (int numChannels, int sampleRate, Engine& engine, const int numBuffers, const int numSamples) diff --git a/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp b/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp index 5cb069ff5..a33544149 100644 --- a/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp +++ b/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp @@ -24,7 +24,9 @@ class iOSAudioIODevice; -//================================================================================================== +static const char* const iOSAudioDeviceName = "iOS Audio"; + +//============================================================================== struct AudioSessionHolder { AudioSessionHolder(); @@ -74,7 +76,7 @@ bool getNotificationValueForKey (NSNotification* notification, NSString* key, NS } // juce namespace -//================================================================================================== +//============================================================================== @interface iOSAudioSessionNative : NSObject { @private @@ -178,7 +180,7 @@ bool getNotificationValueForKey (NSNotification* notification, NSString* key, NS @end -//================================================================================================== +//============================================================================== namespace juce { #ifndef JUCE_IOS_AUDIO_LOGGING @@ -203,11 +205,12 @@ static void logNSError (NSError* e) #define JUCE_NSERROR_CHECK(X) { NSError* error = nil; X; logNSError (error); } -//================================================================================================== +//============================================================================== class iOSAudioIODevice : public AudioIODevice { public: - iOSAudioIODevice (const String& deviceName) : AudioIODevice (deviceName, "Audio") + iOSAudioIODevice (const String& deviceName) + : AudioIODevice (deviceName, iOSAudioDeviceName) { sessionHolder->activeDevices.add (this); updateSampleRateAndAudioInput(); @@ -350,13 +353,14 @@ public: { isRunning = false; - setAudioSessionActive (false); - if (audioUnit != 0) { + AudioOutputUnitStart (audioUnit); AudioComponentInstanceDispose (audioUnit); audioUnit = 0; } + + setAudioSessionActive (false); } } @@ -463,13 +467,13 @@ public: AudioOutputUnitStart (audioUnit); } - if (callback) + if (callback != nullptr) callback->audioDeviceAboutToStart (this); } } private: - //================================================================================================== + //============================================================================== SharedResourcePointer sessionHolder; CriticalSection callbackLock; NSTimeInterval sampleRate = 0; @@ -505,7 +509,7 @@ private: } } - //================================================================================================== + //============================================================================== OSStatus process (AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time, const UInt32 numFrames, AudioBufferList* data) { @@ -592,21 +596,22 @@ private: auto session = [AVAudioSession sharedInstance]; sampleRate = session.sampleRate; audioInputIsAvailable = session.isInputAvailable; - JUCE_IOS_AUDIO_LOG ("AVAudioSession: sampleRate: " << sampleRate << "Hz, audioInputAvailable: " << (int) audioInputIsAvailable); + actualBufferSize = roundToInt (sampleRate * session.IOBufferDuration); + + JUCE_IOS_AUDIO_LOG ("AVAudioSession: sampleRate: " << sampleRate + << "Hz, audioInputAvailable: " << (int) audioInputIsAvailable); } void updateCurrentBufferSize() { - auto session = [AVAudioSession sharedInstance]; - NSTimeInterval bufferDuration = sampleRate > 0 ? (NSTimeInterval) (preferredBufferSize / sampleRate) : 0.0; - JUCE_NSERROR_CHECK ([session setPreferredIOBufferDuration: bufferDuration - error: &error]); + NSTimeInterval bufferDuration = sampleRate > 0 ? (NSTimeInterval) ((preferredBufferSize + 1) / sampleRate) : 0.0; - bufferDuration = session.IOBufferDuration; - actualBufferSize = roundToInt (sampleRate * bufferDuration); + JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setPreferredIOBufferDuration: bufferDuration + error: &error]); + updateSampleRateAndAudioInput(); } - //================================================================================================== + //============================================================================== static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time, UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data) { @@ -614,7 +619,7 @@ private: return static_cast (client)->process (flags, time, numFrames, data); } - //================================================================================================== + //============================================================================== void resetFormat (const int numChannels) noexcept { zerostruct (format); @@ -712,10 +717,10 @@ private: class iOSAudioIODeviceType : public AudioIODeviceType { public: - iOSAudioIODeviceType() : AudioIODeviceType ("iOS Audio") {} + iOSAudioIODeviceType() : AudioIODeviceType (iOSAudioDeviceName) {} void scanForDevices() {} - StringArray getDeviceNames (bool /*wantInputNames*/) const { return StringArray ("iOS Audio"); } + StringArray getDeviceNames (bool /*wantInputNames*/) const { return StringArray (iOSAudioDeviceName); } int getDefaultDeviceIndex (bool /*forInput*/) const { return 0; } int getIndexOfDevice (AudioIODevice* d, bool /*asInput*/) const { return d != nullptr ? 0 : -1; } bool hasSeparateInputsAndOutputs() const { return false; } @@ -738,7 +743,7 @@ AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() return new iOSAudioIODeviceType(); } -//================================================================================================== +//============================================================================== AudioSessionHolder::AudioSessionHolder() { nativeSession = [[iOSAudioSessionNative alloc] init: this]; } AudioSessionHolder::~AudioSessionHolder() { [nativeSession release]; } diff --git a/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp b/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp index b5768f40c..7a0dbf30a 100644 --- a/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp +++ b/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp @@ -31,6 +31,12 @@ #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" // aovid some spurious 10.11 SDK warnings + + // The AudioHardwareService stuff was deprecated in 10.11 but there's no replacement yet, + // so we'll have to silence the warnings here and revisit it in a future OS version.. + #if defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_11 + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + #endif #endif //============================================================================== @@ -382,65 +388,84 @@ public: return 0; } - void updateDetailsFromDevice() + int getFrameSizeFromDevice() const { - stopTimer(); + AudioObjectPropertyAddress pa; + pa.mScope = kAudioObjectPropertyScopeWildcard; + pa.mElement = kAudioObjectPropertyElementMaster; + pa.mSelector = kAudioDevicePropertyBufferFrameSize; - if (deviceID == 0) - return; + UInt32 framesPerBuf = (UInt32) bufferSize; + UInt32 size = sizeof (framesPerBuf); + AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &framesPerBuf); + return (int) framesPerBuf; + } - // this collects all the new details from the device without any locking, then - // locks + swaps them afterwards. + bool isDeviceAlive() const + { AudioObjectPropertyAddress pa; pa.mScope = kAudioObjectPropertyScopeWildcard; pa.mElement = kAudioObjectPropertyElementMaster; + pa.mSelector = kAudioDevicePropertyDeviceIsAlive; - UInt32 isAlive; + UInt32 isAlive = 0; UInt32 size = sizeof (isAlive); - pa.mSelector = kAudioDevicePropertyDeviceIsAlive; - if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &isAlive)) && isAlive == 0) - return; + return deviceID != 0 + && OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &isAlive)) + && isAlive != 0; + } - const double currentRate = getNominalSampleRate(); - if (currentRate > 0) - sampleRate = currentRate; + bool updateDetailsFromDevice() + { + stopTimer(); - UInt32 framesPerBuf = (UInt32) bufferSize; - size = sizeof (framesPerBuf); - pa.mSelector = kAudioDevicePropertyBufferFrameSize; - AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &framesPerBuf); + if (! isDeviceAlive()) + return false; + + // this collects all the new details from the device without any locking, then + // locks + swaps them afterwards. - Array newBufferSizes (getBufferSizesFromDevice()); - Array newSampleRates (getSampleRatesFromDevice()); + const double newSampleRate = getNominalSampleRate(); + const int newBufferSize = getFrameSizeFromDevice(); - inputLatency = getLatencyFromDevice (kAudioDevicePropertyScopeInput); - outputLatency = getLatencyFromDevice (kAudioDevicePropertyScopeOutput); + Array newBufferSizes = getBufferSizesFromDevice(); + Array newSampleRates = getSampleRatesFromDevice(); + + const int newInputLatency = getLatencyFromDevice (kAudioDevicePropertyScopeInput); + const int newOutputLatency = getLatencyFromDevice (kAudioDevicePropertyScopeOutput); Array newInChans, newOutChans; StringArray newInNames (getChannelInfo (true, newInChans)); StringArray newOutNames (getChannelInfo (false, newOutChans)); - const int inputBitDepth = getBitDepthFromDevice (kAudioDevicePropertyScopeInput); - const int outputBitDepth = getBitDepthFromDevice (kAudioDevicePropertyScopeOutput); + const int newBitDepth = jmax (getBitDepthFromDevice (kAudioDevicePropertyScopeInput), + getBitDepthFromDevice (kAudioDevicePropertyScopeOutput)); + + { + const ScopedLock sl (callbackLock); - bitDepth = jmax (inputBitDepth, outputBitDepth); - if (bitDepth <= 0) - bitDepth = 32; + bitDepth = newBitDepth > 0 ? newBitDepth : 32; - // after getting the new values, lock + apply them - const ScopedLock sl (callbackLock); + if (newSampleRate > 0) + sampleRate = newSampleRate; - bufferSize = (int) framesPerBuf; - allocateTempBuffers(); + inputLatency = newInputLatency; + outputLatency = newOutputLatency; + bufferSize = newBufferSize; - sampleRates.swapWith (newSampleRates); - bufferSizes.swapWith (newBufferSizes); + sampleRates.swapWith (newSampleRates); + bufferSizes.swapWith (newBufferSizes); - inChanNames.swapWith (newInNames); - outChanNames.swapWith (newOutNames); + inChanNames.swapWith (newInNames); + outChanNames.swapWith (newOutNames); - inputChannelInfo.swapWith (newInChans); - outputChannelInfo.swapWith (newOutChans); + inputChannelInfo.swapWith (newInChans); + outputChannelInfo.swapWith (newOutChans); + + allocateTempBuffers(); + } + + return true; } //============================================================================== @@ -763,9 +788,10 @@ public: stopTimer(); const double oldSampleRate = sampleRate; const int oldBufferSize = bufferSize; - updateDetailsFromDevice(); - if (oldBufferSize != bufferSize || oldSampleRate != sampleRate) + if (! updateDetailsFromDevice()) + owner.stop(); + else if (oldBufferSize != bufferSize || oldSampleRate != sampleRate) owner.restart(); } diff --git a/source/modules/juce_audio_devices/native/juce_win32_ASIO.cpp b/source/modules/juce_audio_devices/native/juce_win32_ASIO.cpp index 16c8b524b..670eb4115 100644 --- a/source/modules/juce_audio_devices/native/juce_win32_ASIO.cpp +++ b/source/modules/juce_audio_devices/native/juce_win32_ASIO.cpp @@ -320,9 +320,9 @@ public: classId (clsID), inputLatency (0), outputLatency (0), - minSize (0), maxSize (0), - preferredSize (0), - granularity (0), + minBufferSize (0), maxBufferSize (0), + preferredBufferSize (0), + bufferGranularity (0), numClockSources (0), currentBlockSizeSamples (0), currentBitDepth (16), @@ -403,7 +403,7 @@ public: Array getAvailableSampleRates() override { return sampleRates; } Array getAvailableBufferSizes() override { return bufferSizes; } - int getDefaultBufferSize() override { return preferredSize; } + int getDefaultBufferSize() override { return preferredBufferSize; } String open (const BigInteger& inputChannels, const BigInteger& outputChannels, @@ -469,10 +469,10 @@ public: removeCurrentDriver(); loadDriver(); - const String error (initDriver()); + String initError = initDriver(); - if (error.isNotEmpty()) - JUCE_ASIO_LOG ("ASIOInit: " + error); + if (initError.isNotEmpty()) + JUCE_ASIO_LOG ("ASIOInit: " + initError); needToReset = false; } @@ -489,7 +489,7 @@ public: if (err != ASE_OK) { - currentBlockSizeSamples = preferredSize; + currentBlockSizeSamples = preferredBufferSize; JUCE_ASIO_LOG_ERROR ("create buffers 2", err); asioObject->disposeBuffers(); @@ -561,8 +561,7 @@ public: } readLatencies(); - - asioObject->getBufferSize (&minSize, &maxSize, &preferredSize, &granularity); + refreshBufferSizes(); deviceIsOpen = true; JUCE_ASIO_LOG ("starting"); @@ -762,7 +761,7 @@ private: Array sampleRates; Array bufferSizes; long inputLatency, outputLatency; - long minSize, maxSize, preferredSize, granularity; + long minBufferSize, maxBufferSize, preferredBufferSize, bufferGranularity; ASIOClockSource clocks[32]; int numClockSources; @@ -829,23 +828,27 @@ private: } } - int readBufferSizes (int bufferSizeSamples) + long refreshBufferSizes() { - minSize = 0; - maxSize = 0; - granularity = 0; + return asioObject->getBufferSize (&minBufferSize, &maxBufferSize, &preferredBufferSize, &bufferGranularity); + } + int readBufferSizes (int bufferSizeSamples) + { + minBufferSize = 0; + maxBufferSize = 0; + bufferGranularity = 0; long newPreferredSize = 0; - if (asioObject->getBufferSize (&minSize, &maxSize, &newPreferredSize, &granularity) == ASE_OK) + if (asioObject->getBufferSize (&minBufferSize, &maxBufferSize, &newPreferredSize, &bufferGranularity) == ASE_OK) { - if (preferredSize != 0 && newPreferredSize != 0 && newPreferredSize != preferredSize) + if (preferredBufferSize != 0 && newPreferredSize != 0 && newPreferredSize != preferredBufferSize) shouldUsePreferredSize = true; - if (bufferSizeSamples < minSize || bufferSizeSamples > maxSize) + if (bufferSizeSamples < minBufferSize || bufferSizeSamples > maxBufferSize) shouldUsePreferredSize = true; - preferredSize = newPreferredSize; + preferredBufferSize = newPreferredSize; } // unfortunate workaround for certain drivers which crash if you make @@ -855,11 +858,11 @@ private: if (shouldUsePreferredSize) { JUCE_ASIO_LOG ("Using preferred size for buffer.."); - long err = asioObject->getBufferSize (&minSize, &maxSize, &preferredSize, &granularity); + long err = refreshBufferSizes(); if (err == ASE_OK) { - bufferSizeSamples = (int) preferredSize; + bufferSizeSamples = (int) preferredBufferSize; } else { @@ -1082,8 +1085,8 @@ private: if (i < 2) { // clear the channels that are used with the dummy stuff - outputFormat[i].clear (bufferInfos [outputBufferIndex + i].buffers[0], preferredSize); - outputFormat[i].clear (bufferInfos [outputBufferIndex + i].buffers[1], preferredSize); + outputFormat[i].clear (bufferInfos [outputBufferIndex + i].buffers[0], preferredBufferSize); + outputFormat[i].clear (bufferInfos [outputBufferIndex + i].buffers[1], preferredBufferSize); } } } @@ -1203,9 +1206,9 @@ private: inputFormat.calloc (chansToAllocate); outputFormat.calloc (chansToAllocate); - if ((err = asioObject->getBufferSize (&minSize, &maxSize, &preferredSize, &granularity)) == 0) + if ((err = refreshBufferSizes()) == 0) { - addBufferSizes (minSize, maxSize, preferredSize, granularity); + addBufferSizes (minBufferSize, maxBufferSize, preferredBufferSize, bufferGranularity); double currentRate = getSampleRate(); @@ -1226,8 +1229,8 @@ private: updateSampleRates(); - readLatencies(); // ..doing these steps because cubase does so at this stage - createDummyBuffers (preferredSize); // in initialisation, and some devices fail if we don't. + readLatencies(); // ..doing these steps because cubase does so at this stage + createDummyBuffers (preferredBufferSize); // in initialisation, and some devices fail if we don't. readLatencies(); // start and stop because cubase does it.. @@ -1419,6 +1422,12 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ASIOAudioIODevice) }; +template <> +struct ASIOAudioIODevice::ASIOCallbackFunctions +{ + static void setCallbacksForDevice (ASIOCallbacks&, ASIOAudioIODevice*) noexcept {} +}; + //============================================================================== class ASIOAudioIODeviceType : public AudioIODeviceType { diff --git a/source/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp b/source/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp index 8211dbc2e..d2b6422be 100644 --- a/source/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp +++ b/source/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp @@ -112,6 +112,11 @@ bool check (HRESULT hr) #define JUCE_COMCLASS(name, guid) struct __declspec (uuid (guid)) name #endif +#if JUCE_MINGW && defined (KSDATAFORMAT_SUBTYPE_PCM) + #undef KSDATAFORMAT_SUBTYPE_PCM + #undef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT +#endif + #ifndef KSDATAFORMAT_SUBTYPE_PCM #define KSDATAFORMAT_SUBTYPE_PCM uuidFromString ("00000001-0000-0010-8000-00aa00389b71") #define KSDATAFORMAT_SUBTYPE_IEEE_FLOAT uuidFromString ("00000003-0000-0010-8000-00aa00389b71") diff --git a/source/modules/juce_audio_formats/codecs/flac/libFLAC/include/private/metadata.h b/source/modules/juce_audio_formats/codecs/flac/libFLAC/include/private/metadata.h index 29c73e0fd..06c6d98f9 100644 --- a/source/modules/juce_audio_formats/codecs/flac/libFLAC/include/private/metadata.h +++ b/source/modules/juce_audio_formats/codecs/flac/libFLAC/include/private/metadata.h @@ -33,7 +33,7 @@ #ifndef FLAC__PRIVATE__METADATA_H #define FLAC__PRIVATE__METADATA_H -#include "FLAC/metadata.h" +#include "../../../metadata.h" /* WATCHOUT: all malloc()ed data in the block is free()ed; this may not * be a consistent state (e.g. PICTURE) or equivalent to the initial diff --git a/source/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp b/source/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp index d11b17b34..ceb8a5ee2 100644 --- a/source/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp +++ b/source/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp @@ -112,19 +112,28 @@ struct CoreAudioFormatMetatdata }; //============================================================================== - struct UserDefinedChunk + static StringPairArray parseUserDefinedChunk (InputStream& input, int64 size) { - UserDefinedChunk (InputStream& input, int64 size) + StringPairArray infoStrings; + const int64 originalPosition = input.getPosition(); + + uint8 uuid[16]; + input.read (uuid, sizeof (uuid)); + + if (memcmp (uuid, "\x29\x81\x92\x73\xB5\xBF\x4A\xEF\xB7\x8D\x62\xD1\xEF\x90\xBB\x2C", 16) == 0) { - // a user defined chunk contains 16 bytes of a UUID first - uuid[1] = input.readInt64BigEndian(); - uuid[0] = input.readInt64BigEndian(); + const uint32 numEntries = (uint32) input.readIntBigEndian(); - input.skipNextBytes (size - 16); + for (uint32 i = 0; i < numEntries && input.getPosition() < originalPosition + size; ++i) + { + String keyName = input.readString(); + infoStrings.set (keyName, input.readString()); + } } - int64 uuid[2]; - }; + input.setPosition (originalPosition + size); + return infoStrings; + } //============================================================================== static StringPairArray parseMidiChunk (InputStream& input, int64 size) @@ -288,7 +297,7 @@ struct CoreAudioFormatMetatdata } else if (chunkHeader.chunkType == chunkName ("uuid")) { - UserDefinedChunk userDefinedChunk (input, chunkHeader.chunkSize); + metadataValues.addArray (parseUserDefinedChunk (input, chunkHeader.chunkSize)); } else if (chunkHeader.chunkType == chunkName ("data")) { diff --git a/source/modules/juce_audio_formats/codecs/juce_QuickTimeAudioFormat.cpp b/source/modules/juce_audio_formats/codecs/juce_QuickTimeAudioFormat.cpp index 8c270e4e0..ffcaf5147 100644 --- a/source/modules/juce_audio_formats/codecs/juce_QuickTimeAudioFormat.cpp +++ b/source/modules/juce_audio_formats/codecs/juce_QuickTimeAudioFormat.cpp @@ -27,15 +27,11 @@ } // (juce namespace) #if ! JUCE_WINDOWS - #define Point CarbonDummyPointName // (workaround to avoid definition of "Point" by old Carbon headers) - #define Component CarbonDummyCompName #include #include #include #include #include - #undef Point - #undef Component #else #if JUCE_MSVC #pragma warning (push) diff --git a/source/modules/juce_audio_formats/juce_audio_formats.cpp b/source/modules/juce_audio_formats/juce_audio_formats.cpp index 370a084fc..499fae1bc 100644 --- a/source/modules/juce_audio_formats/juce_audio_formats.cpp +++ b/source/modules/juce_audio_formats/juce_audio_formats.cpp @@ -31,19 +31,18 @@ #error "Incorrect use of JUCE cpp file" #endif -#include "../juce_core/native/juce_BasicNativeHeaders.h" +#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 +#define JUCE_CORE_INCLUDE_JNI_HELPERS 1 +#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 + #include "juce_audio_formats.h" //============================================================================== #if JUCE_MAC - #define Point CarbonDummyPointName - #define Component CarbonDummyCompName #if JUCE_QUICKTIME #import #endif #include - #undef Component - #undef Point #elif JUCE_IOS #import @@ -85,14 +84,9 @@ namespace juce { #if JUCE_ANDROID - #include "../juce_core/native/juce_android_JNIHelpers.h" #undef JUCE_QUICKTIME #endif -#if JUCE_WINDOWS - #include "../juce_core/native/juce_win32_ComSmartPtr.h" -#endif - #include "format/juce_AudioFormat.cpp" #include "format/juce_AudioFormatManager.cpp" #include "format/juce_AudioFormatReader.cpp" diff --git a/source/modules/juce_audio_formats/juce_audio_formats.h b/source/modules/juce_audio_formats/juce_audio_formats.h index 13cc25f7a..24ca93422 100644 --- a/source/modules/juce_audio_formats/juce_audio_formats.h +++ b/source/modules/juce_audio_formats/juce_audio_formats.h @@ -27,7 +27,7 @@ #include "../juce_audio_basics/juce_audio_basics.h" -//============================================================================= +//============================================================================== /** Config: JUCE_USE_FLAC Enables the FLAC audio codec classes (available on all platforms). If your app doesn't need to read FLAC files, you might want to disable this to @@ -81,7 +81,7 @@ #define JUCE_USE_WINDOWS_MEDIA_FORMAT 0 #endif -//============================================================================= +//============================================================================== namespace juce { diff --git a/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index acba4857c..62c3bcbcd 100644 --- a/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -387,11 +387,6 @@ public: void* getPlatformSpecificData() override { return audioUnit; } const String getName() const override { return pluginName; } - bool silenceInProducesSilenceOut() const override - { - return getTailLengthSeconds() <= 0; - } - double getTailLengthSeconds() const override { Float64 tail = 0; diff --git a/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp index b23f711df..08d86f8ed 100644 --- a/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp +++ b/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp @@ -252,7 +252,6 @@ public: bool acceptsMidi() const { return false; } bool producesMidi() const { return false; } - bool silenceInProducesSilenceOut() const { return plugin == nullptr; } // ..any way to get a proper answer for these? double getTailLengthSeconds() const { return 0.0; } //============================================================================== diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3Common.h b/source/modules/juce_audio_processors/format_types/juce_VST3Common.h index 2ede0a636..e44cb02e8 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/source/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -143,25 +143,25 @@ static inline Steinberg::Vst::Speaker getSpeakerType (AudioChannelSet::ChannelTy switch (type) { - case AudioChannelSet::ChannelType::left: return kSpeakerL; - case AudioChannelSet::ChannelType::right: return kSpeakerR; - case AudioChannelSet::ChannelType::centre: return kSpeakerC; - case AudioChannelSet::ChannelType::subbass: return kSpeakerLfe; - case AudioChannelSet::ChannelType::surroundLeft: return kSpeakerLs; - case AudioChannelSet::ChannelType::surroundRight: return kSpeakerRs; - case AudioChannelSet::ChannelType::centreLeft: return kSpeakerLc; - case AudioChannelSet::ChannelType::centreRight: return kSpeakerRc; - case AudioChannelSet::ChannelType::surround: return kSpeakerS; - case AudioChannelSet::ChannelType::sideLeft: return kSpeakerSl; - case AudioChannelSet::ChannelType::sideRight: return kSpeakerSr; - case AudioChannelSet::ChannelType::topMiddle: return kSpeakerTm; - case AudioChannelSet::ChannelType::topFrontLeft: return kSpeakerTfl; - case AudioChannelSet::ChannelType::topFrontCentre: return kSpeakerTfc; - case AudioChannelSet::ChannelType::topFrontRight: return kSpeakerTfr; - case AudioChannelSet::ChannelType::topRearLeft: return kSpeakerTrl; - case AudioChannelSet::ChannelType::topRearCentre: return kSpeakerTrc; - case AudioChannelSet::ChannelType::topRearRight: return kSpeakerTrr; - case AudioChannelSet::ChannelType::subbass2: return kSpeakerLfe2; + case AudioChannelSet::left: return kSpeakerL; + case AudioChannelSet::right: return kSpeakerR; + case AudioChannelSet::centre: return kSpeakerC; + case AudioChannelSet::subbass: return kSpeakerLfe; + case AudioChannelSet::surroundLeft: return kSpeakerLs; + case AudioChannelSet::surroundRight: return kSpeakerRs; + case AudioChannelSet::centreLeft: return kSpeakerLc; + case AudioChannelSet::centreRight: return kSpeakerRc; + case AudioChannelSet::surround: return kSpeakerS; + case AudioChannelSet::sideLeft: return kSpeakerSl; + case AudioChannelSet::sideRight: return kSpeakerSr; + case AudioChannelSet::topMiddle: return kSpeakerTm; + case AudioChannelSet::topFrontLeft: return kSpeakerTfl; + case AudioChannelSet::topFrontCentre: return kSpeakerTfc; + case AudioChannelSet::topFrontRight: return kSpeakerTfr; + case AudioChannelSet::topRearLeft: return kSpeakerTrl; + case AudioChannelSet::topRearCentre: return kSpeakerTrc; + case AudioChannelSet::topRearRight: return kSpeakerTrr; + case AudioChannelSet::subbass2: return kSpeakerLfe2; default: break; } @@ -174,29 +174,29 @@ static inline AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::Speak switch (type) { - case kSpeakerL: return AudioChannelSet::ChannelType::left; - case kSpeakerR: return AudioChannelSet::ChannelType::right; - case kSpeakerC: return AudioChannelSet::ChannelType::centre; - case kSpeakerLfe: return AudioChannelSet::ChannelType::subbass; - case kSpeakerLs: return AudioChannelSet::ChannelType::surroundLeft; - case kSpeakerRs: return AudioChannelSet::ChannelType::surroundRight; - case kSpeakerLc: return AudioChannelSet::ChannelType::centreLeft; - case kSpeakerRc: return AudioChannelSet::ChannelType::centreRight; - case kSpeakerS: return AudioChannelSet::ChannelType::surround; - case kSpeakerSl: return AudioChannelSet::ChannelType::sideLeft; - case kSpeakerSr: return AudioChannelSet::ChannelType::sideRight; - case kSpeakerTm: return AudioChannelSet::ChannelType::topMiddle; - case kSpeakerTfl: return AudioChannelSet::ChannelType::topFrontLeft; - case kSpeakerTfc: return AudioChannelSet::ChannelType::topFrontCentre; - case kSpeakerTfr: return AudioChannelSet::ChannelType::topFrontRight; - case kSpeakerTrl: return AudioChannelSet::ChannelType::topRearLeft; - case kSpeakerTrc: return AudioChannelSet::ChannelType::topRearCentre; - case kSpeakerTrr: return AudioChannelSet::ChannelType::topRearRight; - case kSpeakerLfe2: return AudioChannelSet::ChannelType::subbass2; + case kSpeakerL: return AudioChannelSet::left; + case kSpeakerR: return AudioChannelSet::right; + case kSpeakerC: return AudioChannelSet::centre; + case kSpeakerLfe: return AudioChannelSet::subbass; + case kSpeakerLs: return AudioChannelSet::surroundLeft; + case kSpeakerRs: return AudioChannelSet::surroundRight; + case kSpeakerLc: return AudioChannelSet::centreLeft; + case kSpeakerRc: return AudioChannelSet::centreRight; + case kSpeakerS: return AudioChannelSet::surround; + case kSpeakerSl: return AudioChannelSet::sideLeft; + case kSpeakerSr: return AudioChannelSet::sideRight; + case kSpeakerTm: return AudioChannelSet::topMiddle; + case kSpeakerTfl: return AudioChannelSet::topFrontLeft; + case kSpeakerTfc: return AudioChannelSet::topFrontCentre; + case kSpeakerTfr: return AudioChannelSet::topFrontRight; + case kSpeakerTrl: return AudioChannelSet::topRearLeft; + case kSpeakerTrc: return AudioChannelSet::topRearCentre; + case kSpeakerTrr: return AudioChannelSet::topRearRight; + case kSpeakerLfe2: return AudioChannelSet::subbass2; default: break; } - return AudioChannelSet::ChannelType::unknown; + return AudioChannelSet::unknown; } static inline Steinberg::Vst::SpeakerArrangement getSpeakerArrangement (const AudioChannelSet& channels) noexcept diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3Headers.h b/source/modules/juce_audio_processors/format_types/juce_VST3Headers.h index 987a94f37..fbd58c827 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VST3Headers.h +++ b/source/modules/juce_audio_processors/format_types/juce_VST3Headers.h @@ -25,9 +25,6 @@ #ifndef JUCE_VST3HEADERS_H_INCLUDED #define JUCE_VST3HEADERS_H_INCLUDED -#undef Point -#undef Component - // Wow, those Steinberg guys really don't worry too much about compiler warnings. #if _MSC_VER #pragma warning (disable: 4505) @@ -85,8 +82,6 @@ #if JUCE_MINGW #define _set_abort_behavior(...) #endif - #define Point CarbonDummyPointName // The VST headers include some system headers that need - // to match the name our hacky Carbon workaround used. #include #include #include @@ -112,7 +107,6 @@ #include #include #include - #undef Point //============================================================================== namespace Steinberg @@ -177,7 +171,5 @@ namespace Steinberg #undef DEF_CLASS2 #undef DEF_CLASS_W #undef END_FACTORY -#undef Point -#undef Component #endif // JUCE_VST3HEADERS_H_INCLUDED diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index bd99e52b8..58d5b5dc7 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -1861,23 +1861,15 @@ public: bool producesMidi() const override { return getBusInfo (false, false).channelCount > 0; } //============================================================================== - bool silenceInProducesSilenceOut() const override - { - if (processor != nullptr) - return processor->getTailSamples() == Vst::kNoTail; - - return true; - } - /** May return a negative value as a means of informing us that the plugin has "infinite tail," or 0 for "no tail." */ double getTailLengthSeconds() const override { if (processor != nullptr) { - const double currentSampleRate = getSampleRate(); + const double sampleRate = getSampleRate(); - if (currentSampleRate > 0.0) - return jlimit (0, 0x7fffffff, (int) processor->getTailSamples()) / currentSampleRate; + if (sampleRate > 0.0) + return jlimit (0, 0x7fffffff, (int) processor->getTailSamples()) / sampleRate; } return 0.0; diff --git a/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index 6c1a38bec..b9d38ba94 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -890,23 +890,18 @@ public: return uid; } - bool silenceInProducesSilenceOut() const override - { - return effect == nullptr || (effect->flags & effFlagsNoSoundInStop) != 0; - } - double getTailLengthSeconds() const override { if (effect == nullptr) return 0.0; - const double currentSampleRate = getSampleRate(); + const double sampleRate = getSampleRate(); - if (currentSampleRate <= 0) + if (sampleRate <= 0) return 0.0; VstIntPtr samples = dispatch (effGetTailSize, 0, 0, 0, 0); - return samples / currentSampleRate; + return samples / sampleRate; } bool acceptsMidi() const override { return wantsMidiMessages; } @@ -2915,11 +2910,11 @@ FileSearchPath VSTPluginFormat::getDefaultLocationsToSearch() const String programFiles (File::getSpecialLocation (File::globalApplicationsDirectory).getFullPathName()); FileSearchPath paths; - paths.add (WindowsRegistry::getValue ("HKLM\\Software\\VST\\VSTPluginsPath", + paths.add (WindowsRegistry::getValue ("HKEY_LOCAL_MACHINE\\Software\\VST\\VSTPluginsPath", programFiles + "\\Steinberg\\VstPlugins")); paths.removeNonExistentPaths(); - paths.add (WindowsRegistry::getValue ("HKLM\\Software\\VST\\VSTPluginsPath", + paths.add (WindowsRegistry::getValue ("HKEY_LOCAL_MACHINE\\Software\\VST\\VSTPluginsPath", programFiles + "\\VstPlugins")); return paths; #endif diff --git a/source/modules/juce_audio_processors/juce_audio_processors.cpp b/source/modules/juce_audio_processors/juce_audio_processors.cpp index dd62b2e90..8fd8064df 100644 --- a/source/modules/juce_audio_processors/juce_audio_processors.cpp +++ b/source/modules/juce_audio_processors/juce_audio_processors.cpp @@ -31,7 +31,8 @@ #error "Incorrect use of JUCE cpp file" #endif -#include "../juce_core/native/juce_BasicNativeHeaders.h" +#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 + #include "juce_audio_processors.h" #include "../juce_gui_extra/juce_gui_extra.h" @@ -40,11 +41,7 @@ #if JUCE_SUPPORT_CARBON \ && ((JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_AU) \ || ! (defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6)) - #define Point CarbonDummyPointName // (workaround to avoid definition of "Point" by old Carbon headers) - #define Component CarbonDummyCompName #include - #undef Point - #undef Component #endif #endif diff --git a/source/modules/juce_audio_processors/juce_audio_processors.h b/source/modules/juce_audio_processors/juce_audio_processors.h index 9d91ec7f8..16ab502ad 100644 --- a/source/modules/juce_audio_processors/juce_audio_processors.h +++ b/source/modules/juce_audio_processors/juce_audio_processors.h @@ -29,7 +29,7 @@ #include "../juce_audio_basics/juce_audio_basics.h" -//============================================================================= +//============================================================================== /** Config: JUCE_PLUGINHOST_VST Enables the VST audio plugin hosting classes. This requires the Steinberg VST SDK to be installed on your machine. @@ -67,8 +67,8 @@ #define JUCE_SUPPORT_CARBON 1 #endif -//============================================================================= -//============================================================================= +//============================================================================== +//============================================================================== namespace juce { diff --git a/source/modules/juce_audio_processors/processors/juce_AudioChannelSet.cpp b/source/modules/juce_audio_processors/processors/juce_AudioChannelSet.cpp index 2186bbcee..118bfc516 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioChannelSet.cpp +++ b/source/modules/juce_audio_processors/processors/juce_AudioChannelSet.cpp @@ -28,8 +28,11 @@ bool AudioChannelSet::operator== (const AudioChannelSet& other) const noexcept bool AudioChannelSet::operator!= (const AudioChannelSet& other) const noexcept { return channels != other.channels; } bool AudioChannelSet::operator< (const AudioChannelSet& other) const noexcept { return channels < other.channels; } -const char* AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type) noexcept +String AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type) { + if (type >= discreteChannel0) + return String ("Discrete ") + String (type - discreteChannel0 + 1); + switch (type) { case left: return NEEDS_TRANS("Left"); @@ -63,8 +66,11 @@ const char* AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType ty return "Unknown"; } -const char* AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelType type) noexcept +String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelType type) { + if (type >= discreteChannel0) + return String (type - discreteChannel0 + 1); + switch (type) { case left: return "L"; @@ -151,17 +157,17 @@ AudioChannelSet AudioChannelSet::mono() { return AudioChannelSet ( AudioChannelSet AudioChannelSet::stereo() { return AudioChannelSet ((1u << left) | (1u << right)); } AudioChannelSet AudioChannelSet::createLCR() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre)); } AudioChannelSet AudioChannelSet::createLCRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surround)); } -AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << sideLeft) | (1u << sideRight)); } -AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << sideLeft) | (1u << sideRight) | (1u << centre)); } -AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << sideLeft) | (1u << sideRight) | (1u << centre) | (1u << surround)); } -AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << sideLeft) | (1u << sideRight) | (1u << centre) | (1u << surround) | (1u << wideLeft) | (1u << wideRight)); } +AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surroundLeft) | (1u << surroundRight)); } +AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surroundLeft) | (1u << surroundRight) | (1u << centre)); } +AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surroundLeft) | (1u << surroundRight) | (1u << centre) | (1u << surround)); } +AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surroundLeft) | (1u << surroundRight) | (1u << centre) | (1u << surround) | (1u << wideLeft) | (1u << wideRight)); } AudioChannelSet AudioChannelSet::ambisonic() { return AudioChannelSet ((1u << ambisonicW) | (1u << ambisonicX) | (1u << ambisonicY) | (1u << ambisonicZ)); } -AudioChannelSet AudioChannelSet::create5point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surroundLeft) | (1u << surroundRight)); } -AudioChannelSet AudioChannelSet::create5point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << surroundLeft) | (1u << surroundRight)); } -AudioChannelSet AudioChannelSet::create6point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surroundLeft) | (1u << surroundRight) | (1u << surround)); } -AudioChannelSet AudioChannelSet::create6point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << surroundLeft) | (1u << surroundRight) | (1u << surround)); } -AudioChannelSet AudioChannelSet::create7point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surroundLeft) | (1u << surroundRight) | (1u << topRearLeft) | (1u << topRearRight)); } -AudioChannelSet AudioChannelSet::create7point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << surroundLeft) | (1u << surroundRight) | (1u << topRearLeft) | (1u << topRearRight)); } +AudioChannelSet AudioChannelSet::create5point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << sideLeft) | (1u << sideRight)); } +AudioChannelSet AudioChannelSet::create5point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << sideLeft) | (1u << sideRight)); } +AudioChannelSet AudioChannelSet::create6point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << sideLeft) | (1u << sideRight) | (1u << surround)); } +AudioChannelSet AudioChannelSet::create6point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << sideLeft) | (1u << sideRight) | (1u << surround)); } +AudioChannelSet AudioChannelSet::create7point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << sideLeft) | (1u << sideRight) | (1u << surroundLeft) | (1u << surroundRight)); } +AudioChannelSet AudioChannelSet::create7point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << sideLeft) | (1u << sideRight) | (1u << surroundLeft) | (1u << surroundRight)); } AudioChannelSet AudioChannelSet::createFront7point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surroundLeft) | (1u << surroundRight) | (1u << centreLeft) | (1u << centreRight)); } AudioChannelSet AudioChannelSet::createFront7point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << surroundLeft) | (1u << surroundRight) | (1u << centreLeft) | (1u << centreRight)); } diff --git a/source/modules/juce_audio_processors/processors/juce_AudioChannelSet.h b/source/modules/juce_audio_processors/processors/juce_AudioChannelSet.h index 886abcc86..50be92740 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioChannelSet.h +++ b/source/modules/juce_audio_processors/processors/juce_AudioChannelSet.h @@ -145,10 +145,10 @@ public: }; /** Returns the name of a given channel type. For example, this method may return "Surround Left". */ - static const char* getChannelTypeName (ChannelType) noexcept; + static String getChannelTypeName (ChannelType); /** Returns the abbreviated name of a channel type. For example, this method may return "Ls". */ - static const char* getAbbreviatedChannelTypeName (ChannelType) noexcept; + static String getAbbreviatedChannelTypeName (ChannelType); //============================================================================== /** Adds a channel to the set. */ diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index b39e35d11..a96c7d98c 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -32,7 +32,7 @@ void JUCE_CALLTYPE AudioProcessor::setTypeOfNextNewPlugin (AudioProcessor::Wrapp AudioProcessor::AudioProcessor() : wrapperType (wrapperTypeBeingCreated.get()), playHead (nullptr), - sampleRate (0), + currentSampleRate (0), blockSize (0), latencySamples (0), #if JUCE_DEBUG @@ -42,23 +42,25 @@ AudioProcessor::AudioProcessor() nonRealtime (false), processingPrecision (singlePrecision) { - #if ! JucePlugin_IsMidiEffect #ifdef JucePlugin_PreferredChannelConfigurations const short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations }; #else const short channelConfigs[][2] = { {2, 2} }; #endif - int numChannelConfigs = sizeof (channelConfigs) / sizeof (*channelConfigs); - if (numChannelConfigs > 0) - { - #if ! JucePlugin_IsSynth - busArrangement.inputBuses.add (AudioProcessorBus ("Input", AudioChannelSet::canonicalChannelSet (channelConfigs[0][0]))); - #endif - busArrangement.outputBuses.add (AudioProcessorBus ("Output", AudioChannelSet::canonicalChannelSet (channelConfigs[0][1]))); - } - #endif + #if ! JucePlugin_IsMidiEffect + #if ! JucePlugin_IsSynth + busArrangement.inputBuses.add (AudioProcessorBus ("Input", AudioChannelSet::canonicalChannelSet (channelConfigs[0][0]))); + #endif + busArrangement.outputBuses.add (AudioProcessorBus ("Output", AudioChannelSet::canonicalChannelSet (channelConfigs[0][1]))); + #ifdef JucePlugin_PreferredChannelConfigurations + #if ! JucePlugin_IsSynth + AudioProcessor::setPreferredBusArrangement (true, 0, AudioChannelSet::stereo()); + #endif + AudioProcessor::setPreferredBusArrangement (false, 0, AudioChannelSet::stereo()); + #endif + #endif updateSpeakerFormatStrings(); } @@ -123,7 +125,7 @@ void AudioProcessor::setPlayConfigDetails (const int newNumIns, void AudioProcessor::setRateAndBufferSizeDetails (double newSampleRate, int newBlockSize) noexcept { - sampleRate = newSampleRate; + currentSampleRate = newSampleRate; blockSize = newBlockSize; } diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h index 8bf34569a..fa1b1cfb7 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -317,6 +317,14 @@ public: changing the channel layout of other buses, for example, if your plug-in requires the same number of input and output channels. + For most basic plug-ins, which do not require side-chains, aux buses or detailed audio + channel layout information, it is easier to specify the acceptable channel configurations + via the "PlugIn Channel Configurations" field in the Introjucer. In this case, you should + not override this method. + + If, on the other hand, you decide to override this method then you need to make sure that + "PlugIn Channel Configurations" field in the Introjucer is empty. + Note, that you must not do any heavy allocations or calculations in this callback as it may be called several hundred times during initialization. If you require any layout specific allocations then defer these to prepareToPlay callback. @@ -423,7 +431,7 @@ public: This can be called from your processBlock() method - it's not guaranteed to be valid at any other time, and may return 0 if it's unknown. */ - double getSampleRate() const noexcept { return sampleRate; } + double getSampleRate() const noexcept { return currentSampleRate; } /** Returns the current typical block size that is being used. @@ -453,9 +461,6 @@ public: */ void setLatencySamples (int newLatency); - /** Returns true if a silent input always produces a silent output. */ - virtual bool silenceInProducesSilenceOut() const = 0; - /** Returns the length of the filter's tail, in seconds. */ virtual double getTailLengthSeconds() const = 0; @@ -883,7 +888,7 @@ public: //============================================================================== /** This is called by the processor to specify its details before being played. Use this - version of the function if you are not interested in any sidechain or aux buses + version of the function if you are not interested in any sidechain and/or aux buses and do not care about the layout of channels. Otherwise use setRateAndBufferSizeDetails.*/ void setPlayConfigDetails (int numIns, int numOuts, double sampleRate, int blockSize); @@ -919,7 +924,7 @@ public: WrapperType wrapperType; //============================================================================== -#ifndef DOXYGEN + #ifndef DOXYGEN /** Deprecated: use getTotalNumInputChannels instead. */ JUCE_DEPRECATED_WITH_BODY (int getNumInputChannels() const noexcept, { return getTotalNumInputChannels(); }) JUCE_DEPRECATED_WITH_BODY (int getNumOutputChannels() const noexcept, { return getTotalNumOutputChannels(); }) @@ -946,7 +951,7 @@ public: the constructor. */ JUCE_DEPRECATED (virtual bool isInputChannelStereoPair (int index) const); JUCE_DEPRECATED (virtual bool isOutputChannelStereoPair (int index) const); -#endif + #endif //============================================================================== /** Helper function that just converts an xml element into a binary blob. @@ -982,7 +987,7 @@ private: #if ! JUCE_AUDIO_PROCESSOR_NO_GUI Component::SafePointer activeEditor; #endif - double sampleRate; + double currentSampleRate; int blockSize, latencySamples; #if JUCE_DEBUG bool textRecursionCheck; @@ -1005,6 +1010,9 @@ private: void disableNonMainBuses (bool isInput); void updateSpeakerFormatStrings(); + // This method is no longer used - you can delete it from your AudioProcessor classes. + JUCE_DEPRECATED_WITH_BODY (virtual bool silenceInProducesSilenceOut() const, { return false; }); + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessor) }; diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index 39beaba35..92b635165 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -1460,7 +1460,6 @@ void AudioProcessorGraph::processAudio (AudioBuffer& buffer, MidiBuff midiMessages.addEvents (currentMidiOutputBuffer, 0, buffer.getNumSamples(), 0); } -bool AudioProcessorGraph::silenceInProducesSilenceOut() const { return false; } double AudioProcessorGraph::getTailLengthSeconds() const { return 0; } bool AudioProcessorGraph::acceptsMidi() const { return true; } bool AudioProcessorGraph::producesMidi() const { return true; } @@ -1601,11 +1600,6 @@ void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer&, MidiBuffer&) override; bool supportsDoublePrecisionProcessing() const override; - bool silenceInProducesSilenceOut() const override; double getTailLengthSeconds() const override; bool acceptsMidi() const override; bool producesMidi() const override; @@ -356,7 +355,6 @@ public: void setNonRealtime (bool) noexcept override; void setPlayHead (AudioPlayHead*) override; - bool silenceInProducesSilenceOut() const override; double getTailLengthSeconds() const override; bool acceptsMidi() const override; bool producesMidi() const override; diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp index 4ee06b8b9..858a396b9 100644 --- a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp +++ b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp @@ -50,14 +50,17 @@ void AudioParameterFloat::setValue (float newValue) { value float AudioParameterFloat::getDefaultValue() const { return range.convertTo0to1 (defaultValue); } int AudioParameterFloat::getNumSteps() const { return AudioProcessorParameterWithID::getNumSteps(); } float AudioParameterFloat::getValueForText (const String& text) const { return range.convertTo0to1 (text.getFloatValue()); } -String AudioParameterFloat::getText (float v, int length) const { return String (range.convertFrom0to1 (v), 2).substring (0, length); } -AudioParameterFloat& AudioParameterFloat::operator= (float newValue) +String AudioParameterFloat::getText (float v, int length) const { - const float normalisedValue = range.convertTo0to1 (newValue); + String asText (range.convertFrom0to1 (v), 2); + return length > 0 ? asText.substring (0, length) : asText; +} - if (value != normalisedValue) - setValueNotifyingHost (normalisedValue); +AudioParameterFloat& AudioParameterFloat::operator= (float newValue) +{ + if (value != newValue) + setValueNotifyingHost (range.convertTo0to1 (newValue)); return *this; } @@ -87,10 +90,8 @@ String AudioParameterInt::getText (float v, int /*length*/) const { retur AudioParameterInt& AudioParameterInt::operator= (int newValue) { - const float normalisedValue = convertTo0to1 (newValue); - - if (value != normalisedValue) - setValueNotifyingHost (normalisedValue); + if (get() != newValue) + setValueNotifyingHost (convertTo0to1 (newValue)); return *this; } @@ -115,10 +116,8 @@ String AudioParameterBool::getText (float v, int /*length*/) const { retur AudioParameterBool& AudioParameterBool::operator= (bool newValue) { - const float normalisedValue = newValue ? 1.0f : 0.0f; - - if (value != normalisedValue) - setValueNotifyingHost (normalisedValue); + if (get() != newValue) + setValueNotifyingHost (newValue ? 1.0f : 0.0f); return *this; } @@ -148,10 +147,8 @@ String AudioParameterChoice::getText (float v, int /*length*/) const { retur AudioParameterChoice& AudioParameterChoice::operator= (int newValue) { - const float normalisedValue = convertTo0to1 (newValue); - - if (value != normalisedValue) - setValueNotifyingHost (normalisedValue); + if (getIndex() != newValue) + setValueNotifyingHost (convertTo0to1 (newValue)); return *this; } diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp index 76063062c..27a4ac9fd 100644 --- a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp +++ b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp @@ -258,21 +258,21 @@ void AudioProcessorValueTreeState::updateParameterConnectionsToChildTrees() } } -void AudioProcessorValueTreeState::valueTreePropertyChanged (ValueTree&, const Identifier& property) +void AudioProcessorValueTreeState::valueTreePropertyChanged (ValueTree& tree, const Identifier& property) { - if (property == idPropertyID) + if (property == idPropertyID && tree.hasType (valueType) && tree.getParent() == state) updateParameterConnectionsToChildTrees(); } -void AudioProcessorValueTreeState::valueTreeChildAdded (ValueTree& parent, ValueTree&) +void AudioProcessorValueTreeState::valueTreeChildAdded (ValueTree& parent, ValueTree& tree) { - if (parent == state) + if (parent == state && tree.hasType (valueType)) updateParameterConnectionsToChildTrees(); } -void AudioProcessorValueTreeState::valueTreeChildRemoved (ValueTree& parent, ValueTree&, int) +void AudioProcessorValueTreeState::valueTreeChildRemoved (ValueTree& parent, ValueTree& tree, int) { - if (parent == state) + if (parent == state && tree.hasType (valueType)) updateParameterConnectionsToChildTrees(); } diff --git a/source/modules/juce_core/containers/juce_HashMap.h b/source/modules/juce_core/containers/juce_HashMap.h index c1c098876..80eb79f20 100644 --- a/source/modules/juce_core/containers/juce_HashMap.h +++ b/source/modules/juce_core/containers/juce_HashMap.h @@ -424,6 +424,13 @@ public: return entry != nullptr ? entry->value : ValueType(); } + /** Resets the iterator to its starting position. */ + void reset() noexcept + { + entry = nullptr; + index = 0; + } + private: //============================================================================== const HashMap& hashMap; diff --git a/source/modules/juce_core/files/juce_File.cpp b/source/modules/juce_core/files/juce_File.cpp index 9737fa8aa..4463a04fd 100644 --- a/source/modules/juce_core/files/juce_File.cpp +++ b/source/modules/juce_core/files/juce_File.cpp @@ -70,8 +70,24 @@ File& File::operator= (File&& other) noexcept const File File::nonexistent; - //============================================================================== +static String removeEllipsis (const String& path) +{ + StringArray toks; + toks.addTokens (path, File::separatorString, StringRef()); + + for (int i = 1; i < toks.size(); ++i) + { + if (toks[i] == ".." && toks[i - 1] != "..") + { + toks.removeRange (i - 1, 2); + i = jmax (0, i - 2); + } + } + + return toks.joinIntoString (File::separatorString); +} + String File::parseAbsolutePath (const String& p) { if (p.isEmpty()) @@ -81,6 +97,9 @@ String File::parseAbsolutePath (const String& p) // Windows.. String path (p.replaceCharacter ('/', '\\')); + if (path.contains ("\\..\\")) + path = removeEllipsis (path); + if (path.startsWithChar (separator)) { if (path[1] != separator) @@ -120,6 +139,9 @@ String File::parseAbsolutePath (const String& p) String path (p); + if (path.contains ("/../")) + path = removeEllipsis (path); + if (path.startsWithChar ('~')) { if (path[1] == separator || path[1] == 0) @@ -347,62 +369,69 @@ int64 File::hashCode64() const { return fullPath.hashCode64(); } //============================================================================== bool File::isAbsolutePath (StringRef path) { - return path.text[0] == separator + const juce_wchar firstChar = *(path.text); + + return firstChar == separator #if JUCE_WINDOWS - || (path.isNotEmpty() && path.text[1] == ':'); + || (firstChar != 0 && path.text[1] == ':'); #else - || path.text[0] == '~'; + || firstChar == '~'; #endif } File File::getChildFile (StringRef relativePath) const { - if (isAbsolutePath (relativePath)) - return File (String (relativePath.text)); + String::CharPointerType r = relativePath.text; - if (relativePath[0] != '.') - return File (addTrailingSeparator (fullPath) + relativePath); + if (isAbsolutePath (r)) + return File (String (r)); - String path (fullPath); - - // It's relative, so remove any ../ or ./ bits at the start.. #if JUCE_WINDOWS - if (relativePath.text.indexOf ((juce_wchar) '/') >= 0) - return getChildFile (String (relativePath.text).replaceCharacter ('/', '\\')); + if (r.indexOf ((juce_wchar) '/') >= 0) + return getChildFile (String (r).replaceCharacter ('/', '\\')); #endif - while (relativePath[0] == '.') + String path (fullPath); + + while (*r == '.') { - const juce_wchar secondChar = relativePath[1]; + String::CharPointerType lastPos = r; + const juce_wchar secondChar = *++r; - if (secondChar == '.') + if (secondChar == '.') // remove "../" { - const juce_wchar thirdChar = relativePath[2]; + const juce_wchar thirdChar = *++r; - if (thirdChar == 0 || thirdChar == separator) + if (thirdChar == separator || thirdChar == 0) { const int lastSlash = path.lastIndexOfChar (separator); if (lastSlash >= 0) path = path.substring (0, lastSlash); - relativePath = relativePath.text + (thirdChar == 0 ? 2 : 3); + while (*r == separator) // ignore duplicate slashes + ++r; } else { + r = lastPos; break; } } - else if (secondChar == separator) + else if (secondChar == separator || secondChar == 0) // remove "./" { - relativePath = relativePath.text + 2; + while (*r == separator) // ignore duplicate slashes + ++r; } else { + r = lastPos; break; } } - return File (addTrailingSeparator (path) + relativePath); + path = addTrailingSeparator (path); + path.appendCharPointer (r); + return File (path); } File File::getSiblingFile (StringRef fileName) const @@ -1020,6 +1049,17 @@ public: expect (tempFile.getSiblingFile ("foo").isAChildOf (temp)); expect (tempFile.hasWriteAccess()); + expect (home.getChildFile (".") == home); + expect (home.getChildFile ("..") == home.getParentDirectory()); + expect (home.getChildFile (".xyz").getFileName() == ".xyz"); + expect (home.getChildFile ("..xyz").getFileName() == "..xyz"); + expect (home.getChildFile ("...xyz").getFileName() == "...xyz"); + expect (home.getChildFile ("./xyz") == home.getChildFile ("xyz")); + expect (home.getChildFile ("././xyz") == home.getChildFile ("xyz")); + expect (home.getChildFile ("../xyz") == home.getParentDirectory().getChildFile ("xyz")); + expect (home.getChildFile (".././xyz") == home.getParentDirectory().getChildFile ("xyz")); + expect (home.getChildFile ("./../xyz") == home.getParentDirectory().getChildFile ("xyz")); + { FileOutputStream fo (tempFile); fo.write ("0123456789", 10); diff --git a/source/modules/juce_core/files/juce_File.h b/source/modules/juce_core/files/juce_File.h index 8e729a5ce..9848c13da 100644 --- a/source/modules/juce_core/files/juce_File.h +++ b/source/modules/juce_core/files/juce_File.h @@ -48,7 +48,7 @@ public: //============================================================================== /** Creates an (invalid) file object. - The file is initially set to an empty path, so getFullPath() will return + The file is initially set to an empty path, so getFullPathName() will return an empty string, and comparing the file to File::nonexistent will return true. diff --git a/source/modules/juce_core/files/juce_WildcardFileFilter.h b/source/modules/juce_core/files/juce_WildcardFileFilter.h index af9a757b7..ada685068 100644 --- a/source/modules/juce_core/files/juce_WildcardFileFilter.h +++ b/source/modules/juce_core/files/juce_WildcardFileFilter.h @@ -66,10 +66,10 @@ public: //============================================================================== /** Returns true if the filename matches one of the patterns specified. */ - bool isFileSuitable (const File& file) const; + bool isFileSuitable (const File& file) const override; /** This always returns true. */ - bool isDirectorySuitable (const File& file) const; + bool isDirectorySuitable (const File& file) const override; private: //============================================================================== diff --git a/source/modules/juce_core/javascript/juce_Javascript.cpp b/source/modules/juce_core/javascript/juce_Javascript.cpp index 3c8c0114d..4b88a7882 100644 --- a/source/modules/juce_core/javascript/juce_Javascript.cpp +++ b/source/modules/juce_core/javascript/juce_Javascript.cpp @@ -44,7 +44,8 @@ #define JUCE_JS_KEYWORDS(X) \ X(var, "var") X(if_, "if") X(else_, "else") X(do_, "do") X(null_, "null") \ X(while_, "while") X(for_, "for") X(break_, "break") X(continue_, "continue") X(undefined, "undefined") \ - X(function, "function") X(return_, "return") X(true_, "true") X(false_, "false") X(new_, "new") + X(function, "function") X(return_, "return") X(true_, "true") X(false_, "false") X(new_, "new") \ + X(typeof_, "typeof") namespace TokenTypes { @@ -71,6 +72,7 @@ struct JavascriptEngine::RootObject : public DynamicObject setMethod ("trace", trace); setMethod ("charToInt", charToInt); setMethod ("parseInt", IntegerClass::parseInt); + setMethod ("typeof", typeof_internal); } Time timeout; @@ -97,12 +99,13 @@ struct JavascriptEngine::RootObject : public DynamicObject && (((a.isUndefined() || a.isVoid()) && (b.isUndefined() || b.isVoid())) || a == b); } - static String getTokenName (TokenType t) { return t[0] == '$' ? String (t + 1) : ("'" + String (t) + "'"); } - static bool isFunction (const var& v) { return dynamic_cast (v.getObject()) != nullptr; } - 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, const Identifier& i) { return o->getProperties().getVarPointer (i); } + static String getTokenName (TokenType t) { return t[0] == '$' ? String (t + 1) : ("'" + String (t) + "'"); } + static bool isFunction (const var& v) noexcept { return dynamic_cast (v.getObject()) != nullptr; } + static bool isNumeric (const var& v) noexcept { return v.isInt() || v.isDouble() || v.isInt64() || v.isBool(); } + static bool isNumericOrUndefined (const var& v) noexcept { return isNumeric (v) || 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, const Identifier& i) noexcept { return o->getProperties().getVarPointer (i); } //============================================================================== struct CodeLocation @@ -1079,7 +1082,7 @@ struct JavascriptEngine::RootObject : public DynamicObject if (matchIf (TokenTypes::while_)) return parseDoOrWhileLoop (false); if (matchIf (TokenTypes::do_)) return parseDoOrWhileLoop (true); if (matchIf (TokenTypes::for_)) return parseForLoop(); - if (matchIf (TokenTypes::return_)) return new ReturnStatement (location, matchIf (TokenTypes::semicolon) ? new Expression (location) : parseExpression()); + if (matchIf (TokenTypes::return_)) return parseReturn(); if (matchIf (TokenTypes::break_)) return new BreakStatement (location); if (matchIf (TokenTypes::continue_)) return new ContinueStatement (location); if (matchIf (TokenTypes::function)) return parseFunction(); @@ -1111,6 +1114,16 @@ struct JavascriptEngine::RootObject : public DynamicObject return s.release(); } + Statement* parseReturn() + { + if (matchIf (TokenTypes::semicolon)) + return new ReturnStatement (location, new Expression (location)); + + ReturnStatement* r = new ReturnStatement (location, parseExpression()); + matchIf (TokenTypes::semicolon); + return r; + } + Statement* parseVar() { ScopedPointer s (new VarStatement (location)); @@ -1345,12 +1358,21 @@ struct JavascriptEngine::RootObject : public DynamicObject return new PostAssignment (location, e, new OpType (location, lhs2, one)); } + Expression* parseTypeof() + { + ScopedPointer f (new FunctionCall (location)); + f->object = new UnqualifiedName (location, "typeof"); + f->arguments.add (parseExpression()); + return f.release(); + } + Expression* parseUnary() { if (matchIf (TokenTypes::minus)) { ExpPtr a (new LiteralValue (location, (int) 0)), b (parseUnary()); return new SubtractionOp (location, a, b); } if (matchIf (TokenTypes::logicalNot)) { ExpPtr a (new LiteralValue (location, (int) 0)), b (parseUnary()); return new EqualsOp (location, a, b); } if (matchIf (TokenTypes::plusplus)) return parsePreIncDec(); if (matchIf (TokenTypes::minusminus)) return parsePreIncDec(); + if (matchIf (TokenTypes::typeof_)) return parseTypeof(); return parseFactor(); } @@ -1478,6 +1500,7 @@ struct JavascriptEngine::RootObject : public DynamicObject setMethod ("contains", contains); setMethod ("remove", remove); setMethod ("join", join); + setMethod ("push", push); } static Identifier getClassName() { static const Identifier i ("Array"); return i; } @@ -1508,6 +1531,19 @@ struct JavascriptEngine::RootObject : public DynamicObject return strings.joinIntoString (getString (a, 0)); } + + static var push (Args a) + { + if (Array* array = a.thisObject.getArray()) + { + for (int i = 0; i < a.numArguments; ++i) + array->add (a.arguments[i]); + + return array->size(); + } + + return var::undefined(); + } }; //============================================================================== @@ -1638,6 +1674,19 @@ struct JavascriptEngine::RootObject : public DynamicObject static var trace (Args a) { Logger::outputDebugString (JSON::toString (a.thisObject)); return var::undefined(); } static var charToInt (Args a) { return (int) (getString (a, 0)[0]); } + static var typeof_internal (Args a) + { + var v (get (a, 0)); + + if (v.isVoid()) return "void"; + if (v.isString()) return "string"; + if (isNumeric (v)) return "number"; + if (isFunction (v) || v.isMethod()) return "function"; + if (v.isObject()) return "object"; + + return "undefined"; + } + static var exec (Args a) { if (RootObject* root = dynamic_cast (a.thisObject.getObject())) diff --git a/source/modules/juce_core/juce_core.cpp b/source/modules/juce_core/juce_core.cpp index c43d0cd8e..b052bf54e 100644 --- a/source/modules/juce_core/juce_core.cpp +++ b/source/modules/juce_core/juce_core.cpp @@ -35,7 +35,10 @@ #error "Incorrect use of JUCE cpp file" #endif -#include "native/juce_BasicNativeHeaders.h" +#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 +#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 +#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 + #include "juce_core.h" #include @@ -80,6 +83,7 @@ #if JUCE_LINUX #include #include + #include #if JUCE_USE_CURL #include @@ -140,6 +144,7 @@ namespace juce #include "maths/juce_Expression.cpp" #include "maths/juce_Random.cpp" #include "memory/juce_MemoryBlock.cpp" +#include "misc/juce_RuntimePermissions.cpp" #include "misc/juce_Result.cpp" #include "misc/juce_Uuid.cpp" #include "network/juce_MACAddress.cpp" @@ -180,10 +185,6 @@ namespace juce #include "files/juce_WildcardFileFilter.cpp" //============================================================================== -#if JUCE_MAC || JUCE_IOS -#include "native/juce_osx_ObjCHelpers.h" -#endif - #if JUCE_ANDROID #include "native/juce_android_JNIHelpers.h" #endif @@ -203,7 +204,6 @@ namespace juce //============================================================================== #elif JUCE_WINDOWS -#include "native/juce_win32_ComSmartPtr.h" #include "native/juce_win32_Files.cpp" #include "native/juce_win32_Network.cpp" #include "native/juce_win32_Registry.cpp" @@ -229,6 +229,7 @@ namespace juce #include "native/juce_android_Network.cpp" #include "native/juce_android_SystemStats.cpp" #include "native/juce_android_Threads.cpp" +#include "native/juce_android_RuntimePermissions.cpp" #endif diff --git a/source/modules/juce_core/juce_core.h b/source/modules/juce_core/juce_core.h index e9beac3d3..7f7523ed8 100644 --- a/source/modules/juce_core/juce_core.h +++ b/source/modules/juce_core/juce_core.h @@ -41,7 +41,7 @@ #include "system/juce_TargetPlatform.h" -//============================================================================= +//============================================================================== /** Config: JUCE_FORCE_DEBUG Normally, JUCE_DEBUG is set to 1 or 0 based on compiler and project settings, @@ -51,7 +51,7 @@ //#define JUCE_FORCE_DEBUG 0 #endif -//============================================================================= +//============================================================================== /** Config: JUCE_LOG_ASSERTIONS If this flag is enabled, the jassert and jassertfalse macros will always use Logger::writeToLog() @@ -71,7 +71,7 @@ #endif #endif -//============================================================================= +//============================================================================== /** Config: JUCE_CHECK_MEMORY_LEAKS Enables a memory-leak check for certain objects when the app terminates. See the LeakedObjectDetector @@ -81,7 +81,7 @@ #define JUCE_CHECK_MEMORY_LEAKS 1 #endif -//============================================================================= +//============================================================================== /** Config: JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES In a Visual C++ build, this can be used to stop the required system libs being @@ -130,8 +130,12 @@ #define JUCE_STRING_UTF_TYPE 8 #endif -//============================================================================= -//============================================================================= +//============================================================================== +//============================================================================== + +#if JUCE_CORE_INCLUDE_NATIVE_HEADERS + #include "native/juce_BasicNativeHeaders.h" +#endif #include "system/juce_StandardHeader.h" @@ -188,6 +192,7 @@ extern JUCE_API void JUCE_CALLTYPE logAssertion (const char* file, int line) noe #include "threads/juce_CriticalSection.h" #include "maths/juce_Range.h" #include "maths/juce_NormalisableRange.h" +#include "maths/juce_StatisticsAccumulator.h" #include "containers/juce_ElementComparator.h" #include "containers/juce_ArrayAllocationBase.h" #include "containers/juce_Array.h" @@ -237,6 +242,7 @@ extern JUCE_API void JUCE_CALLTYPE logAssertion (const char* file, int line) noe #include "maths/juce_BigInteger.h" #include "maths/juce_Expression.h" #include "maths/juce_Random.h" +#include "misc/juce_RuntimePermissions.h" #include "misc/juce_Uuid.h" #include "misc/juce_WindowsRegistry.h" #include "system/juce_SystemStats.h" @@ -269,6 +275,19 @@ extern JUCE_API void JUCE_CALLTYPE logAssertion (const char* file, int line) noe #include "containers/juce_PropertySet.h" #include "memory/juce_SharedResourcePointer.h" +#if JUCE_CORE_INCLUDE_OBJC_HELPERS && (JUCE_MAC || JUCE_IOS) + #include "native/juce_osx_ObjCHelpers.h" +#endif + +#if JUCE_CORE_INCLUDE_COM_SMART_PTR && JUCE_WINDOWS + #include "native/juce_win32_ComSmartPtr.h" +#endif + +#if JUCE_CORE_INCLUDE_JNI_HELPERS && JUCE_ANDROID + #include "native/juce_android_JNIHelpers.h" +#endif + + #ifndef DOXYGEN /* As the very long class names here try to explain, the purpose of this code is to cause diff --git a/source/modules/juce_core/maths/juce_BigInteger.cpp b/source/modules/juce_core/maths/juce_BigInteger.cpp index f03710156..0e2b1d938 100644 --- a/source/modules/juce_core/maths/juce_BigInteger.cpp +++ b/source/modules/juce_core/maths/juce_BigInteger.cpp @@ -315,7 +315,7 @@ inline static int highestBitInInt (uint32 n) noexcept { jassert (n != 0); // (the built-in functions may not work for n = 0) - #if JUCE_GCC + #if JUCE_GCC || JUCE_CLANG return 31 - __builtin_clz (n); #elif JUCE_USE_MSVC_INTRINSICS unsigned long highest; diff --git a/source/modules/juce_core/maths/juce_MathsFunctions.h b/source/modules/juce_core/maths/juce_MathsFunctions.h index 9d9a142cc..de92d9718 100644 --- a/source/modules/juce_core/maths/juce_MathsFunctions.h +++ b/source/modules/juce_core/maths/juce_MathsFunctions.h @@ -324,9 +324,9 @@ template <> inline float juce_hypot (float a, float b) noexcept { #if JUCE_MSVC - return (_hypotf (a, b)); + return _hypotf (a, b); #else - return (hypotf (a, b)); + return hypotf (a, b); #endif } #endif @@ -356,12 +356,16 @@ const float float_Pi = 3.14159265358979323846f; /** Converts an angle in degrees to radians. */ -template -FloatType degreesToRadians (FloatType degrees) noexcept { return degrees * static_cast (double_Pi / 180.0); } +inline float degreesToRadians (float degrees) noexcept { return degrees * (float_Pi / 180.0f); } + +/** Converts an angle in degrees to radians. */ +inline double degreesToRadians (double degrees) noexcept { return degrees * (double_Pi / 180.0); } /** Converts an angle in radians to degrees. */ -template -FloatType radiansToDegrees (FloatType radians) noexcept { return radians * static_cast (180.0 / double_Pi); } +inline float radiansToDegrees (float radians) noexcept { return radians * (180.0f / float_Pi); } + +/** Converts an angle in radians to degrees. */ +inline double radiansToDegrees (double radians) noexcept { return radians * (180.0 / double_Pi); } //============================================================================== diff --git a/source/modules/juce_core/maths/juce_StatisticsAccumulator.h b/source/modules/juce_core/maths/juce_StatisticsAccumulator.h new file mode 100644 index 000000000..43eba3e77 --- /dev/null +++ b/source/modules/juce_core/maths/juce_StatisticsAccumulator.h @@ -0,0 +1,145 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2016 - ROLI 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_STATISTICSACCUMULATOR_H_INCLUDED +#define JUCE_STATISTICSACCUMULATOR_H_INCLUDED + + +//============================================================================== +/** + A class that measures various statistics about a series of floating point + values that it is given. +*/ +template +class StatisticsAccumulator +{ +public: + //============================================================================== + /** Constructs a new StatisticsAccumulator. */ + StatisticsAccumulator() noexcept + : count (0), + minimum ( std::numeric_limits::infinity()), + maximum (-std::numeric_limits::infinity()) + {} + + //============================================================================== + /** Add a new value to the accumulator. + This will update all running statistics accordingly. + */ + void addValue (FloatType v) noexcept + { + jassert (juce_isfinite (v)); + + sum += v; + sumSquares += v * v; + ++count; + + if (v > maximum) maximum = v; + if (v < minimum) minimum = v; + } + + /** Reset the accumulator. + This will reset all currently saved statistcs. + */ + void reset() noexcept { *this = StatisticsAccumulator(); } + + //============================================================================== + /** Returns the average (arithmetic mean) of all previously added values. + If no values have been added yet, this will return zero. + */ + FloatType getAverage() const noexcept + { + return count > 0 ? sum / (FloatType) count + : FloatType(); + } + + /** Returns the variance of all previously added values. + If no values have been added yet, this will return zero. + */ + FloatType getVariance() const noexcept + { + return count > 0 ? (sumSquares - sum * sum / (FloatType) count) / (FloatType) count + : FloatType(); + } + + /** Returns the standard deviation of all previously added values. + If no values have been added yet, this will return zero. + */ + FloatType getStandardDeviation() const noexcept + { + return std::sqrt (getVariance()); + } + + /** Returns the smallest of all previously added values. + If no values have been added yet, this will return positive infinity. + */ + FloatType getMinValue() const noexcept + { + return minimum; + } + + /** Returns the largest of all previously added values. + If no values have been added yet, this will return negative infinity. + */ + FloatType getMaxValue() const noexcept + { + return maximum; + } + + /** Returns how many values have been added to this accumulator. */ + size_t getCount() const noexcept + { + return count; + } + +private: + //============================================================================== + struct KahanSum + { + KahanSum() noexcept : sum(), error() {} + operator FloatType() const noexcept { return sum; } + + void JUCE_NO_ASSOCIATIVE_MATH_OPTIMISATIONS operator+= (FloatType value) noexcept + { + FloatType correctedValue = value - error; + FloatType newSum = sum + correctedValue; + error = (newSum - sum) - correctedValue; + sum = newSum; + } + + FloatType sum, error; + }; + + //============================================================================== + size_t count; + KahanSum sum, sumSquares; + FloatType minimum, maximum; +}; + + +#endif // JUCE_STATISTICSACCUMULATOR_H_INCLUDED diff --git a/source/modules/juce_core/memory/juce_Atomic.h b/source/modules/juce_core/memory/juce_Atomic.h index 970d4d38e..1e8a65cc4 100644 --- a/source/modules/juce_core/memory/juce_Atomic.h +++ b/source/modules/juce_core/memory/juce_Atomic.h @@ -201,7 +201,7 @@ private: #endif //============================================================================== -#elif (JUCE_GCC || JUCE_CLANG) && ! JUCE_MSVC +#elif JUCE_GCC || JUCE_CLANG #define JUCE_ATOMICS_GCC 1 // GCC with intrinsics #if JUCE_IOS || JUCE_ANDROID // (64-bit ops will compile but not link on these mobile OSes) diff --git a/source/modules/juce_core/memory/juce_ByteOrder.h b/source/modules/juce_core/memory/juce_ByteOrder.h index 680f432f3..02c0f71ae 100644 --- a/source/modules/juce_core/memory/juce_ByteOrder.h +++ b/source/modules/juce_core/memory/juce_ByteOrder.h @@ -157,7 +157,7 @@ inline uint32 ByteOrder::swap (uint32 n) noexcept { #if JUCE_MAC || JUCE_IOS return OSSwapInt32 (n); - #elif JUCE_GCC && JUCE_INTEL && ! JUCE_NO_INLINE_ASM + #elif (JUCE_GCC || JUCE_CLANG) && JUCE_INTEL && ! JUCE_NO_INLINE_ASM asm("bswap %%eax" : "=a"(n) : "a"(n)); return n; #elif JUCE_USE_MSVC_INTRINSICS diff --git a/source/modules/juce_core/memory/juce_OptionalScopedPointer.h b/source/modules/juce_core/memory/juce_OptionalScopedPointer.h index fd0d5f6a5..46fa34cd5 100644 --- a/source/modules/juce_core/memory/juce_OptionalScopedPointer.h +++ b/source/modules/juce_core/memory/juce_OptionalScopedPointer.h @@ -180,6 +180,12 @@ private: //============================================================================== ScopedPointer object; bool shouldDelete; + + // This is here to avoid people accidentally taking a second owned copy of + // a scoped pointer, which is almost certainly not what you intended to do! + // If you hit a problem with this, you probably meant to say + // myPointer.setOwned (myScopedPointer.release()) + void setOwned (const ScopedPointer&) JUCE_DELETED_FUNCTION; }; diff --git a/source/modules/juce_core/misc/juce_RuntimePermissions.cpp b/source/modules/juce_core/misc/juce_RuntimePermissions.cpp new file mode 100644 index 000000000..c9e38ea0e --- /dev/null +++ b/source/modules/juce_core/misc/juce_RuntimePermissions.cpp @@ -0,0 +1,48 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2016 - ROLI 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 + + ============================================================================== +*/ + +#if ! JUCE_ANDROID // We currently don't request runtime permissions on any other platform + // than Android, so this file contains a dummy implementation for those. + // This may change in the future. + +void RuntimePermissions::request (PermissionID /*permission*/, Callback callback) +{ + callback (true); +} + +bool RuntimePermissions::isRequired (PermissionID /*permission*/) +{ + return false; +} + +bool RuntimePermissions::isGranted (PermissionID /*permission*/) +{ + return true; +} + +#endif diff --git a/source/modules/juce_core/misc/juce_RuntimePermissions.h b/source/modules/juce_core/misc/juce_RuntimePermissions.h new file mode 100644 index 000000000..814cecc69 --- /dev/null +++ b/source/modules/juce_core/misc/juce_RuntimePermissions.h @@ -0,0 +1,131 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2016 - ROLI 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_RUNTIMEPERMISSIONS_H_INCLUDED +#define JUCE_RUNTIMEPERMISSIONS_H_INCLUDED + +//============================================================================== +/** + Class to handle app runtime permissions for certain functionality on some platforms. + + The use of this class is currently only required if the app should run on + Android API level 23 and higher. + + On lower API levels, the permissions are specified in the app manifest. On iOS, + runtime permission requests are handled automatically by the Apple APIs and not + manually in the app code. On Windows, OS X, and Linux, runtime permissions are not + used at all. In all these cases, request() will simply call through to the + callback with no overhead and pass true (making it safe to use on all platforms). + + For example, to enable audio recording on Android in your cross-platform app, + you could modify your code as follows: + + Old code: + + audioDeviceManager.initialise (2, 2, nullptr, true, String(), nullptr); + + New code: + + RuntimePermissions::request ( + RuntimePermissions::audioRecording, + [this] (bool wasGranted) + { + if (! wasGranted) + { + // e.g. display an error or initialise with 0 input channels + return; + } + + audioDeviceManager.initialise (2, 2, nullptr, true, String(), nullptr); + } + ); +*/ +class JUCE_API RuntimePermissions +{ +public: + //========================================================================== + enum PermissionID + { + /** Permission to access the microphone (required on Android). + You need to request this, for example, to initialise an AudioDeviceManager with + a non-zero number of input channels, and to open the default audio input device. + */ + recordAudio = 1, + + /** Permission to scan for and pair to Bluetooth MIDI devices (required on Android). + You need to request this before calling BluetoothMidiDevicePairingDialogue::open(), + otherwise no devices will be found. + */ + bluetoothMidi = 2, + }; + + //========================================================================== + /** Function type of runtime permission request callbacks. */ + #if JUCE_COMPILER_SUPPORTS_LAMBDAS + typedef std::function Callback; + #else + typedef void (*Callback) (bool); + #endif + + //========================================================================== + /** Call this method to request a runtime permission. + + @param permission The PermissionID of the permission you want to request. + + @param callback The callback to be called after the request has been granted + or denied; the argument passed will be true if the permission + has been granted and false otherwise. + + If no runtime request is required or possible to obtain the permission, the + callback will be called immediately. The argument passed in will be true + if the permission is granted or no permission is required on this platform, + and false otherwise. + + If a runtime request is required to obtain the permission, the callback + will be called asynchronously after the OS has granted or denied the requested + permission (typically by displaying a dialog box to the user and waiting until + the user has responded). + */ + static void request (PermissionID permission, Callback callback); + + /** Returns whether a runtime request is required to obtain the permission + on the current platform. + */ + static bool isRequired (PermissionID permission); + + /** Returns true if the app has been already granted this permission, either + via a previous runtime request or otherwise, or no permission is necessary. + + Note that this can be false even if isRequired returns false. In this case, + the permission can not be obtained at all at runtime. + */ + static bool isGranted (PermissionID permission); +}; + + +#endif // JUCE_RUNTIMEPERMISSIONS_H_INCLUDED diff --git a/source/modules/juce_core/native/java/AndroidRuntimePermissions.java b/source/modules/juce_core/native/java/AndroidRuntimePermissions.java new file mode 100644 index 000000000..b2a8a461c --- /dev/null +++ b/source/modules/juce_core/native/java/AndroidRuntimePermissions.java @@ -0,0 +1,14 @@ + private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback); + + @Override + public void onRequestPermissionsResult (int permissionID, String permissions[], int[] grantResults) + { + boolean permissionsGranted = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED); + + if (! permissionsGranted) + Log.d ("JUCE", "onRequestPermissionsResult: runtime permission was DENIED: " + getAndroidPermissionName (permissionID)); + + Long ptrToCallback = permissionCallbackPtrMap.get (permissionID); + permissionCallbackPtrMap.remove (permissionID); + androidRuntimePermissionsCallback (permissionsGranted, ptrToCallback); + } diff --git a/source/modules/juce_core/native/java/JuceAppActivity.java b/source/modules/juce_core/native/java/JuceAppActivity.java index f61b29a1e..63f80e09f 100644 --- a/source/modules/juce_core/native/java/JuceAppActivity.java +++ b/source/modules/juce_core/native/java/JuceAppActivity.java @@ -30,14 +30,14 @@ import android.content.DialogInterface; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.Looper; import android.os.Handler; -import android.os.Build; -import android.os.Process; import android.os.ParcelUuid; +import android.os.Environment; import android.view.*; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; @@ -49,19 +49,16 @@ import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; import java.lang.Runnable; -import java.util.List; -import java.util.Arrays; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Hashtable; -import java.util.TimerTask; +import java.util.*; import java.io.*; import java.net.URL; import java.net.HttpURLConnection; import android.media.AudioManager; import android.media.MediaScannerConnection; import android.media.MediaScannerConnection.MediaScannerConnectionClient; - +import android.support.v4.content.ContextCompat; +import android.support.v4.app.ActivityCompat; +import android.Manifest; $$JuceAndroidMidiImports$$ // If you get an error here, you need to re-save your project with the introjucer! @@ -74,6 +71,73 @@ public class JuceAppActivity extends Activity System.loadLibrary ("juce_jni"); } + //============================================================================== + public boolean isPermissionDeclaredInManifest (int permissionID) + { + String permissionToCheck = getAndroidPermissionName(permissionID); + + try + { + PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS); + + if (info.requestedPermissions != null) + for (String permission : info.requestedPermissions) + if (permission.equals (permissionToCheck)) + return true; + } + catch (PackageManager.NameNotFoundException e) + { + Log.d ("JUCE", "isPermissionDeclaredInManifest: PackageManager.NameNotFoundException = " + e.toString()); + } + + Log.d ("JUCE", "isPermissionDeclaredInManifest: could not find requested permission " + permissionToCheck); + return false; + } + + //============================================================================== + // these have to match the values of enum PermissionID in C++ class RuntimePermissions: + private static final int JUCE_PERMISSIONS_RECORD_AUDIO = 1; + private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2; + + private static String getAndroidPermissionName (int permissionID) + { + switch (permissionID) + { + case JUCE_PERMISSIONS_RECORD_AUDIO: return Manifest.permission.RECORD_AUDIO; + case JUCE_PERMISSIONS_BLUETOOTH_MIDI: return Manifest.permission.ACCESS_COARSE_LOCATION; + } + + // unknown permission ID! + assert false; + return new String(); + } + + public boolean isPermissionGranted (int permissionID) + { + return ContextCompat.checkSelfPermission (this, getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED; + } + + private Map permissionCallbackPtrMap; + + public void requestRuntimePermission (int permissionID, long ptrToCallback) + { + String permissionName = getAndroidPermissionName (permissionID); + + if (ContextCompat.checkSelfPermission (this, permissionName) != PackageManager.PERMISSION_GRANTED) + { + // remember callbackPtr, request permissions, and let onRequestPermissionResult call callback asynchronously + permissionCallbackPtrMap.put (permissionID, ptrToCallback); + ActivityCompat.requestPermissions (this, new String[]{permissionName}, permissionID); + } + else + { + // permissions were already granted before, we can call callback directly + androidRuntimePermissionsCallback (true, ptrToCallback); + } + } + + $$JuceAndroidRuntimePermissionsCode$$ // If you get an error here, you need to re-save your project with the introjucer! + //============================================================================== public static class MidiPortID extends Object { @@ -132,10 +196,13 @@ public class JuceAppActivity extends Activity super.onCreate (savedInstanceState); isScreenSaverEnabled = true; + hideActionBar(); viewHolder = new ViewHolder (this); setContentView (viewHolder); setVolumeControlStream (AudioManager.STREAM_MUSIC); + + permissionCallbackPtrMap = new HashMap(); } @Override @@ -174,6 +241,49 @@ public class JuceAppActivity extends Activity getApplicationInfo().dataDir); } + private void hideActionBar() + { + // get "getActionBar" method + java.lang.reflect.Method getActionBarMethod = null; + try + { + getActionBarMethod = this.getClass().getMethod ("getActionBar"); + } + catch (SecurityException e) { return; } + catch (NoSuchMethodException e) { return; } + if (getActionBarMethod == null) return; + + // invoke "getActionBar" method + Object actionBar = null; + try + { + actionBar = getActionBarMethod.invoke (this); + } + catch (java.lang.IllegalArgumentException e) { return; } + catch (java.lang.IllegalAccessException e) { return; } + catch (java.lang.reflect.InvocationTargetException e) { return; } + if (actionBar == null) return; + + // get "hide" method + java.lang.reflect.Method actionBarHideMethod = null; + try + { + actionBarHideMethod = actionBar.getClass().getMethod ("hide"); + } + catch (SecurityException e) { return; } + catch (NoSuchMethodException e) { return; } + if (actionBarHideMethod == null) return; + + // invoke "hide" method + try + { + actionBarHideMethod.invoke (actionBar); + } + catch (java.lang.IllegalArgumentException e) {} + catch (java.lang.IllegalAccessException e) {} + catch (java.lang.reflect.InvocationTargetException e) {} + } + //============================================================================== private native void launchApp (String appFile, String appDataDir); private native void quitApp(); @@ -691,7 +801,7 @@ public class JuceAppActivity extends Activity int format, int width, int height); } - public NativeSurfaceView createNativeSurfaceView(long nativeSurfacePtr) + public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr) { return new NativeSurfaceView (this, nativeSurfacePtr); } @@ -917,6 +1027,17 @@ public class JuceAppActivity extends Activity : locale.getDisplayLanguage (java.util.Locale.US); } + private static final String getFileLocation (String type) + { + return Environment.getExternalStoragePublicDirectory (type).getAbsolutePath(); + } + + public static final String getDocumentsFolder() { return Environment.getDataDirectory().getAbsolutePath(); } + public static final String getPicturesFolder() { return getFileLocation (Environment.DIRECTORY_PICTURES); } + public static final String getMusicFolder() { return getFileLocation (Environment.DIRECTORY_MUSIC); } + public static final String getMoviesFolder() { return getFileLocation (Environment.DIRECTORY_MOVIES); } + public static final String getDownloadsFolder() { return getFileLocation (Environment.DIRECTORY_DOWNLOADS); } + //============================================================================== private final class SingleMediaScanner implements MediaScannerConnectionClient { @@ -1041,23 +1162,24 @@ public class JuceAppActivity extends Activity return null; java.lang.reflect.Method method; - try { + + try + { method = obj.getClass().getMethod ("getProperty", String.class); - } catch (SecurityException e) { - return null; - } catch (NoSuchMethodException e) { - return null; } + catch (SecurityException e) { return null; } + catch (NoSuchMethodException e) { return null; } if (method == null) return null; - try { + try + { return (String) method.invoke (obj, property); - } catch (java.lang.IllegalArgumentException e) { - } catch (java.lang.IllegalAccessException e) { - } catch (java.lang.reflect.InvocationTargetException e) { } + catch (java.lang.IllegalArgumentException e) {} + catch (java.lang.IllegalAccessException e) {} + catch (java.lang.reflect.InvocationTargetException e) {} return null; } @@ -1075,8 +1197,9 @@ public class JuceAppActivity extends Activity private static class JuceThread extends Thread { - public JuceThread (long host) + public JuceThread (long host, String threadName, long threadStackSize) { + super (null, null, threadName, threadStackSize); _this = host; } @@ -1089,9 +1212,8 @@ public class JuceAppActivity extends Activity private long _this; } - public final Thread createNewThread(long host) + public final Thread createNewThread(long host, String threadName, long threadStackSize) { - return new JuceThread(host); + return new JuceThread(host, threadName, threadStackSize); } - } diff --git a/source/modules/juce_core/native/juce_BasicNativeHeaders.h b/source/modules/juce_core/native/juce_BasicNativeHeaders.h index 1856afcea..bdf0d60ea 100644 --- a/source/modules/juce_core/native/juce_BasicNativeHeaders.h +++ b/source/modules/juce_core/native/juce_BasicNativeHeaders.h @@ -29,7 +29,6 @@ #ifndef JUCE_BASICNATIVEHEADERS_H_INCLUDED #define JUCE_BASICNATIVEHEADERS_H_INCLUDED -#include "../system/juce_TargetPlatform.h" #undef T //============================================================================== @@ -42,12 +41,8 @@ #import #include #else - #define Point CarbonDummyPointName - #define Component CarbonDummyCompName #import #import - #undef Point - #undef Component #include #endif diff --git a/source/modules/juce_core/native/juce_android_Files.cpp b/source/modules/juce_core/native/juce_android_Files.cpp index a3442149e..81701fe78 100644 --- a/source/modules/juce_core/native/juce_android_Files.cpp +++ b/source/modules/juce_core/native/juce_android_Files.cpp @@ -46,23 +46,27 @@ String File::getVersion() const return String(); } +static File getSpecialFile (jmethodID type) +{ + return File (juceString (LocalRef ((jstring) getEnv()->CallStaticObjectMethod (JuceAppActivity, type)))); +} + File File::getSpecialLocation (const SpecialLocationType type) { switch (type) { case userHomeDirectory: - case userDocumentsDirectory: - case userMusicDirectory: - case userMoviesDirectory: - case userPicturesDirectory: case userApplicationDataDirectory: case userDesktopDirectory: - return File (android.appDataDir); - case commonApplicationDataDirectory: - case commonDocumentsDirectory: return File (android.appDataDir); + case userDocumentsDirectory: + case commonDocumentsDirectory: return getSpecialFile (JuceAppActivity.getDocumentsFolder); + case userPicturesDirectory: return getSpecialFile (JuceAppActivity.getPicturesFolder); + case userMusicDirectory: return getSpecialFile (JuceAppActivity.getMusicFolder); + case userMoviesDirectory: return getSpecialFile (JuceAppActivity.getMoviesFolder); + case globalApplicationsDirectory: return File ("/system/app"); diff --git a/source/modules/juce_core/native/juce_android_JNIHelpers.h b/source/modules/juce_core/native/juce_android_JNIHelpers.h index 0776d7199..ca3f05610 100644 --- a/source/modules/juce_core/native/juce_android_JNIHelpers.h +++ b/source/modules/juce_core/native/juce_android_JNIHelpers.h @@ -266,6 +266,7 @@ extern AndroidSystem android; METHOD (createNativeSurfaceView, "createNativeSurfaceView", "(J)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$NativeSurfaceView;") \ METHOD (postMessage, "postMessage", "(J)V") \ METHOD (finish, "finish", "()V") \ + METHOD (setRequestedOrientation,"setRequestedOrientation", "(I)V") \ METHOD (getClipboardContent, "getClipboardContent", "()Ljava/lang/String;") \ METHOD (setClipboardContent, "setClipboardContent", "(Ljava/lang/String;)V") \ METHOD (excludeClipRegion, "excludeClipRegion", "(Landroid/graphics/Canvas;FFFF)V") \ @@ -276,6 +277,11 @@ extern AndroidSystem android; METHOD (showOkCancelBox, "showOkCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ METHOD (showYesNoCancelBox, "showYesNoCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ STATICMETHOD (getLocaleValue, "getLocaleValue", "(Z)Ljava/lang/String;") \ + STATICMETHOD (getDocumentsFolder, "getDocumentsFolder", "()Ljava/lang/String;") \ + STATICMETHOD (getPicturesFolder, "getPicturesFolder", "()Ljava/lang/String;") \ + STATICMETHOD (getMusicFolder, "getMusicFolder", "()Ljava/lang/String;") \ + STATICMETHOD (getDownloadsFolder, "getDownloadsFolder", "()Ljava/lang/String;") \ + STATICMETHOD (getMoviesFolder, "getMoviesFolder", "()Ljava/lang/String;") \ METHOD (scanFile, "scanFile", "(Ljava/lang/String;)V") \ METHOD (getTypeFaceFromAsset, "getTypeFaceFromAsset", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \ METHOD (getTypeFaceFromByteArray,"getTypeFaceFromByteArray","([B)Landroid/graphics/Typeface;") \ @@ -287,7 +293,10 @@ extern AndroidSystem android; METHOD (audioManagerGetProperty, "audioManagerGetProperty", "(Ljava/lang/String;)Ljava/lang/String;") \ METHOD (setCurrentThreadPriority, "setCurrentThreadPriority", "(I)I") \ METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \ - METHOD (createNewThread, "createNewThread", "(J)Ljava/lang/Thread;") \ + METHOD (createNewThread, "createNewThread", "(JLjava/lang/String;J)Ljava/lang/Thread;") \ + METHOD (requestRuntimePermission, "requestRuntimePermission", "(IJ)V" ) \ + METHOD (isPermissionGranted, "isPermissionGranted", "(I)Z" ) \ + METHOD (isPermissionDeclaredInManifest, "isPermissionDeclaredInManifest", "(I)Z" ) \ DECLARE_JNI_CLASS (JuceAppActivity, JUCE_ANDROID_ACTIVITY_CLASSPATH); #undef JNI_CLASS_MEMBERS diff --git a/source/modules/juce_core/native/juce_android_RuntimePermissions.cpp b/source/modules/juce_core/native/juce_android_RuntimePermissions.cpp new file mode 100644 index 000000000..d36841574 --- /dev/null +++ b/source/modules/juce_core/native/juce_android_RuntimePermissions.cpp @@ -0,0 +1,90 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2016 - ROLI 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 + + ============================================================================== +*/ + +namespace +{ + void handleAndroidCallback (bool permissionWasGranted, RuntimePermissions::Callback* callbackPtr) + { + if (callbackPtr == nullptr) + { + // got a nullptr passed in from java! this should never happen... + jassertfalse; + return; + } + + std::unique_ptr uptr (callbackPtr); + (*uptr) (permissionWasGranted); + } +} + +JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, + androidRuntimePermissionsCallback, + void, (JNIEnv* env, jobject /*javaObjectHandle*/, jboolean permissionsGranted, jlong callbackPtr)) +{ + setEnv (env); + handleAndroidCallback (permissionsGranted != 0, + reinterpret_cast (callbackPtr)); +} + +void RuntimePermissions::request (PermissionID permission, Callback callback) +{ + if (! android.activity.callBooleanMethod (JuceAppActivity.isPermissionDeclaredInManifest, (jint) permission)) + { + // Error! If you want to be able to request this runtime permission, you + // also need to declare it in your app's manifest. You can do so via + // the Introjucer. Otherwise this can't work. + jassertfalse; + + callback (false); + return; + } + + if (JUCE_ANDROID_API_VERSION < 23) + { + // There is no runtime permission system on API level below 23. As long as the + // permission is in the manifest (seems to be the case), we can simply ask Android + // if the app has the permission, and then directly call through to the callback. + callback (isGranted (permission)); + return; + } + + // we need to move the callback object to the heap so Java can keep track of the pointer + // and asynchronously pass it back to us (to be called and then deleted) + Callback* callbackPtr = new Callback (std::move (callback)); + android.activity.callVoidMethod (JuceAppActivity.requestRuntimePermission, permission, (jlong) callbackPtr); +} + +bool RuntimePermissions::isRequired (PermissionID /*permission*/) +{ + return JUCE_ANDROID_API_VERSION >= 23; +} + +bool RuntimePermissions::isGranted (PermissionID permission) +{ + return android.activity.callBooleanMethod (JuceAppActivity.isPermissionGranted, permission); +} diff --git a/source/modules/juce_core/native/juce_android_Threads.cpp b/source/modules/juce_core/native/juce_android_Threads.cpp index 585b97a7e..0636fe5c4 100644 --- a/source/modules/juce_core/native/juce_android_Threads.cpp +++ b/source/modules/juce_core/native/juce_android_Threads.cpp @@ -84,10 +84,8 @@ struct AndroidThreadData void JUCE_API juce_threadEntryPoint (void*); -extern "C" void* threadEntryProc (void*); -extern "C" void* threadEntryProc (void* userData) +void* threadEntryProc (AndroidThreadData* priv) { - ScopedPointer priv (reinterpret_cast (userData)); priv->tId = (Thread::ThreadID) pthread_self(); priv->eventSet.signal(); priv->eventGet.wait (-1); @@ -100,12 +98,10 @@ extern "C" void* threadEntryProc (void* userData) JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceThread), runThread, void, (JNIEnv* env, jobject device, jlong host)) { - // Java may create a Midi thread which JUCE doesn't know about and this callback may be - // received on this thread. Java will have already created a JNI Env for this new thread, - // which we need to tell Juce about + // This thread does not have a JNIEnv assigned to it yet. So assign it now. setEnv (env); - if (Thread* thread = reinterpret_cast (host)) + if (AndroidThreadData* thread = reinterpret_cast (host)) threadEntryProc (thread); } @@ -114,8 +110,11 @@ void Thread::launchThread() threadHandle = 0; ScopedPointer threadPrivateData = new AndroidThreadData (this); + const LocalRef jName (javaString (threadName)); - jobject juceNewThread = android.activity.callObjectMethod (JuceAppActivity.createNewThread, (jlong) threadPrivateData.get()); + jobject juceNewThread = android.activity.callObjectMethod (JuceAppActivity.createNewThread, + (jlong) threadPrivateData.get(), + jName.get(), (jlong) threadStackSize); if (jobject juceThread = getEnv()->NewGlobalRef (juceNewThread)) { diff --git a/source/modules/juce_core/native/juce_linux_CommonFile.cpp b/source/modules/juce_core/native/juce_linux_CommonFile.cpp index 9f2d3e938..31117cd99 100644 --- a/source/modules/juce_core/native/juce_linux_CommonFile.cpp +++ b/source/modules/juce_core/native/juce_linux_CommonFile.cpp @@ -63,7 +63,7 @@ static String getLinkedFile (const String& file) HeapBlock buffer (8194); const int numBytes = (int) readlink (file.toRawUTF8(), buffer, 8192); return String::fromUTF8 (buffer, jmax (0, numBytes)); -}; +} bool File::isSymbolicLink() const { diff --git a/source/modules/juce_core/native/juce_linux_SystemStats.cpp b/source/modules/juce_core/native/juce_linux_SystemStats.cpp index 85062bf2e..b7665f825 100644 --- a/source/modules/juce_core/native/juce_linux_SystemStats.cpp +++ b/source/modules/juce_core/native/juce_linux_SystemStats.cpp @@ -156,7 +156,10 @@ void CPUInformation::initialise() noexcept hasSSE3 = flags.contains ("sse3"); has3DNow = flags.contains ("3dnow"); hasSSSE3 = flags.contains ("ssse3"); + hasSSE41 = flags.contains ("sse4_1"); + hasSSE42 = flags.contains ("sse4_2"); hasAVX = flags.contains ("avx"); + hasAVX2 = flags.contains ("avx2"); numCpus = LinuxStatsHelpers::getCpuInfo ("processor").getIntValue() + 1; } diff --git a/source/modules/juce_core/native/juce_mac_Strings.mm b/source/modules/juce_core/native/juce_mac_Strings.mm index 53341e07f..a104da671 100644 --- a/source/modules/juce_core/native/juce_mac_Strings.mm +++ b/source/modules/juce_core/native/juce_mac_Strings.mm @@ -45,8 +45,14 @@ String String::fromCFString (CFStringRef cfString) CFStringRef String::toCFString() const { const char* const utf8 = toRawUTF8(); - return CFStringCreateWithBytes (kCFAllocatorDefault, (const UInt8*) utf8, - (CFIndex) strlen (utf8), kCFStringEncodingUTF8, false); + + if (CFStringRef result = CFStringCreateWithBytes (kCFAllocatorDefault, (const UInt8*) utf8, + (CFIndex) strlen (utf8), kCFStringEncodingUTF8, false)) + return result; + + // If CFStringCreateWithBytes fails, it probably means there was a UTF8 format + // error, so we'll return an empty string rather than a null pointer. + return String().toCFString(); } String String::convertToPrecomposedUnicode() const diff --git a/source/modules/juce_core/native/juce_mac_SystemStats.mm b/source/modules/juce_core/native/juce_mac_SystemStats.mm index 0767a7606..002ec0fda 100644 --- a/source/modules/juce_core/native/juce_mac_SystemStats.mm +++ b/source/modules/juce_core/native/juce_mac_SystemStats.mm @@ -81,27 +81,17 @@ void CPUInformation::initialise() noexcept has3DNow = (b & (1u << 31)) != 0; hasSSE3 = (c & (1u << 0)) != 0; hasSSSE3 = (c & (1u << 9)) != 0; + hasSSE41 = (c & (1u << 20)) != 0; + hasSSE42 = (c & (1u << 19)) != 0; hasAVX = (c & (1u << 28)) != 0; + + SystemStatsHelpers::doCPUID (a, b, c, d, 7); + hasAVX2 = (b & (1u << 5)) != 0; #endif numCpus = (int) [[NSProcessInfo processInfo] activeProcessorCount]; } -#if JUCE_MAC -struct RLimitInitialiser -{ - RLimitInitialiser() - { - rlimit lim; - getrlimit (RLIMIT_NOFILE, &lim); - lim.rlim_cur = lim.rlim_max = RLIM_INFINITY; - setrlimit (RLIMIT_NOFILE, &lim); - } -}; - -static RLimitInitialiser rLimitInitialiser; -#endif - //============================================================================== #if ! JUCE_IOS static String getOSXVersion() diff --git a/source/modules/juce_core/native/juce_posix_SharedCode.h b/source/modules/juce_core/native/juce_posix_SharedCode.h index 1b70e1688..f152a3f02 100644 --- a/source/modules/juce_core/native/juce_posix_SharedCode.h +++ b/source/modules/juce_core/native/juce_posix_SharedCode.h @@ -55,6 +55,7 @@ WaitableEvent::WaitableEvent (const bool useManualReset) noexcept pthread_mutexattr_setprotocol (&atts, PTHREAD_PRIO_INHERIT); #endif pthread_mutex_init (&mutex, &atts); + pthread_mutexattr_destroy (&atts); } WaitableEvent::~WaitableEvent() noexcept @@ -149,6 +150,45 @@ void JUCE_CALLTYPE Process::terminate() #endif } + +#if JUCE_MAC || JUCE_LINUX +bool Process::setMaxNumberOfFileHandles (int newMaxNumber) noexcept +{ + rlimit lim; + if (getrlimit (RLIMIT_NOFILE, &lim) == 0) + { + if (newMaxNumber <= 0 && lim.rlim_cur == RLIM_INFINITY && lim.rlim_max == RLIM_INFINITY) + return true; + + if (lim.rlim_cur >= (rlim_t) newMaxNumber) + return true; + } + + lim.rlim_cur = lim.rlim_max = newMaxNumber <= 0 ? RLIM_INFINITY : (rlim_t) newMaxNumber; + return setrlimit (RLIMIT_NOFILE, &lim) == 0; +} + +struct MaxNumFileHandlesInitialiser +{ + MaxNumFileHandlesInitialiser() noexcept + { + #ifndef JUCE_PREFERRED_MAX_FILE_HANDLES + enum { JUCE_PREFERRED_MAX_FILE_HANDLES = 8192 }; + #endif + + // Try to give our app a decent number of file handles by default + if (! Process::setMaxNumberOfFileHandles (0)) + { + for (int num = JUCE_PREFERRED_MAX_FILE_HANDLES; num > 256; num -= 1024) + if (Process::setMaxNumberOfFileHandles (num)) + break; + } + } +}; + +static MaxNumFileHandlesInitialiser maxNumFileHandlesInitialiser; +#endif + //============================================================================== const juce_wchar File::separator = '/'; const String File::separatorString ("/"); @@ -365,7 +405,7 @@ bool File::setFileTimesInternal (int64 modificationTime, int64 accessTime, int64 bool File::deleteFile() const { - if (! exists()) + if (! exists() && ! isSymbolicLink()) return true; if (isDirectory()) @@ -395,7 +435,7 @@ Result File::createDirectoryInternal (const String& fileName) const return getResultForReturnValue (mkdir (fileName.toUTF8(), 0777)); } -//===================================================================== +//============================================================================== int64 juce_fileSetPosition (void* handle, int64 pos) { if (handle != 0 && lseek (getFD (handle), pos, SEEK_SET) == pos) @@ -885,13 +925,25 @@ void Thread::launchThread() { threadHandle = 0; pthread_t handle = 0; + pthread_attr_t attr; + pthread_attr_t* attrPtr = nullptr; - if (pthread_create (&handle, 0, threadEntryProc, this) == 0) + if (pthread_attr_init (&attr) == 0) + { + attrPtr = &attr; + + pthread_attr_setstacksize (attrPtr, threadStackSize); + } + + if (pthread_create (&handle, attrPtr, threadEntryProc, this) == 0) { pthread_detach (handle); threadHandle = (void*) handle; threadId = (ThreadID) threadHandle; } + + if (attrPtr != nullptr) + pthread_attr_destroy (attrPtr); } void Thread::closeThreadHandle() @@ -1029,10 +1081,12 @@ public: ActiveProcess (const StringArray& arguments, int streamFlags) : childPID (0), pipeHandle (0), readHandle (0) { + String exe (arguments[0].unquoted()); + // Looks like you're trying to launch a non-existent exe or a folder (perhaps on OSX // you're trying to launch the .app folder rather than the actual binary inside it?) - jassert ((! arguments[0].containsChar ('/')) - || File::getCurrentWorkingDirectory().getChildFile (arguments[0]).existsAsFile()); + jassert (File::getCurrentWorkingDirectory().getChildFile (exe).existsAsFile() + || ! exe.containsChar (File::separator)); int pipeHandles[2] = { 0 }; @@ -1041,7 +1095,7 @@ public: Array argv; for (int i = 0; i < arguments.size(); ++i) if (arguments[i].isNotEmpty()) - argv.add (const_cast (arguments[i].toUTF8().getAddress())); + argv.add (const_cast (arguments[i].toRawUTF8())); argv.add (nullptr); @@ -1075,7 +1129,7 @@ public: close (pipeHandles[1]); #endif - if (execvp (argv[0], argv.getRawDataPointer()) < 0) + if (execvp (exe.toRawUTF8(), argv.getRawDataPointer())) _exit (-1); } else diff --git a/source/modules/juce_core/native/juce_win32_Registry.cpp b/source/modules/juce_core/native/juce_win32_Registry.cpp index 8615f4325..caf71e531 100644 --- a/source/modules/juce_core/native/juce_win32_Registry.cpp +++ b/source/modules/juce_core/native/juce_win32_Registry.cpp @@ -31,13 +31,7 @@ struct RegistryKeyWrapper RegistryKeyWrapper (String name, const bool createForWriting, const DWORD wow64Flags) : key (0), wideCharValueName (nullptr) { - HKEY rootKey = 0; - - if (name.startsWithIgnoreCase ("HKEY_CURRENT_USER\\")) rootKey = HKEY_CURRENT_USER; - else if (name.startsWithIgnoreCase ("HKEY_LOCAL_MACHINE\\")) rootKey = HKEY_LOCAL_MACHINE; - else if (name.startsWithIgnoreCase ("HKEY_CLASSES_ROOT\\")) rootKey = HKEY_CLASSES_ROOT; - - if (rootKey != 0) + if (HKEY rootKey = getRootKey (name)) { name = name.substring (name.indexOfChar ('\\') + 1); @@ -63,6 +57,21 @@ struct RegistryKeyWrapper RegCloseKey (key); } + static HKEY getRootKey (const String& name) noexcept + { + if (name.startsWithIgnoreCase ("HKEY_CURRENT_USER\\")) return HKEY_CURRENT_USER; + if (name.startsWithIgnoreCase ("HKCU\\")) return HKEY_CURRENT_USER; + if (name.startsWithIgnoreCase ("HKEY_LOCAL_MACHINE\\")) return HKEY_LOCAL_MACHINE; + if (name.startsWithIgnoreCase ("HKLM\\")) return HKEY_LOCAL_MACHINE; + if (name.startsWithIgnoreCase ("HKEY_CLASSES_ROOT\\")) return HKEY_CLASSES_ROOT; + if (name.startsWithIgnoreCase ("HKCR\\")) return HKEY_CLASSES_ROOT; + if (name.startsWithIgnoreCase ("HKEY_USERS\\")) return HKEY_USERS; + if (name.startsWithIgnoreCase ("HKU\\")) return HKEY_USERS; + + jassertfalse; // The name starts with an unknown root key (or maybe an old Win9x type) + return 0; + } + static bool setValue (const String& regValuePath, const DWORD type, const void* data, size_t dataSize, const DWORD wow64Flags) { diff --git a/source/modules/juce_core/native/juce_win32_SystemStats.cpp b/source/modules/juce_core/native/juce_win32_SystemStats.cpp index fee1b764e..bc207acaa 100644 --- a/source/modules/juce_core/native/juce_win32_SystemStats.cpp +++ b/source/modules/juce_core/native/juce_win32_SystemStats.cpp @@ -58,7 +58,7 @@ static void callCPUID (int result[4], int infoType) __try #endif { - #if JUCE_GCC + #if JUCE_GCC || JUCE_CLANG __asm__ __volatile__ ("cpuid" : "=a" (result[0]), "=b" (result[1]), "=c" (result[2]),"=d" (result[3]) : "a" (infoType)); #else __asm @@ -107,8 +107,14 @@ void CPUInformation::initialise() noexcept hasSSE3 = (info[2] & (1 << 0)) != 0; hasAVX = (info[2] & (1 << 28)) != 0; hasSSSE3 = (info[2] & (1 << 9)) != 0; + hasSSE41 = (info[2] & (1 << 19)) != 0; + hasSSE42 = (info[2] & (1 << 20)) != 0; has3DNow = (info[1] & (1 << 31)) != 0; + callCPUID (info, 7); + + hasAVX2 = (info[1] & (1 << 5)) != 0; + SYSTEM_INFO systemInfo; GetNativeSystemInfo (&systemInfo); numCpus = (int) systemInfo.dwNumberOfProcessors; @@ -325,7 +331,7 @@ static int64 juce_getClockCycleCounter() noexcept // MS intrinsics version... return (int64) __rdtsc(); - #elif JUCE_GCC + #elif JUCE_GCC || JUCE_CLANG // GNU inline asm version... unsigned int hi = 0, lo = 0; diff --git a/source/modules/juce_core/native/juce_win32_Threads.cpp b/source/modules/juce_core/native/juce_win32_Threads.cpp index 062c18d39..a25168a2e 100644 --- a/source/modules/juce_core/native/juce_win32_Threads.cpp +++ b/source/modules/juce_core/native/juce_win32_Threads.cpp @@ -109,7 +109,8 @@ static unsigned int __stdcall threadEntryProc (void* userData) void Thread::launchThread() { unsigned int newThreadId; - threadHandle = (void*) _beginthreadex (0, 0, &threadEntryProc, this, 0, &newThreadId); + threadHandle = (void*) _beginthreadex (0, (unsigned int) threadStackSize, + &threadEntryProc, this, 0, &newThreadId); threadId = (ThreadID) (pointer_sized_int) newThreadId; } diff --git a/source/modules/juce_core/network/juce_Socket.cpp b/source/modules/juce_core/network/juce_Socket.cpp index 7de9475b5..98e6edb79 100644 --- a/source/modules/juce_core/network/juce_Socket.cpp +++ b/source/modules/juce_core/network/juce_Socket.cpp @@ -62,6 +62,11 @@ namespace SocketHelpers #endif } + inline bool isValidPortNumber (int port) noexcept + { + return isPositiveAndBelow (port, 65536); + } + template static bool setOption (const SocketHandle handle, int mode, int property, Type value) noexcept { @@ -115,6 +120,7 @@ namespace SocketHelpers { // unblock any pending read requests ::shutdown (h, SHUT_RDWR); + { // see man-page of recv on linux about a race condition where the // shutdown command is lost if the receiving thread does not have @@ -133,37 +139,32 @@ namespace SocketHelpers #endif } - static bool bindSocket (const SocketHandle handle, const int port, const String& address) noexcept + static bool bindSocket (const SocketHandle handle, int port, const String& address) noexcept { - if (handle <= 0 || port < 0) + if (handle <= 0 || ! isValidPortNumber (port)) return false; - struct sockaddr_in servTmpAddr; - zerostruct (servTmpAddr); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) - servTmpAddr.sin_family = PF_INET; - servTmpAddr.sin_addr.s_addr = htonl (INADDR_ANY); - servTmpAddr.sin_port = htons ((uint16) port); + struct sockaddr_in addr; + zerostruct (addr); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) - #if JUCE_WINDOWS - if (address.isNotEmpty()) - servTmpAddr.sin_addr.s_addr = ::inet_addr (address.toUTF8()); - #else - ignoreUnused (address); - #endif + addr.sin_family = PF_INET; + addr.sin_port = htons ((uint16) port); + addr.sin_addr.s_addr = address.isNotEmpty() ? ::inet_addr (address.toRawUTF8()) + : htonl (INADDR_ANY); - return bind (handle, (struct sockaddr*) &servTmpAddr, sizeof (struct sockaddr_in)) >= 0; + return ::bind (handle, (struct sockaddr*) &addr, sizeof (addr)) >= 0; } static int getBoundPort (const SocketHandle handle) noexcept { - if (handle <= 0) - return -1; - - struct sockaddr_in sin_addr; - socklen_t len = sizeof (sin_addr); + if (handle > 0) + { + struct sockaddr_in addr; + socklen_t len = sizeof (addr); - if (getsockname (handle, (struct sockaddr*) &sin_addr, &len) == 0) - return ntohs (sin_addr.sin_port); + if (getsockname (handle, (struct sockaddr*) &addr, &len) == 0) + return ntohs (addr.sin_port); + } return -1; } @@ -384,11 +385,11 @@ namespace SocketHelpers struct ip_mreq mreq; zerostruct (mreq); - mreq.imr_multiaddr.s_addr = inet_addr (multicastIPAddress.toUTF8()); + mreq.imr_multiaddr.s_addr = inet_addr (multicastIPAddress.toRawUTF8()); mreq.imr_interface.s_addr = INADDR_ANY; if (interfaceIPAddress.isNotEmpty()) - mreq.imr_interface.s_addr = inet_addr (interfaceIPAddress.toUTF8()); + mreq.imr_interface.s_addr = inet_addr (interfaceIPAddress.toRawUTF8()); return setsockopt (handle, IPPROTO_IP, join ? IP_ADD_MEMBERSHIP @@ -414,6 +415,8 @@ StreamingSocket::StreamingSocket (const String& host, int portNum, int h) connected (true), isListener (false) { + jassert (SocketHelpers::isValidPortNumber (portNum)); + SocketHelpers::initSockets(); SocketHelpers::resetSocketOptions (h, false, false); } @@ -455,6 +458,8 @@ bool StreamingSocket::bindToPort (const int port) bool StreamingSocket::bindToPort (const int port, const String& addr) { + jassert (SocketHelpers::isValidPortNumber (port)); + return SocketHelpers::bindSocket (handle, port, addr); } @@ -467,6 +472,8 @@ bool StreamingSocket::connect (const String& remoteHostName, const int remotePortNumber, const int timeOutMillisecs) { + jassert (SocketHelpers::isValidPortNumber (remotePortNumber)); + if (isListener) { jassertfalse; // a listener socket can't connect to another one! @@ -505,6 +512,8 @@ void StreamingSocket::close() //============================================================================== bool StreamingSocket::createListener (const int newPortNumber, const String& localHostName) { + jassert (SocketHelpers::isValidPortNumber (newPortNumber)); + if (connected) close(); @@ -512,17 +521,6 @@ bool StreamingSocket::createListener (const int newPortNumber, const String& loc portNumber = newPortNumber; isListener = true; - struct sockaddr_in servTmpAddr; - zerostruct (servTmpAddr); - - servTmpAddr.sin_family = PF_INET; - servTmpAddr.sin_addr.s_addr = htonl (INADDR_ANY); - - if (localHostName.isNotEmpty()) - servTmpAddr.sin_addr.s_addr = ::inet_addr (localHostName.toUTF8()); - - servTmpAddr.sin_port = htons ((uint16) portNumber); - handle = (int) socket (AF_INET, SOCK_STREAM, 0); if (handle < 0) @@ -532,15 +530,15 @@ bool StreamingSocket::createListener (const int newPortNumber, const String& loc SocketHelpers::makeReusable (handle); #endif - if (bind (handle, (struct sockaddr*) &servTmpAddr, sizeof (struct sockaddr_in)) < 0 - || listen (handle, SOMAXCONN) < 0) + if (SocketHelpers::bindSocket (handle, portNumber, localHostName) + && listen (handle, SOMAXCONN) >= 0) { - close(); - return false; + connected = true; + return true; } - connected = true; - return true; + close(); + return false; } StreamingSocket* StreamingSocket::waitForNextConnection() const @@ -614,14 +612,12 @@ bool DatagramSocket::bindToPort (const int port) bool DatagramSocket::bindToPort (const int port, const String& addr) { - if (handle < 0) - return false; + jassert (SocketHelpers::isValidPortNumber (port)); if (SocketHelpers::bindSocket (handle, port, addr)) { isBound = true; lastBindAddress = addr; - return true; } @@ -673,6 +669,8 @@ int DatagramSocket::read (void* destBuffer, int maxBytesToRead, bool shouldBlock int DatagramSocket::write (const String& remoteHostname, int remotePortNumber, const void* sourceBuffer, int numBytesToWrite) { + jassert (SocketHelpers::isValidPortNumber (remotePortNumber)); + if (handle < 0) return -1; diff --git a/source/modules/juce_core/system/juce_PlatformDefs.h b/source/modules/juce_core/system/juce_PlatformDefs.h index 2ff33bcdf..67b5e872f 100644 --- a/source/modules/juce_core/system/juce_PlatformDefs.h +++ b/source/modules/juce_core/system/juce_PlatformDefs.h @@ -109,7 +109,7 @@ #endif //============================================================================== -#if JUCE_DEBUG || DOXYGEN +#if (JUCE_DEBUG && ! JUCE_DISABLE_ASSERTIONS) || DOXYGEN /** Writes a string to the standard error stream. Note that as well as a single string, you can use this to write multiple items as a stream, e.g. @@ -290,7 +290,7 @@ #elif JUCE_MSVC && ! JUCE_NO_DEPRECATION_WARNINGS #define JUCE_DEPRECATED(functionDef) __declspec(deprecated) functionDef #define JUCE_DEPRECATED_WITH_BODY(functionDef, body) __declspec(deprecated) functionDef body -#elif JUCE_GCC && ! JUCE_NO_DEPRECATION_WARNINGS +#elif (JUCE_GCC || JUCE_CLANG) && ! JUCE_NO_DEPRECATION_WARNINGS #define JUCE_DEPRECATED(functionDef) functionDef __attribute__ ((deprecated)) #define JUCE_DEPRECATED_WITH_BODY(functionDef, body) functionDef __attribute__ ((deprecated)) body #else @@ -308,10 +308,19 @@ #endif //============================================================================== -#if JUCE_GCC +#if JUCE_GCC || JUCE_CLANG #define JUCE_PACKED __attribute__((packed)) #elif ! DOXYGEN #define JUCE_PACKED #endif +//============================================================================== +#if JUCE_GCC || DOXYGEN + /** This can be appended to a function declaration to tell gcc to disable associative + math optimisations which break some floating point algorithms. */ + #define JUCE_NO_ASSOCIATIVE_MATH_OPTIMISATIONS __attribute__((__optimize__("no-associative-math"))) +#else + #define JUCE_NO_ASSOCIATIVE_MATH_OPTIMISATIONS +#endif + #endif // JUCE_PLATFORMDEFS_H_INCLUDED diff --git a/source/modules/juce_core/system/juce_SystemStats.cpp b/source/modules/juce_core/system/juce_SystemStats.cpp index 65bb117cb..598fd871d 100644 --- a/source/modules/juce_core/system/juce_SystemStats.cpp +++ b/source/modules/juce_core/system/juce_SystemStats.cpp @@ -68,7 +68,8 @@ struct CPUInformation CPUInformation() noexcept : numCpus (0), hasMMX (false), hasSSE (false), hasSSE2 (false), hasSSE3 (false), has3DNow (false), - hasSSSE3 (false), hasAVX (false) + hasSSSE3 (false), hasSSE41 (false), hasSSE42 (false), + hasAVX (false), hasAVX2 (false) { initialise(); } @@ -76,7 +77,7 @@ struct CPUInformation void initialise() noexcept; int numCpus; - bool hasMMX, hasSSE, hasSSE2, hasSSE3, has3DNow, hasSSSE3, hasAVX; + bool hasMMX, hasSSE, hasSSE2, hasSSE3, has3DNow, hasSSSE3, hasSSE41, hasSSE42, hasAVX, hasAVX2; }; static const CPUInformation& getCPUInformation() noexcept @@ -92,7 +93,10 @@ bool SystemStats::hasSSE() noexcept { return getCPUInformation().hasSS bool SystemStats::hasSSE2() noexcept { return getCPUInformation().hasSSE2; } bool SystemStats::hasSSE3() noexcept { return getCPUInformation().hasSSE3; } bool SystemStats::hasSSSE3() noexcept { return getCPUInformation().hasSSSE3; } +bool SystemStats::hasSSE41() noexcept { return getCPUInformation().hasSSE41; } +bool SystemStats::hasSSE42() noexcept { return getCPUInformation().hasSSE42; } bool SystemStats::hasAVX() noexcept { return getCPUInformation().hasAVX; } +bool SystemStats::hasAVX2() noexcept { return getCPUInformation().hasAVX2; } //============================================================================== diff --git a/source/modules/juce_core/system/juce_SystemStats.h b/source/modules/juce_core/system/juce_SystemStats.h index 9cbd187e0..28f4399f7 100644 --- a/source/modules/juce_core/system/juce_SystemStats.h +++ b/source/modules/juce_core/system/juce_SystemStats.h @@ -152,13 +152,16 @@ public: */ static String getCpuVendor(); - static bool hasMMX() noexcept; /**< Returns true if Intel MMX instructions are available. */ - static bool has3DNow() noexcept; /**< Returns true if AMD 3DNOW instructions are available. */ - static bool hasSSE() noexcept; /**< Returns true if Intel SSE instructions are available. */ - static bool hasSSE2() noexcept; /**< Returns true if Intel SSE2 instructions are available. */ - static bool hasSSE3() noexcept; /**< Returns true if Intel SSE2 instructions are available. */ - static bool hasSSSE3() noexcept; /**< Returns true if Intel SSSE3 instructions are available. */ - static bool hasAVX() noexcept; /**< Returns true if Intel AVX instructions are available. */ + static bool hasMMX() noexcept; /**< Returns true if Intel MMX instructions are available. */ + static bool has3DNow() noexcept; /**< Returns true if AMD 3DNOW instructions are available. */ + static bool hasSSE() noexcept; /**< Returns true if Intel SSE instructions are available. */ + static bool hasSSE2() noexcept; /**< Returns true if Intel SSE2 instructions are available. */ + static bool hasSSE3() noexcept; /**< Returns true if Intel SSE3 instructions are available. */ + static bool hasSSSE3() noexcept; /**< Returns true if Intel SSSE3 instructions are available. */ + static bool hasSSE41() noexcept; /**< Returns true if Intel SSE4.1 instructions are available. */ + static bool hasSSE42() noexcept; /**< Returns true if Intel SSE4.2 instructions are available. */ + static bool hasAVX() noexcept; /**< Returns true if Intel AVX instructions are available. */ + static bool hasAVX2() noexcept; /**< Returns true if Intel AVX2 instructions are available. */ //============================================================================== /** Finds out how much RAM is in the machine. diff --git a/source/modules/juce_core/system/juce_TargetPlatform.h b/source/modules/juce_core/system/juce_TargetPlatform.h index 6dd013085..6b4c6f173 100644 --- a/source/modules/juce_core/system/juce_TargetPlatform.h +++ b/source/modules/juce_core/system/juce_TargetPlatform.h @@ -39,7 +39,7 @@ - Either JUCE_32BIT or JUCE_64BIT, depending on the architecture. - Either JUCE_LITTLE_ENDIAN or JUCE_BIG_ENDIAN. - Either JUCE_INTEL or JUCE_PPC - - Either JUCE_GCC or JUCE_MSVC + - Either JUCE_GCC or JUCE_CLANG or JUCE_MSVC */ //============================================================================== @@ -61,12 +61,8 @@ #elif defined (LINUX) || defined (__linux__) #define JUCE_LINUX 1 #elif defined (__APPLE_CPP__) || defined(__APPLE_CC__) - #define Point CarbonDummyPointName // (workaround to avoid definition of "Point" by old Carbon headers) - #define Component CarbonDummyCompName #include // (needed to find out what platform we're using) #include "../native/juce_mac_ClangBugWorkaround.h" - #undef Point - #undef Component #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR #define JUCE_IPHONE 1 @@ -183,7 +179,6 @@ #ifdef __clang__ #define JUCE_CLANG 1 - #define JUCE_GCC 1 #elif defined (__GNUC__) #define JUCE_GCC 1 #elif defined (_MSC_VER) diff --git a/source/modules/juce_core/text/juce_CharPointer_UTF8.h b/source/modules/juce_core/text/juce_CharPointer_UTF8.h index facd5a57a..628897f61 100644 --- a/source/modules/juce_core/text/juce_CharPointer_UTF8.h +++ b/source/modules/juce_core/text/juce_CharPointer_UTF8.h @@ -520,6 +520,9 @@ public: return false; } + if (numExtraValues == 0) + return false; + maxBytesToRead -= numExtraValues; if (maxBytesToRead < 0) return false; diff --git a/source/modules/juce_core/threads/juce_Process.h b/source/modules/juce_core/threads/juce_Process.h index 5274be3b8..9ba0af0ce 100644 --- a/source/modules/juce_core/threads/juce_Process.h +++ b/source/modules/juce_core/threads/juce_Process.h @@ -144,6 +144,15 @@ public: static void setDockIconVisible (bool isVisible); #endif + #if JUCE_MAC || JUCE_LINUX || DOXYGEN + //============================================================================== + /** UNIX ONLY - Attempts to use setrlimit to change the maximum number of file + handles that the app can open. Pass 0 or less as the parameter to mean + 'infinite'. Returns true if it succeeds. + */ + static bool setMaxNumberOfFileHandles (int maxNumberOfFiles) noexcept; + #endif + private: Process(); JUCE_DECLARE_NON_COPYABLE (Process) diff --git a/source/modules/juce_core/threads/juce_Thread.cpp b/source/modules/juce_core/threads/juce_Thread.cpp index 721f7410b..27ab0d5c5 100644 --- a/source/modules/juce_core/threads/juce_Thread.cpp +++ b/source/modules/juce_core/threads/juce_Thread.cpp @@ -26,11 +26,12 @@ ============================================================================== */ -Thread::Thread (const String& threadName_) +Thread::Thread (const String& threadName_, const size_t stackSize) : threadName (threadName_), threadHandle (nullptr), threadId (0), threadPriority (5), + threadStackSize (stackSize), affinityMask (0), shouldExit (false) { diff --git a/source/modules/juce_core/threads/juce_Thread.h b/source/modules/juce_core/threads/juce_Thread.h index 89a0c507a..88bcfe9a2 100644 --- a/source/modules/juce_core/threads/juce_Thread.h +++ b/source/modules/juce_core/threads/juce_Thread.h @@ -53,8 +53,14 @@ public: When first created, the thread is not running. Use the startThread() method to start it. + + @param threadName The name of the thread which typically appears in + debug logs and profiles. + @param threadStackSize The size of the stack of the thread. If this value + is zero then the default stack size of the OS will + be used. */ - explicit Thread (const String& threadName); + explicit Thread (const String& threadName, size_t threadStackSize = 0); /** Destructor. @@ -270,6 +276,7 @@ private: CriticalSection startStopLock; WaitableEvent startSuspensionEvent, defaultEvent; int threadPriority; + size_t threadStackSize; uint32 affinityMask; bool volatile shouldExit; diff --git a/source/modules/juce_core/time/juce_Time.cpp b/source/modules/juce_core/time/juce_Time.cpp index 847b360af..1d0b613ff 100644 --- a/source/modules/juce_core/time/juce_Time.cpp +++ b/source/modules/juce_core/time/juce_Time.cpp @@ -28,55 +28,64 @@ namespace TimeHelpers { - static struct tm millisToLocal (const int64 millis) noexcept + static std::tm millisToLocal (int64 millis) noexcept { - struct tm result; - const int64 seconds = millis / 1000; + #if JUCE_WINDOWS && JUCE_MINGW + time_t now = (time_t) (millis / 1000); + return *localtime (&now); - if (seconds < 86400LL || seconds >= 2145916800LL) - { - // use extended maths for dates beyond 1970 to 2037.. - const int timeZoneAdjustment = 31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000); - const int64 jdm = seconds + timeZoneAdjustment + 210866803200LL; - - const int days = (int) (jdm / 86400LL); - const int a = 32044 + days; - const int b = (4 * a + 3) / 146097; - const int c = a - (b * 146097) / 4; - const int d = (4 * c + 3) / 1461; - const int e = c - (d * 1461) / 4; - const int m = (5 * e + 2) / 153; - - result.tm_mday = e - (153 * m + 2) / 5 + 1; - result.tm_mon = m + 2 - 12 * (m / 10); - result.tm_year = b * 100 + d - 6700 + (m / 10); - result.tm_wday = (days + 1) % 7; - result.tm_yday = -1; - - int t = (int) (jdm % 86400LL); - result.tm_hour = t / 3600; - t %= 3600; - result.tm_min = t / 60; - result.tm_sec = t % 60; - result.tm_isdst = -1; - } - else - { - time_t now = static_cast (seconds); - - #if JUCE_WINDOWS && JUCE_MINGW - return *localtime (&now); - #elif JUCE_WINDOWS - if (now >= 0 && now <= 0x793406fff) - localtime_s (&result, &now); - else - zerostruct (result); - #else - localtime_r (&now, &result); // more thread-safe - #endif - } + #elif JUCE_WINDOWS + std::tm result; + millis /= 1000; + + if (_localtime64_s (&result, &millis) != 0) + zerostruct (result); return result; + + #else + std::tm result; + time_t now = (time_t) (millis / 1000); + + if (localtime_r (&now, &result) == nullptr) + zerostruct (result); + + return result; + #endif + } + + static std::tm millisToUTC (int64 millis) noexcept + { + #if JUCE_WINDOWS && JUCE_MINGW + time_t now = (time_t) (millis / 1000); + return *gmtime (&now); + + #elif JUCE_WINDOWS + std::tm result; + millis /= 1000; + + if (_gmtime64_s (&result, &millis) != 0) + zerostruct (result); + + return result; + + #else + std::tm result; + time_t now = (time_t) (millis / 1000); + + if (gmtime_r (&now, &result) == nullptr) + zerostruct (result); + + return result; + #endif + } + + static int getUTCOffsetSeconds (const int64 millis) noexcept + { + std::tm utc = millisToUTC (millis); + utc.tm_isdst = -1; // Treat this UTC time as local to find the offset + + return (int) ((millis / 1000) - (int64) mktime (&utc)); } static int extendedModulo (const int64 value, const int modulo) noexcept @@ -85,7 +94,7 @@ namespace TimeHelpers : (value - ((value / modulo) + 1) * modulo)); } - static inline String formatString (const String& format, const struct tm* const tm) + static inline String formatString (const String& format, const std::tm* const tm) { #if JUCE_ANDROID typedef CharPointer_UTF8 StringType; @@ -95,17 +104,23 @@ namespace TimeHelpers typedef CharPointer_UTF32 StringType; #endif + #ifdef JUCE_MSVC + if (tm->tm_year < -1900 || tm->tm_year > 8099) + return String(); // Visual Studio's library can only handle 0 -> 9999 AD + #endif + for (size_t bufferSize = 256; ; bufferSize += 256) { HeapBlock buffer (bufferSize); - #if JUCE_ANDROID - const size_t numChars = strftime (buffer, bufferSize - 1, format.toUTF8(), tm); - #elif JUCE_WINDOWS - const size_t numChars = wcsftime (buffer, bufferSize - 1, format.toWideCharPointer(), tm); - #else - const size_t numChars = wcsftime (buffer, bufferSize - 1, format.toUTF32(), tm); - #endif + const size_t numChars = + #if JUCE_ANDROID + strftime (buffer, bufferSize - 1, format.toUTF8(), tm); + #elif JUCE_WINDOWS + wcsftime (buffer, bufferSize - 1, format.toWideCharPointer(), tm); + #else + wcsftime (buffer, bufferSize - 1, format.toUTF32(), tm); + #endif if (numChars > 0 || format.isEmpty()) return String (StringType (buffer), @@ -113,22 +128,70 @@ namespace TimeHelpers } } + //============================================================================== + static inline bool isLeapYear (int year) noexcept + { + return (year % 400 == 0) || ((year % 100 != 0) && (year % 4 == 0)); + } + + static inline int daysFromJan1 (int year, int month) noexcept + { + const short dayOfYear[] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; + + return dayOfYear [(isLeapYear (year) ? 12 : 0) + month]; + } + + static inline int64 daysFromYear0 (int year) noexcept + { + return 365 * (year - 1) + (year / 400) - (year / 100) + (year / 4); + } + + static inline int64 daysFrom1970 (int year) noexcept + { + return daysFromYear0 (year) - daysFromYear0 (1970); + } + + static inline int64 daysFrom1970 (int year, int month) noexcept + { + if (month > 11) + { + year += month / 12; + month %= 12; + } + else if (month < 0) + { + const int numYears = (11 - month) / 12; + year -= numYears; + month += 12 * numYears; + } + + return daysFrom1970 (year) + daysFromJan1 (year, month); + } + + // There's no posix function that does a UTC version of mktime, + // so annoyingly we need to implement this manually.. + static inline int64 mktime_utc (const std::tm& t) noexcept + { + return 24 * 3600 * (daysFrom1970 (t.tm_year + 1900, t.tm_mon) + (t.tm_mday - 1)) + + 3600 * t.tm_hour + + 60 * t.tm_min + + t.tm_sec; + } + static uint32 lastMSCounterValue = 0; } //============================================================================== -Time::Time() noexcept - : millisSinceEpoch (0) +Time::Time() noexcept : millisSinceEpoch (0) { } -Time::Time (const Time& other) noexcept - : millisSinceEpoch (other.millisSinceEpoch) +Time::Time (const Time& other) noexcept : millisSinceEpoch (other.millisSinceEpoch) { } -Time::Time (const int64 ms) noexcept - : millisSinceEpoch (ms) +Time::Time (const int64 ms) noexcept : millisSinceEpoch (ms) { } @@ -141,41 +204,26 @@ Time::Time (const int year, const int milliseconds, const bool useLocalTime) noexcept { - jassert (year > 100); // year must be a 4-digit version - - if (year < 1971 || year >= 2038 || ! useLocalTime) + std::tm t; + t.tm_year = year - 1900; + t.tm_mon = month; + t.tm_mday = day; + t.tm_hour = hours; + t.tm_min = minutes; + t.tm_sec = seconds; + t.tm_isdst = -1; + + const int64 time = useLocalTime ? (int64) mktime (&t) + : TimeHelpers::mktime_utc (t); + + if (time >= 0) { - // use extended maths for dates beyond 1970 to 2037.. - const int timeZoneAdjustment = useLocalTime ? (31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000)) - : 0; - const int a = (13 - month) / 12; - const int y = year + 4800 - a; - const int jd = day + (153 * (month + 12 * a - 2) + 2) / 5 - + (y * 365) + (y / 4) - (y / 100) + (y / 400) - - 32045; - - const int64 s = ((int64) jd) * 86400LL - 210866803200LL; - - millisSinceEpoch = 1000 * (s + (hours * 3600 + minutes * 60 + seconds - timeZoneAdjustment)) - + milliseconds; + millisSinceEpoch = 1000 * time + milliseconds; } else { - struct tm t; - t.tm_year = year - 1900; - t.tm_mon = month; - t.tm_mday = day; - t.tm_hour = hours; - t.tm_min = minutes; - t.tm_sec = seconds; - t.tm_isdst = -1; - - millisSinceEpoch = 1000 * (int64) mktime (&t); - - if (millisSinceEpoch < 0) - millisSinceEpoch = 0; - else - millisSinceEpoch += milliseconds; + jassertfalse; // trying to create a date that is beyond the range that mktime supports! + millisSinceEpoch = 0; } } @@ -315,7 +363,7 @@ String Time::toString (const bool includeDate, String Time::formatted (const String& format) const { - struct tm t (TimeHelpers::millisToLocal (millisSinceEpoch)); + std::tm t (TimeHelpers::millisToLocal (millisSinceEpoch)); return TimeHelpers::formatString (format, &t); } @@ -388,6 +436,117 @@ String Time::getTimeZone() const noexcept return zone[0].substring (0, 3); } +int Time::getUTCOffsetSeconds() const noexcept +{ + return TimeHelpers::getUTCOffsetSeconds (millisSinceEpoch); +} + +String Time::getUTCOffsetString (bool includeSemiColon) const +{ + if (int seconds = getUTCOffsetSeconds()) + { + const int minutes = seconds / 60; + + return String::formatted (includeSemiColon ? "%+03d:%02d" + : "%+03d%02d", + minutes / 60, + minutes % 60); + } + + return "Z"; +} + +String Time::toISO8601 (bool includeDividerCharacters) const +{ + return String::formatted (includeDividerCharacters ? "%04d-%02d-%02dT%02d:%02d:%02.03f" + : "%04d%02d%02dT%02d%02d%02.03f", + getYear(), + getMonth() + 1, + getDayOfMonth(), + getHours(), + getMinutes(), + getSeconds() + getMilliseconds() / 1000.0) + + getUTCOffsetString (includeDividerCharacters); +} + +static int parseFixedSizeIntAndSkip (String::CharPointerType& t, int numChars, char charToSkip) noexcept +{ + int n = 0; + + for (int i = numChars; --i >= 0;) + { + const int digit = (int) (*t - '0'); + + if (! isPositiveAndBelow (digit, 10)) + return -1; + + ++t; + n = n * 10 + digit; + } + + if (charToSkip != 0 && *t == (juce_wchar) charToSkip) + ++t; + + return n; +} + +Time Time::fromISO8601 (StringRef iso) noexcept +{ + String::CharPointerType t = iso.text; + + const int year = parseFixedSizeIntAndSkip (t, 4, '-'); + if (year < 0) + return Time(); + + const int month = parseFixedSizeIntAndSkip (t, 2, '-'); + if (month < 0) + return Time(); + + const int day = parseFixedSizeIntAndSkip (t, 2, 0); + if (day < 0) + return Time(); + + int hours = 0, minutes = 0, milliseconds = 0; + + if (*t == 'T') + { + ++t; + hours = parseFixedSizeIntAndSkip (t, 2, ':'); + if (hours < 0) + return Time(); + + minutes = parseFixedSizeIntAndSkip (t, 2, ':'); + if (minutes < 0) + return Time(); + + milliseconds = (int) (1000.0 * CharacterFunctions::readDoubleValue (t)); + } + + const juce_wchar nextChar = t.getAndAdvance(); + + if (nextChar == '-' || nextChar == '+') + { + const int offsetHours = parseFixedSizeIntAndSkip (t, 2, ':'); + if (offsetHours < 0) + return Time(); + + const int offsetMinutes = parseFixedSizeIntAndSkip (t, 2, 0); + if (offsetMinutes < 0) + return Time(); + + const int offsetMs = (offsetHours * 60 + offsetMinutes) * 60 * 1000; + milliseconds += nextChar == '-' ? offsetMs : -offsetMs; // NB: this seems backwards but is correct! + } + else if (nextChar != 0 && nextChar != 'Z') + { + return Time(); + } + + Time result (year, month - 1, day, hours, minutes, 0, 0, false); + result.millisSinceEpoch += milliseconds; + return result; +} + String Time::getMonthName (const bool threeLetterVersion) const { return getMonthName (getMonth(), threeLetterVersion); @@ -442,19 +601,76 @@ static int getMonthNumberForCompileDate (const String& m) noexcept if (m.equalsIgnoreCase (shortMonthNames[i])) return i; - // If you hit this because your compiler has a non-standard __DATE__ format, - // let me know so we can add support for it! + // If you hit this because your compiler has an unusual __DATE__ + // format, let us know so we can add support for it! jassertfalse; return 0; } Time Time::getCompilationDate() { - StringArray dateTokens; + StringArray dateTokens, timeTokens; + dateTokens.addTokens (__DATE__, true); dateTokens.removeEmptyStrings (true); + timeTokens.addTokens (__TIME__, ":", StringRef()); + return Time (dateTokens[2].getIntValue(), getMonthNumberForCompileDate (dateTokens[0]), - dateTokens[1].getIntValue(), 12, 0); + dateTokens[1].getIntValue(), + timeTokens[0].getIntValue(), + timeTokens[1].getIntValue()); } + + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class TimeTests : public UnitTest +{ +public: + TimeTests() : UnitTest ("Time") {} + + void runTest() override + { + beginTest ("Time"); + + Time t = Time::getCurrentTime(); + expect (t > Time()); + + Thread::sleep (15); + expect (Time::getCurrentTime() > t); + + expect (t.getTimeZone().isNotEmpty()); + expect (t.getUTCOffsetString (true) == "Z" || t.getUTCOffsetString (true).length() == 6); + expect (t.getUTCOffsetString (false) == "Z" || t.getUTCOffsetString (false).length() == 5); + + expect (Time::fromISO8601 (t.toISO8601 (true)) == t); + expect (Time::fromISO8601 (t.toISO8601 (false)) == t); + + expect (Time::fromISO8601 ("2016-02-16") == Time (2016, 1, 16, 0, 0, 0, 0, false)); + expect (Time::fromISO8601 ("20160216Z") == Time (2016, 1, 16, 0, 0, 0, 0, false)); + expect (Time::fromISO8601 ("2016-02-16T15:03:57+00:00") == Time (2016, 1, 16, 15, 3, 57, 0, false)); + expect (Time::fromISO8601 ("20160216T150357+0000") == Time (2016, 1, 16, 15, 3, 57, 0, false)); + expect (Time::fromISO8601 ("2016-02-16T15:03:57.999+00:00") == Time (2016, 1, 16, 15, 3, 57, 999, false)); + expect (Time::fromISO8601 ("20160216T150357.999+0000") == Time (2016, 1, 16, 15, 3, 57, 999, false)); + expect (Time::fromISO8601 ("2016-02-16T15:03:57.999Z") == Time (2016, 1, 16, 15, 3, 57, 999, false)); + expect (Time::fromISO8601 ("20160216T150357.999Z") == Time (2016, 1, 16, 15, 3, 57, 999, false)); + expect (Time::fromISO8601 ("2016-02-16T15:03:57.999-02:30") == Time (2016, 1, 16, 17, 33, 57, 999, false)); + expect (Time::fromISO8601 ("20160216T150357.999-0230") == Time (2016, 1, 16, 17, 33, 57, 999, false)); + + expect (Time (1982, 1, 1, 12, 0, 0, 0, true) + RelativeTime::days (365) == Time (1983, 1, 1, 12, 0, 0, 0, true)); + expect (Time (1970, 1, 1, 12, 0, 0, 0, true) + RelativeTime::days (365) == Time (1971, 1, 1, 12, 0, 0, 0, true)); + expect (Time (2038, 1, 1, 12, 0, 0, 0, true) + RelativeTime::days (365) == Time (2039, 1, 1, 12, 0, 0, 0, true)); + + expect (Time (1982, 1, 1, 12, 0, 0, 0, false) + RelativeTime::days (365) == Time (1983, 1, 1, 12, 0, 0, 0, false)); + expect (Time (1970, 1, 1, 12, 0, 0, 0, false) + RelativeTime::days (365) == Time (1971, 1, 1, 12, 0, 0, 0, false)); + expect (Time (2038, 1, 1, 12, 0, 0, 0, false) + RelativeTime::days (365) == Time (2039, 1, 1, 12, 0, 0, 0, false)); + } +}; + +static TimeTests timeTests; + +#endif diff --git a/source/modules/juce_core/time/juce_Time.h b/source/modules/juce_core/time/juce_Time.h index b51ed0779..a71776c2e 100644 --- a/source/modules/juce_core/time/juce_Time.h +++ b/source/modules/juce_core/time/juce_Time.h @@ -44,7 +44,7 @@ public: //============================================================================== /** Creates a Time object. - This default constructor creates a time of 1st January 1970, (which is + This default constructor creates a time of midnight Jan 1st 1970 UTC, (which is represented internally as 0ms). To create a time object representing the current time, use getCurrentTime(). @@ -55,19 +55,16 @@ public: /** Creates a time based on a number of milliseconds. - The internal millisecond count is set to 0 (1st January 1970). To create a - time object set to the current time, use getCurrentTime(). + To create a time object set to the current time, use getCurrentTime(). @param millisecondsSinceEpoch the number of milliseconds since the unix - 'epoch' (midnight Jan 1st 1970). + 'epoch' (midnight Jan 1st 1970 UTC). @see getCurrentTime, currentTimeMillis */ explicit Time (int64 millisecondsSinceEpoch) noexcept; /** Creates a time from a set of date components. - The timezone is assumed to be whatever the system is using as its locale. - @param year the year, in 4-digit format, e.g. 2004 @param month the month, in the range 0 to 11 @param day the day of the month, in the range 1 to 31 @@ -75,8 +72,8 @@ public: @param minutes minutes 0 to 59 @param seconds seconds 0 to 59 @param milliseconds milliseconds 0 to 999 - @param useLocalTime if true, encode using the current machine's local time; if - false, it will always work in GMT. + @param useLocalTime if true, assume input is in this machine's local timezone + if false, assume input is in UTC. */ Time (int year, int month, @@ -99,87 +96,79 @@ public: //============================================================================== /** Returns a Time object that is set to the current system time. + This may not be monotonic, as the system time can change at any moment. + You should therefore not use this method for measuring time intervals. + @see currentTimeMillis */ static Time JUCE_CALLTYPE getCurrentTime() noexcept; /** Returns the time as a number of milliseconds. - @returns the number of milliseconds this Time object represents, since - midnight jan 1st 1970. + midnight Jan 1st 1970 UTC. @see getMilliseconds */ int64 toMilliseconds() const noexcept { return millisSinceEpoch; } - /** Returns the year. - + /** Returns the year (in this machine's local timezone). A 4-digit format is used, e.g. 2004. */ int getYear() const noexcept; - /** Returns the number of the month. - + /** Returns the number of the month (in this machine's local timezone). The value returned is in the range 0 to 11. @see getMonthName */ int getMonth() const noexcept; - /** Returns the name of the month. - + /** Returns the name of the month (in this machine's local timezone). @param threeLetterVersion if true, it'll be a 3-letter abbreviation, e.g. "Jan"; if false it'll return the long form, e.g. "January" @see getMonth */ String getMonthName (bool threeLetterVersion) const; - /** Returns the day of the month. + /** Returns the day of the month (in this machine's local timezone). The value returned is in the range 1 to 31. */ int getDayOfMonth() const noexcept; - /** Returns the number of the day of the week. + /** Returns the number of the day of the week (in this machine's local timezone). The value returned is in the range 0 to 6 (0 = sunday, 1 = monday, etc). */ int getDayOfWeek() const noexcept; - /** Returns the number of the day of the year. + /** Returns the number of the day of the year (in this machine's local timezone). The value returned is in the range 0 to 365. */ int getDayOfYear() const noexcept; - /** Returns the name of the weekday. - + /** Returns the name of the weekday (in this machine's local timezone). @param threeLetterVersion if true, it'll return a 3-letter abbreviation, e.g. "Tue"; if false, it'll return the full version, e.g. "Tuesday". */ String getWeekdayName (bool threeLetterVersion) const; - /** Returns the number of hours since midnight. - + /** Returns the number of hours since midnight (in this machine's local timezone). This is in 24-hour clock format, in the range 0 to 23. - @see getHoursInAmPmFormat, isAfternoon */ int getHours() const noexcept; - /** Returns true if the time is in the afternoon. - - So it returns true for "PM", false for "AM". - + /** Returns true if the time is in the afternoon (in this machine's local timezone). + @returns true for "PM", false for "AM". @see getHoursInAmPmFormat, getHours */ bool isAfternoon() const noexcept; - /** Returns the hours in 12-hour clock format. - + /** Returns the hours in 12-hour clock format (in this machine's local timezone). This will return a value 1 to 12 - use isAfternoon() to find out whether this is in the afternoon or morning. - @see getHours, isAfternoon */ int getHoursInAmPmFormat() const noexcept; - /** Returns the number of minutes, 0 to 59. */ + /** Returns the number of minutes, 0 to 59 (in this machine's local timezone). */ int getMinutes() const noexcept; /** Returns the number of seconds, 0 to 59. */ @@ -197,11 +186,21 @@ public: /** Returns true if the local timezone uses a daylight saving correction. */ bool isDaylightSavingTime() const noexcept; + //============================================================================== /** Returns a 3-character string to indicate the local timezone. */ String getTimeZone() const noexcept; + /** Returns the local timezone offset from UTC in seconds. */ + int getUTCOffsetSeconds() const noexcept; + + /** Returns a string to indicate the offset of the local timezone from UTC. + @returns "+XX:XX", "-XX:XX" or "Z" + @param includeDividerCharacters whether to include or omit the ":" divider in the string + */ + String getUTCOffsetString (bool includeDividerCharacters) const; + //============================================================================== - /** Quick way of getting a string version of a date and time. + /** Returns a string version of this date and time, using this machine's local timezone. For a more powerful way of formatting the date and time, see the formatted() method. @@ -224,33 +223,45 @@ public: looking it up, these are the escape codes that strftime uses (other codes might work on some platforms and not others, but these are the common ones): - %a is replaced by the locale's abbreviated weekday name. - %A is replaced by the locale's full weekday name. - %b is replaced by the locale's abbreviated month name. - %B is replaced by the locale's full month name. - %c is replaced by the locale's appropriate date and time representation. - %d is replaced by the day of the month as a decimal number [01,31]. - %H is replaced by the hour (24-hour clock) as a decimal number [00,23]. - %I is replaced by the hour (12-hour clock) as a decimal number [01,12]. - %j is replaced by the day of the year as a decimal number [001,366]. - %m is replaced by the month as a decimal number [01,12]. - %M is replaced by the minute as a decimal number [00,59]. - %p is replaced by the locale's equivalent of either a.m. or p.m. - %S is replaced by the second as a decimal number [00,61]. - %U is replaced by the week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. - %w is replaced by the weekday as a decimal number [0,6], with 0 representing Sunday. - %W is replaced by the week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0. - %x is replaced by the locale's appropriate date representation. - %X is replaced by the locale's appropriate time representation. - %y is replaced by the year without century as a decimal number [00,99]. - %Y is replaced by the year with century as a decimal number. - %Z is replaced by the timezone name or abbreviation, or by no bytes if no timezone information exists. - %% is replaced by %. + - %a is replaced by the locale's abbreviated weekday name. + - %A is replaced by the locale's full weekday name. + - %b is replaced by the locale's abbreviated month name. + - %B is replaced by the locale's full month name. + - %c is replaced by the locale's appropriate date and time representation. + - %d is replaced by the day of the month as a decimal number [01,31]. + - %H is replaced by the hour (24-hour clock) as a decimal number [00,23]. + - %I is replaced by the hour (12-hour clock) as a decimal number [01,12]. + - %j is replaced by the day of the year as a decimal number [001,366]. + - %m is replaced by the month as a decimal number [01,12]. + - %M is replaced by the minute as a decimal number [00,59]. + - %p is replaced by the locale's equivalent of either a.m. or p.m. + - %S is replaced by the second as a decimal number [00,61]. + - %U is replaced by the week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. + - %w is replaced by the weekday as a decimal number [0,6], with 0 representing Sunday. + - %W is replaced by the week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0. + - %x is replaced by the locale's appropriate date representation. + - %X is replaced by the locale's appropriate time representation. + - %y is replaced by the year without century as a decimal number [00,99]. + - %Y is replaced by the year with century as a decimal number. + - %Z is replaced by the timezone name or abbreviation, or by no bytes if no timezone information exists. + - %% is replaced by %. @see toString */ String formatted (const String& format) const; + //============================================================================== + /** Returns a fully described string of this date and time in ISO-8601 format + (using the local timezone). + + @param includeDividerCharacters whether to include or omit the "-" and ":" + dividers in the string + */ + String toISO8601 (bool includeDividerCharacters) const; + + /** Parses an ISO-8601 string and returns it as a Time. */ + static Time fromISO8601 (StringRef iso8601) noexcept; + //============================================================================== /** Adds a RelativeTime to this time. */ Time& operator+= (RelativeTime delta) noexcept; @@ -287,7 +298,7 @@ public: /** Returns the current system time. - Returns the number of milliseconds since midnight jan 1st 1970. + Returns the number of milliseconds since midnight Jan 1st 1970 UTC. Should be accurate to within a few millisecs, depending on platform, hardware, etc. diff --git a/source/modules/juce_core/zip/juce_ZipFile.cpp b/source/modules/juce_core/zip/juce_ZipFile.cpp index c7ae45d36..b743b2798 100644 --- a/source/modules/juce_core/zip/juce_ZipFile.cpp +++ b/source/modules/juce_core/zip/juce_ZipFile.cpp @@ -31,34 +31,29 @@ class ZipFile::ZipEntryHolder public: ZipEntryHolder (const char* const buffer, const int fileNameLen) { - entry.filename = String::fromUTF8 (buffer + 46, fileNameLen); - - const int time = ByteOrder::littleEndianShort (buffer + 12); - const int date = ByteOrder::littleEndianShort (buffer + 14); - entry.fileTime = getFileTimeFromRawEncodings (time, date); - - compressed = ByteOrder::littleEndianShort (buffer + 10) != 0; - compressedSize = (size_t) ByteOrder::littleEndianInt (buffer + 20); - entry.uncompressedSize = ByteOrder::littleEndianInt (buffer + 24); - - streamOffset = ByteOrder::littleEndianInt (buffer + 42); + isCompressed = ByteOrder::littleEndianShort (buffer + 10) != 0; + entry.fileTime = parseFileTime ((uint32) ByteOrder::littleEndianShort (buffer + 12), + (uint32) ByteOrder::littleEndianShort (buffer + 14)); + compressedSize = (int64) (uint32) ByteOrder::littleEndianInt (buffer + 20); + entry.uncompressedSize = (int64) (uint32) ByteOrder::littleEndianInt (buffer + 24); + streamOffset = (int64) (uint32) ByteOrder::littleEndianInt (buffer + 42); + entry.filename = String::fromUTF8 (buffer + 46, fileNameLen); } struct FileNameComparator { - static int compareElements (const ZipEntryHolder* first, const ZipEntryHolder* second) + static int compareElements (const ZipEntryHolder* e1, const ZipEntryHolder* e2) noexcept { - return first->entry.filename.compare (second->entry.filename); + return e1->entry.filename.compare (e2->entry.filename); } }; ZipEntry entry; - size_t streamOffset; - size_t compressedSize; - bool compressed; + int64 streamOffset, compressedSize; + bool isCompressed; private: - static Time getFileTimeFromRawEncodings (int time, int date) + static Time parseFileTime (uint32 time, uint32 date) noexcept { const int year = 1980 + (date >> 9); const int month = ((date >> 5) & 15) - 1; @@ -135,7 +130,7 @@ public: char buffer [30]; if (inputStream != nullptr - && inputStream->setPosition ((int64) zei.streamOffset) + && inputStream->setPosition (zei.streamOffset) && inputStream->read (buffer, 30) == 30 && ByteOrder::littleEndianInt (buffer) == 0x04034b50) { @@ -152,17 +147,17 @@ public: #endif } - int64 getTotalLength() + int64 getTotalLength() override { - return (int64) zipEntryHolder.compressedSize; + return zipEntryHolder.compressedSize; } - int read (void* buffer, int howMany) + int read (void* buffer, int howMany) override { if (headerSize <= 0) return 0; - howMany = (int) jmin ((int64) howMany, ((int64) zipEntryHolder.compressedSize) - pos); + howMany = (int) jmin ((int64) howMany, zipEntryHolder.compressedSize - pos); if (inputStream == nullptr) return 0; @@ -172,12 +167,12 @@ public: if (inputStream == file.inputStream) { const ScopedLock sl (file.lock); - inputStream->setPosition (pos + (int64) zipEntryHolder.streamOffset + headerSize); + inputStream->setPosition (pos + zipEntryHolder.streamOffset + headerSize); num = inputStream->read (buffer, howMany); } else { - inputStream->setPosition (pos + (int64) zipEntryHolder.streamOffset + headerSize); + inputStream->setPosition (pos + zipEntryHolder.streamOffset + headerSize); num = inputStream->read (buffer, howMany); } @@ -185,19 +180,19 @@ public: return num; } - bool isExhausted() + bool isExhausted() override { - return headerSize <= 0 || pos >= (int64) zipEntryHolder.compressedSize; + return headerSize <= 0 || pos >= zipEntryHolder.compressedSize; } - int64 getPosition() + int64 getPosition() override { return pos; } - bool setPosition (int64 newPos) + bool setPosition (int64 newPos) override { - pos = jlimit ((int64) 0, (int64) zipEntryHolder.compressedSize, newPos); + pos = jlimit ((int64) 0, zipEntryHolder.compressedSize, newPos); return true; } @@ -296,11 +291,11 @@ InputStream* ZipFile::createStreamForEntry (const int index) { stream = new ZipInputStream (*this, *zei); - if (zei->compressed) + if (zei->isCompressed) { stream = new GZIPDecompressorInputStream (stream, true, GZIPDecompressorInputStream::deflateFormat, - (int64) zei->entry.uncompressedSize); + zei->entry.uncompressedSize); // (much faster to unzip in big blocks using a buffer..) stream = new BufferedInputStream (stream, 32768, true); @@ -440,14 +435,14 @@ Result ZipFile::uncompressEntry (const int index, } -//============================================================================= +//============================================================================== class ZipFile::Builder::Item { public: - Item (const File& f, InputStream* s, const int compression, const String& storedPath, Time time) - : file (f), stream (s), storedPathname (storedPath), - fileTime (time), compressionLevel (compression), - compressedSize (0), uncompressedSize (0), headerStart (0), checksum (0) + Item (const File& f, InputStream* s, int compression, const String& storedPath, Time time) + : file (f), stream (s), storedPathname (storedPath), fileTime (time), + compressedSize (0), uncompressedSize (0), headerStart (0), + compressionLevel (compression), checksum (0) { } @@ -468,8 +463,8 @@ public: return false; } - compressedSize = (int) compressedData.getDataSize(); - headerStart = (int) (target.getPosition() - overallStartPosition); + compressedSize = (int64) compressedData.getDataSize(); + headerStart = target.getPosition() - overallStartPosition; target.writeInt (0x04034b50); writeFlagsAndSizes (target); @@ -488,7 +483,7 @@ public: target.writeShort (0); // start disk num target.writeShort (0); // internal attributes target.writeInt (0); // external attributes - target.writeInt (headerStart); + target.writeInt ((int) (uint32) headerStart); target << storedPathname; return true; @@ -499,7 +494,8 @@ private: ScopedPointer stream; String storedPathname; Time fileTime; - int compressionLevel, compressedSize, uncompressedSize, headerStart; + int64 compressedSize, uncompressedSize, headerStart; + int compressionLevel; unsigned long checksum; static void writeTimeAndDate (OutputStream& target, Time t) @@ -546,8 +542,8 @@ private: target.writeShort (compressionLevel > 0 ? (short) 8 : (short) 0); writeTimeAndDate (target, fileTime); target.writeInt ((int) checksum); - target.writeInt (compressedSize); - target.writeInt (uncompressedSize); + target.writeInt ((int) (uint32) compressedSize); + target.writeInt ((int) (uint32) uncompressedSize); target.writeShort ((short) storedPathname.toUTF8().sizeInBytes() - 1); target.writeShort (0); // extra field length } @@ -555,7 +551,7 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Item) }; -//============================================================================= +//============================================================================== ZipFile::Builder::Builder() {} ZipFile::Builder::~Builder() {} diff --git a/source/modules/juce_core/zip/juce_ZipFile.h b/source/modules/juce_core/zip/juce_ZipFile.h index 4d45da2cb..b253eeb76 100644 --- a/source/modules/juce_core/zip/juce_ZipFile.h +++ b/source/modules/juce_core/zip/juce_ZipFile.h @@ -80,7 +80,7 @@ public: String filename; /** The file's original size. */ - unsigned int uncompressedSize; + int64 uncompressedSize; /** The last time the file was modified. */ Time fileTime; diff --git a/source/modules/juce_data_structures/juce_data_structures.h b/source/modules/juce_data_structures/juce_data_structures.h index c130d57a9..48d321a63 100644 --- a/source/modules/juce_data_structures/juce_data_structures.h +++ b/source/modules/juce_data_structures/juce_data_structures.h @@ -25,7 +25,7 @@ #ifndef JUCE_DATA_STRUCTURES_H_INCLUDED #define JUCE_DATA_STRUCTURES_H_INCLUDED -//============================================================================= +//============================================================================== #include "../juce_events/juce_events.h" namespace juce diff --git a/source/modules/juce_events/broadcasters/juce_AsyncUpdater.cpp b/source/modules/juce_events/broadcasters/juce_AsyncUpdater.cpp index 64acb79da..8c67b003c 100644 --- a/source/modules/juce_events/broadcasters/juce_AsyncUpdater.cpp +++ b/source/modules/juce_events/broadcasters/juce_AsyncUpdater.cpp @@ -33,10 +33,8 @@ public: owner.handleAsyncUpdate(); } - Atomic shouldDeliver; - -private: AsyncUpdater& owner; + Atomic shouldDeliver; JUCE_DECLARE_NON_COPYABLE (AsyncUpdaterMessage) }; @@ -53,13 +51,19 @@ AsyncUpdater::~AsyncUpdater() // pending on the main event thread - that's pretty dodgy threading, as the callback could // happen after this destructor has finished. You should either use a MessageManagerLock while // deleting this object, or find some other way to avoid such a race condition. - jassert ((! isUpdatePending()) || MessageManager::getInstance()->currentThreadHasLockedMessageManager()); + jassert ((! isUpdatePending()) + || MessageManager::getInstanceWithoutCreating() == nullptr + || MessageManager::getInstanceWithoutCreating()->currentThreadHasLockedMessageManager()); activeMessage->shouldDeliver.set (0); } void AsyncUpdater::triggerAsyncUpdate() { + // If you're calling this before (or after) the MessageManager is + // running, then you're not going to get any callbacks! + jassert (MessageManager::getInstanceWithoutCreating() != nullptr); + if (activeMessage->shouldDeliver.compareAndSetBool (1, 0)) if (! activeMessage->post()) cancelPendingUpdate(); // if the message queue fails, this avoids getting diff --git a/source/modules/juce_events/juce_events.cpp b/source/modules/juce_events/juce_events.cpp index 89adf04dc..5514be444 100644 --- a/source/modules/juce_events/juce_events.cpp +++ b/source/modules/juce_events/juce_events.cpp @@ -31,7 +31,11 @@ #error "Incorrect use of JUCE cpp file" #endif -#include "../juce_core/native/juce_BasicNativeHeaders.h" +#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 +#define JUCE_CORE_INCLUDE_JNI_HELPERS 1 +#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 +#define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 + #include "juce_events.h" //============================================================================== @@ -69,17 +73,14 @@ namespace juce //============================================================================== #if JUCE_MAC - #include "../juce_core/native/juce_osx_ObjCHelpers.h" #include "native/juce_osx_MessageQueue.h" #include "native/juce_mac_MessageManager.mm" #elif JUCE_IOS - #include "../juce_core/native/juce_osx_ObjCHelpers.h" #include "native/juce_osx_MessageQueue.h" #include "native/juce_ios_MessageManager.mm" #elif JUCE_WINDOWS - #include "native/juce_win32_HiddenMessageWindow.h" #include "native/juce_win32_Messaging.cpp" #elif JUCE_LINUX @@ -87,7 +88,6 @@ namespace juce #include "native/juce_linux_Messaging.cpp" #elif JUCE_ANDROID - #include "../juce_core/native/juce_android_JNIHelpers.h" #include "native/juce_android_Messaging.cpp" #endif diff --git a/source/modules/juce_events/juce_events.h b/source/modules/juce_events/juce_events.h index 357df2de1..637e8623f 100644 --- a/source/modules/juce_events/juce_events.h +++ b/source/modules/juce_events/juce_events.h @@ -25,7 +25,7 @@ #ifndef JUCE_EVENTS_H_INCLUDED #define JUCE_EVENTS_H_INCLUDED -//============================================================================= +//============================================================================== #include "../juce_core/juce_core.h" namespace juce @@ -52,6 +52,10 @@ namespace juce #include "interprocess/juce_ConnectedChildProcess.h" #include "native/juce_ScopedXLock.h" +#if JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW && JUCE_WINDOWS + #include "native/juce_win32_HiddenMessageWindow.h" +#endif + } #endif // JUCE_EVENTS_H_INCLUDED diff --git a/source/modules/juce_events/native/juce_osx_MessageQueue.h b/source/modules/juce_events/native/juce_osx_MessageQueue.h index 7042f03a1..eee8242eb 100644 --- a/source/modules/juce_events/native/juce_osx_MessageQueue.h +++ b/source/modules/juce_events/native/juce_osx_MessageQueue.h @@ -46,7 +46,7 @@ public: CFRunLoopAddSource (runLoop, runLoopSource, kCFRunLoopCommonModes); } - ~MessageQueue() + ~MessageQueue() noexcept { CFRunLoopRemoveSource (runLoop, runLoopSource, kCFRunLoopCommonModes); CFRunLoopSourceInvalidate (runLoopSource); @@ -56,15 +56,20 @@ public: void post (MessageManager::MessageBase* const message) { messages.add (message); - CFRunLoopSourceSignal (runLoopSource); - CFRunLoopWakeUp (runLoop); + wakeUp(); } private: - ReferenceCountedArray messages; + ReferenceCountedArray messages; CFRunLoopRef runLoop; CFRunLoopSourceRef runLoopSource; + void wakeUp() noexcept + { + CFRunLoopSourceSignal (runLoopSource); + CFRunLoopWakeUp (runLoop); + } + bool deliverNextMessage() { const MessageManager::MessageBase::Ptr nextMessage (messages.removeAndReturn (0)); @@ -84,17 +89,16 @@ private: return true; } - void runLoopCallback() + void runLoopCallback() noexcept { for (int i = 4; --i >= 0;) if (! deliverNextMessage()) return; - CFRunLoopSourceSignal (runLoopSource); - CFRunLoopWakeUp (runLoop); + wakeUp(); } - static void runLoopSourceCallback (void* info) + static void runLoopSourceCallback (void* info) noexcept { static_cast (info)->runLoopCallback(); } diff --git a/source/modules/juce_events/native/juce_win32_Messaging.cpp b/source/modules/juce_events/native/juce_win32_Messaging.cpp index 36621acb6..c8fb937eb 100644 --- a/source/modules/juce_events/native/juce_win32_Messaging.cpp +++ b/source/modules/juce_events/native/juce_win32_Messaging.cpp @@ -30,15 +30,15 @@ CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr; //============================================================================== namespace WindowsMessageHelpers { - const unsigned int specialId = WM_APP + 0x4400; - const unsigned int broadcastId = WM_APP + 0x4403; + const unsigned int customMessageID = WM_USER + 123; + const unsigned int broadcastMessageMagicNumber = 0xc403; const TCHAR messageWindowName[] = _T("JUCEWindow"); ScopedPointer messageWindow; void dispatchMessageFromLParam (LPARAM lParam) { - if (MessageManager::MessageBase* const message = reinterpret_cast (lParam)) + if (MessageManager::MessageBase* message = reinterpret_cast (lParam)) { JUCE_TRY { @@ -50,12 +50,44 @@ namespace WindowsMessageHelpers } } + BOOL CALLBACK broadcastEnumWindowProc (HWND hwnd, LPARAM lParam) + { + if (hwnd != juce_messageWindowHandle) + { + TCHAR windowName[64] = { 0 }; // no need to read longer strings than this + GetWindowText (hwnd, windowName, 63); + + if (String (windowName) == messageWindowName) + reinterpret_cast*> (lParam)->add (hwnd); + } + + return TRUE; + } + + void handleBroadcastMessage (const COPYDATASTRUCT* const data) + { + if (data != nullptr && data->dwData == broadcastMessageMagicNumber) + { + struct BroadcastMessage : public CallbackMessage + { + BroadcastMessage (CharPointer_UTF32 text, size_t length) : message (text, length) {} + void messageCallback() override { MessageManager::getInstance()->deliverBroadcastMessage (message); } + + String message; + }; + + (new BroadcastMessage (CharPointer_UTF32 ((const CharPointer_UTF32::CharType*) data->lpData), + data->cbData / sizeof (CharPointer_UTF32::CharType))) + ->post(); + } + } + //============================================================================== LRESULT CALLBACK messageWndProc (HWND h, const UINT message, const WPARAM wParam, const LPARAM lParam) noexcept { if (h == juce_messageWindowHandle) { - if (message == specialId) + if (message == customMessageID) { // (These are trapped early in our dispatch loop, but must also be checked // here in case some 3rd-party code is running the dispatch loop). @@ -63,43 +95,15 @@ namespace WindowsMessageHelpers return 0; } - if (message == broadcastId) - { - if (String* const m = reinterpret_cast (lParam)) - { - const ScopedPointer messageString (m); - MessageManager::getInstance()->deliverBroadcastMessage (*m); - } - - return 0; - } - if (message == WM_COPYDATA) { - if (const COPYDATASTRUCT* const data = reinterpret_cast (lParam)) - { - if (data->dwData == broadcastId) - { - const String messageString (CharPointer_UTF32 ((const CharPointer_UTF32::CharType*) data->lpData), - data->cbData / sizeof (CharPointer_UTF32::CharType)); - - PostMessage (juce_messageWindowHandle, broadcastId, 0, (LPARAM) new String (messageString)); - return 0; - } - } + handleBroadcastMessage (reinterpret_cast (lParam)); + return 0; } } return DefWindowProc (h, message, wParam, lParam); } - - BOOL CALLBACK broadcastEnumWindowProc (HWND hwnd, LPARAM lParam) - { - if (hwnd != juce_messageWindowHandle) - reinterpret_cast*> (lParam)->add (hwnd); - - return TRUE; - } } //============================================================================== @@ -113,7 +117,7 @@ bool MessageManager::dispatchNextMessageOnSystemQueue (const bool returnIfNoPend if (GetMessage (&m, (HWND) 0, 0, 0) >= 0) { - if (m.message == specialId && m.hwnd == juce_messageWindowHandle) + if (m.message == customMessageID && m.hwnd == juce_messageWindowHandle) { dispatchMessageFromLParam (m.lParam); } @@ -146,36 +150,28 @@ bool MessageManager::dispatchNextMessageOnSystemQueue (const bool returnIfNoPend bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) { message->incReferenceCount(); - return PostMessage (juce_messageWindowHandle, WindowsMessageHelpers::specialId, 0, (LPARAM) message) != 0; + return PostMessage (juce_messageWindowHandle, WindowsMessageHelpers::customMessageID, 0, (LPARAM) message) != 0; } void MessageManager::broadcastMessage (const String& value) { - Array windows; - EnumWindows (&WindowsMessageHelpers::broadcastEnumWindowProc, (LPARAM) &windows); - const String localCopy (value); - COPYDATASTRUCT data; - data.dwData = WindowsMessageHelpers::broadcastId; - data.cbData = (localCopy.length() + 1) * sizeof (CharPointer_UTF32::CharType); - data.lpData = (void*) localCopy.toUTF32().getAddress(); + Array windows; + EnumWindows (&WindowsMessageHelpers::broadcastEnumWindowProc, (LPARAM) &windows); for (int i = windows.size(); --i >= 0;) { - HWND hwnd = windows.getUnchecked(i); - - TCHAR windowName[64] = { 0 }; // no need to read longer strings than this - GetWindowText (hwnd, windowName, 63); - - if (String (windowName) == WindowsMessageHelpers::messageWindowName) - { - DWORD_PTR result; - SendMessageTimeout (hwnd, WM_COPYDATA, - (WPARAM) juce_messageWindowHandle, - (LPARAM) &data, - SMTO_BLOCK | SMTO_ABORTIFHUNG, 8000, &result); - } + COPYDATASTRUCT data; + data.dwData = WindowsMessageHelpers::broadcastMessageMagicNumber; + data.cbData = (localCopy.length() + 1) * sizeof (CharPointer_UTF32::CharType); + data.lpData = (void*) localCopy.toUTF32().getAddress(); + + DWORD_PTR result; + SendMessageTimeout (windows.getUnchecked(i), WM_COPYDATA, + (WPARAM) juce_messageWindowHandle, + (LPARAM) &data, + SMTO_BLOCK | SMTO_ABORTIFHUNG, 8000, &result); } } diff --git a/source/modules/juce_events/timers/juce_Timer.cpp b/source/modules/juce_events/timers/juce_Timer.cpp index 5d9c38c43..e2be686ed 100644 --- a/source/modules/juce_events/timers/juce_Timer.cpp +++ b/source/modules/juce_events/timers/juce_Timer.cpp @@ -92,6 +92,9 @@ public: void callTimers() { + // avoid getting stuck in a loop if a timer callback repeatedly takes too long + const uint32 timeout = Time::getMillisecondCounter() + 100; + const LockType::ScopedLockType sl (lock); while (firstTimer != nullptr && firstTimer->timerCountdownMs <= 0) @@ -109,6 +112,9 @@ public: t->timerCallback(); } JUCE_CATCH_EXCEPTION + + if (Time::getMillisecondCounter() > timeout) + break; } callbackArrived.signal(); @@ -294,6 +300,10 @@ Timer::~Timer() void Timer::startTimer (const int interval) noexcept { + // If you're calling this before (or after) the MessageManager is + // running, then you're not going to get any timer callbacks! + jassert (MessageManager::getInstanceWithoutCreating() != nullptr); + const TimerThread::LockType::ScopedLockType sl (TimerThread::lock); if (timerPeriodMs == 0) diff --git a/source/modules/juce_graphics/colour/juce_PixelFormats.h b/source/modules/juce_graphics/colour/juce_PixelFormats.h index 0df5987b1..0666dd79f 100644 --- a/source/modules/juce_graphics/colour/juce_PixelFormats.h +++ b/source/modules/juce_graphics/colour/juce_PixelFormats.h @@ -108,7 +108,7 @@ public: forcedinline uint8 getGreen() const noexcept { return components.g; } forcedinline uint8 getBlue() const noexcept { return components.b; } - #if JUCE_GCC && ! JUCE_CLANG + #if JUCE_GCC // NB these are here as a workaround because GCC refuses to bind to packed values. forcedinline uint8& getAlpha() noexcept { return comps [indexA]; } forcedinline uint8& getRed() noexcept { return comps [indexR]; } diff --git a/source/modules/juce_graphics/geometry/juce_Line.h b/source/modules/juce_graphics/geometry/juce_Line.h index 02f91699b..a6dfadf37 100644 --- a/source/modules/juce_graphics/geometry/juce_Line.h +++ b/source/modules/juce_graphics/geometry/juce_Line.h @@ -126,6 +126,9 @@ public: /** Returns the length of the line. */ ValueType getLength() const noexcept { return start.getDistanceFrom (end); } + /** Returns the length of the line. */ + ValueType getLengthSquared() const noexcept { return start.getDistanceSquaredFrom (end); } + /** Returns true if the line's start and end x coordinates are the same. */ bool isVertical() const noexcept { return start.x == end.x; } diff --git a/source/modules/juce_graphics/geometry/juce_PathStrokeType.cpp b/source/modules/juce_graphics/geometry/juce_PathStrokeType.cpp index dbb5cb944..1923f153d 100644 --- a/source/modules/juce_graphics/geometry/juce_PathStrokeType.cpp +++ b/source/modules/juce_graphics/geometry/juce_PathStrokeType.cpp @@ -99,7 +99,8 @@ namespace PathStrokeHelpers return along >= 0 && along <= 1.0f; } - else if (dy2 == 0 && dy1 != 0) + + if (dy2 == 0 && dy1 != 0) { const float along = (y3 - y1) / dy1; intersectionX = x1 + along * dx1; @@ -112,7 +113,8 @@ namespace PathStrokeHelpers return along >= 0 && along <= 1.0f; } - else if (dx1 == 0 && dx2 != 0) + + if (dx1 == 0 && dx2 != 0) { const float along = (x1 - x3) / dx2; intersectionX = x1; @@ -126,7 +128,8 @@ namespace PathStrokeHelpers return along >= 0 && along <= 1.0f; } - else if (dx2 == 0 && dx1 != 0) + + if (dx2 == 0 && dx1 != 0) { const float along = (x3 - x1) / dx1; intersectionX = x3; @@ -147,33 +150,31 @@ namespace PathStrokeHelpers distanceBeyondLine1EndSquared = 0.0f; return false; } - else - { - const float along1 = ((y1 - y3) * dx2 - (x1 - x3) * dy2) / divisor; - intersectionX = x1 + along1 * dx1; - intersectionY = y1 + along1 * dy1; + const float along1 = ((y1 - y3) * dx2 - (x1 - x3) * dy2) / divisor; - if (along1 >= 0 && along1 <= 1.0f) - { - const float along2 = ((y1 - y3) * dx1 - (x1 - x3) * dy1); + intersectionX = x1 + along1 * dx1; + intersectionY = y1 + along1 * dy1; - if (along2 >= 0 && along2 <= divisor) - { - distanceBeyondLine1EndSquared = 0.0f; - return true; - } + if (along1 >= 0 && along1 <= 1.0f) + { + const float along2 = ((y1 - y3) * dx1 - (x1 - x3) * dy1) / divisor; + + if (along2 >= 0 && along2 <= 1.0f) + { + distanceBeyondLine1EndSquared = 0.0f; + return true; } + } - distanceBeyondLine1EndSquared = along1 - 1.0f; - distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; - distanceBeyondLine1EndSquared *= (dx1 * dx1 + dy1 * dy1); + distanceBeyondLine1EndSquared = along1 - 1.0f; + distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; + distanceBeyondLine1EndSquared *= (dx1 * dx1 + dy1 * dy1); - if (along1 < 1.0f) - distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; + if (along1 < 1.0f) + distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; - return false; - } + return false; } intersectionX = x2; @@ -666,9 +667,6 @@ void PathStrokeType::createDashedStroke (Path& destPath, if (thickness <= 0) return; - // this should really be an even number.. - jassert ((numDashLengths & 1) == 0); - Path newDestPath; PathFlatteningIterator it (sourcePath, transform, PathFlatteningIterator::defaultTolerance / extraAccuracy); diff --git a/source/modules/juce_graphics/geometry/juce_Point.h b/source/modules/juce_graphics/geometry/juce_Point.h index 33dc1c774..6f8f86385 100644 --- a/source/modules/juce_graphics/geometry/juce_Point.h +++ b/source/modules/juce_graphics/geometry/juce_Point.h @@ -147,10 +147,18 @@ public: /** Returns the straight-line distance between this point and another one. */ ValueType getDistanceFrom (Point other) const noexcept { return juce_hypot (x - other.x, y - other.y); } + /** Returns the square of the straight-line distance between this point and the origin. */ + ValueType getDistanceSquaredFromOrigin() const noexcept { return x * x + y * y; } + + /** Returns the square of the straight-line distance between this point and another one. */ + ValueType getDistanceSquaredFrom (Point other) const noexcept { return (*this - other).getDistanceSquaredFromOrigin(); } + /** Returns the angle from this point to another one. - The return value is the number of radians clockwise from the 12 o'clock direction, - where this point is the centre and the other point is on the circumference. + Taking this point to be the centre of a circle, and the other point being a position on + the circumference, the return value is the number of radians clockwise from the 12 o'clock + direction. + So 12 o'clock = 0, 3 o'clock = Pi/2, 6 o'clock = Pi, 9 o'clock = -Pi/2 */ FloatType getAngleToPoint (Point other) const noexcept { @@ -227,5 +235,9 @@ public: ValueType y; /**< The point's Y coordinate. */ }; +/** Multiplies the point's coordinates by a scalar value. */ +template +Point operator* (ValueType value, Point p) noexcept { return p * value; } + #endif // JUCE_POINT_H_INCLUDED diff --git a/source/modules/juce_graphics/geometry/juce_Rectangle.h b/source/modules/juce_graphics/geometry/juce_Rectangle.h index b3b74d06c..d3f3e7b41 100644 --- a/source/modules/juce_graphics/geometry/juce_Rectangle.h +++ b/source/modules/juce_graphics/geometry/juce_Rectangle.h @@ -774,7 +774,7 @@ public: /** Returns the smallest integer-aligned rectangle that completely contains this one. This is only relevent for floating-point rectangles, of course. - @see toFloat() + @see toFloat(), toNearestInt() */ Rectangle getSmallestIntegerContainer() const noexcept { @@ -786,6 +786,17 @@ public: return Rectangle (x1, y1, x2 - x1, y2 - y1); } + /** Casts this rectangle to a Rectangle. + This uses roundToInt to snap x, y, width and height to the nearest integer (losing precision). + If the rectangle already uses integers, this will simply return a copy. + @see getSmallestIntegerContainer() + */ + Rectangle toNearestInt() const noexcept + { + return Rectangle (roundToInt (pos.x), roundToInt (pos.y), + roundToInt (w), roundToInt (h)); + } + /** Casts this rectangle to a Rectangle. @see getSmallestIntegerContainer */ diff --git a/source/modules/juce_graphics/geometry/juce_RectangleList.h b/source/modules/juce_graphics/geometry/juce_RectangleList.h index 8863a0e20..ac3bc7d82 100644 --- a/source/modules/juce_graphics/geometry/juce_RectangleList.h +++ b/source/modules/juce_graphics/geometry/juce_RectangleList.h @@ -524,7 +524,7 @@ public: */ void consolidate() { - for (int i = 0; i < getNumRectangles() - 1; ++i) + for (int i = 0; i < rects.size() - 1; ++i) { RectangleType& r = rects.getReference (i); const ValueType rx1 = r.getX(); diff --git a/source/modules/juce_graphics/image_formats/jpglib/jconfig.h b/source/modules/juce_graphics/image_formats/jpglib/jconfig.h index 22f61408d..217ac449a 100644 --- a/source/modules/juce_graphics/image_formats/jpglib/jconfig.h +++ b/source/modules/juce_graphics/image_formats/jpglib/jconfig.h @@ -20,7 +20,9 @@ /* #define const */ #undef CHAR_IS_UNSIGNED #define HAVE_STDDEF_H -#define HAVE_STDLIB_H +#ifndef HAVE_STDLIB_H + #define HAVE_STDLIB_H +#endif #undef NEED_BSD_STRINGS #undef NEED_SYS_TYPES_H #undef NEED_FAR_POINTERS /* we presume a 32-bit flat memory model */ diff --git a/source/modules/juce_graphics/images/juce_Image.cpp b/source/modules/juce_graphics/images/juce_Image.cpp index d801b48ce..cb51d5d88 100644 --- a/source/modules/juce_graphics/images/juce_Image.cpp +++ b/source/modules/juce_graphics/images/juce_Image.cpp @@ -58,10 +58,17 @@ Image ImageType::convert (const Image& source) const Image newImage (create (src.pixelFormat, src.width, src.height, false)); Image::BitmapData dest (newImage, Image::BitmapData::writeOnly); - jassert (src.pixelStride == dest.pixelStride && src.pixelFormat == dest.pixelFormat); - - for (int y = 0; y < dest.height; ++y) - memcpy (dest.getLinePointer (y), src.getLinePointer (y), (size_t) dest.lineStride); + if (src.pixelStride == dest.pixelStride && src.pixelFormat == dest.pixelFormat) + { + for (int y = 0; y < dest.height; ++y) + memcpy (dest.getLinePointer (y), src.getLinePointer (y), (size_t) dest.lineStride); + } + else + { + for (int y = 0; y < dest.height; ++y) + for (int x = 0; x < dest.width; ++x) + dest.setPixelColour (x, y, src.getPixelColour (x, y)); + } return newImage; } diff --git a/source/modules/juce_graphics/juce_graphics.cpp b/source/modules/juce_graphics/juce_graphics.cpp index 4c0b2e0aa..a87a16639 100644 --- a/source/modules/juce_graphics/juce_graphics.cpp +++ b/source/modules/juce_graphics/juce_graphics.cpp @@ -31,7 +31,12 @@ #error "Incorrect use of JUCE cpp file" #endif -#include "../juce_core/native/juce_BasicNativeHeaders.h" +#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 +#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 +#define JUCE_CORE_INCLUDE_JNI_HELPERS 1 +#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 +#define JUCE_GRAPHICS_INCLUDE_COREGRAPHICS_HELPERS 1 + #include "juce_graphics.h" //============================================================================== @@ -127,13 +132,10 @@ namespace juce //============================================================================== #if JUCE_MAC || JUCE_IOS - #include "../juce_core/native/juce_osx_ObjCHelpers.h" - #include "native/juce_mac_CoreGraphicsHelpers.h" #include "native/juce_mac_Fonts.mm" #include "native/juce_mac_CoreGraphicsContext.mm" #elif JUCE_WINDOWS - #include "../juce_core/native/juce_win32_ComSmartPtr.h" #include "native/juce_win32_DirectWriteTypeface.cpp" #include "native/juce_win32_DirectWriteTypeLayout.cpp" #include "native/juce_win32_Fonts.cpp" @@ -145,7 +147,6 @@ namespace juce #include "native/juce_linux_Fonts.cpp" #elif JUCE_ANDROID - #include "../juce_core/native/juce_android_JNIHelpers.h" #include "native/juce_android_GraphicsContext.cpp" #include "native/juce_android_Fonts.cpp" diff --git a/source/modules/juce_graphics/juce_graphics.h b/source/modules/juce_graphics/juce_graphics.h index 3aa7a16e5..9e334963a 100644 --- a/source/modules/juce_graphics/juce_graphics.h +++ b/source/modules/juce_graphics/juce_graphics.h @@ -28,7 +28,7 @@ #include "../juce_core/juce_core.h" #include "../juce_events/juce_events.h" -//============================================================================= +//============================================================================== /** Config: JUCE_USE_COREIMAGE_LOADER On OSX, enabling this flag means that the CoreImage codecs will be used to load @@ -60,7 +60,7 @@ #define USE_COREGRAPHICS_RENDERING 1 #endif -//============================================================================= +//============================================================================== namespace juce { @@ -108,6 +108,11 @@ class LowLevelGraphicsContext; #include "effects/juce_DropShadowEffect.h" #include "effects/juce_GlowEffect.h" +#if JUCE_GRAPHICS_INCLUDE_COREGRAPHICS_HELPERS && (JUCE_MAC || JUCE_IOS) + #include "native/juce_mac_CoreGraphicsHelpers.h" + #include "native/juce_mac_CoreGraphicsContext.h" +#endif + } #endif // JUCE_GRAPHICS_H_INCLUDED diff --git a/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.h b/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.h index df34033b3..4210c2acb 100644 --- a/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.h +++ b/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.h @@ -29,7 +29,7 @@ class CoreGraphicsContext : public LowLevelGraphicsContext { public: - CoreGraphicsContext (CGContextRef context, const float flipHeight, const float targetScale); + CoreGraphicsContext (CGContextRef context, float flipHeight, float targetScale); ~CoreGraphicsContext(); //============================================================================== diff --git a/source/modules/juce_graphics/native/juce_mac_Fonts.mm b/source/modules/juce_graphics/native/juce_mac_Fonts.mm index dcff0be5e..239053f68 100644 --- a/source/modules/juce_graphics/native/juce_mac_Fonts.mm +++ b/source/modules/juce_graphics/native/juce_mac_Fonts.mm @@ -212,6 +212,45 @@ namespace CoreTextTypeLayout return createCTFont (f, referenceFontSize, transform); } + //============================================================================== + static CTTextAlignment getTextAlignment (const AttributedString& text) + { + switch (text.getJustification().getOnlyHorizontalFlags()) + { + #if defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 + case Justification::right: return kCTTextAlignmentRight; + case Justification::horizontallyCentred: return kCTTextAlignmentCenter; + case Justification::horizontallyJustified: return kCTTextAlignmentJustified; + default: return kCTTextAlignmentLeft; + #else + case Justification::right: return kCTRightTextAlignment; + case Justification::horizontallyCentred: return kCTCenterTextAlignment; + case Justification::horizontallyJustified: return kCTJustifiedTextAlignment; + default: return kCTLeftTextAlignment; + #endif + } + } + + static CTLineBreakMode getLineBreakMode (const AttributedString& text) + { + switch (text.getWordWrap()) + { + case AttributedString::none: return kCTLineBreakByClipping; + case AttributedString::byChar: return kCTLineBreakByCharWrapping; + default: return kCTLineBreakByWordWrapping; + } + } + + static CTWritingDirection getWritingDirection (const AttributedString& text) + { + switch (text.getReadingDirection()) + { + case AttributedString::ReadingDirection::rightToLeft: return kCTWritingDirectionRightToLeft; + case AttributedString::ReadingDirection::leftToRight: return kCTWritingDirectionLeftToRight; + default: return kCTWritingDirectionNatural; + } + } + //============================================================================== static CFAttributedStringRef createCFAttributedString (const AttributedString& text) { @@ -221,11 +260,9 @@ namespace CoreTextTypeLayout CFMutableAttributedStringRef attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0); - if (CFStringRef cfText = text.getText().toCFString()) - { - CFAttributedStringReplaceString (attribString, CFRangeMake (0, 0), cfText); - CFRelease (cfText); - } + CFStringRef cfText = text.getText().toCFString(); + CFAttributedStringReplaceString (attribString, CFRangeMake (0, 0), cfText); + CFRelease (cfText); const int numCharacterAttributes = text.getNumAttributes(); const CFIndex attribStringLen = CFAttributedStringGetLength (attribString); @@ -282,42 +319,16 @@ namespace CoreTextTypeLayout } // Paragraph Attributes - #if defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 - CTTextAlignment ctTextAlignment = kCTTextAlignmentLeft; - #else - CTTextAlignment ctTextAlignment = kCTLeftTextAlignment; - #endif - - CTLineBreakMode ctLineBreakMode = kCTLineBreakByWordWrapping; + CTTextAlignment ctTextAlignment = getTextAlignment (text); + CTLineBreakMode ctLineBreakMode = getLineBreakMode (text); + CTWritingDirection ctWritingDirection = getWritingDirection (text); const CGFloat ctLineSpacing = text.getLineSpacing(); - switch (text.getJustification().getOnlyHorizontalFlags()) - { - case Justification::left: break; - #if defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 - case Justification::right: ctTextAlignment = kCTTextAlignmentRight; break; - case Justification::horizontallyCentred: ctTextAlignment = kCTTextAlignmentCenter; break; - case Justification::horizontallyJustified: ctTextAlignment = kCTTextAlignmentJustified; break; - #else - case Justification::right: ctTextAlignment = kCTRightTextAlignment; break; - case Justification::horizontallyCentred: ctTextAlignment = kCTCenterTextAlignment; break; - case Justification::horizontallyJustified: ctTextAlignment = kCTJustifiedTextAlignment; break; - #endif - default: jassertfalse; break; // Illegal justification flags - } - - switch (text.getWordWrap()) - { - case AttributedString::byWord: break; - case AttributedString::none: ctLineBreakMode = kCTLineBreakByClipping; break; - case AttributedString::byChar: ctLineBreakMode = kCTLineBreakByCharWrapping; break; - default: break; - } - CTParagraphStyleSetting settings[] = { { kCTParagraphStyleSpecifierAlignment, sizeof (CTTextAlignment), &ctTextAlignment }, { kCTParagraphStyleSpecifierLineBreakMode, sizeof (CTLineBreakMode), &ctLineBreakMode }, + { kCTParagraphStyleSpecifierBaseWritingDirection, sizeof (CTWritingDirection), &ctWritingDirection}, #if defined (MAC_OS_X_VERSION_10_7) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof (CGFloat), &ctLineSpacing } diff --git a/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp b/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp index e5d47236f..c03ed3721 100644 --- a/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp +++ b/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp @@ -190,7 +190,7 @@ namespace DirectWriteTypeLayout JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomDirectWriteTextRenderer) }; - //================================================================================================== + //============================================================================== static float getFontHeightToEmSizeFactor (IDWriteFont& dwFont) { ComSmartPtr dwFontFace; diff --git a/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp b/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp index 66e4d2e25..d6f785e6d 100644 --- a/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp +++ b/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp @@ -132,7 +132,7 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DFactories) }; -//================================================================================================== +//============================================================================== class WindowsDirectWriteTypeface : public Typeface { public: diff --git a/source/modules/juce_gui_basics/buttons/juce_Button.cpp b/source/modules/juce_gui_basics/buttons/juce_Button.cpp index 89181a348..e33ab2677 100644 --- a/source/modules/juce_gui_basics/buttons/juce_Button.cpp +++ b/source/modules/juce_gui_basics/buttons/juce_Button.cpp @@ -84,6 +84,7 @@ Button::Button (const String& name) connectedEdgeFlags (0), commandID(), buttonState (buttonNormal), + lastStatePainted (buttonNormal), lastToggleState (false), clickTogglesState (false), needsToRelease (false), @@ -430,6 +431,7 @@ void Button::paint (Graphics& g) } paintButton (g, isOver(), isDown()); + lastStatePainted = buttonState; } //============================================================================== @@ -457,7 +459,12 @@ void Button::mouseUp (const MouseEvent& e) updateState (isMouseOver(), false); if (wasDown && wasOver && ! triggerOnMouseDown) + { + if (lastStatePainted != buttonDown) + flashButtonState(); + internalClickCallback (e.mods); + } } void Button::mouseDrag (const MouseEvent&) diff --git a/source/modules/juce_gui_basics/buttons/juce_Button.h b/source/modules/juce_gui_basics/buttons/juce_Button.h index 8704e6c59..ef4a2e7d7 100644 --- a/source/modules/juce_gui_basics/buttons/juce_Button.h +++ b/source/modules/juce_gui_basics/buttons/juce_Button.h @@ -478,7 +478,7 @@ private: int autoRepeatDelay, autoRepeatSpeed, autoRepeatMinimumDelay; int radioGroupId, connectedEdgeFlags; CommandID commandID; - ButtonState buttonState; + ButtonState buttonState, lastStatePainted; Value isOn; bool lastToggleState; diff --git a/source/modules/juce_gui_basics/components/juce_Component.cpp b/source/modules/juce_gui_basics/components/juce_Component.cpp index 100643442..77c61c3e9 100644 --- a/source/modules/juce_gui_basics/components/juce_Component.cpp +++ b/source/modules/juce_gui_basics/components/juce_Component.cpp @@ -401,7 +401,7 @@ struct Component::ComponentHelpers static bool clipObscuredRegions (const Component& comp, Graphics& g, const Rectangle clipRect, Point delta) { - bool nothingChanged = true; + bool wasClipped = false; for (int i = comp.childComponentList.size(); --i >= 0;) { @@ -416,19 +416,19 @@ struct Component::ComponentHelpers if (child.isOpaque() && child.componentTransparency == 0) { g.excludeClipRegion (newClip + delta); - nothingChanged = false; + wasClipped = true; } else { const Point childPos (child.getPosition()); if (clipObscuredRegions (child, g, newClip - childPos, childPos + delta)) - nothingChanged = false; + wasClipped = true; } } } } - return nothingChanged; + return wasClipped; } static Rectangle getParentOrMainMonitorBounds (const Component& comp) @@ -1721,50 +1721,46 @@ void Component::enterModalState (const bool shouldTakeKeyboardFocus, // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. ASSERT_MESSAGE_MANAGER_IS_LOCKED - // Check for an attempt to make a component modal when it already is! - // This can cause nasty problems.. - jassert (! flags.currentlyModalFlag); - if (! isCurrentlyModal()) { - ModalComponentManager* const mcm = ModalComponentManager::getInstance(); - mcm->startModal (this, deleteWhenDismissed); - mcm->attachCallback (this, callback); + ModalComponentManager& mcm = *ModalComponentManager::getInstance(); + mcm.startModal (this, deleteWhenDismissed); + mcm.attachCallback (this, callback); - flags.currentlyModalFlag = true; setVisible (true); if (shouldTakeKeyboardFocus) grabKeyboardFocus(); } + else + { + // Probably a bad idea to try to make a component modal twice! + jassertfalse; + } } void Component::exitModalState (const int returnValue) { - if (flags.currentlyModalFlag) + if (isCurrentlyModal()) { if (MessageManager::getInstance()->isThisTheMessageThread()) { - ModalComponentManager::getInstance()->endModal (this, returnValue); - flags.currentlyModalFlag = false; - - ModalComponentManager::getInstance()->bringModalComponentsToFront(); + ModalComponentManager& mcm = *ModalComponentManager::getInstance(); + mcm.endModal (this, returnValue); + mcm.bringModalComponentsToFront(); } else { - class ExitModalStateMessage : public CallbackMessage + struct ExitModalStateMessage : public CallbackMessage { - public: - ExitModalStateMessage (Component* const c, const int res) - : target (c), result (res) {} + ExitModalStateMessage (Component* c, int res) : target (c), result (res) {} void messageCallback() override { - if (target.get() != nullptr) // (get() required for VS2003 bug) - target->exitModalState (result); + if (Component* c = target) + c->exitModalState (result); } - private: WeakReference target; int result; }; @@ -1776,8 +1772,7 @@ void Component::exitModalState (const int returnValue) bool Component::isCurrentlyModal() const noexcept { - return flags.currentlyModalFlag - && getCurrentlyModalComponent() == this; + return getCurrentlyModalComponent() == this; } bool Component::isCurrentlyBlockedByAnotherModalComponent() const @@ -1966,7 +1961,7 @@ void Component::paintComponentAndChildren (Graphics& g) { g.saveState(); - if (ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, Point()) || ! g.isClipEmpty()) + if (! (ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, Point()) && g.isClipEmpty())) paint (g); g.restoreState(); @@ -2969,14 +2964,18 @@ bool Component::isMouseButtonDown() const return false; } -bool Component::isMouseOverOrDragging() const +bool Component::isMouseOverOrDragging (const bool includeChildren) const { const Array& mouseSources = Desktop::getInstance().getMouseSources(); for (MouseInputSource* mi = mouseSources.begin(), * const e = mouseSources.end(); mi != e; ++mi) - if (mi->getComponentUnderMouse() == this + { + Component* const c = mi->getComponentUnderMouse(); + + if ((c == this || (includeChildren && isParentOf (c))) && (mi->isMouse() || mi->isDragging())) return true; + } return false; } diff --git a/source/modules/juce_gui_basics/components/juce_Component.h b/source/modules/juce_gui_basics/components/juce_Component.h index 7100365fd..ce47460bc 100644 --- a/source/modules/juce_gui_basics/components/juce_Component.h +++ b/source/modules/juce_gui_basics/components/juce_Component.h @@ -544,9 +544,8 @@ public: /** Changes the position of the component's centre. - Leaves the position unchanged, but positions its centre relative to its - parent's size. E.g. setCentreRelative (0.5f, 0.5f) would place it centrally in - its parent. + Leaves the size unchanged, but positions its centre relative to its parent's size. + E.g. setCentreRelative (0.5f, 0.5f) would place it centrally in its parent. */ void setCentreRelative (float x, float y); @@ -1768,7 +1767,7 @@ public: This is a handy equivalent to (isMouseOver() || isMouseButtonDown()). @see isMouseOver, isMouseButtonDown, isMouseButtonDownAnywhere */ - bool isMouseOverOrDragging() const; + bool isMouseOverOrDragging (bool includeChildren = false) const; /** Returns true if a mouse button is currently down. @@ -2277,7 +2276,6 @@ private: bool bufferToImageFlag : 1; bool bringToFrontOnClickFlag : 1; bool repaintOnMouseActivityFlag : 1; - bool currentlyModalFlag : 1; bool isDisabledFlag : 1; bool childCompFocusedFlag : 1; bool dontClipGraphicsFlag : 1; diff --git a/source/modules/juce_gui_basics/components/juce_Desktop.cpp b/source/modules/juce_gui_basics/components/juce_Desktop.cpp index 56f818b60..26cc3c9d5 100644 --- a/source/modules/juce_gui_basics/components/juce_Desktop.cpp +++ b/source/modules/juce_gui_basics/components/juce_Desktop.cpp @@ -386,10 +386,14 @@ void Desktop::setKioskModeComponent (Component* componentToUse, const bool allow //============================================================================== void Desktop::setOrientationsEnabled (const int newOrientations) { - // Dodgy set of flags being passed here! Make sure you specify at least one permitted orientation. - jassert (newOrientations != 0 && (newOrientations & ~allOrientations) == 0); + if (allowedOrientations != newOrientations) + { + // Dodgy set of flags being passed here! Make sure you specify at least one permitted orientation. + jassert (newOrientations != 0 && (newOrientations & ~allOrientations) == 0); - allowedOrientations = newOrientations; + allowedOrientations = newOrientations; + allowedOrientationsChanged(); + } } bool Desktop::isOrientationEnabled (const DisplayOrientation orientation) const noexcept diff --git a/source/modules/juce_gui_basics/components/juce_Desktop.h b/source/modules/juce_gui_basics/components/juce_Desktop.h index a31d71e94..02657b63c 100644 --- a/source/modules/juce_gui_basics/components/juce_Desktop.h +++ b/source/modules/juce_gui_basics/components/juce_Desktop.h @@ -394,6 +394,11 @@ public: /** True if the OS supports semitransparent windows */ static bool canUseSemiTransparentWindows() noexcept; + #if JUCE_MAC + /** OSX-specific function to check for the "dark" title-bar and menu mode. */ + static bool isOSXDarkModeActive(); + #endif + private: //============================================================================== static Desktop* instance; @@ -429,6 +434,8 @@ private: bool kioskModeReentrant; int allowedOrientations; + void allowedOrientationsChanged(); + float masterScaleFactor; ComponentAnimator animator; diff --git a/source/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp b/source/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp index b2a65d70b..96d8e9092 100644 --- a/source/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp +++ b/source/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp @@ -32,6 +32,7 @@ DrawableShape::DrawableShape() DrawableShape::DrawableShape (const DrawableShape& other) : Drawable (other), strokeType (other.strokeType), + dashLengths (other.dashLengths), mainFill (other.mainFill), strokeFill (other.strokeFill) { @@ -132,6 +133,15 @@ void DrawableShape::setStrokeType (const PathStrokeType& newStrokeType) } } +void DrawableShape::setDashLengths (const Array& newDashLengths) +{ + if (dashLengths != newDashLengths) + { + dashLengths = newDashLengths; + strokeChanged(); + } +} + void DrawableShape::setStrokeThickness (const float newThickness) { setStrokeType (PathStrokeType (newThickness, strokeType.getJointStyle(), strokeType.getEndStyle())); @@ -178,7 +188,13 @@ void DrawableShape::pathChanged() void DrawableShape::strokeChanged() { strokePath.clear(); - strokeType.createStrokedPath (strokePath, path, AffineTransform(), 4.0f); + const float extraAccuracy = 4.0f; + + if (dashLengths.empty()) + strokeType.createStrokedPath (strokePath, path, AffineTransform(), extraAccuracy); + else + strokeType.createDashedStroke (strokePath, path, dashLengths.getRawDataPointer(), + dashLengths.size(), AffineTransform(), extraAccuracy); setBoundsToEnclose (getDrawableBounds()); repaint(); diff --git a/source/modules/juce_gui_basics/drawables/juce_DrawableShape.h b/source/modules/juce_gui_basics/drawables/juce_DrawableShape.h index 5fa5b3ed4..20cbc3cf7 100644 --- a/source/modules/juce_gui_basics/drawables/juce_DrawableShape.h +++ b/source/modules/juce_gui_basics/drawables/juce_DrawableShape.h @@ -122,6 +122,12 @@ public: /** Returns the current outline style. */ const PathStrokeType& getStrokeType() const noexcept { return strokeType; } + /** Provides a set of dash lengths to use for stroking the path. */ + void setDashLengths (const Array& newDashLengths); + + /** Returns the set of dash lengths that the path is using. */ + const Array& getDashLengths() const noexcept { return dashLengths; }; + //============================================================================== /** @internal */ class FillAndStrokeState : public Drawable::ValueTreeWrapperBase @@ -165,6 +171,7 @@ protected: //============================================================================== PathStrokeType strokeType; + Array dashLengths; Path path, strokePath; private: diff --git a/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp b/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp index 01d487228..6ae599aba 100644 --- a/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp +++ b/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp @@ -42,6 +42,26 @@ public: const XmlElement* operator->() const noexcept { return xml; } XmlPath getChild (const XmlElement* e) const noexcept { return XmlPath (e, this); } + template + bool applyOperationToChildWithID (const String& id, OperationType& op) const + { + forEachXmlChildElement (*xml, e) + { + XmlPath child (e, this); + + if (e->compareAttribute ("id", id)) + { + op (child); + return true; + } + + if (child.applyOperationToChildWithID (id, op)) + return true; + } + + return false; + } + const XmlElement* xml; const XmlPath* parent; }; @@ -364,25 +384,40 @@ private: Drawable* parseSubElement (const XmlPath& xml) { + { + Path path; + if (parsePathElement (xml, path)) + return parseShape (xml, path); + } + const String tag (xml->getTagNameWithoutNamespace()); - if (tag == "g") return parseGroupElement (xml); - if (tag == "svg") return parseSVGElement (xml); - if (tag == "path") return parsePath (xml); - if (tag == "rect") return parseRect (xml); - if (tag == "circle") return parseCircle (xml); - if (tag == "ellipse") return parseEllipse (xml); - if (tag == "line") return parseLine (xml); - if (tag == "polyline") return parsePolygon (xml, true); - if (tag == "polygon") return parsePolygon (xml, false); - if (tag == "text") return parseText (xml, true); - if (tag == "switch") return parseSwitch (xml); - if (tag == "a") return parseLinkElement (xml); - if (tag == "style") parseCSSStyle (xml); + if (tag == "g") return parseGroupElement (xml); + if (tag == "svg") return parseSVGElement (xml); + if (tag == "text") return parseText (xml, true); + if (tag == "switch") return parseSwitch (xml); + if (tag == "a") return parseLinkElement (xml); + if (tag == "style") parseCSSStyle (xml); return nullptr; } + bool parsePathElement (const XmlPath& xml, Path& path) const + { + const String tag (xml->getTagNameWithoutNamespace()); + + if (tag == "path") { parsePath (xml, path); return true; } + if (tag == "rect") { parseRect (xml, path); return true; } + if (tag == "circle") { parseCircle (xml, path); return true; } + if (tag == "ellipse") { parseEllipse (xml, path); return true; } + if (tag == "line") { parseLine (xml, path); return true; } + if (tag == "polyline") { parsePolygon (xml, true, path); return true; } + if (tag == "polygon") { parsePolygon (xml, false, path); return true; } + if (tag == "use") { parseUse (xml, path); return true; } + + return false; + } + DrawableComposite* parseSwitch (const XmlPath& xml) { if (const XmlElement* const group = xml->getChildByName ("g")) @@ -419,21 +454,16 @@ private: } //============================================================================== - Drawable* parsePath (const XmlPath& xml) const + void parsePath (const XmlPath& xml, Path& path) const { - Path path; parsePathString (path, xml->getStringAttribute ("d")); if (getStyleAttribute (xml, "fill-rule").trim().equalsIgnoreCase ("evenodd")) path.setUsingNonZeroWinding (false); - - return parseShape (xml, path); } - Drawable* parseRect (const XmlPath& xml) const + void parseRect (const XmlPath& xml, Path& rect) const { - Path rect; - const bool hasRX = xml->hasAttribute ("rx"); const bool hasRY = xml->hasAttribute ("ry"); @@ -460,41 +490,29 @@ private: getCoordLength (xml, "width", viewBoxW), getCoordLength (xml, "height", viewBoxH)); } - - return parseShape (xml, rect); } - Drawable* parseCircle (const XmlPath& xml) const + void parseCircle (const XmlPath& xml, Path& circle) const { - Path circle; - const float cx = getCoordLength (xml, "cx", viewBoxW); const float cy = getCoordLength (xml, "cy", viewBoxH); const float radius = getCoordLength (xml, "r", viewBoxW); circle.addEllipse (cx - radius, cy - radius, radius * 2.0f, radius * 2.0f); - - return parseShape (xml, circle); } - Drawable* parseEllipse (const XmlPath& xml) const + void parseEllipse (const XmlPath& xml, Path& ellipse) const { - Path ellipse; - const float cx = getCoordLength (xml, "cx", viewBoxW); const float cy = getCoordLength (xml, "cy", viewBoxH); const float radiusX = getCoordLength (xml, "rx", viewBoxW); const float radiusY = getCoordLength (xml, "ry", viewBoxH); ellipse.addEllipse (cx - radiusX, cy - radiusY, radiusX * 2.0f, radiusY * 2.0f); - - return parseShape (xml, ellipse); } - Drawable* parseLine (const XmlPath& xml) const + void parseLine (const XmlPath& xml, Path& line) const { - Path line; - const float x1 = getCoordLength (xml, "x1", viewBoxW); const float y1 = getCoordLength (xml, "y1", viewBoxH); const float x2 = getCoordLength (xml, "x2", viewBoxW); @@ -502,15 +520,12 @@ private: line.startNewSubPath (x1, y1); line.lineTo (x2, y2); - - return parseShape (xml, line); } - Drawable* parsePolygon (const XmlPath& xml, const bool isPolyline) const + void parsePolygon (const XmlPath& xml, const bool isPolyline, Path& path) const { const String pointsAtt (xml->getStringAttribute ("points")); String::CharPointerType points (pointsAtt.getCharPointer()); - Path path; Point p; if (parseCoords (points, p, true)) @@ -528,8 +543,39 @@ private: if ((! isPolyline) || first == last) path.closeSubPath(); } + } + + void parseUse (const XmlPath& xml, Path& path) const + { + const String link (xml->getStringAttribute ("xlink:href")); + + if (link.startsWithChar ('#')) + { + const String linkedID = link.substring (1); + + struct UsePathOp + { + const SVGState* state; + Path* targetPath; - return parseShape (xml, path); + void operator() (const XmlPath& xmlPath) + { + state->parsePathElement (xmlPath, *targetPath); + } + }; + + UsePathOp op = { this, &path }; + topLevelXml.applyOperationToChildWithID (linkedID, op); + } + } + + static String parseURL (const String& str) + { + if (str.startsWithIgnoreCase ("url")) + return str.fromFirstOccurrenceOf ("#", false, false) + .upToLastOccurrenceOf (")", false, false).trim(); + + return String(); } //============================================================================== @@ -570,6 +616,13 @@ private: dp->setStrokeType (getStrokeFor (xml)); } + const String strokeDashArray (getStyleAttribute (xml, "stroke-dasharray")); + + if (strokeDashArray.isNotEmpty()) + parseDashArray (strokeDashArray, *dp); + + parseClipPath (xml, *dp); + return dp; } @@ -582,16 +635,88 @@ private: return false; } - struct SetGradientStopsOp + void parseDashArray (const String& dashList, DrawablePath& dp) const { - const SVGState* state; - ColourGradient* gradient; + if (dashList.equalsIgnoreCase ("null") || dashList.equalsIgnoreCase ("none")) + return; + + Array dashLengths; - void operator() (const XmlPath& xml) + for (String::CharPointerType t = dashList.getCharPointer();;) { - state->addGradientStopsIn (*gradient, xml); + float value; + if (! parseCoord (t, value, true, true)) + break; + + dashLengths.add (value); + + t = t.findEndOfWhitespace(); + + if (*t == ',') + ++t; } - }; + + if (dashLengths.size() > 0) + { + float* const dashes = dashLengths.getRawDataPointer(); + + for (int i = 0; i < dashLengths.size(); ++i) + { + if (dashes[i] <= 0) // SVG uses zero-length dashes to mean a dotted line + { + if (dashLengths.size() == 1) + return; + + const float nonZeroLength = 0.001f; + dashes[i] = nonZeroLength; + + const int pairedIndex = i ^ 1; + + if (isPositiveAndBelow (pairedIndex, dashLengths.size()) + && dashes[pairedIndex] > nonZeroLength) + dashes[pairedIndex] -= nonZeroLength; + } + } + + dp.setDashLengths (dashLengths); + } + } + + void parseClipPath (const XmlPath& xml, Drawable& d) const + { + const String clipPath (getStyleAttribute (xml, "clip-path")); + + if (clipPath.isNotEmpty()) + { + String urlID = parseURL (clipPath); + + if (urlID.isNotEmpty()) + { + struct GetClipPathOp + { + const SVGState* state; + Drawable* target; + + void operator() (const XmlPath& xmlPath) + { + state->applyClipPath (*target, xmlPath); + } + }; + + GetClipPathOp op = { this, &d }; + topLevelXml.applyOperationToChildWithID (urlID, op); + } + } + } + + void applyClipPath (Drawable& target, const XmlPath& xmlPath) const + { + if (xmlPath->hasTagNameIgnoringNamespace ("clipPath")) + { + // TODO: implement clipping.. + ignoreUnused (target); + } + } void addGradientStopsIn (ColourGradient& cg, const XmlPath& fillXml) const { @@ -626,8 +751,19 @@ private: if (id.startsWithChar ('#')) { + struct SetGradientStopsOp + { + const SVGState* state; + ColourGradient* gradient; + + void operator() (const XmlPath& xml) + { + state->addGradientStopsIn (*gradient, xml); + } + }; + SetGradientStopsOp op = { this, &gradient, }; - findElementForId (topLevelXml, id.substring (1), op); + topLevelXml.applyOperationToChildWithID (id.substring (1), op); } } @@ -736,21 +872,6 @@ private: return type; } - struct GetFillTypeOp - { - const SVGState* state; - FillType* dest; - const Path* path; - float opacity; - - void operator() (const XmlPath& xml) - { - if (xml->hasTagNameIgnoringNamespace ("linearGradient") - || xml->hasTagNameIgnoringNamespace ("radialGradient")) - *dest = state->getGradientFillType (xml, *path, opacity); - } - }; - FillType getPathFillType (const Path& path, const String& fill, const String& fillOpacity, @@ -765,16 +886,29 @@ private: if (fillOpacity.isNotEmpty()) opacity *= (jlimit (0.0f, 1.0f, fillOpacity.getFloatValue())); - if (fill.startsWithIgnoreCase ("url")) + String urlID = parseURL (fill); + + if (urlID.isNotEmpty()) { - const String id (fill.fromFirstOccurrenceOf ("#", false, false) - .upToLastOccurrenceOf (")", false, false).trim()); + struct GetFillTypeOp + { + const SVGState* state; + const Path* path; + float opacity; + FillType fillType; + + void operator() (const XmlPath& xml) + { + if (xml->hasTagNameIgnoringNamespace ("linearGradient") + || xml->hasTagNameIgnoringNamespace ("radialGradient")) + fillType = state->getGradientFillType (xml, *path, opacity); + } + }; - FillType result; - GetFillTypeOp op = { this, &result, &path, opacity }; + GetFillTypeOp op = { this, &path, opacity }; - if (findElementForId (topLevelXml, id, op)) - return result; + if (topLevelXml.applyOperationToChildWithID (urlID, op)) + return op.fillType; } if (fill.equalsIgnoreCase ("none")) @@ -1337,24 +1471,6 @@ private: deltaAngle = fmod (deltaAngle, double_Pi * 2.0); } - template - static bool findElementForId (const XmlPath& parent, const String& id, OperationType& op) - { - forEachXmlChildElement (*parent, e) - { - if (e->compareAttribute ("id", id)) - { - op (parent.getChild (e)); - return true; - } - - if (findElementForId (parent.getChild (e), id, op)) - return true; - } - - return false; - } - SVGState& operator= (const SVGState&) JUCE_DELETED_FUNCTION; }; diff --git a/source/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp b/source/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp index d7c0feb3c..afe498a22 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp +++ b/source/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp @@ -33,7 +33,7 @@ FileBrowserComponent::FileBrowserComponent (int flags_, currentPathBox ("path"), fileLabel ("f", TRANS ("file:")), thread ("Juce FileBrowser"), - wasProcessActive (false) + wasProcessActive (true) { // You need to specify one or other of the open/save flags.. jassert ((flags & (saveMode | openMode)) != 0); diff --git a/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp b/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp index 40c6108cd..97c3f3252 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp +++ b/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp @@ -25,11 +25,13 @@ FileChooser::FileChooser (const String& chooserBoxTitle, const File& currentFileOrDirectory, const String& fileFilters, - const bool useNativeBox) + const bool useNativeBox, + const bool treatFilePackagesAsDirectories) : title (chooserBoxTitle), filters (fileFilters), startingFile (currentFileOrDirectory), - useNativeDialogBox (useNativeBox && isPlatformDialogAvailable()) + useNativeDialogBox (useNativeBox && isPlatformDialogAvailable()), + treatFilePackagesAsDirs (treatFilePackagesAsDirectories) { if (! fileFilters.containsNonWhitespaceChars()) filters = "*"; @@ -106,7 +108,8 @@ bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previ { showPlatformDialog (results, title, startingFile, filters, selectsDirectories, selectsFiles, isSave, - warnAboutOverwrite, selectMultiple, previewComp); + warnAboutOverwrite, selectMultiple, treatFilePackagesAsDirs, + previewComp); } else { diff --git a/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.h b/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.h index 4dc44b3d4..f7399751b 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.h +++ b/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.h @@ -60,25 +60,32 @@ public: After creating one of these, use one of the browseFor... methods to display it. - @param dialogBoxTitle a text string to display in the dialog box to - tell the user what's going on - @param initialFileOrDirectory the file or directory that should be selected when - the dialog box opens. If this parameter is set to - File::nonexistent, a sensible default directory - will be used instead. - @param filePatternsAllowed a set of file patterns to specify which files can be - selected - each pattern should be separated by a - comma or semi-colon, e.g. "*" or "*.jpg;*.gif". An - empty string means that all files are allowed - @param useOSNativeDialogBox if true, then a native dialog box will be used if - possible; if false, then a Juce-based browser dialog - box will always be used + @param dialogBoxTitle a text string to display in the dialog box to + tell the user what's going on + @param initialFileOrDirectory the file or directory that should be selected + when the dialog box opens. If this parameter is + set to File::nonexistent, a sensible default + directory will be used instead. + @param filePatternsAllowed a set of file patterns to specify which files + can be selected - each pattern should be + separated by a comma or semi-colon, e.g. "*" or + "*.jpg;*.gif". An empty string means that all + files are allowed + @param useOSNativeDialogBox if true, then a native dialog box will be used + if possible; if false, then a Juce-based + browser dialog box will always be used + @param treatFilePackagesAsDirectories if true, then the file chooser will allow the + selection of files inside packages when + invoked on OS X and when using native dialog + boxes. + @see browseForFileToOpen, browseForFileToSave, browseForDirectory */ FileChooser (const String& dialogBoxTitle, const File& initialFileOrDirectory = File::nonexistent, const String& filePatternsAllowed = String::empty, - bool useOSNativeDialogBox = true); + bool useOSNativeDialogBox = true, + bool treatFilePackagesAsDirectories = false); /** Destructor. */ ~FileChooser(); @@ -183,11 +190,12 @@ private: const File startingFile; Array results; const bool useNativeDialogBox; + const bool treatFilePackagesAsDirs; static void showPlatformDialog (Array& results, const String& title, const File& file, const String& filters, bool selectsDirectories, bool selectsFiles, bool isSave, bool warnAboutOverwritingExistingFiles, bool selectMultipleFiles, - FilePreviewComponent* previewComponent); + bool treatFilePackagesAsDirs, FilePreviewComponent* previewComponent); static bool isPlatformDialogAvailable(); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileChooser) diff --git a/source/modules/juce_gui_basics/juce_gui_basics.cpp b/source/modules/juce_gui_basics/juce_gui_basics.cpp index 33a595543..b65ae4250 100644 --- a/source/modules/juce_gui_basics/juce_gui_basics.cpp +++ b/source/modules/juce_gui_basics/juce_gui_basics.cpp @@ -33,7 +33,13 @@ #define NS_FORMAT_FUNCTION(F,A) // To avoid spurious warnings from GCC -#include "../juce_core/native/juce_BasicNativeHeaders.h" +#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 +#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 +#define JUCE_CORE_INCLUDE_JNI_HELPERS 1 +#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 +#define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 +#define JUCE_GRAPHICS_INCLUDE_COREGRAPHICS_HELPERS 1 + #include "juce_gui_basics.h" //============================================================================== @@ -256,9 +262,6 @@ extern bool juce_areThereAnyAlwaysOnTopWindows(); #endif #if JUCE_MAC || JUCE_IOS - #include "../juce_core/native/juce_osx_ObjCHelpers.h" - #include "../juce_graphics/native/juce_mac_CoreGraphicsHelpers.h" - #include "../juce_graphics/native/juce_mac_CoreGraphicsContext.h" #if JUCE_IOS #include "native/juce_ios_UIViewComponentPeer.mm" @@ -273,8 +276,6 @@ extern bool juce_areThereAnyAlwaysOnTopWindows(); #include "native/juce_mac_FileChooser.mm" #elif JUCE_WINDOWS - #include "../juce_core/native/juce_win32_ComSmartPtr.h" - #include "../juce_events/native/juce_win32_HiddenMessageWindow.h" #include "native/juce_win32_Windowing.cpp" #include "native/juce_win32_DragAndDrop.cpp" #include "native/juce_win32_FileChooser.cpp" @@ -285,7 +286,6 @@ extern bool juce_areThereAnyAlwaysOnTopWindows(); #include "native/juce_linux_FileChooser.cpp" #elif JUCE_ANDROID - #include "../juce_core/native/juce_android_JNIHelpers.h" #include "native/juce_android_Windowing.cpp" #include "native/juce_android_FileChooser.cpp" diff --git a/source/modules/juce_gui_basics/juce_gui_basics.h b/source/modules/juce_gui_basics/juce_gui_basics.h index e34640ecf..cf53565eb 100644 --- a/source/modules/juce_gui_basics/juce_gui_basics.h +++ b/source/modules/juce_gui_basics/juce_gui_basics.h @@ -28,7 +28,7 @@ #include "../juce_graphics/juce_graphics.h" #include "../juce_data_structures/juce_data_structures.h" -//============================================================================= +//============================================================================== /** Config: JUCE_ENABLE_REPAINT_DEBUGGING If this option is turned on, each area of the screen that gets repainted will flash in a random colour, so that you can see exactly which bits of your @@ -80,7 +80,7 @@ #define JUCE_USE_XCURSOR 1 #endif -//============================================================================= +//============================================================================== namespace juce { diff --git a/source/modules/juce_gui_basics/layout/juce_AnimatedPositionBehaviours.h b/source/modules/juce_gui_basics/layout/juce_AnimatedPositionBehaviours.h index 6eca4a4ca..fafdeaa94 100644 --- a/source/modules/juce_gui_basics/layout/juce_AnimatedPositionBehaviours.h +++ b/source/modules/juce_gui_basics/layout/juce_AnimatedPositionBehaviours.h @@ -145,7 +145,7 @@ namespace AnimatedPositionBehaviours private: double targetSnapPosition; }; -}; +} #endif // JUCE_ANIMATEDPOSITIONBEHAVIOURS_H_INCLUDED diff --git a/source/modules/juce_gui_basics/layout/juce_ComponentBuilder.cpp b/source/modules/juce_gui_basics/layout/juce_ComponentBuilder.cpp index da2e247b1..157d9e5e9 100644 --- a/source/modules/juce_gui_basics/layout/juce_ComponentBuilder.cpp +++ b/source/modules/juce_gui_basics/layout/juce_ComponentBuilder.cpp @@ -88,7 +88,7 @@ namespace ComponentBuilderHelpers } } -//============================================================================= +//============================================================================== const Identifier ComponentBuilder::idProperty ("id"); ComponentBuilder::ComponentBuilder() diff --git a/source/modules/juce_gui_basics/layout/juce_ComponentBuilder.h b/source/modules/juce_gui_basics/layout/juce_ComponentBuilder.h index e87fe80c8..0b5868409 100644 --- a/source/modules/juce_gui_basics/layout/juce_ComponentBuilder.h +++ b/source/modules/juce_gui_basics/layout/juce_ComponentBuilder.h @@ -167,7 +167,7 @@ public: /** Registers handlers for various standard juce components. */ void registerStandardComponentTypes(); - //============================================================================= + //============================================================================== /** This class is used when references to images need to be stored in ValueTrees. An instance of an ImageProvider provides a mechanism for converting an Image to/from @@ -213,7 +213,7 @@ public: /** Returns the current image provider that this builder is using, or nullptr if none has been set. */ ImageProvider* getImageProvider() const noexcept; - //============================================================================= + //============================================================================== /** Updates the children of a parent component by updating them from the children of a given ValueTree. */ @@ -225,7 +225,7 @@ public: static const Identifier idProperty; private: - //============================================================================= + //============================================================================== OwnedArray types; ScopedPointer component; ImageProvider* imageProvider; diff --git a/source/modules/juce_gui_basics/layout/juce_Viewport.cpp b/source/modules/juce_gui_basics/layout/juce_Viewport.cpp index 909eb2c23..b6ac2ca97 100644 --- a/source/modules/juce_gui_basics/layout/juce_Viewport.cpp +++ b/source/modules/juce_gui_basics/layout/juce_Viewport.cpp @@ -54,7 +54,7 @@ Viewport::Viewport (const String& name) Viewport::~Viewport() { - deleteContentComp(); + deleteOrRemoveContentComp(); } //============================================================================== @@ -62,20 +62,24 @@ void Viewport::visibleAreaChanged (const Rectangle&) {} void Viewport::viewedComponentChanged (Component*) {} //============================================================================== -void Viewport::deleteContentComp() +void Viewport::deleteOrRemoveContentComp() { if (contentComp != nullptr) + { contentComp->removeComponentListener (this); - if (deleteContent) - { - // This sets the content comp to a null pointer before deleting the old one, in case - // anything tries to use the old one while it's in mid-deletion.. - ScopedPointer oldCompDeleter (contentComp); - } - else - { - contentComp = nullptr; + if (deleteContent) + { + // This sets the content comp to a null pointer before deleting the old one, in case + // anything tries to use the old one while it's in mid-deletion.. + ScopedPointer oldCompDeleter (contentComp); + contentComp = nullptr; + } + else + { + contentHolder.removeChildComponent (contentComp); + contentComp = nullptr; + } } } @@ -83,7 +87,7 @@ void Viewport::setViewedComponent (Component* const newViewedComponent, const bo { if (contentComp.get() != newViewedComponent) { - deleteContentComp(); + deleteOrRemoveContentComp(); contentComp = newViewedComponent; deleteContent = deleteComponentWhenNoLongerNeeded; @@ -179,7 +183,10 @@ void Viewport::componentMovedOrResized (Component&, bool, bool) void Viewport::lookAndFeelChanged() { if (! customScrollBarThickness) + { scrollBarThickness = getLookAndFeel().getDefaultScrollbarWidth(); + resized(); + } } void Viewport::resized() diff --git a/source/modules/juce_gui_basics/layout/juce_Viewport.h b/source/modules/juce_gui_basics/layout/juce_Viewport.h index 9873c4cc5..50b01c43b 100644 --- a/source/modules/juce_gui_basics/layout/juce_Viewport.h +++ b/source/modules/juce_gui_basics/layout/juce_Viewport.h @@ -274,7 +274,7 @@ private: Point viewportPosToCompPos (Point) const; void updateVisibleArea(); - void deleteContentComp(); + void deleteOrRemoveContentComp(); #if JUCE_CATCH_DEPRECATED_CODE_MISUSE // If you get an error here, it's because this method's parameters have changed! See the new definition above.. diff --git a/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp b/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp index 571a51edc..2418a5281 100644 --- a/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp +++ b/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp @@ -61,8 +61,8 @@ public: { String shortcutKey; - const Array keyPresses (commandManager->getKeyMappings() - ->getKeyPressesAssignedToCommand (itemID)); + const Array keyPresses (commandManager->getKeyMappings() + ->getKeyPressesAssignedToCommand (itemID)); for (int i = 0; i < keyPresses.size(); ++i) { @@ -1351,16 +1351,22 @@ void PopupMenu::addCommandItem (ApplicationCommandManager* commandManager, } void PopupMenu::addColouredItem (int itemResultID, const String& itemText, Colour itemTextColour, - bool isActive, bool isTicked, const Image& iconToUse) + bool isActive, bool isTicked, Drawable* iconToUse) { jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user // didn't pick anything, so you shouldn't use it as the id // for an item.. - items.add (new Item (itemResultID, itemText, isActive, isTicked, createDrawableFromImage (iconToUse), + items.add (new Item (itemResultID, itemText, isActive, isTicked, iconToUse, itemTextColour, true, nullptr, nullptr, nullptr)); } +void PopupMenu::addColouredItem (int itemResultID, const String& itemText, Colour itemTextColour, + bool isActive, bool isTicked, const Image& iconToUse) +{ + addColouredItem (itemResultID, itemText, itemTextColour, isActive, isTicked, createDrawableFromImage (iconToUse)); +} + void PopupMenu::addCustomItem (int itemID, CustomComponent* cc, const PopupMenu* subMenu) { jassert (itemID != 0); // 0 is used as a return value to indicate that the user diff --git a/source/modules/juce_gui_basics/menus/juce_PopupMenu.h b/source/modules/juce_gui_basics/menus/juce_PopupMenu.h index a6e833bf3..f24072310 100644 --- a/source/modules/juce_gui_basics/menus/juce_PopupMenu.h +++ b/source/modules/juce_gui_basics/menus/juce_PopupMenu.h @@ -189,7 +189,20 @@ public: Colour itemTextColour, bool isEnabled = true, bool isTicked = false, - const Image& iconToUse = Image::null); + const Image& iconToUse = Image()); + + /** Appends a text item with a special colour. + + This is the same as addItem(), but specifies a colour to use for the + text, which will override the default colours that are used by the + current look-and-feel. See addItem() for a description of the parameters. + */ + void addColouredItem (int itemResultID, + const String& itemText, + Colour itemTextColour, + bool isEnabled, + bool isTicked, + Drawable* iconToUse); /** Appends a custom menu item. diff --git a/source/modules/juce_gui_basics/misc/juce_BubbleComponent.cpp b/source/modules/juce_gui_basics/misc/juce_BubbleComponent.cpp index f45c93eb8..7a547854b 100644 --- a/source/modules/juce_gui_basics/misc/juce_BubbleComponent.cpp +++ b/source/modules/juce_gui_basics/misc/juce_BubbleComponent.cpp @@ -49,35 +49,37 @@ void BubbleComponent::setAllowedPlacement (const int newPlacement) allowablePlacements = newPlacement; } -void BubbleComponent::setPosition (Component* componentToPointTo) +//============================================================================== +void BubbleComponent::setPosition (Component* componentToPointTo, int distanceFromTarget, int arrowLength) { jassert (componentToPointTo != nullptr); + Rectangle target; + if (Component* p = getParentComponent()) - setPosition (p->getLocalArea (componentToPointTo, componentToPointTo->getLocalBounds())); + target = p->getLocalArea (componentToPointTo, componentToPointTo->getLocalBounds()); else - setPosition (componentToPointTo->getScreenBounds()); + target = componentToPointTo->getScreenBounds(); + + setPosition (target, distanceFromTarget, arrowLength); } -void BubbleComponent::setPosition (Point pos) +void BubbleComponent::setPosition (Point arrowTipPos, int arrowLength) { - setPosition (Rectangle (pos.x, pos.y, 1, 1)); + setPosition (Rectangle (arrowTipPos.x, arrowTipPos.y, 1, 1), arrowLength, arrowLength); } -//============================================================================== -void BubbleComponent::setPosition (const Rectangle& rectangleToPointTo) +void BubbleComponent::setPosition (Rectangle rectangleToPointTo, + int distanceFromTarget, int arrowLength) { - const int edgeSpace = 15; - const int arrowLength = 10; - { int contentW = 150, contentH = 30; getContentSize (contentW, contentH); - content.setBounds (edgeSpace, edgeSpace, contentW, contentH); + content.setBounds (distanceFromTarget, distanceFromTarget, contentW, contentH); } - const int totalW = content.getWidth() + edgeSpace * 2; - const int totalH = content.getHeight() + edgeSpace * 2; + const int totalW = content.getWidth() + distanceFromTarget * 2; + const int totalH = content.getHeight() + distanceFromTarget * 2; const Rectangle availableSpace (getParentComponent() != nullptr ? getParentComponent()->getLocalBounds() : getParentMonitorArea()); diff --git a/source/modules/juce_gui_basics/misc/juce_BubbleComponent.h b/source/modules/juce_gui_basics/misc/juce_BubbleComponent.h index c6c758a4a..8536f9bd7 100644 --- a/source/modules/juce_gui_basics/misc/juce_BubbleComponent.h +++ b/source/modules/juce_gui_basics/misc/juce_BubbleComponent.h @@ -94,7 +94,8 @@ public: on where there's the most space, honouring any restrictions that were set with setAllowedPlacement(). */ - void setPosition (Component* componentToPointTo); + void setPosition (Component* componentToPointTo, + int distanceFromTarget = 15, int arrowLength = 10); /** Moves and resizes the bubble to point at a given point. @@ -107,7 +108,7 @@ public: on where there's the most space, honouring any restrictions that were set with setAllowedPlacement(). */ - void setPosition (Point arrowTipPosition); + void setPosition (Point arrowTipPosition, int arrowLength = 10); /** Moves and resizes the bubble to point at a given rectangle. @@ -119,8 +120,12 @@ public: It'll put itself either above, below, or to the side of the component depending on where there's the most space, honouring any restrictions that were set with setAllowedPlacement(). + + distanceFromTarget is the amount of space to leave between the bubble and the + target rectangle, and arrowLength is the length of the arrow that it will draw. */ - void setPosition (const Rectangle& rectangleToPointTo); + void setPosition (Rectangle rectangleToPointTo, + int distanceFromTarget = 15, int arrowLength = 10); //============================================================================== /** A set of colour IDs to use to change the colour of various aspects of the bubble component. diff --git a/source/modules/juce_gui_basics/mouse/juce_MouseEvent.cpp b/source/modules/juce_gui_basics/mouse/juce_MouseEvent.cpp index bf5cb87a7..8d558cac8 100644 --- a/source/modules/juce_gui_basics/mouse/juce_MouseEvent.cpp +++ b/source/modules/juce_gui_basics/mouse/juce_MouseEvent.cpp @@ -79,9 +79,14 @@ MouseEvent MouseEvent::withNewPosition (Point newPosition) const noexcept } //============================================================================== +bool MouseEvent::mouseWasDraggedSinceMouseDown() const noexcept +{ + return wasMovedSinceMouseDown != 0; +} + bool MouseEvent::mouseWasClicked() const noexcept { - return wasMovedSinceMouseDown == 0; + return ! mouseWasDraggedSinceMouseDown(); } int MouseEvent::getLengthOfMousePress() const noexcept diff --git a/source/modules/juce_gui_basics/mouse/juce_MouseEvent.h b/source/modules/juce_gui_basics/mouse/juce_MouseEvent.h index 9d993e31d..b9b06bd81 100644 --- a/source/modules/juce_gui_basics/mouse/juce_MouseEvent.h +++ b/source/modules/juce_gui_basics/mouse/juce_MouseEvent.h @@ -156,19 +156,19 @@ public: //============================================================================== /** Returns the x coordinate of the last place that a mouse was pressed. The coordinate is relative to the component specified in MouseEvent::component. - @see getDistanceFromDragStart, getDistanceFromDragStartX, mouseWasClicked + @see getDistanceFromDragStart, getDistanceFromDragStartX, mouseWasDraggedSinceMouseDown */ int getMouseDownX() const noexcept; /** Returns the y coordinate of the last place that a mouse was pressed. The coordinate is relative to the component specified in MouseEvent::component. - @see getDistanceFromDragStart, getDistanceFromDragStartX, mouseWasClicked + @see getDistanceFromDragStart, getDistanceFromDragStartX, mouseWasDraggedSinceMouseDown */ int getMouseDownY() const noexcept; /** Returns the coordinates of the last place that a mouse was pressed. The coordinates are relative to the component specified in MouseEvent::component. - @see getDistanceFromDragStart, getDistanceFromDragStartX, mouseWasClicked + @see getDistanceFromDragStart, getDistanceFromDragStartX, mouseWasDraggedSinceMouseDown */ Point getMouseDownPosition() const noexcept; @@ -203,25 +203,27 @@ public: */ Point getOffsetFromDragStart() const noexcept; - /** Returns true if the mouse has just been clicked. + /** Returns true if the user seems to be performing a drag gesture. - Used in either your mouseUp() or mouseDrag() methods, this will tell you whether - the user has dragged the mouse more than a few pixels from the place where the - mouse-down occurred. + This is only meaningful if called in either a mouseUp() or mouseDrag() method. - Once they have dragged it far enough for this method to return false, it will continue - to return false until the mouse-up, even if they move the mouse back to the same - position where they originally pressed it. This means that it's very handy for + It will return true if the user has dragged the mouse more than a few pixels + from the place where the mouse-down occurred. + + Once they have dragged it far enough for this method to return true, it will continue + to return true until the mouse-up, even if they move the mouse back to the same + location at which the mouse-down happened. This means that it's very handy for objects that can either be clicked on or dragged, as you can use it in the mouseDrag() - callback to ignore any small movements they might make while clicking. + callback to ignore small movements they might make while trying to click. + */ + bool mouseWasDraggedSinceMouseDown() const noexcept; - @returns true if the mouse wasn't dragged by more than a few pixels between - the last time the button was pressed and released. + /** Returns true if the mouse event is part of a click gesture rather than a drag. + This is effectively the opposite of mouseWasDraggedSinceMouseDown() */ bool mouseWasClicked() const noexcept; /** For a click event, the number of times the mouse was clicked in succession. - So for example a double-click event will return 2, a triple-click 3, etc. */ int getNumberOfClicks() const noexcept { return numberOfClicks; } diff --git a/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp b/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp index 2302105cd..3f83081a4 100644 --- a/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp +++ b/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp @@ -330,7 +330,7 @@ public: setScreenPos (screenPos, time, false); triggerFakeMove(); - return isDragging() ? nullptr : getComponentUnderMouse(); + return getComponentUnderMouse(); } void handleWheel (ComponentPeer& peer, Point positionWithinPeer, diff --git a/source/modules/juce_gui_basics/native/juce_android_FileChooser.cpp b/source/modules/juce_gui_basics/native/juce_android_FileChooser.cpp index f563fea17..1313f6e2b 100644 --- a/source/modules/juce_gui_basics/native/juce_android_FileChooser.cpp +++ b/source/modules/juce_gui_basics/native/juce_android_FileChooser.cpp @@ -31,6 +31,7 @@ void FileChooser::showPlatformDialog (Array& results, bool isSaveDialogue, bool warnAboutOverwritingExistingFiles, bool selectMultipleFiles, + bool /*treatFilePackagesAsDirs*/, FilePreviewComponent* extraInfoComponent) { // TODO diff --git a/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp b/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp index 106e70572..79f091e4f 100644 --- a/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp +++ b/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp @@ -106,13 +106,15 @@ DECLARE_JNI_CLASS (CanvasMinimal, "android/graphics/Canvas"); METHOD (invalidate, "invalidate", "(IIII)V") \ METHOD (containsPoint, "containsPoint", "(II)Z") \ METHOD (showKeyboard, "showKeyboard", "(Ljava/lang/String;)V") \ + METHOD (setSystemUiVisibility, "setSystemUiVisibility", "(I)V") \ DECLARE_JNI_CLASS (ComponentPeerView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView"); #undef JNI_CLASS_MEMBERS //============================================================================== -class AndroidComponentPeer : public ComponentPeer +class AndroidComponentPeer : public ComponentPeer, + private Timer { public: AndroidComponentPeer (Component& comp, const int windowStyleFlags) @@ -183,7 +185,6 @@ public: view.callVoidMethod (ComponentPeerView.setVisible, shouldBeVisible); } - private: GlobalRef view; bool shouldBeVisible; }; @@ -199,7 +200,7 @@ public: void setBounds (const Rectangle& userRect, bool isNowFullScreen) override { - Rectangle r = userRect * scale; + Rectangle r = (userRect.toFloat() * scale).toNearestInt(); if (MessageManager::getInstance()->isThisTheMessageThread()) { @@ -231,13 +232,13 @@ public: Rectangle getBounds() const override { - return Rectangle (view.callIntMethod (ComponentPeerView.getLeft), - view.callIntMethod (ComponentPeerView.getTop), - view.callIntMethod (ComponentPeerView.getWidth), - view.callIntMethod (ComponentPeerView.getHeight)) / scale; + return (Rectangle (view.callIntMethod (ComponentPeerView.getLeft), + view.callIntMethod (ComponentPeerView.getTop), + view.callIntMethod (ComponentPeerView.getWidth), + view.callIntMethod (ComponentPeerView.getHeight)) / scale).toNearestInt(); } - void handleScreenSizeChange() + void handleScreenSizeChange() override { ComponentPeer::handleScreenSizeChange(); @@ -271,8 +272,46 @@ public: return false; } + bool shouldNavBarsBeHidden() const + { + if (fullScreen) + if (Component* kiosk = Desktop::getInstance().getKioskModeComponent()) + if (kiosk->getPeer() == this) + return true; + + return false; + } + + void setNavBarsHidden (bool hidden) const + { + enum + { + SYSTEM_UI_FLAG_VISIBLE = 0, + SYSTEM_UI_FLAG_LOW_PROFILE = 1, + SYSTEM_UI_FLAG_HIDE_NAVIGATION = 2, + SYSTEM_UI_FLAG_FULLSCREEN = 4, + SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 512, + SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 1024, + SYSTEM_UI_FLAG_IMMERSIVE = 2048, + SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 4096 + }; + + view.callVoidMethod (ComponentPeerView.setSystemUiVisibility, + hidden ? (jint) (SYSTEM_UI_FLAG_HIDE_NAVIGATION | SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_IMMERSIVE_STICKY) + : (jint) (SYSTEM_UI_FLAG_VISIBLE)); + } + void setFullScreen (bool shouldBeFullScreen) override { + // updating the nav bar visibility is a bit odd on Android - need to wait for + if (shouldNavBarsBeHidden()) + { + if (! isTimerRunning()) + startTimer (500); + } + else + setNavBarsHidden (false); + Rectangle r (shouldBeFullScreen ? Desktop::getInstance().getDisplays().getMainDisplay().userArea : lastNonFullscreenBounds); @@ -291,6 +330,13 @@ public: return fullScreen; } + void timerCallback() override + { + setNavBarsHidden (shouldNavBarsBeHidden()); + setFullScreen (fullScreen); + stopTimer(); + } + void setIcon (const Image& newIcon) override { // n/a @@ -327,7 +373,7 @@ public: handleBroughtToFront(); } - void toBehind (ComponentPeer* other) override + void toBehind (ComponentPeer*) override { // TODO } @@ -723,9 +769,46 @@ bool Desktop::isScreenSaverEnabled() } //============================================================================== -void Desktop::setKioskComponent (Component* kioskModeComponent, bool enableOrDisable, bool allowMenusAndBars) +void Desktop::setKioskComponent (Component* kioskComp, bool enableOrDisable, bool allowMenusAndBars) { - // TODO + ignoreUnused (allowMenusAndBars); + + if (AndroidComponentPeer* peer = dynamic_cast (kioskComp->getPeer())) + peer->setFullScreen (enableOrDisable); + else + jassertfalse; // (this should have been checked by the caller) +} + +//============================================================================== +static jint getAndroidOrientationFlag (int orientations) noexcept +{ + enum + { + SCREEN_ORIENTATION_LANDSCAPE = 0, + SCREEN_ORIENTATION_PORTRAIT = 1, + SCREEN_ORIENTATION_USER = 2, + SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8, + SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9, + SCREEN_ORIENTATION_USER_LANDSCAPE = 11, + SCREEN_ORIENTATION_USER_PORTRAIT = 12, + }; + + switch (orientations) + { + case Desktop::upright: return (jint) SCREEN_ORIENTATION_PORTRAIT; + case Desktop::upsideDown: return (jint) SCREEN_ORIENTATION_REVERSE_PORTRAIT; + case Desktop::upright + Desktop::upsideDown: return (jint) SCREEN_ORIENTATION_USER_PORTRAIT; + case Desktop::rotatedAntiClockwise: return (jint) SCREEN_ORIENTATION_LANDSCAPE; + case Desktop::rotatedClockwise: return (jint) SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + case Desktop::rotatedClockwise + Desktop::rotatedAntiClockwise: return (jint) SCREEN_ORIENTATION_USER_LANDSCAPE; + default: return (jint) SCREEN_ORIENTATION_USER; + } +} + +void Desktop::allowedOrientationsChanged() +{ + android.activity.callVoidMethod (JuceAppActivity.setRequestedOrientation, + getAndroidOrientationFlag (allowedOrientations)); } //============================================================================== @@ -764,7 +847,7 @@ JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, setScreenSize, void, (JNIEnv //============================================================================== Image juce_createIconForFile (const File& file) { - return Image::null; + return Image(); } //============================================================================== diff --git a/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm b/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm index 6c1f7efd1..b97105d83 100644 --- a/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm +++ b/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm @@ -118,6 +118,8 @@ using namespace juce; - (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation duration: (NSTimeInterval) duration; - (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation; - (void) viewWillTransitionToSize: (CGSize) size withTransitionCoordinator: (id) coordinator; +- (BOOL) prefersStatusBarHidden; +- (UIStatusBarStyle) preferredStatusBarStyle; - (void) viewDidLoad; - (void) viewWillAppear: (BOOL) animated; @@ -298,6 +300,15 @@ static void sendScreenBoundsUpdate (JuceUIViewController* c) juceView->owner->updateTransformAndScreenBounds(); } +static bool isKioskModeView (JuceUIViewController* c) +{ + JuceUIView* juceView = (JuceUIView*) [c view]; + jassert (juceView != nil && juceView->owner != nullptr); + + return Desktop::getInstance().getKioskModeComponent() == &(juceView->owner->getComponent()); +} + + } // (juce namespace) //============================================================================== @@ -339,6 +350,16 @@ static void sendScreenBoundsUpdate (JuceUIViewController* c) MessageManager::callAsync ([=]() { sendScreenBoundsUpdate (self); }); } +- (BOOL) prefersStatusBarHidden +{ + return isKioskModeView (self); +} + +- (UIStatusBarStyle) preferredStatusBarStyle +{ + return UIStatusBarStyleDefault; +} + - (void) viewDidLoad { sendScreenBoundsUpdate (self); @@ -991,15 +1012,19 @@ bool UIViewComponentPeer::canBecomeKeyWindow() //============================================================================== void Desktop::setKioskComponent (Component* kioskModeComp, bool enableOrDisable, bool /*allowMenusAndBars*/) { - [[UIApplication sharedApplication] setStatusBarHidden: enableOrDisable - withAnimation: UIStatusBarAnimationSlide]; - displays->refresh(); - if (ComponentPeer* const peer = kioskModeComp->getPeer()) + if (ComponentPeer* peer = kioskModeComp->getPeer()) + { + if (UIViewComponentPeer* uiViewPeer = dynamic_cast (peer)) + [uiViewPeer->controller setNeedsStatusBarAppearanceUpdate]; + peer->setFullScreen (enableOrDisable); + } } +void Desktop::allowedOrientationsChanged() {} + //============================================================================== void UIViewComponentPeer::repaint (const Rectangle& area) { diff --git a/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm b/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm index 10a9607f9..07bdc3f9b 100644 --- a/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm +++ b/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm @@ -124,20 +124,27 @@ void LookAndFeel::playAlertSound() //============================================================================== class iOSMessageBox; -} // (juce namespace) +#if defined (__IPHONE_8_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0 + #define JUCE_USE_NEW_IOS_ALERTWINDOW 1 +#endif -@interface JuceAlertBoxDelegate : NSObject -{ -@public - iOSMessageBox* owner; -} +#if ! JUCE_USE_NEW_IOS_ALERTWINDOW + } // (juce namespace) -- (void) alertView: (UIAlertView*) alertView clickedButtonAtIndex: (NSInteger) buttonIndex; + @interface JuceAlertBoxDelegate : NSObject + { + @public + iOSMessageBox* owner; + } -@end + - (void) alertView: (UIAlertView*) alertView clickedButtonAtIndex: (NSInteger) buttonIndex; + + @end + + namespace juce + { +#endif -namespace juce -{ class iOSMessageBox { @@ -145,9 +152,30 @@ public: iOSMessageBox (const String& title, const String& message, NSString* button1, NSString* button2, NSString* button3, ModalComponentManager::Callback* cb, const bool async) - : result (0), resultReceived (false), delegate (nil), alert (nil), - callback (cb), isYesNo (button3 != nil), isAsync (async) + : result (0), resultReceived (false), callback (cb), isAsync (async) { + #if JUCE_USE_NEW_IOS_ALERTWINDOW + if (currentlyFocusedPeer != nullptr) + { + UIAlertController* alert = [UIAlertController alertControllerWithTitle: juceStringToNS (title) + message: juceStringToNS (message) + preferredStyle: UIAlertControllerStyleAlert]; + addButton (alert, button1, 0); + addButton (alert, button2, 1); + addButton (alert, button3, 2); + + [currentlyFocusedPeer->controller presentViewController: alert + animated: YES + completion: nil]; + } + else + { + // Since iOS8, alert windows need to be associated with a window, so you need to + // have at least one window on screen when you use this + jassertfalse; + } + + #else delegate = [[JuceAlertBoxDelegate alloc] init]; delegate->owner = this; @@ -158,12 +186,15 @@ public: otherButtonTitles: button2, button3, nil]; [alert retain]; [alert show]; + #endif } ~iOSMessageBox() { + #if ! JUCE_USE_NEW_IOS_ALERTWINDOW [alert release]; [delegate release]; + #endif } int getResult() @@ -172,7 +203,11 @@ public: JUCE_AUTORELEASEPOOL { + #if JUCE_USE_NEW_IOS_ALERTWINDOW + while (! resultReceived) + #else while (! (alert.hidden || resultReceived)) + #endif [[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; } @@ -194,28 +229,43 @@ public: private: int result; bool resultReceived; - JuceAlertBoxDelegate* delegate; - UIAlertView* alert; ScopedPointer callback; - const bool isYesNo, isAsync; + const bool isAsync; + + #if JUCE_USE_NEW_IOS_ALERTWINDOW + void addButton (UIAlertController* alert, NSString* text, int index) + { + if (text != nil) + [alert addAction: [UIAlertAction actionWithTitle: text + style: UIAlertActionStyleDefault + handler: ^(UIAlertAction*) { this->buttonClicked (index); }]]; + } + #else + UIAlertView* alert; + JuceAlertBoxDelegate* delegate; + #endif JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSMessageBox) }; -} // (juce namespace) -@implementation JuceAlertBoxDelegate +#if ! JUCE_USE_NEW_IOS_ALERTWINDOW + } // (juce namespace) -- (void) alertView: (UIAlertView*) alertView clickedButtonAtIndex: (NSInteger) buttonIndex -{ - owner->buttonClicked ((int) buttonIndex); - alertView.hidden = true; -} + @implementation JuceAlertBoxDelegate -@end + - (void) alertView: (UIAlertView*) alertView clickedButtonAtIndex: (NSInteger) buttonIndex + { + owner->buttonClicked ((int) buttonIndex); + alertView.hidden = true; + } + + @end + + namespace juce + { +#endif -namespace juce -{ //============================================================================== #if JUCE_MODAL_LOOPS_PERMITTED @@ -313,10 +363,7 @@ void SystemClipboard::copyTextToClipboard (const String& text) String SystemClipboard::getTextFromClipboard() { - if (NSString* text = [[UIPasteboard generalPasteboard] valueForPasteboardType: @"public.text"]) - return nsStringToJuce (text); - - return String(); + return nsStringToJuce ([[UIPasteboard generalPasteboard] valueForPasteboardType: @"public.text"]); } //============================================================================== @@ -357,8 +404,7 @@ void Desktop::Displays::findDisplays (float masterScale) UIScreen* s = [UIScreen mainScreen]; Display d; - d.userArea = UIViewComponentPeer::realScreenPosToRotated (convertToRectInt ([s bounds])) / masterScale; - d.totalArea = UIViewComponentPeer::realScreenPosToRotated (convertToRectInt ([s bounds])) / masterScale; + d.userArea = d.totalArea = UIViewComponentPeer::realScreenPosToRotated (convertToRectInt ([s bounds])) / masterScale; d.isMain = true; d.scale = masterScale; diff --git a/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp b/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp index a24b14bfc..180a0bfaa 100644 --- a/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp +++ b/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp @@ -157,6 +157,7 @@ void FileChooser::showPlatformDialog (Array& results, const String& title, const File& file, const String& filters, bool isDirectory, bool /* selectsFiles */, bool isSave, bool /* warnAboutOverwritingExistingFiles */, + bool /*treatFilePackagesAsDirs*/, bool selectMultipleFiles, FilePreviewComponent*) { const File previousWorkingDirectory (File::getCurrentWorkingDirectory()); diff --git a/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp b/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp index 7ac69a0ab..0bc8e1b87 100644 --- a/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp +++ b/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp @@ -44,7 +44,6 @@ struct Atoms pid = getCreating ("_NET_WM_PID"); windowType = getIfExists ("_NET_WM_WINDOW_TYPE"); windowState = getIfExists ("_NET_WM_STATE"); - compositingManager = getCreating ("_NET_WM_CM_S0"); XdndAware = getCreating ("XdndAware"); XdndEnter = getCreating ("XdndEnter"); @@ -66,9 +65,6 @@ struct Atoms allowedMimeTypes[2] = getCreating ("text/plain"); allowedMimeTypes[3] = getCreating ("text/uri-list"); - externalAllowedFileMimeTypes[0] = getCreating ("text/uri-list"); - externalAllowedTextMimeTypes[0] = getCreating ("text/plain"); - allowedActions[0] = getCreating ("XdndActionMove"); allowedActions[1] = XdndActionCopy; allowedActions[2] = getCreating ("XdndActionLink"); @@ -84,14 +80,12 @@ struct Atoms }; Atom protocols, protocolList[3], changeState, state, userTime, - activeWin, pid, windowType, windowState, compositingManager, + activeWin, pid, windowType, windowState, XdndAware, XdndEnter, XdndLeave, XdndPosition, XdndStatus, XdndDrop, XdndFinished, XdndSelection, XdndTypeList, XdndActionList, XdndActionDescription, XdndActionCopy, XdndActionPrivate, allowedActions[5], - allowedMimeTypes[4], - externalAllowedFileMimeTypes[1], - externalAllowedTextMimeTypes[1]; + allowedMimeTypes[4]; static const unsigned long DndVersion; @@ -225,41 +219,42 @@ namespace XSHMHelpers XShmSegmentInfo segmentInfo; zerostruct (segmentInfo); - XImage* xImage = XShmCreateImage (display, DefaultVisual (display, DefaultScreen (display)), - 24, ZPixmap, 0, &segmentInfo, 50, 50); - - if ((segmentInfo.shmid = shmget (IPC_PRIVATE, - (size_t) (xImage->bytes_per_line * xImage->height), - IPC_CREAT | 0777)) >= 0) + if (XImage* xImage = XShmCreateImage (display, DefaultVisual (display, DefaultScreen (display)), + 24, ZPixmap, 0, &segmentInfo, 50, 50)) { - segmentInfo.shmaddr = (char*) shmat (segmentInfo.shmid, 0, 0); - - if (segmentInfo.shmaddr != (void*) -1) + if ((segmentInfo.shmid = shmget (IPC_PRIVATE, + (size_t) (xImage->bytes_per_line * xImage->height), + IPC_CREAT | 0777)) >= 0) { - segmentInfo.readOnly = False; - xImage->data = segmentInfo.shmaddr; - XSync (display, False); + segmentInfo.shmaddr = (char*) shmat (segmentInfo.shmid, 0, 0); - if (XShmAttach (display, &segmentInfo) != 0) + if (segmentInfo.shmaddr != (void*) -1) { + segmentInfo.readOnly = False; + xImage->data = segmentInfo.shmaddr; XSync (display, False); - XShmDetach (display, &segmentInfo); - isAvailable = true; + if (XShmAttach (display, &segmentInfo) != 0) + { + XSync (display, False); + XShmDetach (display, &segmentInfo); + + isAvailable = true; + } } - } - XFlush (display); - XDestroyImage (xImage); + XFlush (display); + XDestroyImage (xImage); - shmdt (segmentInfo.shmaddr); - } + shmdt (segmentInfo.shmaddr); + } - shmctl (segmentInfo.shmid, IPC_RMID, 0); + shmctl (segmentInfo.shmid, IPC_RMID, 0); - XSetErrorHandler (oldHandler); - if (trappedErrorCode != 0) - isAvailable = false; + XSetErrorHandler (oldHandler); + if (trappedErrorCode != 0) + isAvailable = false; + } } } } @@ -320,10 +315,10 @@ namespace XRender return xRenderQueryVersion != nullptr; } - static bool hasCompositingWindowManager() + static bool hasCompositingWindowManager() noexcept { - const Atom compositingManager = Atoms::getCreating ("_NET_WM_CM_S0"); - return display != nullptr && XGetSelectionOwner (display, compositingManager) != 0; + return display != nullptr + && XGetSelectionOwner (display, Atoms::getCreating ("_NET_WM_CM_S0")) != 0; } static XRenderPictFormat* findPictureFormat() @@ -399,12 +394,10 @@ namespace Visuals desiredMask |= VisualBitsPerRGBMask; } - XVisualInfo* xvinfos = XGetVisualInfo (display, - desiredMask, - &desiredVisual, - &numVisuals); - - if (xvinfos != nullptr) + if (XVisualInfo* xvinfos = XGetVisualInfo (display, + desiredMask, + &desiredVisual, + &numVisuals)) { for (int i = 0; i < numVisuals; i++) { @@ -433,9 +426,7 @@ namespace Visuals #if JUCE_USE_XRENDER if (XRender::isAvailable()) { - XRenderPictFormat* pictFormat = XRender::findPictureFormat(); - - if (pictFormat != 0) + if (XRenderPictFormat* pictFormat = XRender::findPictureFormat()) { int numVisuals = 0; XVisualInfo desiredVisual; @@ -443,10 +434,9 @@ namespace Visuals desiredVisual.depth = 32; desiredVisual.bits_per_rgb = 8; - XVisualInfo* xvinfos = XGetVisualInfo (display, - VisualScreenMask | VisualDepthMask | VisualBitsPerRGBMask, - &desiredVisual, &numVisuals); - if (xvinfos != nullptr) + if (XVisualInfo* xvinfos = XGetVisualInfo (display, + VisualScreenMask | VisualDepthMask | VisualBitsPerRGBMask, + &desiredVisual, &numVisuals)) { for (int i = 0; i < numVisuals; ++i) { @@ -2507,7 +2497,7 @@ public: return currentScaleFactor; } - //=============================================================================== + //============================================================================== void addOpenGLRepaintListener (Component* dummy) { if (dummy != nullptr) @@ -2834,6 +2824,14 @@ private: ScopedXLock xlock; xchangeProperty (wndH, hints, hints, 32, &kwmHints, 1); } + + hints = Atoms::getIfExists ("_KDE_NET_WM_WINDOW_TYPE_OVERRIDE"); + + if (hints != None) + { + ScopedXLock xlock; + xchangeProperty (wndH, atoms.windowType, XA_ATOM, 32, &hints, 1); + } } void addWindowButtons (Window wndH) @@ -2909,9 +2907,7 @@ private: else netHints [0] = Atoms::getIfExists ("_NET_WM_WINDOW_TYPE_NORMAL"); - netHints[1] = Atoms::getIfExists ("_KDE_NET_WM_WINDOW_TYPE_OVERRIDE"); - - xchangeProperty (windowH, atoms.windowType, XA_ATOM, 32, &netHints, 2); + xchangeProperty (windowH, atoms.windowType, XA_ATOM, 32, &netHints, 1); int numHints = 0; @@ -3121,10 +3117,14 @@ private: //============================================================================== struct DragState { - DragState() noexcept + DragState() : isText (false), dragging (false), expectingStatus (false), canDrop (false), targetWindow (None), xdndVersion (-1) { + if (isText) + allowedTypes.add (Atoms::getCreating ("text/plain")); + else + allowedTypes.add (Atoms::getCreating ("text/uri-list")); } bool isText; @@ -3135,27 +3135,7 @@ private: int xdndVersion; // negotiated version with target Rectangle silentRect; String textOrFiles; - - const Atom* getMimeTypes (const Atoms& atoms) const noexcept - { - return isText ? atoms.externalAllowedTextMimeTypes - : atoms.externalAllowedFileMimeTypes; - } - - int getNumMimeTypes (const Atoms& atoms) const noexcept - { - return isText ? numElementsInArray (atoms.externalAllowedTextMimeTypes) - : numElementsInArray (atoms.externalAllowedFileMimeTypes); - } - - bool matchesTarget (const Atoms& atoms, Atom targetType) const - { - for (int i = getNumMimeTypes(atoms); --i >= 0;) - if (getMimeTypes(atoms)[i] == targetType) - return true; - - return false; - } + Array allowedTypes; }; //============================================================================== @@ -3216,13 +3196,8 @@ private: msg.message_type = atoms.XdndEnter; - const Atom* mimeTypes = dragState.getMimeTypes (atoms); - const int numMimeTypes = dragState.getNumMimeTypes (atoms); - - msg.data.l[1] = (dragState.xdndVersion << 24) | (numMimeTypes > 3); - msg.data.l[2] = numMimeTypes > 0 ? (long) mimeTypes[0] : 0; - msg.data.l[3] = numMimeTypes > 1 ? (long) mimeTypes[1] : 0; - msg.data.l[4] = numMimeTypes > 2 ? (long) mimeTypes[2] : 0; + msg.data.l[1] = (dragState.xdndVersion << 24); + msg.data.l[2] = (long) dragState.allowedTypes[0]; sendExternalDragAndDropMessage (msg, targetWindow); } @@ -3296,7 +3271,7 @@ private: s.xselection.property = None; s.xselection.time = evt.xselectionrequest.time; - if (dragState.matchesTarget (atoms, targetType)) + if (dragState.allowedTypes.contains (targetType)) { s.xselection.property = evt.xselectionrequest.property; @@ -3570,15 +3545,15 @@ private: bool isWindowDnDAware (Window w) const { int numProperties = 0; - Atom* const watoms = XListProperties (display, w, &numProperties); + Atom* const properties = XListProperties (display, w, &numProperties); bool dndAwarePropFound = false; for (int i = 0; i < numProperties; ++i) - if (watoms[i] == atoms.XdndAware) + if (properties[i] == atoms.XdndAware) dndAwarePropFound = true; - if (watoms != nullptr) - XFree (watoms); + if (properties != nullptr) + XFree (properties); return dndAwarePropFound; } @@ -3622,8 +3597,8 @@ private: // save the available types to XdndTypeList xchangeProperty (windowH, atoms.XdndTypeList, XA_ATOM, 32, - dragState.getMimeTypes (atoms), - dragState.getNumMimeTypes (atoms)); + dragState.allowedTypes.getRawDataPointer(), + dragState.allowedTypes.size()); dragState.dragging = true; handleExternalDragMotionNotify(); @@ -3741,6 +3716,8 @@ void Desktop::setKioskComponent (Component* comp, bool enableOrDisable, bool /* comp->setBounds (getDisplays().getMainDisplay().totalArea); } +void Desktop::allowedOrientationsChanged() {} + //============================================================================== ComponentPeer* Component::createNewPeer (int styleFlags, void* nativeWindowToAttachTo) { diff --git a/source/modules/juce_gui_basics/native/juce_mac_FileChooser.mm b/source/modules/juce_gui_basics/native/juce_mac_FileChooser.mm index 8097620cf..42cfd93ad 100644 --- a/source/modules/juce_gui_basics/native/juce_mac_FileChooser.mm +++ b/source/modules/juce_gui_basics/native/juce_mac_FileChooser.mm @@ -141,6 +141,7 @@ void FileChooser::showPlatformDialog (Array& results, bool isSaveDialogue, bool /*warnAboutOverwritingExistingFiles*/, bool selectMultipleFiles, + bool treatFilePackagesAsDirs, FilePreviewComponent* extraInfoComponent) { JUCE_AUTORELEASEPOOL @@ -177,6 +178,9 @@ void FileChooser::showPlatformDialog (Array& results, [openPanel setCanChooseFiles: selectsFiles]; [openPanel setAllowsMultipleSelection: selectMultipleFiles]; [openPanel setResolvesAliases: YES]; + + if (treatFilePackagesAsDirs) + [openPanel setTreatsFilePackagesAsDirectories: YES]; } if (extraInfoComponent != nullptr) @@ -260,6 +264,7 @@ void FileChooser::showPlatformDialog (Array&, bool /*isSaveDialogue*/, bool /*warnAboutOverwritingExistingFiles*/, bool /*selectMultipleFiles*/, + bool /*treatFilePackagesAsDirs*/, FilePreviewComponent*) { jassertfalse; //there's no such thing in iOS diff --git a/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm index 9287ae669..d786ac61a 100644 --- a/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm @@ -59,7 +59,8 @@ static NSRect flippedScreenRect (NSRect r) noexcept } //============================================================================== -class NSViewComponentPeer : public ComponentPeer +class NSViewComponentPeer : public ComponentPeer, + private AsyncUpdater { public: NSViewComponentPeer (Component& comp, const int windowStyleFlags, NSView* viewToAttachTo) @@ -531,6 +532,8 @@ public: usingCoreGraphics = index > 0; [view setNeedsDisplay: true]; } + #else + ignoreUnused (index); #endif } @@ -771,6 +774,7 @@ public: handleModifierKeysChange(); } + //============================================================================== void drawRect (NSRect r) { if (r.size.width < 1.0f || r.size.height < 1.0f) @@ -792,12 +796,8 @@ public: #if USE_COREGRAPHICS_RENDERING if (usingCoreGraphics) { - CoreGraphicsContext context (cg, (float) [view frame].size.height, displayScale); - - insideDrawRect = true; - handlePaint (context); - insideDrawRect = false; + invokePaint (context); } else #endif @@ -828,9 +828,7 @@ public: if (intScale != 1) context->addTransform (AffineTransform::scale (displayScale)); - insideDrawRect = true; - handlePaint (*context); - insideDrawRect = false; + invokePaint (*context); } CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB(); @@ -842,17 +840,69 @@ public: } } - bool sendModalInputAttemptIfBlocked() + void handleAsyncUpdate() override + { + // When windows are being resized, artificially throttling high-frequency repaints helps + // to stop the event queue getting clogged, and keeps everything working smoothly + if (areAnyWindowsInLiveResize() + && Time::getCurrentTime() < lastRepaintTime + RelativeTime::milliseconds (1000 / 30)) + { + triggerAsyncUpdate(); + return; + } + + for (const Rectangle* i = deferredRepaints.begin(), *e = deferredRepaints.end(); i != e; ++i) + [view setNeedsDisplayInRect: makeNSRect (*i)]; + + deferredRepaints.clear(); + } + + void repaint (const Rectangle& area) override + { + // In 10.11 changes were made to the way the OS handles repaint regions, and it seems that it can + // no longer be trusted to coalesce all the regions, or to even remember them all without losing + // a few when there's a lot of activity. + // As a work around for this, we use a RectangleList to do our own coalescing of regions before + // asynchronously asking the OS to repaint them. + deferredRepaints.add ((float) area.getX(), (float) ([view frame].size.height - area.getBottom()), + (float) area.getWidth(), (float) area.getHeight()); + triggerAsyncUpdate(); + } + + void invokePaint (LowLevelGraphicsContext& context) + { + lastRepaintTime = Time::getCurrentTime(); + insideDrawRect = true; + handlePaint (context); + insideDrawRect = false; + } + + void performAnyPendingRepaintsNow() override { - Component* const modal = Component::getCurrentlyModalComponent(); + [view displayIfNeeded]; + } + + static bool areAnyWindowsInLiveResize() noexcept + { + for (NSWindow* w in [NSApp windows]) + if ([w inLiveResize]) + return true; + + return false; + } - if (modal != nullptr - && insideToFrontCall == 0 - && (! getComponent().isParentOf (modal)) - && getComponent().isCurrentlyBlockedByAnotherModalComponent()) + //============================================================================== + bool sendModalInputAttemptIfBlocked() + { + if (Component* modal = Component::getCurrentlyModalComponent()) { - modal->inputAttemptWhenModal(); - return true; + if (insideToFrontCall == 0 + && (! getComponent().isParentOf (modal)) + && getComponent().isCurrentlyBlockedByAnotherModalComponent()) + { + modal->inputAttemptWhenModal(); + return true; + } } return false; @@ -865,9 +915,15 @@ public: bool canBecomeMainWindow() { - Component* owner = &juce::ComponentPeer::getComponent(); + return dynamic_cast (&component) != nullptr; + } - return dynamic_cast (owner) != nullptr; + bool worksWhenModal() const + { + // In plugins, the host could put our plugin window inside a modal window, so this + // allows us to successfully open other popups. Feels like there could be edge-case + // problems caused by this, so let us know if you spot any issues.. + return ! JUCEApplication::isStandaloneApp(); } void becomeKeyWindow() @@ -1224,43 +1280,6 @@ public: void textInputRequired (Point, TextInputTarget&) override {} - //============================================================================== - void repaint (const Rectangle& area) override - { - if (insideDrawRect) - { - class AsyncRepaintMessage : public CallbackMessage - { - public: - AsyncRepaintMessage (NSViewComponentPeer* const p, const Rectangle& r) - : peer (p), rect (r) - {} - - void messageCallback() override - { - if (ComponentPeer::isValidPeer (peer)) - peer->repaint (rect); - } - - private: - NSViewComponentPeer* const peer; - const Rectangle rect; - }; - - (new AsyncRepaintMessage (this, area))->post(); - } - else - { - [view setNeedsDisplayInRect: NSMakeRect ((CGFloat) area.getX(), [view frame].size.height - (CGFloat) area.getBottom(), - (CGFloat) area.getWidth(), (CGFloat) area.getHeight())]; - } - } - - void performAnyPendingRepaintsNow() override - { - [view displayIfNeeded]; - } - //============================================================================== NSWindow* window; NSView* view; @@ -1269,6 +1288,9 @@ public: String stringBeingComposed; NSNotificationCenter* notificationCenter; + RectangleList deferredRepaints; + Time lastRepaintTime; + static ModifierKeys currentModifiers; static ComponentPeer* currentlyFocusedPeer; static Array keysCurrentlyDown; @@ -1368,24 +1390,22 @@ private: for (int i = ComponentPeer::getNumPeers(); --i >= 0;) { - ComponentPeer* const peer = ComponentPeer::getPeer (i); - NSView* const compView = (NSView*) peer->getNativeHandle(); - - if ([compView window] == w) + if (NSViewComponentPeer* peer = dynamic_cast (ComponentPeer::getPeer (i))) { - if (isKey) + if ([peer->view window] == w) { - if (compView == [w firstResponder]) - return false; - } - else - { - NSViewComponentPeer* nsViewPeer = dynamic_cast (peer); - - if ((nsViewPeer == nullptr || ! nsViewPeer->isSharedWindow) - ? NSPointInRect ([e locationInWindow], NSMakeRect (0, 0, [w frame].size.width, [w frame].size.height)) - : NSPointInRect ([compView convertPoint: [e locationInWindow] fromView: nil], [compView bounds])) - return false; + if (isKey) + { + if (peer->view == [w firstResponder]) + return false; + } + else + { + if (peer->isSharedWindow + ? NSPointInRect ([peer->view convertPoint: [e locationInWindow] fromView: nil], [peer->view bounds]) + : NSPointInRect ([e locationInWindow], NSMakeRect (0, 0, [w frame].size.width, [w frame].size.height))) + return false; + } } } } @@ -1408,9 +1428,9 @@ private: int NSViewComponentPeer::insideToFrontCall = 0; //============================================================================== -struct JuceNSViewClass : public ObjCClass +struct JuceNSViewClass : public ObjCClass { - JuceNSViewClass() : ObjCClass ("JUCEView_") + JuceNSViewClass() : ObjCClass ("JUCEView_") { addIvar ("owner"); @@ -1432,8 +1452,10 @@ struct JuceNSViewClass : public ObjCClass addMethod (@selector (otherMouseUp:), mouseUp, "v@:@"); addMethod (@selector (scrollWheel:), scrollWheel, "v@:@"); addMethod (@selector (magnifyWithEvent:), magnify, "v@:@"); - addMethod (@selector (acceptsFirstMouse:), acceptsFirstMouse, "v@:@"); + addMethod (@selector (acceptsFirstMouse:), acceptsFirstMouse, "c@:@"); addMethod (@selector (frameChanged:), frameChanged, "v@:@"); + addMethod (@selector (wantsDefaultClipping:), wantsDefaultClipping, "c@:"); + addMethod (@selector (worksWhenModal), worksWhenModal, "c@:"); addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow, "v@:"); addMethod (@selector (keyDown:), keyDown, "v@:@"); addMethod (@selector (keyUp:), keyUp, "v@:@"); @@ -1503,23 +1525,25 @@ private: waitUntilDone: NO]; } - static void asyncMouseDown (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* const p = getOwner (self)) p->redirectMouseDown (ev); } - static void asyncMouseUp (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* const p = getOwner (self)) p->redirectMouseUp (ev); } - static void mouseDragged (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* const p = getOwner (self)) p->redirectMouseDrag (ev); } - static void mouseMoved (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* const p = getOwner (self)) p->redirectMouseMove (ev); } - static void mouseEntered (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* const p = getOwner (self)) p->redirectMouseEnter (ev); } - static void mouseExited (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* const p = getOwner (self)) p->redirectMouseExit (ev); } - static void scrollWheel (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* const p = getOwner (self)) p->redirectMouseWheel (ev); } - static void magnify (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* const p = getOwner (self)) p->redirectMagnify (ev); } - static void copy (id self, SEL, NSObject* s) { if (NSViewComponentPeer* const p = getOwner (self)) p->redirectCopy (s); } - static void paste (id self, SEL, NSObject* s) { if (NSViewComponentPeer* const p = getOwner (self)) p->redirectPaste (s); } - static void cut (id self, SEL, NSObject* s) { if (NSViewComponentPeer* const p = getOwner (self)) p->redirectCut (s); } + static void asyncMouseDown (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* p = getOwner (self)) p->redirectMouseDown (ev); } + static void asyncMouseUp (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* p = getOwner (self)) p->redirectMouseUp (ev); } + static void mouseDragged (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* p = getOwner (self)) p->redirectMouseDrag (ev); } + static void mouseMoved (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* p = getOwner (self)) p->redirectMouseMove (ev); } + static void mouseEntered (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* p = getOwner (self)) p->redirectMouseEnter (ev); } + static void mouseExited (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* p = getOwner (self)) p->redirectMouseExit (ev); } + static void scrollWheel (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* p = getOwner (self)) p->redirectMouseWheel (ev); } + static void magnify (id self, SEL, NSEvent* ev) { if (NSViewComponentPeer* p = getOwner (self)) p->redirectMagnify (ev); } + static void copy (id self, SEL, NSObject* s) { if (NSViewComponentPeer* p = getOwner (self)) p->redirectCopy (s); } + static void paste (id self, SEL, NSObject* s) { if (NSViewComponentPeer* p = getOwner (self)) p->redirectPaste (s); } + static void cut (id self, SEL, NSObject* s) { if (NSViewComponentPeer* p = getOwner (self)) p->redirectCut (s); } static BOOL acceptsFirstMouse (id, SEL, NSEvent*) { return YES; } + static BOOL wantsDefaultClipping (id, SEL) { return YES; } // (this is the default, but may want to customise it in future) + static BOOL worksWhenModal (id self, SEL) { if (NSViewComponentPeer* p = getOwner (self)) return p->worksWhenModal(); return NO; }; - static void drawRect (id self, SEL, NSRect r) { if (NSViewComponentPeer* const p = getOwner (self)) p->drawRect (r); } - static void frameChanged (id self, SEL, NSNotification*) { if (NSViewComponentPeer* const p = getOwner (self)) p->redirectMovedOrResized(); } - static void viewDidMoveToWindow (id self, SEL) { if (NSViewComponentPeer* const p = getOwner (self)) p->viewMovedToWindow(); } + static void drawRect (id self, SEL, NSRect r) { if (NSViewComponentPeer* p = getOwner (self)) p->drawRect (r); } + static void frameChanged (id self, SEL, NSNotification*) { if (NSViewComponentPeer* p = getOwner (self)) p->redirectMovedOrResized(); } + static void viewDidMoveToWindow (id self, SEL) { if (NSViewComponentPeer* p = getOwner (self)) p->viewMovedToWindow(); } static BOOL isOpaque (id self, SEL) { @@ -1711,12 +1735,12 @@ private: } //============================================================================== - static NSDragOperation draggingEntered (id self, SEL s, id sender) + static NSDragOperation draggingEntered (id self, SEL s, id sender) { return draggingUpdated (self, s, sender); } - static NSDragOperation draggingUpdated (id self, SEL, id sender) + static NSDragOperation draggingUpdated (id self, SEL, id sender) { if (NSViewComponentPeer* const owner = getOwner (self)) if (owner->sendDragCallback (0, sender)) @@ -1725,29 +1749,29 @@ private: return NSDragOperationNone; } - static void draggingEnded (id self, SEL s, id sender) + static void draggingEnded (id self, SEL s, id sender) { draggingExited (self, s, sender); } - static void draggingExited (id self, SEL, id sender) + static void draggingExited (id self, SEL, id sender) { if (NSViewComponentPeer* const owner = getOwner (self)) owner->sendDragCallback (1, sender); } - static BOOL prepareForDragOperation (id, SEL, id ) + static BOOL prepareForDragOperation (id, SEL, id) { return YES; } - static BOOL performDragOperation (id self, SEL, id sender) + static BOOL performDragOperation (id self, SEL, id sender) { NSViewComponentPeer* const owner = getOwner (self); return owner != nullptr && owner->sendDragCallback (2, sender); } - static void concludeDragOperation (id, SEL, id ) {} + static void concludeDragOperation (id, SEL, id) {} }; //============================================================================== @@ -2006,6 +2030,8 @@ void Desktop::setKioskComponent (Component* kioskComp, bool shouldBeEnabled, boo #endif } +void Desktop::allowedOrientationsChanged() {} + //============================================================================== ComponentPeer* Component::createNewPeer (int styleFlags, void* windowToAttachTo) { diff --git a/source/modules/juce_gui_basics/native/juce_mac_Windowing.mm b/source/modules/juce_gui_basics/native/juce_mac_Windowing.mm index ced9b0f7a..b39229065 100644 --- a/source/modules/juce_gui_basics/native/juce_mac_Windowing.mm +++ b/source/modules/juce_gui_basics/native/juce_mac_Windowing.mm @@ -395,6 +395,19 @@ bool juce_areThereAnyAlwaysOnTopWindows() } //============================================================================== +static void selectImageForDrawing (const Image& image) +{ + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithGraphicsPort: juce_getImageContext (image) + flipped: false]]; +} + +static void releaseImageAfterDrawing() +{ + [[NSGraphicsContext currentContext] flushGraphics]; + [NSGraphicsContext restoreGraphicsState]; +} + Image juce_createIconForFile (const File& file) { JUCE_AUTORELEASEPOOL @@ -403,20 +416,54 @@ Image juce_createIconForFile (const File& file) Image result (Image::ARGB, (int) [image size].width, (int) [image size].height, true); - [NSGraphicsContext saveGraphicsState]; - [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithGraphicsPort: juce_getImageContext (result) flipped: false]]; - + selectImageForDrawing (result); [image drawAtPoint: NSMakePoint (0, 0) fromRect: NSMakeRect (0, 0, [image size].width, [image size].height) operation: NSCompositeSourceOver fraction: 1.0f]; + releaseImageAfterDrawing(); + + return result; + } +} + +static Image createNSWindowSnapshot (NSWindow* nsWindow) +{ + JUCE_AUTORELEASEPOOL + { + CGImageRef screenShot = CGWindowListCreateImage (CGRectNull, + kCGWindowListOptionIncludingWindow, + (CGWindowID) [nsWindow windowNumber], + kCGWindowImageBoundsIgnoreFraming); - [[NSGraphicsContext currentContext] flushGraphics]; - [NSGraphicsContext restoreGraphicsState]; + NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: screenShot]; + + Image result (Image::ARGB, (int) [bitmapRep size].width, (int) [bitmapRep size].height, true); + + selectImageForDrawing (result); + [bitmapRep drawAtPoint: NSMakePoint (0, 0)]; + releaseImageAfterDrawing(); + + [bitmapRep release]; + CGImageRelease (screenShot); return result; } } +Image createSnapshotOfNativeWindow (void* nativeWindowHandle) +{ + if (id windowOrView = (id) nativeWindowHandle) + { + if ([windowOrView isKindOfClass: [NSWindow class]]) + return createNSWindowSnapshot ((NSWindow*) windowOrView); + + if ([windowOrView isKindOfClass: [NSView class]]) + return createNSWindowSnapshot ([(NSView*) windowOrView window]); + } + + return Image(); +} + //============================================================================== void SystemClipboard::copyTextToClipboard (const String& text) { @@ -431,10 +478,7 @@ void SystemClipboard::copyTextToClipboard (const String& text) String SystemClipboard::getTextFromClipboard() { - NSString* text = [[NSPasteboard generalPasteboard] stringForType: NSStringPboardType]; - - return text == nil ? String() - : nsStringToJuce (text); + return nsStringToJuce ([[NSPasteboard generalPasteboard] stringForType: NSStringPboardType]); } void Process::setDockIconVisible (bool isVisible) @@ -447,3 +491,9 @@ void Process::setDockIconVisible (bool isVisible) jassertfalse; // sorry, not available in 10.5! #endif } + +bool Desktop::isOSXDarkModeActive() +{ + return [[[NSUserDefaults standardUserDefaults] stringForKey: nsStringLiteral ("AppleInterfaceStyle")] + isEqualToString: nsStringLiteral ("Dark")]; +} diff --git a/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp b/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp index f610dfb75..ad11d4063 100644 --- a/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp +++ b/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp @@ -130,7 +130,8 @@ bool FileChooser::isPlatformDialogAvailable() void FileChooser::showPlatformDialog (Array& results, const String& title_, const File& currentFileOrDirectory, const String& filter, bool selectsDirectory, bool /*selectsFiles*/, bool isSaveDialogue, bool warnAboutOverwritingExistingFiles, - bool selectMultipleFiles, FilePreviewComponent* extraInfoComponent) + bool selectMultipleFiles, bool /*treatFilePackagesAsDirs*/, + FilePreviewComponent* extraInfoComponent) { using namespace FileChooserHelpers; diff --git a/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp index 7595e9c15..4dd23d379 100644 --- a/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -444,6 +444,25 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsBitmapImage) }; +//============================================================================== +Image createSnapshotOfNativeWindow (void* nativeWindowHandle) +{ + HWND hwnd = (HWND) nativeWindowHandle; + + RECT r = getWindowRect (hwnd); + const int w = r.right - r.left; + const int h = r.bottom - r.top; + + WindowsBitmapImage* nativeBitmap = new WindowsBitmapImage (Image::RGB, w, h, true); + Image bitmap (nativeBitmap); + + HDC dc = GetDC (hwnd); + BitBlt (nativeBitmap->hdc, 0, 0, w, h, dc, 0, 0, SRCCOPY); + ReleaseDC (hwnd, dc); + + return SoftwareImageType().convert (bitmap); +} + //============================================================================== namespace IconConverters { @@ -1539,7 +1558,7 @@ private: DeleteObject (rgn); EndPaint (hwnd, &paintStruct); - #ifndef JUCE_GCC + #if JUCE_MSVC _fpreset(); // because some graphics cards can unmask FP exceptions #endif @@ -3311,6 +3330,8 @@ void Desktop::setKioskComponent (Component* kioskModeComp, bool enableOrDisable, kioskModeComp->setBounds (getDisplays().getMainDisplay().totalArea); } +void Desktop::allowedOrientationsChanged() {} + //============================================================================== struct MonitorInfo { diff --git a/source/modules/juce_gui_basics/widgets/juce_ComboBox.cpp b/source/modules/juce_gui_basics/widgets/juce_ComboBox.cpp index 409969ee5..68e1c2389 100644 --- a/source/modules/juce_gui_basics/widgets/juce_ComboBox.cpp +++ b/source/modules/juce_gui_basics/widgets/juce_ComboBox.cpp @@ -579,7 +579,7 @@ void ComboBox::mouseDrag (const MouseEvent& e) { beginDragAutoRepeat (50); - if (isButtonDown && ! e.mouseWasClicked()) + if (isButtonDown && e.mouseWasDraggedSinceMouseDown()) showPopupIfNotActive(); } diff --git a/source/modules/juce_gui_basics/widgets/juce_Label.cpp b/source/modules/juce_gui_basics/widgets/juce_Label.cpp index 2fb4b6d0a..0393dda6e 100644 --- a/source/modules/juce_gui_basics/widgets/juce_Label.cpp +++ b/source/modules/juce_gui_basics/widgets/juce_Label.cpp @@ -329,9 +329,8 @@ void Label::mouseUp (const MouseEvent& e) { if (editSingleClick && isEnabled() - && e.mouseWasClicked() && contains (e.getPosition()) - && ! e.mods.isPopupMenu()) + && ! (e.mouseWasDraggedSinceMouseDown() || e.mods.isPopupMenu())) { showEditor(); } diff --git a/source/modules/juce_gui_basics/widgets/juce_ListBox.cpp b/source/modules/juce_gui_basics/widgets/juce_ListBox.cpp index 91487420b..6445402f7 100644 --- a/source/modules/juce_gui_basics/widgets/juce_ListBox.cpp +++ b/source/modules/juce_gui_basics/widgets/juce_ListBox.cpp @@ -104,7 +104,7 @@ public: { if (ListBoxModel* m = owner.getModel()) { - if (isEnabled() && ! (e.mouseWasClicked() || isDragging)) + if (isEnabled() && e.mouseWasDraggedSinceMouseDown() && ! isDragging) { SparseSet rowsToDrag; diff --git a/source/modules/juce_gui_basics/widgets/juce_Slider.cpp b/source/modules/juce_gui_basics/widgets/juce_Slider.cpp index 8fa758936..75aaddc93 100644 --- a/source/modules/juce_gui_basics/widgets/juce_Slider.cpp +++ b/source/modules/juce_gui_basics/widgets/juce_Slider.cpp @@ -35,8 +35,6 @@ public: minimum (0), maximum (10), interval (0), doubleClickReturnValue (0), skewFactor (1.0), velocityModeSensitivity (1.0), velocityModeOffset (0.0), velocityModeThreshold (1), - rotaryStart (float_Pi * 1.2f), - rotaryEnd (float_Pi * 2.8f), sliderRegionStart (0), sliderRegionSize (1), sliderBeingDragged (-1), pixelsForFullDragExtent (250), textBoxPos (textBoxPosition), @@ -47,7 +45,6 @@ public: doubleClickToValue (false), isVelocityBased (false), userKeyOverridesVelocity (true), - rotaryStop (true), incDecButtonsSideBySide (false), sendChangeOnlyOnRelease (false), popupDisplayEnabled (false), @@ -57,6 +54,9 @@ public: snapsToMousePos (true), parentForPopupDisplay (nullptr) { + rotaryParams.startAngleRadians = float_Pi * 1.2f; + rotaryParams.endAngleRadians = float_Pi * 2.8f; + rotaryParams.stopAtEnd = true; } ~Pimpl() @@ -465,20 +465,6 @@ public: } } - void setRotaryParameters (const float startAngleRadians, - const float endAngleRadians, - const bool stopAtEnd) - { - // make sure the values are sensible.. - jassert (startAngleRadians >= 0 && endAngleRadians >= 0); - jassert (startAngleRadians < float_Pi * 4.0f && endAngleRadians < float_Pi * 4.0f); - jassert (startAngleRadians < endAngleRadians); - - rotaryStart = startAngleRadians; - rotaryEnd = endAngleRadians; - rotaryStop = stopAtEnd; - } - void setVelocityModeParameters (const double sensitivity, const int threshold, const double offset, const bool userCanPressKeyToSwapMode) { @@ -707,7 +693,7 @@ public: while (angle < 0.0) angle += double_Pi * 2.0; - if (rotaryStop && ! e.mouseWasClicked()) + if (rotaryParams.stopAtEnd && e.mouseWasDraggedSinceMouseDown()) { if (std::abs (angle - lastAngle) > double_Pi) { @@ -718,26 +704,26 @@ public: } if (angle >= lastAngle) - angle = jmin (angle, (double) jmax (rotaryStart, rotaryEnd)); + angle = jmin (angle, (double) jmax (rotaryParams.startAngleRadians, rotaryParams.endAngleRadians)); else - angle = jmax (angle, (double) jmin (rotaryStart, rotaryEnd)); + angle = jmax (angle, (double) jmin (rotaryParams.startAngleRadians, rotaryParams.endAngleRadians)); } else { - while (angle < rotaryStart) + while (angle < rotaryParams.startAngleRadians) angle += double_Pi * 2.0; - if (angle > rotaryEnd) + if (angle > rotaryParams.endAngleRadians) { - if (smallestAngleBetween (angle, rotaryStart) - <= smallestAngleBetween (angle, rotaryEnd)) - angle = rotaryStart; + if (smallestAngleBetween (angle, rotaryParams.startAngleRadians) + <= smallestAngleBetween (angle, rotaryParams.endAngleRadians)) + angle = rotaryParams.startAngleRadians; else - angle = rotaryEnd; + angle = rotaryParams.endAngleRadians; } } - const double proportion = (angle - rotaryStart) / (rotaryEnd - rotaryStart); + const double proportion = (angle - rotaryParams.startAngleRadians) / (rotaryParams.endAngleRadians - rotaryParams.startAngleRadians); valueWhenLastDragged = owner.proportionOfLengthToValue (jlimit (0.0, 1.0, proportion)); lastAngle = angle; } @@ -853,8 +839,9 @@ public: minMaxDiff = (double) valueMax.getValue() - (double) valueMin.getValue(); - lastAngle = rotaryStart + (rotaryEnd - rotaryStart) - * owner.valueToProportionOfLength (currentValue.getValue()); + lastAngle = rotaryParams.startAngleRadians + + (rotaryParams.endAngleRadians - rotaryParams.startAngleRadians) + * owner.valueToProportionOfLength (currentValue.getValue()); valueWhenLastDragged = (sliderBeingDragged == 2 ? valueMax : (sliderBeingDragged == 1 ? valueMin @@ -896,7 +883,7 @@ public: { if (style == IncDecButtons && ! incDecDragged) { - if (e.getDistanceFromDragStart() < 10 || e.mouseWasClicked()) + if (e.getDistanceFromDragStart() < 10 || ! e.mouseWasDraggedSinceMouseDown()) return; incDecDragged = true; @@ -1108,7 +1095,8 @@ public: lf.drawRotarySlider (g, sliderRect.getX(), sliderRect.getY(), sliderRect.getWidth(), sliderRect.getHeight(), - sliderPos, rotaryStart, rotaryEnd, owner); + sliderPos, rotaryParams.startAngleRadians, + rotaryParams.endAngleRadians, owner); } else { @@ -1195,7 +1183,7 @@ public: double valueWhenLastDragged, valueOnMouseDown, skewFactor, lastAngle; double velocityModeSensitivity, velocityModeOffset, minMaxDiff; int velocityModeThreshold; - float rotaryStart, rotaryEnd; + RotaryParameters rotaryParams; Point mouseDragStartPos, mousePosWhenLastDragged; int sliderRegionStart, sliderRegionSize; int sliderBeingDragged; @@ -1214,7 +1202,6 @@ public: bool doubleClickToValue; bool isVelocityBased; bool userKeyOverridesVelocity; - bool rotaryStop; bool incDecButtonsSideBySide; bool sendChangeOnlyOnRelease; bool popupDisplayEnabled; @@ -1326,9 +1313,25 @@ void Slider::removeListener (SliderListener* const listener) { pimpl->listene Slider::SliderStyle Slider::getSliderStyle() const noexcept { return pimpl->style; } void Slider::setSliderStyle (const SliderStyle newStyle) { pimpl->setSliderStyle (newStyle); } -void Slider::setRotaryParameters (const float startAngleRadians, const float endAngleRadians, const bool stopAtEnd) +void Slider::setRotaryParameters (RotaryParameters p) noexcept +{ + // make sure the values are sensible.. + jassert (p.startAngleRadians >= 0 && p.endAngleRadians >= 0); + jassert (p.startAngleRadians < float_Pi * 4.0f && p.endAngleRadians < float_Pi * 4.0f); + jassert (p.startAngleRadians < p.endAngleRadians); + + pimpl->rotaryParams = p; +} + +void Slider::setRotaryParameters (float startAngleRadians, float endAngleRadians, bool stopAtEnd) noexcept +{ + RotaryParameters p = { startAngleRadians, endAngleRadians, stopAtEnd }; + setRotaryParameters (p); +} + +Slider::RotaryParameters Slider::getRotaryParameters() const noexcept { - pimpl->setRotaryParameters (startAngleRadians, endAngleRadians, stopAtEnd); + return pimpl->rotaryParams; } void Slider::setVelocityBasedMode (bool vb) { pimpl->isVelocityBased = vb; } diff --git a/source/modules/juce_gui_basics/widgets/juce_Slider.h b/source/modules/juce_gui_basics/widgets/juce_Slider.h index 2e29348d5..29c89de8e 100644 --- a/source/modules/juce_gui_basics/widgets/juce_Slider.h +++ b/source/modules/juce_gui_basics/widgets/juce_Slider.h @@ -138,22 +138,35 @@ public: SliderStyle getSliderStyle() const noexcept; //============================================================================== - /** Changes the properties of a rotary slider. - - @param startAngleRadians the angle (in radians, clockwise from the top) at which - the slider's minimum value is represented - @param endAngleRadians the angle (in radians, clockwise from the top) at which - the slider's maximum value is represented. This must be - greater than startAngleRadians - @param stopAtEnd determines what happens when a circular drag action rotates beyond - the minimum or maximum angle. If true, the value will stop changing - until the mouse moves back the way it came; if false, the value - will snap back to the value nearest to the mouse. Note that this has - no effect if the drag mode is vertical or horizontal. - */ + struct RotaryParameters + { + /** The angle (in radians, clockwise from the top) at which + the slider's minimum value is represented. */ + float startAngleRadians; + + /** The angle (in radians, clockwise from the top) at which + the slider's maximum value is represented. This must be + greater than startAngleRadians. */ + float endAngleRadians; + + /** Determines what happens when a circular drag action rotates beyond + the minimum or maximum angle. If true, the value will stop changing + until the mouse moves back the way it came; if false, the value + will snap back to the value nearest to the mouse. Note that this has + no effect if the drag mode is vertical or horizontal.*/ + bool stopAtEnd; + }; + + /** Changes the properties of a rotary slider. */ + void setRotaryParameters (RotaryParameters newParameters) noexcept; + + /** Changes the properties of a rotary slider. */ void setRotaryParameters (float startAngleRadians, float endAngleRadians, - bool stopAtEnd); + bool stopAtEnd) noexcept; + + /** Changes the properties of a rotary slider. */ + RotaryParameters getRotaryParameters() const noexcept; /** Sets the distance the mouse has to move to drag the slider across the full extent of its range. diff --git a/source/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp b/source/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp index 96482c776..13fee7e23 100644 --- a/source/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp +++ b/source/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp @@ -590,7 +590,8 @@ void TableHeaderComponent::mouseDrag (const MouseEvent& e) { if (columnIdBeingResized == 0 && columnIdBeingDragged == 0 - && ! (e.mouseWasClicked() || e.mods.isPopupMenu())) + && e.mouseWasDraggedSinceMouseDown() + && ! e.mods.isPopupMenu()) { dragOverlayComp = nullptr; @@ -599,6 +600,7 @@ void TableHeaderComponent::mouseDrag (const MouseEvent& e) if (columnIdBeingResized != 0) { const ColumnInfo* const ci = getInfoForId (columnIdBeingResized); + jassert (ci != nullptr); initialColumnWidth = ci->width; } else @@ -767,7 +769,7 @@ void TableHeaderComponent::mouseUp (const MouseEvent& e) updateColumnUnderMouse (e); - if (columnIdUnderMouse != 0 && e.mouseWasClicked() && ! e.mods.isPopupMenu()) + if (columnIdUnderMouse != 0 && ! (e.mouseWasDraggedSinceMouseDown() || e.mods.isPopupMenu())) columnClicked (columnIdUnderMouse, e.mods); dragOverlayComp = nullptr; diff --git a/source/modules/juce_gui_basics/widgets/juce_TableListBox.cpp b/source/modules/juce_gui_basics/widgets/juce_TableListBox.cpp index 541f424d3..deb7785bb 100644 --- a/source/modules/juce_gui_basics/widgets/juce_TableListBox.cpp +++ b/source/modules/juce_gui_basics/widgets/juce_TableListBox.cpp @@ -38,19 +38,28 @@ public: const TableHeaderComponent& headerComp = owner.getHeader(); const int numColumns = headerComp.getNumColumns (true); + const Rectangle clipBounds (g.getClipBounds()); for (int i = 0; i < numColumns; ++i) { if (columnComponents[i] == nullptr) { - const int columnId = headerComp.getColumnIdOfIndex (i, true); const Rectangle columnRect (headerComp.getColumnPosition(i).withHeight (getHeight())); - Graphics::ScopedSaveState ss (g); + if (columnRect.getX() >= clipBounds.getRight()) + break; - g.reduceClipRegion (columnRect); - g.setOrigin (columnRect.getX(), 0); - tableModel->paintCell (g, row, columnId, columnRect.getWidth(), columnRect.getHeight(), isSelected); + if (columnRect.getRight() > clipBounds.getX()) + { + Graphics::ScopedSaveState ss (g); + + if (g.reduceClipRegion (columnRect)) + { + g.setOrigin (columnRect.getX(), 0); + tableModel->paintCell (g, row, headerComp.getColumnIdOfIndex (i, true), + columnRect.getWidth(), columnRect.getHeight(), isSelected); + } + } } } } @@ -144,7 +153,10 @@ public: void mouseDrag (const MouseEvent& e) override { - if (isEnabled() && owner.getModel() != nullptr && ! (e.mouseWasClicked() || isDragging)) + if (isEnabled() + && owner.getModel() != nullptr + && e.mouseWasDraggedSinceMouseDown() + && ! isDragging) { SparseSet rowsToDrag; diff --git a/source/modules/juce_gui_basics/widgets/juce_TextEditor.h b/source/modules/juce_gui_basics/widgets/juce_TextEditor.h index 6089db6b0..6d2bd5fb1 100644 --- a/source/modules/juce_gui_basics/widgets/juce_TextEditor.h +++ b/source/modules/juce_gui_basics/widgets/juce_TextEditor.h @@ -554,12 +554,12 @@ public: */ LengthAndCharacterRestriction (int maxNumChars, const String& allowedCharacters); + String filterNewText (TextEditor&, const String&) override; + private: String allowedCharacters; int maxLength; - String filterNewText (TextEditor&, const String&) override; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LengthAndCharacterRestriction) }; diff --git a/source/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp b/source/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp index 5e5ce3f2e..c11cec139 100644 --- a/source/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp +++ b/source/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp @@ -64,7 +64,7 @@ public: void mouseDrag (const MouseEvent& e) override { - if (! (isDragging || e.mouseWasClicked())) + if (e.mouseWasDraggedSinceMouseDown() && ! isDragging) { isDragging = true; diff --git a/source/modules/juce_gui_basics/windows/juce_DialogWindow.cpp b/source/modules/juce_gui_basics/windows/juce_DialogWindow.cpp index 263ce930a..432474932 100644 --- a/source/modules/juce_gui_basics/windows/juce_DialogWindow.cpp +++ b/source/modules/juce_gui_basics/windows/juce_DialogWindow.cpp @@ -33,14 +33,22 @@ DialogWindow::~DialogWindow() { } -bool DialogWindow::keyPressed (const KeyPress& key) +bool DialogWindow::escapeKeyPressed() { - if (escapeKeyTriggersCloseButton && key == KeyPress::escapeKey) + if (escapeKeyTriggersCloseButton) { setVisible (false); return true; } + return false; +} + +bool DialogWindow::keyPressed (const KeyPress& key) +{ + if (key == KeyPress::escapeKey && escapeKeyPressed()) + return true; + return DocumentWindow::keyPressed (key); } diff --git a/source/modules/juce_gui_basics/windows/juce_DialogWindow.h b/source/modules/juce_gui_basics/windows/juce_DialogWindow.h index 6064e46a3..4a6badcc6 100644 --- a/source/modules/juce_gui_basics/windows/juce_DialogWindow.h +++ b/source/modules/juce_gui_basics/windows/juce_DialogWindow.h @@ -241,6 +241,12 @@ public: #endif + /** Called when the escape key is pressed. + This can be overridden to do things other than the default behaviour, which is to hide + the window. Return true if the key has been used, or false if it was ignored. + */ + virtual bool escapeKeyPressed(); + protected: //============================================================================== /** @internal */ diff --git a/source/modules/juce_gui_basics/windows/juce_ResizableWindow.cpp b/source/modules/juce_gui_basics/windows/juce_ResizableWindow.cpp index 7aa03ee4a..341acaa6e 100644 --- a/source/modules/juce_gui_basics/windows/juce_ResizableWindow.cpp +++ b/source/modules/juce_gui_basics/windows/juce_ResizableWindow.cpp @@ -335,9 +335,7 @@ void ResizableWindow::setConstrainer (ComponentBoundsConstrainer* newConstrainer resizableBorder = nullptr; setResizable (shouldBeResizable, useBottomRightCornerResizer); - - if (ComponentPeer* const peer = getPeer()) - peer->setConstrainer (newConstrainer); + updatePeerConstrainer(); } } @@ -384,9 +382,7 @@ void ResizableWindow::lookAndFeelChanged() if (isOnDesktop()) { Component::addToDesktop (getDesktopWindowStyleFlags()); - - if (ComponentPeer* const peer = getPeer()) - peer->setConstrainer (constrainer); + updatePeerConstrainer(); } } @@ -492,7 +488,10 @@ bool ResizableWindow::isKioskMode() const void ResizableWindow::updateLastPosIfShowing() { if (isShowing()) + { updateLastPosIfNotFullScreen(); + updatePeerConstrainer(); + } } void ResizableWindow::updateLastPosIfNotFullScreen() @@ -501,6 +500,13 @@ void ResizableWindow::updateLastPosIfNotFullScreen() lastNonFullScreenPos = getBounds(); } +void ResizableWindow::updatePeerConstrainer() +{ + if (isOnDesktop()) + if (ComponentPeer* const peer = getPeer()) + peer->setConstrainer (constrainer); +} + void ResizableWindow::parentSizeChanged() { if (isFullScreen() && getParentComponent() != nullptr) diff --git a/source/modules/juce_gui_basics/windows/juce_ResizableWindow.h b/source/modules/juce_gui_basics/windows/juce_ResizableWindow.h index aff66092e..a8f0bf429 100644 --- a/source/modules/juce_gui_basics/windows/juce_ResizableWindow.h +++ b/source/modules/juce_gui_basics/windows/juce_ResizableWindow.h @@ -395,6 +395,7 @@ private: void updateLastPosIfNotFullScreen(); void updateLastPosIfShowing(); void setContent (Component*, bool takeOwnership, bool resizeToFit); + void updatePeerConstrainer(); #if JUCE_CATCH_DEPRECATED_CODE_MISUSE // The parameters for these methods have changed - please update your code! diff --git a/source/modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp b/source/modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp index 247ccd487..ead51c2e7 100644 --- a/source/modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp +++ b/source/modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp @@ -52,7 +52,7 @@ CodeEditorComponent::ColourScheme XmlTokeniser::getDefaultColourScheme() cs.set (types[i].name, Colour (types[i].colour)); return cs; -}; +} template static void skipToEndOfXmlDTD (Iterator& source) noexcept diff --git a/source/modules/juce_gui_extra/juce_gui_extra.cpp b/source/modules/juce_gui_extra/juce_gui_extra.cpp index 96b15280a..89c739703 100644 --- a/source/modules/juce_gui_extra/juce_gui_extra.cpp +++ b/source/modules/juce_gui_extra/juce_gui_extra.cpp @@ -31,7 +31,12 @@ #error "Incorrect use of JUCE cpp file" #endif -#include "../juce_core/native/juce_BasicNativeHeaders.h" +#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 +#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 +#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 +#define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 +#define JUCE_GRAPHICS_INCLUDE_COREGRAPHICS_HELPERS 1 + #include "juce_gui_extra.h" //============================================================================== @@ -69,10 +74,6 @@ namespace juce { -#if JUCE_MAC || JUCE_IOS - #include "../juce_core/native/juce_osx_ObjCHelpers.h" -#endif - #include "documents/juce_FileBasedDocument.cpp" #include "code_editor/juce_CodeDocument.cpp" #include "code_editor/juce_CodeEditorComponent.cpp" @@ -98,9 +99,6 @@ namespace juce //============================================================================== #if JUCE_MAC || JUCE_IOS - #include "../juce_core/native/juce_osx_ObjCHelpers.h" - #include "../juce_graphics/native/juce_mac_CoreGraphicsHelpers.h" - #if JUCE_MAC #include "native/juce_mac_NSViewComponent.mm" #include "native/juce_mac_AppleRemote.mm" @@ -117,8 +115,6 @@ namespace juce //============================================================================== #elif JUCE_WINDOWS - #include "../juce_core/native/juce_win32_ComSmartPtr.h" - #include "../juce_events/native/juce_win32_HiddenMessageWindow.h" #include "native/juce_win32_ActiveXComponent.cpp" #if JUCE_WEB_BROWSER #include "native/juce_win32_WebBrowserComponent.cpp" diff --git a/source/modules/juce_gui_extra/juce_gui_extra.h b/source/modules/juce_gui_extra/juce_gui_extra.h index f40d5f4a3..73aff3385 100644 --- a/source/modules/juce_gui_extra/juce_gui_extra.h +++ b/source/modules/juce_gui_extra/juce_gui_extra.h @@ -28,7 +28,7 @@ #include "../juce_gui_basics/juce_gui_basics.h" -//============================================================================= +//============================================================================== /** Config: JUCE_WEB_BROWSER This lets you disable the WebBrowserComponent class (Mac and Windows). If you're not using any embedded web-pages, turning this off may reduce your code size. @@ -47,7 +47,7 @@ #endif #endif -//============================================================================= +//============================================================================== namespace juce { diff --git a/source/modules/juce_gui_extra/native/juce_mac_CarbonViewWrapperComponent.h b/source/modules/juce_gui_extra/native/juce_mac_CarbonViewWrapperComponent.h index 6a1b747c1..bd1d56456 100644 --- a/source/modules/juce_gui_extra/native/juce_mac_CarbonViewWrapperComponent.h +++ b/source/modules/juce_gui_extra/native/juce_mac_CarbonViewWrapperComponent.h @@ -40,9 +40,9 @@ class CarbonViewWrapperComponent : public Component, public: CarbonViewWrapperComponent() : ComponentMovementWatcher (this), + carbonWindow (nil), keepPluginWindowWhenHidden (false), - wrapperWindow (0), - carbonWindow (0), + wrapperWindow (nil), embeddedView (0), recursiveResize (false), repaintChildOnCreation (true) @@ -73,7 +73,7 @@ public: void createWindow() { - if (wrapperWindow == 0) + if (wrapperWindow == nil) { Rect r; r.left = (short) getScreenX(); @@ -135,7 +135,7 @@ public: removeView (embeddedView); embeddedView = 0; - if (wrapperWindow != 0) + if (wrapperWindow != nil) { NSWindow* ownerWindow = getOwnerWindow(); @@ -147,7 +147,7 @@ public: RemoveEventHandler (eventHandlerRef); DisposeWindow (wrapperWindow); - wrapperWindow = 0; + wrapperWindow = nil; } } @@ -192,7 +192,7 @@ public: HIViewSetFrame (embeddedView, &r); } - if (wrapperWindow != 0) + if (wrapperWindow != nil) { jassert (getTopLevelComponent()->getDesktopScaleFactor() == 1.0f); Rectangle screenBounds (getScreenBounds() * Desktop::getInstance().getGlobalScaleFactor()); @@ -316,11 +316,11 @@ public: return ((CarbonViewWrapperComponent*) userData)->carbonEventHandler (nextHandlerRef, event); } + NSWindow* carbonWindow; bool keepPluginWindowWhenHidden; protected: WindowRef wrapperWindow; - NSWindow* carbonWindow; HIViewRef embeddedView; bool recursiveResize, repaintChildOnCreation; Time creationTime; @@ -330,4 +330,15 @@ protected: NSWindow* getOwnerWindow() const { return [((NSView*) getWindowHandle()) window]; } }; +//============================================================================== +// Non-public utility function that hosts can use if they need to get hold of the +// internals of a carbon wrapper window.. +void* getCarbonWindow (Component* possibleCarbonComponent) +{ + if (CarbonViewWrapperComponent* cv = dynamic_cast (possibleCarbonComponent)) + return cv->carbonWindow; + + return nullptr; +} + #endif // JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_H_INCLUDED diff --git a/source/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm b/source/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm index 36cd7817b..1b8a8b223 100644 --- a/source/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm +++ b/source/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm @@ -260,6 +260,10 @@ public: #else [webView loadRequest: r]; #endif + + #if JUCE_IOS + [webView setScalesPageToFit:YES]; + #endif } } diff --git a/source/utils/CarlaSemUtils.hpp b/source/utils/CarlaSemUtils.hpp index 59acc801f..44ce9e068 100644 --- a/source/utils/CarlaSemUtils.hpp +++ b/source/utils/CarlaSemUtils.hpp @@ -74,6 +74,7 @@ bool carla_sem_create2(carla_sem_t& sem) noexcept { static int bootcounter = 0; std::snprintf(sem.bootname, 31, "crlsm_%i_%i_%p", ++bootcounter, getpid(), &sem); + sem.bootname[31] = '\0'; if (bootstrap_register(bootport, sem.bootname, sem.sem) == KERN_SUCCESS) return true; @@ -83,7 +84,6 @@ bool carla_sem_create2(carla_sem_t& sem) noexcept return false; #elif defined(CARLA_USE_FUTEXES) - sem.count = 0; return true; #else return (::sem_init(&sem.sem, 1, 0) == 0); @@ -219,8 +219,8 @@ bool carla_sem_timedwait(carla_sem_t& sem, const uint msecs, const bool server) timespec now; ::clock_gettime(CLOCK_REALTIME, &now); - struct timespec delta = { secs, nsecs }; - struct timespec end = { now.tv_sec + delta.tv_sec, now.tv_nsec + delta.tv_nsec }; + timespec delta = { secs, nsecs }; + timespec end = { now.tv_sec + delta.tv_sec, now.tv_nsec + delta.tv_nsec }; if (end.tv_nsec >= 1000000000L) { ++end.tv_sec; end.tv_nsec -= 1000000000L;