diff options
| -rw-r--r-- | cocoa/ScintillaCocoa.h | 8 | ||||
| -rw-r--r-- | cocoa/ScintillaCocoa.mm | 95 | ||||
| -rw-r--r-- | cocoa/ScintillaView.h | 3 | ||||
| -rw-r--r-- | cocoa/ScintillaView.mm | 231 | ||||
| -rw-r--r-- | doc/ScintillaDoc.html | 19 | ||||
| -rw-r--r-- | doc/ScintillaHistory.html | 2 | 
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 | 
