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

Last change on this file since 103 was 103, checked in by Nicholas Riley, 21 years ago

Localization, bug fixes

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