1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Accessibility: Added VoiceOver (macOS) and Narrator (Windows) accessibility screen reader support to juce_gui_basics

This commit is contained in:
ed 2021-05-10 09:38:00 +01:00
parent 1df59f7469
commit ec990202b1
133 changed files with 10158 additions and 1297 deletions

View file

@ -26,6 +26,131 @@
namespace juce
{
//==============================================================================
class CodeEditorComponent::CodeEditorAccessibilityHandler : public AccessibilityHandler
{
public:
explicit CodeEditorAccessibilityHandler (CodeEditorComponent& codeEditorComponentToWrap)
: AccessibilityHandler (codeEditorComponentToWrap,
codeEditorComponentToWrap.isReadOnly() ? AccessibilityRole::staticText
: AccessibilityRole::editableText,
{},
{ codeEditorComponentToWrap.isReadOnly() ? nullptr
: std::make_unique<CodeEditorComponentTextInterface> (codeEditorComponentToWrap) }),
codeEditorComponent (codeEditorComponentToWrap)
{
}
String getTitle() const override
{
return codeEditorComponent.isReadOnly() ? codeEditorComponent.document.getAllContent()
: codeEditorComponent.getTitle();
}
private:
class CodeEditorComponentTextInterface : public AccessibilityTextInterface
{
public:
explicit CodeEditorComponentTextInterface (CodeEditorComponent& codeEditorComponentToWrap)
: codeEditorComponent (codeEditorComponentToWrap)
{
}
bool isDisplayingProtectedText() const override
{
return false;
}
int getTotalNumCharacters() const override
{
return codeEditorComponent.document.getAllContent().length();
}
Range<int> getSelection() const override
{
return { codeEditorComponent.selectionStart.getPosition(),
codeEditorComponent.selectionEnd.getPosition() };
}
void setSelection (Range<int> r) override
{
auto& doc = codeEditorComponent.document;
codeEditorComponent.selectRegion (CodeDocument::Position (doc, r.getStart()),
CodeDocument::Position (doc, r.getEnd()));
}
String getText (Range<int> r) const override
{
auto& doc = codeEditorComponent.document;
return doc.getTextBetween (CodeDocument::Position (doc, r.getStart()),
CodeDocument::Position (doc, r.getEnd()));
}
void setText (const String& newText) override
{
codeEditorComponent.document.replaceAllContent (newText);
}
int getTextInsertionOffset() const override
{
return codeEditorComponent.caretPos.getPosition();
}
RectangleList<int> getTextBounds (Range<int> textRange) const override
{
auto& doc = codeEditorComponent.document;
RectangleList<int> localRects;
CodeDocument::Position startPosition (doc, textRange.getStart());
CodeDocument::Position endPosition (doc, textRange.getEnd());
for (int line = startPosition.getLineNumber(); line <= endPosition.getLineNumber(); ++line)
{
CodeDocument::Position lineStart (doc, line, 0);
CodeDocument::Position lineEnd (doc, line, doc.getLine (line).length());
if (line == startPosition.getLineNumber())
lineStart = lineStart.movedBy (startPosition.getIndexInLine());
if (line == endPosition.getLineNumber())
lineEnd = { doc, line, endPosition.getIndexInLine() };
auto startPos = codeEditorComponent.getCharacterBounds (lineStart).getTopLeft();
auto endPos = codeEditorComponent.getCharacterBounds (lineEnd).getTopLeft();
localRects.add (startPos.x,
startPos.y,
endPos.x - startPos.x,
codeEditorComponent.getLineHeight());
}
RectangleList<int> globalRects;
for (auto r : localRects)
globalRects.add (codeEditorComponent.localAreaToGlobal (r));
return globalRects;
}
int getOffsetAtPoint (Point<int> point) const override
{
return codeEditorComponent.getPositionAt (point.x, point.y).getPosition();
}
private:
CodeEditorComponent& codeEditorComponent;
};
CodeEditorComponent& codeEditorComponent;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CodeEditorAccessibilityHandler)
};
//==============================================================================
class CodeEditorComponent::CodeEditorLine
{
public:
@ -437,6 +562,8 @@ void CodeEditorComponent::setReadOnly (bool b) noexcept
removeChildComponent (caret.get());
else
addAndMakeVisible (caret.get());
invalidateAccessibilityHandler();
}
}
@ -585,7 +712,12 @@ void CodeEditorComponent::retokenise (int startIndex, int endIndex)
void CodeEditorComponent::updateCaretPosition()
{
if (caret != nullptr)
{
caret->setCaretPosition (getCharacterBounds (getCaretPos()));
if (auto* handler = getAccessibilityHandler())
handler->notifyAccessibilityEvent (AccessibilityEvent::textSelectionChanged);
}
}
void CodeEditorComponent::moveCaretTo (const CodeDocument::Position& newPos, const bool highlighting)
@ -598,38 +730,36 @@ void CodeEditorComponent::moveCaretTo (const CodeDocument::Position& newPos, con
{
if (dragType == notDragging)
{
if (std::abs (caretPos.getPosition() - selectionStart.getPosition())
< std::abs (caretPos.getPosition() - selectionEnd.getPosition()))
dragType = draggingSelectionStart;
else
dragType = draggingSelectionEnd;
auto oldCaretPos = caretPos.getPosition();
auto isStart = std::abs (oldCaretPos - selectionStart.getPosition())
< std::abs (oldCaretPos - selectionEnd.getPosition());
dragType = isStart ? draggingSelectionStart : draggingSelectionEnd;
}
if (dragType == draggingSelectionStart)
{
selectionStart = caretPos;
if (selectionEnd.getPosition() < selectionStart.getPosition())
if (selectionEnd.getPosition() < caretPos.getPosition())
{
auto temp = selectionStart;
selectionStart = selectionEnd;
selectionEnd = temp;
setSelection (selectionEnd, caretPos);
dragType = draggingSelectionEnd;
}
else
{
setSelection (caretPos, selectionEnd);
}
}
else
{
selectionEnd = caretPos;
if (selectionEnd.getPosition() < selectionStart.getPosition())
if (caretPos.getPosition() < selectionStart.getPosition())
{
auto temp = selectionStart;
selectionStart = selectionEnd;
selectionEnd = temp;
setSelection (caretPos, selectionStart);
dragType = draggingSelectionStart;
}
else
{
setSelection (selectionStart, caretPos);
}
}
rebuildLineTokensAsync();
@ -644,6 +774,9 @@ void CodeEditorComponent::moveCaretTo (const CodeDocument::Position& newPos, con
updateScrollBars();
caretPositionMoved();
if (auto* handler = getAccessibilityHandler())
handler->notifyAccessibilityEvent (AccessibilityEvent::textChanged);
if (appCommandManager != nullptr && selectionWasActive != isHighlightActive())
appCommandManager->commandStatusChanged();
}
@ -653,8 +786,7 @@ void CodeEditorComponent::deselectAll()
if (isHighlightActive())
rebuildLineTokensAsync();
selectionStart = caretPos;
selectionEnd = caretPos;
setSelection (caretPos, caretPos);
dragType = notDragging;
}
@ -746,7 +878,7 @@ Rectangle<int> CodeEditorComponent::getCharacterBounds (const CodeDocument::Posi
lineHeight };
}
CodeDocument::Position CodeEditorComponent::getPositionAt (int x, int y)
CodeDocument::Position CodeEditorComponent::getPositionAt (int x, int y) const
{
const int line = y / lineHeight + firstLineOnScreen;
const int column = roundToInt ((x - (getGutterSize() - xOffset * charWidth)) / charWidth);
@ -772,6 +904,9 @@ void CodeEditorComponent::insertText (const String& newText)
scrollToKeepCaretOnScreen();
caretPositionMoved();
if (auto* handler = getAccessibilityHandler())
handler->notifyAccessibilityEvent (AccessibilityEvent::textChanged);
}
}
@ -865,9 +1000,15 @@ void CodeEditorComponent::indentSelectedLines (const int spacesToAdd)
}
}
selectionStart = oldSelectionStart;
selectionEnd = oldSelectionEnd;
caretPos = oldCaret;
setSelection (oldSelectionStart, oldSelectionEnd);
if (caretPos != oldCaret)
{
caretPos = oldCaret;
if (auto* handler = getAccessibilityHandler())
handler->notifyAccessibilityEvent (AccessibilityEvent::textChanged);
}
}
}
@ -1341,6 +1482,20 @@ bool CodeEditorComponent::performCommand (const CommandID commandID)
return true;
}
void CodeEditorComponent::setSelection (CodeDocument::Position newSelectionStart,
CodeDocument::Position newSelectionEnd)
{
if (selectionStart != newSelectionStart
|| selectionEnd != newSelectionEnd)
{
selectionStart = newSelectionStart;
selectionEnd = newSelectionEnd;
if (auto* handler = getAccessibilityHandler())
handler->notifyAccessibilityEvent (AccessibilityEvent::textSelectionChanged);
}
}
//==============================================================================
void CodeEditorComponent::addPopupMenuItems (PopupMenu& m, const MouseEvent*)
{
@ -1674,4 +1829,10 @@ String CodeEditorComponent::State::toString() const
return String (lastTopLine) + ":" + String (lastCaretPos) + ":" + String (lastSelectionEnd);
}
//==============================================================================
std::unique_ptr<AccessibilityHandler> CodeEditorComponent::createAccessibilityHandler()
{
return std::make_unique<CodeEditorAccessibilityHandler> (*this);
}
} // namespace juce

View file

@ -110,7 +110,7 @@ public:
/** Finds the character at a given on-screen position.
The coordinates are relative to this component's top-left origin.
*/
CodeDocument::Position getPositionAt (int x, int y);
CodeDocument::Position getPositionAt (int x, int y) const;
/** Returns the start of the selection as a position. */
CodeDocument::Position getSelectionStart() const { return selectionStart; }
@ -380,6 +380,8 @@ public:
bool perform (const InvocationInfo&) override;
/** @internal */
void lookAndFeelChanged() override;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private:
//==============================================================================
@ -404,6 +406,8 @@ private:
class GutterComponent;
std::unique_ptr<GutterComponent> gutter;
class CodeEditorAccessibilityHandler;
enum DragType
{
notDragging,
@ -442,6 +446,7 @@ private:
void indentSelectedLines (int spacesToAdd);
bool skipBackwardsToPreviousTab();
bool performCommand (CommandID);
void setSelection (CodeDocument::Position, CodeDocument::Position);
int indexToColumn (int line, int index) const noexcept;
int columnToIndex (int line, int column) const noexcept;