aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorNeil Hodgson <nyamatongwe@gmail.com>2016-11-05 20:42:12 +1100
committerNeil Hodgson <nyamatongwe@gmail.com>2016-11-05 20:42:12 +1100
commitab0bd10bf685d2868b245893afd7512ddadc5fb1 (patch)
tree2512c11643a54285979a0dd67103fb79962b88e2
parent8a62263409f5d222ac0d0ccf7bf0e7e0261224a8 (diff)
downloadscintilla-mirror-ab0bd10bf685d2868b245893afd7512ddadc5fb1.tar.gz
Support for NSAccessibility protocol added sufficient for the VoiceOver
screen reader.
-rw-r--r--cocoa/ScintillaCocoa.h8
-rw-r--r--cocoa/ScintillaCocoa.mm95
-rw-r--r--cocoa/ScintillaView.h3
-rw-r--r--cocoa/ScintillaView.mm231
-rw-r--r--doc/ScintillaDoc.html19
-rw-r--r--doc/ScintillaHistory.html2
6 files changed, 344 insertions, 14 deletions
diff --git a/cocoa/ScintillaCocoa.h b/cocoa/ScintillaCocoa.h
index 1bf05c9f0..25b9807d7 100644
--- a/cocoa/ScintillaCocoa.h
+++ b/cocoa/ScintillaCocoa.h
@@ -196,8 +196,12 @@ public:
virtual void IdleWork();
virtual void QueueIdleWork(WorkNeeded::workItems items, int upTo);
int InsertText(NSString* input);
- NSRange PositionsFromCharacters(NSRange range) const;
- NSRange CharactersFromPositions(NSRange range) const;
+ NSRange PositionsFromCharacters(NSRange rangeCharacters) const;
+ NSRange CharactersFromPositions(NSRange rangePositions) const;
+ NSString *RangeTextAsString(NSRange rangePositions) const;
+ NSInteger VisibleLineForIndex(NSInteger index);
+ NSRange RangeForVisibleLine(NSInteger lineVisible);
+ NSRect FrameForRange(NSRange rangeCharacters);
void SelectOnlyMainSelection();
void ConvertSelectionVirtualSpace();
bool ClearAllSelections();
diff --git a/cocoa/ScintillaCocoa.mm b/cocoa/ScintillaCocoa.mm
index 20f0e4440..3ae4ab575 100644
--- a/cocoa/ScintillaCocoa.mm
+++ b/cocoa/ScintillaCocoa.mm
@@ -1689,6 +1689,79 @@ int ScintillaCocoa::TargetAsUTF8(char *text)
//--------------------------------------------------------------------------------------------------
+// Returns the text in the range converted to an NSString.
+NSString *ScintillaCocoa::RangeTextAsString(NSRange rangePositions) const {
+ const std::string text = RangeText(static_cast<int>(rangePositions.location),
+ static_cast<int>(NSMaxRange(rangePositions)));
+ if (IsUnicodeMode())
+ {
+ return [NSString stringWithUTF8String: text.c_str()];
+ }
+ else
+ {
+ // Need to convert
+ const CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
+ vs.styles[STYLE_DEFAULT].characterSet);
+ CFStringRef cfsVal = CFStringCreateWithBytes(kCFAllocatorDefault,
+ reinterpret_cast<const UInt8 *>(text.c_str()),
+ text.length(), encoding, false);
+
+ return (NSString *)cfsVal;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+
+// Return character range of a line.
+NSRange ScintillaCocoa::RangeForVisibleLine(NSInteger lineVisible) {
+ const Range posRangeLine = RangeDisplayLine(static_cast<int>(lineVisible));
+ return CharactersFromPositions(NSMakeRange(posRangeLine.First(),
+ posRangeLine.Last() - posRangeLine.First()));
+}
+
+//--------------------------------------------------------------------------------------------------
+
+// Returns visible line number of a text position in characters.
+NSInteger ScintillaCocoa::VisibleLineForIndex(NSInteger index) {
+ const NSRange rangePosition = PositionsFromCharacters(NSMakeRange(index, 0));
+ const int lineVisible = DisplayFromPosition(static_cast<int>(rangePosition.location));
+ return lineVisible;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+// Returns a rectangle that frames the range for use by the VoiceOver cursor.
+NSRect ScintillaCocoa::FrameForRange(NSRange rangeCharacters) {
+ const NSRange posRange = PositionsFromCharacters(rangeCharacters);
+
+ NSUInteger rangeEnd = NSMaxRange(posRange);
+ const bool endsWithLineEnd = rangeCharacters.length &&
+ (pdoc->GetColumn(static_cast<int>(rangeEnd)) == 0);
+
+ Point ptStart = LocationFromPosition(static_cast<int>(posRange.location));
+ const PointEnd peEndRange = static_cast<PointEnd>(peSubLineEnd|peLineEnd);
+ Point ptEnd = LocationFromPosition(static_cast<int>(rangeEnd), peEndRange);
+
+ NSRect rect = NSMakeRect(ptStart.x, ptStart.y,
+ ptEnd.x - ptStart.x,
+ ptEnd.y - ptStart.y);
+
+ rect.size.width += 2; // Shows the last character better
+ if (endsWithLineEnd) {
+ // Add a block to the right to indicate a line end is selected
+ rect.size.width += 20;
+ }
+
+ rect.size.height += vs.lineHeight;
+
+ // Adjust for margin and scroll
+ rect.origin.x = rect.origin.x - vs.textStart + vs.fixedColumnWidth;
+
+ return rect;
+}
+
+//--------------------------------------------------------------------------------------------------
+
// Translates a UTF8 string into the document encoding.
// Return the length of the result in bytes.
int ScintillaCocoa::EncodedFromUTF8(char *utf8, char *encoded) const
@@ -2013,6 +2086,15 @@ void ScintillaCocoa::NotifyParent(SCNotification scn)
notifyProc(notifyObj, WM_NOTIFY, GetCtrlID(), (uintptr_t) &scn);
if (delegate)
[delegate notification:&scn];
+ if (scn.nmhdr.code == SCN_UPDATEUI) {
+ NSView *content = ContentView();
+ if (scn.updated & SC_UPDATE_CONTENT) {
+ NSAccessibilityPostNotification(content, NSAccessibilityValueChangedNotification);
+ }
+ if (scn.updated & SC_UPDATE_SELECTION) {
+ NSAccessibilityPostNotification(content, NSAccessibilitySelectedTextChangedNotification);
+ }
+ }
}
//--------------------------------------------------------------------------------------------------
@@ -2217,12 +2299,12 @@ int ScintillaCocoa::InsertText(NSString* input)
/**
* Convert from a range of characters to a range of bytes.
*/
-NSRange ScintillaCocoa::PositionsFromCharacters(NSRange range) const
+NSRange ScintillaCocoa::PositionsFromCharacters(NSRange rangeCharacters) const
{
- long start = pdoc->GetRelativePositionUTF16(0, static_cast<int>(range.location));
+ long start = pdoc->GetRelativePositionUTF16(0, static_cast<int>(rangeCharacters.location));
if (start == INVALID_POSITION)
start = pdoc->Length();
- long end = pdoc->GetRelativePositionUTF16(static_cast<int>(start), static_cast<int>(range.length));
+ long end = pdoc->GetRelativePositionUTF16(static_cast<int>(start), static_cast<int>(rangeCharacters.length));
if (end == INVALID_POSITION)
end = pdoc->Length();
return NSMakeRange(start, end - start);
@@ -2233,10 +2315,11 @@ NSRange ScintillaCocoa::PositionsFromCharacters(NSRange range) const
/**
* Convert from a range of characters from a range of bytes.
*/
-NSRange ScintillaCocoa::CharactersFromPositions(NSRange range) const
+NSRange ScintillaCocoa::CharactersFromPositions(NSRange rangePositions) const
{
- const long start = pdoc->CountUTF16(0, static_cast<int>(range.location));
- const long len = pdoc->CountUTF16(static_cast<int>(range.location), static_cast<int>(NSMaxRange(range)));
+ const long start = pdoc->CountUTF16(0, static_cast<int>(rangePositions.location));
+ const long len = pdoc->CountUTF16(static_cast<int>(rangePositions.location),
+ static_cast<int>(NSMaxRange(rangePositions)));
return NSMakeRange(start, len);
}
diff --git a/cocoa/ScintillaView.h b/cocoa/ScintillaView.h
index ec2c8ee14..079e42116 100644
--- a/cocoa/ScintillaView.h
+++ b/cocoa/ScintillaView.h
@@ -74,7 +74,8 @@ extern NSString *const SCIUpdateUINotification;
NSTextInputClient,
NSUserInterfaceValidations,
NSDraggingSource,
- NSDraggingDestination>
+ NSDraggingDestination,
+ NSAccessibilityStaticText>
{
@private
ScintillaView* mOwner;
diff --git a/cocoa/ScintillaView.mm b/cocoa/ScintillaView.mm
index 51cb0af5f..491aba572 100644
--- a/cocoa/ScintillaView.mm
+++ b/cocoa/ScintillaView.mm
@@ -64,6 +64,7 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor)
[currentCursors addObject: [reverseArrowCursor retain]];
}
[self setClientView:[aScrollView documentView]];
+ self.accessibilityLabel = @"Scintilla Margin";
}
return self;
}
@@ -122,6 +123,12 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor)
owner.backend->MouseUp(theEvent);
}
+// Not a simple button so return failure
+- (BOOL)accessibilityPerformPress
+{
+ return NO;
+}
+
/**
* This method is called to give us the opportunity to define our mouse sensitive rectangle.
*/
@@ -167,6 +174,14 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor)
[self registerForDraggedTypes: [NSArray arrayWithObjects:
NSStringPboardType, ScintillaRecPboardType, NSFilenamesPboardType, nil]];
+
+ // Set up accessibility in the text role
+ self.accessibilityElement = TRUE;
+ self.accessibilityEnabled = TRUE;
+ self.accessibilityLabel = NSLocalizedString(@"Scintilla", nil); // No real localization
+ self.accessibilityRoleDescription = @"source code editor";
+ self.accessibilityRole = NSAccessibilityTextAreaRole;
+ self.accessibilityIdentifier = @"Scintilla";
}
return self;
@@ -931,6 +946,222 @@ sourceOperationMaskForDraggingContext: (NSDraggingContext) context
return mOwner.backend->WndProc(SCI_GETREADONLY, 0, 0) == 0;
}
+#pragma mark - NSAccessibility
+
+//--------------------------------------------------------------------------------------------------
+
+// Adoption of NSAccessibility protocol.
+// NSAccessibility wants to pass ranges in UTF-16 code units, not bytes (like Scintilla)
+// or characters.
+// Needs more testing with non-ASCII and non-BMP text.
+// Needs to take account of folding and wraping.
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : Text of the whole document as a string.
+ */
+- (id) accessibilityValue {
+ const sptr_t length = [mOwner message: SCI_GETLENGTH];
+ return mOwner.backend->RangeTextAsString(NSMakeRange(0,static_cast<int>(length)));
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : Line of the caret.
+ */
+- (NSInteger) accessibilityInsertionPointLineNumber {
+ const int caret = static_cast<int>([mOwner message: SCI_GETCURRENTPOS]);
+ const NSRange rangeCharactersCaret = mOwner.backend->CharactersFromPositions(NSMakeRange(caret, 0));
+ return mOwner.backend->VisibleLineForIndex(rangeCharactersCaret.location);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : Not implemented and not called by VoiceOver.
+ */
+- (NSRange)accessibilityRangeForPosition:(NSPoint)point {
+ return NSMakeRange(0,0);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : Number of characters in the whole document.
+ */
+- (NSInteger) accessibilityNumberOfCharacters {
+ sptr_t length = [mOwner message: SCI_GETLENGTH];
+ const NSRange posRange = mOwner.backend->CharactersFromPositions(NSMakeRange(length, 0));
+ return posRange.location;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : The selection text as a string.
+ */
+- (NSString *) accessibilitySelectedText {
+ const sptr_t positionBegin = [mOwner message: SCI_GETSELECTIONSTART];
+ const sptr_t positionEnd = [mOwner message: SCI_GETSELECTIONEND];
+ const NSRange posRangeSel = NSMakeRange(positionBegin, positionEnd-positionBegin);
+ return mOwner.backend->RangeTextAsString(posRangeSel);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : The character range of the main selection.
+ */
+- (NSRange) accessibilitySelectedTextRange {
+ const sptr_t positionBegin = [mOwner message: SCI_GETSELECTIONSTART];
+ const sptr_t positionEnd = [mOwner message: SCI_GETSELECTIONEND];
+ const NSRange posRangeSel = NSMakeRange(positionBegin, positionEnd-positionBegin);
+ return mOwner.backend->CharactersFromPositions(posRangeSel);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : The setter for accessibilitySelectedTextRange.
+ * This method is the only setter required for reasonable VoiceOver behaviour.
+ */
+- (void) setAccessibilitySelectedTextRange: (NSRange) range {
+ NSRange rangePositions = mOwner.backend->PositionsFromCharacters(range);
+ [mOwner message: SCI_SETSELECTION wParam: rangePositions.location lParam:NSMaxRange(rangePositions)];
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : Range of the glyph at a character index.
+ * Currently doesn't try to handle composite characters.
+ */
+- (NSRange) accessibilityRangeForIndex: (NSInteger)index {
+ sptr_t length = [mOwner message: SCI_GETLENGTH];
+ const NSRange rangeLength = mOwner.backend->CharactersFromPositions(NSMakeRange(length, 0));
+ NSRange rangePositions = NSMakeRange(length, 0);
+ if (index < rangeLength.location) {
+ rangePositions = mOwner.backend->PositionsFromCharacters(NSMakeRange(index, 1));
+ }
+ return mOwner.backend->CharactersFromPositions(rangePositions);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : All the text ranges.
+ * Currently only returns the main selection.
+ */
+- (NSArray<NSValue *>*) accessibilitySelectedTextRanges {
+ const sptr_t positionBegin = [mOwner message: SCI_GETSELECTIONSTART];
+ const sptr_t positionEnd = [mOwner message: SCI_GETSELECTIONEND];
+ const NSRange posRangeSel = NSMakeRange(positionBegin, positionEnd-positionBegin);
+ NSRange rangeCharacters = mOwner.backend->CharactersFromPositions(posRangeSel);
+ NSValue *valueRange = [NSValue valueWithRange:(NSRange)rangeCharacters];
+ return @[valueRange];
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : Character range currently visible.
+ */
+- (NSRange) accessibilityVisibleCharacterRange {
+ const sptr_t lineTopVisible = [mOwner message: SCI_GETFIRSTVISIBLELINE];
+ const sptr_t lineTop = [mOwner message:SCI_DOCLINEFROMVISIBLE wParam:lineTopVisible];
+ const sptr_t lineEndVisible = lineTopVisible + [mOwner message: SCI_LINESONSCREEN] - 1;
+ const sptr_t lineEnd = [mOwner message:SCI_DOCLINEFROMVISIBLE wParam:lineEndVisible];
+ const sptr_t posStartView = [mOwner message: SCI_POSITIONFROMLINE wParam: lineTop];
+ const sptr_t posEndView = [mOwner message: SCI_GETLINEENDPOSITION wParam: lineEnd];
+ const NSRange posRangeSel = NSMakeRange(posStartView, posEndView-posStartView);
+ return mOwner.backend->CharactersFromPositions(posRangeSel);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : Character range of a line.
+ */
+- (NSRange)accessibilityRangeForLine:(NSInteger)line {
+ return mOwner.backend->RangeForVisibleLine(line);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : Line number of a text position in characters.
+ */
+- (NSInteger)accessibilityLineForIndex:(NSInteger)index {
+ return mOwner.backend->VisibleLineForIndex(index);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : A rectangle that covers a range which will be shown as the
+ * VoiceOver cursor.
+ * Producing a nice rectangle is a little tricky particularly when including new
+ * lines. Needs to improve the case where parts of two lines are included.
+ */
+- (NSRect)accessibilityFrameForRange:(NSRange)range {
+ const NSRect rectInView = mOwner.backend->FrameForRange(range);
+ const NSRect rectInWindow = [[[self superview] superview] convertRect:rectInView toView:nil];
+ return [self.window convertRectToScreen:rectInWindow];
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : A range of text as a string.
+ */
+- (NSString *) accessibilityStringForRange:(NSRange)range {
+ const NSRange posRange = mOwner.backend->PositionsFromCharacters(range);
+ return mOwner.backend->RangeTextAsString(posRange);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : A range of text as an attributed string.
+ * Currently no attributes are set.
+ */
+- (NSAttributedString *) accessibilityAttributedStringForRange:(NSRange)range {
+ const NSRange posRange = mOwner.backend->PositionsFromCharacters(range);
+ NSString *result = mOwner.backend->RangeTextAsString(posRange);
+ return [[[NSMutableAttributedString alloc] initWithString:result] autorelease];
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * NSAccessibility : Show the context menu at the caret.
+ */
+- (BOOL)accessibilityPerformShowMenu {
+ const sptr_t caret = [mOwner message: SCI_GETCURRENTPOS];
+ NSRect rect;
+ rect.origin.x = [mOwner message: SCI_POINTXFROMPOSITION wParam: 0 lParam: caret];
+ rect.origin.y = [mOwner message: SCI_POINTYFROMPOSITION wParam: 0 lParam: caret];
+ rect.origin.y += [mOwner message: SCI_TEXTHEIGHT wParam: 0 lParam: 0];
+ rect.size.width = 1.0;
+ rect.size.height = 1.0;
+ NSRect rectInWindow = [[[self superview] superview] convertRect:rect toView:nil];
+ NSPoint pt = rectInWindow.origin;
+ NSEvent *event = [NSEvent mouseEventWithType: NSRightMouseDown
+ location: pt
+ modifierFlags: 0
+ timestamp: 0
+ windowNumber: [[self window] windowNumber]
+ context: nil
+ eventNumber: 0
+ clickCount: 1
+ pressure: 0.0];
+ NSMenu *menu = mOwner.backend->CreateContextMenu(event);
+ [NSMenu popUpContextMenu:menu withEvent:event forView:self];
+ return YES;
+}
+
//--------------------------------------------------------------------------------------------------
- (void) dealloc
diff --git a/doc/ScintillaDoc.html b/doc/ScintillaDoc.html
index 87064b938..9d60800cd 100644
--- a/doc/ScintillaDoc.html
+++ b/doc/ScintillaDoc.html
@@ -347,22 +347,24 @@
<tr>
<td>o <a class="toc" href="#Notifications">Notifications</a></td>
- <td>o <a class="toc" href="#Images">Images</a></td>
+ <td>o <a class="toc" href="#Accessibility">Accessibility</a></td>
- <td>o <a class="toc" href="#GTK">GTK+</a></td>
+ <td>o <a class="toc" href="#Images">Images</a></td>
</tr>
<tr>
+ <td>o <a class="toc" href="#GTK">GTK+</a></td>
+
<td>o <a class="toc" href="#ProvisionalMessages"><span class="provisional">Provisional messages</span></a></td>
<td>o <a class="toc" href="#DeprecatedMessages">Deprecated messages</a></td>
+ </tr>
+ <tr>
<td>o <a class="toc" href="#EditMessagesNeverSupportedByScintilla">Edit messages never
supported by Scintilla</a></td>
- </tr>
- <tr>
<td>o <a class="toc" href="#BuildingScintilla">Building Scintilla</a></td>
</tr>
@@ -7672,6 +7674,15 @@ for line = lineStart to lineEnd do SCI_ENSUREVISIBLE(line) next
<a class="jump" href="#SCN_AUTOCSELECTION">SCN_AUTOCSELECTION</a></code>
notification.</p>
+ <h2 id="Accessibility">Accessibility</h2>
+
+ <p>Scintilla supports some platform accessibility features.
+ This support differs between platforms.
+ On GTK+ and Cocoa the platform accessibility APIs are implemented sufficiently to
+ make screen readers work.
+ On Win32, the system caret is manipulated to help screen readers.
+ </p>
+
<h2 id="Images">Images</h2>
<p>Two formats are supported for images used in margin markers and autocompletion lists, RGBA and XPM.</p>
diff --git a/doc/ScintillaHistory.html b/doc/ScintillaHistory.html
index a1e7a9d82..17880f5ee 100644
--- a/doc/ScintillaHistory.html
+++ b/doc/ScintillaHistory.html
@@ -524,7 +524,7 @@
Released 16 October 2016.
</li>
<li>
- Accessibility supported on GTK+.
+ Accessibility support allowing screen readers to work added on GTK+ and Cocoa.
</li>
<li>
INDIC_POINT and INDIC_POINTCHARACTER indicators added to display small arrows