source: trunk/Cocoa/Pester/Source/NJRReadMeController.m @ 600

Last change on this file since 600 was 600, checked in by Nicholas Riley, 10 years ago

Prototypes to pacify GCC.

File size: 11.4 KB
Line 
1//
2//  NJRReadMeController.m
3//  Pester
4//
5//  Created by Nicholas Riley on Tue Feb 18 2003.
6//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
7//
8
9#import "NJRReadMeController.h"
10#import "NJRSplitView.h"
11#import "NSString-NJRExtensions.h"
12
13@interface NJRHelpContentsEntry : NSObject {
14    unsigned level;
15    NSString *description;
16    NSRange range;
17}
18
19+ (NJRHelpContentsEntry *)headingLevel:(int)aLevel description:(NSString *)aDescription range:(NSRange)aRange;
20- (id)initWithLevel:(int)aLevel description:(NSString *)aDescription range:(NSRange)aRange;
21
22- (NSString *)description;
23- (NSRange)range;
24- (NSMutableString *)displayString;
25
26@end
27
28@implementation NJRHelpContentsEntry
29
30+ (NJRHelpContentsEntry *)headingLevel:(int)aLevel description:(NSString *)aDescription range:(NSRange)aRange;
31{
32    return [[[self alloc] initWithLevel: aLevel description: aDescription range: aRange] autorelease];
33}
34
35- (id)initWithLevel:(int)aLevel description:(NSString *)aDescription range:(NSRange)aRange;
36{
37    if ( (self = [super init]) != nil) {
38        level = aLevel;
39        description = [[aDescription stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] retain];
40        range = aRange;
41    }
42    return self;
43}
44
45- (void)dealloc;
46{
47    [description release];
48    [super dealloc];
49}
50
51- (NSString *)description;
52{
53    return [NSString stringWithFormat: @"%u > %@ %@", level, description, NSStringFromRange(range)];
54}
55
56- (NSRange)range;
57{
58    return range;
59}
60
61- (NSMutableString *)displayString;
62{
63    NSMutableString *s = [[NSMutableString alloc] init];
64
65    unsigned i;
66    for (i = 0 ; i < level ; i++) {
67        [s appendString: @"  "];
68    }
69    [s appendString: description];
70    return [s autorelease];
71}
72
73@end
74
75@interface NJRReadMeController (Private)
76- (void)_saveSplit;
77- (void)_restoreSplit;
78- (void)readRTF:(NSString *)aPath;
79@end
80
81@implementation NJRReadMeController
82
83static NJRReadMeController *sharedController = nil;
84
85+ (NJRReadMeController *)readMeControllerWithRTFDocument:(NSString *)aPath;
86{
87    return [[self alloc] initWithRTFDocument: aPath];
88}
89
90- (id)initWithRTFDocument:(NSString *)aPath;
91{
92    if (sharedController != nil) {
93        [self release];
94        [[sharedController window] makeKeyAndOrderFront: sharedController];
95        return sharedController;
96    }
97    if ( (self = [super initWithWindowNibName: @"Read Me"]) != nil) {
98        NSWindow *window = [self window];
99        [progress setIndeterminate: YES];
100        [progressTabs selectTabViewItemWithIdentifier: @"progress"];
101
102        [NSThread detachNewThreadSelector: @selector(readRTF:) toTarget: self withObject: aPath];
103        NSString *frameAutosaveName;
104        if ( (frameAutosaveName = [window frameAutosaveName]) == nil) {
105            // XXX workaround for bug in 10.1.5–10.2.4 (at least): autosave name set in IB doesn't show up
106            [self setWindowFrameAutosaveName: @"Read Me"];
107            frameAutosaveName = [window frameAutosaveName];
108        }
109        if (frameAutosaveName == nil || ![window setFrameUsingName: frameAutosaveName])
110            [window center];
111        [self _restoreSplit];
112
113        [window makeKeyAndOrderFront: self];
114        sharedController = self;
115    }
116    return self;
117}
118
119- (void)dealloc;
120{
121    [headings release];
122    [headingAttributes release];
123    if (sharedController == self) sharedController = nil;
124    [super dealloc];
125}
126
127- (void)readRTF:(NSString *)aPath;
128{
129    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
130    NSAttributedString *readMe = [[NSAttributedString alloc] initWithPath: aPath documentAttributes: nil];
131    if (readMe == nil) {
132    [body insertText: [NSString stringWithFormat: NSLocalizedString(@"Can't read document '%@'", "Message displayed in in place of read me contents when read me file could not be read; %@ replaced by last path component of filename, e.g. 'Read Me.rtfd'"), [aPath lastPathComponent]]];
133    } else {
134        NSTextStorage *storage = [body textStorage];
135        [storage setAttributedString: readMe];
136        [readMe release]; readMe = nil;
137
138        unsigned int length = [storage length];
139        [progress setIndeterminate: NO];
140        [progress setMaxValue: length];
141        NSRange effectiveRange = NSMakeRange(0, 0);
142        unsigned int chunkLength = 0;
143        NSFont *fontAttr = nil;
144        NSString *fontName = nil; float fontSize = 0;
145        NSString *heading = nil;
146
147        headings = [[NSMutableArray alloc] init];
148       
149        // XXX need this instead? (id)attribute:(NSString *)attrName atIndex:(unsigned int)location longestEffectiveRange:(NSRangePointer)range inRange:(NSRange)rangeLimit;
150        while (NSMaxRange(effectiveRange) < length) {
151            fontAttr = (NSFont *)[storage attribute: NSFontAttributeName
152                                            atIndex: NSMaxRange(effectiveRange) effectiveRange: &effectiveRange];
153            if (effectiveRange.length < 3) continue;
154            fontName = [fontAttr fontName]; fontSize = [fontAttr pointSize];
155            chunkLength = effectiveRange.length;
156            if ([fontName isEqualToString: @"GillSans-Bold"]) {
157                heading = [[storage attributedSubstringFromRange: effectiveRange] string];
158                if (fontSize == 24)
159                    [headings addObject: [NJRHelpContentsEntry headingLevel: 0 description: [[storage attributedSubstringFromRange: effectiveRange] string] range: effectiveRange]];
160                else
161                    [headings addObject:
162                        [NJRHelpContentsEntry headingLevel: (fontSize == 14) ? 1: 2 description: heading range: effectiveRange]];
163            }
164            if (fontSize != 14) continue;
165            if ([fontName isEqualToString: @"HoeflerText-Black"]) {
166                heading = [[storage attributedSubstringFromRange: NSMakeRange(effectiveRange.location, chunkLength + 1)] string];
167                switch ([heading characterAtIndex: chunkLength]) {
168                    case ':':
169                    case ',':
170                        break;
171                    case ' ':
172                        switch ([heading characterAtIndex: chunkLength - 1]) {
173                            case ':':
174                            case ',':
175                                chunkLength--;
176                                break;
177                            default:
178                                continue;
179                        }
180                        break;
181                    default:
182                        continue;
183                }
184                [headings addObject: [NJRHelpContentsEntry headingLevel: 2 description: [heading substringToIndex: chunkLength] range: NSMakeRange(effectiveRange.location, chunkLength)]];
185            }
186            [progress setDoubleValue: NSMaxRange(effectiveRange)];
187        }
188    }
189    headingAttributes = [[NSDictionary alloc] initWithObjectsAndKeys: [[[contents tableColumnWithIdentifier: @"heading"] dataCell] font], NSFontAttributeName, nil];
190    NSEnumerator *e = [headings objectEnumerator];
191    NSString *s;
192    float width;
193    maxHeadingWidth = 0;
194    while ( (s = [(NJRHelpContentsEntry *)[e nextObject] displayString]) != nil) {
195        width = [s sizeWithAttributes: headingAttributes].width;
196        if (width > maxHeadingWidth) maxHeadingWidth = width;
197    }
198    maxHeadingWidth += 25; // account for scroll bar and frame
199    [self _saveSplit];
200    [self _restoreSplit];
201   
202    [contents reloadData];
203    [progressTabs selectTabViewItemWithIdentifier: @"completed"];
204    [pool release];
205}
206
207- (void)_saveSplit;
208{
209    NSString *frameAutosaveName;
210    if ( (frameAutosaveName = [[self window] frameAutosaveName]) != nil) {
211        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
212        [defaults setFloat: maxHeadingWidth forKey:
213            [frameAutosaveName stringByAppendingString: @" maximum contents heading width"]];
214        NSBox *contentsBox = [[splitter subviews] objectAtIndex: 0];
215        [defaults setBool: [splitter isSubviewCollapsed: contentsBox] forKey:
216                [frameAutosaveName stringByAppendingString: @" contents are collapsed"]];
217        [defaults synchronize];
218    }
219}
220
221- (void)_restoreSplit;
222{
223    NSString *frameAutosaveName;
224    BOOL contentsCollapsed = NO;
225    if ( (frameAutosaveName = [[self window] frameAutosaveName]) != nil) {
226        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
227        contentsCollapsed = [defaults boolForKey:
228            [frameAutosaveName stringByAppendingString: @" contents are collapsed"]];
229        if (maxHeadingWidth == 0) { // don't want to restore 0 if we can't write to defaults
230            maxHeadingWidth = [defaults floatForKey:
231                [frameAutosaveName stringByAppendingString: @" maximum contents heading width"]];
232        }
233    }
234    NSBox *contentsBox = [[splitter subviews] objectAtIndex: 0];
235    if ([splitter isSubviewCollapsed: contentsBox] ||
236        (maxHeadingWidth == 0 && !contentsCollapsed)) return;
237    if (contentsCollapsed) {
238        [splitter performSelectorOnMainThread: @selector(collapseSubview:) withObject: contentsBox waitUntilDone: YES];
239        return;
240    }
241    NSSize contentsSize = [contentsBox frame].size;
242    float widthDiff = contentsSize.width - maxHeadingWidth;
243    if (widthDiff < 1) return;
244    NSSize bodySize = [bodyBox frame].size;
245    bodySize.width += widthDiff;
246    contentsSize.width -= widthDiff;
247    [contentsBox setFrameSize: contentsSize];
248    [bodyBox setFrameSize: bodySize];
249    [splitter performSelectorOnMainThread: @selector(adjustSubviews) withObject: nil waitUntilDone: NO];
250}
251
252- (IBAction)contentsClicked:(NSTableView *)sender;
253{
254    int row = [sender clickedRow];
255    if (row == -1) return;
256    NSRange range = [[headings objectAtIndex: row] range];
257    [body setSelectedRange: range];
258    [body scrollRangeToVisible: range];
259}
260
261@end
262
263@implementation NJRReadMeController (NSTableDataSource)
264
265- (int)numberOfRowsInTableView:(NSTableView *)tableView;
266{
267    return [headings count];
268}
269
270// need to enable column resizing for this to work, otherwise we never get called again
271- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row;
272{
273    NSMutableString *s = [[headings objectAtIndex: row] displayString];
274    [s truncateToWidth: [tableView frameOfCellAtColumn: 0 row: row].size.width by: NSLineBreakByTruncatingTail withAttributes: headingAttributes];
275    return s;
276}
277
278@end
279
280@implementation NJRReadMeController (NSSplitViewDelegate)
281
282- (float)splitView:(NSSplitView *)sender constrainMinCoordinate:(float)proposedCoord ofSubviewAt:(int)offset;
283{
284    return MAX(proposedCoord, 80);
285}
286
287- (float)splitView:(NSSplitView *)sender constrainMaxCoordinate:(float)proposedCoord ofSubviewAt:(int)offset;
288{
289    return MIN(proposedCoord, maxHeadingWidth);
290}
291
292- (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize;
293{
294    NSSize newSize = [sender frame].size;
295    NSBox *contentsBox = [[splitter subviews] objectAtIndex: 0];
296    NSSize contentsSize = [contentsBox frame].size;
297    NSSize bodySize = [bodyBox frame].size;
298    contentsSize.height += newSize.height - oldSize.height;
299    [contentsBox setFrameSize: contentsSize];
300    bodySize.width += newSize.width - oldSize.width;
301    bodySize.height += newSize.height - oldSize.height;
302    [bodyBox setFrameSize: bodySize];
303    [sender adjustSubviews];
304}
305
306- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview;
307{
308    return [contents isDescendantOf: subview];
309}
310
311@end
312
313@implementation NJRReadMeController (NSWindowNotifications)
314
315- (void)windowWillClose:(NSNotification *)notification;
316{
317    [self _saveSplit];
318    [self autorelease];
319}
320
321@end
Note: See TracBrowser for help on using the repository browser.