1// This file is part of BOINC. 2// http://boinc.berkeley.edu 3// Copyright (C) 2014 University of California 4// 5// BOINC is free software; you can redistribute it and/or modify it 6// under the terms of the GNU Lesser General Public License 7// as published by the Free Software Foundation, 8// either version 3 of the License, or (at your option) any later version. 9// 10// BOINC is distributed in the hope that it will be useful, 11// but WITHOUT ANY WARRANTY; without even the implied warranty of 12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13// See the GNU Lesser General Public License for more details. 14// 15// You should have received a copy of the GNU Lesser General Public License 16// along with BOINC. If not, see <http://www.gnu.org/licenses/>. 17 18// MacAccessiblity.mm 19 20#include "MacGUI.pch" 21#include <Cocoa/Cocoa.h> 22#include <objc/runtime.h> 23 24#include "BOINCBaseFrame.h" 25#include "MainDocument.h" 26#include "BOINCListCtrl.h" 27#include "DlgEventLogListCtrl.h" 28#include "ViewStatistics.h" 29#include "wxPieCtrl.h" 30 31#import <AppKit/NSAccessibility.h> 32 33// I have not been able to get it to work when this is FALSE 34#define RETAIN_KIDS_OF_ROWS TRUE 35 36#if !wxOSX_USE_NATIVE_FLIPPED 37#error: must convert to Quartz coordinates in accessibilityHitTest and NSAccessibilityPositionAttribute 38#endif 39 40#pragma mark === CBOINCListCtrl & CDlgEventLogListCtrl Accessibility Shared Code === 41 42#if USE_NATIVE_LISTCONTROL 43#error: This code assumes wxGenericListCtrl 44#endif 45 46#if (DLG_LISTCTRL_BASE != wxGenericListCtrl) 47#error: This code assumes wxGenericListCtrl 48#endif 49 50// Allowable column numbers are 0 to 15, but 15 is reserved to indicate a full row. 51#define MAX_LIST_COL 16 52 53#define isHeaderFlag (1<<0) 54#define isEventLogFlag (1<<1) 55#define isRowFlag (1<<2) 56 57static UInt32 makeElementIdentifier(SInt32 row, SInt32 col) { 58 UInt32 id = (((row + 1) * MAX_LIST_COL) + (col + 1)) * 2; 59return id; 60} 61 62static void wxRectToNSRect(wxRect &wxr, NSRect &nsr) { 63 nsr.origin.x = wxr.x; 64 nsr.origin.y = wxr.y; 65 nsr.size.width = wxr.width; 66 nsr.size.height = wxr.height; 67} 68 69 70@interface EventLogCellUIElement : NSObject { 71 NSInteger row, col; 72 NSInteger listFlags; 73 BOOL isHeader; 74 BOOL isEventLog; 75 BOOL isRow; // is this needed? 76 wxGenericListCtrl *pList; 77 id parent; 78 NSView *listControlView; 79 NSInteger headerHeight; 80 CBOINCBaseView *BOINCView; 81 82 83} 84- (id)initWithRow:(NSInteger)aRow column:(NSInteger)acol listFlags:(NSInteger)flags listCtrl:(wxGenericListCtrl *)aListCtrl parent:(id)aParent BOINCView:(CBOINCBaseView *)aBOINCView; 85+ (EventLogCellUIElement *)elementWithRow:(NSInteger)aRow column:(NSInteger)acol listFlags:(NSInteger)flags listCtrl:(wxGenericListCtrl *)aListCtrl parent:(id)aParent BOINCView:(CBOINCBaseView *)aBOINCView; 86- (NSInteger)row; 87- (NSInteger)col; 88 89@end 90 91 92@implementation EventLogCellUIElement 93 94- (id)initWithRow:(NSInteger)aRow column:(NSInteger)acol listFlags:(NSInteger)flags listCtrl:(wxGenericListCtrl *)aListCtrl parent:(id)aParent BOINCView:(CBOINCBaseView *)aBOINCView { 95 if (self = [super init]) { 96 row = aRow; 97 col = acol; 98 listFlags = flags; 99 isHeader = (flags & isHeaderFlag) ? YES : NO; 100 isEventLog = (flags & isEventLogFlag) ? YES : NO; 101 isRow = (flags & isRowFlag) ? YES : NO; // is this needed? 102 pList = aListCtrl; 103 parent = aParent; 104 listControlView = pList->GetHandle(); 105 headerHeight = ((wxWindow *)(pList->m_headerWin))->GetSize().y; 106 BOINCView = aBOINCView; 107 } 108 return self; 109} 110 111+ (EventLogCellUIElement *)elementWithRow:(NSInteger)aRow column:(NSInteger)acol listFlags:(NSInteger)flags listCtrl:(wxGenericListCtrl *)aListCtrl parent:(id)aParent BOINCView:(CBOINCBaseView *)aBOINCView { 112#if RETAIN_KIDS_OF_ROWS 113 return [[[EventLogCellUIElement alloc] initWithRow:aRow column:acol listFlags:flags listCtrl:aListCtrl parent:aParent BOINCView:aBOINCView] autorelease]; 114#else 115 return [[[self alloc] initWithRow:aRow column:acol listFlags:flags listCtrl:aListCtrl parent:aParent BOINCView:aBOINCView] autorelease]; 116#endif 117} 118 119- (BOOL)accessibilityIsIgnored { 120 return NO; 121} 122 123- (BOOL)isEqual:(id)object { 124 if ([object isKindOfClass:[EventLogCellUIElement self]]) { 125 EventLogCellUIElement *other = object; 126 return (row == other->row) && (col == other->col) && (listFlags == other->listFlags) && [super isEqual:object]; 127 } else { 128 return NO; 129 } 130} 131 132//TODO: Is this ever actually called? 133- (id)accessibilityHitTest:(NSPoint)point { 134 NSPoint windowPoint; 135#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1070 136 //convertRectFromScreen is not available before OS 10.7 137 if (! [[listControlView window] respondsToSelector: @selector(convertRectFromScreen:)]) { 138#pragma clang diagnostic push 139#pragma clang diagnostic ignored "-Wdeprecated-declarations" 140 windowPoint = [[listControlView window] convertScreenToBase:point]; 141#pragma clang diagnostic pop 142 } else 143#endif 144 { 145 NSRect r1 = NSMakeRect(point.x, point.y, 1, 1); 146 NSRect r2 = [[listControlView window] convertRectFromScreen:r1]; 147 windowPoint = r2.origin; 148 } 149 150 NSPoint localPoint = [listControlView convertPoint:windowPoint fromView:nil]; 151 152 NSInteger i, x = 0, yoff; 153 // First get the position relative to the ListCtrl 154 pList->CalcScrolledPosition(0, 0, &x, &yoff); 155 for (i=0; i<col; i++) { 156 x += pList->GetColumnWidth(i); 157 } 158 NSRect r; 159 r = [listControlView bounds]; 160 r.origin.x = x; 161 r.size.width = pList->GetColumnWidth(col); 162 if (isHeader) { 163 r.size.height = headerHeight; 164 } else { 165 wxRect wxr; 166 pList->GetItemRect(row, wxr); 167 r.origin.y = wxr.y; 168// r.origin.y = r.size.height - wxr.y; // Convert to Quartz coordinates 169 r.size.height = wxr.height; 170 } 171 if (NSPointInRect(localPoint, r)){ 172 return self; 173 } 174 return nil; 175} 176 177- (NSUInteger)hash { 178 // Equal objects must hash the same. 179 return [super hash] + makeElementIdentifier(isHeader ? row : row + 1, col); 180} 181 182- (NSInteger)row { 183 return row; 184} 185 186- (NSInteger)col { 187 return col; 188} 189 190- (void)dealloc { 191 [super dealloc]; 192} 193 194// 195// accessibility protocol 196// 197 198// attributes 199 200- (NSArray *)accessibilityAttributeNames { 201 static NSArray *attributes = nil; 202 if (attributes == nil) { 203 attributes = [[NSArray alloc] initWithObjects: 204 NSAccessibilityWindowAttribute, 205 NSAccessibilityTopLevelUIElementAttribute, 206 NSAccessibilityDescriptionAttribute, 207 NSAccessibilitySizeAttribute, 208 NSAccessibilityPositionAttribute, 209 NSAccessibilityTitleAttribute, 210 NSAccessibilityEnabledAttribute, 211 NSAccessibilityFocusedAttribute, 212 NSAccessibilityRoleAttribute, 213 NSAccessibilityRoleDescriptionAttribute, 214 NSAccessibilityParentAttribute, 215 nil]; 216 } 217 if (isHeader && !isEventLog) { 218 NSMutableArray *temp = [attributes mutableCopy]; 219 [temp addObject:NSAccessibilitySubroleAttribute]; 220 [temp addObject:NSAccessibilitySortDirectionAttribute]; 221 return [temp copy]; 222 } 223 return attributes; 224} 225 226- (id)accessibilityAttributeValue:(NSString *)attribute { 227 if ([attribute isEqualToString:NSAccessibilityParentAttribute]) { 228 return NSAccessibilityUnignoredAncestor(parent); 229 230 } else if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) { 231 return [listControlView window]; 232 233 } else if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute]) { 234 return [listControlView window]; 235 236 } else if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute]) { 237 NSString *desc; 238 wxString s; 239 BOOL isCurrentSortCol = false; 240 241 if (isHeader) { 242 int numCols = pList->GetColumnCount(); 243 if ((!isEventLog) && (BOINCView != nil)) { 244 if (col == BOINCView->m_iColumnIDToColumnIndex[BOINCView->m_iSortColumnID]) { 245 isCurrentSortCol = YES; 246 } 247 } 248 if (isCurrentSortCol) { 249 if (BOINCView->m_bReverseSort) { 250 s.Printf(_("(current sort column %d of %d; descending order)"), col+1, numCols); 251 } else { 252 s.Printf(_("(current sort column %d of %d; ascending order)"), col+1, numCols); 253 } 254 } else { 255 s.Printf(_("(column %d of %d)"), col+1, numCols); 256 } 257 } else { 258 if (pList->GetItemState(row, wxLIST_STATE_SELECTED) & wxLIST_STATE_SELECTED) { 259 if (col == 0) { 260 s.Printf(_("(selected row %d of %d)"), row+1, pList->GetItemCount()); 261 } else { 262 s.Printf(_("(selected row %d)"), row+1); 263 } 264 } else { 265 if (col == 0) { // Row is not selected 266 s.Printf(_("(row %d of %d)"), row+1, pList->GetItemCount()); 267 } else { 268 s.Printf(_("(row %d)"), row+1); 269 } 270 } 271 } 272 desc = [NSString stringWithUTF8String:(char *)(s.utf8_str().data())]; 273 return desc; 274 275 } else if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) { 276 NSSize sz; 277 sz.width = pList->GetColumnWidth(col); 278 if (isHeader) { 279 sz.height = headerHeight; 280 } else { 281 wxRect r; 282 pList->GetItemRect(row, r); 283 sz.height = r.height; 284 } 285 return [NSValue valueWithSize:sz]; 286 287 } else if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) { 288 int i, xoff, yoff; 289 NSPoint pt; 290 pt.x = 0; 291 pt.y = 0; 292 // First get the position relative to the ListCtrl 293 for (i=0; i<col; i++) { 294 pt.x += pList->GetColumnWidth(i); 295 } 296 if (!isHeader) { 297 wxRect r; 298 pList->GetItemRect(row, r); 299 pt.y = r.y; 300 } 301 pList->CalcScrolledPosition(0, 0, &xoff, &yoff); 302 pt.x += xoff; 303 pt.y += headerHeight; 304// pt.y = [listControlView bounds].size.height - headerHeight - pt.y; // Convert to Quartz coordinates 305 306 //Convert the point to global (screen) coordinates 307 NSPoint windowPoint = [listControlView convertPoint:pt toView: nil]; 308 pt = [[listControlView window] convertBaseToScreen:windowPoint]; 309 310 return [NSValue valueWithPoint:pt]; 311 312 } else if ([attribute isEqualToString:NSAccessibilityTitleAttribute]) { 313 wxString str; 314 if (isHeader) { 315 wxListItem headerItem; 316 pList->GetColumn(col, headerItem); 317 str = headerItem.GetText(); 318 } else { 319 str = pList->GetItemText(row, col); 320 if (str.IsEmpty()) { 321 str = _("blank"); 322 } 323 } 324 char *s = (char *)(str.utf8_str().data()); 325 NSString *text = [[NSString alloc] initWithUTF8String:s]; 326 return text; 327 328 } else if ([attribute isEqualToString:NSAccessibilityEnabledAttribute]) { 329 return [NSNumber numberWithBool:YES]; 330 331 } else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { 332 return NO; 333 334 } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { 335 return NSAccessibilityStaticTextRole; 336 337 } else if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) { 338 return NSAccessibilityRoleDescription(NSAccessibilityStaticTextRole, 339 (isHeader && !isEventLog) ? NSAccessibilitySortButtonSubrole : nil); 340 341 } else if ([attribute isEqualToString:NSAccessibilitySubroleAttribute]) { 342 return NSAccessibilitySortButtonSubrole; 343 344 } else if ([attribute isEqualToString:NSAccessibilitySortDirectionAttribute]) { 345 if (col == BOINCView->m_iColumnIDToColumnIndex[BOINCView->m_iSortColumnID]) { 346 return BOINCView->m_bReverseSort ? 347 NSAccessibilityDescendingSortDirectionValue : NSAccessibilityAscendingSortDirectionValue; 348 } else { 349 return NSAccessibilityUnknownSortDirectionValue; 350 } 351 352 } else { 353 return nil; 354 } 355} 356 357- (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute { 358 // return [super accessibilityIsAttributeSettable:attribute]; 359 return NO; 360} 361 362- (NSArray *)accessibilityActionNames { 363 // All cells except EventLog Header accept press action 364 if (!isHeader || !isEventLog) { 365 return [NSArray arrayWithObject:NSAccessibilityPressAction]; 366 } 367 return [NSArray array]; 368} 369 370- (NSString *)accessibilityActionDescription:(NSString *)action { 371 return NSAccessibilityActionDescription(action); 372} 373 374- (void)accessibilityPerformAction:(NSString *)action { 375 if (isHeader) { 376 wxWindowID id = pList->GetId(); 377 wxListEvent event(wxEVT_COMMAND_LIST_COL_CLICK, id); 378 event.m_col = col; 379 pList->GetEventHandler()->AddPendingEvent(event); 380 } else { 381 int i = -1; // Step through all selected items and deselect each one 382 while (1) { 383 i = pList->GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); 384 if (i < 0) break; 385 pList->SetItemState(i, 0, wxLIST_STATE_SELECTED); 386 } 387 // Select the one row the user "clicked" on 388 pList->SetItemState(row, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); 389 } 390} 391 392@end 393 394 395@interface FauxListRow : NSObject { 396 NSInteger row; 397 BOOL isEventLog; 398 wxGenericListCtrl *pList; 399#if RETAIN_KIDS_OF_ROWS 400 NSMutableArray *kids; 401 NSInteger numkids; 402#endif 403 id parent; 404 NSView *listControlView; 405} 406 407+ (FauxListRow *)listRowWithRow:(NSInteger)aRow listFlags:(NSInteger)flags listCtrl:(wxGenericListCtrl *)aListCtrl parent:(id)aParent; 408- (id)initWithRow:(NSInteger)aRow listFlags:(NSInteger)flags listCtrl:(wxGenericListCtrl *)aListCtrl parent:(id)aParent; 409#if RETAIN_KIDS_OF_ROWS 410- (void) adjustKidsIfNeeded; 411#endif 412@end 413 414 415@implementation FauxListRow 416 417- (id)initWithRow:(NSInteger)aRow listFlags:(NSInteger)flags listCtrl:(wxGenericListCtrl *)aListCtrl parent:(id)aParent { 418 if (self = [super init]) { 419 row = aRow; 420 pList = aListCtrl; 421 isEventLog = (flags & isEventLogFlag) ? YES : NO; 422#if RETAIN_KIDS_OF_ROWS 423 numkids = 0; 424 kids = nil; 425#endif 426 parent = aParent; 427 listControlView = pList->GetHandle(); 428 } 429 return self; 430} 431 432+ (FauxListRow *)listRowWithRow:(NSInteger)aRow listFlags:(NSInteger)flags listCtrl:(wxGenericListCtrl *)aListCtrl parent:(id)aParent { 433#if RETAIN_KIDS_OF_ROWS 434 return [[[FauxListRow alloc]initWithRow:aRow listFlags:flags listCtrl:aListCtrl parent:aParent] autorelease]; 435#else 436 return [[[self alloc]initWithRow:aRow listFlags:flags listCtrl:aListCtrl parent:aParent] autorelease]; 437#endif 438} 439 440- (BOOL)accessibilityIsIgnored { 441 return NO; 442} 443 444- (BOOL)isEqual:(id)object { 445 if ([object isKindOfClass:[FauxListRow self]]) { 446 FauxListRow *other = object; 447 return (row == other->row) && (isEventLog == other->isEventLog) && [super isEqual:object]; 448 } else { 449 return NO; 450 } 451} 452 453- (id)accessibilityHitTest:(NSPoint)point { 454 NSPoint windowPoint; 455#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1070 456 //convertRectFromScreen is not available before OS 10.7 457 if (! [[listControlView window] respondsToSelector: @selector(convertRectFromScreen:)]) { 458#pragma clang diagnostic push 459#pragma clang diagnostic ignored "-Wdeprecated-declarations" 460 windowPoint = [[listControlView window] convertScreenToBase:point]; 461#pragma clang diagnostic pop 462 } else 463#endif 464 { 465 NSRect r1 = NSMakeRect(point.x, point.y, 1, 1); 466 NSRect r2 = [[listControlView window] convertRectFromScreen:r1]; 467 windowPoint = r2.origin; 468 } 469 470 NSPoint localPoint = [listControlView convertPoint:windowPoint fromView:nil]; 471 472//TODO: should we just generate temporary EventLogCellUIElement objects as needed? 473#if RETAIN_KIDS_OF_ROWS 474 [self adjustKidsIfNeeded]; 475#endif 476 477 NSRect r; 478 wxRect wxcr = pList->GetClientRect(); 479 wxRect wxr; 480 pList->GetItemRect(row, wxr); 481 482 NSInteger col, numCols, x = 0, yoff; 483 pList->CalcScrolledPosition(0, 0, &x, &yoff); 484 485 numCols = pList->GetColumnCount(); 486 for (col=0; col<numCols; col++) { 487 // First get the position relative to the ListCtrl 488 r = [listControlView bounds]; 489 r.origin.x = x; 490 r.size.width = pList->GetColumnWidth(col); 491 r.origin.y = wxr.y; 492// r.origin.y = r.size.height - wxr.y; // Convert to Quartz coordinates 493 r.size.height = wxr.height; 494 if (NSPointInRect(localPoint, r)){ 495#if RETAIN_KIDS_OF_ROWS 496 return (EventLogCellUIElement *)[kids objectAtIndex:col]; 497#else 498 NSInteger myFlags = isRowFlag | (isEventLog ? isEventLogFlag : 0); 499 EventLogCellUIElement * cell = [EventLogCellUIElement elementWithRow:row column:col listFlags:myFlags listCtrl:pList parent:self BOINCView:nil]; 500 return cell; 501#endif 502 } 503 504 x += r.size.width; 505 } 506 507 r = [listControlView bounds]; 508 r.origin.x = 0; 509 r.size.width = wxcr.width; 510 r.size.height = wxcr.height; 511 r.origin.y = wxr.y; 512// r.origin.y = r.size.height - wxr.y; // Convert to Quartz coordinates 513 r.size.height = wxr.height; 514 if (NSPointInRect(localPoint, r)){ 515 return self; 516 } 517// return [super accessibilityHitTest:point]; 518 return nil; 519} 520 521 522- (NSUInteger)hash { 523 // Equal objects must hash the same. 524 return [super hash] + makeElementIdentifier(row, MAX_LIST_COL-1); 525} 526 527#if RETAIN_KIDS_OF_ROWS 528- (void)adjustKidsIfNeeded { 529 if (kids == nil) { 530 numkids = pList->GetColumnCount(); 531 kids = [[NSMutableArray arrayWithCapacity:numkids] retain]; 532 // The number of columns never changes 533 NSInteger myFlags = isRowFlag | (isEventLog ? isEventLogFlag : 0); 534 for (NSInteger i = 0; i < numkids; ++i) { 535 EventLogCellUIElement * cell = [EventLogCellUIElement elementWithRow:row column:i listFlags:myFlags listCtrl:pList parent:self BOINCView:nil]; 536 [cell retain]; 537 [kids addObject:cell]; 538 } 539 } 540} 541#endif 542 543// 544// accessibility protocol 545// 546 547// attributes 548 549- (NSArray *)accessibilityAttributeNames { 550 static NSArray *attributes = nil; 551 if (attributes == nil) { 552 attributes = [[NSArray alloc] initWithObjects: 553 NSAccessibilityRoleAttribute, 554 NSAccessibilityRoleDescriptionAttribute, 555 NSAccessibilitySubroleAttribute, 556// NSAccessibilityDescriptionAttribute, 557 NSAccessibilityTitleAttribute, 558 NSAccessibilityEnabledAttribute, 559 NSAccessibilityFocusedAttribute, 560 NSAccessibilityParentAttribute, 561 NSAccessibilityWindowAttribute, 562 NSAccessibilityTopLevelUIElementAttribute, 563 NSAccessibilityPositionAttribute, 564 NSAccessibilitySizeAttribute, 565 NSAccessibilityChildrenAttribute, 566 NSAccessibilityVisibleChildrenAttribute, 567 NSAccessibilityWindowAttribute, 568 NSAccessibilityIndexAttribute, 569 NSAccessibilitySelectedAttribute, 570 nil]; 571 } 572 return attributes; 573} 574 575- (id)accessibilityAttributeValue:(NSString *)attribute { 576 if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { 577 return NSAccessibilityRowRole; 578 579 } else if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) { 580 return NSAccessibilityRoleDescription(NSAccessibilityRowRole, nil); 581 582 } else if ([attribute isEqualToString:NSAccessibilitySubroleAttribute]) { 583 return NSAccessibilityTableRowSubrole; 584 585 } else if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute]) { 586 return [parent window]; 587 588 } else if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute]) { 589 wxString s; 590 s.Printf(_("(row %d)"), row+1); 591 NSString *desc = [NSString stringWithUTF8String:(char *)(s.utf8_str().data())]; 592 return desc; 593 594 } else if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) { 595 NSSize sz; 596 wxRect r; 597 pList->GetItemRect(row, r); 598 sz.height = r.height; 599 sz.width = r.width; 600 return [NSValue valueWithSize:sz]; 601 602 } else if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) { 603 int xoff, yoff; 604 NSPoint pt; 605 // First get the position relative to the ListCtrl 606 wxRect r; 607 pList->GetItemRect(row, r); 608 pList->CalcScrolledPosition(0, 0, &xoff, &yoff); 609 pt.x = xoff; 610 pt.y = r.y; 611// pt.y = [parent bounds].size.height - pt.y; // Convert to Quartz coordinates 612 613 //Convert the point to global (screen) coordinates 614 NSPoint windowPoint = [parent convertPoint:pt toView: nil]; 615 pt = [[parent window] convertBaseToScreen:windowPoint]; 616 617 return [NSValue valueWithPoint:pt]; 618 619 } else if ([attribute isEqualToString:NSAccessibilityTitleAttribute]) { 620 wxString s; 621 s.Printf(_("row %d"), row+1); 622 NSString *title = [NSString stringWithUTF8String:(char *)(s.utf8_str().data())]; 623 return title; 624 625 } else if ([attribute isEqualToString:NSAccessibilityEnabledAttribute]) { 626 return [NSNumber numberWithBool:YES]; 627 628 } else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { 629 return NO; 630 631 } else if ([attribute isEqualToString:NSAccessibilityParentAttribute]) { 632 return NSAccessibilityUnignoredAncestor(parent); 633 634 } else if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { 635#if RETAIN_KIDS_OF_ROWS 636 [self adjustKidsIfNeeded]; 637#else 638 NSInteger i; 639 NSInteger numkids = pList->GetColumnCount(); 640 641 NSMutableArray *kids = [NSMutableArray arrayWithCapacity:numkids]; // autorelease ?? 642 NSInteger myFlags = isRowFlag | (isEventLog ? isEventLogFlag : 0); 643 for (i = 0; i < numkids; ++i) { 644 EventLogCellUIElement * cell = [EventLogCellUIElement elementWithRow:row column:i listFlags:myFlags listCtrl:pList parent:self BOINCView:nil]; // Retain??? 645 [kids addObject:cell]; 646 } 647#endif 648 return kids; 649// return NSAccessibilityUnignoredChildren(kids); // No children are ignored 650 651 } else if ([attribute isEqualToString:NSAccessibilityVisibleChildrenAttribute]) { 652 int w, h, xoff, yoff; 653 pList->GetClientSize(&w, &h); 654 pList->CalcUnscrolledPosition(0, 0, &xoff, &yoff); 655 int leftVisiblePos = xoff; // Horizontal scrolled offset in pixels 656 int rightVisiblePos = leftVisiblePos + w; 657 658 NSInteger i, cellLeftEdge = 0, cellRightEdge; 659 NSMutableArray *visibleChildren = [NSMutableArray array]; 660 661#if RETAIN_KIDS_OF_ROWS 662 [self adjustKidsIfNeeded]; 663#else 664 NSInteger numkids = pList->GetColumnCount(); 665#endif 666 for (i = 0; i < numkids; ++i) { 667 BOOL isVisible = NO; 668 cellRightEdge = cellLeftEdge + pList->GetColumnWidth(i); 669 if ((leftVisiblePos <= cellLeftEdge) && rightVisiblePos >= cellLeftEdge) { 670 isVisible = YES; // Left edge of cell is in visible area of row 671 } else if ((leftVisiblePos <= cellRightEdge) && rightVisiblePos >= cellRightEdge) { 672 isVisible = YES; // Right edge of cell is in visible area of row 673 } else if ((leftVisiblePos > cellLeftEdge) && (rightVisiblePos < cellRightEdge)) { 674 isVisible = YES; // Visible area of row is totally within cell 675 } 676 677 if (isVisible) { 678#if RETAIN_KIDS_OF_ROWS 679 [visibleChildren addObject:[kids objectAtIndex:i] ]; 680#else 681 NSInteger myFlags = isRowFlag | (isEventLog ? isEventLogFlag : 0); 682 EventLogCellUIElement * cell = [EventLogCellUIElement elementWithRow:row column:i listFlags:myFlags listCtrl:pList parent:self BOINCView:nil]; // Retain??? 683 [visibleChildren addObject:cell]; 684#endif 685 } 686 687 cellLeftEdge = cellRightEdge; 688 } 689 690 return visibleChildren; 691 692 } else if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) { 693 return [parent window]; 694 695 } else if ([attribute isEqualToString:NSAccessibilityIndexAttribute]) { 696 return [NSNumber numberWithInt:row]; 697 698 } else if ([attribute isEqualToString:NSAccessibilitySelectedAttribute]) { 699 BOOL isSelected = pList->GetItemState(row, wxLIST_STATE_SELECTED) & wxLIST_STATE_SELECTED; 700 return [NSNumber numberWithBool:isSelected]; 701 702 } else { 703 return nil; 704// return [super accessibilityAttributeValue:attribute]; 705 } 706} 707 708- (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute { 709 // return [super accessibilityIsAttributeSettable:attribute]; 710 return NO; 711} 712 713- (NSArray *)accessibilityActionNames { 714 return [NSArray array]; 715} 716 717- (NSString *)accessibilityActionDescription:(NSString *)action { 718 return nil; 719} 720 721- (void)accessibilityPerformAction:(NSString *)action { 722} 723 724- (void)dealloc { 725#if RETAIN_KIDS_OF_ROWS 726 for (NSInteger i = 0; i < numkids; ++i) { 727 [(EventLogCellUIElement*)[kids objectAtIndex:i] release]; 728 } 729//TODO: Do we need to delete or free up kids array? 730 if (kids) [kids release]; 731#endif 732 [super dealloc]; 733} 734 735@end 736 737 738 739@interface FauxListBodyView : NSView { 740 BOOL isHeader; 741 BOOL isEventLog; 742 wxGenericListCtrl *pList; 743 NSMutableArray *kids; 744 NSInteger numkids; 745 id parent; 746 CBOINCBaseView *BOINCView; 747} 748 749- (id)initWithFrame:(NSRect)frame listCtrl:(wxGenericListCtrl *)aListCtrl listFlags:(NSInteger)flags parent:aParent BOINCView:(CBOINCBaseView *)aBOINCView; 750- (void) adjustKidsIfNeeded; 751 752@end 753 754 755@implementation FauxListBodyView 756 757- (id)initWithFrame:(NSRect)frame listCtrl:(wxGenericListCtrl *)aListCtrl listFlags:(NSInteger)flags parent:aParent BOINCView:(CBOINCBaseView *)aBOINCView { 758 if ((self = [super initWithFrame:frame]) != nil) { 759 pList = aListCtrl; 760 isEventLog = (flags & isEventLogFlag) ? YES : NO; 761 numkids = 0; 762 kids = nil; 763 parent = aParent; 764 BOINCView = aBOINCView; 765 } 766 return self; 767} 768 769- (BOOL)isFlipped { 770 return YES; 771} 772 773- (BOOL)accessibilityIsIgnored { 774 return NO; 775} 776 777- (id)accessibilityHitTest:(NSPoint)point { 778 NSInteger rowNumber; 779 id item; 780 781 [self adjustKidsIfNeeded]; 782 783 NSPoint windowPoint; 784#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1070 785 //convertRectFromScreen is not available before OS 10.7 786 if (! [[parent window] respondsToSelector: @selector(convertRectFromScreen:)]) { // parent == listControlView 787#pragma clang diagnostic push 788#pragma clang diagnostic ignored "-Wdeprecated-declarations" 789 windowPoint = [[parent window] convertScreenToBase:point]; 790#pragma clang diagnostic pop 791 } else 792#endif 793 { 794 NSRect r1 = NSMakeRect(point.x, point.y, 1, 1); 795 NSRect r2 = [[parent window] convertRectFromScreen:r1]; 796 windowPoint = r2.origin; 797 } 798 799 NSPoint localPoint = [parent convertPoint:windowPoint fromView:nil]; 800 801 // The scroll bars are among the list control's subviews. 802 // If we are outside the list control's client area, determine 803 // which scroll bar we are over and return the info for it. 804 wxRect wxcr = pList->GetClientRect(); 805 NSRect cr; 806 wxRectToNSRect(wxcr, cr); 807 if (!NSPointInRect(localPoint, cr)) { 808 NSArray *mySubViews = [parent subviews]; 809 NSInteger nSubViews = [mySubViews count]; 810 for (NSInteger i=0; i<nSubViews; i++) { 811 NSView *aView = [mySubViews objectAtIndex:i]; 812 if ([aView isKindOfClass:[NSScroller class]]) { 813 if (NSPointInRect(localPoint, [aView frame ])) { 814 return [[mySubViews objectAtIndex:i] accessibilityHitTest:point]; 815 } 816 } 817 } 818 } 819 820 if (kids) { 821 // Check only rows that are visible 822 NSInteger firstVisibleRow = pList->GetTopItem(); 823 NSInteger numVisibleRows = pList->GetCountPerPage(); 824 825 if (numkids <= (firstVisibleRow + numVisibleRows)) { 826 numVisibleRows = numkids - firstVisibleRow; 827 } 828 // This allows for an additional partially visible row 829 NSInteger lastVisibleRow = firstVisibleRow + numVisibleRows; 830 if (lastVisibleRow > (numkids - 1)) { 831 lastVisibleRow = (numkids - 1); 832 } 833 834 for (rowNumber = firstVisibleRow; rowNumber <= lastVisibleRow; ++rowNumber) { 835 NSRect r; 836 r = [parent bounds]; 837 r.origin.x = 0; 838 r.size.width = wxcr.width; 839 r.size.height = wxcr.height; 840 841 wxRect wxr; 842 pList->GetItemRect(rowNumber, wxr); 843 r.origin.y = wxr.y; 844 // r.origin.y = r.size.height - wxr.y; // Convert to Quartz coordinates 845 r.size.height = wxr.height; 846 if (!NSPointInRect(localPoint, r)) continue; 847 848//TODO: should we just generate a temporary FauxListRow object as needed? 849 FauxListRow *row = (FauxListRow *)[kids objectAtIndex:rowNumber]; 850 item = [row accessibilityHitTest:point]; 851 if (item != nil) { 852 return item; 853 } 854 } 855 } 856 857 return self; 858// return [super accessibilityHitTest:point]; 859} 860 861- (void)adjustKidsIfNeeded { 862 NSInteger i, newNumKids; 863 newNumKids = pList->GetItemCount(); // Number of rows 864 865 if (kids == nil) { 866 kids = [[NSMutableArray arrayWithCapacity:newNumKids] retain]; 867 } 868 if (newNumKids < numkids) { 869 for (i = numkids; i > newNumKids; i--) { 870 [[kids objectAtIndex:i-1] release]; 871 [kids removeLastObject]; 872 } 873 numkids = newNumKids; 874 } else if (newNumKids > numkids) { 875 NSInteger myFlags = isRowFlag | (isEventLog ? isEventLogFlag : 0); 876 for (i = numkids; i < newNumKids; ++i) { 877 FauxListRow *row = [FauxListRow listRowWithRow:i listFlags:myFlags listCtrl:pList parent:self]; 878 [row retain]; 879 [kids addObject:row]; 880 } 881 numkids = newNumKids; 882 } 883} 884 885 886// 887// accessibility protocol 888// 889 890// attributes 891 892- (NSArray *)accessibilityAttributeNames { 893 static NSArray *attributes = nil; 894 if (attributes == nil) { 895 attributes = [[NSArray alloc] initWithObjects: 896 NSAccessibilityRoleAttribute, 897 NSAccessibilityRoleDescriptionAttribute, 898 NSAccessibilityFocusedAttribute, 899 NSAccessibilityParentAttribute, 900 NSAccessibilityWindowAttribute, 901 NSAccessibilityTopLevelUIElementAttribute, 902 NSAccessibilityPositionAttribute, 903 NSAccessibilitySizeAttribute, 904 NSAccessibilityChildrenAttribute, 905 NSAccessibilityVisibleChildrenAttribute, 906 NSAccessibilitySelectedChildrenAttribute, 907 NSAccessibilitySelectedRowsAttribute, 908 NSAccessibilityOrientationAttribute, 909// NSAccessibilityHeaderAttribute, 910// NSAccessibilityColumnsAttribute, 911// NSAccessibilityRowsAttribute, 912// NSAccessibilitySelectedColumnsAttribute, 913// NSAccessibilityVisibleColumnsAttribute 914// NSAccessibilityVisibleRowsAttribute, 915 NSAccessibilityDescriptionAttribute, 916 nil]; 917 } 918 return attributes; 919} 920 921- (id)accessibilityAttributeValue:(NSString *)attribute { 922 if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { 923 return NSAccessibilityListRole; 924 925 } else if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) { 926 return NSAccessibilityRoleDescription(NSAccessibilityListRole, nil); 927 928 } else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { 929 return NO; 930 931 } else if ([attribute isEqualToString:NSAccessibilityParentAttribute]) { 932 return NSAccessibilityUnignoredAncestor(parent); 933 934 } else if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) { 935 // We're in the same window as our parent. 936 return [parent window]; 937 938 } else if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute]) { 939 return [parent window]; 940 941 } else if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute]) { 942 // To allow localization, we can't just append string 943 // " is empty" because that assumes English word order. 944 wxString s; 945 if (isEventLog) { 946 if (numkids) { 947 s = _("Event Log"); 948 } else { 949 s = _("Event Log is empty"); 950 } 951 } else { 952 wxString viewName = BOINCView->GetViewDisplayName(); 953 if (numkids) { 954 s.Printf(_("%s"), viewName.c_str()); 955 } else { 956 s.Printf(_("You currently have no %s"), viewName.c_str()); 957 } 958 } 959 NSString *desc = [NSString stringWithUTF8String:(char *)(s.utf8_str().data())]; 960 return desc; 961 962 } else if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) { 963 return [NSValue valueWithSize:[parent frame].size]; 964 965 } else if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) { 966 NSPoint pt = [parent bounds].origin; 967 pt.y += [parent bounds].size.height; // We need the bottom left corner 968 //Convert the point to global (screen) coordinates 969 NSPoint windowPoint = [parent convertPoint:pt toView: nil]; 970 pt = [[parent window] convertBaseToScreen:windowPoint]; 971 972 return [NSValue valueWithPoint:pt]; 973 974 } else if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { 975 [self adjustKidsIfNeeded]; 976 return kids; 977// return NSAccessibilityUnignoredChildren(kids); // No children are ignored 978 979 } else if ([attribute isEqualToString:NSAccessibilityVisibleChildrenAttribute]) { 980 NSInteger i; 981 NSInteger firstVisibleKid = pList->GetTopItem(); 982 NSInteger numVisibleKids = pList->GetCountPerPage(); 983 NSMutableArray *visibleChildren = [NSMutableArray array]; 984 985 [self adjustKidsIfNeeded]; 986 987//TODO: allow for additional partially visible rows at bottom?? 988 if (numkids <= (firstVisibleKid + numVisibleKids)) { 989 numVisibleKids = numkids - firstVisibleKid; 990 } 991 for (i = 0; i < numVisibleKids; ++i) { 992 [visibleChildren addObject:[kids objectAtIndex:i+firstVisibleKid] ]; 993 } 994 995 return visibleChildren; 996 997 } else if ([attribute isEqualToString:NSAccessibilitySelectedChildrenAttribute] || 998 [attribute isEqualToString:NSAccessibilitySelectedRowsAttribute]) { 999 NSMutableArray *selectedChildren = [NSMutableArray array]; 1000 1001 int i = -1; // Step through all selected items 1002 while (1) { 1003 i = pList->GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); 1004 if (i < 0) break; 1005 [selectedChildren addObject:[kids objectAtIndex:i] ]; 1006 } 1007 return selectedChildren; 1008 1009 } else if ([attribute isEqualToString:NSAccessibilityOrientationAttribute]) { 1010 return NSAccessibilityVerticalOrientationValue; 1011 1012 } else { 1013 return [super accessibilityAttributeValue:attribute]; 1014 } 1015} 1016 1017- (NSArray *)accessibilityActionNames { 1018 return [NSArray arrayWithObject:NSAccessibilityPressAction]; 1019} 1020 1021- (NSString *)accessibilityActionDescription:(NSString *)action { 1022 return NSAccessibilityActionDescription(action); 1023} 1024 1025// If user does a simulated click below last row, deselect all rows 1026- (void)accessibilityPerformAction:(NSString *)action { 1027 if ([self accessibilityHitTest:[NSEvent mouseLocation]] == self) { 1028 int i = -1; // Step through all selected items and deselect each one 1029 while (1) { 1030 i = pList->GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); 1031 if (i < 0) break; 1032 pList->SetItemState(i, 0, wxLIST_STATE_SELECTED); 1033 } 1034 } 1035} 1036 1037- (void)dealloc { 1038//TODO: remove this if we don't retain rows in kids 1039 for (NSInteger i = 0; i < numkids; ++i) { 1040 [(FauxListRow *)[kids objectAtIndex:i] release]; 1041 } 1042//TODO: Do we need to delete or free up kids array? 1043 if (kids) [kids release]; 1044 [super dealloc]; 1045} 1046 1047@end 1048 1049 1050@interface FauxListHeaderView : NSView { 1051 wxGenericListCtrl *pList; 1052 BOOL isEventLog; 1053 NSMutableArray *kids; 1054 NSInteger numkids; 1055 id parent; 1056 CBOINCBaseView *BOINCView; 1057} 1058 1059- (id)initWithFrame:(NSRect)frame listCtrl:(wxGenericListCtrl *)aListCtrl listFlags:(NSInteger)flags parent:aParent BOINCView:(CBOINCBaseView *)aBOINCView; 1060 1061@end 1062 1063 1064@implementation FauxListHeaderView 1065 1066- (id)initWithFrame:(NSRect)frame listCtrl:(wxGenericListCtrl *)aListCtrl listFlags:(NSInteger)flags parent:aParent BOINCView:(CBOINCBaseView *)aBOINCView 1067{ 1068 if ((self = [super initWithFrame:frame]) != nil) { 1069 pList = aListCtrl; 1070 isEventLog = (flags & isEventLogFlag) ? YES : NO; 1071 numkids = 0; 1072 kids = nil; 1073 parent = aParent; 1074 BOINCView = aBOINCView; 1075 } 1076 return self; 1077} 1078 1079- (BOOL)isFlipped { 1080 return YES; 1081} 1082 1083// wxWidgetCocoaImpl::mouseEvent() discards NSMouseMoved events unless 1084// they are for the deepest child in the hierarchy, so the cursor is 1085// not adjusted over wxListCtrl header column separators unless hitTest 1086// reports the real wxListHeaderWindow as the deepest (topmost) view. 1087// But the Accessibility logic doesn work unless hitTest reports our 1088// FauxListHeaderView as the deepest (topmost) view. 1089// To work around this, we make the real wxListHeaderWindow a subview 1090// of our FauxListHeaderView, but FauxListHeaderView reports itself 1091// as the deepest view when [NSWindowAccessibility accessibilityHitTest] 1092// is somewhere in the caller chain. 1093// I wish I could find a more efficient way to do this. 1094// 1095static BOOL AccessibilityEnabled = false; 1096 1097- (NSView *)hitTest:(NSPoint)aPoint { 1098 // [NSThread callStackSymbols] is not available in OS 10.5, so 1099 // BOINC does not fully implement accessibility under OS 10.5. 1100 // 1101 // Weak linking of objective-C classes and methods is not 1102 // supported before OS 10.6.8 so to be compatible with 1103 // OS 10.5 we must test availability at run time. 1104 // 1105 static BOOL firstTime = true; 1106 static BOOL haveMethod = false; 1107 1108 if (AccessibilityEnabled) { 1109 if (firstTime) { 1110 IMP callStackSyms = class_getMethodImplementation(objc_getClass("NSThread"), @selector(callStackSymbols)); 1111 haveMethod = (callStackSyms != nil); 1112 firstTime = false; 1113 } 1114 1115 if (!haveMethod) { 1116 return [super hitTest:aPoint]; 1117 } 1118 1119 NSRect r = [parent bounds]; 1120 r.size.height = [self bounds].size.height; 1121 if (!NSPointInRect(aPoint, r)){ 1122 return [super hitTest:aPoint]; // Point is not within our rect 1123 } 1124 1125 // NSArray *theStack = [NSThread callStackSymbols]; 1126 NSArray *theStack = [ NSThread performSelector:@selector(callStackSymbols) ]; 1127 1128 int limit = [ theStack count ]; 1129 int i = 0; 1130 do { 1131 if (limit < (i+1)) break; 1132 NSString *sourceString = [theStack objectAtIndex:i]; 1133 NSCharacterSet *separatorSet = [NSCharacterSet characterSetWithCharactersInString:@" -[]+?.,"]; 1134 NSMutableArray *array = [NSMutableArray arrayWithArray:[sourceString componentsSeparatedByCharactersInSet:separatorSet]]; 1135 [array removeObject:@""]; 1136 1137 if ([array count] >= 5) { 1138 NSString *FunctionCaller = [array objectAtIndex:4]; 1139 if ([ FunctionCaller hasPrefix: @"accessibility"]) { 1140 return self; 1141 } 1142 1143 } 1144 ++i; 1145 } while (i < 15); 1146 } 1147 1148 return [super hitTest:aPoint]; // Not an accessibility call 1149} 1150 1151 1152- (BOOL)accessibilityIsIgnored { 1153 AccessibilityEnabled = true; 1154 return NO; 1155} 1156 1157- (id)accessibilityHitTest:(NSPoint)point { 1158 NSPoint windowPoint; 1159#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1070 1160 //convertRectFromScreen is not available before OS 10.7 1161 if (! [[parent window] respondsToSelector: @selector(convertRectFromScreen:)]) { // parent == listControlView 1162#pragma clang diagnostic push 1163#pragma clang diagnostic ignored "-Wdeprecated-declarations" 1164 windowPoint = [[parent window] convertScreenToBase:point]; 1165#pragma clang diagnostic pop 1166 } else 1167#endif 1168 { 1169 NSRect r1 = NSMakeRect(point.x, point.y, 1, 1); 1170 NSRect r2 = [[parent window] convertRectFromScreen:r1]; 1171 windowPoint = r2.origin; 1172 } 1173 1174 NSPoint localPoint = [parent convertPoint:windowPoint fromView:nil]; 1175 1176 // The scroll bars are among the list control's subviews. 1177 // If we are outside the list control's client area, determine 1178 // which scroll bar we are over and return the info for it. 1179 wxRect wxcr = pList->GetClientRect(); 1180 NSRect cr; 1181 wxRectToNSRect(wxcr, cr); 1182 if (!NSPointInRect(localPoint, cr)) { 1183 NSArray *mySubViews = [parent subviews]; 1184 NSInteger nSubViews = [mySubViews count]; 1185 for (NSInteger i=0; i<nSubViews; i++) { 1186 NSView *aView = [mySubViews objectAtIndex:i]; 1187 if ([aView isKindOfClass:[NSScroller class]]) { 1188 if (NSPointInRect(localPoint, [aView frame ])) { 1189 return [[mySubViews objectAtIndex:i] accessibilityHitTest:point]; 1190 } 1191 } 1192 } 1193 } 1194 1195 if (kids) { 1196 NSInteger col, numCols, x = 0, yoff; 1197 pList->CalcScrolledPosition(0, 0, &x, &yoff); 1198 1199 numCols = pList->GetColumnCount(); 1200 for (col=0; col<numCols; col++) { 1201 // First get the position relative to the ListCtrl 1202 NSRect r = [parent bounds]; 1203 r.origin.x = x; 1204 r.size.width = pList->GetColumnWidth(col); 1205 r.size.height = [self bounds].size.height; 1206 if (NSPointInRect(localPoint, r)){ 1207 return (EventLogCellUIElement *)[kids objectAtIndex:col]; 1208 } 1209 x += r.size.width; 1210 } 1211 } 1212 return self; 1213// return [super accessibilityHitTest:point]; 1214} 1215 1216// 1217// accessibility protocol 1218// 1219 1220// attributes 1221 1222- (NSArray *)accessibilityAttributeNames { 1223 static NSArray *attributes = nil; 1224 if (attributes == nil) { 1225 attributes = [[NSArray alloc] initWithObjects: 1226 NSAccessibilityRoleAttribute, 1227 NSAccessibilityRoleDescriptionAttribute, 1228 NSAccessibilityFocusedAttribute, 1229 NSAccessibilityParentAttribute, 1230 NSAccessibilityWindowAttribute, 1231 NSAccessibilityTopLevelUIElementAttribute, 1232 NSAccessibilityPositionAttribute, 1233 NSAccessibilitySizeAttribute, 1234 NSAccessibilityChildrenAttribute, 1235//TODO: Why does NSAccessibilityDescriptionAttribute for header view prevent 1236//TODO: its NSAccessibilityChildrenAttribute from ever being called ??? 1237// NSAccessibilityDescriptionAttribute, 1238 nil]; 1239 } 1240 return attributes; 1241} 1242 1243- (id)accessibilityAttributeValue:(NSString *)attribute { 1244 if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { 1245 return NSAccessibilityGroupRole; 1246 1247 } else if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) { 1248 return NSAccessibilityRoleDescription(NSAccessibilityGroupRole, nil); 1249 1250 } else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { 1251 return NO; 1252 1253 } else if ([attribute isEqualToString:NSAccessibilityParentAttribute]) { 1254 return NSAccessibilityUnignoredAncestor(parent); 1255 1256 } else if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) { 1257 // We're in the same window as our parent. 1258 return [parent window]; 1259 1260 } else if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute]) { 1261 return [parent window]; 1262 1263 } else if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) { 1264 return [NSValue valueWithSize:[self frame].size]; 1265 1266 } else if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) { 1267 NSPoint pt = [self bounds].origin; 1268 pt.y += [self bounds].size.height; // We need the bottom left corner 1269 //Convert the point to global (screen) coordinates 1270// NSPoint windowPoint = [self convertPoint:pt toView: nil]; 1271 NSPoint windowPoint = [parent convertPoint:pt toView: nil]; 1272 pt = [[parent window] convertBaseToScreen:windowPoint]; 1273 1274 return [NSValue valueWithPoint:pt]; 1275 1276//TODO: Why does NSAccessibilityDescriptionAttribute for header view prevent 1277//TODO: its NSAccessibilityChildrenAttribute from ever being called ??? 1278 } else if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute]) { 1279 wxString s; 1280 if (isEventLog) { 1281 s = _("Header for Event Log"); 1282 } else { 1283 wxString viewName = BOINCView->GetViewDisplayName(); 1284 s.Printf(_("Header for %s"), viewName.c_str()); 1285 } 1286 NSString *desc = [NSString stringWithUTF8String:(char *)(s.utf8_str().data())]; 1287 return desc; 1288 1289 } else if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { 1290 if (kids == nil) { 1291 numkids = pList->GetColumnCount(); 1292 kids = [[NSMutableArray arrayWithCapacity:numkids] retain]; 1293 // The number of columns never changes 1294 NSInteger myFlags = isHeaderFlag | (isEventLog ? isEventLogFlag : 0); 1295 for (NSInteger i = 0; i < numkids; ++i) { 1296 EventLogCellUIElement *cell = [EventLogCellUIElement elementWithRow:0 column:i listFlags:myFlags listCtrl:pList parent:self BOINCView:BOINCView]; 1297 [cell retain]; 1298 [kids addObject:cell]; 1299 } 1300 } 1301 1302 return kids; 1303// return NSAccessibilityUnignoredChildren(kids); // No children are ignored 1304 1305 } else { 1306 return [super accessibilityAttributeValue:attribute]; 1307 } 1308} 1309 1310- (void)dealloc { 1311 for (NSInteger i = 0; i < numkids; ++i) { 1312 [(EventLogCellUIElement *)[kids objectAtIndex:i] release]; 1313 } 1314//TODO: Do we need to delete or free up kids array? 1315 if (kids) [kids release]; 1316 [super dealloc]; 1317} 1318 1319@end 1320 1321#pragma mark === CDlgEventLogListCtrl Accessibility Support === 1322 1323void CDlgEventLogListCtrl::SetupMacAccessibilitySupport() { 1324 NSView *listControlView = GetHandle(); 1325 NSRect r = [ listControlView bounds ]; 1326 FauxListHeaderView *fauxHeaderView = [ FauxListHeaderView alloc ]; 1327 FauxListBodyView *fauxBodyView = [ FauxListBodyView alloc ]; 1328 1329 NSRect rh = r; 1330 rh.size.height = ((wxWindow *)m_headerWin)->GetSize().y; 1331 1332 [fauxHeaderView initWithFrame:rh listCtrl:this listFlags:(isHeaderFlag | isEventLogFlag) parent:listControlView BOINCView:nil]; 1333 [listControlView addSubview:fauxHeaderView ]; 1334 1335 // See comments in [ FauxListHeaderView hitTest:aPoint ] 1336 NSView *realHeaderView = ((wxWindow *)m_headerWin)->GetHandle(); 1337 [realHeaderView retain]; 1338 [realHeaderView removeFromSuperview]; 1339 [listControlView addSubview:fauxHeaderView ]; 1340 [fauxHeaderView addSubview:realHeaderView ]; 1341 [realHeaderView release]; 1342 1343 NSRect rb = r; 1344 rb.origin.y += ((wxWindow *)m_headerWin)->GetSize().y; 1345 rb.size.height -= ((wxWindow *)m_headerWin)->GetSize().y; 1346 [fauxBodyView initWithFrame:rb listCtrl:this listFlags:(isEventLogFlag) parent:listControlView BOINCView:nil]; 1347 [listControlView addSubview:fauxBodyView ]; 1348 1349 m_fauxHeaderView = fauxHeaderView; 1350 m_fauxBodyView = fauxBodyView; 1351} 1352 1353 1354void CDlgEventLogListCtrl::OnSize(wxSizeEvent& event) { 1355 NSView *listControlView = GetHandle(); 1356 NSRect r = [ listControlView bounds ]; 1357 FauxListHeaderView *fauxHeaderView = (FauxListHeaderView *)m_fauxHeaderView; 1358 FauxListBodyView *fauxBodyView = (FauxListBodyView *)m_fauxBodyView; 1359 1360 if (fauxHeaderView) { 1361 NSRect rh = r; 1362 rh.size.height = ((wxWindow *)m_headerWin)->GetSize().y; 1363 [fauxHeaderView setFrame:rh]; 1364 } 1365 1366 if (fauxBodyView) { 1367 NSRect rb = r; 1368 rb.origin.y += ((wxWindow *)m_headerWin)->GetSize().y; 1369 rb.size.height -= ((wxWindow *)m_headerWin)->GetSize().y; 1370 wxString s = wxEmptyString; 1371 [fauxBodyView setFrame:rb]; 1372 } 1373 1374 event.Skip(); 1375} 1376 1377 1378void CDlgEventLogListCtrl::RemoveMacAccessibilitySupport() { 1379 [(FauxListHeaderView *)m_fauxHeaderView release]; 1380 m_fauxHeaderView = nil; 1381 [(FauxListBodyView *)m_fauxBodyView release]; 1382 m_fauxBodyView = nil; 1383} 1384 1385#pragma mark === CBOINCListCtrl Accessibility Support === 1386 1387#if ! USE_NATIVE_LISTCONTROL 1388 1389void CBOINCListCtrl::SetupMacAccessibilitySupport() { 1390 NSView *listControlView = GetHandle(); 1391 NSRect r = [ listControlView bounds ]; 1392 FauxListHeaderView *fauxHeaderView = [ FauxListHeaderView alloc ]; 1393 FauxListBodyView *fauxBodyView = [ FauxListBodyView alloc ]; 1394 1395 NSRect rh = r; 1396 rh.size.height = ((wxWindow *)m_headerWin)->GetSize().y; 1397 1398 [fauxHeaderView initWithFrame:rh listCtrl:this listFlags:isHeaderFlag parent:listControlView BOINCView:m_pParentView ]; 1399 1400 // See comments in [ FauxListHeaderView hitTest:aPoint ] 1401 NSView *realHeaderView = ((wxWindow *)m_headerWin)->GetHandle(); 1402 [realHeaderView retain]; 1403 [realHeaderView removeFromSuperview]; 1404 [listControlView addSubview:fauxHeaderView ]; 1405 [fauxHeaderView addSubview:realHeaderView ]; 1406 [realHeaderView release]; 1407 1408 NSRect rb = r; 1409 rb.origin.y += ((wxWindow *)m_headerWin)->GetSize().y; 1410 rb.size.height -= ((wxWindow *)m_headerWin)->GetSize().y; 1411 [fauxBodyView initWithFrame:rb listCtrl:this listFlags:0 parent:listControlView BOINCView:m_pParentView]; 1412 [listControlView addSubview:fauxBodyView ]; 1413 1414 m_fauxHeaderView = fauxHeaderView; 1415 m_fauxBodyView = fauxBodyView; 1416} 1417 1418 1419void CBOINCListCtrl::OnSize(wxSizeEvent& event) { 1420 NSView *listControlView = GetHandle(); 1421 NSRect r = [ listControlView bounds ]; 1422 FauxListHeaderView *fauxHeaderView = (FauxListHeaderView *)m_fauxHeaderView; 1423 FauxListBodyView *fauxBodyView = (FauxListBodyView *)m_fauxBodyView; 1424 1425 if (fauxHeaderView) { 1426 NSRect rh = r; 1427 rh.size.height = ((wxWindow *)m_headerWin)->GetSize().y; 1428 [fauxHeaderView setFrame:rh]; 1429 } 1430 1431 if (fauxBodyView) { 1432 NSRect rb = r; 1433 rb.origin.y += ((wxWindow *)m_headerWin)->GetSize().y; 1434 rb.size.height -= ((wxWindow *)m_headerWin)->GetSize().y; 1435 wxString s = wxEmptyString; 1436 [fauxBodyView setFrame:rb]; 1437 } 1438 1439 event.Skip(); 1440} 1441 1442 1443void CBOINCListCtrl::RemoveMacAccessibilitySupport() { 1444 [(FauxListHeaderView *)m_fauxHeaderView release]; 1445 m_fauxHeaderView = nil; 1446 [(FauxListBodyView *)m_fauxBodyView release]; 1447 m_fauxBodyView = nil; 1448} 1449 1450#endif // ! USE_NATIVE_LISTCONTROL 1451 1452#pragma mark === CPaintStatistics & wxPieCtrl Accessibility Shared Code === 1453 1454#define statisticsPage 1 1455#define resourcesPage 2 1456 1457@interface FauxGeneralView : NSView { 1458 id parent; 1459 NSInteger viewPage; 1460 void* theClass; 1461} 1462 1463- (id)initWithFrame:(NSRect)frame whichViewPage:(NSInteger)aViewPage callingClass:(void*)aClass parent:aParent; 1464- (NSString*) getValue; 1465@end 1466 1467 1468@implementation FauxGeneralView 1469 1470- (id)initWithFrame:(NSRect)frame whichViewPage:(NSInteger)aViewPage callingClass:(void*)aClass parent:aParent 1471{ 1472 [super initWithFrame:frame]; 1473 parent = aParent; 1474 viewPage = aViewPage; 1475 theClass = aClass; 1476 return self; 1477} 1478 1479- (BOOL)isFlipped { 1480 return YES; 1481} 1482 1483- (BOOL)accessibilityIsIgnored { 1484 return NO; 1485} 1486 1487- (NSString*) getValue { 1488 wxString s; 1489 1490 switch (viewPage) { 1491 case statisticsPage: 1492 s = _("This panel contains graphs showing user totals for projects"); 1493 break; 1494 case resourcesPage: 1495 { 1496 wxPieCtrl* pPieCtrl = (wxPieCtrl*)theClass; 1497 s = pPieCtrl->GetLabel(); 1498 unsigned int i; 1499 1500 for(i=0; i<pPieCtrl->m_Series.Count(); i++) { 1501 s += wxT("; "); 1502 s += pPieCtrl->m_Series[i].GetLabel(); 1503 } 1504 } 1505 break; 1506 default: 1507 s = wxEmptyString; 1508 break; 1509 } 1510 NSString *desc = [NSString stringWithUTF8String:(char *)(s.utf8_str().data())]; 1511 return desc; 1512} 1513 1514 1515- (NSArray *)accessibilityAttributeNames { 1516 static NSArray *attributes = nil; 1517 if (attributes == nil) { 1518 attributes = [[NSArray alloc] initWithObjects: 1519 NSAccessibilityEnabledAttribute, 1520 NSAccessibilityFocusedAttribute, 1521 NSAccessibilityNumberOfCharactersAttribute, 1522 NSAccessibilityParentAttribute, 1523 NSAccessibilityPositionAttribute, 1524 NSAccessibilityRoleAttribute, 1525 NSAccessibilityRoleDescriptionAttribute, 1526 NSAccessibilitySelectedTextAttribute, 1527 NSAccessibilitySelectedTextRangeAttribute, 1528 NSAccessibilityValueAttribute, 1529 NSAccessibilityVisibleCharacterRangeAttribute, 1530 NSAccessibilitySizeAttribute, 1531 NSAccessibilityTopLevelUIElementAttribute, 1532 NSAccessibilityWindowAttribute, 1533 nil]; 1534 } 1535 return attributes; 1536} 1537 1538- (id)accessibilityAttributeValue:(NSString *)attribute { 1539 if ([attribute isEqualToString:NSAccessibilityEnabledAttribute]) { 1540 return [NSNumber numberWithBool:YES]; 1541 1542 } else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { 1543 return NO; 1544 1545 } else if ([attribute isEqualToString:NSAccessibilityNumberOfCharactersAttribute]) { 1546 NSString *s = [self getValue]; 1547 NSUInteger n = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; 1548 return [NSNumber numberWithUnsignedInt:n]; 1549 1550 } else if ([attribute isEqualToString:NSAccessibilityParentAttribute]) { 1551 return NSAccessibilityUnignoredAncestor(parent); 1552 1553 } else if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) { 1554 NSPoint pt = [self bounds].origin; 1555 pt.y += [self bounds].size.height; // We need the bottom left corner 1556 //Convert the point to global (screen) coordinates 1557// NSPoint windowPoint = [self convertPoint:pt toView: nil]; 1558 NSPoint windowPoint = [parent convertPoint:pt toView: nil]; 1559 pt = [[parent window] convertBaseToScreen:windowPoint]; 1560 1561 return [NSValue valueWithPoint:pt]; 1562 1563 } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { 1564 return NSAccessibilityStaticTextRole; 1565 1566 } else if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) { 1567 return NSAccessibilityRoleDescription(NSAccessibilityStaticTextRole, nil); 1568 1569 } else if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) { 1570 NSString *s = [NSString init]; 1571 return s; 1572 1573 } else if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) { 1574 NSRange range = NSMakeRange(0, 0); 1575 return [NSValue valueWithRange:range]; 1576 1577 } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { 1578 return [self getValue]; 1579 1580 } else if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) { 1581 NSString *s = [self getValue]; 1582 NSRange range = NSMakeRange(0, [s length]); 1583 return [NSValue valueWithRange:range]; 1584 1585 } else if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) { 1586 return [NSValue valueWithSize:[self frame].size]; 1587 1588 } else if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute]) { 1589 return [parent window]; 1590 1591 } else if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) { 1592 // We're in the same window as our parent. 1593 return [parent window]; 1594 1595 } else { 1596 return [super accessibilityAttributeValue:attribute]; 1597 } 1598} 1599 1600- (void)dealloc { 1601 [super dealloc]; 1602} 1603 1604@end 1605 1606#pragma mark === CPaintStatistics Accessibility Support === 1607 1608void CPaintStatistics::SetupMacAccessibilitySupport() { 1609 NSView *statisticsView = GetHandle(); 1610 NSRect r = [ statisticsView bounds ]; 1611 FauxGeneralView *fauxStatisticsView = [FauxGeneralView alloc ]; 1612 [fauxStatisticsView initWithFrame:r whichViewPage:statisticsPage callingClass:this parent:statisticsView]; 1613 [ statisticsView addSubview:fauxStatisticsView ]; 1614 m_fauxStatisticsView = fauxStatisticsView; 1615} 1616 1617 1618void CPaintStatistics::ResizeMacAccessibilitySupport() { 1619 NSView *statisticsView = GetHandle(); 1620 NSRect r = [ statisticsView bounds ]; 1621 FauxGeneralView *fauxStatisticsView = (FauxGeneralView *)m_fauxStatisticsView; 1622 if (fauxStatisticsView) { 1623 [ fauxStatisticsView setFrame:r ]; 1624 } 1625} 1626 1627void CPaintStatistics::RemoveMacAccessibilitySupport() { 1628 [(FauxGeneralView *)m_fauxStatisticsView release]; 1629 m_fauxStatisticsView = nil; 1630} 1631 1632#pragma mark === wxPieCtrl Accessibility Support === 1633 1634void wxPieCtrl::SetupMacAccessibilitySupport() { 1635 NSView *resourcesView = GetHandle(); 1636 NSRect r = [ resourcesView bounds ]; 1637 FauxGeneralView *fauxResourcesView = [FauxGeneralView alloc ]; 1638 [fauxResourcesView initWithFrame:r whichViewPage:resourcesPage callingClass:this parent:resourcesView]; 1639 [ resourcesView addSubview:fauxResourcesView ]; 1640 m_fauxResourcesView = fauxResourcesView; 1641} 1642 1643 1644void wxPieCtrl::ResizeMacAccessibilitySupport() { 1645 NSView *resourcesView = GetHandle(); 1646 NSRect r = [ resourcesView bounds ]; 1647 FauxGeneralView *fauxResourcesView = (FauxGeneralView *)m_fauxResourcesView; 1648 if (fauxResourcesView) { 1649 [ fauxResourcesView setFrame:r ]; 1650 } 1651} 1652 1653void wxPieCtrl::RemoveMacAccessibilitySupport() { 1654 [(FauxGeneralView *)m_fauxResourcesView release]; 1655 m_fauxResourcesView = nil; 1656} 1657