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

Last change on this file since 636 was 600, checked in by Nicholas Riley, 15 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.