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

Last change on this file since 355 was 355, checked in by Nicholas Riley, 12 years ago

English.lproj/MainMenu.nib: Modernize menu and alarm set dialog
layout. Use keyed archiving (10.2+) nib format.

Info-Pester.plist: Moved from old PBX project.

NJRFSObjectSelector.m: Bug fixes from code sent to Joey: remove
incorrect usage of tryToPerform:with:; fix logic error in menu
construction. Work around Cocoa's deciding that the menu font size
needs adjustment when it doesn't - so the menu font size now matches
the button font size, though the position is still off. Don't pop up
a menu if we're disabled. Use IconRefs? for menu icons, though not
(yet) for the button icon.

NJRHistoryTrackingComboBox.m: Remove item height adjustment
workaround; it now makes the items too tall.

NJRHotKey.m: Add a missing [super dealloc] caught by current GCC.

NJRHotKeyField.m: Add a missing [super dealloc] caught by current GCC.

NJRHotKeyManager.m: Add a missing [super dealloc] caught by current
GCC.

NJRIntervalField.m: Fix some type errors.

NJRQTMediaPopUpButton.m: Replace SoundFileManager? SPI usage, which
doesn't work in Leopard anyway, with manual enumeration of system
sounds. Start migration to QTKit. Use IconRefs? for menu icons.

NJRReadMeController.m: Change source encoding to UTF-8.

NJRSoundManager.m: Fix a type error.

NJRVoicePopUpButton.m: Change source encoding to UTF-8.

NSMenuItem-NJRExtensions.[hm]: Code from ICeCoffEE to use IconRefs? for
menu item icons.

PSAlarm.m: Change source encoding to UTF-8.

PSAlarms.m: Fix a signedness mismatch.

PSAlarmsController.m: Change source encoding to UTF-8.

PSAlarmSetController.m: Set keyboard focus after unchecking "Do
script:" and "Play" checkboxes.

PSAlerts.m: Add a missing [super dealloc] caught by current GCC. Fix
a memory leak in property list serialization.

PSPowerManager.[hm]: There's now API for scheduling wakeups; use it
(the old code asserted on startup). To fix: removing scheduled
wakeup. Fix a small type-checking error.

PSPreferencesController.m: Add a missing [super dealloc] caught by
current GCC.

PSScriptAlert.m: Change source encoding to UTF-8.

PSTimeDateEditor.m: Fix a tiny, and one-time, memory leak.

PSTimer.m: Update for new PSPowerManager API.

Pester.pbproj: Deleted; now supporting OS X 10.4+ (up from 10.1,
aiee.)

Pester.xcodeproj: Xcode 2.4+ project, upgraded targets, etc.

SoundFileManager?.h: Deleted; this SPI no longer exists in Leopard and
possibly earlier.

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.