source: trunk/Cocoa/Pester/Source/NJRTableDelegate.m @ 516

Last change on this file since 516 was 364, checked in by Nicholas Riley, 13 years ago

English.lproj/Alarms.nib: Specify alternating row coloring in the nib,
now we're 10.4+.

English.lproj/InfoPlist.strings: Updated for 1.1b6.

English.lproj/Localizable.strings: Quote alarm message in pretty
description (used in tooltip). Change voice error now it no longer
incorporates OSStatus.

English.lproj/MainMenu.nib: Add speech prefs again; turn repetitions
field into a NJRValidatingField and hook up its delegate.

Info-Pester.plist: Updated for 1.1b6.

NJRHotKey.m: Switch to new Objective-C exception style.

NJRIntervalField.[hm]: Now a subclass of NJRValidatingField.

NJRTableDelegate.m: Get rid of our own tooltip support as NSTableView
now supports them (though with a minor visual glitch on the first
tooltip).

NJRTableView.[hm]: Remove tooltip support. Remove alternating row
coloring support.

NJRValidatingField.[hm]: Contains validation sheet stuff from
NJRIntervalField.

NJRVoicePopUpButton.[hm]: Switch to NSSpeechSynthesizer.

PSAlarm.m: Quote alarm message in pretty description (used in
tooltip). Fix repeating alarms not restoring as repeating if they
didn't expire while Pester was not running. No longer set timer on
Pester 1.0 alarm import, to help make importing atomic.

PSAlarmSetController.[hm]: Use NJRValidatingField for repetitions
field. Switch to new Objective-C exception style. Fix validation
issues on in/at changing. Temporary changes to restore speech support
and allow the sound popup to be removed entirely from the nib (rather
than being dragged out of the visible area, as it was in 1.1b5).
Changes for NSSpeechSynthesizer, which uses different voice names.

PSAlarms.m: Switch to new Objective-C exception style. Fix
duplication and error handling in Pester 1.0 alarm import, making
atomic.

PSAlarmsController.m: Use new tooltip support (since it's implemented
in the delegate rather than the data source, we have to proxy it).

PSAlerts.m: Wrap initialization in exception block so we don't leak.

PSApplication.m: Switch to new Objective-C exception style.

PSMediaAlert.m: Clamp repetitions at 1..99 so the user can't type an
invalid value, then quit and have it saved.

PSSpeechAlert.[hm]: Switch to NSSpeechSynthesizer. Throw an
intelligible exception if the voice is unavailable.

PSTimer.m: Switch to new Objective-C exception style.

Pester.xcodeproj: Remove VERSION generation; rename targets to be more
understandable.

Read Me.rtfd: Updated for 1.1b6.

SUSpeaker.[hm]: Gone in switch to NSSpeechSynthesizer.

VERSION: Gone - we use agvtool for everything now.

Updates/release-notes.html: Updated for 1.1b6.

Updates/updates.xml: Updated for 1.1b6.

package-Pester.sh: Use agvtool to get version. Atomically update
file on Web server to avoid partial downloads.

File size: 9.4 KB
Line 
1//
2//  NJRTableDelegate.m
3//  Pester
4//
5//  Created by Nicholas Riley on Sun Oct 27 2002.
6//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
7//
8
9#import "NJRTableDelegate.h"
10#import "NSTableView-NJRExtensions.h"
11
12#pragma mark sorting support
13
14typedef struct { NSString *key; BOOL descending; } SortContext;
15
16// Sort array of itemNums, by looking up the itemNum in the dictionary of objects.
17// based on code of Ondra Cada <ocs@ocs.cz> on cocoa-dev list
18
19int ORDER_BY_CONTEXT(id left, id right, void *ctxt) {
20    SortContext *context = (SortContext *)ctxt;
21    int order = 0;
22    id key = context->key;
23    if (0 != key) {
24        id first, second;       // the actual objects to compare
25
26        if (context->descending) {
27            first  = [right valueForKey: key];
28            second = [left  valueForKey: key];
29        } else {
30            first  = [left  valueForKey: key];
31            second = [right valueForKey: key];
32        }
33
34        if ([first respondsToSelector: @selector(caseInsensitiveCompare:)]) {
35            order = [first caseInsensitiveCompare:second];
36        } else { // sort numbers or dates
37            order = [(NSNumber *)first compare:second];
38        }
39    }
40    return order;
41}
42
43@interface NJRTableDelegate (Private)
44
45- (void)_positionTypeSelectDisplay;
46- (void)_sortByColumn:(NSTableColumn *)inTableColumn;
47
48@end
49
50@implementation NJRTableDelegate
51
52#pragma mark initialize-release
53
54- (void)awakeFromNib;
55{
56    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_positionTypeSelectDisplay) name: NSViewFrameDidChangeNotification object: tableView];
57}
58
59- (void)dealloc
60{
61    [[NSNotificationCenter defaultCenter] removeObserver: self];
62    [sortingColumn release];
63    [sortingKey release];
64    [reorderedData release];
65    [super dealloc];
66}
67
68#pragma mark accessing
69
70- (void)setSortingColumn:(NSTableColumn *)inNewValue;
71{
72    [inNewValue retain];
73    [sortingColumn release];
74    sortingColumn = inNewValue;
75}
76
77- (void)setSortingKey:(NSString *)inNewValue;
78{
79    [inNewValue retain];
80    [sortingKey release];
81    sortingKey = inNewValue;
82}
83
84#pragma mark sorting
85
86- (NSString *)_sortContextDefaultKey;
87{
88    NSString *autosaveName = [tableView autosaveName];
89    if (autosaveName != nil)
90        return [NSString stringWithFormat: @"NJRTableDelegate SortContext %@", autosaveName];
91    else
92        return nil;
93}
94
95- (void)_sortData;
96{
97    SortContext ctxt = { sortingKey, sortDescending };
98    NSString *sortContextKey = [self _sortContextDefaultKey];
99
100    if (sortContextKey != nil) {
101        [[NSUserDefaults standardUserDefaults] setObject:
102            [NSDictionary dictionaryWithObjectsAndKeys: sortingKey, @"sortingKey", [NSNumber numberWithBool: sortDescending], @"sortDescending", nil]
103                                                    forKey: [self _sortContextDefaultKey]];
104    }
105   
106    // sort the NSMutableArray
107    [reorderedData sortUsingFunction: ORDER_BY_CONTEXT context: &ctxt];
108    [tableView reloadData];
109}
110
111- (void)_sortByColumn:(NSTableColumn *)inTableColumn;
112{
113    NSSet *oldSelection = [self selectedItems];
114    if (sortingColumn == inTableColumn) {
115        // User clicked same column, change sort order
116        sortDescending = !sortDescending;
117        // Possible optimization: Don't actually re-sort if you just change the sorting direction; instead, just display either the nth item or the (count-1-n)th item depending on ascending/descending.)
118    } else {
119        // User clicked new column, change old/new column headers, save new sorting column, and re-sort the array.
120        if (sortingColumn != nil) {
121            [tableView setIndicatorImage: nil inTableColumn: sortingColumn];
122            sortDescending = NO; // on initial sort, preserve previous sort order
123        }
124        [self setSortingKey: [inTableColumn identifier]];
125        [self setSortingColumn: inTableColumn];
126        [tableView setHighlightedTableColumn: inTableColumn];
127    }
128    [tableView setIndicatorImage: (sortDescending ? [NSTableView descendingSortIndicator] : [NSTableView ascendingSortIndicator]) inTableColumn: inTableColumn];
129    [self _positionTypeSelectDisplay];
130    // Actually sort the data
131    [self _sortData];
132    [self selectItems: oldSelection];
133}
134
135- (void)_initialSortData;
136{
137    NSString *sortContextKey = [self _sortContextDefaultKey];
138    NSDictionary *sortContext;
139    NSString *key;
140    NSTableColumn *column;
141
142    if (sortContextKey == nil) goto noContext;
143    if ( (sortContext = [[NSUserDefaults standardUserDefaults] dictionaryForKey: sortContextKey]) == nil) goto noContext;
144    if ( (key = [sortContext objectForKey: @"sortingKey"]) == nil) goto noContext;
145    if ( (column = [tableView tableColumnWithIdentifier: key]) == nil) goto noContext;
146    sortDescending = [[sortContext objectForKey: @"sortDescending"] boolValue];
147    [self _sortByColumn: column];
148    return;
149   
150noContext:
151    sortDescending = NO;
152    [self _sortByColumn: [[tableView tableColumns] objectAtIndex: 0]];
153}
154
155- (NSMutableArray *)reorderedDataForData:(NSArray *)data;
156{
157    if (reorderedData == nil) {
158        reorderedData = [data mutableCopy];
159        [self _initialSortData];
160    } else {
161        NSSet *oldSelection = [self selectedItems];
162        [reorderedData release]; reorderedData = nil;
163        reorderedData = [data mutableCopy];
164        [self _sortData];
165        [self selectItems: oldSelection];
166    }
167    return reorderedData;
168}
169
170#pragma mark type selection
171
172- (void)_positionTypeSelectDisplay;
173{
174    [tableView resetTypeSelect]; // avoid extraneous matching
175    if ([tableView typeSelectDisplay] != nil && sortingColumn != nil) {
176        NSControl *typeSelectControl = [tableView typeSelectDisplay];
177        if ([typeSelectControl isKindOfClass: [NSControl class]]) {
178            NSView *superview = [typeSelectControl superview];
179            NSRect columnRect = [superview convertRect: [tableView rectOfColumn: [tableView columnWithIdentifier: sortingKey]] fromView: tableView];
180            // XXX support horizontal scroll bar/clipping (not for Pester, but eventually)
181            // NSRect tableScrollFrame = [[tableView enclosingScrollView] frame];
182            NSRect selectFrame = [typeSelectControl frame];
183            [superview setNeedsDisplayInRect: selectFrame]; // fix artifacts caused by moving view
184            selectFrame.origin.x = columnRect.origin.x;
185            selectFrame.size.width = columnRect.size.width;
186            [typeSelectControl setAlignment: [[sortingColumn dataCell] alignment]];
187            [typeSelectControl setFrame: selectFrame];
188        }
189    }
190}
191
192#pragma mark saving/restoring selection
193
194- (NSSet *)selectedItems;
195{
196    NSMutableSet *result = [NSMutableSet set];
197    NSEnumerator *e = [tableView selectedRowEnumerator];
198    NSNumber *rowNum;
199
200    while ( (rowNum = [e nextObject]) != nil) {
201        id item = [reorderedData objectAtIndex: [rowNum intValue]];
202        [result addObject: item];
203    }
204    return result;
205}
206
207- (void)selectItems:(NSSet *)inSelectedItems;
208{
209    NSEnumerator *e = [inSelectedItems objectEnumerator];
210    id item;
211    int savedLastRow = 0;
212
213    [tableView deselectAll: nil];
214
215    while ( (item = [e nextObject]) != nil ) {
216        int row = [reorderedData indexOfObjectIdenticalTo: item];
217        if (row != NSNotFound) {
218            [tableView selectRow: row byExtendingSelection: YES];
219            savedLastRow = row;
220        }
221    }
222    [tableView scrollRowToVisible: savedLastRow];
223}
224
225@end
226
227@implementation NJRTableDelegate (NSTableViewDelegate)
228
229- (void)tableView:(NSTableView *)aTableView didClickTableColumn:(NSTableColumn *)inTableColumn
230{
231    [[tableView window] makeFirstResponder: aTableView];
232    [self _sortByColumn: inTableColumn];
233}
234
235- (void)tableViewColumnDidResize:(NSNotification *)notification;
236{
237    [self _positionTypeSelectDisplay];
238}
239
240- (void)tableViewColumnDidMove:(NSNotification *)notification;
241{
242    [self _positionTypeSelectDisplay];
243}
244
245- (NSString *)tableView:(NSTableView *)aTableView toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tc row:(int)rowIndex mouseLocation:(NSPoint)mouseLocation;
246{
247    id dataSource = [aTableView dataSource];
248   
249    if ([dataSource respondsToSelector: @selector(toolTipForRow:)])
250        return [dataSource toolTipForRow: rowIndex];
251   
252    return nil;
253}
254
255@end
256
257@implementation NJRTableDelegate (NJRTableViewDelegate)
258
259- (void)tableView:(NSTableView *)aTableView selectRowMatchingString:(NSString *)matchString;
260{
261    // Look for a highlighted column, presuming we are sorted by that column, and search its values.
262    NSTableColumn *col = [aTableView highlightedTableColumn];
263    id dataSource = [aTableView dataSource];
264    int i, rowCount = [reorderedData count];
265    if (nil == col) return;
266    if (sortDescending) {
267        for ( i = rowCount - 1 ; i >= 0 ; i-- ) {
268            NSComparisonResult order = [matchString caseInsensitiveCompare:
269                [dataSource tableView: aTableView objectValueForTableColumn: col row: i]];
270            if (order != NSOrderedDescending) break;
271        }
272        if (i < 0) i = 0;
273    } else {
274        for ( i = 0 ; i < rowCount ; i++ ) {
275            NSComparisonResult order = [matchString caseInsensitiveCompare:
276                [dataSource tableView: aTableView objectValueForTableColumn: col row: i]];
277            if (order != NSOrderedDescending) break;
278        }
279        if (i >= rowCount) i = rowCount - 1;
280    }
281    // Now select row i -- either the one we found, or the first/last row if not found.
282    [aTableView selectRow: i byExtendingSelection: NO];
283    [aTableView scrollRowToVisible: i];
284}
285
286@end
Note: See TracBrowser for help on using the repository browser.