1/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2/* vim:expandtab:shiftwidth=2:tabstop=2:
3 */
4/* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7
8#import "mozTableAccessible.h"
9#import "nsCocoaUtils.h"
10#import "AccIterator.h"
11#import "TableAccessible.h"
12
13@implementation mozColumnContainer
14
15- (id)initWithIndex:(uint32_t)aIndex andParent:(mozAccessible*)aParent {
16  self = [super init];
17  mIndex = aIndex;
18  mParent = aParent;
19  return self;
20}
21
22- (NSString*)moxRole {
23  return NSAccessibilityColumnRole;
24}
25
26- (NSString*)moxRoleDescription {
27  return NSAccessibilityRoleDescription(NSAccessibilityColumnRole, nil);
28}
29
30- (mozAccessible*)moxParent {
31  return mParent;
32}
33
34- (NSArray*)moxChildren {
35  if (mChildren) return mChildren;
36
37  mChildren = [[NSMutableArray alloc] init];
38
39  if (Accessible* acc = [mParent geckoAccessible].AsAccessible()) {
40    TableAccessible* table = acc->AsTable();
41    NSAssert(table, @"Got null table when fetching column children!");
42    uint32_t numRows = table->RowCount();
43
44    for (uint32_t j = 0; j < numRows; j++) {
45      Accessible* cell = table->CellAt(j, mIndex);
46      mozAccessible* nativeCell = cell ? GetNativeFromGeckoAccessible(cell) : nil;
47      if ([nativeCell isAccessibilityElement]) {
48        [mChildren addObject:nativeCell];
49      }
50    }
51
52  } else if (ProxyAccessible* proxy = [mParent geckoAccessible].AsProxy()) {
53    uint32_t numRows = proxy->TableRowCount();
54
55    for (uint32_t j = 0; j < numRows; j++) {
56      ProxyAccessible* cell = proxy->TableCellAt(j, mIndex);
57      mozAccessible* nativeCell = cell ? GetNativeFromGeckoAccessible(cell) : nil;
58      if ([nativeCell isAccessibilityElement]) {
59        [mChildren addObject:nativeCell];
60      }
61    }
62  }
63
64  return mChildren;
65}
66
67- (void)dealloc {
68  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
69
70  [self invalidateChildren];
71  [super dealloc];
72
73  NS_OBJC_END_TRY_ABORT_BLOCK;
74}
75
76- (void)expire {
77  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
78
79  [self invalidateChildren];
80
81  mParent = nil;
82
83  [super expire];
84
85  NS_OBJC_END_TRY_ABORT_BLOCK;
86}
87
88- (BOOL)isExpired {
89  MOZ_ASSERT((mChildren == nil && mParent == nil) == mIsExpired);
90
91  return [super isExpired];
92}
93
94- (void)invalidateChildren {
95  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
96
97  // make room for new children
98  if (mChildren) {
99    [mChildren release];
100    mChildren = nil;
101  }
102
103  NS_OBJC_END_TRY_ABORT_BLOCK;
104}
105
106@end
107
108@implementation mozTablePartAccessible
109
110- (NSString*)moxTitle {
111  return @"";
112}
113
114- (NSString*)moxRole {
115  return [self isLayoutTablePart] ? NSAccessibilityGroupRole : [super moxRole];
116}
117
118- (BOOL)isLayoutTablePart {
119  if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
120    while (acc) {
121      if (acc->IsTable()) {
122        return acc->AsTable()->IsProbablyLayoutTable();
123      }
124      acc = acc->Parent();
125    }
126    return false;
127  }
128
129  if (ProxyAccessible* proxy = mGeckoAccessible.AsProxy()) {
130    while (proxy) {
131      if (proxy->IsTable()) {
132        return proxy->TableIsProbablyForLayout();
133      }
134      proxy = proxy->Parent();
135    }
136  }
137
138  return false;
139}
140
141@end
142
143@implementation mozTableAccessible
144
145- (void)handleAccessibleEvent:(uint32_t)eventType {
146  if (eventType == nsIAccessibleEvent::EVENT_REORDER) {
147    [self invalidateColumns];
148  }
149
150  [super handleAccessibleEvent:eventType];
151}
152
153- (void)dealloc {
154  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
155
156  [self invalidateColumns];
157  [super dealloc];
158
159  NS_OBJC_END_TRY_ABORT_BLOCK;
160}
161
162- (NSNumber*)moxRowCount {
163  MOZ_ASSERT(!mGeckoAccessible.IsNull());
164
165  return mGeckoAccessible.IsAccessible() ? @(mGeckoAccessible.AsAccessible()->AsTable()->RowCount())
166                                         : @(mGeckoAccessible.AsProxy()->TableRowCount());
167}
168
169- (NSNumber*)moxColumnCount {
170  MOZ_ASSERT(!mGeckoAccessible.IsNull());
171
172  return mGeckoAccessible.IsAccessible() ? @(mGeckoAccessible.AsAccessible()->AsTable()->ColCount())
173                                         : @(mGeckoAccessible.AsProxy()->TableColumnCount());
174}
175
176- (NSArray*)moxRows {
177  // Create a new array with the list of table rows.
178  return [[self moxChildren]
179      filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(mozAccessible* child,
180                                                                        NSDictionary* bindings) {
181        return [child isKindOfClass:[mozTableRowAccessible class]];
182      }]];
183}
184
185- (NSArray*)moxColumns {
186  MOZ_ASSERT(!mGeckoAccessible.IsNull());
187
188  if (mColContainers) {
189    return mColContainers;
190  }
191
192  mColContainers = [[NSMutableArray alloc] init];
193  uint32_t numCols = 0;
194
195  if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
196    numCols = acc->AsTable()->ColCount();
197  } else {
198    numCols = mGeckoAccessible.AsProxy()->TableColumnCount();
199  }
200
201  for (uint32_t i = 0; i < numCols; i++) {
202    mozColumnContainer* container = [[mozColumnContainer alloc] initWithIndex:i andParent:self];
203    [mColContainers addObject:container];
204  }
205
206  return mColContainers;
207}
208
209- (NSArray*)moxChildren {
210  return [[super moxChildren] arrayByAddingObjectsFromArray:[self moxColumns]];
211}
212
213- (void)invalidateColumns {
214  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
215  if (mColContainers) {
216    [mColContainers release];
217    mColContainers = nil;
218  }
219  NS_OBJC_END_TRY_ABORT_BLOCK;
220}
221
222@end
223
224@implementation mozTableRowAccessible
225
226- (void)handleAccessibleEvent:(uint32_t)eventType {
227  if (eventType == nsIAccessibleEvent::EVENT_REORDER) {
228    id parent = [self moxParent];
229    if ([parent isKindOfClass:[mozTableAccessible class]]) {
230      [parent invalidateColumns];
231    }
232  }
233
234  [super handleAccessibleEvent:eventType];
235}
236
237- (NSNumber*)moxIndex {
238  mozTableAccessible* parent = (mozTableAccessible*)[self moxParent];
239  return @([[parent moxRows] indexOfObjectIdenticalTo:self]);
240}
241
242@end
243
244@implementation mozTableCellAccessible
245
246- (NSValue*)moxRowIndexRange {
247  MOZ_ASSERT(!mGeckoAccessible.IsNull());
248
249  if (mGeckoAccessible.IsAccessible()) {
250    TableCellAccessible* cell = mGeckoAccessible.AsAccessible()->AsTableCell();
251    return [NSValue valueWithRange:NSMakeRange(cell->RowIdx(), cell->RowExtent())];
252  } else {
253    ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
254    return [NSValue valueWithRange:NSMakeRange(proxy->RowIdx(), proxy->RowExtent())];
255  }
256}
257
258- (NSValue*)moxColumnIndexRange {
259  MOZ_ASSERT(!mGeckoAccessible.IsNull());
260
261  if (mGeckoAccessible.IsAccessible()) {
262    TableCellAccessible* cell = mGeckoAccessible.AsAccessible()->AsTableCell();
263    return [NSValue valueWithRange:NSMakeRange(cell->ColIdx(), cell->ColExtent())];
264  } else {
265    ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
266    return [NSValue valueWithRange:NSMakeRange(proxy->ColIdx(), proxy->ColExtent())];
267  }
268}
269
270- (NSArray*)moxRowHeaderUIElements {
271  MOZ_ASSERT(!mGeckoAccessible.IsNull());
272
273  if (mGeckoAccessible.IsAccessible()) {
274    TableCellAccessible* cell = mGeckoAccessible.AsAccessible()->AsTableCell();
275    AutoTArray<Accessible*, 10> headerCells;
276    cell->RowHeaderCells(&headerCells);
277    return utils::ConvertToNSArray(headerCells);
278  } else {
279    ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
280    nsTArray<ProxyAccessible*> headerCells;
281    proxy->RowHeaderCells(&headerCells);
282    return utils::ConvertToNSArray(headerCells);
283  }
284}
285
286- (NSArray*)moxColumnHeaderUIElements {
287  MOZ_ASSERT(!mGeckoAccessible.IsNull());
288
289  if (mGeckoAccessible.IsAccessible()) {
290    TableCellAccessible* cell = mGeckoAccessible.AsAccessible()->AsTableCell();
291    AutoTArray<Accessible*, 10> headerCells;
292    cell->ColHeaderCells(&headerCells);
293    return utils::ConvertToNSArray(headerCells);
294  } else {
295    ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
296    nsTArray<ProxyAccessible*> headerCells;
297    proxy->ColHeaderCells(&headerCells);
298    return utils::ConvertToNSArray(headerCells);
299  }
300}
301
302@end
303