Expand Up @@ -335,28 +335,28 @@ class ARADocumentControllerSpecialisation : public ARADocument::Listener,

//==============================================================================
/** Override to return a custom subclass instance of ARADocument. */
ARADocument* doCreateDocument();
virtual ARADocument* doCreateDocument();

/** Override to return a custom subclass instance of ARAMusicalContext. */
ARAMusicalContext* doCreateMusicalContext (ARADocument* document,
ARA::ARAMusicalContextHostRef hostRef);
virtual ARAMusicalContext* doCreateMusicalContext (ARADocument* document,
ARA::ARAMusicalContextHostRef hostRef);

/** Override to return a custom subclass instance of ARARegionSequence. */
ARARegionSequence* doCreateRegionSequence (ARADocument* document,
ARA::ARARegionSequenceHostRef hostRef);
virtual ARARegionSequence* doCreateRegionSequence (ARADocument* document,
ARA::ARARegionSequenceHostRef hostRef);

/** Override to return a custom subclass instance of ARAAudioSource. */
ARAAudioSource* doCreateAudioSource (ARADocument* document,
ARA::ARAAudioSourceHostRef hostRef);
virtual ARAAudioSource* doCreateAudioSource (ARADocument* document,
ARA::ARAAudioSourceHostRef hostRef);

/** Override to return a custom subclass instance of ARAAudioModification. */
ARAAudioModification* doCreateAudioModification (ARAAudioSource* audioSource,
ARA::ARAAudioModificationHostRef hostRef,
const ARAAudioModification* optionalModificationToClone);
virtual ARAAudioModification* doCreateAudioModification (ARAAudioSource* audioSource,
ARA::ARAAudioModificationHostRef hostRef,
const ARAAudioModification* optionalModificationToClone);

/** Override to return a custom subclass instance of ARAPlaybackRegion. */
ARAPlaybackRegion* doCreatePlaybackRegion (ARAAudioModification* modification,
ARA::ARAPlaybackRegionHostRef hostRef);
virtual ARAPlaybackRegion* doCreatePlaybackRegion (ARAAudioModification* modification,
ARA::ARAPlaybackRegionHostRef hostRef);

private:
//==============================================================================
Expand Down
Expand Up @@ -458,7 +458,7 @@ class JUCE_API ARAMusicalContextListener
}

/** Called before the musical context is destroyed.
@param musicalContext The musical context that will be destoyed.
@param musicalContext The musical context that will be destroyed.
*/
virtual void willDestroyMusicalContext (ARAMusicalContext* musicalContext)
{
Expand Down Expand Up @@ -559,7 +559,7 @@ class JUCE_API ARAPlaybackRegionListener
}

/** Called before the playback region is destroyed.
@param playbackRegion The playback region that will be destoyed.
@param playbackRegion The playback region that will be destroyed.
*/
virtual void willDestroyPlaybackRegion (ARAPlaybackRegion* playbackRegion)
{
Expand Down Expand Up @@ -706,7 +706,7 @@ class JUCE_API ARARegionSequenceListener
}

/** Called before the region sequence is destroyed.
@param regionSequence The region sequence that will be destoyed.
@param regionSequence The region sequence that will be destroyed.
*/
virtual void willDestroyRegionSequence (ARARegionSequence* regionSequence)
{
Expand Down Expand Up @@ -898,7 +898,7 @@ class JUCE_API ARAAudioSourceListener
}

/** Called before the audio source is destroyed.
@param audioSource The audio source that will be destoyed.
@param audioSource The audio source that will be destroyed.
*/
virtual void willDestroyAudioSource (ARAAudioSource* audioSource)
{
Expand Down Expand Up @@ -1072,7 +1072,7 @@ class JUCE_API ARAAudioModificationListener
}

/** Called before the audio modification is destroyed.
@param audioModification The audio modification that will be destoyed.
@param audioModification The audio modification that will be destroyed.
*/
virtual void willDestroyAudioModification (ARAAudioModification* audioModification)
{
Expand Down
Expand Up @@ -30,7 +30,7 @@ namespace juce

bool ARARenderer::processBlock (AudioBuffer<double>& buffer,
AudioProcessor::Realtime realtime,
const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept
const AudioPlayHead::PositionInfo& positionInfo) noexcept
{
ignoreUnused (buffer, realtime, positionInfo);

Expand Down
Expand Up @@ -88,7 +88,7 @@ class JUCE_API ARARenderer
*/
virtual bool processBlock (AudioBuffer<float>& buffer,
AudioProcessor::Realtime realtime,
const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept = 0;
const AudioPlayHead::PositionInfo& positionInfo) noexcept = 0;

/** Renders the output into the given buffer. Returns true if rendering executed without error,
false otherwise.
Expand All @@ -108,7 +108,7 @@ class JUCE_API ARARenderer
*/
virtual bool processBlock (AudioBuffer<double>& buffer,
AudioProcessor::Realtime realtime,
const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept;
const AudioPlayHead::PositionInfo& positionInfo) noexcept;
};

//==============================================================================
Expand All @@ -128,7 +128,7 @@ class JUCE_API ARAPlaybackRenderer : public ARA::PlugIn::PlaybackRenderer,

bool processBlock (AudioBuffer<float>& buffer,
AudioProcessor::Realtime realtime,
const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept override
const AudioPlayHead::PositionInfo& positionInfo) noexcept override
{
ignoreUnused (buffer, realtime, positionInfo);
return false;
Expand Down Expand Up @@ -189,7 +189,7 @@ class JUCE_API ARAEditorRenderer : public ARA::PlugIn::EditorRenderer,
// isNonRealtime of the process context - typically preview is limited to realtime.
bool processBlock (AudioBuffer<float>& buffer,
AudioProcessor::Realtime isNonRealtime,
const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept override
const AudioPlayHead::PositionInfo& positionInfo) noexcept override
{
ignoreUnused (buffer, isNonRealtime, positionInfo);
return true;
Expand Down
Expand Up @@ -84,7 +84,7 @@ bool AudioProcessorARAExtension::releaseResourcesForARA()

bool AudioProcessorARAExtension::processBlockForARA (AudioBuffer<float>& buffer,
AudioProcessor::Realtime realtime,
const AudioPlayHead::CurrentPositionInfo& positionInfo)
const AudioPlayHead::PositionInfo& positionInfo)
{
// validate that the host has prepared us before processing
ARA_VALIDATE_API_STATE (isPrepared);
Expand All @@ -109,12 +109,10 @@ bool AudioProcessorARAExtension::processBlockForARA (AudioBuffer<float>& buffer,
juce::AudioProcessor::Realtime realtime,
AudioPlayHead* playhead)
{
AudioPlayHead::CurrentPositionInfo positionInfo;

if (! isBoundToARA() || ! playhead || ! playhead->getCurrentPosition (positionInfo))
positionInfo.resetToDefault();

return processBlockForARA (buffer, realtime, positionInfo);
return processBlockForARA (buffer,
realtime,
playhead != nullptr ? playhead->getPosition().orFallback (AudioPlayHead::PositionInfo{})
: AudioPlayHead::PositionInfo{});
}

//==============================================================================
Expand Down
Expand Up @@ -144,7 +144,7 @@ class JUCE_API AudioProcessorARAExtension : public ARA::PlugIn::PlugInExtensio
*/
bool processBlockForARA (AudioBuffer<float>& buffer,
AudioProcessor::Realtime realtime,
const AudioPlayHead::CurrentPositionInfo& positionInfo);
const AudioPlayHead::PositionInfo& positionInfo);

/** Implementation helper for AudioProcessor::processBlock().
Expand Down
Expand Up @@ -122,6 +122,14 @@ struct ExtensionsVisitor
virtual void createARAFactoryAsync (std::function<void (ARAFactoryWrapper)>) const = 0;
};

ExtensionsVisitor() = default;

ExtensionsVisitor (const ExtensionsVisitor&) = default;
ExtensionsVisitor (ExtensionsVisitor&&) = default;

ExtensionsVisitor& operator= (const ExtensionsVisitor&) = default;
ExtensionsVisitor& operator= (ExtensionsVisitor&&) = default;

virtual ~ExtensionsVisitor() = default;

/** Will be called if there is no platform specific information available. */
Expand Down
Expand Up @@ -35,7 +35,7 @@
ID: juce_audio_utils
vendor: juce
version: 6.1.6
version: 7.0.1
name: JUCE extra audio utility classes
description: Classes for audio-related GUI and miscellaneous tasks.
website: http://www.juce.com/juce
Expand Down
Expand Up @@ -171,6 +171,8 @@ void AudioProcessorPlayer::setProcessor (AudioProcessor* const processorToPlay)
if (processor == processorToPlay)
return;

sampleCount = 0;

if (processorToPlay != nullptr && sampleRate > 0 && blockSize > 0)
{
defaultProcessorChannels = NumChannels { processorToPlay->getBusesLayout() };
Expand Down Expand Up @@ -267,15 +269,48 @@ void AudioProcessorPlayer::audioDeviceIOCallbackWithContext (const float** const

const ScopedLock sl2 (processor->getCallbackLock());

processor->setHostTimeNanos (context.hostTimeNs);

struct AtEndOfScope
class PlayHead : private AudioPlayHead
{
~AtEndOfScope() { proc.setHostTimeNanos (nullptr); }
AudioProcessor& proc;
public:
PlayHead (AudioProcessor& proc,
Optional<uint64_t> hostTimeIn,
uint64_t sampleCountIn,
double sampleRateIn)
: processor (proc),
hostTimeNs (hostTimeIn),
sampleCount (sampleCountIn),
seconds ((double) sampleCountIn / sampleRateIn)
{
processor.setPlayHead (this);
}

~PlayHead() override
{
processor.setPlayHead (nullptr);
}

private:
Optional<PositionInfo> getPosition() const override
{
PositionInfo info;
info.setHostTimeNs (hostTimeNs);
info.setTimeInSamples ((int64_t) sampleCount);
info.setTimeInSeconds (seconds);
return info;
}

AudioProcessor& processor;
Optional<uint64_t> hostTimeNs;
uint64_t sampleCount;
double seconds;
};

const AtEndOfScope scope { *processor };
PlayHead playHead { *processor,
context.hostTimeNs != nullptr ? makeOptional (*context.hostTimeNs) : nullopt,
sampleCount,
sampleRate };

sampleCount += (uint64_t) numSamples;

if (! processor->isSuspended())
{
Expand Down
Expand Up @@ -137,6 +137,7 @@ class JUCE_API AudioProcessorPlayer : public AudioIODeviceCallback,
MidiBuffer incomingMidi;
MidiMessageCollector messageCollector;
MidiOutput* midiOutput = nullptr;
uint64_t sampleCount = 0;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorPlayer)
};
Expand Down
59 changes: 41 additions & 18 deletions libs/juce7/source/modules/juce_core/containers/juce_Optional.h
Expand Up @@ -20,10 +20,6 @@
==============================================================================
*/

#pragma once

#include <utility>

namespace juce
{

Expand All @@ -38,24 +34,40 @@ constexpr auto isNothrowSwappable = noexcept (swap (std::declval<T&>(), std::dec
} // namespace adlSwap
} // namespace detail

struct Nullopt {};
/** A type representing the null state of an Optional.
Similar to std::nullopt_t.
*/
struct Nullopt
{
explicit constexpr Nullopt (int) {}
};

constexpr Nullopt nullopt;
/** An object that can be used when constructing and comparing Optional instances.
Similar to std::nullopt.
*/
constexpr Nullopt nullopt { 0 };

// Without this, our tests can emit "unreachable code" warnings during
// link time code generation.
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4702)

/* For internal use only!
/**
A simple optional type.
Has similar (not necessarily identical!) semantics to std::optional.
This is intended to stand-in for std::optional while JUCE's minimum
supported language standard is lower than C++17. When the minimum language
standard moves to C++17, this class will probably be deprecated, in much
the same way that juce::ScopedPointer was deprecated in favour of
std::unique_ptr after C++11.
This isn't really intended to be used by JUCE clients. Instead, it's to be
used internally in JUCE code, with an API close-enough to std::optional
that the types can be swapped with fairly minor disruption at some point in
the future, but *without breaking any public APIs*.
@tags{Core}
*/
template <typename Value>
class Optional
Expand Down Expand Up @@ -97,40 +109,41 @@ class Optional
&& NotConstructibleFromSimilarType<T, U>::value>;

public:
Optional() = default;
Optional() : placeholder() {}

Optional (Nullopt) noexcept {}
Optional (Nullopt) noexcept : placeholder() {}

template <typename U = Value,
typename = std::enable_if_t<std::is_constructible<Value, U&&>::value
&& ! std::is_same<std::decay_t<U>, Optional>::value>>
Optional (U&& value) noexcept (noexcept (Value (std::forward<U> (value))))
: valid (true)
: storage (std::forward<U> (value)), valid (true)
{
new (&storage) Value (std::forward<U> (value));
}

Optional (Optional&& other) noexcept (noexcept (std::declval<Optional>().constructFrom (other)))
: placeholder()
{
constructFrom (other);
}

Optional (const Optional& other)
: valid (other.valid)
: placeholder(), valid (other.valid)
{
if (valid)
new (&storage) Value (*other);
}

template <typename Other, typename = OptionalMoveConstructorEnabled<Value, Other>>
Optional (Optional<Other>&& other) noexcept (noexcept (std::declval<Optional>().constructFrom (other)))
: placeholder()
{
constructFrom (other);
}

template <typename Other, typename = OptionalCopyConstructorEnabled<Value, Other>>
Optional (const Optional<Other>& other)
: valid (other.hasValue())
: placeholder(), valid (other.hasValue())
{
if (valid)
new (&storage) Value (*other);
Expand Down Expand Up @@ -167,7 +180,7 @@ class Optional
return *this;
}

/* Maintains the strong exception safety guarantee. */
/** Maintains the strong exception safety guarantee. */
Optional& operator= (const Optional& other)
{
auto copy = other;
Expand All @@ -182,7 +195,7 @@ class Optional
return *this;
}

/* Maintains the strong exception safety guarantee. */
/** Maintains the strong exception safety guarantee. */
template <typename Other, typename = OptionalCopyAssignmentEnabled<Value, Other>>
Optional& operator= (const Optional<Other>& other)
{
Expand Down Expand Up @@ -211,7 +224,7 @@ class Optional
operator*().~Value();
}

/* Like std::optional::value_or */
/** Like std::optional::value_or */
template <typename U>
Value orFallback (U&& fallback) const { return *this ? **this : std::forward<U> (fallback); }

Expand Down Expand Up @@ -271,12 +284,22 @@ class Optional
}
}

std::aligned_storage_t<sizeof (Value), alignof (Value)> storage;
union
{
char placeholder;
Value storage;
};
bool valid = false;
};

JUCE_END_IGNORE_WARNINGS_MSVC

template <typename Value>
Optional<std::decay_t<Value>> makeOptional (Value&& v)
{
return std::forward<Value> (v);
}

template <class T, class U>
bool operator== (const Optional<T>& lhs, const Optional<U>& rhs)
{
Expand Down
476 changes: 476 additions & 0 deletions libs/juce7/source/modules/juce_core/files/juce_AndroidDocument.h

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions libs/juce7/source/modules/juce_core/files/juce_File.cpp
Expand Up @@ -1146,11 +1146,18 @@ class FileTests : public UnitTest
expect (home.getChildFile ("./../xyz") == home.getParentDirectory().getChildFile ("xyz"));
expect (home.getChildFile ("a1/a2/a3/./../../a4") == home.getChildFile ("a1/a4"));

expect (! File().hasReadAccess());
expect (! File().hasWriteAccess());

expect (! tempFile.hasReadAccess());

{
FileOutputStream fo (tempFile);
fo.write ("0123456789", 10);
}

expect (tempFile.hasReadAccess());

expect (tempFile.exists());
expect (tempFile.getSize() == 10);
expect (std::abs ((int) (tempFile.getLastModificationTime().toMilliseconds() - Time::getCurrentTime().toMilliseconds())) < 3000);
Expand Down
6 changes: 6 additions & 0 deletions libs/juce7/source/modules/juce_core/files/juce_File.h
Expand Up @@ -342,6 +342,12 @@ class JUCE_API File final
*/
bool hasWriteAccess() const;

/** Checks whether a file can be read.
@returns true if it's possible to read this file.
*/
bool hasReadAccess() const;

/** Changes the write-permission of a file or directory.
@param shouldBeReadOnly whether to add or remove write-permission
Expand Down
Expand Up @@ -33,17 +33,34 @@ struct MimeTypeTableEntry
static MimeTypeTableEntry table[641];
};

static StringArray getMimeTypesForFileExtension (const String& fileExtension)
static StringArray getMatches (const String& toMatch,
const char* MimeTypeTableEntry::* matchField,
const char* MimeTypeTableEntry::* returnField)
{
StringArray result;

for (auto type : MimeTypeTableEntry::table)
if (fileExtension == type.fileExtension)
result.add (type.mimeType);
if (toMatch == type.*matchField)
result.add (type.*returnField);

return result;
}

namespace MimeTypeTable
{

StringArray getMimeTypesForFileExtension (const String& fileExtension)
{
return getMatches (fileExtension, &MimeTypeTableEntry::fileExtension, &MimeTypeTableEntry::mimeType);
}

StringArray getFileExtensionsForMimeType (const String& mimeType)
{
return getMatches (mimeType, &MimeTypeTableEntry::mimeType, &MimeTypeTableEntry::fileExtension);
}

} // namespace MimeTypeTable

//==============================================================================
MimeTypeTableEntry MimeTypeTableEntry::table[641] =
{
Expand Down
42 changes: 42 additions & 0 deletions libs/juce7/source/modules/juce_core/files/juce_common_MimeTypes.h
@@ -0,0 +1,42 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/

#pragma once

namespace juce
{

namespace MimeTypeTable
{

/* @internal */
StringArray getMimeTypesForFileExtension (const String& fileExtension);

/* @internal */
StringArray getFileExtensionsForMimeType (const String& mimeType);

} // namespace MimeTypeTable

} // namespace juce
4 changes: 3 additions & 1 deletion libs/juce7/source/modules/juce_core/juce_core.cpp
Expand Up @@ -249,6 +249,9 @@

#endif

#include "files/juce_common_MimeTypes.h"
#include "files/juce_common_MimeTypes.cpp"
#include "native/juce_android_AndroidDocument.cpp"
#include "threads/juce_HighResolutionTimer.cpp"
#include "threads/juce_WaitableEvent.cpp"
#include "network/juce_URL.cpp"
Expand All @@ -263,7 +266,6 @@
#if JUCE_UNIT_TESTS
#include "containers/juce_HashMap_test.cpp"

#include "containers/juce_Optional.h"
#include "containers/juce_Optional_test.cpp"
#endif

Expand Down
4 changes: 3 additions & 1 deletion libs/juce7/source/modules/juce_core/juce_core.h
Expand Up @@ -32,7 +32,7 @@
ID: juce_core
vendor: juce
version: 6.1.6
version: 7.0.1
name: JUCE core classes
description: The essential set of basic JUCE classes, as required by all the other JUCE modules. Includes text, container, memory, threading and i/o functionality.
website: http://www.juce.com/juce
Expand Down Expand Up @@ -244,6 +244,7 @@ JUCE_END_IGNORE_WARNINGS_MSVC
#include "memory/juce_ReferenceCountedObject.h"
#include "memory/juce_ScopedPointer.h"
#include "memory/juce_OptionalScopedPointer.h"
#include "containers/juce_Optional.h"
#include "containers/juce_ScopedValueSetter.h"
#include "memory/juce_Singleton.h"
#include "memory/juce_WeakReference.h"
Expand Down Expand Up @@ -342,6 +343,7 @@ JUCE_END_IGNORE_WARNINGS_MSVC
#include "memory/juce_SharedResourcePointer.h"
#include "memory/juce_AllocationHooks.h"
#include "memory/juce_Reservoir.h"
#include "files/juce_AndroidDocument.h"

#if JUCE_CORE_INCLUDE_OBJC_HELPERS && (JUCE_MAC || JUCE_IOS)
#include "native/juce_mac_ObjCHelpers.h"
Expand Down
Expand Up @@ -154,6 +154,8 @@
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <accctrl.h>
#include <aclapi.h>

#if ! JUCE_CXX17_IS_AVAILABLE
#pragma push_macro ("WIN_NOEXCEPT")
Expand Down
Expand Up @@ -502,7 +502,7 @@ class ObjCBlock
bool operator!= (const void* ptr) const { return ((const void*) block != ptr); }
~ObjCBlock() { if (block != nullptr) [block release]; }

operator BlockType() { return block; }
operator BlockType() const { return block; }

private:
BlockType block;
Expand Down
Expand Up @@ -288,6 +288,12 @@ bool File::hasWriteAccess() const
return false;
}

bool File::hasReadAccess() const
{
return fullPath.isNotEmpty()
&& access (fullPath.toUTF8(), R_OK) == 0;
}

static bool setFileModeFlags (const String& fullPath, mode_t flags, bool shouldSet) noexcept
{
juce_statStruct info;
Expand Down
Expand Up @@ -23,7 +23,7 @@
namespace juce
{

#if (! defined (_MSC_VER) && ! defined (__uuidof))
#if (JUCE_MINGW && JUCE_32BIT) || (! defined (_MSC_VER) && ! defined (__uuidof))
#ifdef __uuidof
#undef __uuidof
#endif
Expand Down
103 changes: 94 additions & 9 deletions libs/juce7/source/modules/juce_core/native/juce_win32_Files.cpp
Expand Up @@ -159,6 +159,83 @@ namespace WindowsFileHelpers

return Result::fail (String (messageBuffer));
}

// The docs for the Windows security API aren't very clear. Some parts of the following
// function (the flags passed to GetNamedSecurityInfo, duplicating the primary access token)
// were guided by the example at https://blog.aaronballman.com/2011/08/how-to-check-access-rights/
static bool hasFileAccess (const File& file, DWORD accessType)
{
const auto& path = file.getFullPathName();

if (path.isEmpty())
return false;

struct PsecurityDescriptorGuard
{
~PsecurityDescriptorGuard() { if (psecurityDescriptor != nullptr) LocalFree (psecurityDescriptor); }
PSECURITY_DESCRIPTOR psecurityDescriptor = nullptr;
};

PsecurityDescriptorGuard descriptorGuard;

if (GetNamedSecurityInfo (path.toWideCharPointer(),
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
nullptr,
nullptr,
nullptr,
nullptr,
&descriptorGuard.psecurityDescriptor) != ERROR_SUCCESS)
{
return false;
}

struct HandleGuard
{
~HandleGuard() { if (handle != INVALID_HANDLE_VALUE) CloseHandle (handle); }
HANDLE handle = nullptr;
};

HandleGuard primaryTokenGuard;

if (! OpenProcessToken (GetCurrentProcess(),
TOKEN_IMPERSONATE | TOKEN_DUPLICATE | TOKEN_QUERY | STANDARD_RIGHTS_READ,
&primaryTokenGuard.handle))
{
return false;
}

HandleGuard duplicatedTokenGuard;

if (! DuplicateToken (primaryTokenGuard.handle,
SecurityImpersonation,
&duplicatedTokenGuard.handle))
{
return false;
}

GENERIC_MAPPING mapping { FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS };

MapGenericMask (&accessType, &mapping);
DWORD allowed = 0;
BOOL granted = false;
PRIVILEGE_SET set;
DWORD setSize = sizeof (set);

if (! AccessCheck (descriptorGuard.psecurityDescriptor,
duplicatedTokenGuard.handle,
accessType,
&mapping,
&set,
&setSize,
&allowed,
&granted))
{
return false;
}

return granted != FALSE;
}
} // namespace WindowsFileHelpers

//==============================================================================
Expand Down Expand Up @@ -199,17 +276,25 @@ bool File::isDirectory() const

bool File::hasWriteAccess() const
{
if (fullPath.isEmpty())
return true;
if (exists())
{
const auto attr = WindowsFileHelpers::getAtts (fullPath);

auto attr = WindowsFileHelpers::getAtts (fullPath);
return WindowsFileHelpers::hasFileAccess (*this, GENERIC_WRITE)
&& (attr == INVALID_FILE_ATTRIBUTES
|| (attr & FILE_ATTRIBUTE_DIRECTORY) != 0
|| (attr & FILE_ATTRIBUTE_READONLY) == 0);
}

if ((! isDirectory()) && fullPath.containsChar (getSeparatorChar()))
return getParentDirectory().hasWriteAccess();

// NB: According to MS, the FILE_ATTRIBUTE_READONLY attribute doesn't work for
// folders, and can be incorrectly set for some special folders, so we'll just say
// that folders are always writable.
return attr == INVALID_FILE_ATTRIBUTES
|| (attr & FILE_ATTRIBUTE_DIRECTORY) != 0
|| (attr & FILE_ATTRIBUTE_READONLY) == 0;
return false;
}

bool File::hasReadAccess() const
{
return WindowsFileHelpers::hasFileAccess (*this, GENERIC_READ);
}

bool File::setFileReadOnlyInternal (bool shouldBeReadOnly) const
Expand Down
9 changes: 5 additions & 4 deletions libs/juce7/source/modules/juce_core/network/juce_URL.cpp
Expand Up @@ -792,6 +792,11 @@ std::unique_ptr<InputStream> URL::createInputStream (const InputStreamOptions& o

std::unique_ptr<OutputStream> URL::createOutputStream() const
{
#if JUCE_ANDROID
if (auto stream = AndroidDocument::fromDocument (*this).createOutputStream())
return stream;
#endif

if (isLocalFile())
{
#if JUCE_IOS
Expand All @@ -802,11 +807,7 @@ std::unique_ptr<OutputStream> URL::createOutputStream() const
#endif
}

#if JUCE_ANDROID
return std::unique_ptr<OutputStream> (juce_CreateContentURIOutputStream (*this));
#else
return nullptr;
#endif
}

//==============================================================================
Expand Down
Expand Up @@ -72,8 +72,8 @@
// MSVC
#if JUCE_MSVC

#if _MSC_FULL_VER < 190024210 // VS2015
#error "JUCE requires Visual Studio 2015 Update 3 or later"
#if _MSC_FULL_VER < 191025017 // VS2017
#error "JUCE requires Visual Studio 2017 or later"
#endif

#ifndef JUCE_EXCEPTIONS_DISABLED
Expand Down
11 changes: 8 additions & 3 deletions libs/juce7/source/modules/juce_core/system/juce_StandardHeader.h
Expand Up @@ -27,9 +27,9 @@
See also SystemStats::getJUCEVersion() for a string version.
*/
#define JUCE_MAJOR_VERSION 6
#define JUCE_MINOR_VERSION 1
#define JUCE_BUILDNUMBER 6
#define JUCE_MAJOR_VERSION 7
#define JUCE_MINOR_VERSION 0
#define JUCE_BUILDNUMBER 1

/** Current JUCE version number.
Expand All @@ -41,6 +41,11 @@
*/
#define JUCE_VERSION ((JUCE_MAJOR_VERSION << 16) + (JUCE_MINOR_VERSION << 8) + JUCE_BUILDNUMBER)

#if ! DOXYGEN
#define JUCE_VERSION_ID \
volatile auto juceVersionId = "juce_version_" JUCE_STRINGIFY(JUCE_MAJOR_VERSION) "_" JUCE_STRINGIFY(JUCE_MINOR_VERSION) "_" JUCE_STRINGIFY(JUCE_BUILDNUMBER); \
ignoreUnused (juceVersionId);
#endif

//==============================================================================
#include <algorithm>
Expand Down
Expand Up @@ -81,7 +81,7 @@ String ChildProcess::readAllProcessOutput()
}


uint32 ChildProcess::getPID() const noexcept
int ChildProcess::getPID() const noexcept
{
return activeProcess != nullptr ? activeProcess->getPID() : 0;
}
Expand Down
Expand Up @@ -101,7 +101,7 @@ class JUCE_API ChildProcess
*/
bool kill();

uint32 getPID() const noexcept;
int getPID() const noexcept;

private:
//==============================================================================
Expand Down
Expand Up @@ -35,7 +35,7 @@
ID: juce_cryptography
vendor: juce
version: 6.1.6
version: 7.0.1
name: JUCE cryptography classes
description: Classes for various basic cryptography functions, including RSA, Blowfish, MD5, SHA, etc.
website: http://www.juce.com/juce
Expand Down
Expand Up @@ -35,7 +35,7 @@
ID: juce_data_structures
vendor: juce
version: 6.1.6
version: 7.0.1
name: JUCE data model helper classes
description: Classes for undo/redo management, and smart data structures.
website: http://www.juce.com/juce
Expand Down
4 changes: 2 additions & 2 deletions libs/juce7/source/modules/juce_dsp/juce_dsp.h
Expand Up @@ -35,7 +35,7 @@
ID: juce_dsp
vendor: juce
version: 6.1.6
version: 7.0.1
name: JUCE DSP classes
description: Classes for audio buffer manipulation, digital audio processing, filtering, oversampling, fast math functions etc.
website: http://www.juce.com/juce
Expand Down Expand Up @@ -93,7 +93,7 @@

#ifndef JUCE_VECTOR_CALLTYPE
// __vectorcall does not work on 64-bit due to internal compiler error in
// release mode in both VS2015 and VS2017. Re-enable when Microsoft fixes this
// release mode VS2017. Re-enable when Microsoft fixes this
#if _MSC_VER && JUCE_USE_SIMD && ! (defined(_M_X64) || defined(__amd64__))
#define JUCE_VECTOR_CALLTYPE __vectorcall
#else
Expand Down
Expand Up @@ -61,6 +61,8 @@ struct ChildProcessPingThread : public Thread,

int timeoutMs;

using AsyncUpdater::cancelPendingUpdate;

private:
Atomic<int> countdown;

Expand Down Expand Up @@ -97,6 +99,7 @@ struct ChildProcessCoordinator::Connection : public InterprocessConnection,

~Connection() override
{
cancelPendingUpdate();
stopThread (10000);
}

Expand Down Expand Up @@ -206,6 +209,7 @@ struct ChildProcessWorker::Connection : public InterprocessConnection,

~Connection() override
{
cancelPendingUpdate();
stopThread (10000);
disconnect();
}
Expand Down
2 changes: 1 addition & 1 deletion libs/juce7/source/modules/juce_events/juce_events.h
Expand Up @@ -32,7 +32,7 @@
ID: juce_events
vendor: juce
version: 6.1.6
version: 7.0.1
name: JUCE message and event handling classes
description: Classes for running an application's main event loop and sending/receiving messages, timers, etc.
website: http://www.juce.com/juce
Expand Down
Expand Up @@ -26,6 +26,8 @@ namespace juce
MessageManager::MessageManager() noexcept
: messageThreadId (Thread::getCurrentThreadId())
{
JUCE_VERSION_ID

if (JUCEApplicationBase::isStandaloneApp())
Thread::setCurrentThreadName ("JUCE Message Thread");
}
Expand Down
2 changes: 1 addition & 1 deletion libs/juce7/source/modules/juce_graphics/juce_graphics.h
Expand Up @@ -35,7 +35,7 @@
ID: juce_graphics
vendor: juce
version: 6.1.6
version: 7.0.1
name: JUCE graphics classes
description: Classes for 2D vector graphics, image loading/saving, font handling, etc.
website: http://www.juce.com/juce
Expand Down
Expand Up @@ -66,6 +66,9 @@ class JUCE_API AccessibilityTextInterface
/** Returns a section of text. */
virtual String getText (Range<int> range) const = 0;

/** Returns the full text. */
String getAllText() const { return getText ({ 0, getTotalNumCharacters() }); }

/** Replaces the text with a new string. */
virtual void setText (const String& newText) = 0;

Expand Down
Expand Up @@ -63,7 +63,6 @@ AccessibilityHandler::AccessibilityHandler (Component& comp,
interfaces (std::move (interfacesIn)),
nativeImpl (createNativeImpl (*this))
{
notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::elementCreated);
}

AccessibilityHandler::~AccessibilityHandler()
Expand Down
Expand Up @@ -327,7 +327,7 @@ struct Component::ComponentHelpers
static bool hitTest (Component& comp, Point<float> localPoint)
{
const auto intPoint = localPoint.roundToInt();
return Rectangle<int> { comp.getWidth(), comp.getHeight() }.toFloat().contains (localPoint)
return Rectangle<int> { comp.getWidth(), comp.getHeight() }.contains (intPoint)
&& comp.hitTest (intPoint.x, intPoint.y);
}

Expand Down Expand Up @@ -2943,6 +2943,11 @@ void Component::takeKeyboardFocus (FocusChangeType cause)
return;

WeakReference<Component> componentLosingFocus (currentlyFocusedComponent);

if (auto* losingFocus = componentLosingFocus.get())
if (auto* otherPeer = losingFocus->getPeer())
otherPeer->closeInputMethodContext();

currentlyFocusedComponent = this;

Desktop::getInstance().triggerFocusCallback();
Expand Down Expand Up @@ -3008,6 +3013,9 @@ void Component::giveAwayKeyboardFocusInternal (bool sendFocusLossEvent)
{
if (auto* componentLosingFocus = currentlyFocusedComponent)
{
if (auto* otherPeer = componentLosingFocus->getPeer())
otherPeer->closeInputMethodContext();

currentlyFocusedComponent = nullptr;

if (sendFocusLossEvent && componentLosingFocus != nullptr)
Expand Down Expand Up @@ -3300,6 +3308,18 @@ AccessibilityHandler* Component::getAccessibilityHandler()
|| accessibilityHandler->getTypeIndex() != std::type_index (typeid (*this)))
{
accessibilityHandler = createAccessibilityHandler();

// On Android, notifying that an element was created can cause the system to request
// the accessibility node info for the new element. If we're not careful, this will lead
// to recursive calls, as each time an element is created, new node info will be requested,
// causing an element to be created, causing a new info request...
// By assigning the accessibility handler before notifying the system that an element was
// created, the if() predicate above should evaluate to false on recursive calls,
// terminating the recursion.
if (accessibilityHandler != nullptr)
notifyAccessibilityEventInternal (*accessibilityHandler, InternalAccessibilityEvent::elementCreated);
else
jassertfalse; // createAccessibilityHandler must return non-null
}

return accessibilityHandler.get();
Expand Down
10 changes: 8 additions & 2 deletions libs/juce7/source/modules/juce_gui_basics/juce_gui_basics.cpp
Expand Up @@ -45,6 +45,8 @@

#include "juce_gui_basics.h"

#include <cctype>

//==============================================================================
#if JUCE_MAC
#import <WebKit/WebKit.h>
Expand Down Expand Up @@ -259,7 +261,7 @@ namespace juce
#include "native/juce_MultiTouchMapper.h"
#endif

#if JUCE_ANDROID || JUCE_WINDOWS
#if JUCE_ANDROID || JUCE_WINDOWS || JUCE_UNIT_TESTS
#include "native/accessibility/juce_AccessibilityTextHelpers.h"
#endif

Expand Down Expand Up @@ -314,9 +316,9 @@ namespace juce
#include "native/juce_linux_FileChooser.cpp"

#elif JUCE_ANDROID
#include "juce_core/files/juce_common_MimeTypes.h"
#include "native/accessibility/juce_android_Accessibility.cpp"
#include "native/juce_android_Windowing.cpp"
#include "native/juce_common_MimeTypes.cpp"
#include "native/juce_android_FileChooser.cpp"

#if JUCE_CONTENT_SHARING
Expand Down Expand Up @@ -411,3 +413,7 @@ bool juce::isWindowOnCurrentVirtualDesktop (void* x)

// Depends on types defined in platform-specific windowing files
#include "mouse/juce_MouseCursor.cpp"

#if JUCE_UNIT_TESTS
#include "native/accessibility/juce_AccessibilityTextHelpers_test.cpp"
#endif
Expand Up @@ -35,7 +35,7 @@
ID: juce_gui_basics
vendor: juce
version: 6.1.6
version: 7.0.1
name: JUCE GUI core classes
description: Basic user-interface components and related classes.
website: http://www.juce.com/juce
Expand Down
15 changes: 13 additions & 2 deletions libs/juce7/source/modules/juce_gui_basics/layout/juce_Grid.cpp
Expand Up @@ -597,6 +597,11 @@ struct Grid::AutoPlacement
return referenceCell;
}

void updateMaxCrossDimensionFromAutoPlacementItem (int columnSpan, int rowSpan)
{
highestCrossDimension = jmax (highestCrossDimension, 1 + getCrossDimension ({ columnSpan, rowSpan }));
}

private:
struct SortableCell
{
Expand Down Expand Up @@ -642,9 +647,10 @@ struct Grid::AutoPlacement

bool isOutOfBounds (Cell cell, int columnSpan, int rowSpan) const
{
const auto crossSpan = columnFirst ? rowSpan : columnSpan;
const auto highestIndexOfCell = getCrossDimension (cell) + getCrossDimension ({ columnSpan, rowSpan });
const auto highestIndexOfGrid = getHighestCrossDimension();

return (getCrossDimension (cell) + crossSpan) > getHighestCrossDimension();
return highestIndexOfGrid < highestIndexOfCell;
}

int getHighestCrossDimension() const
Expand Down Expand Up @@ -807,6 +813,11 @@ struct Grid::AutoPlacement
}
}

// https://www.w3.org/TR/css-grid-1/#auto-placement-algo step 3.3
for (auto* item : sortedItems)
if (hasAutoPlacement (*item))
plane.updateMaxCrossDimensionFromAutoPlacementItem (getSpanFromAuto (item->column), getSpanFromAuto (item->row));

lastInsertionCell = { 1, 1 };

for (auto* item : sortedItems)
Expand Down
27 changes: 22 additions & 5 deletions libs/juce7/source/modules/juce_gui_basics/layout/juce_Viewport.cpp
Expand Up @@ -61,6 +61,12 @@ struct Viewport::DragToScrollListener : private MouseListener,
Desktop::getInstance().removeGlobalMouseListener (this);
}

void stopOngoingAnimation()
{
offsetX.setPosition (offsetX.getPosition());
offsetY.setPosition (offsetY.getPosition());
}

void positionChanged (ViewportDragPosition&, double) override
{
viewport.setViewPosition (originalViewPos - Point<int> ((int) offsetX.getPosition(),
Expand Down Expand Up @@ -119,9 +125,11 @@ struct Viewport::DragToScrollListener : private MouseListener,

void endDragAndClearGlobalMouseListener()
{
offsetX.endDrag();
offsetY.endDrag();
isDragging = false;
if (std::exchange (isDragging, false) == true)
{
offsetX.endDrag();
offsetY.endDrag();
}

viewport.contentHolder.addMouseListener (this, true);
Desktop::getInstance().removeGlobalMouseListener (this);
Expand Down Expand Up @@ -229,6 +237,8 @@ void Viewport::recreateScrollbars()

getVerticalScrollBar().addListener (this);
getHorizontalScrollBar().addListener (this);
getVerticalScrollBar().addMouseListener (this, true);
getHorizontalScrollBar().addMouseListener (this, true);

resized();
}
Expand Down Expand Up @@ -531,8 +541,15 @@ void Viewport::scrollBarMoved (ScrollBar* scrollBarThatHasMoved, double newRange

void Viewport::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
{
if (! useMouseWheelMoveIfNeeded (e, wheel))
Component::mouseWheelMove (e, wheel);
if (e.eventComponent == this)
if (! useMouseWheelMoveIfNeeded (e, wheel))
Component::mouseWheelMove (e, wheel);
}

void Viewport::mouseDown (const MouseEvent& e)
{
if (e.eventComponent == horizontalScrollBar.get() || e.eventComponent == verticalScrollBar.get())
dragToScrollListener->stopOngoingAnimation();
}

static int rescaleMouseWheelDistance (float distance, int singleStepSize) noexcept
Expand Down
12 changes: 11 additions & 1 deletion libs/juce7/source/modules/juce_gui_basics/layout/juce_Viewport.h
Expand Up @@ -318,6 +318,8 @@ class JUCE_API Viewport : public Component,
/** @internal */
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
/** @internal */
void mouseDown (const MouseEvent& e) override;
/** @internal */
bool keyPressed (const KeyPress&) override;
/** @internal */
void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override;
Expand All @@ -337,8 +339,16 @@ class JUCE_API Viewport : public Component,

private:
//==============================================================================
class AccessibilityIgnoredComponent : public Component
{
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return createIgnoredAccessibilityHandler (*this);
}
};

std::unique_ptr<ScrollBar> verticalScrollBar, horizontalScrollBar;
Component contentHolder;
AccessibilityIgnoredComponent contentHolder;
WeakReference<Component> contentComp;
Rectangle<int> lastVisibleArea;
int scrollBarThickness = 0;
Expand Down
Expand Up @@ -87,7 +87,7 @@ struct HeaderItemComponent : public PopupMenu::CustomComponent

std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return nullptr;
return createIgnoredAccessibilityHandler (*this);
}

const Options& options;
Expand Down Expand Up @@ -275,7 +275,8 @@ struct ItemComponent : public Component

std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return item.isSeparator ? nullptr : std::make_unique<ItemAccessibilityHandler> (*this);
return item.isSeparator ? createIgnoredAccessibilityHandler (*this)
: std::make_unique<ItemAccessibilityHandler> (*this);
}

//==============================================================================
Expand Down
Expand Up @@ -59,6 +59,7 @@ class DragAndDropContainer::DragImageComponent : public Component,
startTimer (200);

setInterceptsMouseClicks (false, false);
setWantsKeyboardFocus (true);
setAlwaysOnTop (true);
}

Expand Down Expand Up @@ -200,7 +201,12 @@ class DragAndDropContainer::DragImageComponent : public Component,
{
if (key == KeyPress::escapeKey)
{
dismissWithAnimation (true);
const auto wasVisible = isVisible();
setVisible (false);

if (wasVisible)
dismissWithAnimation (true);

deleteSelf();
return true;
}
Expand Down Expand Up @@ -466,8 +472,7 @@ void DragAndDropContainer::startDragging (const var& sourceDescription,
dragImageComponent->setOpaque (true);

dragImageComponent->addToDesktop (ComponentPeer::windowIgnoresMouseClicks
| ComponentPeer::windowIsTemporary
| ComponentPeer::windowIgnoresKeyPresses);
| ComponentPeer::windowIsTemporary);
}
else
{
Expand All @@ -484,6 +489,7 @@ void DragAndDropContainer::startDragging (const var& sourceDescription,

dragImageComponent->sourceDetails.localPosition = sourceComponent->getLocalPoint (nullptr, lastMouseDown);
dragImageComponent->updateLocation (false, lastMouseDown);
dragImageComponent->grabKeyboardFocus();

#if JUCE_WINDOWS
// Under heavy load, the layered window's paint callback can often be lost by the OS,
Expand Down
Expand Up @@ -26,8 +26,58 @@
namespace juce
{

namespace AccessibilityTextHelpers
struct AccessibilityTextHelpers
{
/* Wraps a CharPtr into a stdlib-compatible iterator.
MSVC's std::reverse_iterator requires the wrapped iterator to be default constructible
when building in C++20 mode, but I don't really want to add public default constructors to
the CharPtr types. Instead, we add a very basic default constructor here which sets the
wrapped CharPtr to nullptr.
*/
template <typename CharPtr>
class CharPtrIteratorAdapter
{
public:
using difference_type = int;
using value_type = decltype (*std::declval<CharPtr>());
using pointer = value_type*;
using reference = value_type;
using iterator_category = std::bidirectional_iterator_tag;

CharPtrIteratorAdapter() = default;
constexpr explicit CharPtrIteratorAdapter (CharPtr arg) : ptr (arg) {}

constexpr auto operator*() const { return *ptr; }

constexpr CharPtrIteratorAdapter& operator++()
{
++ptr;
return *this;
}

constexpr CharPtrIteratorAdapter& operator--()
{
--ptr;
return *this;
}

constexpr bool operator== (const CharPtrIteratorAdapter& other) const { return ptr == other.ptr; }
constexpr bool operator!= (const CharPtrIteratorAdapter& other) const { return ptr != other.ptr; }

constexpr auto operator+ (difference_type offset) const { return CharPtrIteratorAdapter { ptr + offset }; }
constexpr auto operator- (difference_type offset) const { return CharPtrIteratorAdapter { ptr - offset }; }

private:
CharPtr ptr { {} };
};

template <typename CharPtr>
static auto makeCharPtrIteratorAdapter (CharPtr ptr)
{
return CharPtrIteratorAdapter<CharPtr> { ptr };
}

enum class BoundaryType
{
character,
Expand All @@ -42,60 +92,177 @@ namespace AccessibilityTextHelpers
backwards
};

static int findTextBoundary (const AccessibilityTextInterface& textInterface,
int currentPosition,
BoundaryType boundary,
Direction direction)
enum class ExtendSelection
{
const auto numCharacters = textInterface.getTotalNumCharacters();
const auto isForwards = (direction == Direction::forwards);
const auto offsetWithDirection = [isForwards] (auto num) { return isForwards ? num : -num; };
no,
yes
};

switch (boundary)
/* Indicates whether a function may return the current text position, in the case that the
position already falls on a text unit boundary.
*/
enum class IncludeThisBoundary
{
no, //< Always search for the following boundary, even if the current position falls on a boundary
yes //< Return the current position if it falls on a boundary
};

/* Indicates whether a word boundary should include any whitespaces that follow the
non-whitespace characters.
*/
enum class IncludeWhitespaceAfterWords
{
no, //< The word ends on the first whitespace character
yes //< The word ends after the last whitespace character
};

/* Like std::distance, but always does an O(N) count rather than an O(1) count, and doesn't
require the iterators to have any member type aliases.
*/
template <typename Iter>
static int countDifference (Iter from, Iter to)
{
int distance = 0;

while (from != to)
{
case BoundaryType::character:
return jlimit (0, numCharacters, currentPosition + offsetWithDirection (1));
++from;
++distance;
}

case BoundaryType::word:
case BoundaryType::line:
return distance;
}

/* Returns the number of characters between ptr and the next word end in a specific
direction.
If ptr is inside a word, the result will be the distance to the end of the same
word.
*/
template <typename CharPtr>
static int findNextWordEndOffset (CharPtr beginIn,
CharPtr endIn,
CharPtr ptrIn,
Direction direction,
IncludeThisBoundary includeBoundary,
IncludeWhitespaceAfterWords includeWhitespace)
{
const auto begin = makeCharPtrIteratorAdapter (beginIn);
const auto end = makeCharPtrIteratorAdapter (endIn);
const auto ptr = makeCharPtrIteratorAdapter (ptrIn);

const auto move = [&] (auto b, auto e, auto iter)
{
const auto isSpace = [] (juce_wchar c) { return CharacterFunctions::isWhitespace (c); };

const auto start = [&]
{
const auto text = [&]() -> String
{
if (isForwards)
return textInterface.getText ({ currentPosition, textInterface.getTotalNumCharacters() });
if (iter == b && includeBoundary == IncludeThisBoundary::yes)
return b;

const auto str = textInterface.getText ({ 0, currentPosition });
const auto nudged = iter - (iter != b && includeBoundary == IncludeThisBoundary::yes ? 1 : 0);

auto start = str.getCharPointer();
auto end = start.findTerminatingNull();
const auto size = getAddressDifference (end.getAddress(), start.getAddress());
return includeWhitespace == IncludeWhitespaceAfterWords::yes
? std::find_if (nudged, e, isSpace)
: std::find_if_not (nudged, e, isSpace);
}();

String reversed;
const auto found = includeWhitespace == IncludeWhitespaceAfterWords::yes
? std::find_if_not (start, e, isSpace)
: std::find_if (start, e, isSpace);

if (size > 0)
{
reversed.preallocateBytes ((size_t) size);
return countDifference (iter, found);
};

auto destPtr = reversed.getCharPointer();
return direction == Direction::forwards ? move (begin, end, ptr)
: -move (std::make_reverse_iterator (end),
std::make_reverse_iterator (begin),
std::make_reverse_iterator (ptr));
}

for (;;)
{
destPtr.write (*--end);
/* Returns the number of characters between ptr and the beginning of the next line in a
specific direction.
*/
template <typename CharPtr>
static int findNextLineOffset (CharPtr beginIn,
CharPtr endIn,
CharPtr ptrIn,
Direction direction,
IncludeThisBoundary includeBoundary)
{
const auto begin = makeCharPtrIteratorAdapter (beginIn);
const auto end = makeCharPtrIteratorAdapter (endIn);
const auto ptr = makeCharPtrIteratorAdapter (ptrIn);

if (end == start)
break;
}
const auto findNewline = [] (auto from, auto to) { return std::find (from, to, juce_wchar { '\n' }); };

destPtr.writeNull();
}
if (direction == Direction::forwards)
{
if (ptr != begin && includeBoundary == IncludeThisBoundary::yes && *(ptr - 1) == '\n')
return 0;

return reversed;
}();
const auto newline = findNewline (ptr, end);
return countDifference (ptr, newline) + (newline == end ? 0 : 1);
}

auto tokens = (boundary == BoundaryType::line ? StringArray::fromLines (text)
: StringArray::fromTokens (text, false));
const auto rbegin = std::make_reverse_iterator (ptr);
const auto rend = std::make_reverse_iterator (begin);

return currentPosition + offsetWithDirection (tokens[0].length());
return -countDifference (rbegin, findNewline (rbegin + (rbegin == rend || includeBoundary == IncludeThisBoundary::yes ? 0 : 1), rend));
}

/* Unfortunately, the method of computing end-points of text units depends on context, and on
the current platform.
Some examples of different behaviour:
- On Android, updating the cursor/selection always searches for the next text unit boundary;
but on Windows, ExpandToEnclosingUnit() should not move the starting point of the
selection if it already at a unit boundary. This means that we need both inclusive and
exclusive methods for finding the next text boundary.
- On Android, moving the cursor by 'words' should move to the first space following a
non-space character in the requested direction. On Windows, a 'word' includes trailing
whitespace, but not preceding whitespace. This means that we need a way of specifying
whether whitespace should be included when navigating by words.
*/
static int findTextBoundary (const AccessibilityTextInterface& textInterface,
int currentPosition,
BoundaryType boundary,
Direction direction,
IncludeThisBoundary includeBoundary,
IncludeWhitespaceAfterWords includeWhitespace)
{
const auto numCharacters = textInterface.getTotalNumCharacters();
const auto isForwards = (direction == Direction::forwards);
const auto currentClamped = jlimit (0, numCharacters, currentPosition);

switch (boundary)
{
case BoundaryType::character:
{
const auto offset = includeBoundary == IncludeThisBoundary::yes ? 0
: (isForwards ? 1 : -1);
return jlimit (0, numCharacters, currentPosition + offset);
}

case BoundaryType::word:
{
const auto str = textInterface.getText ({ 0, numCharacters });
return currentClamped + findNextWordEndOffset (str.begin(),
str.end(),
str.begin() + currentClamped,
direction,
includeBoundary,
includeWhitespace);
}

case BoundaryType::line:
{
const auto str = textInterface.getText ({ 0, numCharacters });
return currentClamped + findNextLineOffset (str.begin(),
str.end(),
str.begin() + currentClamped,
direction,
includeBoundary);
}

case BoundaryType::document:
Expand All @@ -105,6 +272,31 @@ namespace AccessibilityTextHelpers
jassertfalse;
return -1;
}
}

/* Adjusts the current text selection range, using an algorithm appropriate for cursor movement
on Android.
*/
static Range<int> findNewSelectionRangeAndroid (const AccessibilityTextInterface& textInterface,
BoundaryType boundaryType,
ExtendSelection extend,
Direction direction)
{
const auto oldPos = textInterface.getTextInsertionOffset();
const auto cursorPos = findTextBoundary (textInterface,
oldPos,
boundaryType,
direction,
IncludeThisBoundary::no,
IncludeWhitespaceAfterWords::no);

if (extend == ExtendSelection::no)
return { cursorPos, cursorPos };

const auto currentSelection = textInterface.getSelection();
const auto start = currentSelection.getStart();
const auto end = currentSelection.getEnd();
return Range<int>::between (cursorPos, oldPos == start ? end : start);
}
};

} // namespace juce

Large diffs are not rendered by default.

Expand Up @@ -206,10 +206,14 @@ void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventTyp
{
if (auto* valueInterface = getValueInterface())
{
VARIANT newValue;
VariantHelpers::setString (valueInterface->getCurrentValueAsString(), &newValue);
const auto propertyType = getRole() == AccessibilityRole::slider ? UIA_RangeValueValuePropertyId
: UIA_ValueValuePropertyId;

sendAccessibilityPropertyChangedEvent (*this, UIA_ValueValuePropertyId, newValue);
const auto value = getRole() == AccessibilityRole::slider
? VariantHelpers::getWithValue (valueInterface->getCurrentValue())
: VariantHelpers::getWithValue (valueInterface->getCurrentValueAsString());

sendAccessibilityPropertyChangedEvent (*this, propertyType, value);
}

return;
Expand Down
Expand Up @@ -28,6 +28,17 @@ namespace juce

namespace VariantHelpers
{
namespace Detail
{
template <typename Fn, typename ValueType>
inline VARIANT getWithValueGeneric (Fn&& setter, ValueType value)
{
VARIANT result{};
setter (value, &result);
return result;
}
}

inline void clear (VARIANT* variant)
{
variant->vt = VT_EMPTY;
Expand Down Expand Up @@ -56,6 +67,9 @@ namespace VariantHelpers
variant->vt = VT_R8;
variant->dblVal = value;
}

inline VARIANT getWithValue (double value) { return Detail::getWithValueGeneric (&setDouble, value); }
inline VARIANT getWithValue (const String& value) { return Detail::getWithValueGeneric (&setString, value); }
}

inline JUCE_COMRESULT addHandlersToArray (const std::vector<const AccessibilityHandler*>& handlers, SAFEARRAY** pRetVal)
Expand Down
Expand Up @@ -246,19 +246,22 @@ class UIATextProvider : public UIAProviderBase,

if (auto* textInterface = owner->getHandler().getTextInterface())
{
const auto boundaryType = getBoundaryType (unit);

const auto start = unit == ComTypes::TextUnit::TextUnit_Character
? selectionRange.getStart()
: AccessibilityTextHelpers::findTextBoundary (*textInterface,
selectionRange.getStart(),
boundaryType,
AccessibilityTextHelpers::Direction::backwards);
using ATH = AccessibilityTextHelpers;

const auto end = AccessibilityTextHelpers::findTextBoundary (*textInterface,
start,
boundaryType,
AccessibilityTextHelpers::Direction::forwards);
const auto boundaryType = getBoundaryType (unit);
const auto start = ATH::findTextBoundary (*textInterface,
selectionRange.getStart(),
boundaryType,
ATH::Direction::backwards,
ATH::IncludeThisBoundary::yes,
ATH::IncludeWhitespaceAfterWords::no);

const auto end = ATH::findTextBoundary (*textInterface,
start,
boundaryType,
ATH::Direction::forwards,
ATH::IncludeThisBoundary::no,
ATH::IncludeWhitespaceAfterWords::yes);

selectionRange = Range<int> (start, end);

Expand Down Expand Up @@ -413,19 +416,39 @@ class UIATextProvider : public UIAProviderBase,

JUCE_COMRESULT Move (ComTypes::TextUnit unit, int count, int* pRetVal) override
{
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface&)
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
{
if (count > 0)
{
MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_End, unit, count, pRetVal);
MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_Start, unit, count, pRetVal);
}
else if (count < 0)
using ATH = AccessibilityTextHelpers;

const auto boundaryType = getBoundaryType (unit);
const auto previousUnitBoundary = ATH::findTextBoundary (textInterface,
selectionRange.getStart(),
boundaryType,
ATH::Direction::backwards,
ATH::IncludeThisBoundary::yes,
ATH::IncludeWhitespaceAfterWords::no);

auto numMoved = 0;
auto movedEndpoint = previousUnitBoundary;

for (; numMoved < std::abs (count); ++numMoved)
{
MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_Start, unit, count, pRetVal);
MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_End, unit, count, pRetVal);
const auto nextEndpoint = ATH::findTextBoundary (textInterface,
movedEndpoint,
boundaryType,
count > 0 ? ATH::Direction::forwards : ATH::Direction::backwards,
ATH::IncludeThisBoundary::no,
count > 0 ? ATH::IncludeWhitespaceAfterWords::yes : ATH::IncludeWhitespaceAfterWords::no);

if (nextEndpoint == movedEndpoint)
break;

movedEndpoint = nextEndpoint;
}

*pRetVal = numMoved;

ExpandToEnclosingUnit (unit);
return S_OK;
});
}
Expand Down Expand Up @@ -463,34 +486,37 @@ class UIATextProvider : public UIAProviderBase,
if (count == 0 || textInterface.getTotalNumCharacters() == 0)
return S_OK;

auto endpointToMove = (endpoint == ComTypes::TextPatternRangeEndpoint_Start ? selectionRange.getStart()
: selectionRange.getEnd());
const auto endpointToMove = (endpoint == ComTypes::TextPatternRangeEndpoint_Start ? selectionRange.getStart()
: selectionRange.getEnd());

const auto direction = (count > 0 ? AccessibilityTextHelpers::Direction::forwards
: AccessibilityTextHelpers::Direction::backwards);
using ATH = AccessibilityTextHelpers;

const auto boundaryType = getBoundaryType (unit);
const auto direction = (count > 0 ? ATH::Direction::forwards
: ATH::Direction::backwards);

// handle case where endpoint is on a boundary
if (AccessibilityTextHelpers::findTextBoundary (textInterface, endpointToMove, boundaryType, direction) == endpointToMove)
endpointToMove += (direction == AccessibilityTextHelpers::Direction::forwards ? 1 : -1);
const auto boundaryType = getBoundaryType (unit);
auto movedEndpoint = endpointToMove;

int numMoved;
for (numMoved = 0; numMoved < std::abs (count); ++numMoved)
int numMoved = 0;
for (; numMoved < std::abs (count); ++numMoved)
{
auto nextEndpoint = AccessibilityTextHelpers::findTextBoundary (textInterface,
endpointToMove,
boundaryType,
direction);

if (nextEndpoint == endpointToMove)
auto nextEndpoint = ATH::findTextBoundary (textInterface,
movedEndpoint,
boundaryType,
direction,
ATH::IncludeThisBoundary::no,
direction == ATH::Direction::forwards ? ATH::IncludeWhitespaceAfterWords::yes
: ATH::IncludeWhitespaceAfterWords::no);

if (nextEndpoint == movedEndpoint)
break;

endpointToMove = nextEndpoint;
movedEndpoint = nextEndpoint;
}

*pRetVal = numMoved;
setEndpointChecked (endpoint, endpointToMove);

setEndpointChecked (endpoint, movedEndpoint);

return S_OK;
});
Expand Down
Expand Up @@ -273,6 +273,8 @@ std::shared_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser
#if JUCE_MODAL_LOOPS_PERMITTED
return std::make_shared<Native> (owner, flags);
#else
ignoreUnused (owner);
ignoreUnused (flags);
return nullptr;
#endif
}
Expand Down

Large diffs are not rendered by default.

Expand Up @@ -40,6 +40,29 @@ - (float)deviceDeltaY;
extern CheckEventBlockedByModalComps isEventBlockedByModalComps;

//==============================================================================
static void resetTrackingArea (NSView* view)
{
const auto trackingAreas = [view trackingAreas];

jassert ([trackingAreas count] <= 1);

for (NSTrackingArea* area in trackingAreas)
[view removeTrackingArea: area];

const auto options = NSTrackingMouseEnteredAndExited
| NSTrackingMouseMoved
| NSTrackingEnabledDuringMouseDrag
| NSTrackingActiveAlways
| NSTrackingInVisibleRect;

const NSUniquePtr<NSTrackingArea> trackingArea { [[NSTrackingArea alloc] initWithRect: [view bounds]
options: options
owner: view
userInfo: nil] };

[view addTrackingArea: trackingArea.get()];
}

static constexpr int translateVirtualToAsciiKeyCode (int keyCode) noexcept
{
switch (keyCode)
Expand Down Expand Up @@ -119,16 +142,7 @@ static constexpr int translateVirtualToAsciiKeyCode (int keyCode) noexcept

[view registerForDraggedTypes: getSupportedDragTypes()];

const auto options = NSTrackingMouseEnteredAndExited
| NSTrackingMouseMoved
| NSTrackingEnabledDuringMouseDrag
| NSTrackingActiveAlways
| NSTrackingInVisibleRect;
const NSUniquePtr<NSTrackingArea> trackingArea { [[NSTrackingArea alloc] initWithRect: r
options: options
owner: view
userInfo: nil] };
[view addTrackingArea: trackingArea.get()];
resetTrackingArea (view);

notificationCenter = [NSNotificationCenter defaultCenter];

Expand All @@ -139,7 +153,11 @@ static constexpr int translateVirtualToAsciiKeyCode (int keyCode) noexcept

[view setPostsFrameChangedNotifications: YES];

#if USE_COREGRAPHICS_RENDERING
#if USE_COREGRAPHICS_RENDERING
#if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
if (@available (macOS 10.14, *))
metalRenderer = std::make_unique<CoreGraphicsMetalLayerRenderer<NSView>> (view, getComponent());
#endif
if ((windowStyleFlags & ComponentPeer::windowRequiresSynchronousCoreGraphicsRendering) == 0)
{
if (@available (macOS 10.8, *))
Expand All @@ -148,7 +166,7 @@ static constexpr int translateVirtualToAsciiKeyCode (int keyCode) noexcept
[[view layer] setDrawsAsynchronously: YES];
}
}
#endif
#endif

createCVDisplayLink();

Expand Down Expand Up @@ -348,7 +366,10 @@ void setBounds (const Rectangle<int>& newBounds, bool) override
}

if (oldViewSize.width != r.size.width || oldViewSize.height != r.size.height)
{
numFramesToSkipMetalRenderer = 5;
[view setNeedsDisplay: true];
}
}

Rectangle<int> getBounds (const bool global) const
Expand Down Expand Up @@ -1062,54 +1083,41 @@ void setNeedsDisplayRectangles()
if (msSinceLastRepaint < minimumRepaintInterval && shouldThrottleRepaint())
return;

#if USE_COREGRAPHICS_RENDERING && JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
// We require macOS 10.14 to use the Metal layer renderer
if (@available (macOS 10.14, *))
if (metalRenderer != nullptr)
{
const auto& comp = getComponent();
const auto compBounds = getComponent().getLocalBounds().toFloat();

// If we are resizing we need to fall back to synchronous drawing to avoid artefacts
if (areAnyWindowsInLiveResize())
if ([window inLiveResize] || numFramesToSkipMetalRenderer > 0)
{
if (metalRenderer != nullptr)
if (metalRenderer->isAttachedToView (view))
{
metalRenderer.reset();
view.wantsLayer = NO;
view.layer = nil;
deferredRepaints = comp.getLocalBounds().toFloat();
metalRenderer->detach();
deferredRepaints = compBounds;
}

if (numFramesToSkipMetalRenderer > 0)
--numFramesToSkipMetalRenderer;
}
else
{
if (metalRenderer == nullptr)
if (! metalRenderer->isAttachedToView (view))
{
view.wantsLayer = YES;
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
view.layerContentsPlacement = NSViewLayerContentsPlacementTopLeft;
view.layer = [CAMetalLayer layer];
metalRenderer = std::make_unique<CoreGraphicsMetalLayerRenderer> ((CAMetalLayer*) view.layer, getComponent());
deferredRepaints = comp.getLocalBounds().toFloat();
metalRenderer->attach (view, getComponent());
deferredRepaints = compBounds;
}
}
}
#endif

auto dispatchRectangles = [this] ()
auto dispatchRectangles = [this]
{
#if USE_COREGRAPHICS_RENDERING
if (@available (macOS 10.14, *))
{
if (metalRenderer != nullptr)
{
return metalRenderer->drawRectangleList ((CAMetalLayer*) view.layer,
(float) [[view window] backingScaleFactor],
view.frame,
getComponent(),
[this] (CGContextRef ctx, CGRect r) { drawRectWithContext (ctx, r); },
deferredRepaints);
}
}
#endif
if (metalRenderer != nullptr && metalRenderer->isAttachedToView (view))
return metalRenderer->drawRectangleList (view,
(float) [[view window] backingScaleFactor],
view.frame,
getComponent(),
[this] (CGContextRef ctx, CGRect r) { drawRectWithContext (ctx, r); },
deferredRepaints);

for (auto& i : deferredRepaints)
[view setNeedsDisplayInRect: makeNSRect (i)];
Expand Down Expand Up @@ -1881,9 +1889,8 @@ void createCVDisplayLink()
CVDisplayLinkRef displayLink = nullptr;
dispatch_source_t displaySource = nullptr;

#if USE_COREGRAPHICS_RENDERING
std::unique_ptr<CoreGraphicsMetalLayerRenderer> metalRenderer;
#endif
int numFramesToSkipMetalRenderer = 0;
std::unique_ptr<CoreGraphicsMetalLayerRenderer<NSView>> metalRenderer;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewComponentPeer)
};
Expand Down Expand Up @@ -1930,6 +1937,7 @@ static id getAccessibleChild (id self)
{
addMethod (@selector (isOpaque), isOpaque);
addMethod (@selector (drawRect:), drawRect);
addMethod (@selector (updateTrackingAreas), updateTrackingAreas);
addMethod (@selector (mouseDown:), mouseDown);
addMethod (@selector (mouseUp:), mouseUp);
addMethod (@selector (mouseDragged:), mouseDragged);
Expand Down Expand Up @@ -2013,6 +2021,13 @@ static id getAccessibleChild (id self)
}

private:
static void updateTrackingAreas (id self, SEL)
{
sendSuperclassMessage<void> (self, @selector (updateTrackingAreas));

resetTrackingArea (static_cast<NSView*> (self));
}

static void mouseDown (id self, SEL s, NSEvent* ev)
{
if (JUCEApplicationBase::isStandaloneApp())
Expand Down
Expand Up @@ -1264,93 +1264,6 @@ __CRT_UUID_DECL (juce::ITipInvocation, 0x37c994e7, 0x432b, 0x4834, 0xa2, 0xf7, 0
namespace juce
{

struct OnScreenKeyboard : public DeletedAtShutdown,
private Timer
{
void activate()
{
shouldBeActive = true;
startTimer (10);
}

void deactivate()
{
shouldBeActive = false;
startTimer (10);
}

JUCE_DECLARE_SINGLETON_SINGLETHREADED (OnScreenKeyboard, false)

private:
OnScreenKeyboard()
{
tipInvocation.CoCreateInstance (ITipInvocation::getCLSID(), CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER);
}

~OnScreenKeyboard() override
{
clearSingletonInstance();
}

void timerCallback() override
{
stopTimer();

if (reentrant || tipInvocation == nullptr)
return;

const ScopedValueSetter<bool> setter (reentrant, true, false);

auto isActive = isKeyboardVisible();

if (isActive != shouldBeActive)
{
if (! isActive)
{
tipInvocation->Toggle (GetDesktopWindow());
}
else
{
if (auto hwnd = FindWindow (L"IPTip_Main_Window", nullptr))
PostMessage (hwnd, WM_SYSCOMMAND, (int) SC_CLOSE, 0);
}
}
}

bool isVisible()
{
if (auto hwnd = FindWindowEx (nullptr, nullptr, L"ApplicationFrameWindow", nullptr))
return FindWindowEx (hwnd, nullptr, L"Windows.UI.Core.CoreWindow", L"Microsoft Text Input Application") != nullptr;

return false;
}

bool isVisibleLegacy()
{
if (auto hwnd = FindWindow (L"IPTip_Main_Window", nullptr))
{
auto style = GetWindowLong (hwnd, GWL_STYLE);
return (style & WS_DISABLED) == 0 && (style & WS_VISIBLE) != 0;
}

return false;
}

bool isKeyboardVisible()
{
if (isVisible())
return true;

// isVisible() may fail on Win10 versions < 1709 so try the old method too
return isVisibleLegacy();
}

bool shouldBeActive = false, reentrant = false;
ComSmartPtr<ITipInvocation> tipInvocation;
};

JUCE_IMPLEMENT_SINGLETON (OnScreenKeyboard)

//==============================================================================
struct HSTRING_PRIVATE;
typedef HSTRING_PRIVATE* HSTRING;
Expand Down Expand Up @@ -1430,30 +1343,6 @@ struct UWPUIViewSettings
}
}

bool isTabletModeActivatedForWindow (::HWND hWnd) const
{
if (viewSettingsInterop == nullptr)
return false;

ComSmartPtr<IUIViewSettings> viewSettings;

JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")

if (viewSettingsInterop->GetForWindow (hWnd, __uuidof (IUIViewSettings),
(void**) viewSettings.resetAndGetPointerAddress()) == S_OK
&& viewSettings != nullptr)
{
IUIViewSettings::UserInteractionMode mode;

if (viewSettings->GetUserInteractionMode (&mode) == S_OK)
return mode == IUIViewSettings::Touch;
}

JUCE_END_IGNORE_WARNINGS_GCC_LIKE

return false;
}

private:
//==============================================================================
struct ComBaseModule
Expand Down Expand Up @@ -1684,7 +1573,7 @@ class VBlankDispatcher : public DeletedAtShutdown
threads.end());
}

JUCE_DECLARE_SINGLETON_SINGLETHREADED (VBlankDispatcher, true)
JUCE_DECLARE_SINGLETON_SINGLETHREADED (VBlankDispatcher, false)

private:
//==============================================================================
Expand Down Expand Up @@ -1753,8 +1642,6 @@ class HWNDComponentPeer : public ComponentPeer,
setTitle (component.getName());
updateShadower();

OnScreenKeyboard::getInstance();

getNativeRealtimeModifiers = []
{
HWNDComponentPeer::updateKeyModifiers();
Expand Down Expand Up @@ -2120,16 +2007,22 @@ class HWNDComponentPeer : public ComponentPeer,
void textInputRequired (Point<int>, TextInputTarget&) override
{
if (! hasCreatedCaret)
hasCreatedCaret = CreateCaret (hwnd, (HBITMAP) 1, 0, 0);

if (hasCreatedCaret)
{
hasCreatedCaret = true;
CreateCaret (hwnd, (HBITMAP) 1, 0, 0);
SetCaretPos (0, 0);
ShowCaret (hwnd);
}

ShowCaret (hwnd);
SetCaretPos (0, 0);
ImmAssociateContext (hwnd, nullptr);

if (uwpViewSettings.isTabletModeActivatedForWindow (hwnd))
OnScreenKeyboard::getInstance()->activate();
// MSVC complains about the nullptr argument, but the docs for this
// function say that the second argument is ignored when the third
// argument is IACE_DEFAULT.
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6387)
ImmAssociateContextEx (hwnd, nullptr, IACE_DEFAULT);
JUCE_END_IGNORE_WARNINGS_MSVC
}

void closeInputMethodContext() override
Expand All @@ -2141,8 +2034,10 @@ class HWNDComponentPeer : public ComponentPeer,
{
closeInputMethodContext();

if (uwpViewSettings.isTabletModeActivatedForWindow (hwnd))
OnScreenKeyboard::getInstance()->deactivate();
ImmAssociateContext (hwnd, nullptr);

if (std::exchange (hasCreatedCaret, false))
DestroyCaret();
}

void repaint (const Rectangle<int>& area) override
Expand Down Expand Up @@ -4405,13 +4300,18 @@ class HWNDComponentPeer : public ComponentPeer,
{
if (compositionInProgress && ! windowIsActive)
{
compositionInProgress = false;

if (HIMC hImc = ImmGetContext (hWnd))
{
ImmNotifyIME (hImc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
ImmReleaseContext (hWnd, hImc);
}

// If the composition is still in progress, calling ImmNotifyIME may call back
// into handleComposition to let us know that the composition has finished.
// We need to set compositionInProgress *after* calling handleComposition, so that
// the text replaces the current selection, rather than being inserted after the
// caret.
compositionInProgress = false;
}
}

Expand Down
Expand Up @@ -413,6 +413,31 @@ namespace Keys
static bool capsLock = false;
static char keyStates [32];
static constexpr int extendedKeyModifier = 0x10000000;
static bool modifierKeysAreStale = false;

static void refreshStaleModifierKeys()
{
if (modifierKeysAreStale)
{
XWindowSystem::getInstance()->getNativeRealtimeModifiers();
modifierKeysAreStale = false;
}
}

// Call this function when only the mouse keys need to be refreshed e.g. when the event
// parameter already has information about the keys.
static void refreshStaleMouseKeys()
{
if (modifierKeysAreStale)
{
const auto oldMods = ModifierKeys::currentModifiers;
XWindowSystem::getInstance()->getNativeRealtimeModifiers();
ModifierKeys::currentModifiers = oldMods.withoutMouseButtons()
.withFlags (ModifierKeys::currentModifiers.withOnlyMouseButtons()
.getRawFlags());
modifierKeysAreStale = false;
}
}
}

const int KeyPress::spaceKey = XK_space & 0xff;
Expand Down Expand Up @@ -1747,17 +1772,17 @@ void XWindowSystem::setBounds (::Window windowH, Rectangle<int> newBounds, bool
X11Symbols::getInstance()->xSetWMNormalHints (display, windowH, hints.get());
}

const auto windowBorder = [&]() -> BorderSize<int>
const auto nativeWindowBorder = [&]() -> BorderSize<int>
{
if (const auto& frameSize = peer->getFrameSizeIfPresent())
return *frameSize;
return frameSize->multipliedBy (peer->getPlatformScaleFactor());

return {};
}();

X11Symbols::getInstance()->xMoveResizeWindow (display, windowH,
newBounds.getX() - windowBorder.getLeft(),
newBounds.getY() - windowBorder.getTop(),
newBounds.getX() - nativeWindowBorder.getLeft(),
newBounds.getY() - nativeWindowBorder.getTop(),
(unsigned int) newBounds.getWidth(),
(unsigned int) newBounds.getHeight());
}
Expand Down Expand Up @@ -2448,6 +2473,18 @@ ModifierKeys XWindowSystem::getNativeRealtimeModifiers() const

ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (mouseMods);

// We are keeping track of the state of modifier keys and mouse buttons with the assumption that
// for every mouse down we are going to receive a mouse up etc.
//
// This assumption is broken when getNativeRealtimeModifiers() is called. If for example we call
// this function when the mouse cursor is in another application and the mouse button happens to
// be down, then its represented state in currentModifiers may remain down indefinitely, since
// we aren't going to receive an event when it's released.
//
// We mark this state in this variable, and we can restore synchronization when our window
// receives an event again.
Keys::modifierKeysAreStale = true;

return ModifierKeys::currentModifiers;
}

Expand Down Expand Up @@ -3314,6 +3351,7 @@ void XWindowSystem::handleWindowMessage (LinuxComponentPeer* peer, XEvent& event
void XWindowSystem::handleKeyPressEvent (LinuxComponentPeer* peer, XKeyEvent& keyEvent) const
{
auto oldMods = ModifierKeys::currentModifiers;
Keys::refreshStaleModifierKeys();

char utf8 [64] = { 0 };
juce_wchar unicodeChar = 0;
Expand Down Expand Up @@ -3544,6 +3582,7 @@ void XWindowSystem::handleButtonReleaseEvent (LinuxComponentPeer* peer, const XB
void XWindowSystem::handleMotionNotifyEvent (LinuxComponentPeer* peer, const XPointerMovedEvent& movedEvent) const
{
updateKeyModifiers ((int) movedEvent.state);
Keys::refreshStaleMouseKeys();

auto& dragState = dragAndDropStateMap[peer];

Expand Down
Expand Up @@ -208,9 +208,6 @@ void Label::editorShown (TextEditor* textEditor)

void Label::editorAboutToBeHidden (TextEditor* textEditor)
{
if (auto* peer = getPeer())
peer->dismissPendingTextInput();

Component::BailOutChecker checker (this);
listeners.callChecked (checker, [this, textEditor] (Label::Listener& l) { l.editorHidden (this, *textEditor); });

Expand Down
53 changes: 44 additions & 9 deletions libs/juce7/source/modules/juce_gui_basics/widgets/juce_Slider.cpp
Expand Up @@ -26,6 +26,14 @@
namespace juce
{

static double getStepSize (const Slider& slider)
{
const auto interval = slider.getInterval();

return interval != 0.0 ? interval
: slider.getRange().getLength() * 0.01;
}

class Slider::Pimpl : public AsyncUpdater, // this needs to be public otherwise it will cause an
// error when JUCE_DLL_BUILD=1
private Value::Listener
Expand Down Expand Up @@ -991,6 +999,38 @@ class Slider::Pimpl : public AsyncUpdater, // this needs to be public otherwis
popupDisplay.reset();
}

bool keyPressed (const KeyPress& key)
{
if (key.getModifiers().isAnyModifierKeyDown())
return false;

const auto getInterval = [this]
{
if (auto* accessibility = owner.getAccessibilityHandler())
if (auto* valueInterface = accessibility->getValueInterface())
return valueInterface->getRange().getInterval();

return getStepSize (owner);
};

const auto valueChange = [&]
{
if (key == KeyPress::rightKey || key == KeyPress::upKey)
return getInterval();

if (key == KeyPress::leftKey || key == KeyPress::downKey)
return -getInterval();

return 0.0;
}();

if (valueChange == 0.0)
return false;

setValue (getValue() + valueChange, sendNotificationSync);
return true;
}

void showPopupDisplay()
{
if (style == IncDecButtons)
Expand Down Expand Up @@ -1661,6 +1701,9 @@ void Slider::mouseExit (const MouseEvent&) { pimpl->mouseExit(); }
// it is shown when dragging the mouse over a slider and releasing
void Slider::mouseEnter (const MouseEvent&) { pimpl->mouseMove(); }

/** @internal */
bool Slider::keyPressed (const KeyPress& k) { return pimpl->keyPressed (k); }

void Slider::modifierKeysChanged (const ModifierKeys& modifiers)
{
if (isEnabled())
Expand Down Expand Up @@ -1734,18 +1777,10 @@ class SliderAccessibilityHandler : public AccessibilityHandler
AccessibleValueRange getRange() const override
{
return { { slider.getMinimum(), slider.getMaximum() },
getStepSize() };
getStepSize (slider) };
}

private:
double getStepSize() const
{
auto interval = slider.getInterval();

return interval != 0.0 ? interval
: slider.getRange().getLength() * 0.01;
}

Slider& slider;
const bool useMaxValue;

Expand Down
Expand Up @@ -991,6 +991,8 @@ class JUCE_API Slider : public Component,
void mouseExit (const MouseEvent&) override;
/** @internal */
void mouseEnter (const MouseEvent&) override;
/** @internal */
bool keyPressed (const KeyPress&) override;

//==============================================================================
#ifndef DOXYGEN
Expand Down
Expand Up @@ -868,6 +868,12 @@ struct TextEditor::TextHolderComponent : public Component,

TextEditor& owner;

private:
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return createIgnoredAccessibilityHandler (*this);
}

JUCE_DECLARE_NON_COPYABLE (TextHolderComponent)
};

Expand All @@ -894,6 +900,11 @@ struct TextEditor::TextEditorViewport : public Viewport
}

private:
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return createIgnoredAccessibilityHandler (*this);
}

TextEditor& owner;
int lastWordWrapWidth = 0;
bool reentrant = false;
Expand Down Expand Up @@ -933,13 +944,13 @@ TextEditor::TextEditor (const String& name, juce_wchar passwordChar)

setWantsKeyboardFocus (true);
recreateCaret();

juce::Desktop::getInstance().addGlobalMouseListener (this);
}

TextEditor::~TextEditor()
{
if (wasFocused)
if (auto* peer = getPeer())
peer->dismissPendingTextInput();
juce::Desktop::getInstance().removeGlobalMouseListener (this);

textValue.removeListener (textHolder);
textValue.referTo (Value());
Expand Down Expand Up @@ -1017,17 +1028,25 @@ void TextEditor::setReadOnly (bool shouldBeReadOnly)
readOnly = shouldBeReadOnly;
enablementChanged();
invalidateAccessibilityHandler();

if (auto* peer = getPeer())
peer->refreshTextInputTarget();
}
}

void TextEditor::setClicksOutsideDismissVirtualKeyboard (bool newValue)
{
clicksOutsideDismissVirtualKeyboard = newValue;
}

bool TextEditor::isReadOnly() const noexcept
{
return readOnly || ! isEnabled();
}

bool TextEditor::isTextInputActive() const
{
return ! isReadOnly();
return ! isReadOnly() && (! clicksOutsideDismissVirtualKeyboard || mouseDownInEditor);
}

void TextEditor::setReturnKeyStartsNewLine (bool shouldStartNewLine)
Expand Down Expand Up @@ -1322,13 +1341,7 @@ void TextEditor::timerCallbackInt()
void TextEditor::checkFocus()
{
if (! wasFocused && hasKeyboardFocus (false) && ! isCurrentlyBlockedByAnotherModalComponent())
{
wasFocused = true;

if (auto* peer = getPeer())
if (! isReadOnly())
peer->textInputRequired (peer->globalToLocal (getScreenPosition()), *this);
}
}

void TextEditor::repaintText (Range<int> range)
Expand Down Expand Up @@ -1827,6 +1840,11 @@ void TextEditor::performPopupMenuAction (const int menuItemID)
//==============================================================================
void TextEditor::mouseDown (const MouseEvent& e)
{
mouseDownInEditor = e.originalComponent == this;

if (! mouseDownInEditor)
return;

beginDragAutoRepeat (100);
newTransaction();

Expand Down Expand Up @@ -1865,13 +1883,19 @@ void TextEditor::mouseDown (const MouseEvent& e)

void TextEditor::mouseDrag (const MouseEvent& e)
{
if (! mouseDownInEditor)
return;

if (wasFocused || ! selectAllTextWhenFocused)
if (! (popupMenuEnabled && e.mods.isPopupMenu()))
moveCaretTo (getTextIndexAt (e.x, e.y), true);
}

void TextEditor::mouseUp (const MouseEvent& e)
{
if (! mouseDownInEditor)
return;

newTransaction();
textHolder->restartTimer();

Expand All @@ -1884,6 +1908,9 @@ void TextEditor::mouseUp (const MouseEvent& e)

void TextEditor::mouseDoubleClick (const MouseEvent& e)
{
if (! mouseDownInEditor)
return;

int tokenEnd = getTextIndexAt (e.x, e.y);
int tokenStart = 0;

Expand Down Expand Up @@ -1950,6 +1977,9 @@ void TextEditor::mouseDoubleClick (const MouseEvent& e)

void TextEditor::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
{
if (! mouseDownInEditor)
return;

if (! viewport->useMouseWheelMoveIfNeeded (e, wheel))
Component::mouseWheelMove (e, wheel);
}
Expand Down Expand Up @@ -2214,9 +2244,6 @@ void TextEditor::focusLost (FocusChangeType)

underlinedSections.clear();

if (auto* peer = getPeer())
peer->dismissPendingTextInput();

updateCaretPosition();

postCommandMessage (TextEditorDefs::focusLossMessageId);
Expand Down Expand Up @@ -2668,10 +2695,10 @@ void TextEditor::coalesceSimilarSections()
}

//==============================================================================
class TextEditorAccessibilityHandler : public AccessibilityHandler
class TextEditor::EditorAccessibilityHandler : public AccessibilityHandler
{
public:
explicit TextEditorAccessibilityHandler (TextEditor& textEditorToWrap)
explicit EditorAccessibilityHandler (TextEditor& textEditorToWrap)
: AccessibilityHandler (textEditorToWrap,
textEditorToWrap.isReadOnly() ? AccessibilityRole::staticText : AccessibilityRole::editableText,
{},
Expand Down Expand Up @@ -2699,10 +2726,20 @@ class TextEditorAccessibilityHandler : public AccessibilityHandler

void setSelection (Range<int> r) override
{
if (r == textEditor.getHighlightedRegion())
return;

if (r.isEmpty())
{
textEditor.setCaretPosition (r.getStart());
}
else
textEditor.setHighlightedRegion (r);
{
const auto cursorAtStart = r.getEnd() == textEditor.getHighlightedRegion().getStart()
|| r.getEnd() == textEditor.getHighlightedRegion().getEnd();
textEditor.moveCaretTo (cursorAtStart ? r.getEnd() : r.getStart(), false);
textEditor.moveCaretTo (cursorAtStart ? r.getStart() : r.getEnd(), true);
}
}

String getText (Range<int> r) const override
Expand Down Expand Up @@ -2748,12 +2785,12 @@ class TextEditorAccessibilityHandler : public AccessibilityHandler
TextEditor& textEditor;

//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextEditorAccessibilityHandler)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EditorAccessibilityHandler)
};

std::unique_ptr<AccessibilityHandler> TextEditor::createAccessibilityHandler()
{
return std::make_unique<TextEditorAccessibilityHandler> (*this);
return std::make_unique<EditorAccessibilityHandler> (*this);
}

} // namespace juce