source: releases/ICeCoffEE/1.4.4b1/ICeCoffEE/ICeCoffEETerminal.m @ 336

Last change on this file since 336 was 216, checked in by Nicholas Riley, 14 years ago

VERSION: Starting with 1.4.3d1.

APE.icns: Generic APE icon (not sure whether we care).

SmartCrashReportsAPI.[ho]: SCR 1.1.

ICeCoffEEWebKit.m: -elementAtPoint:? isn't in current
development WebKit?; switch to the version in WebHTMLView.

Info-APE Module.plist: Update version to 1.4.3d1.

ICeCoffEE.xcodeproj: Xcode 2 version of project, required fixing
script which copies the APE bundle into the installer. We're still
building for 10.3.9 and later on PowerPC only for this release.

English.lproj/APEInfo.rtfd: Small clarifications, update release notes
and version information.

English.lproj/InfoPlist.strings: Update version to 1.4.3d1.

ICeCoffEEShared.h: Use varargs macros to finally fix the stupid
warnings when ICCF_DEBUG is 0.

ICeCoffEE APE.xcode: Removed.

ICeCoffEETerminal.m: Fixes crash on clicking disabled close/minimize
widgets in Open dialog (invalid super method call) by implementing
overridden methods in the faked superclass.

ape_install: APE 1.5.1.

Info-APEManagerPrefPane.plist: Update version number.

APEMain.m: Implement SCR. Fix a comment typo. Remove some useless
uses of the comma operator so the new warning-less ICapeprintf works.

package-ICeCoffEE.sh: Use new xcodebuild syntax and build layout.
Remove a useless use of the semicolon. Nuke localizations on
development builds.

File size: 16.3 KB
Line 
1//
2//  ICeCoffEETerminal.m
3//  ICeCoffEE
4//
5//  Created by Nicholas Riley on Mon Jan 28 2002.
6//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
7//
8
9#import "ICeCoffEETerminal.h"
10#import "ICeCoffEEScanner.h"
11#import "ICeCoffEE.h"
12#import <Carbon/Carbon.h>
13#include <unistd.h>
14
15static NSRange ICCF_zeroRange = { NSNotFound, 5 };
16
17#define min(a,b)        (a < b ? a : b)
18#define max(a,b)        (a > b ? a : b)
19
20@interface TermStorage:NSObject
21- (struct _FSelPt)selPt0;
22- (struct _FSelPt)selPt1;
23- (BOOL)hasSelection;
24- (void)startSelectionAtLine:(unsigned int)row column:(unsigned short)column;
25- (void)startSelectionAtLine:(unsigned int)row offset:(unsigned short)offset;
26- (void)endSelectionAtLine:(unsigned int)row offset:(unsigned short)offset;
27- (void)selectWhitespaceDelimitedTextAtLine:(unsigned int)line offset:(unsigned short)offset;
28- (void)selectWordAtLine:(unsigned int)line offset:(unsigned short)offset;
29- (NSString *)selectedString;
30- (unsigned int)numberOfLines;
31- (unsigned int)effectiveColumnsForLine:(unsigned int)line;
32- (void)clearSelection;
33- (BOOL)isSelected:(unsigned int)line :(unsigned int)column;
34@end
35
36@interface TermController:NSObject
37- (TermStorage *)storage;
38@end
39
40@implementation ICECoffEETermSubviewSuper
41// NSTextInput implementation
42- (void)insertText:(id)aString {}
43- (void)doCommandBySelector:(SEL)aSelector {}
44- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange {}
45- (void)unmarkText {}
46- (BOOL)hasMarkedText { return NO; }
47- (long)conversationIdentifier { return 0; }
48- (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange { return nil; }
49- (NSRange)markedRange { return ICCF_zeroRange; }
50- (NSRange)selectedRange { return ICCF_zeroRange; }
51- (NSRect)firstRectForCharacterRange:(NSRange)theRange { return NSZeroRect; }
52- (unsigned int)characterIndexForPoint:(NSPoint)thePoint { return 0; }
53- (NSArray*)validAttributesForMarkedText { return nil; }
54// NSDraggingDestination
55- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender { return NSDragOperationNone; }
56// misc. other stuff
57- (void)_optionClickEvent:(NSEvent *)event:(unsigned int)row:(unsigned short)column {}
58- (void)setNeedsDisplay {}
59// NSView methods (needed to avoid crash in super invocation)
60- (void)mouseUp:(NSEvent *)e { [super mouseUp: e]; }
61- (void)mouseDown:(NSEvent *)e { [super mouseDown: e]; }
62@end
63
64@interface ICeCoffEETerminalRange : NSObject
65{
66    TermStorage *storage;
67    struct _FSelPt pt0;
68    struct _FSelPt pt1;
69    unsigned int width;
70    unsigned int height;
71}
72
73+ (ICeCoffEETerminalRange *)rangeWithTerminal:(ICeCoffEETerminal *)terminal;
74+ (ICeCoffEETerminalRange *)rangeWithTerminal:(ICeCoffEETerminal *)terminal pt0:(struct _FSelPt)selPt0 pt1:(struct _FSelPt)selPt1;
75
76- (id)initWithTerminal:(ICeCoffEETerminal *)terminal;
77- (id)initWithTerminal:(ICeCoffEETerminal *)terminal pt0:(struct _FSelPt)selPt0 pt1:(struct _FSelPt)selPt1;
78
79- (struct _FSelPt)pt0;
80- (struct _FSelPt)pt1;
81
82- (NSString *)stringFromRange;
83
84- (void)growBackwardByLength:(unsigned long)length;
85- (void)growForwardByLength:(unsigned long)length;
86
87- (void)shrinkBackByLength:(unsigned long)length;
88- (void)shrinkFrontByLength:(unsigned long)length;
89
90- (BOOL)rangeIsEmpty;
91
92- (void)select;
93
94@end
95
96@implementation ICeCoffEETerminalRange
97
98+ (ICeCoffEETerminalRange *)rangeWithTerminal:(ICeCoffEETerminal *)terminal;
99{
100    return [[[self alloc] initWithTerminal: terminal] autorelease];
101}
102
103+ (ICeCoffEETerminalRange *)rangeWithTerminal:(ICeCoffEETerminal *)terminal pt0:(struct _FSelPt)selPt0 pt1:(struct _FSelPt)selPt1;
104{
105    return [[[self alloc] initWithTerminal: terminal pt0: selPt0 pt1: selPt1] autorelease];
106}
107
108- (id)initWithTerminal:(ICeCoffEETerminal *)terminal;
109{
110    if ( (self = [self init]) != nil) {
111        storage = [(TermController *)[terminal valueForKey: @"controller"] storage];
112        pt0 = [storage selPt0];
113        pt1 = [storage selPt1];
114        width = [storage effectiveColumnsForLine: pt0.line];
115        height = [storage numberOfLines];
116    }
117    return self;
118}
119
120- (id)initWithTerminal:(ICeCoffEETerminal *)terminal pt0:(struct _FSelPt)selPt0 pt1:(struct _FSelPt)selPt1;
121{
122    if ( (self = [self initWithTerminal: terminal]) != nil) {
123        pt0 = selPt0;
124        pt1 = selPt1;
125    }
126    return self;
127}
128
129- (struct _FSelPt)pt0;
130{
131    return pt0;
132}
133
134- (struct _FSelPt)pt1;
135{
136    return pt1;
137}
138
139- (NSString *)stringFromRange;
140{
141    struct _FSelPt oldPt0 = [storage selPt0], oldPt1 = [storage selPt1];
142    NSString *str;
143
144    [storage startSelectionAtLine: pt0.line offset: pt0.col];
145    [storage endSelectionAtLine: pt1.line offset: pt1.col];
146
147    str = [storage selectedString];
148
149    [storage startSelectionAtLine: oldPt0.line offset: oldPt0.col];
150    [storage endSelectionAtLine: oldPt1.line offset: oldPt1.col];
151
152    return str;
153}
154
155- (struct _FSelPt)_moveBack:(struct _FSelPt)pt byLength:(unsigned long)length;
156{
157    unsigned int extraLines = length / width;
158    if ((long)(pt.line - extraLines) < 0) {
159        pt.line = 0;
160        pt.col = 0;
161    } else if (pt.line - extraLines == 0) {
162        pt.line = 0;
163        pt.col += width - (length % height);
164        if (pt.col < width) pt.col = 0;
165        else pt.col -= width;
166    } else {
167        pt.line -= extraLines;
168        pt.col += width - (length % height);
169        if (pt.col < width) pt.line--;
170        else pt.col -= width;
171    }
172    return pt;
173}
174
175- (struct _FSelPt)_moveForward:(struct _FSelPt)pt byLength:(unsigned long)length;
176{
177    unsigned int extraLines = length / width;
178    if (pt.line + extraLines >= height) {
179        pt.line = height - 1;
180        pt.col = width - 1;
181    } else if (pt.line + extraLines == height - 1) {
182        pt.line = height - 1;
183        pt.col += length % height;
184        if (pt.col >= width) pt.col = width - 1;
185    } else {
186        pt.line += extraLines;
187        pt.col = pt.col + (length % height);
188        if (pt.col > width) {
189            pt.line++;
190            pt.col -= width;
191        }
192    }
193    return pt;
194}
195
196- (BOOL)rangeIsEmpty;
197{
198    return (pt1.line < pt0.line) || (pt1.line == pt0.line && pt1.col <= pt0.col);
199}
200
201- (void)growBackwardByLength:(unsigned long)length;
202{
203    pt0 = [self _moveBack: pt0 byLength: length];
204}
205
206- (void)growForwardByLength:(unsigned long)length;
207{
208    pt1 = [self _moveForward: pt1 byLength: length];
209}
210
211- (void)shrinkFrontByLength:(unsigned long)length;
212{
213    pt0 = [self _moveForward: pt0 byLength: length];
214}
215
216- (void)shrinkBackByLength:(unsigned long)length;
217{
218    pt1 = [self _moveBack: pt1 byLength: length];
219}
220
221- (void)select;
222{
223    [storage startSelectionAtLine: pt0.line offset: pt0.col];
224    [storage endSelectionAtLine: pt1.line offset: pt1.col];
225}
226
227- (NSString *)description;
228{
229    return [NSString stringWithFormat: @"L%uC%hu - L%uC%hu: |%@|", pt0.line, pt0.col, pt1.line, pt1.col, [[self stringFromRange] stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]];
230}
231
232@end
233
234// TermSubview's NSTextInput implementation is essentially a noop: make one that does something
235@implementation ICeCoffEETerminal // XXX normally in category (NSTextInput), but APE doesnÕt work if I do that
236
237- (NSRange)selectedRange {
238    TermStorage *storage = [(TermController *)[self valueForKey: @"controller"] storage];
239    struct _FSelPt selPt0 = [storage selPt0], selPt1 = [storage selPt1];
240    unsigned width = [storage effectiveColumnsForLine: selPt0.line];
241    unsigned pt0 = selPt0.line * width + selPt0.col;
242    ICLog(@"selPt0 %d x %d selPt1 %d x %d", selPt0.line, selPt0.col, selPt1.line, selPt1.col);
243    return NSMakeRange(pt0, selPt1.line * width + selPt1.col - pt0);
244}
245
246// XXX string isn't padded: what we need is something like the ICeCoffEE 1.2.x method, adapted to Terminal 1.2, but we've chosen another way
247- (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange;
248{
249    TermStorage *storage = [(TermController *)[self valueForKey: @"controller"] storage];
250    struct _FSelPt oldPt0 = [storage selPt0], oldPt1 = [storage selPt1], realPt0, realPt1;
251    unsigned pt1 = theRange.location + theRange.length;
252    unsigned width = [storage effectiveColumnsForLine: 0];
253    NSAttributedString *str;
254
255    realPt0.line = theRange.location / width;
256    realPt0.col = theRange.location % width;
257    realPt1.line = pt1 / width;
258    realPt1.col = pt1 % width;
259
260    [storage startSelectionAtLine: realPt0.line offset: realPt0.col];
261    [storage endSelectionAtLine: realPt1.line offset: realPt1.col];
262
263    str = [[NSAttributedString alloc] initWithString: [storage selectedString]];
264
265    NSAssert2([str length] == theRange.length, @"Substring has length %lu when we expected %lu", [str length], theRange.length);
266
267    [storage startSelectionAtLine: oldPt0.line offset: oldPt0.col];
268    [storage endSelectionAtLine: oldPt1.line offset: oldPt1.col];
269   
270    return [str autorelease];
271}
272
273static NSEvent *ICCF_downEvent;
274static unsigned int ICCF_line;
275static unsigned short ICCF_col;
276static BOOL ICCF_optionClickRegistered;
277
278- (void)setSelectedRange:(NSRange)charRange affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag;
279{
280    TermStorage *storage = [(TermController *)[self valueForKey: @"controller"] storage];
281    unsigned width = [storage effectiveColumnsForLine: 0];
282    unsigned pt1 = charRange.location + charRange.length;
283    [storage startSelectionAtLine: charRange.location / width offset: charRange.location % width];
284    [storage endSelectionAtLine: pt1 / width offset: pt1 % width];
285    // [self refresh];
286}
287
288void ICCF_LaunchURLFromTerminal(ICeCoffEETerminal *self) {
289    NSCharacterSet *urlLeftDelimiters = nil, *urlRightDelimiters = nil;
290
291    ICeCoffEETerminalRange *termRange = nil, *selRange = nil;
292    NSString *s;
293    NSRange range, delimiterRange;
294
295    NS_DURING
296
297        TermStorage *storage = [(TermController *)[self valueForKey: @"controller"] storage];
298
299        if ([storage hasSelection] && [storage isSelected: ICCF_line : ICCF_col]) {
300            selRange = [ICeCoffEETerminalRange rangeWithTerminal: self];
301        } else { // select something
302            [storage selectWordAtLine: ICCF_line offset: ICCF_col];
303            selRange = [ICeCoffEETerminalRange rangeWithTerminal: self];
304            NSCAssert(![selRange rangeIsEmpty], ICCF_LocalizedString(@"Sorry, ICeCoffEE was unable to find anything to select"));
305        }
306
307        // However, word selection does not capture even the approximate boundaries of a URL
308        // (text to a space/line ending character); it'll stop at a period in the middle of a hostname.
309        // So, we expand it as follows:
310
311        ICCF_Delimiters(&urlLeftDelimiters, &urlRightDelimiters);
312
313        termRange = [ICeCoffEETerminalRange rangeWithTerminal: self pt0: [selRange pt0] pt1: [selRange pt0]];
314        // add 1 to range to trap delimiters that are on the edge of the selection (i.e., <...)
315        [termRange growForwardByLength: 1];
316        [termRange growBackwardByLength: ICCF_MAX_URL_LEN]; // potentially too big
317        s = [termRange stringFromRange];
318        ICLog(@"front %@", termRange);
319        delimiterRange = [s rangeOfCharacterFromSet: urlLeftDelimiters
320                                            options: NSLiteralSearch | NSBackwardsSearch];
321        if (delimiterRange.location == NSNotFound) {
322            // extend to beginning of string (as much as possible)
323            [selRange growBackwardByLength: [s length] - 1];
324        } else {
325            NSCAssert(delimiterRange.length == 1, @"Internal error: delimiter matched range is not of length 1");
326            [selRange growBackwardByLength: [s length] - delimiterRange.location - 2];
327        }
328
329        ICLog(@"parsed front %@", selRange);
330
331        termRange = [ICeCoffEETerminalRange rangeWithTerminal: self pt0: [selRange pt1] pt1: [selRange pt1]];
332        // subtract 1 from range to trap delimiters that are on the edge of the selection (i.e., ...>)
333        [termRange growBackwardByLength: 1];
334        [termRange growForwardByLength: ICCF_MAX_URL_LEN]; // potentially too big
335        s = [termRange stringFromRange];
336        ICLog(@"back %@", termRange);
337        delimiterRange = [s rangeOfCharacterFromSet: urlRightDelimiters
338                                            options: NSLiteralSearch];
339        if (delimiterRange.location == NSNotFound) {
340            // extend to end of string
341            [selRange growForwardByLength: [s length] - 1];
342        } else {
343            NSCAssert(delimiterRange.length == 1, @"Internal error: delimiter matched range is not of length 1");
344            [selRange growForwardByLength: delimiterRange.location - 1];
345        }
346
347        ICCF_StartIC();
348
349        s = [selRange stringFromRange];
350
351        range = NSMakeRange(0, [s length]);
352
353        ICCF_CheckRange(range);
354
355        ICLog(@"parsed back %@", selRange);
356        ICLog(@"range of string %@", NSStringFromRange(range));
357        ICCF_ParseURL(s, &range);
358        ICLog(@"parsed range %@ |%@|", NSStringFromRange(range), [s substringWithRange: range]);
359
360        [selRange shrinkFrontByLength: range.location];
361        [selRange shrinkBackByLength: [s length] - range.length - range.location];
362
363        s = [selRange stringFromRange];
364        ICLog(@"reconstituted URL %@", selRange);
365
366        [selRange select];
367        [self setNeedsDisplay];
368        [[self superview] display];
369
370        if (ICCF_LaunchURL(s, ICCF_KeyboardAction([NSApp currentEvent])) && ICCF_prefs.textBlinkEnabled) {
371            int i;
372            // Terminal flashes the selection one more time, so blink one fewer
373            for (i = 1 ; i < ICCF_prefs.textBlinkCount ; i++) {
374                [storage clearSelection];
375                [self setNeedsDisplay];
376                [[self superview] display];
377                usleep(kICBlinkDelayUsecs);
378                [selRange select];
379                [self setNeedsDisplay];
380                [[self superview] display];
381                usleep(kICBlinkDelayUsecs);
382            }
383        }
384    NS_HANDLER
385        ICCF_HandleException(localException);
386    NS_ENDHANDLER
387
388    ICCF_StopIC();
389}
390
391- (void)_optionClickEvent:(NSEvent *)event:(unsigned int)row:(unsigned short)column;
392{
393    if (ICCF_downEvent != nil) {
394        ICCF_line = row; // XXX are these lines or rows? check with wrapping
395        ICCF_col = column;
396        ICCF_optionClickRegistered = YES;
397    } else {
398        [super _optionClickEvent: event :row :column];
399    }
400}
401
402- (void)mouseUp:(NSEvent *)upEvent;
403{
404    ICLog(@"ICeCoffEE Terminal up: %@", upEvent);
405    [super mouseUp: upEvent];
406    if (ICCF_downEvent != nil) {
407        NSPoint downPt = [ICCF_downEvent locationInWindow];
408        NSPoint upPt = [upEvent locationInWindow];
409        [ICCF_downEvent release];
410        ICCF_downEvent = nil;
411        if (abs(downPt.x - upPt.x) <= kICHysteresisPixels && abs(downPt.y - upPt.y) <= kICHysteresisPixels) {
412            if (ICCF_optionClickRegistered) {
413                ICCF_optionClickRegistered = NO;
414                ICLog(@"========= launching... %d x %d", ICCF_line, ICCF_col);
415                ICCF_LaunchURLFromTerminal(self);
416            }
417        }
418    }
419}
420
421- (void)mouseDown:(NSEvent *)downEvent;
422{
423    if (ICCF_enabled && ICCF_prefs.commandClickEnabled && ICCF_EventIsCommandMouseDown(downEvent)) {
424        NSEvent *optionClickEvent = [NSEvent mouseEventWithType: NSLeftMouseDown location: [downEvent locationInWindow] modifierFlags: NSAlternateKeyMask timestamp: [downEvent timestamp] windowNumber: [downEvent windowNumber] context: [downEvent context] eventNumber: [downEvent eventNumber] clickCount: 1 pressure: 0];
425        [ICCF_downEvent release];
426        ICCF_downEvent = [downEvent retain];
427        ICLog(@"ICeCoffEE Terminal constructed: %@", optionClickEvent);
428        ICCF_optionClickRegistered = NO;
429        [super mouseDown: optionClickEvent];
430    } else {
431        [super mouseDown: downEvent];
432    }
433}
434
435// NSDraggingDestination
436// -[TermSubview draggingUpdated:] just invokes draggingEntered...
437// XXX Crashing on repeated drag snap-back can happen even without ICeCoffEE installed; don't bother to try to fix
438- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender;
439{
440    if (!ICCF_prefs.terminalRequireOptionForSelfDrag || [sender draggingSource] != self || ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask)) {
441        [super draggingEntered: sender];
442        // When doing non-self drags, this works around one bug in Terminal wherein the option key acts as a toggle, and it shouldn't (see Aqua HIG).  Unfortunately, this messes up drag feedback for self drags, but I don't know of any way to fix it.  Not that most Cocoa apps get it remotely right, anyway.
443        return NSDragOperationCopy;
444    }
445    return NSDragOperationNone;
446}
447
448@end
Note: See TracBrowser for help on using the repository browser.