/* ============================================================================== 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. ============================================================================== */ namespace juce { AccessibilityHandler* AccessibilityHandler::currentlyFocusedHandler = nullptr; enum class InternalAccessibilityEvent { elementCreated, elementDestroyed, focusChanged, windowOpened, windowClosed }; void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent event); inline String getAccessibleApplicationOrPluginName() { #if defined (JucePlugin_Name) return JucePlugin_Name; #else if (auto* app = JUCEApplicationBase::getInstance()) return app->getApplicationName(); return "JUCE Application"; #endif } AccessibilityHandler::AccessibilityHandler (Component& comp, AccessibilityRole accessibilityRole, AccessibilityActions accessibilityActions, Interfaces interfacesIn) : component (comp), typeIndex (typeid (component)), role (accessibilityRole), actions (std::move (accessibilityActions)), interfaces (std::move (interfacesIn)), nativeImpl (createNativeImpl (*this)) { notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::elementCreated); } AccessibilityHandler::~AccessibilityHandler() { giveAwayFocus(); notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::elementDestroyed); } //============================================================================== AccessibleState AccessibilityHandler::getCurrentState() const { auto state = AccessibleState().withFocusable(); return hasFocus (false) ? state.withFocused() : state; } static bool isComponentVisibleWithinWindow (const Component& comp) { if (auto* peer = comp.getPeer()) return ! peer->getAreaCoveredBy (comp).getIntersection (peer->getComponent().getLocalBounds()).isEmpty(); return false; } static bool isComponentVisibleWithinParent (Component* comp) { if (auto* parent = comp->getParentComponent()) { if (comp->getBoundsInParent().getIntersection (parent->getLocalBounds()).isEmpty()) return false; return isComponentVisibleWithinParent (parent); } return true; } bool AccessibilityHandler::isIgnored() const { const auto state = getCurrentState(); return role == AccessibilityRole::ignored || state.isIgnored() || ! component.isShowing() || (! state.isAccessibleOffscreen() && (! isComponentVisibleWithinParent (&component) || ! isComponentVisibleWithinWindow (component))); } //============================================================================== const AccessibilityActions& AccessibilityHandler::getActions() const noexcept { return actions; } AccessibilityValueInterface* AccessibilityHandler::getValueInterface() const { return interfaces.value.get(); } AccessibilityTableInterface* AccessibilityHandler::getTableInterface() const { return interfaces.table.get(); } AccessibilityCellInterface* AccessibilityHandler::getCellInterface() const { return interfaces.cell.get(); } AccessibilityTextInterface* AccessibilityHandler::getTextInterface() const { return interfaces.text.get(); } //============================================================================== static AccessibilityHandler* findEnclosingHandler (Component* comp) { if (comp != nullptr) { if (auto* handler = comp->getAccessibilityHandler()) return handler; return findEnclosingHandler (comp->getParentComponent()); } return nullptr; } static AccessibilityHandler* getUnignoredAncestor (AccessibilityHandler* handler) { while (handler != nullptr && handler->isIgnored() && handler->getParent() != nullptr) { handler = handler->getParent(); } return handler; } static AccessibilityHandler* findFirstUnignoredChild (const std::vector& handlers) { if (! handlers.empty()) { const auto iter = std::find_if (handlers.cbegin(), handlers.cend(), [] (const AccessibilityHandler* handler) { return ! handler->isIgnored(); }); if (iter != handlers.cend()) return *iter; for (auto* handler : handlers) if (auto* unignored = findFirstUnignoredChild (handler->getChildren())) return unignored; } return nullptr; } static AccessibilityHandler* getFirstUnignoredDescendant (AccessibilityHandler* handler) { if (handler != nullptr && handler->isIgnored()) return findFirstUnignoredChild (handler->getChildren()); return handler; } AccessibilityHandler* AccessibilityHandler::getParent() const { if (auto* focusContainer = component.findFocusContainer()) return getUnignoredAncestor (findEnclosingHandler (focusContainer)); return nullptr; } std::vector AccessibilityHandler::getChildren() const { if (! component.isFocusContainer() && component.getParentComponent() != nullptr) return {}; std::vector children; if (auto traverser = component.createFocusTraverser()) { for (auto* focusableChild : traverser->getAllComponents (&component)) { if (auto* handler = findEnclosingHandler (focusableChild)) { if (! isParentOf (handler)) continue; if (auto* unignored = getFirstUnignoredDescendant (handler)) if (std::find (children.cbegin(), children.cend(), unignored) == children.cend()) children.push_back (unignored); } } } return children; } bool AccessibilityHandler::isParentOf (const AccessibilityHandler* possibleChild) const noexcept { while (possibleChild != nullptr) { possibleChild = possibleChild->getParent(); if (possibleChild == this) return true; } return false; } AccessibilityHandler* AccessibilityHandler::getChildAt (Point screenPoint) { if (auto* comp = Desktop::getInstance().findComponentAt (screenPoint)) if (isParentOf (comp->getAccessibilityHandler())) return getUnignoredAncestor (findEnclosingHandler (comp)); return nullptr; } AccessibilityHandler* AccessibilityHandler::getChildFocus() { return hasFocus (true) ? getUnignoredAncestor (currentlyFocusedHandler) : nullptr; } bool AccessibilityHandler::hasFocus (bool trueIfChildFocused) const { return currentlyFocusedHandler != nullptr && (currentlyFocusedHandler == this || (trueIfChildFocused && isParentOf (currentlyFocusedHandler))); } void AccessibilityHandler::grabFocus() { if (! hasFocus (false)) grabFocusInternal (true); } void AccessibilityHandler::giveAwayFocus() const { if (hasFocus (true)) giveAwayFocusInternal(); } void AccessibilityHandler::grabFocusInternal (bool canTryParent) { if (getCurrentState().isFocusable() && ! isIgnored()) { takeFocus(); return; } if (isParentOf (currentlyFocusedHandler) && ! currentlyFocusedHandler->isIgnored()) return; if (component.isFocusContainer() || component.getParentComponent() == nullptr) { if (auto traverser = component.createFocusTraverser()) { if (auto* defaultComp = traverser->getDefaultComponent (&component)) { if (auto* handler = getUnignoredAncestor (findEnclosingHandler (defaultComp))) { if (isParentOf (handler)) { handler->grabFocusInternal (false); return; } } } } } if (canTryParent) if (auto* parent = getParent()) parent->grabFocusInternal (true); } void AccessibilityHandler::giveAwayFocusInternal() const { currentlyFocusedHandler = nullptr; notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::focusChanged); if (auto* focusedComponent = Component::getCurrentlyFocusedComponent()) if (auto* handler = focusedComponent->getAccessibilityHandler()) handler->grabFocus(); } void AccessibilityHandler::takeFocus() { currentlyFocusedHandler = this; WeakReference weakComponent (&component); actions.invoke (AccessibilityActionType::focus); if (weakComponent != nullptr && component.getWantsKeyboardFocus() && ! component.hasKeyboardFocus (true)) { component.grabKeyboardFocus(); } notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::focusChanged); } //============================================================================== #if ! (JUCE_MAC || (JUCE_WINDOWS && ! JUCE_MINGW)) class AccessibilityHandler::AccessibilityNativeImpl { public: AccessibilityNativeImpl (AccessibilityHandler&) {} }; void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent) const {} void AccessibilityHandler::postAnnouncement (const String&, AnnouncementPriority) {} AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const { return nullptr; } AccessibilityHandler::AccessibilityNativeImpl* AccessibilityHandler::createNativeImpl (AccessibilityHandler&) { return nullptr; } void AccessibilityHandler::DestroyNativeImpl::operator() (AccessibilityHandler::AccessibilityNativeImpl*) const noexcept {} void notifyAccessibilityEventInternal (const AccessibilityHandler&, InternalAccessibilityEvent) {} #endif } // namespace juce