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