Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
8320 lines (7447 sloc) 295 KB
// -*- mode:objc -*-
// $Id: PTYTextView.m,v 1.325 2009-02-06 14:33:17 delx Exp $
/*
** PTYTextView.m
**
** Copyright (c) 2002, 2003
**
** Author: Fabian, Ujwal S. Setlur
** Initial code by Kiichi Kusama
**
** Project: iTerm
**
** Description: NSTextView subclass. The view object for the VT100 screen.
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#define DEBUG_ALLOC 0
#define DEBUG_METHOD_TRACE 0
#define GREED_KEYDOWN 1
static const int MAX_WORKING_DIR_COUNT = 50;
static const int kMaxSelectedTextLengthForCustomActions = 8192;
static const int kMaxSelectedTextLinesForCustomActions = 100;
//#define DEBUG_DRAWING
// Constants for converting RGB to luma.
#define RED_COEFFICIENT 0.30
#define GREEN_COEFFICIENT 0.59
#define BLUE_COEFFICIENT 0.11
#define SWAPINT(a, b) { int temp; temp = a; a = b; b = temp; }
#import "iTerm.h"
#import "PTYTextView.h"
#import "PseudoTerminal.h"
#import "PTYSession.h"
#import "VT100Screen.h"
#import "PreferencePanel.h"
#import "PTYScrollView.h"
#import "PTYTask.h"
#import "iTermController.h"
#import "NSStringITerm.h"
#import "iTermApplicationDelegate.h"
#import "PreferencePanel.h"
#import "PasteboardHistory.h"
#import "PTYTab.h"
#import "iTermExpose.h"
#import "RegexKitLite/RegexKitLite.h"
#import "NSStringITerm.h"
#import "FontSizeEstimator.h"
#import "MovePaneController.h"
#import "FutureMethods.h"
#import "SmartSelectionController.h"
#import "ITAddressBookMgr.h"
#import "PointerController.h"
#import "PointerPrefsController.h"
#include <sys/time.h>
#include <math.h>
// When performing the "find cursor" action, a gray window is shown with a
// transparent "hole" around the cursor. This is the radius of that hole in
// pixels.
const double kFindCursorHoleRadius = 30;
const int kDragPaneModifiers = (NSAlternateKeyMask | NSCommandKeyMask | NSShiftKeyMask);
// If the cursor's background color is too close to nearby background colors,
// force it to the "most different" color. This is the difference threshold
// that triggers that change. 0 means always trigger, 1 means never trigger.
static double gSmartCursorBgThreshold = 0.5;
// The cursor's text is forced to black or white if it is too similar to the
// background. If the brightness difference is below a threshold then the
// B/W text mode is triggered. 0 means always trigger, 1 means never trigger.
static double gSmartCursorFgThreshold = 0.75;
@implementation FindCursorView
@synthesize cursor;
- (void)drawRect:(NSRect)dirtyRect
{
const double initialAlpha = 0.7;
NSGradient *grad = [[NSGradient alloc] initWithStartingColor:[NSColor whiteColor]
endingColor:[NSColor blackColor]];
NSPoint relativeCursorPosition = NSMakePoint(2 * (cursor.x / self.frame.size.width - 0.5),
2 * (cursor.y / self.frame.size.height - 0.5));
[grad drawInRect:NSMakeRect(0, 0, self.frame.size.width, self.frame.size.height)
relativeCenterPosition:relativeCursorPosition];
[grad release];
double x = cursor.x;
double y = cursor.y;
const double numSteps = 1;
const double stepSize = 1;
const double initialRadius = kFindCursorHoleRadius + numSteps * stepSize;
double a = initialAlpha;
for (double focusRadius = initialRadius; a > 0 && focusRadius >= initialRadius - numSteps * stepSize; focusRadius -= stepSize) {
[[NSGraphicsContext currentContext] setCompositingOperation:NSCompositeCopy];
NSBezierPath *circle = [NSBezierPath bezierPathWithOvalInRect:NSMakeRect(x - focusRadius, y - focusRadius, focusRadius*2, focusRadius*2)];
a -= initialAlpha / numSteps;
a = MAX(0, a);
[[NSColor colorWithDeviceWhite:0.5 alpha:a] set];
[circle fill];
}
}
@end
// Minimum distance that the mouse must move before a cmd+drag will be
// recognized as a drag.
static const int kDragThreshold = 3;
static const double kBackgroundConsideredDarkThreshold = 0.5;
static const int kBroadcastMargin = 4;
static const int kCoprocessMargin = 4;
// When drawing lines, we use this structure to represent a run of cells of
// the same font, color, and attributes.
typedef enum {
SINGLE_CODE_POINT_RUN, // A run of cells with one code point each.
MULTIPLE_CODE_POINT_RUN // A single cell with multiple code points.
} RunType;
typedef struct {
RunType runType;
PTYFontInfo* fontInfo;
NSColor* color;
// Should bold text be rendered by drawing text twice with a 1px shift?
BOOL fakeBold;
// Pointer to code point(s) for this char.
unichar* codes;
// Number of codes that 'codes' points to.
int numCodes;
// Advances for each code.
CGSize* advances;
// Number of advances.
int numAdvances;
// x pixel coordinate for this cell.
CGFloat x;
// Pointer to glyphs for these chars (single code point only)
CGGlyph* glyphs;
// Number of glyphs.
int numGlyphs;
// Should this run be anti-aliased.
BOOL antiAlias;
} CharRun;
static NSCursor* textViewCursor;
static NSCursor* xmrCursor;
static NSImage* bellImage;
static NSImage* wrapToTopImage;
static NSImage* wrapToBottomImage;
static NSImage* broadcastInputImage;
static NSImage* coprocessImage;
static CGFloat PerceivedBrightness(CGFloat r, CGFloat g, CGFloat b) {
return (RED_COEFFICIENT * r) + (GREEN_COEFFICIENT * g) + (BLUE_COEFFICIENT * b);
}
@interface Coord : NSObject
{
@public
int x, y;
}
+ (Coord*)coordWithX:(int)x y:(int)y;
@end
@implementation Coord
+ (Coord*)coordWithX:(int)x y:(int)y
{
Coord* c = [[[Coord alloc] init] autorelease];
c->x = x;
c->y = y;
return c;
}
@end
@interface SmartMatch : NSObject
{
@public
double score;
int startX;
long long absStartY;
int endX;
long long absEndY;
}
- (NSComparisonResult)compare:(SmartMatch *)aNumber;
@end
@implementation SmartMatch
- (NSComparisonResult)compare:(SmartMatch *)other
{
return [[NSNumber numberWithDouble:score] compare:[NSNumber numberWithDouble:other->score]];
}
@end
@implementation PTYTextView
+ (void)initialize
{
NSPoint hotspot = NSMakePoint(4, 5);
NSBundle* bundle = [NSBundle bundleForClass:[self class]];
NSString* ibarFile = [bundle
pathForResource:@"IBarCursor"
ofType:@"png"];
NSImage* image = [[[NSImage alloc] initWithContentsOfFile:ibarFile] autorelease];
textViewCursor = [[NSCursor alloc] initWithImage:image hotSpot:hotspot];
NSString* xmrFile = [bundle
pathForResource:@"IBarCursorXMR"
ofType:@"png"];
NSImage* xmrImage = [[[NSImage alloc] initWithContentsOfFile:xmrFile] autorelease];
xmrCursor = [[NSCursor alloc] initWithImage:xmrImage hotSpot:hotspot];
NSString* bellFile = [bundle
pathForResource:@"bell"
ofType:@"png"];
bellImage = [[NSImage alloc] initWithContentsOfFile:bellFile];
[bellImage setFlipped:YES];
NSString* wrapToTopFile = [bundle
pathForResource:@"wrap_to_top"
ofType:@"png"];
wrapToTopImage = [[NSImage alloc] initWithContentsOfFile:wrapToTopFile];
[wrapToTopImage setFlipped:YES];
NSString* wrapToBottomFile = [bundle
pathForResource:@"wrap_to_bottom"
ofType:@"png"];
wrapToBottomImage = [[NSImage alloc] initWithContentsOfFile:wrapToBottomFile];
[wrapToBottomImage setFlipped:YES];
NSString* broadcastInputFile = [bundle pathForResource:@"BroadcastInput"
ofType:@"png"];
broadcastInputImage = [[NSImage alloc] initWithContentsOfFile:broadcastInputFile];
[broadcastInputImage setFlipped:YES];
NSString* coprocessFile = [bundle pathForResource:@"Coprocess"
ofType:@"png"];
coprocessImage = [[NSImage alloc] initWithContentsOfFile:coprocessFile];
[coprocessImage setFlipped:YES];
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"SmartCursorColorBgThreshold"]) {
// Override the default.
double d = [[NSUserDefaults standardUserDefaults] doubleForKey:@"SmartCursorColorBgThreshold"];
if (d > 0) {
gSmartCursorBgThreshold = d;
}
}
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"SmartCursorColorFgThreshold"]) {
// Override the default.
double d = [[NSUserDefaults standardUserDefaults] doubleForKey:@"SmartCursorColorFgThreshold"];
if (d > 0) {
gSmartCursorFgThreshold = d;
}
}
}
- (BOOL)xtermMouseReporting
{
NSEvent *event = [NSApp currentEvent];
return (([[self delegate] xtermMouseReporting]) && // Xterm mouse reporting is on
!([event modifierFlags] & NSAlternateKeyMask)); // Not holding Opt to disable mouse reporting
}
+ (NSCursor *)textViewCursor
{
return textViewCursor;
}
- (id)initWithFrame:(NSRect)aRect
{
self = [super initWithFrame: aRect];
dataSource=_delegate=markedTextAttributes=NULL;
firstMouseEventNumber_ = -1;
fallbackFonts = [[NSMutableDictionary alloc] init];
[self setMarkedTextAttributes:
[NSDictionary dictionaryWithObjectsAndKeys:
defaultBGColor, NSBackgroundColorAttributeName,
defaultFGColor, NSForegroundColorAttributeName,
secondaryFont.font, NSFontAttributeName,
[NSNumber numberWithInt:(NSUnderlineStyleSingle|NSUnderlineByWordMask)],
NSUnderlineStyleAttributeName,
NULL]];
CURSOR=YES;
lastFindStartX = lastFindEndX = oldStartX = startX = -1;
markedText = nil;
gettimeofday(&lastBlink, NULL);
[[self window] useOptimizedDrawing:YES];
// register for drag and drop
[self registerForDraggedTypes: [NSArray arrayWithObjects:
NSFilenamesPboardType,
NSStringPboardType,
nil]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_useBackgroundIndicatorChanged:)
name:kUseBackgroundPatternIndicatorChangedNotification
object:nil];
[self _useBackgroundIndicatorChanged:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_settingsChanged:)
name:@"iTermRefreshTerminal"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_pointerSettingsChanged:)
name:kPointerPrefsChangedNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(flagsChangedNotification:)
name:@"iTermFlagsChanged"
object:nil];
advancedFontRendering = [[PreferencePanel sharedInstance] advancedFontRendering];
strokeThickness = [[PreferencePanel sharedInstance] strokeThickness];
imeOffset = 0;
resultMap_ = [[NSMutableDictionary alloc] init];
trouter = [[Trouter alloc] init];
trouterDragged = NO;
workingDirectoryAtLines = [[NSMutableArray alloc] init];
pointer_ = [[PointerController alloc] init];
pointer_.delegate = self;
if ([pointer_ viewShouldTrackTouches]) {
[self futureSetAcceptsTouchEvents:YES];
[self futureSetWantsRestingTouches:YES];
}
return self;
}
- (void)touchesBeganWithEvent:(NSEvent *)ev
{
numTouches_ = [[ev futureTouchesMatchingPhase:1 | (1 << 2)/*NSTouchPhasesBegan | NSTouchPhasesStationary*/
inView:self] count];
}
- (void)touchesEndedWithEvent:(NSEvent *)ev
{
numTouches_ = [[ev futureTouchesMatchingPhase:(1 << 2)/*NSTouchPhasesStationary*/
inView:self] count];
}
- (BOOL)resignFirstResponder
{
return YES;
}
- (BOOL)becomeFirstResponder
{
PTYSession* mySession = [dataSource session];
[[mySession tab] setActiveSession:mySession];
return YES;
}
- (void)viewWillMoveToWindow:(NSWindow *)win
{
if (!win && [self window] && trackingArea) {
[self removeTrackingArea:trackingArea];
trackingArea = nil;
}
[super viewWillMoveToWindow:win];
}
- (void)viewDidMoveToWindow
{
if ([self window]) {
if (trackingArea) {
[self removeTrackingArea:trackingArea];
}
trackingArea = [[[NSTrackingArea alloc] initWithRect:[self visibleRect]
options:NSTrackingMouseEnteredAndExited | NSTrackingInVisibleRect | NSTrackingActiveAlways | NSTrackingEnabledDuringMouseDrag
owner:self
userInfo:nil] autorelease];
[self addTrackingArea:trackingArea];
}
}
- (void)dealloc
{
[smartSelectionRules_ release];
int i;
if (mouseDownEvent != nil) {
[mouseDownEvent release];
mouseDownEvent = nil;
}
if (trackingArea) {
[self removeTrackingArea:trackingArea];
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
for (i = 0; i < 256; i++) {
[colorTable[i] release];
}
[lastFlashUpdate_ release];
[resultMap_ release];
[findResults_ release];
[findString_ release];
[defaultFGColor release];
[defaultBGColor release];
[defaultBoldColor release];
[selectionColor release];
[defaultCursorColor release];
[self releaseFontInfo:&primaryFont];
[self releaseFontInfo:&secondaryFont];
[markedTextAttributes release];
[markedText release];
[self releaseAllFallbackFonts];
[fallbackFonts release];
[selectionScrollTimer release];
[workingDirectoryAtLines release];
[trouter release];
[initialFindContext_.substring release];
[pointer_ release];
[cursor_ release];
[super dealloc];
}
- (BOOL)shouldDrawInsertionPoint
{
return NO;
}
- (BOOL)isFlipped
{
return YES;
}
- (BOOL)isOpaque
{
return YES;
}
- (void)setAntiAlias:(BOOL)asciiAA nonAscii:(BOOL)nonAsciiAA
{
asciiAntiAlias = asciiAA;
nonasciiAntiAlias = nonAsciiAA;
[self setNeedsDisplay:YES];
}
- (BOOL)useBoldFont
{
return useBoldFont;
}
- (void)setUseBoldFont:(BOOL)boldFlag
{
useBoldFont = boldFlag;
[self setNeedsDisplay:YES];
}
- (void)setUseBrightBold:(BOOL)flag
{
useBrightBold = flag;
[self setNeedsDisplay:YES];
}
- (BOOL)blinkingCursor
{
return blinkingCursor;
}
- (void)setBlinkingCursor:(BOOL)bFlag
{
blinkingCursor = bFlag;
}
- (void)setBlinkAllowed:(BOOL)value
{
blinkAllowed_ = value;
}
- (void)setCursorType:(ITermCursorType)value
{
cursorType_ = value;
}
- (void)setDimOnlyText:(BOOL)value
{
dimOnlyText_ = value;
[[self superview] setNeedsDisplay:YES];
}
- (NSDictionary*)markedTextAttributes
{
return markedTextAttributes;
}
- (void)setMarkedTextAttributes:(NSDictionary *)attr
{
[markedTextAttributes release];
[attr retain];
markedTextAttributes=attr;
}
- (int)selectionStartX
{
return startX;
}
- (int)selectionStartY
{
return startY;
}
- (int)selectionEndX
{
return endX;
}
- (int)selectionEndY
{
return endY;
}
- (void)setSelectionTime
{
if (startX < 0 || (startX == endX && startY == endY)) {
selectionTime_ = 0;
} else {
selectionTime_ = [[NSDate date] timeIntervalSince1970];
}
}
- (void)setSelectionFromX:(int)fromX fromY:(int)fromY toX:(int)toX toY:(int)toY
{
startX = fromX;
startY = fromY;
endX = toX;
endY = toY;
[self setSelectionTime];
}
- (void)setRectangularSelection:(BOOL)isBox
{
selectMode = isBox ? SELECT_BOX : SELECT_CHAR;
}
- (void)setFGColor:(NSColor*)color
{
[defaultFGColor release];
[color retain];
defaultFGColor = color;
[self setNeedsDisplay:YES];
}
- (void)setBGColor:(NSColor*)color
{
[defaultBGColor release];
[color retain];
defaultBGColor = color;
PTYScroller *scroller = (PTYScroller*)[[[dataSource session] SCROLLVIEW] verticalScroller];
BOOL isDark = ([self perceivedBrightness:color] < kBackgroundConsideredDarkThreshold);
backgroundBrightness_ = PerceivedBrightness([color redComponent], [color greenComponent], [color blueComponent]);
[scroller setHasDarkBackground:isDark];
[self setNeedsDisplay:YES];
}
- (void)setBoldColor:(NSColor*)color
{
[defaultBoldColor release];
[color retain];
defaultBoldColor = color;
[self setNeedsDisplay:YES];
}
- (void)setCursorColor:(NSColor*)color
{
[defaultCursorColor release];
[color retain];
defaultCursorColor = color;
[self setNeedsDisplay:YES];
}
- (void)setSelectedTextColor:(NSColor *)aColor
{
[selectedTextColor release];
[aColor retain];
selectedTextColor = aColor;
[self setNeedsDisplay:YES];
}
- (void)setCursorTextColor:(NSColor*)aColor
{
[cursorTextColor release];
[aColor retain];
cursorTextColor = aColor;
[self setNeedsDisplay:YES];
}
- (NSColor*)cursorTextColor
{
return cursorTextColor;
}
- (NSColor*)selectedTextColor
{
return selectedTextColor;
}
- (NSColor*)defaultFGColor
{
return defaultFGColor;
}
- (NSColor *) defaultBGColor
{
return defaultBGColor;
}
- (NSColor *) defaultBoldColor
{
return defaultBoldColor;
}
- (NSColor *) defaultCursorColor
{
return defaultCursorColor;
}
- (void)setColorTable:(int)theIndex color:(NSColor*)origColor
{
NSColor* theColor = [origColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
[colorTable[theIndex] release];
[theColor retain];
colorTable[theIndex] = theColor;
[self setNeedsDisplay:YES];
}
- (NSColor*)_colorForCode:(int)theIndex alternateSemantics:(BOOL)alt bold:(BOOL)isBold
{
NSColor* color;
if (alt) {
switch (theIndex) {
case ALTSEM_SELECTED:
color = selectedTextColor;
break;
case ALTSEM_CURSOR:
color = cursorTextColor;
break;
case ALTSEM_BG_DEFAULT:
color = defaultBGColor;
break;
default:
if (isBold && useBrightBold) {
if (theIndex == ALTSEM_BG_DEFAULT) {
color = defaultBGColor;
} else {
color = [self defaultBoldColor];
}
} else {
color = defaultFGColor;
}
}
} else {
// Render bold text as bright. The spec (ECMA-48) describes the intense
// display setting (esc[1m) as "bold or bright". We make it a
// preference.
if (isBold &&
useBrightBold &&
(theIndex < 8)) { // Only colors 0-7 can be made "bright".
theIndex |= 8; // set "bright" bit.
}
color = colorTable[theIndex];
}
return color;
}
- (NSColor*)_dimmedColorFrom:(NSColor*)orig
{
if (dimmingAmount_ == 0) {
return orig;
}
double r = [orig redComponent];
double g = [orig greenComponent];
double b = [orig blueComponent];
// This algorithm limits the dynamic range of colors as well as brightening
// them. Both attributes change in proportion to the dimmingAmount_.
// Find a linear interpolation between kCenter and the requested color component
// in proportion to 1- dimmingAmount_.
if (!dimOnlyText_) {
const double kCenter = 0.5;
return [NSColor colorWithCalibratedRed:(1 - dimmingAmount_) * r + dimmingAmount_ * kCenter
green:(1 - dimmingAmount_) * g + dimmingAmount_ * kCenter
blue:(1 - dimmingAmount_) * b + dimmingAmount_ * kCenter
alpha:[orig alphaComponent]];
} else {
return [NSColor colorWithCalibratedRed:(1 - dimmingAmount_) * r + dimmingAmount_ * backgroundBrightness_
green:(1 - dimmingAmount_) * g + dimmingAmount_ * backgroundBrightness_
blue:(1 - dimmingAmount_) * b + dimmingAmount_ * backgroundBrightness_
alpha:[orig alphaComponent]];
}
}
- (NSColor*)colorForCode:(int)theIndex alternateSemantics:(BOOL)alt bold:(BOOL)isBold isBackground:(BOOL)isBackground
{
NSColor *theColor = [self _colorForCode:theIndex
alternateSemantics:alt
bold:isBold];
if (isBackground && dimOnlyText_) {
return theColor;
} else {
return [self _dimmedColorFrom:theColor];
}
}
- (NSColor *)selectionColor
{
return selectionColor;
}
- (void)setSelectionColor:(NSColor *)aColor
{
[selectionColor release];
[aColor retain];
selectionColor = aColor;
[self setNeedsDisplay:YES];
}
- (NSFont *)font
{
return primaryFont.font;
}
- (NSFont *)nafont
{
return secondaryFont.font;
}
+ (NSSize)charSizeForFont:(NSFont*)aFont horizontalSpacing:(double)hspace verticalSpacing:(double)vspace baseline:(double*)baseline
{
FontSizeEstimator* fse = [FontSizeEstimator fontSizeEstimatorForFont:aFont];
NSSize size = [fse size];
size.width = ceil(size.width * hspace);
size.height = ceil(vspace * ceil(size.height + [aFont leading]));
if (baseline) {
*baseline = [fse baseline];
}
return size;
}
+ (NSSize)charSizeForFont:(NSFont*)aFont horizontalSpacing:(double)hspace verticalSpacing:(double)vspace
{
return [PTYTextView charSizeForFont:aFont horizontalSpacing:hspace verticalSpacing:vspace baseline:nil];
}
- (double)horizontalSpacing
{
return horizontalSpacing_;
}
- (double)verticalSpacing
{
return verticalSpacing_;
}
- (void)setFont:(NSFont*)aFont
nafont:(NSFont *)naFont
horizontalSpacing:(double)horizontalSpacing
verticalSpacing:(double)verticalSpacing
{
double baseline;
NSSize sz = [PTYTextView charSizeForFont:aFont
horizontalSpacing:1.0
verticalSpacing:1.0
baseline:&baseline];
charWidthWithoutSpacing = sz.width;
charHeightWithoutSpacing = sz.height;
horizontalSpacing_ = horizontalSpacing;
verticalSpacing_ = verticalSpacing;
charWidth = ceil(charWidthWithoutSpacing * horizontalSpacing);
lineHeight = ceil(charHeightWithoutSpacing * verticalSpacing);
[self modifyFont:aFont baseline:baseline info:&primaryFont];
[self modifyFont:naFont baseline:baseline info:&secondaryFont];
// Cannot keep fallback fonts if the primary font changes because their
// baseline offsets are set by the primary font. It's simplest to remove
// them and then re-add them as needed.
[self releaseAllFallbackFonts];
// Force the secondary font to use the same baseline as the primary font.
secondaryFont.baselineOffset = primaryFont.baselineOffset;
if (secondaryFont.boldVersion) {
if (primaryFont.boldVersion) {
secondaryFont.boldVersion->baselineOffset = primaryFont.boldVersion->baselineOffset;
} else {
secondaryFont.boldVersion->baselineOffset = secondaryFont.baselineOffset;
}
}
[self setMarkedTextAttributes:
[NSDictionary dictionaryWithObjectsAndKeys:
defaultBGColor, NSBackgroundColorAttributeName,
defaultFGColor, NSForegroundColorAttributeName,
secondaryFont.font, NSFontAttributeName,
[NSNumber numberWithInt:(NSUnderlineStyleSingle|NSUnderlineByWordMask)],
NSUnderlineStyleAttributeName,
NULL]];
[self setNeedsDisplay:YES];
NSScrollView* scrollview = [self enclosingScrollView];
[scrollview setLineScroll:[self lineHeight]];
[scrollview setPageScroll:2 * [self lineHeight]];
[_delegate textViewFontDidChange];
}
- (void)changeFont:(id)fontManager
{
if ([[PreferencePanel sharedInstance] onScreen]) {
[[PreferencePanel sharedInstance] changeFont:fontManager];
} else if ([[PreferencePanel sessionsInstance] onScreen]) {
[[PreferencePanel sessionsInstance] changeFont:fontManager];
}
}
- (id)dataSource
{
return dataSource;
}
- (void)setDataSource:(id)aDataSource
{
dataSource = aDataSource;
}
- (id)delegate
{
return _delegate;
}
- (void)setDelegate:(id)aDelegate
{
_delegate = aDelegate;
}
- (double)lineHeight
{
return ceil(lineHeight);
}
- (void)setLineHeight:(double)aLineHeight
{
lineHeight = aLineHeight;
}
- (double)charWidth
{
return ceil(charWidth);
}
- (void)setCharWidth:(double)width
{
charWidth = width;
}
#ifdef DEBUG_DRAWING
NSMutableArray* screens=0;
- (void)appendDebug:(NSString*)str
{
if (!screens) {
screens = [[NSMutableArray alloc] init];
}
[screens addObject:str];
if ([screens count] > 100) {
[screens removeObjectAtIndex:0];
}
}
#endif
- (NSRect)scrollViewContentSize
{
NSRect r = NSMakeRect(0, 0, 0, 0);
r.size = [[self enclosingScrollView] contentSize];
return r;
}
- (double)excess
{
NSRect visible = [self scrollViewContentSize];
visible.size.height -= VMARGIN * 2; // Height without top and bottom margins.
int rows = visible.size.height / lineHeight;
double usablePixels = rows * lineHeight;
return MAX(visible.size.height - usablePixels + VMARGIN, VMARGIN); // Never have less than VMARGIN excess, but it can be more (if another tab has a bigger font)
}
// We override this method since both refresh and window resize can conflict
// resulting in this happening twice So we do not allow the size to be set
// larger than what the data source can fill
- (void)setFrameSize:(NSSize)frameSize
{
// Force the height to always be correct
frameSize.height = [dataSource numberOfLines] * lineHeight + [self excess] + imeOffset * lineHeight;
[super setFrameSize:frameSize];
frameSize.height += VMARGIN; // This causes a margin to be left at the top
[[self superview] setFrameSize:frameSize];
}
- (void)scheduleSelectionScroll
{
if (selectionScrollTimer) {
[selectionScrollTimer release];
if (prevScrollDelay > 0.001) {
// Maximum speed hasn't been reached so accelerate scrolling speed by 5%.
prevScrollDelay *= 0.95;
}
} else {
// Set a slow initial scrolling speed.
prevScrollDelay = 0.1;
}
lastSelectionScroll = [[NSDate date] timeIntervalSince1970];
selectionScrollTimer = [[NSTimer scheduledTimerWithTimeInterval:prevScrollDelay
target:self
selector:@selector(updateSelectionScroll)
userInfo:nil
repeats:NO] retain];
}
// Scroll the screen up or down a line for a selection drag scroll.
- (void)updateSelectionScroll
{
double actualDelay = [[NSDate date] timeIntervalSince1970] - lastSelectionScroll;
const int kMaxLines = 100;
int numLines = MIN(kMaxLines, MAX(1, actualDelay / prevScrollDelay));
NSRect visibleRect = [self visibleRect];
int y = 0;
if (!selectionScrollDirection) {
[selectionScrollTimer release];
selectionScrollTimer = nil;
return;
} else if (selectionScrollDirection < 0) {
visibleRect.origin.y -= [self lineHeight] * numLines;
// Allow the origin to go as far as y=-VMARGIN so the top border is shown when the first line is
// on screen.
if (visibleRect.origin.y >= -VMARGIN) {
[self scrollRectToVisible:visibleRect];
}
y = visibleRect.origin.y / lineHeight;
} else if (selectionScrollDirection > 0) {
visibleRect.origin.y += lineHeight * numLines;
if (visibleRect.origin.y + visibleRect.size.height > [self frame].size.height) {
visibleRect.origin.y = [self frame].size.height - visibleRect.size.height;
}
[self scrollRectToVisible:visibleRect];
y = (visibleRect.origin.y + visibleRect.size.height - [self excess]) / lineHeight;
}
[self moveSelectionEndpointToX:scrollingX
Y:y
locationInTextView:scrollingLocation];
[[[self dataSource] session] refreshAndStartTimerIfNeeded];
[self scheduleSelectionScroll];
}
- (BOOL)accessibilityIsIgnored
{
return NO;
}
- (NSArray*)accessibilityAttributeNames
{
return [NSArray arrayWithObjects:
NSAccessibilityRoleAttribute,
NSAccessibilityRoleDescriptionAttribute,
NSAccessibilityHelpAttribute,
NSAccessibilityFocusedAttribute,
NSAccessibilityParentAttribute,
NSAccessibilityChildrenAttribute,
NSAccessibilityWindowAttribute,
NSAccessibilityTopLevelUIElementAttribute,
NSAccessibilityPositionAttribute,
NSAccessibilitySizeAttribute,
NSAccessibilityDescriptionAttribute,
NSAccessibilityValueAttribute,
NSAccessibilityNumberOfCharactersAttribute,
NSAccessibilitySelectedTextAttribute,
NSAccessibilitySelectedTextRangeAttribute,
NSAccessibilitySelectedTextRangesAttribute,
NSAccessibilityInsertionPointLineNumberAttribute,
NSAccessibilityVisibleCharacterRangeAttribute,
nil];
}
- (NSArray *)accessibilityParameterizedAttributeNames
{
return [NSArray arrayWithObjects:
NSAccessibilityLineForIndexParameterizedAttribute,
NSAccessibilityRangeForLineParameterizedAttribute,
NSAccessibilityStringForRangeParameterizedAttribute,
NSAccessibilityRangeForPositionParameterizedAttribute,
NSAccessibilityRangeForIndexParameterizedAttribute,
NSAccessibilityBoundsForRangeParameterizedAttribute,
nil];
}
// Range in allText_ of the given line.
- (NSRange)_rangeOfLine:(NSUInteger)lineNumber
{
NSRange range;
if (lineNumber == 0) {
range.location = 0;
} else {
range.location = [[lineBreakCharOffsets_ objectAtIndex:lineNumber-1] unsignedLongValue];
}
if (lineNumber >= [lineBreakCharOffsets_ count]) {
range.length = [allText_ length] - range.location;
} else {
range.length = [[lineBreakCharOffsets_ objectAtIndex:lineNumber] unsignedLongValue] - range.location;
}
return range;
}
// Range in allText_ of the given index.
- (NSUInteger)_lineNumberOfIndex:(NSUInteger)theIndex
{
NSUInteger lineNum = 0;
for (NSNumber* n in lineBreakIndexOffsets_) {
NSUInteger offset = [n unsignedLongValue];
if (offset > theIndex) {
break;
}
lineNum++;
}
return lineNum;
}
// Line number of a location (respecting compositing chars) in allText_.
- (NSUInteger)_lineNumberOfChar:(NSUInteger)location
{
NSUInteger lineNum = 0;
for (NSNumber* n in lineBreakCharOffsets_) {
NSUInteger offset = [n unsignedLongValue];
if (offset > location) {
break;
}
lineNum++;
}
return lineNum;
}
// Number of unichar a character uses (normally 1 in English).
- (int)_lengthOfChar:(screen_char_t)sct
{
return [ScreenCharToStr(&sct) length];
}
// Position, respecting compositing chars, in allText_ of a line.
- (NSUInteger)_offsetOfLine:(NSUInteger)lineNum
{
if (lineNum == 0) {
return 0;
}
assert(lineNum < [lineBreakCharOffsets_ count] + 1);
return [[lineBreakCharOffsets_ objectAtIndex:lineNum - 1] unsignedLongValue];
}
// Onscreen X-position of a location (respecting compositing chars) in allText_.
- (NSUInteger)_columnOfChar:(NSUInteger)location inLine:(NSUInteger)lineNum
{
NSUInteger lineStart = [self _offsetOfLine:lineNum];
screen_char_t* theLine = [dataSource getLineAtIndex:lineNum];
assert(location >= lineStart);
int remaining = location - lineStart;
int i = 0;
while (remaining > 0 && i < [dataSource width]) {
remaining -= [self _lengthOfChar:theLine[i++]];
}
return i;
}
// Index (ignoring compositing chars) of a line in allText_.
- (NSUInteger)_startingIndexOfLineNumber:(NSUInteger)lineNumber
{
if (lineNumber < [lineBreakIndexOffsets_ count]) {
return [[lineBreakCharOffsets_ objectAtIndex:lineNumber] unsignedLongValue];
} else if ([lineBreakIndexOffsets_ count] > 0) {
return [[lineBreakIndexOffsets_ lastObject] unsignedLongValue];
} else {
return 0;
}
}
// Range in allText_ of an index (ignoring compositing chars).
- (NSRange)_rangeOfIndex:(NSUInteger)theIndex
{
NSUInteger lineNumber = [self _lineNumberOfIndex:theIndex];
screen_char_t* theLine = [dataSource getLineAtIndex:lineNumber];
NSUInteger startingIndexOfLine = [self _startingIndexOfLineNumber:lineNumber];
assert(theIndex >= startingIndexOfLine);
int x = theIndex - startingIndexOfLine;
NSRange rangeOfLine = [self _rangeOfLine:lineNumber];
NSRange range;
range.location = rangeOfLine.location;
for (int i = 0; i < x; i++) {
range.location += [self _lengthOfChar:theLine[i]];
}
range.length = [self _lengthOfChar:theLine[x]];
return range;
}
// Range, respecting compositing chars, of a character at an x,y position where 0,0 is the
// first char of the first line in the scrollback buffer.
- (NSRange)_rangeOfCharAtX:(int)x y:(int)y
{
screen_char_t* theLine = [dataSource getLineAtIndex:y];
NSRange lineRange = [self _rangeOfLine:y];
NSRange result = lineRange;
for (int i = 0; i < x; i++) {
result.location += [self _lengthOfChar:theLine[i]];
}
result.length = [self _lengthOfChar:theLine[x]];
return result;
}
/*
* The concepts used here are not defined, so I'm going to give it my best guess.
*
* Suppose we have a terminal window like this:
*
* Line On-Screen Contents
* 0 [x]
* 1 [ba'r] (the ' is a combining accent)
* 2 [y]
*
* Index Location (as in a range) Character
* 0 0 f
* 1 1 b
* 2 2-3 b + [']
* 3 4 r
* 4 5 y
*
* Index 012 34
* Char 012345
* allText_ = xba´ry
* lineBreakCharOffests_ = [1, 4]
* lineBreakInexOffsets_ = [1, 3]
*/
- (id)_accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter
{
if ([attribute isEqualToString:NSAccessibilityLineForIndexParameterizedAttribute]) {
//(NSNumber *) - line# for char index; param:(NSNumber *)
NSUInteger theIndex = [(NSNumber*)parameter unsignedLongValue];
return [NSNumber numberWithUnsignedLong:[self _lineNumberOfIndex:theIndex]];
} else if ([attribute isEqualToString:NSAccessibilityRangeForLineParameterizedAttribute]) {
//(NSValue *) - (rangeValue) range of line; param:(NSNumber *)
NSUInteger lineNumber = [(NSNumber*)parameter unsignedLongValue];
if (lineNumber >= [lineBreakIndexOffsets_ count]) {
return [NSValue valueWithRange:NSMakeRange(NSNotFound, 0)];
} else {
return [NSValue valueWithRange:[self _rangeOfLine:lineNumber]];
}
} else if ([attribute isEqualToString:NSAccessibilityStringForRangeParameterizedAttribute]) {
//(NSString *) - substring; param:(NSValue * - rangeValue)
NSRange range = [(NSValue*)parameter rangeValue];
return [allText_ substringWithRange:range];
} else if ([attribute isEqualToString:NSAccessibilityRangeForPositionParameterizedAttribute]) {
//(NSValue *) - (rangeValue) composed char range; param:(NSValue * - pointValue)
NSPoint position = [(NSValue*)parameter pointValue];
int x = position.x / charWidth;
NSRect myFrame = [self frame];
myFrame.size.height = 0;
int y = (myFrame.size.height - position.y) / lineHeight;
return [NSValue valueWithRange:[self _rangeOfCharAtX:x y:y]];
} else if ([attribute isEqualToString:NSAccessibilityRangeForIndexParameterizedAttribute]) {
//(NSValue *) - (rangeValue) composed char range; param:(NSNumber *)
NSUInteger theIndex = [(NSNumber*)parameter unsignedLongValue];
return [NSValue valueWithRange:[self _rangeOfIndex:theIndex]];
} else if ([attribute isEqualToString:NSAccessibilityBoundsForRangeParameterizedAttribute]) {
//(NSValue *) - (rectValue) bounds of text; param:(NSValue * - rangeValue)
NSRange range = [(NSValue*)parameter rangeValue];
int yStart = [self _lineNumberOfChar:range.location];
int y2 = [self _lineNumberOfChar:range.location + range.length - 1];
int xStart = [self _columnOfChar:range.location inLine:yStart];
int x2 = [self _columnOfChar:range.location + range.length - 1 inLine:y2];
++x2;
if (x2 == [dataSource width]) {
x2 = 0;
++y2;
}
int yMin = MIN(yStart, y2);
int yMax = MAX(yStart, y2);
int xMin = MIN(xStart, x2);
int xMax = MAX(xStart, x2);
NSRect myFrame = [self frame];
myFrame.size.height = 0;
NSRect result = NSMakeRect(xMin * charWidth,
myFrame.size.height - yMin * lineHeight,
(xMax - xMin + 1) * charWidth,
(yMax - yMin + 1) * lineHeight);
return [NSValue valueWithRect:result];
} else {
return [super accessibilityAttributeValue:attribute forParameter:parameter];
}
}
- (id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter
{
id result = [self _accessibilityAttributeValue:attribute forParameter:parameter];
return result;
}
// TODO(georgen): Speed this up! This code is dreadfully slow but it's only used
// when accessibility is on, and it might be faster than voiceover for reasonable
// amounts of text.
- (NSString*)_allText
{
[allText_ release];
[lineBreakCharOffsets_ release];
[lineBreakIndexOffsets_ release];
allText_ = [[NSMutableString alloc] init];
lineBreakCharOffsets_ = [[NSMutableArray alloc] init];
lineBreakIndexOffsets_ = [[NSMutableArray alloc] init];
int width = [dataSource width];
unichar chars[width * kMaxParts];
int offset = 0;
for (int i = 0; i < [dataSource numberOfLines]; i++) {
screen_char_t* line = [dataSource getLineAtIndex:i];
int k;
// Get line width, store it in k
for (k = width - 1; k >= 0; k--) {
if (line[k].code) {
break;
}
}
int o = 0;
// Add first width-k chars to the 'chars' array, expanding complex chars.
for (int j = 0; j <= k; j++) {
if (line[j].complexChar) {
NSString* cs = ComplexCharToStr(line[j].code);
for (int l = 0; l < [cs length]; ++l) {
chars[o++] = [cs characterAtIndex:l];
}
} else {
chars[o++] = line[j].code;
}
}
// Append this line to allText_.
offset += o;
if (k > 0) {
[allText_ appendString:[NSString stringWithCharacters:chars length:o]];
}
if (line[width].code == EOL_HARD) {
// Add a newline and update offsets arrays that track line break locations.
[allText_ appendString:@"\n"];
++offset;
[lineBreakCharOffsets_ addObject:[NSNumber numberWithUnsignedLong:[allText_ length]]];
[lineBreakIndexOffsets_ addObject:[NSNumber numberWithUnsignedLong:offset]];
}
}
return allText_;
}
- (id)accessibilityAttributeValue:(NSString *)attribute
{
if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
return NSAccessibilityTextAreaRole;
} else if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) {
return NSAccessibilityRoleDescriptionForUIElement(NSAccessibilityTextAreaRole);
} else if ([attribute isEqualToString:NSAccessibilityHelpAttribute]) {
return nil;
} else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
return [NSNumber numberWithBool:YES];
} else if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute]) {
return @"shell";
} else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
return [self _allText];
} else if ([attribute isEqualToString:NSAccessibilityNumberOfCharactersAttribute]) {
return [NSNumber numberWithInt:[[self _allText] length]];
} else if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) {
return [self selectedText];
} else if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
int x = [dataSource cursorX] - 1;
int y = [dataSource numberOfLines] - [dataSource height] + [dataSource cursorY] - 1;
NSRange range = [self _rangeOfCharAtX:x y:y];
range.length--;
return [NSValue valueWithRange:range];
} else if ([attribute isEqualToString:NSAccessibilitySelectedTextRangesAttribute]) {
int x = [dataSource cursorX] - 1;
int y = [dataSource numberOfLines] - [dataSource height] + [dataSource cursorY] - 1;
NSRange range = [self _rangeOfCharAtX:x y:y];
range.length--;
return [NSArray arrayWithObject:
[NSValue valueWithRange:range]];
} else if ([attribute isEqualToString:NSAccessibilityInsertionPointLineNumberAttribute]) {
return [NSNumber numberWithInt:[dataSource cursorY]-1 + [dataSource numberOfScrollbackLines]];
} else if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) {
return [NSValue valueWithRange:NSMakeRange(0, [[self _allText] length])];
} else {
return [super accessibilityAttributeValue:attribute];
}
}
- (BOOL)_isCursorBlinking
{
if ([self blinkingCursor] &&
[[self window] isKeyWindow] &&
[[[dataSource session] tab] activeSession] == [dataSource session]) {
return YES;
} else {
return NO;
}
}
- (BOOL)_charBlinks:(screen_char_t)sct
{
return blinkAllowed_ && sct.blink;
}
- (BOOL)_isTextBlinking
{
int width = [dataSource width];
int lineStart = ([self visibleRect].origin.y + VMARGIN) / lineHeight; // add VMARGIN because stuff under top margin isn't visible.
int lineEnd = ceil(([self visibleRect].origin.y + [self visibleRect].size.height - [self excess]) / lineHeight);
if (lineStart < 0) {
lineStart = 0;
}
if (lineEnd > [dataSource numberOfLines]) {
lineEnd = [dataSource numberOfLines];
}
for (int y = lineStart; y < lineEnd; y++) {
screen_char_t* theLine = [dataSource getLineAtIndex:y];
for (int x = 0; x < width; x++) {
if (theLine[x].blink) {
return YES;
}
}
}
return NO;
}
- (BOOL)_isAnythingBlinking
{
return [self _isCursorBlinking] || (blinkAllowed_ && [self _isTextBlinking]);
}
- (BOOL)refresh
{
DebugLog(@"PTYTextView refresh called");
if (dataSource == nil) {
return YES;
}
// number of lines that have disappeared if scrollback buffer is full
int scrollbackOverflow = [dataSource scrollbackOverflow];
[dataSource resetScrollbackOverflow];
// frame size changed?
int height = [dataSource numberOfLines] * lineHeight;
NSRect frame = [self frame];
double excess = [self excess];
if ((int)(height + excess + imeOffset * lineHeight) != frame.size.height) {
// The old iTerm code had a comment about a hack at this location
// that worked around an (alleged) bug in NSClipView not respecting
// setCopiesOnScroll:YES and a gross workaround. The workaround caused
// drawing bugs on Snow Leopard. It was a performance optimization, but
// the penalty was too great.
//
// I believe the redraw errors occurred because they disabled drawing
// for the duration of the call. [drawRects] was called (in another thread?)
// and didn't redraw some invalid rects. Those rects never got another shot
// at being drawn. They had originally been invalidated because they had
// new content. The bug only happened when drawRect was called twice for
// the same screen (I guess because the timer fired while the screen was
// being updated).
// Resize the frame
// Add VMARGIN to include top margin.
frame.size.height = height + excess + imeOffset * lineHeight + VMARGIN;
[[self superview] setFrame:frame];
frame.size.height -= VMARGIN;
NSAccessibilityPostNotification(self, NSAccessibilityRowCountChangedNotification);
} else if (scrollbackOverflow > 0) {
// Some number of lines were lost from the head of the buffer.
NSScrollView* scrollView = [self enclosingScrollView];
double amount = [scrollView verticalLineScroll] * scrollbackOverflow;
BOOL userScroll = [(PTYScroller*)([scrollView verticalScroller]) userScroll];
// Keep correct selection highlighted
startY -= scrollbackOverflow;
if (startY < 0) {
startX = -1;
[self setSelectionTime];
}
endY -= scrollbackOverflow;
oldStartY -= scrollbackOverflow;
if (oldStartY < 0) {
oldStartX = -1;
}
oldEndY -= scrollbackOverflow;
// Keep the user's current scroll position, nothing to redraw.
if (userScroll) {
BOOL redrawAll = NO;
NSRect scrollRect = [self visibleRect];
scrollRect.origin.y -= amount;
if (scrollRect.origin.y < 0) {
scrollRect.origin.y = 0;
redrawAll = YES;
[self setNeedsDisplay:YES];
}
[self scrollRectToVisible:scrollRect];
if (!redrawAll) {
return [self _isAnythingBlinking];
}
}
// Shift the old content upwards
if (scrollbackOverflow < [dataSource height] && !userScroll) {
[self scrollRect:[self visibleRect] by:NSMakeSize(0, -amount)];
NSRect topMargin = [self visibleRect];
topMargin.size.height = VMARGIN;
[self setNeedsDisplayInRect:topMargin];
#ifdef DEBUG_DRAWING
[self appendDebug:[NSString stringWithFormat:@"refresh: Scroll by %d", (int)amount]];
#endif
if ([self needsDisplay]) {
// If any part of the view needed to be drawn prior to
// scrolling, mark the whole thing as needing to be redrawn.
// This avoids some race conditions between scrolling and
// drawing. For example, if there was a region that needed to
// be displayed because the underlying data changed, but before
// drawRect is called we scroll with [self scrollRect], then
// the wrong region will be drawn. This could be optimized by
// storing the regions that need to be drawn and re-invaliding
// them in their new positions, but it should be somewhat rare
// that this branch of the if statement is taken.
[self setNeedsDisplay:YES];
} else {
// Invalidate the bottom of the screen that was revealed by
// scrolling.
NSRect dr = NSMakeRect(0, frame.size.height - amount, frame.size.width, amount);
#ifdef DEBUG_DRAWING
[self appendDebug:[NSString stringWithFormat:@"refresh: setNeedsDisplayInRect:%d,%d %dx%d", (int)dr.origin.x, (int)dr.origin.y, (int)dr.size.width, (int)dr.size.height]];
#endif
[self setNeedsDisplayInRect:dr];
}
}
NSAccessibilityPostNotification(self, NSAccessibilityRowCountChangedNotification);
}
// Scroll to the bottom if needed.
BOOL userScroll = [(PTYScroller*)([[self enclosingScrollView] verticalScroller]) userScroll];
if (!userScroll) {
[self scrollEnd];
}
NSAccessibilityPostNotification(self, NSAccessibilityValueChangedNotification);
long long absCursorY = [dataSource cursorY] + [dataSource numberOfLines] + [dataSource totalScrollbackOverflow] - [dataSource height];
if ([dataSource cursorX] != accX ||
absCursorY != accY) {
NSAccessibilityPostNotification(self, NSAccessibilitySelectedTextChangedNotification);
NSAccessibilityPostNotification(self, NSAccessibilitySelectedRowsChangedNotification);
NSAccessibilityPostNotification(self, NSAccessibilitySelectedColumnsChangedNotification);
accX = [dataSource cursorX];
accY = absCursorY;
}
return [self updateDirtyRects] || [self _isCursorBlinking];
}
- (NSRect)adjustScroll:(NSRect)proposedVisibleRect
{
proposedVisibleRect.origin.y=(int)(proposedVisibleRect.origin.y/lineHeight+0.5)*lineHeight;
return proposedVisibleRect;
}
- (void)scrollLineUp:(id)sender
{
NSRect scrollRect;
scrollRect= [self visibleRect];
scrollRect.origin.y-=[[self enclosingScrollView] verticalLineScroll];
if (scrollRect.origin.y<0) scrollRect.origin.y=0;
[self scrollRectToVisible: scrollRect];
}
- (void)scrollLineDown:(id)sender
{
NSRect scrollRect;
scrollRect= [self visibleRect];
scrollRect.origin.y+=[[self enclosingScrollView] verticalLineScroll];
[self scrollRectToVisible: scrollRect];
}
- (void)scrollPageUp:(id)sender
{
NSRect scrollRect;
scrollRect= [self visibleRect];
scrollRect.origin.y-= scrollRect.size.height - [[self enclosingScrollView] verticalPageScroll];
[self scrollRectToVisible: scrollRect];
}
- (void)scrollPageDown:(id)sender
{
NSRect scrollRect;
scrollRect = [self visibleRect];
scrollRect.origin.y+= scrollRect.size.height - [[self enclosingScrollView] verticalPageScroll];
[self scrollRectToVisible: scrollRect];
}
- (void)scrollHome
{
NSRect scrollRect;
scrollRect = [self visibleRect];
scrollRect.origin.y = 0;
[self scrollRectToVisible: scrollRect];
}
- (void)scrollEnd
{
if ([dataSource numberOfLines] <= 0) {
return;
}
NSRect lastLine = [self visibleRect];
lastLine.origin.y = ([dataSource numberOfLines] - 1) * lineHeight + [self excess] + imeOffset * lineHeight;
lastLine.size.height = lineHeight;
[self scrollRectToVisible:lastLine];
}
- (long long)absoluteScrollPosition
{
NSRect visibleRect = [self visibleRect];
long long localOffset = (visibleRect.origin.y + VMARGIN) / [self lineHeight];
return localOffset + [dataSource totalScrollbackOverflow];
}
- (void)scrollToAbsoluteOffset:(long long)absOff height:(int)height
{
NSRect aFrame;
aFrame.origin.x = 0;
aFrame.origin.y = (absOff - [dataSource totalScrollbackOverflow]) * lineHeight - VMARGIN;
aFrame.size.width = [self frame].size.width;
aFrame.size.height = lineHeight * height;
[self scrollRectToVisible: aFrame];
[(PTYScroller*)([[self enclosingScrollView] verticalScroller]) setUserScroll:YES];
}
- (void)scrollToSelection
{
NSRect aFrame;
aFrame.origin.x = 0;
aFrame.origin.y = startY * lineHeight - VMARGIN; // allow for top margin
aFrame.size.width = [self frame].size.width;
aFrame.size.height = (endY - startY + 1) *lineHeight;
[self scrollRectToVisible: aFrame];
[(PTYScroller*)([[self enclosingScrollView] verticalScroller]) setUserScroll:YES];
}
- (void)hideCursor
{
CURSOR=NO;
}
- (void)showCursor
{
CURSOR=YES;
}
- (void)drawRect:(NSRect)rect
{
[self drawRect:rect to:nil];
const NSRect frame = [self visibleRect];
double x = frame.origin.x + frame.size.width;
if ([[[[dataSource session] tab] realParentWindow] broadcastInputToSession:[dataSource session]]) {
NSSize size = [broadcastInputImage size];
x -= size.width + kBroadcastMargin;
[broadcastInputImage drawAtPoint:NSMakePoint(x,
frame.origin.y + kBroadcastMargin)
fromRect:NSMakeRect(0, 0, size.width, size.height)
operation:NSCompositeSourceOver
fraction:0.5];
}
if ([[[dataSource session] SHELL] hasCoprocess]) {
NSSize size = [coprocessImage size];
x -= size.width + kCoprocessMargin;
[coprocessImage drawAtPoint:NSMakePoint(x,
frame.origin.y + kCoprocessMargin)
fromRect:NSMakeRect(0, 0, size.width, size.height)
operation:NSCompositeSourceOver
fraction:0.5];
}
if (flashing_ > 0) {
NSImage* image = nil;
switch (flashImage_) {
case FlashBell:
image = bellImage;
break;
case FlashWrapToTop:
image = wrapToTopImage;
break;
case FlashWrapToBottom:
image = wrapToBottomImage;
break;
}
NSSize size = [image size];
[image drawAtPoint:NSMakePoint(frame.origin.x + frame.size.width/2 - size.width/2,
frame.origin.y + frame.size.height/2 - size.height/2)
fromRect:NSMakeRect(0, 0, size.width, size.height)
operation:NSCompositeSourceOver
fraction:flashing_];
}
[self drawOutlineInRect:rect topOnly:NO];
}
- (void)drawOutlineInRect:(NSRect)rect topOnly:(BOOL)topOnly
{
if ([[[dataSource session] tab] hasMaximizedPane]) {
NSColor *color = [self defaultBGColor];
double r = [color redComponent];
double g = [color greenComponent];
double b = [color blueComponent];
double pb = PerceivedBrightness(r, g, b);
double k;
if (pb <= 0.5) {
k = 1;
} else {
k = 0;
}
const double alpha = 0.2;
r = alpha * k + (1 - alpha) * r;
g = alpha * k + (1 - alpha) * g;
b = alpha * k + (1 - alpha) * b;
color = [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1];
NSRect frame = [self visibleRect];
if (!topOnly) {
if (frame.origin.y < VMARGIN) {
frame.size.height += (VMARGIN - frame.origin.y);
frame.origin.y -= (VMARGIN - frame.origin.y);
}
}
NSBezierPath *path = [[[NSBezierPath alloc] init] autorelease];
CGFloat left = frame.origin.x + 0.5;
CGFloat right = frame.origin.x + frame.size.width - 0.5;
CGFloat top = frame.origin.y + 0.5;
CGFloat bottom = frame.origin.y + frame.size.height - 0.5;
if (topOnly) {
[path moveToPoint:NSMakePoint(left, top + VMARGIN)];
[path lineToPoint:NSMakePoint(left, top)];
[path lineToPoint:NSMakePoint(right, top)];
[path lineToPoint:NSMakePoint(right, top + VMARGIN)];
} else {
[path moveToPoint:NSMakePoint(left, top + VMARGIN)];
[path lineToPoint:NSMakePoint(left, top)];
[path lineToPoint:NSMakePoint(right, top)];
[path lineToPoint:NSMakePoint(right, bottom)];
[path lineToPoint:NSMakePoint(left, bottom)];
[path lineToPoint:NSMakePoint(left, top + VMARGIN)];
}
CGFloat dashPattern[2] = { 5, 5 };
[path setLineDash:dashPattern count:2 phase:0];
[color set];
[path stroke];
}
}
- (void)drawRect:(NSRect)rect to:(NSPoint*)toOrigin
{
#ifdef DEBUG_DRAWING
static int iteration=0;
static BOOL prevBad=NO;
++iteration;
if (prevBad) {
NSLog(@"Last was bad.");
prevBad = NO;
}
#endif
DebugLog([NSString stringWithFormat:@"%s(0x%x): rect=(%f,%f,%f,%f) frameRect=(%f,%f,%f,%f)]",
__PRETTY_FUNCTION__, self,
rect.origin.x, rect.origin.y, rect.size.width, rect.size.height,
[self frame].origin.x, [self frame].origin.y, [self frame].size.width, [self frame].size.height]);
double curLineWidth = [dataSource width] * charWidth;
if (lineHeight <= 0 || curLineWidth <= 0) {
DebugLog(@"height or width too small");
return;
}
// Configure graphics
[[NSGraphicsContext currentContext] setCompositingOperation:NSCompositeCopy];
// Where to start drawing?
int lineStart = rect.origin.y / lineHeight;
int lineEnd = ceil((rect.origin.y + rect.size.height) / lineHeight);
// Ensure valid line ranges
if (lineStart < 0) {
lineStart = 0;
}
if (lineEnd > [dataSource numberOfLines]) {
lineEnd = [dataSource numberOfLines];
}
NSRect visible = [self scrollViewContentSize];
int vh = visible.size.height;
int lh = lineHeight;
int visibleRows = vh / lh;
NSRect docVisibleRect = [[[dataSource session] SCROLLVIEW] documentVisibleRect];
double hiddenAbove = docVisibleRect.origin.y + [self frame].origin.y;
int firstVisibleRow = hiddenAbove / lh;
if (lineEnd > firstVisibleRow + visibleRows) {
lineEnd = firstVisibleRow + visibleRows;
}
DebugLog([NSString stringWithFormat:@"drawRect: Draw lines in range [%d, %d)", lineStart, lineEnd]);
// Draw each line
#ifdef DEBUG_DRAWING
NSMutableDictionary* dct =
[NSDictionary dictionaryWithObjectsAndKeys:
[NSColor textBackgroundColor], NSBackgroundColorAttributeName,
[NSColor textColor], NSForegroundColorAttributeName,
[NSFont userFixedPitchFontOfSize: 0], NSFontAttributeName, NULL];
#endif
int overflow = [dataSource scrollbackOverflow];
#ifdef DEBUG_DRAWING
NSMutableString* lineDebug = [NSMutableString stringWithFormat:@"drawRect:%d,%d %dx%d drawing these lines with scrollback overflow of %d, iteration=%d:\n", (int)rect.origin.x, (int)rect.origin.y, (int)rect.size.width, (int)rect.size.height, (int)[dataSource scrollbackOverflow], iteration];
#endif
double y = lineStart * lineHeight;
const double initialY = y;
BOOL anyBlinking = NO;
for (int line = lineStart; line < lineEnd; line++) {
NSRect lineRect = [self visibleRect];
lineRect.origin.y = line*lineHeight;
lineRect.size.height = lineHeight;
if ([self needsToDrawRect:lineRect]) {
if (overflow <= line) {
// If overflow > 0 then the lines in the dataSource are not
// lined up in the normal way with the view. This happens when
// the datasource has scrolled its contents up but -[refresh]
// has not been called yet, so the view's contents haven't been
// scrolled up yet. When that's the case, the first line of the
// view is what the first line of the datasource was before
// it overflowed. Continue to draw text in this out-of-alignment
// manner until refresh is called and gets things in sync again.
NSPoint temp;
if (toOrigin) {
const CGFloat offsetFromTopOfScreen = y - initialY;
temp = NSMakePoint(toOrigin->x, toOrigin->y + offsetFromTopOfScreen);
}
anyBlinking |= [self _drawLine:line-overflow AtY:y toPoint:toOrigin ? &temp : nil];
}
// if overflow > line then the requested line cannot be drawn
// because it has been lost to the sands of time.
if (gDebugLogging) {
screen_char_t* theLine = [dataSource getLineAtIndex:line-overflow];
int w = [dataSource width];
char dl[w+1];
for (int i = 0; i < [dataSource width]; ++i) {
if (theLine[i].complexChar) {
dl[i] = '#';
} else {
dl[i] = theLine[i].code;
}
}
DebugLog([NSString stringWithUTF8String:dl]);
}
#ifdef DEBUG_DRAWING
screen_char_t* theLine = [dataSource getLineAtIndex:line-overflow];
for (int i = 0; i < [dataSource width]; ++i) {
[lineDebug appendFormat:@"%@", ScreenCharToStr(&theLine[i])];
}
[lineDebug appendString:@"\n"];
[[NSString stringWithFormat:@"Iter %d, line %d, y=%d",
iteration, line, (int)(y)] drawInRect:NSMakeRect(rect.size.width-200,
y,
200,
lineHeight)
withAttributes:dct];
#endif
}
y += lineHeight;
}
#ifdef DEBUG_DRAWING
[self appendDebug:lineDebug];
#endif
NSRect excessRect;
if (imeOffset) {
// Draw a default-color rectangle from below the last line of text to
// the bottom of the frame to make sure that IME offset lines are
// cleared when the screen is scrolled up.
excessRect.origin.x = 0;
excessRect.origin.y = lineEnd * lineHeight;
excessRect.size.width = [[self enclosingScrollView] contentSize].width;
excessRect.size.height = [self frame].size.height - excessRect.origin.y;
} else {
// Draw the excess bar at the bottom of the visible rect the in case
// that some other tab has a larger font and these lines don't fit
// evenly in the available space.
NSRect visibleRect = [self visibleRect];
excessRect.origin.x = 0;
excessRect.origin.y = visibleRect.origin.y + visibleRect.size.height - [self excess];
excessRect.size.width = [[self enclosingScrollView] contentSize].width;
excessRect.size.height = [self excess];
}
#ifdef DEBUG_DRAWING
// Draws the excess bar in a different color each time
static int i;
i++;
double rc = ((double)((i + 0) % 100)) / 100;
double gc = ((double)((i + 33) % 100)) / 100;
double bc = ((double)((i + 66) % 100)) / 100;
[[NSColor colorWithCalibratedRed:rc green:gc blue:bc alpha:1] set];
NSRectFill(excessRect);
#else
if (toOrigin) {
[self drawBackground:excessRect toPoint:*toOrigin];
} else {
[self drawBackground:excessRect];
}
#endif
// Draw a margin at the top of the visible area.
NSRect topMarginRect = [self visibleRect];
if (topMarginRect.origin.y > 0) {
topMarginRect.size.height = VMARGIN;
if (toOrigin) {
[self drawBackground:topMarginRect toPoint:*toOrigin];
} else {
[self drawBackground:topMarginRect];
}
}
#ifdef DEBUG_DRAWING
// Draws a different-colored rectangle around each drawn area. Useful for
// seeing which groups of lines were drawn in a batch.
static double it;
it += 3.14/4;
double red = sin(it);
double green = sin(it + 1*2*3.14/3);
double blue = sin(it + 2*2*3.14/3);
NSColor* c = [NSColor colorWithCalibratedRed:red green:green blue:blue alpha:1];
[c set];
NSRect r = rect;
r.origin.y++;
r.size.height -= 2;
NSFrameRect(rect);
if (overflow != 0) {
// Draw a diagonal line through blocks that were drawn when there
// [dataSource scrollbackOverflow] > 0.
[NSBezierPath strokeLineFromPoint:NSMakePoint(r.origin.x, r.origin.y)
toPoint:NSMakePoint(r.origin.x + r.size.width, r.origin.y + r.size.height)];
}
NSString* debug;
if (overflow == 0) {
debug = [NSString stringWithFormat:@"origin=%d", (int)rect.origin.y];
} else {
debug = [NSString stringWithFormat:@"origin=%d, overflow=%d", (int)rect.origin.y, (int)overflow];
}
[debug drawInRect:rect withAttributes:dct];
#endif
// If the IME is in use, draw its contents over top of the "real" screen
// contents.
[self drawInputMethodEditorTextAt:[dataSource cursorX] - 1
y:[dataSource cursorY] - 1
width:[dataSource width]
height:[dataSource height]
cursorHeight:[self cursorHeight]];
[self _drawCursorTo:toOrigin];
anyBlinking |= [self _isCursorBlinking];
#ifdef DEBUG_DRAWING
if (overflow) {
// It's useful to put a breakpoint at the top of this function
// when prevBad == YES because then you can see the results of this
// draw function.
prevBad=YES;
}
#endif
if (anyBlinking) {
// The user might have used the scroll wheel to cause blinking text to become
// visible. Make sure the timer is running if anything onscreen is
// blinking.
[[dataSource session] scheduleUpdateIn:kBlinkTimerIntervalSec];
}
}
- (NSString*)_getTextInWindowAroundX:(int)x
y:(int)y
numLines:(int)numLines
targetOffset:(int*)targetOffset
coords:(NSMutableArray*)coords
ignoringNewlines:(BOOL)ignoringNewlines
{
const int width = [dataSource width];
NSMutableString* joinedLines = [NSMutableString stringWithCapacity:numLines * width];
*targetOffset = -1;
// If rejectAtHardEol is true, then stop when you hit a hard EOL.
// If false, stop when you hit a hard EOL that has an unused cell before it,
// otherwise keep going.
BOOL rejectAtHardEol = !ignoringNewlines;
int xMin, xMax;
xMin = 0;
xMax = width;
int j = 0;
for (int i = y - numLines; i <= y + numLines; i++) {
if (i < 0 || i >= [dataSource numberOfLines]) {
continue;
}
screen_char_t* theLine = [dataSource getLineAtIndex:i];
if (i < y && theLine[width].code == EOL_HARD) {
if (rejectAtHardEol || theLine[width - 1].code == 0) {
continue;
}
}
unichar* backingStore;
int* deltas;
NSString* string = ScreenCharArrayToString(theLine,
xMin,
MIN(EffectiveLineLength(theLine, width), xMax),
&backingStore,
&deltas);
int o = 0;
for (int k = 0; k < [string length]; k++, o++) {
o += deltas[k];
if (*targetOffset == -1 && i == y && o >= x) {
*targetOffset = k + [joinedLines length];
}
[coords addObject:[Coord coordWithX:o
y:i]];
}
[joinedLines appendString:string];
free(deltas);
free(backingStore);
j++;
if (i >= y && theLine[width].code == EOL_HARD) {
if (rejectAtHardEol || theLine[width - 1].code == 0) {
[coords addObject:[Coord coordWithX:o
y:i]];
break;
}
}
}
// TODO: What if it's multiple lines ending in a soft eol and the selection goes to the end?
return joinedLines;
}
- (BOOL)smartSelectAtX:(int)x
y:(int)y
toStartX:(int*)X1
toStartY:(int*)Y1
toEndX:(int*)X2
toEndY:(int*)Y2
ignoringNewlines:(BOOL)ignoringNewlines
{
NSString* textWindow;
int targetOffset;
const int numLines = 2;
const int width = [dataSource width];
NSMutableArray* coords = [NSMutableArray arrayWithCapacity:numLines * width];
textWindow = [self _getTextInWindowAroundX:x
y:y
numLines:2
targetOffset:&targetOffset
coords:coords
ignoringNewlines:ignoringNewlines];
NSArray* rulesArray = smartSelectionRules_ ? smartSelectionRules_ : [SmartSelectionController defaultRules];
const int numRules = [rulesArray count];
NSMutableDictionary* matches = [NSMutableDictionary dictionaryWithCapacity:13];
int numCoords = [coords count];
BOOL debug = [SmartSelectionController logDebugInfo];
if (debug) {
NSLog(@"Perform smart selection on text: %@", textWindow);
}
for (int j = 0; j < numRules; j++) {
NSDictionary *rule = [rulesArray objectAtIndex:j];
NSString *regex = [SmartSelectionController regexInRule:rule];
double precision = [SmartSelectionController precisionInRule:rule];
if (debug) {
NSLog(@"Try regex %@", regex);
}
for (int i = 0; i <= targetOffset; i++) {
NSString* substring = [textWindow substringWithRange:NSMakeRange(i, [textWindow length] - i)];
NSError* regexError = nil;
NSRange temp = [substring rangeOfRegex:regex
options:0
inRange:NSMakeRange(0, [substring length])
capture:0
error:&regexError];
if (temp.location != NSNotFound) {
if (i + temp.location <= targetOffset && i + temp.location + temp.length > targetOffset) {
NSString* result = [substring substringWithRange:temp];
double score = precision * (double) temp.length;
SmartMatch* oldMatch = [matches objectForKey:result];
if (!oldMatch || score > oldMatch->score) {
SmartMatch* match = [[[SmartMatch alloc] init] autorelease];
match->score = score;
Coord* startCoord = [coords objectAtIndex:i + temp.location];
Coord* endCoord = [coords objectAtIndex:MIN(numCoords - 1, i + temp.location + temp.length)];
match->startX = startCoord->x;
match->absStartY = startCoord->y + [dataSource totalScrollbackOverflow];
match->endX = endCoord->x;
match->absEndY = endCoord->y + [dataSource totalScrollbackOverflow];
[matches setObject:match forKey:result];
if (debug) {
NSLog(@"Add result %@ at %d,%lld -> %d,%lld with score %lf", result, match->startX, match->absStartY, match->endX, match->absEndY, match->score);
}
}
i += temp.location + temp.length - 1;
} else {
i += temp.location;
}
} else {
break;
}
}
}
if ([matches count]) {
NSArray* sortedMatches = [[[matches allValues] sortedArrayUsingSelector:@selector(compare:)] retain];
SmartMatch* bestMatch = [sortedMatches lastObject];
if (debug) {
NSLog(@"Select match with score %lf", bestMatch->score);
}
*X1 = bestMatch->startX;
*Y1 = bestMatch->absStartY - [dataSource totalScrollbackOverflow];
*X2 = bestMatch->endX;
*Y2 = bestMatch->absEndY - [dataSource totalScrollbackOverflow];
return YES;
} else {
if (debug) {
NSLog(@"No matches. Fall back on word selection.");
}
// Fall back on word selection
[self getWordForX:x
y:y
startX:X1
startY:Y1
endX:X2
endY:Y2];
return NO;
}
}
- (BOOL)smartSelectAtX:(int)x y:(int)y ignoringNewlines:(BOOL)ignoringNewlines
{
return [self smartSelectAtX:x
y:y
toStartX:&startX
toStartY:&startY
toEndX:&endX
toEndY:&endY
ignoringNewlines:ignoringNewlines];
}
// Control-pgup and control-pgdown are handled at this level by NSWindow if no
// view handles it. It's necessary to setUserScroll in the PTYScroller, or else
// it scrolls back to the bottom right away. This code handles those two
// keypresses and scrolls correctly.
- (BOOL)performKeyEquivalent:(NSEvent *)theEvent
{
NSString* unmodkeystr = [theEvent charactersIgnoringModifiers];
if ([unmodkeystr length] == 0) {
return [super performKeyEquivalent:theEvent];
}
unichar unmodunicode = [unmodkeystr length] > 0 ? [unmodkeystr characterAtIndex:0] : 0;
NSUInteger modifiers = [theEvent modifierFlags];
if ((modifiers & NSControlKeyMask) &&
(modifiers & NSFunctionKeyMask)) {
switch (unmodunicode) {
case NSPageUpFunctionKey:
[(PTYScroller*)([[self enclosingScrollView] verticalScroller]) setUserScroll:YES];
[self scrollPageUp:self];
return YES;
case NSPageDownFunctionKey:
[(PTYScroller*)([[self enclosingScrollView] verticalScroller]) setUserScroll:YES];
[self scrollPageDown:self];
return YES;
default:
break;
}
}
return [super performKeyEquivalent:theEvent];
}
- (void)keyDown:(NSEvent*)event
{
BOOL debugKeyDown = [[[NSUserDefaults standardUserDefaults] objectForKey:@"DebugKeyDown"] boolValue];
if (debugKeyDown) {
NSLog(@"PTYTextView keyDown BEGIN %@", event);
}
DebugLog(@"PTYTextView keyDown");
id delegate = [self delegate];
if ([[[[[self dataSource] session] tab] realParentWindow] inInstantReplay]) {
if (debugKeyDown) {
NSLog(@"PTYTextView keyDown: in instant replay, send to delegate");
}
// Delegate has special handling for this case.
[delegate keyDown:event];
return;
}
unsigned int modflag = [event modifierFlags];
unsigned short keyCode = [event keyCode];
BOOL prev = [self hasMarkedText];
keyIsARepeat = [event isARepeat];
if (debugKeyDown) {
NSLog(@"PTYTextView keyDown modflag=%d keycode=%d", modflag, (int)keyCode);
NSLog(@"prev=%@", prev);
NSLog(@"hasActionableKeyMappingForEvent=%d", (int)[delegate hasActionableKeyMappingForEvent:event]);
NSLog(@"modFlag & (NSNumericPadKeyMask | NSFUnctionKeyMask)=%d", (modflag & (NSNumericPadKeyMask | NSFunctionKeyMask)));
NSLog(@"charactersIgnoringModififiers length=%d", (int)[[event charactersIgnoringModifiers] length]);
NSLog(@"delegate optionkey=%d, delegate rightOptionKey=%d", (int)[delegate optionKey], (int)[delegate rightOptionKey]);
NSLog(@"modflag & leftAlt == leftAlt && optionKey != NORMAL = %d", (int)((modflag & NSLeftAlternateKeyMask) == NSLeftAlternateKeyMask && [delegate optionKey] != OPT_NORMAL));
NSLog(@"modflag == alt && optionKey != NORMAL = %d", (int)(modflag == NSAlternateKeyMask && [delegate optionKey] != OPT_NORMAL));
NSLog(@"modflag & rightAlt == rightAlt && rightOptionKey != NORMAL = %d", (int)((modflag & NSRightAlternateKeyMask) == NSRightAlternateKeyMask && [delegate rightOptionKey] != OPT_NORMAL));
NSLog(@"isControl=%d", (int)(modflag & NSControlKeyMask));
NSLog(@"keycode is slash=%d, is backslash=%d", (keyCode == 0x2c), (keyCode == 0x2a));
}
// Hide the cursor
[NSCursor setHiddenUntilMouseMoves:YES];
// Should we process the event immediately in the delegate?
if ((!prev) &&
([delegate hasActionableKeyMappingForEvent:event] || // delegate will do something useful
(modflag & (NSNumericPadKeyMask | NSFunctionKeyMask)) || // is an arrow key, f key, etc.
([[event charactersIgnoringModifiers] length] > 0 && // Will send Meta/Esc+ (length is 0 if it's a dedicated dead key)
(((modflag & NSLeftAlternateKeyMask) == NSLeftAlternateKeyMask && [delegate optionKey] != OPT_NORMAL) ||
(modflag == NSAlternateKeyMask && [delegate optionKey] != OPT_NORMAL) || // Synergy sends an Alt key that's neither left nor right!
((modflag & NSRightAlternateKeyMask) == NSRightAlternateKeyMask && [delegate rightOptionKey] != OPT_NORMAL))) ||
((modflag & NSControlKeyMask) && // a few special cases
(keyCode == 0x2c /* slash */ || keyCode == 0x2a /* backslash */)))) {
if (debugKeyDown) {
NSLog(@"PTYTextView keyDown: process in delegate");
}
[delegate keyDown:event];
return;
}
if (debugKeyDown) {
NSLog(@"Test for command key");
}
if (modflag & NSCommandKeyMask) {
// You pressed cmd+something but it's not handled by the delegate. Going further would
// send the unmodified key to the terminal which doesn't make sense.adsjflsd
if (debugKeyDown) {
NSLog(@"PTYTextView keyDown You pressed cmd+something");
}
return;
}
// Control+Key doesn't work right with custom keyboard layouts. Handle ctrl+key here for the
// standard combinations.
BOOL workAroundControlBug = NO;
if (!prev &&
(modflag & (NSControlKeyMask | NSCommandKeyMask | NSAlternateKeyMask)) == NSControlKeyMask) {
if (debugKeyDown) {
NSLog(@"Special ctrl+key handler running");
}
NSString *unmodkeystr = [event charactersIgnoringModifiers];
if ([unmodkeystr length] != 0) {
unichar unmodunicode = [unmodkeystr length] > 0 ? [unmodkeystr characterAtIndex:0] : 0;
unichar cc = 0xffff;
if (unmodunicode >= 'a' && unmodunicode <= 'z') {
cc = unmodunicode - 'a' + 1;
} else if (unmodunicode == ' ' || unmodunicode == '2' || unmodunicode == '@') {
cc = 0;
} else if (unmodunicode == '[') { // esc
cc = 27;
} else if (unmodunicode == '\\') {
cc = 28;
} else if (unmodunicode == ']') {
cc = 29;
} else if (unmodunicode == '^' || unmodunicode == '6') {
cc = 30;
} else if (unmodunicode == '-' || unmodunicode == '_') {
cc = 31;
}
if (cc != 0xffff) {
[self insertText:[NSString stringWithCharacters:&cc length:1]];
if (debugKeyDown) {
NSLog(@"PTYTextView keyDown work around control bug. cc=%d", (int)cc);
}
workAroundControlBug = YES;
}
}
}
if (!workAroundControlBug) {
// Let the IME process key events
IM_INPUT_INSERT = NO;
if (debugKeyDown) {
NSLog(@"PTYTextView keyDown send to IME");
}
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
// If the IME didn't want it, pass it on to the delegate
if (!prev &&
!IM_INPUT_INSERT &&
![self hasMarkedText]) {
if (debugKeyDown) {
NSLog(@"PTYTextView keyDown IME no, send to delegate");
}
[delegate keyDown:event];
}
}
if (debugKeyDown) {
NSLog(@"PTYTextView keyDown END");
}
}
- (BOOL)keyIsARepeat
{
return (keyIsARepeat);
}
// TODO: disable other, righxt mouse for inactive panes
- (void)otherMouseDown: (NSEvent *) event
{
NSPoint locationInWindow, locationInTextView;
locationInWindow = [event locationInWindow];
locationInTextView = [self convertPoint: locationInWindow fromView: nil];
NSRect visibleRect = [[self enclosingScrollView] documentVisibleRect];
if ([self xtermMouseReporting] &&
locationInTextView.y > visibleRect.origin.y) {
// Mouse reporting is on
int rx, ry;
rx = (locationInTextView.x - MARGIN - visibleRect.origin.x) / charWidth;
ry = (locationInTextView.y - visibleRect.origin.y) / lineHeight;
if (rx < 0) {
rx = -1;
}
if (ry < 0) {
ry = -1;
}
VT100Terminal *terminal = [dataSource terminal];
PTYSession* session = [dataSource session];
int bnum = [event buttonNumber];
if (bnum == 2) {
bnum = 1;
}
switch ([terminal mouseMode]) {
case MOUSE_REPORTING_NORMAL:
case MOUSE_REPORTING_BUTTON_MOTION:
case MOUSE_REPORTING_ALL_MOTION:
reportingMouseDown = YES;
[session writeTask:[terminal mousePress:bnum
withModifiers:[event modifierFlags]
atX:rx
Y:ry]];
return;
break;
case MOUSE_REPORTING_NONE:
case MOUSE_REPORTING_HILITE:
// fall through
break;
}
}
[pointer_ mouseDown:event withTouches:numTouches_];
}
- (void)otherMouseUp:(NSEvent *)event
{
NSPoint locationInWindow, locationInTextView;
locationInWindow = [event locationInWindow];
locationInTextView = [self convertPoint: locationInWindow fromView: nil];
NSRect visibleRect = [[self enclosingScrollView] documentVisibleRect];
if (([self xtermMouseReporting]) && reportingMouseDown) {
reportingMouseDown = NO;
int rx, ry;
rx = (locationInTextView.x - MARGIN - visibleRect.origin.x) / charWidth;
ry = (locationInTextView.y - visibleRect.origin.y) / lineHeight;
if (rx < 0) {
rx = -1;
}
if (ry < 0) {
ry = -1;
}
VT100Terminal *terminal = [dataSource terminal];
PTYSession* session = [dataSource session];
switch ([terminal mouseMode]) {
case MOUSE_REPORTING_NORMAL:
case MOUSE_REPORTING_BUTTON_MOTION:
case MOUSE_REPORTING_ALL_MOTION:
[session writeTask:[terminal mouseReleaseWithModifiers:[event modifierFlags]
atX:rx
Y:ry]];
return;
break;
case MOUSE_REPORTING_NONE:
case MOUSE_REPORTING_HILITE:
// fall through
break;
}
}
if (!mouseDownIsThreeFingerClick_) {
[super otherMouseUp:event];
}
[pointer_ mouseUp:event withTouches:numTouches_];
}
- (void)otherMouseDragged:(NSEvent *)event
{
NSPoint locationInWindow, locationInTextView;
locationInWindow = [event locationInWindow];
locationInTextView = [self convertPoint: locationInWindow fromView: nil];
NSRect visibleRect = [[self enclosingScrollView] documentVisibleRect];
if (([self xtermMouseReporting]) &&
(locationInTextView.y > visibleRect.origin.y) &&
reportingMouseDown) {
// Mouse reporting is on.
int rx, ry;
rx = (locationInTextView.x - MARGIN - visibleRect.origin.x) / charWidth;
ry = (locationInTextView.y - visibleRect.origin.y) / lineHeight;
if (rx < 0) {
rx = -1;
}
if (ry < 0) {
ry = -1;
}
VT100Terminal *terminal = [dataSource terminal];
PTYSession* session = [dataSource session];
int bnum = [event buttonNumber];
if (bnum == 2) {
bnum = 1;
}
switch ([terminal mouseMode]) {
case MOUSE_REPORTING_NORMAL:
case MOUSE_REPORTING_BUTTON_MOTION:
case MOUSE_REPORTING_ALL_MOTION:
[session writeTask:[terminal mouseMotion:bnum
withModifiers:[event modifierFlags]
atX:rx
Y:ry]];
return;
break;
case MOUSE_REPORTING_NONE:
case MOUSE_REPORTING_HILITE:
// fall through
break;
}
}
[super otherMouseDragged:event];
}
- (void)rightMouseDown:(NSEvent*)event
{
if ([pointer_ mouseDown:event withTouches:numTouches_]) {
return;
}
NSPoint locationInWindow, locationInTextView;
locationInWindow = [event locationInWindow];
locationInTextView = [self convertPoint: locationInWindow fromView: nil];
NSRect visibleRect = [[self enclosingScrollView] documentVisibleRect];
if (([self xtermMouseReporting]) &&
(locationInTextView.y > visibleRect.origin.y)) {
int rx, ry;
rx = (locationInTextView.x-MARGIN - visibleRect.origin.x)/charWidth;
ry = (locationInTextView.y - visibleRect.origin.y)/lineHeight;
if (rx < 0) rx = -1;
if (ry < 0) ry = -1;
VT100Terminal *terminal = [dataSource terminal];
PTYSession* session = [dataSource session];
switch ([terminal mouseMode]) {
case MOUSE_REPORTING_NORMAL:
case MOUSE_REPORTING_BUTTON_MOTION:
case MOUSE_REPORTING_ALL_MOTION:
reportingMouseDown = YES;
[session writeTask:[terminal mousePress:2
withModifiers:[event modifierFlags]
atX:rx
Y:ry]];
return;
break;
case MOUSE_REPORTING_NONE:
case MOUSE_REPORTING_HILITE:
// fall through
break;
}
}
[super rightMouseDown:event];
}
- (void)rightMouseUp:(NSEvent *)event
{
if ([pointer_ mouseUp:event withTouches:numTouches_]) {
return;
}
NSPoint locationInWindow, locationInTextView;
locationInWindow = [event locationInWindow];
locationInTextView = [self convertPoint: locationInWindow fromView: nil];
NSRect visibleRect = [[self enclosingScrollView] documentVisibleRect];
if (([self xtermMouseReporting]) &&
reportingMouseDown) {
// Mouse reporting is on
reportingMouseDown = NO;
int rx, ry;
rx = (locationInTextView.x-MARGIN - visibleRect.origin.x)/charWidth;
ry = (locationInTextView.y - visibleRect.origin.y)/lineHeight;
if (rx < 0) {
rx = -1;
}
if (ry < 0) {
ry = -1;
}
VT100Terminal *terminal = [dataSource terminal];
PTYSession* session = [dataSource session];
switch ([terminal mouseMode]) {
case MOUSE_REPORTING_NORMAL:
case MOUSE_REPORTING_BUTTON_MOTION:
case MOUSE_REPORTING_ALL_MOTION:
[session writeTask:[terminal mouseReleaseWithModifiers:[event modifierFlags]
atX:rx
Y:ry]];
return;
break;
case MOUSE_REPORTING_NONE:
case MOUSE_REPORTING_HILITE:
// fall through
break;
}
}
[super rightMouseUp:event];
}
- (void)rightMouseDragged:(NSEvent *)event
{
NSPoint locationInWindow, locationInTextView;
locationInWindow = [event locationInWindow];
locationInTextView = [self convertPoint: locationInWindow fromView: nil];
NSRect visibleRect = [[self enclosingScrollView] documentVisibleRect];
if (([self xtermMouseReporting]) &&
(locationInTextView.y > visibleRect.origin.y) &&
reportingMouseDown) {
// Mouse reporting is on.
int rx, ry;
rx = (locationInTextView.x -MARGIN - visibleRect.origin.x) / charWidth;
ry = (locationInTextView.y - visibleRect.origin.y) / lineHeight;
if (rx < 0) {
rx = -1;
}
if (ry < 0) {
ry = -1;
}
VT100Terminal *terminal = [dataSource terminal];
PTYSession* session = [dataSource session];
switch ([terminal mouseMode]) {
case MOUSE_REPORTING_NORMAL:
case MOUSE_REPORTING_BUTTON_MOTION:
case MOUSE_REPORTING_ALL_MOTION:
[session writeTask:[terminal mouseMotion:2
withModifiers:[event modifierFlags]
atX:rx
Y:ry]];
return;
break;
case MOUSE_REPORTING_NONE:
case MOUSE_REPORTING_HILITE:
// fall through
break;
}
}
[super rightMouseDragged:event];
}
- (void)scrollWheel:(NSEvent *)event
{
NSPoint locationInWindow, locationInTextView;
locationInWindow = [event locationInWindow];
locationInTextView = [self convertPoint: locationInWindow fromView: nil];
NSRect visibleRect = [[self enclosingScrollView] documentVisibleRect];
if (([self xtermMouseReporting]) &&
(locationInTextView.y > visibleRect.origin.y)) {
// Mouse reporting is on.
int rx, ry;
rx = (locationInTextView.x-MARGIN - visibleRect.origin.x) / charWidth;
ry = (locationInTextView.y - visibleRect.origin.y) / lineHeight;
if (rx < 0) {
rx = -1;
}
if (ry < 0) {
ry = -1;
}
VT100Terminal *terminal = [dataSource terminal];
PTYSession* session = [dataSource session];
switch ([terminal mouseMode]) {
case MOUSE_REPORTING_NORMAL:
case MOUSE_REPORTING_BUTTON_MOTION:
case MOUSE_REPORTING_ALL_MOTION:
if ([event deltaY] != 0) {
[session writeTask:[terminal mousePress:([event deltaY] > 0 ? 4:5)
withModifiers:[event modifierFlags]
atX:rx
Y:ry]];
return;
}
break;
case MOUSE_REPORTING_NONE:
case MOUSE_REPORTING_HILITE:
// fall through
break;
}
}
[super scrollWheel:event];
}
- (BOOL)setCursor:(NSCursor *)cursor
{
if (cursor == cursor_) {
return NO;
}
[cursor_ autorelease];
cursor_ = [cursor retain];
return YES;
}
- (void)updateCursor:(NSEvent *)event
{
MouseMode mouseMode = [[dataSource terminal] mouseMode];
BOOL changed = NO;
if (([event modifierFlags] & kDragPaneModifiers) == kDragPaneModifiers) {
changed = [self setCursor:[NSCursor openHandCursor]];
} else if (([event modifierFlags] & (NSCommandKeyMask | NSAlternateKeyMask)) == (NSCommandKeyMask | NSAlternateKeyMask)) {
changed = [self setCursor:[NSCursor crosshairCursor]];
} else if (([event modifierFlags] & (NSAlternateKeyMask | NSCommandKeyMask)) == NSCommandKeyMask) {
changed = [self setCursor:[NSCursor pointingHandCursor]];
} else if ([self xtermMouseReporting] &&
mouseMode != MOUSE_REPORTING_NONE &&
mouseMode != MOUSE_REPORTING_HILITE) {
changed = [self setCursor:xmrCursor];
} else {
changed = [self setCursor:textViewCursor];
}
if (changed) {
[[_delegate SCROLLVIEW] setDocumentCursor:cursor_];
}
}
- (void)flagsChanged:(NSEvent *)theEvent
{
[self updateCursor:theEvent];
[super flagsChanged:theEvent];
}
- (void)flagsChangedNotification:(NSNotification *)notification
{
[self updateCursor:(NSEvent *)[notification object]];
}
- (void)swipeWithEvent:(NSEvent *)event
{
[pointer_ swipeWithEvent:event];
}
- (void)mouseExited:(NSEvent *)event
{
mouseInRect_ = NO;
}
- (void)mouseEntered:(NSEvent *)event
{
mouseInRect_ = YES;
[self updateCursor:event];
if ([[PreferencePanel sharedInstance] focusFollowsMouse] &&
[[self window] alphaValue] > 0) {
// Some windows automatically close when they lose key status and are
// incompatible with FFM. Check if the key window or its controller implements
// disableFocusFollowsMouse and if it returns YES do nothing.
id obj = nil;
if ([[NSApp keyWindow] respondsToSelector:@selector(disableFocusFollowsMouse)]) {
obj = [NSApp keyWindow];
} else if ([[[NSApp keyWindow] windowController] respondsToSelector:@selector(disableFocusFollowsMouse)]) {
obj = [[NSApp keyWindow] windowController];
}
if (![obj disableFocusFollowsMouse]) {
[[self window] makeKeyWindow];
}
if ([[self window] isKeyWindow]) {
[[[dataSource session] tab] setActiveSession:[dataSource session]];
}
}
}
- (NSPoint)clickPoint:(NSEvent *)event
{
NSPoint locationInWindow = [event locationInWindow];
NSPoint locationInTextView = [self convertPoint: locationInWindow fromView: nil];
int x, y;
int width = [dataSource width];
x = (locationInTextView.x - MARGIN + charWidth/2)/charWidth;
if (x < 0) {
x = 0;
}
y = locationInTextView.y / lineHeight;
if (x >= width) {
x = width - 1;
}
return NSMakePoint(x, y);
}
- (void)mouseDown:(NSEvent *)event
{
if ([self mouseDownImpl:event]) {
[super mouseDown:event];
}
}
- (BOOL)lineHasSoftEol:(int)y
{
screen_char_t *theLine = [dataSource getLineAtIndex:y];
int width = [dataSource width];
return (theLine[width].code == EOL_SOFT);
}
- (int)lineNumberWithStartOfWholeLineIncludingLine:(int)y
{
int i = y;
while (i > 0 && [self lineHasSoftEol:i - 1]) {
i--;
}
return i;
}
- (int)lineNumberWithEndOfWholeLineIncludingLine:(int)y
{
int i = y + 1;
int maxY = [dataSource numberOfLines];
while (i < maxY && [self lineHasSoftEol:i - 1]) {
i++;
}
return i - 1;
}
- (void)extendWholeLineSelectionToX:(int)x
y:(int)y
withWidth:(int)width
{
if (startY < y) {
// Start of existing selection is before cursor.
if (startY > endY) {
// start is below end. advance start up to end.
startY = endY;
startX = 0;
}
endX = width;
endY = [self lineNumberWithEndOfWholeLineIncludingLine:y];
} else {
// end of existing selection is at or after the cursor
if (startY < endY) {
// start of selection is before end of selection.
// advance start to end.
startY = endY;
startX = endX;
}
// set end of selection to current line
endX = 0;
endY = [self lineNumberWithStartOfWholeLineIncludingLine:y];
}
}
- (void)extendSelectionToX:(int)x y:(int)y
{
int width = [dataSource width];
// If you click before the start then flip start and end and extend end to click location. (effectively extends left)
// If you click after the start then move the end to the click location. (extends right)
// This means that if you click inside the selection it truncates it by moving the end (whichever that is)
if (x + y * width < startX + startY * width) {
// Clicked before the start. Move the start to the old end.
startX = endX;
startY = endY;
[self setSelectionTime];
}
// Move the end to the click location.
endX = x;
endY = y;
}
// Returns yes if [super mouseDown:event] should be run by caller.
- (BOOL)mouseDownImpl:(NSEvent*)event
{
[pointer_ notifyLeftMouseDown];
mouseDownIsThreeFingerClick_ = NO;
if (([event modifierFlags] & kDragPaneModifiers) == kDragPaneModifiers) {
[[MovePaneController sharedInstance] beginDrag:[dataSource session]];
return NO;
}
if (numTouches_ == 3) {
if ([[PreferencePanel sharedInstance] threeFingerEmulatesMiddle]) {
mouseDownIsThreeFingerClick_ = YES;
[self otherMouseDown:event];
} else {
// Perform user-defined gesture action, if any
[pointer_ mouseDown:event withTouches:numTouches_];
mouseDown = YES;
}
return NO;
}
if ([pointer_ eventEmulatesRightClick:event]) {
[pointer_ mouseDown:event withTouches:numTouches_];
return NO;
}
const BOOL altPressed = ([event modifierFlags] & NSAlternateKeyMask) != 0;
const BOOL cmdPressed = ([event modifierFlags] & NSCommandKeyMask) != 0;
const BOOL shiftPressed = ([event modifierFlags] & NSShiftKeyMask) != 0;
dragOk_ = YES;
PTYTextView* frontTextView = [[iTermController sharedInstance] frontTextView];
if (!cmdPressed &&
frontTextView &&
[[frontTextView->dataSource session] tab] != [[dataSource session] tab]) {
// Mouse clicks in inactive tab are always handled by superclass because we don't want clicks
// to select a split pane to be xterm-mouse-reported. We do allow cmd-clicks to go through
// incase you're clicking on a URL.
return YES;
}
if (([event modifierFlags] & kDragPaneModifiers) == kDragPaneModifiers) {
return YES;
}
NSPoint locationInWindow, locationInTextView;
NSPoint clickPoint = [self clickPoint:event];
int x = clickPoint.x;
int y = clickPoint.y;
int width = [dataSource width];
locationInWindow = [event locationInWindow];
locationInTextView = [self convertPoint: locationInWindow fromView: nil];
NSRect visibleRect = [[self enclosingScrollView] documentVisibleRect];
if ([event eventNumber] != firstMouseEventNumber_ && // Not first mouse in formerly non-key app
frontTextView == self && // Is active session's textview
([self xtermMouseReporting]) && // Xterm mouse reporting is on
(locationInTextView.y > visibleRect.origin.y)) { // Not inside the top margin
// Mouse reporting is on.
int rx, ry;
rx = (locationInTextView.x - MARGIN - visibleRect.origin.x) / charWidth;
ry = (locationInTextView.y - visibleRect.origin.y) / lineHeight;
if (rx < 0) {
rx = -1;
}
if (ry < 0) {
ry = -1;
}
VT100Terminal *terminal = [dataSource terminal];
PTYSession* session = [dataSource session];
switch ([terminal mouseMode]) {
case MOUSE_REPORTING_NORMAL:
case MOUSE_REPORTING_BUTTON_MOTION:
case MOUSE_REPORTING_ALL_MOTION:
reportingMouseDown = YES;
[session writeTask:[terminal mousePress:0
withModifiers:[event modifierFlags]
atX:rx
Y:ry]];
return NO;
break;
case MOUSE_REPORTING_NONE:
case MOUSE_REPORTING_HILITE:
// fall through
break;
}
}
// Lock auto scrolling while the user is selecting text
[(PTYScroller*)([[self enclosingScrollView] verticalScroller]) setUserScroll:YES];
if (mouseDownEvent != nil) {
[mouseDownEvent release];
mouseDownEvent = nil;
}
[event retain];
mouseDownEvent = event;
mouseDragged = NO;
mouseDown = YES;
mouseDownOnSelection = NO;
int clickCount = [event clickCount];
if (clickCount < 2) {
// single click
if (altPressed && cmdPressed) {
selectMode = SELECT_BOX;
} else {
selectMode = SELECT_CHAR;
}
if (startX > -1 && shiftPressed) {
// holding down shfit key and there is an existing selection ->
// extend the selection.
[self extendSelectionToX:x y:y];
// startX and endX may be reversed, but mouseUp fixes it.
} else if (startX > -1 &&
[self _isCharSelectedInRow:y col:x checkOld:NO]) {
// not holding down shift key but there is an existing selection.
// Possibly a drag coming up.
mouseDownOnSelection = YES;
return YES;
} else if (!cmdPressed || altPressed) {
// start a new selection
endX = startX = x;
endY = startY = y;
[self setSelectionTime];
}
} else if (clickCount == 2) {
int tmpX1, tmpY1, tmpX2, tmpY2;
// double-click; select word
selectMode = SELECT_WORD;
NSString *selectedWord = [self getWordForX:x
y:y
startX:&tmpX1
startY:&tmpY1
endX:&tmpX2
endY:&tmpY2];
if ([self _findMatchingParenthesis:selectedWord withX:tmpX1 Y:tmpY1]) {
// Found a matching paren
;
} else if (startX > -1 && shiftPressed) {
// no matching paren, but holding shift and extending selection
if (startX + startY * width < tmpX1 + tmpY1 * width) {
// extend end of selection
endX = tmpX2;
endY = tmpY2;
} else {
// extend start of selection
startX = endX;
startY = endY;
endX = tmpX1;
endY = tmpY1;
[self setSelectionTime];
}
} else {
// no matching paren and not holding shift. Set selection to word boundary.
startX = tmpX1;
startY = tmpY1;
endX = tmpX2;
endY = tmpY2;
[self setSelectionTime];
}
} else if (clickCount == 3) {
if ([[PreferencePanel sharedInstance] tripleClickSelectsFullLines]) {
selectMode = SELECT_WHOLE_LINE;
if (startX > -1 && shiftPressed) {
[self extendWholeLineSelectionToX:x y:y withWidth:width];
} else {
// new selection
startX = 0;
startY = [self lineNumberWithStartOfWholeLineIncludingLine:y];
endX = width;
endY = [self lineNumberWithEndOfWholeLineIncludingLine:y];
}
[self setSelectionTime];
} else {
// triple-click; select line
selectMode = SELECT_LINE;
if (startX > -1 && shiftPressed) {
// extend existing selection
if (startY < y) {
// start of existing selection is before cursor so move end point.
endX = width;
endY = y;
} else {
// end of existing selection is at or after the cursor
if (startX + startY * width < endX + endY * width) {
// start of selection is before end of selection.
// advance start to end
startX = endX;
startY = endY;
}
// Set end of selection to current line
endX = 0;
endY = y;
}
} else {
// not holding shift
startX = 0;
endX = width;
startY = endY = y;
}
[self setSelectionTime];
}
} else if (clickCount == 4) {
// quad-click: smart selection
// ignore status of shift key for smart selection because extending was messed up by
// triple click.
selectMode = SELECT_SMART;
[self smartSelectWithEvent:event];
}
DebugLog([NSString stringWithFormat:@"Mouse down. startx=%d starty=%d, endx=%d, endy=%d", startX, startY, endX, endY]);
[[[self dataSource] session] refreshAndStartTimerIfNeeded];
return NO;
}
static double Square(double n) {
return n * n;
}
static double EuclideanDistance(NSPoint p1, NSPoint p2) {
return sqrt(Square(p1.x - p2.x) + Square(p1.y - p2.y));
}
- (void)mouseUp:(NSEvent *)event
{
firstMouseEventNumber_ = -1; // Synergy seems to interfere with event numbers, so reset it here.
if (mouseDownIsThreeFingerClick_) {
[self otherMouseUp:nil];
mouseDownIsThreeFingerClick_ = NO;
return;
} else if (numTouches_ == 3 && mouseDown) {
// Three finger tap is valid but not emulating middle button
[pointer_ mouseUp:event withTouches:numTouches_];
mouseDown = NO;
return;
}
dragOk_ = NO;
trouterDragged = NO;
if ([pointer_ eventEmulatesRightClick:event]) {
[pointer_ mouseUp:event withTouches:numTouches_];
return;
}
PTYTextView* frontTextView = [[iTermController sharedInstance] frontTextView];
const BOOL cmdPressed = ([event modifierFlags] & NSCommandKeyMask) != 0;
if (!cmdPressed &&
frontTextView &&
[[frontTextView->dataSource session] tab] != [[dataSource session] tab]) {
// Mouse clicks in inactive tab are always handled by superclass but make it first responder.
[[self window] makeFirstResponder: self];
[super mouseUp:event];
return;
}
selectionScrollDirection = 0;
NSPoint locationInWindow = [event locationInWindow];
NSPoint locationInTextView = [self convertPoint: locationInWindow fromView: nil];
// Send mouse up event to host if xterm mouse reporting is on
if (frontTextView == self &&
[self xtermMouseReporting] &&
reportingMouseDown) {
// Mouse reporting is on.
reportingMouseDown = NO;
int rx, ry;
NSRect visibleRect = [[self enclosingScrollView] documentVisibleRect];
rx = (locationInTextView.x - MARGIN - visibleRect.origin.x) / charWidth;
ry = (locationInTextView.y - visibleRect.origin.y) / lineHeight;
if (rx < 0) {
rx = -1;
}
if (ry < 0) {
ry = -1;
}
VT100Terminal *terminal = [dataSource terminal];
PTYSession* session = [dataSource session];
switch ([terminal mouseMode]) {
case MOUSE_REPORTING_NORMAL:
case MOUSE_REPORTING_BUTTON_MOTION:
case MOUSE_REPORTING_ALL_MOTION:
[session writeTask:[terminal mouseReleaseWithModifiers:[event modifierFlags]
atX:rx
Y:ry]];
return;
break;
case MOUSE_REPORTING_NONE:
case MOUSE_REPORTING_HILITE:
// fall through
break;
}
}
// Unlock auto scrolling as the user as finished selecting text
if (([self visibleRect].origin.y + [self visibleRect].size.height - [self excess]) / lineHeight == [dataSource numberOfLines]) {
[(PTYScroller*)([[self enclosingScrollView] verticalScroller]) setUserScroll:NO];
}
if (mouseDown == NO) {
DebugLog([NSString stringWithFormat:@"Mouse up. startx=%d starty=%d, endx=%d, endy=%d", startX, startY, endX, endY]);
return;
}
mouseDown = NO;
// make sure we have key focus
[[self window] makeFirstResponder:self];
if (startY > endY ||
(startY == endY && startX > endX)) {
// Make sure the start is before the end.
int t;
t = startY; startY = endY; endY = t;
t = startX; startX = endX; endX = t;
[self setSelectionTime];
} else if ([event clickCount] < 2 &&
!mouseDragged &&
!([event modifierFlags] & NSShiftKeyMask)) {
// Just a click in the window.
startX=-1;
if (cmdPressed &&
[[PreferencePanel sharedInstance] cmdSelection]) {
BOOL altPressed = ([event modifierFlags] & NSAlternateKeyMask) != 0;
if (altPressed) {
[self openTargetInBackgroundWithEvent:event];
} else {
[self openTargetWithEvent:event];
}
} else {
lastFindStartX = endX;
lastFindEndX = endX+1;
absLastFindStartY = endY + [dataSource totalScrollbackOverflow];
absLastFindEndY = absLastFindStartY+1;
}
}
if (startX > -1 && _delegate) {
// if we want to copy our selection, do so
if ([[PreferencePanel sharedInstance] copySelection]) {
[self copy:self];
}
}
if (selectMode != SELECT_BOX) {
selectMode = SELECT_CHAR;
}
DebugLog([NSString stringWithFormat:@"Mouse up. startx=%d starty=%d, endx=%d, endy=%d", startX, startY, endX, endY]);
[[[self dataSource] session] refreshAndStartTimerIfNeeded];
}
- (void)mouseDragged:(NSEvent *)event
{
if (mouseDownIsThreeFingerClick_) {
return;
}
// Prevent accidental dragging while dragging trouter item.
BOOL dragThresholdMet = NO;
NSPoint locationInWindow = [event locationInWindow];
NSPoint locationInTextView = [self convertPoint:locationInWindow fromView:nil];
locationInTextView.x = ceil(locationInTextView.x);
locationInTextView.y = ceil(locationInTextView.y);
NSRect rectInTextView = [self visibleRect];
int x, y;
int width = [dataSource width];
double logicalX = locationInTextView.x - MARGIN - charWidth/2;
if (logicalX >= 0) {
x = logicalX / charWidth;
} else {
x = -1;
}
if (x < -1) {
x = -1;
}
if (x >= width) {
x = width - 1;
}
y = locationInTextView.y / lineHeight;
NSPoint mouseDownLocation = [mouseDownEvent locationInWindow];
if (EuclideanDistance(mouseDownLocation, locationInWindow) >= kDragThreshold) {
dragThresholdMet = YES;
}
if ([event eventNumber] == firstMouseEventNumber_) {
// We accept first mouse for the purposes of focusing or dragging a
// split pane but not for making a selection.
return;
}
if (!dragOk_) {
return;
}
NSString *theSelectedText;
if (([self xtermMouseReporting]) && reportingMouseDown) {
// Mouse reporting is on.
int rx, ry;
NSRect visibleRect = [[self enclosingScrollView] documentVisibleRect];
rx = (locationInTextView.x - MARGIN - visibleRect.origin.x) / charWidth;
ry = (locationInTextView.y - visibleRect.origin.y) / lineHeight;
if (rx < 0) {
rx = -1;
}
if (ry < 0) {
ry = -1;
}
VT100Terminal *terminal = [dataSource terminal];
PTYSession* session = [dataSource session];
switch ([terminal mouseMode]) {
case MOUSE_REPORTING_BUTTON_MOTION:
case MOUSE_REPORTING_ALL_MOTION:
[session writeTask:[terminal mouseMotion:0
withModifiers:[event modifierFlags]
atX:rx
Y:ry]];
case MOUSE_REPORTING_NORMAL:
DebugLog([NSString stringWithFormat:@"Mouse drag. startx=%d starty=%d, endx=%d, endy=%d", startX, startY, endX, endY]);
return;
break;
case MOUSE_REPORTING_NONE:
case MOUSE_REPORTING_HILITE:
// fall through
break;
}
}
BOOL pressingCmdOnly = ([event modifierFlags] & (NSAlternateKeyMask | NSCommandKeyMask)) == NSCommandKeyMask;
if (!pressingCmdOnly || dragThresholdMet) {
mouseDragged = YES;
}
if (mouseDownOnSelection == YES &&
([event modifierFlags] & NSCommandKeyMask) &&
dragThresholdMet) {
// Drag and drop a selection
if (selectMode == SELECT_BOX) {
theSelectedText = [self contentInBoxFromX:startX Y:startY ToX:endX Y:endY pad:NO];
} else {
theSelectedText = [self contentFromX:startX Y:startY ToX:endX Y:endY pad:NO includeLastNewline:[[PreferencePanel sharedInstance] copyLastNewline]];
}
if ([theSelectedText length] > 0) {
[self _dragText: theSelectedText forEvent: event];
DebugLog([NSString stringWithFormat:@"Mouse drag. startx=%d starty=%d, endx=%d, endy=%d", startX, startY, endX, endY]);
return;
}
}
if (pressingCmdOnly && !dragThresholdMet) {
// If you're holding cmd (but not opt) then you're either trying to click on a link and
// accidentally dragged a little bit, or you're trying to drag a selection. Do nothing until
// the threshold is met.
return;
}
if (mouseDownOnSelection == YES &&
([event modifierFlags] & (NSAlternateKeyMask | NSCommandKeyMask)) == (NSAlternateKeyMask | NSCommandKeyMask) &&
!dragThresholdMet) {
// Would be a drag of a rect region but mouse hasn't moved far enough yet. Prevent the
// selection from changing.
return;
}
if (startX < 0 && pressingCmdOnly && trouterDragged == NO) {
// Only one Trouter check per drag
trouterDragged = YES;
// Drag a file handle (only possible when there is no selection).
NSString *path = [self _getURLForX:x y:y];
path = [trouter getFullPath:path
workingDirectory:[self getWorkingDirectoryAtLine:y + 1]
lineNumber:nil];
if (path == nil) {
return;
}
NSPoint dragPosition;
NSImage *dragImage;
NSArray *fileList = [NSArray arrayWithObject: path];
NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
[pboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil];
[pboard setPropertyList:fileList forType:NSFilenamesPboardType];
dragImage = [[NSWorkspace sharedWorkspace] iconForFile:path];
dragPosition = [self convertPoint:[event locationInWindow] fromView:nil];
dragPosition.x -= [dragImage size].width / 2;
[self dragImage:dragImage
at:dragPosition
offset:NSZeroSize
event:event
pasteboard:pboard
source:self
slideBack:YES];
// Valid drag, so we reset the flag because mouseUp doesn't get called when a drag is done
trouterDragged = NO;
return;
}
int prevScrolling = selectionScrollDirection;
if (locationInTextView.y <= rectInTextView.origin.y) {
selectionScrollDirection = -1;
scrollingX = x;
scrollingY = y;
scrollingLocation = locationInTextView;
} else if (locationInTextView.y >= rectInTextView.origin.y + rectInTextView.size.height) {
selectionScrollDirection = 1;
scrollingX = x;
scrollingY = y;
scrollingLocation = locationInTextView;
} else {
selectionScrollDirection = 0;
}
if (selectionScrollDirection && !prevScrolling) {
[self scheduleSelectionScroll];
}
[self moveSelectionEndpointToX:x Y:y locationInTextView:locationInTextView];
}
#pragma mark PointerControllerDelegate
- (void)pasteFromClipboardWithEvent:(NSEvent *)event
{
[self paste:nil];
}
- (void)pasteFromSelectionWithEvent:(NSEvent *)event
{
[self pasteSelection:nil];
}
- (void)_openTargetWithEvent:(NSEvent *)event inBackground:(BOOL)openInBackground
{
NSPoint clickPoint = [self clickPoint:event];
int x = clickPoint.x;
int y = clickPoint.y;
// Command click in place.
NSString *url = [self _getURLForX:x y:y];
NSString *prefix = [self wrappedStringAtX:x
y:y
dir:-1
respectHardNewlines:NO];
NSString *suffix = [self wrappedStringAtX:x
y:y
dir:1
respectHardNewlines:NO];
[self _openSemanticHistoryForUrl:url
atLine:y + 1
inBackground:openInBackground
prefix:prefix
suffix:suffix];
}
- (void)openTargetWithEvent:(NSEvent *)event
{
[self _openTargetWithEvent:event inBackground:NO];
}
- (void)openTargetInBackgroundWithEvent:(NSEvent *)event
{
[self _openTargetWithEvent:event inBackground:YES];
}
- (void)smartSelectWithEvent:(NSEvent *)event
{
NSPoint clickPoint = [self clickPoint:event];
int x = clickPoint.x;
int y = clickPoint.y;
[self smartSelectAtX:x y:y ignoringNewlines:NO];
}
- (void)smartSelectIgnoringNewlinesWithEvent:(NSEvent *)event
{
NSPoint clickPoint = [self clickPoint:event];
int x = clickPoint.x;
int y = clickPoint.y;
[self smartSelectAtX:x y:y ignoringNewlines:YES];
}
- (void)smartSelectAndMaybeCopyWithEvent:(NSEvent *)event
ignoringNewlines:(BOOL)ignoringNewlines
{
NSPoint clickPoint = [self clickPoint:event];
int x = clickPoint.x;
int y = clickPoint.y;
[self smartSelectAtX:x y:y ignoringNewlines:ignoringNewlines];
[self setSelectionTime];
if (startX > -1 && _delegate) {
// if we want to copy our selection, do so
if ([[PreferencePanel sharedInstance] copySelection]) {
[self copy:self];
}
}
}
- (void)openContextMenuWithEvent:(NSEvent *)event
{
NSPoint clickPoint = [self clickPoint:event];
int x = clickPoint.x;
int y = clickPoint.y;
long long clickAt = y;
clickAt *= [dataSource width];
clickAt += x;
long long minAt = startY;
minAt *= [dataSource width];
minAt += startX;
long long maxAt = endY;
maxAt *= [dataSource width];
maxAt += endX;
if (startX < 0 ||
clickAt < minAt ||
clickAt >= maxAt) {
// Didn't click on selection.
[self smartSelectWithEvent:event];
}
[self setNeedsDisplay:YES];
NSMenu *menu = [self menuForEvent:nil];
[NSMenu popUpContextMenu:menu withEvent:event forView:self];
}
- (void)extendSelectionWithEvent:(NSEvent *)event
{
if (startX > -1) {
NSPoint clickPoint = [self clickPoint:event];
int x = clickPoint.x;
int y = clickPoint.y;
[self extendSelectionToX:x y:y];
if (startY > endY || (startY == endY && startX > endX)) {
// Make sure the start is before the end.
int t;
t = startY;
startY = endY;
endY = t;
t = startX;
startX = endX;
endX = t;
}
}
}
- (void)nextTabWithEvent:(NSEvent *)event
{
[[[[dataSource session] tab] realParentWindow] nextTab:nil];
}
- (void)previousTabWithEvent:(NSEvent *)event
{
[[[[dataSource session] tab] realParentWindow] previousTab:nil];
}
- (void)nextWindowWithEvent:(NSEvent *)event
{
[[iTermController sharedInstance] nextTerminal:nil];
}
- (void)previousWindowWithEvent:(NSEvent *)event
{
[[iTermController sharedInstance] previousTerminal:nil];
}
- (void)movePaneWithEvent:(NSEvent *)event
{
[self movePane:nil];
}
- (void)sendEscapeSequence:(NSString *)text withEvent:(NSEvent *)event
{
[_delegate sendEscapeSequence:text];
}
- (void)sendHexCode:(NSString *)codes withEvent:(NSEvent *)event
{
[_delegate sendHexCode:codes];
}
- (void)sendText:(NSString *)text withEvent:(NSEvent *)event
{
[_delegate sendText:text];
}
- (void)selectPaneLeftWithEvent:(NSEvent *)event
{
[[[iTermController sharedInstance] currentTerminal] selectPaneLeft:nil];
}
- (void)selectPaneRightWithEvent:(NSEvent *)event
{
[[[iTermController sharedInstance] currentTerminal] selectPaneRight:nil];
}
- (void)selectPaneAboveWithEvent:(NSEvent *)event
{
[[[iTermController sharedInstance] currentTerminal] selectPaneUp:nil];
}
- (void)selectPaneBelowWithEvent:(NSEvent *)event
{
[[[iTermController sharedInstance] currentTerminal] selectPaneDown:nil];
}
- (void)newWindowWithProfile:(NSString *)guid withEvent:(NSEvent *)event
{
[[[[dataSource session] tab] realParentWindow] newWindowWithBookmarkGuid:guid];
}
- (void)newTabWithProfile:(NSString *)guid withEvent:(NSEvent *)event
{
[[[[dataSource session] tab] realParentWindow] newTabWithBookmarkGuid:guid];
}
- (void)newVerticalSplitWithProfile:(NSString *)guid withEvent:(NSEvent *)event
{
[[[[dataSource session] tab] realParentWindow] splitVertically:YES
withBookmarkGuid:guid];
}
- (void)newHorizontalSplitWithProfile:(NSString *)guid withEvent:(NSEvent *)event
{
[[[[dataSource session] tab] realParentWindow] splitVertically:NO
withBookmarkGuid:guid];
}
- (void)selectNextPaneWithEvent:(NSEvent *)event
{
[[[dataSource session] tab] nextSession];
}
- (void)selectPreviousPaneWithEvent:(NSEvent *)event
{
[[[dataSource session] tab] previousSession];
}
- (NSString*)contentInBoxFromX:(int)startx Y:(int)starty ToX:(int)nonInclusiveEndx Y:(int)endy pad: (BOOL) pad
{
int i;
int estimated_size = abs((endy-startx) * [dataSource width]) + abs(nonInclusiveEndx - startx);
NSMutableString* result = [NSMutableString stringWithCapacity:estimated_size];
for (i = starty; i < endy; ++i) {
NSString* line = [self contentFromX:startx Y:i ToX:nonInclusiveEndx Y:i pad:pad];
[result appendString:line];
if (i < endy-1) {
[result appendString:@"\n"];
}
}
return result;
}
- (NSString *)contentFromX:(int)startx
Y:(int)starty
ToX:(int)nonInclusiveEndx
Y:(int)endy
pad:(BOOL)pad
includeLastNewline:(BOOL)includeLastNewline
{
int endx = nonInclusiveEndx-1;
int width = [dataSource width];
const int estimatedSize = (endy - starty + 1) * (width + 1) + (endx - startx + 1);
NSMutableString* result = [NSMutableString stringWithCapacity:estimatedSize];
int y, x1, x2;
screen_char_t *theLine;
BOOL endOfLine;
int i;
for (y = starty; y <= endy; y++) {
theLine = [dataSource getLineAtIndex:y];
x1 = y == starty ? startx : 0;
x2 = y == endy ? endx : width-1;
for ( ; x1 <= x2; x1++) {
if (theLine[x1].code == TAB_FILLER) {
// Convert orphan tab fillers (those without a subsequent
// tab character) into spaces.
if ([self isTabFillerOrphanAtX:x1 Y:y]) {
[result appendString:@" "];
}
} else if (theLine[x1].code != DWC_RIGHT &&
theLine[x1].code != DWC_SKIP) {
if (theLine[x1].code == 0) { // end of line?
// If there is no text after this, insert a hard line break.
endOfLine = YES;
for (i = x1 + 1; i <= x2 && endOfLine; i++) {
if (theLine[i].code != 0) {
endOfLine = NO;
}
}
if (endOfLine) {
if (pad) {
for (i = x1; i <= x2; i++) {
[result appendString:@" "];
}
}
if (theLine[width].code == EOL_HARD) {
if (includeLastNewline || y < endy) {
[result appendString:@"\n"];
}
}
break;
} else {
[result appendString:@" "]; // represent end-of-line blank with space
}
} else if (x1 == x2 &&
y < endy &&
theLine[width].code == EOL_HARD) {
// Hard line break
[result appendString:ScreenCharToStr(&theLine[x1])];
[result appendString:@"\n"]; // hard break
} else {
// Normal character
[result appendString:ScreenCharToStr(&theLine[x1])];
}
}
}
}
return result;
}
- (NSString *)contentFromX:(int)startx
Y:(int)starty
ToX:(int)nonInclusiveEndx
Y:(int)endy
pad:(BOOL)pad
{
return [self contentFromX:startx
Y:starty
ToX:nonInclusiveEndx
Y:endy
pad:pad
includeLastNewline:NO];
}
- (IBAction)selectAll:(id)sender
{
// set the selection region for the whole text
startX = startY = 0;
endX = [dataSource width];
endY = [dataSource numberOfLines] - 1;
[self setSelectionTime];
[[[self dataSource] session] refreshAndStartTimerIfNeeded];
}
- (void)deselect
{
if (startX > -1) {
startX = -1;
[self setSelectionTime];
[[[self dataSource] session] refreshAndStartTimerIfNeeded];
}
}
- (NSString *)selectedText
{
return [self selectedTextWithPad:NO];
}
- (NSString *)selectedTextWithPad:(BOOL)pad
{
if (startX <= -1) {
return nil;
}
if (selectMode == SELECT_BOX) {
return [self contentInBoxFromX:startX
Y:startY
ToX:endX
Y:endY
pad:pad];
} else {
return ([self contentFromX:startX
Y:startY
ToX:endX
Y:endY
pad:pad
includeLastNewline:[[PreferencePanel sharedInstance] copyLastNewline]]);
}
}
- (NSString *)content
{
return [self contentFromX:0
Y:0
ToX:[dataSource width]
Y:[dataSource numberOfLines] - 1
pad:NO
includeLastNewline:YES];
}
- (void)splitTextViewVertically:(id)sender
{
[[[[dataSource session] tab] realParentWindow] splitVertically:YES
withBookmark:[[ProfileModel sharedInstance] defaultBookmark]
targetSession:[dataSource session]];
}
- (void)splitTextViewHorizontally:(id)sender
{
[[[[dataSource session] tab] realParentWindow] splitVertically:NO
withBookmark:[[ProfileModel sharedInstance] defaultBookmark]
targetSession:[dataSource session]];
}
- (void)movePane:(id)sender
{
[[MovePaneController sharedInstance] movePane:[dataSource session]];
}
- (void)clearWorkingDirectories {
[workingDirectoryAtLines removeAllObjects];
}
- (void)clearTextViewBuffer:(id)sender
{
[[dataSource session] clearBuffer];
}
- (void)editTextViewSession:(id)sender
{
[[[[dataSource session] tab] realParentWindow] editSession:[dataSource session]];
}
- (void)toggleBroadcastingInput:(id)sender
{
[[[[dataSource session] tab] realParentWindow] toggleBroadcastingInputToSession:[dataSource session]];
}
- (void)closeTextViewSession:(id)sender
{
[[[[dataSource session] tab] realParentWindow] closeSessionWithConfirmation:[dataSource session]];
}
- (void)copy:(id)sender
{
NSPasteboard *pboard = [NSPasteboard generalPasteboard];
NSString *copyString;
copyString = [self selectedText];
if (copyString) {
[pboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:self];
[pboard setString:copyString forType:NSStringPboardType];
}
[[PasteboardHistory sharedInstance] save:copyString];
}
- (void)paste:(id)sender
{
NSString* info = [PTYSession pasteboardString];
if (info) {
[[PasteboardHistory sharedInstance] save:info];
}
if ([_delegate respondsToSelector:@selector(paste:)]) {
[_delegate paste:sender];
}
}
- (NSTimeInterval)selectionTime
{
return selectionTime_;
}
- (void)pasteSelection:(id)sender
{
if ([_delegate respondsToSelector:@selector(pasteString:)]) {
PTYSession *session = [[iTermController sharedInstance] sessionWithMostRecentSelection];
if (session) {
PTYTextView *textview = [session TEXTVIEW];
if (textview->startX > -1) {
[_delegate pasteString:[textview selectedText]];
}
}
}
}
- (BOOL)_broadcastToggleable
{
PseudoTerminal *pty = [[[dataSource session] tab] realParentWindow];
if ([pty broadcastMode] == BROADCAST_OFF && [[pty currentSession] TEXTVIEW] == self) {
return NO;
}
return YES;
}
- (BOOL)validateMenuItem:(NSMenuItem *)item
{
if ([item action] == @selector(paste:)) {
NSPasteboard *pboard = [NSPasteboard generalPasteboard];
// Check if there is a string type on the pasteboard
return ([pboard stringForType:NSStringPboardType] != nil);
}
if ([item action ] == @selector(cut:)) {
// Never allow cut.
return NO;
}
if ([item action]==@selector(toggleBroadcastingInput:) &&
[self _broadcastToggleable]) {
return YES;
}
if ([item action]==@selector(saveDocumentAs:)) {
return [self isAnyCharSelected];
} else if ([item action] == @selector(selectAll:) ||
[item action]==@selector(splitTextViewVertically:) ||
[item action]==@selector(splitTextViewHorizontally:) ||
[item action]==@selector(clearTextViewBuffer:) ||
[item action]==@selector(editTextViewSession:) ||
[item action]==@selector(closeTextViewSession:) ||
[item action]==@selector(movePane:) ||
([item action] == @selector(print:) && [item tag] != 1)) {
// We always validate the above commands
return YES;
}
if ([item action]==@selector(mail:) ||
[item action]==@selector(browse:) ||
[item action]==@selector(searchInBrowser:) ||
[item action]==@selector(copy:) ||
[item action]==@selector(pasteSelection:) ||
([item action]==@selector(print:) && [item tag] == 1)) { // print selection
// These commands are allowed only if there is a selection.
return startX > -1;
}
SEL theSel = [item action];
if ([NSStringFromSelector(theSel) hasPrefix:@"contextMenuAction"]) {
return YES;
}
return NO;
}
- (BOOL)_haveShortSelection
{
return startX > -1 && startY >= 0 && abs(startY - endY) <= 1;
}
- (BOOL)addCustomActionsToMenu:(NSMenu *)theMenu matchingText:(NSString *)textWindow
{
BOOL didAdd = NO;
NSArray* rulesArray = smartSelectionRules_ ? smartSelectionRules_ : [SmartSelectionController defaultRules];
const int numRules = [rulesArray count];
for (int j = 0; j < numRules; j++) {
NSDictionary *rule = [rulesArray objectAtIndex:j];
NSString *regex = [SmartSelectionController regexInRule:rule];
for (int i = 0; i <= textWindow.length; i++) {
NSString* substring = [textWindow substringWithRange:NSMakeRange(i, [textWindow length] - i)];
NSError* regexError = nil;
NSArray *components = [substring captureComponentsMatchedByRegex:regex
options:0
range:NSMakeRange(0, [substring length])
error:&regexError];
if (components.count) {
NSLog(@"Components for %@ are %@", regex, components);
NSArray *actions = [SmartSelectionController actionsInRule:rule];
for (NSDictionary *action in actions) {
SEL mySelector = @selector(bogusSelector:);
// The selector's name must begin with contextMenuAction to
// pass validateMenuItem.