summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
diff options
context:
space:
mode:
authorBoris Dušek <me@dusek.me>2014-12-28 23:38:38 +0100
committerBoris Dušek <me@dusek.me>2015-02-12 18:29:42 +0000
commit07cd153c7e9382d4c0fed29cf6682f7d0a37645b (patch)
treeeabeece73b43d8ea50d777f1c44b494c0d72672f /src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
parente82d3b1a1e7fce907fa673f0a4040008e95bf019 (diff)
Fix accessibility lines on OS X
Make QTextView accessibility on OS X mirror that of NSTextView in terms of translating between positions and line numbers. Most significantly, we report softlines (i.e. "lines" resulting from visual wrapping induced by text view's width) as individual lines, not just hardlines (i.e. "lines" delimited by newline characters, a.k.a. paragraphs). This fixes keyboard echo when using just arrow up and down (without VO prefix) as now in such case VoiceOver reads the softline that the text cursor moved to; before this fix it read again the whole paragraph (which it read no matter to which softline of paragraph we moved to). This enables the user to search more effectively for the softline they need (which they do very often when navigating text). Further, we changed the behavior to report the trailing newline character of a line as the last character of the line. This is consistent with how NSTextView (and TextEdit) does things (and makes the newline character be displayed on a braille display for the user to be able to distinguish that this is really the end of paragraph), but could be debated and changed as some important Apple apps do not include the newline character in the line range (most notably Pages and Mail, both in the document (or email text) text area). I asked about this here: http://lists.apple.com/archives/accessibility-dev/2015/Jan/msg00000.html This also fixes the case where empty line previously returned empty range (length == 0) for AXRangeForLine and VoiceOver interpreted that as end of document even if it was in the middle of document (e.g. in examples/widgets/richtext/textedit, there is an empty line in "Lists" section just before the last paragraph, if one attempted to move past it e.g with VO-arrow down after interacting with the text using VO-Shift-arrow down, then no luck). The code is currently O(N) as the previous one, which could mean a performance problem for bigger documents. As it seems QTextLayout has all the information it needs to do AXLineForIndex and AXRangeForLine efficiently (I would presume O(log N)), this should be eventually rewritten to take advantage of that (but the required interfaces are not currently exposed through QAccessibleTextInterface) [ChangeLog][QtGui][Accessibility][OS X] QTextEdit now properly reports to accessibility visual lines (softlines) as lines, instead of whole paragraphs. This allows better VoiceOver user experience when reading text line by line using arrows up/down. Change-Id: Ie7f06eb919de31d0a083b3b30d26ccac4aa27301 Reviewed-by: Frederik Gladhorn <frederik.gladhorn@theqtcompany.com>
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm')
-rw-r--r--src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm74
1 files changed, 58 insertions, 16 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
index 3279fd0580..8a6c27605b 100644
--- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
+++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
@@ -40,6 +40,47 @@
#import <AppKit/NSAccessibility.h>
+
+static void convertLineOffset(QAccessibleTextInterface *text, int &line, int &offset, NSUInteger *start = 0, NSUInteger *end = 0)
+{
+ Q_ASSERT(line == -1 || offset == -1);
+ Q_ASSERT(line != -1 || offset != -1);
+ Q_ASSERT(offset <= text->characterCount());
+
+ int curLine = -1;
+ int curStart = 0, curEnd = 0;
+
+ do {
+ curStart = curEnd;
+ text->textAtOffset(curStart, QAccessible::LineBoundary, &curStart, &curEnd);
+ ++curLine;
+ {
+ // check for a case where a single word longer than the text edit's width and gets wrapped
+ // in the middle of the word; in this case curEnd will be an offset belonging to the next line
+ // and therefore nextEnd will not be equal to curEnd
+ int nextStart;
+ int nextEnd;
+ text->textAtOffset(curEnd, QAccessible::LineBoundary, &nextStart, &nextEnd);
+ if (nextEnd == curEnd)
+ ++curEnd;
+ }
+ } while ((line == -1 || curLine < line) && (offset == -1 || (curEnd <= offset)) && curEnd <= text->characterCount());
+
+ curEnd = qMin(curEnd, text->characterCount());
+
+ if (line == -1)
+ line = curLine;
+ if (offset == -1)
+ offset = curStart;
+
+ Q_ASSERT(curStart >= 0);
+ Q_ASSERT(curEnd >= 0);
+ if (start)
+ *start = curStart;
+ if (end)
+ *end = curEnd;
+}
+
@implementation QMacAccessibilityElement
- (id)initWithId:(QAccessible::Id)anId
@@ -284,8 +325,10 @@
} else if ([attribute isEqualToString:NSAccessibilityInsertionPointLineNumberAttribute]) {
if (QAccessibleTextInterface *text = iface->textInterface()) {
- QString textBeforeCursor = text->text(0, text->cursorPosition());
- return [NSNumber numberWithInt: textBeforeCursor.count(QLatin1Char('\n'))];
+ int line = -1;
+ int position = text->cursorPosition();
+ convertLineOffset(text, line, position);
+ return [NSNumber numberWithInt: line];
}
return nil;
} else if ([attribute isEqualToString:NSAccessibilityMinValueAttribute]) {
@@ -340,22 +383,21 @@
}
if ([attribute isEqualToString: NSAccessibilityLineForIndexParameterizedAttribute]) {
int index = [parameter intValue];
- NSNumber *ln = [QMacAccessibilityElement lineNumberForIndex: index forText: iface->text(QAccessible::Value)];
- return ln;
+ if (index < 0 || index > iface->textInterface()->characterCount())
+ return nil;
+ int line = -1;
+ convertLineOffset(iface->textInterface(), line, index);
+ return [NSNumber numberWithInt:line];
}
if ([attribute isEqualToString: NSAccessibilityRangeForLineParameterizedAttribute]) {
- int lineNumber = [parameter intValue];
- QString text = iface->text(QAccessible::Value);
- int startOffset = 0;
- // skip newlines until we have the one we look for
- for (int i = 0; i < lineNumber; ++i)
- startOffset = text.indexOf(QLatin1Char('\n'), startOffset) + 1;
- if (startOffset < 0) // invalid line number, return the first line
- startOffset = 0;
- int endOffset = text.indexOf(QLatin1Char('\n'), startOffset);
- if (endOffset == -1)
- endOffset = text.length();
- return [NSValue valueWithRange:NSMakeRange(quint32(startOffset), quint32(endOffset - startOffset))];
+ int line = [parameter intValue];
+ if (line < 0)
+ return nil;
+ int lineOffset = -1;
+ NSUInteger startOffset = 0;
+ NSUInteger endOffset = 0;
+ convertLineOffset(iface->textInterface(), line, lineOffset, &startOffset, &endOffset);
+ return [NSValue valueWithRange:NSMakeRange(startOffset, endOffset - startOffset)];
}
if ([attribute isEqualToString: NSAccessibilityBoundsForRangeParameterizedAttribute]) {
NSRange range = [parameter rangeValue];