Archived
1
This repository has been archived on 2022-08-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
mathpad/MathPad/MPExpressionView.m
Kim Wittenburg f4f924bd71 Cleaned Imports
2014-11-08 01:05:08 +01:00

935 lines
41 KiB
Objective-C

//
// MPExpressionView.m
// MathPad
//
// Created by Kim Wittenburg on 17.04.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPExpressionView.h"
#import "MPExpression.h"
#import "MPExpressionElement.h"
#import "MPPowerFunction.h"
#import "MPParenthesisFunction.h"
#import "MPFractionFunction.h"
#import "MPRangePath.h"
#import "MPMathRules.h"
#import "MPExpressionStorage.h"
#import "MPExpressionLayout.h"
#import "MPFunctionsViewController.h"
#import "NSIndexPath+MPAdditions.h"
@interface MPExpressionView ()
@property (nonatomic, strong) NSButton *radiansDegreesButton;
@property (nonatomic, strong) NSButton *functionsButton;
@property (nonatomic, strong) NSPopover *functionsPopover;
@property (nonatomic, strong) MPFunctionsViewController *functionsViewController;
@property (nonatomic, strong) NSTimer *caretTimer;
@property (nonatomic) NSTimeInterval caretBlinkRate;
@property (nonatomic) BOOL caretVisible;
@property (nonatomic, getter = isSelectionModifyingStart) BOOL selectionModifyingStart;
@property (nonatomic, strong) NSIndexPath *mouseAnchor;
@end
@interface MPExpressionView (MPDrawing)
- (NSPoint)expressionOrigin;
@end
@interface MPExpressionView (MPSelection)
- (void)restartCaretTimer;
- (void)updateCaret:(NSTimer *)timer;
- (NSRect)selectionRect;
- (NSIndexPath *)selectionToTheRightOf:(NSIndexPath *)selectionPath
byExtendingSelection:(BOOL)extendingSelection
selectWords:(BOOL)selectWords;
- (NSIndexPath *)selectionToTheLeftOf:(NSIndexPath *)selectionPath
byExtendingSelection:(BOOL)extendingSelection
selectWords:(BOOL)selectWords;
- (MPRangePath *)rangePathEnclosingAnchorPath:(NSIndexPath *)anchor
newSelectionPath:(NSIndexPath *)newSelection;
@end
@implementation MPExpressionView (MPDrawing)
- (NSPoint)expressionOrigin
{
NSRect expressionBounds = [self.expressionStorage.rootLayout bounds];
CGFloat y = (self.bounds.size.height - expressionBounds.size.height) / 2 + fabs(expressionBounds.origin.y);
return NSMakePoint(10, y);
}
@end
@implementation MPExpressionView (MPSelection)
- (void)restartCaretTimer
{
if (self.caretTimer && [self.caretTimer isValid]) {
[self.caretTimer invalidate];
}
self.caretTimer = [NSTimer scheduledTimerWithTimeInterval:self.caretBlinkRate/2 target:self selector:@selector(updateCaret:) userInfo:nil repeats:YES];
self.caretVisible = NO;
[self updateCaret:self.caretTimer];
}
- (void)updateCaret:(NSTimer *)timer
{
self.caretVisible = !self.caretVisible;
NSRect updatedRect = self.selectionRect;
NSPoint expressionOrigin = self.expressionOrigin;
updatedRect.origin.x += expressionOrigin.x;
updatedRect.origin.y += expressionOrigin.y;
[self setNeedsDisplayInRect:updatedRect];
}
- (NSRect)selectionRect
{
NSRect selectionRect = [self.expressionStorage.rootLayout boundingRectForRangePath:self.selection];
if (self.selection.length == 0) {
selectionRect.size.width = 1;
}
return selectionRect;
}
- (NSIndexPath *)selectionToTheRightOf:(NSIndexPath *)selectionPath
byExtendingSelection:(BOOL)extendingSelection
selectWords:(BOOL)selectWords
{
NSIndexPath *targetExpressionPath = [selectionPath indexPathByRemovingLastIndex];
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
NSUInteger locationInTarget = selectionPath.lastIndex;
NSUInteger locationInElement;
NSUInteger targetElementIndex = [targetExpression convertIndex:locationInTarget
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame
offset:&locationInElement];
id<MPExpressionElement> targetElement;
// There is only a target element if the selection is not the last location in an expression
if (targetElementIndex < targetExpression.countElements) {
targetElement = [targetExpression elementAtIndex:targetElementIndex];
}
if (!selectWords && !extendingSelection && (locationInElement == 0 || locationInTarget == targetExpression.countSymbols)) {
// First or last index in an element or expression
// Last element in the expression
if (locationInTarget == targetExpression.countSymbols) {
// The selection is inside a function and should proceed
if (selectionPath.length > 1) {
NSIndexPath *functionPath = [[selectionPath indexPathByRemovingLastIndex] indexPathByRemovingLastIndex];
MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:functionPath];
NSUInteger currentChildIndex = [selectionPath indexPathByRemovingLastIndex].lastIndex;
NSUInteger newChildIndex = [functionLayout indexOfChildAfterChildAtIndex:currentChildIndex];
// The function is to be exited
if (newChildIndex == NSNotFound) {
targetExpression = [self.expressionStorage elementAtIndexPath:[functionPath indexPathByRemovingLastIndex]];
NSUInteger functionLocationInExpression = [targetExpression convertIndex:functionPath.lastIndex
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
return [functionPath indexPathByReplacingLastIndexWithIndex:functionLocationInExpression+1];
} else {
return [[targetExpressionPath indexPathByReplacingLastIndexWithIndex:newChildIndex] indexPathByAddingIndex:0];
}
} // else the selection does not change
// First Element
} else {
if ([targetElement isString]) {
locationInTarget++;
} else {
NSIndexPath *targetFunctionPath = [selectionPath indexPathByReplacingLastIndexWithIndex:targetElementIndex];
MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:targetFunctionPath];
NSUInteger leadingChildIndex = [functionLayout indexOfLeadingChild];
return [[targetFunctionPath indexPathByAddingIndex:leadingChildIndex] indexPathByAddingIndex:0];
}
}
} else if (locationInTarget < targetExpression.countSymbols) {
if (selectWords) {
locationInTarget = [targetExpression convertIndex:targetElementIndex+1
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
} else {
locationInTarget++;
}
}
return [selectionPath indexPathByReplacingLastIndexWithIndex:locationInTarget];
}
- (NSIndexPath *)selectionToTheLeftOf:(NSIndexPath *)selectionPath
byExtendingSelection:(BOOL)extendingSelection
selectWords:(BOOL)selectWords
{
NSIndexPath *targetExpressionPath = [selectionPath indexPathByRemovingLastIndex];
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
NSUInteger locationInTarget = selectionPath.lastIndex;
NSUInteger locationInElement;
NSUInteger targetElementIndex = [targetExpression convertIndex:locationInTarget
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame
offset:&locationInElement];
NSUInteger previousElementIndex = targetElementIndex - (locationInElement == 0 ? 1 : 0);
id<MPExpressionElement> previousElement;
if (locationInTarget > 0) {
previousElement = [targetExpression elementAtIndex:previousElementIndex];
}
if (!selectWords && !extendingSelection && locationInElement == 0) {
// First element in expression
if (locationInTarget == 0) {
if (selectionPath.length > 1) {
NSIndexPath *functionPath = [[selectionPath indexPathByRemovingLastIndex] indexPathByRemovingLastIndex];
MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:functionPath];
NSUInteger currentChildIndex = [selectionPath indexPathByRemovingLastIndex].lastIndex;
NSUInteger newChildIndex = [functionLayout indexOfChildBeforeChildAtIndex:currentChildIndex];
// The function is to be exited
if (newChildIndex == NSNotFound) {
targetExpression = [self.expressionStorage elementAtIndexPath:[functionPath indexPathByRemovingLastIndex]];
NSUInteger functionLocationInExpression = [targetExpression convertIndex:functionPath.lastIndex
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
return [functionPath indexPathByReplacingLastIndexWithIndex:functionLocationInExpression];
} else {
targetExpressionPath = [targetExpressionPath indexPathByReplacingLastIndexWithIndex:newChildIndex];
targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
return [targetExpressionPath indexPathByAddingIndex:targetExpression.countSymbols];
}
} // else the selection does not change
// Just
} else {
if ([previousElement isString]) {
locationInTarget--;
} else {
NSIndexPath *targetFunctionPath = [selectionPath indexPathByReplacingLastIndexWithIndex:previousElementIndex];
MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:targetFunctionPath];
NSUInteger trailingChildIndex = [functionLayout indexOfTrailingChild];
targetExpressionPath = [targetFunctionPath indexPathByAddingIndex:trailingChildIndex];
targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
return [targetExpressionPath indexPathByAddingIndex:targetExpression.countSymbols];
}
}
} else if (locationInTarget > 0) {
if (selectWords) {
if (locationInElement == 0) {
targetElementIndex--;
}
locationInTarget = [targetExpression convertIndex:targetElementIndex
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
} else {
locationInTarget--;
}
}
return [selectionPath indexPathByReplacingLastIndexWithIndex:locationInTarget];
}
- (MPRangePath *)rangePathEnclosingAnchorPath:(NSIndexPath *)anchorPath
newSelectionPath:(NSIndexPath *)newSelectionPath
{
if ([anchorPath isEqual:newSelectionPath]) {
return MPMakeRangePath(anchorPath, 0);
}
NSIndexPath *commonPath = [anchorPath commonIndexPathWith:newSelectionPath];
if (commonPath.length == anchorPath.length-1 && commonPath.length == newSelectionPath.length-1) {
// The two paths point to different locations in the same expression
NSUInteger anchorIndex = [anchorPath indexAtPosition:commonPath.length];
NSUInteger newIndex = [newSelectionPath indexAtPosition:commonPath.length];
NSUInteger minIndex = MIN(anchorIndex, newIndex);
NSUInteger length = MAX(anchorIndex, newIndex) - minIndex;
return MPMakeRangePath([commonPath indexPathByAddingIndex:minIndex], length);
} else {
if ((commonPath.length & 1) == 1) {
commonPath = [commonPath indexPathByRemovingLastIndex];
}
MPExpression *closestCommonAncestor = [self.expressionStorage elementAtIndexPath:commonPath];
NSUInteger anchorIndex = [anchorPath indexAtPosition:commonPath.length];
NSUInteger newIndex = [newSelectionPath indexAtPosition:commonPath.length];
if (commonPath.length < anchorPath.length-1) {
anchorIndex = [closestCommonAncestor convertIndex:anchorIndex
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
}
if (commonPath.length < newSelectionPath.length-1) {
newIndex = [closestCommonAncestor convertIndex:newIndex
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
}
NSUInteger minIndex = MIN(anchorIndex, newIndex);
if (commonPath.length < anchorPath.length-1 && anchorIndex != minIndex) {
anchorIndex++;
} else if (commonPath.length < newSelectionPath.length-1 && newIndex != minIndex) {
newIndex++;
}
NSUInteger length = MAX(anchorIndex, newIndex) - minIndex;
if (anchorIndex == newIndex) {
length++;
}
MPRangePath *newSelection = MPMakeRangePath([commonPath indexPathByAddingIndex:minIndex], length);
return newSelection;
}
}
@end
@implementation MPExpressionView
#pragma mark Creation Methods
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initializeExpressionView];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self initializeExpressionView];
}
return self;
}
- (void)initializeExpressionView
{
// Setup the Expression Storage
MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] init];
expressionStorage.expressionView = self;
_expressionStorage = expressionStorage;
[self initializeButtons];
[self initializeErrorMessageTextField];
self.selection = [[MPRangePath alloc] initWithRange:NSMakeRange(0, 0)];
self.caretBlinkRate = 1.0;
[self restartCaretTimer];
}
- (void)initializeButtons
{
NSButton *radiansDegreesButton = self.radiansDegreesButton;
[self addSubview:radiansDegreesButton];
NSButton *functionsButton = self.functionsButton;
[self addSubview:functionsButton];
NSDictionary *variableBindings = NSDictionaryOfVariableBindings(radiansDegreesButton, functionsButton);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[radiansDegreesButton]"
options:0
metrics:nil
views:variableBindings]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-10-[radiansDegreesButton]"
options:0
metrics:nil
views:variableBindings]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[functionsButton]-10-|"
options:0
metrics:nil
views:variableBindings]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:functionsButton
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterY
multiplier:1
constant:0]];
}
- (void)initializeErrorMessageTextField
{
NSTextField *errorLabel = self.errorMessageTextField;
[self addSubview:errorLabel];
NSDictionary *variableBindings = NSDictionaryOfVariableBindings(errorLabel);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[errorLabel]"
options:0
metrics:nil
views:variableBindings]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[errorLabel]-10-|"
options:0
metrics:nil
views:variableBindings]];
}
#pragma mark Properties
- (void)setExpressionStorage:(MPExpressionStorage *)expressionStorage
{
_expressionStorage.expressionView = nil;
_expressionStorage = expressionStorage;;
_expressionStorage.expressionView = self;
[self invalidateIntrinsicContentSize];
}
- (void)setSelection:(MPRangePath *)selection
{
_selection = selection;
[self restartCaretTimer];
self.needsDisplay = YES;
}
- (NSButton *)radiansDegreesButton
{
if (!_radiansDegreesButton) {
NSButton *radiansDegreesButton = [[NSButton alloc] initWithFrame:NSZeroRect];
radiansDegreesButton.translatesAutoresizingMaskIntoConstraints = NO;
radiansDegreesButton.buttonType = NSMomentaryPushInButton;
radiansDegreesButton.bordered = YES;
radiansDegreesButton.bezelStyle = NSRoundedBezelStyle;
radiansDegreesButton.imagePosition = NSNoImage;
radiansDegreesButton.alignment = NSCenterTextAlignment;
radiansDegreesButton.title = NSLocalizedString([MPMathRules sharedRules].isUsingDegrees ? @"Deg" : @"Rad", nil);
radiansDegreesButton.font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]];
radiansDegreesButton.target = self;
radiansDegreesButton.action = @selector(switchRadiansDegrees:);
_radiansDegreesButton = radiansDegreesButton;
}
return _radiansDegreesButton;
}
- (NSButton *)functionsButton
{
if (!_functionsButton) {
NSBundle *frameworkBundle = [NSBundle bundleForClass:[self class]];
NSImage *image = [frameworkBundle imageForResource:@"FunctionsButtonDisclosure"];
[image setName:@"FunctionsButtonDisclosure"];
NSButton *button = [[NSButton alloc] initWithFrame:NSZeroRect];
button.translatesAutoresizingMaskIntoConstraints = NO;
button.target = self;
button.action = @selector(showFunctions:);
button.buttonType = NSMomentaryChangeButton;
button.bezelStyle = NSShadowlessSquareBezelStyle;
button.bordered = NO;
NSFont *font = [NSFont fontWithName:@"Times New Roman" size:25.0];
NSAttributedString *attributedTitle = [[NSAttributedString alloc] initWithString:@"Σ" attributes:@{NSFontAttributeName: font, NSForegroundColorAttributeName: [NSColor colorWithWhite:.61 alpha:1]}];
button.attributedTitle = attributedTitle;
button.imagePosition = NSImageLeft;
button.image = image;
_functionsButton = button;
}
return _functionsButton;
}
- (NSTextField *)errorMessageTextField
{
if (!_errorMessageTextField) {
NSTextField *label = [[NSTextField alloc] initWithFrame:NSZeroRect];
label.textColor = [NSColor redColor];
label.translatesAutoresizingMaskIntoConstraints = NO;
label.bezeled = NO;
label.drawsBackground = NO;
label.editable = NO;
label.selectable = NO;
_errorMessageTextField = label;
}
return _errorMessageTextField;
}
#pragma mark Actions
- (void)switchRadiansDegrees:(id)sender
{
[MPMathRules sharedRules].isUsingDegrees = ![MPMathRules sharedRules].isUsingDegrees;
self.radiansDegreesButton.title = NSLocalizedString([MPMathRules sharedRules].isUsingDegrees ? @"Deg" : @"Rad", nil);
}
- (void)showFunctions:(id)sender
{
if (self.functionsPopover == nil || self.functionsViewController == nil) {
self.functionsViewController = [[MPFunctionsViewController alloc] init];
self.functionsViewController.target = self;
self.functionsViewController.action = @selector(insertFunction:);
self.functionsPopover = [[NSPopover alloc] init];
self.functionsPopover.contentViewController = self.functionsViewController;
self.functionsPopover.animates = YES;
self.functionsPopover.behavior = NSPopoverBehaviorSemitransient;
}
if (self.functionsPopover.isShown) {
[self.functionsPopover close];
} else {
[self.functionsPopover showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxYEdge];
}
}
- (void)insertFunction:(MPFunction *)function
{
[self.functionsPopover close];
[self.expressionStorage replaceItemsInRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame
withElements:@[function]];
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
NSUInteger functionElementIndex = [targetExpression convertIndex:self.selection.location.lastIndex
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame];
NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex];
MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:functionPath];
self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:functionLayout.indexOfLeadingChild] indexPathByAddingIndex:0], 0);
}
#pragma mark NSView Stuff
- (BOOL)acceptsFirstResponder
{
return YES;
}
- (BOOL)canBecomeKeyView
{
return YES;
}
- (BOOL)becomeFirstResponder
{
[self restartCaretTimer];
return [super becomeFirstResponder];
}
- (BOOL)resignFirstResponder
{
[self.caretTimer invalidate];
self.caretVisible = NO;
self.needsDisplay = YES;
return [super resignFirstResponder];
}
- (BOOL)isFlipped
{
return NO;
}
- (BOOL)isOpaque
{
return YES;
}
- (void)setFrame:(NSRect)frameRect
{
[self setNeedsLayout:YES];
[super setFrame:frameRect];
}
- (NSSize)intrinsicContentSize
{
NSSize size = self.expressionStorage.rootLayout.bounds.size;
size.width += 100;
return size;
}
- (void)resetCursorRects
{
[self addCursorRect:self.bounds
cursor:[NSCursor IBeamCursor]];
}
#pragma mark Key Event Handling
- (void)keyDown:(NSEvent *)theEvent
{
NSString *characters = theEvent.characters;
if ([characters isEqualToString:@"("]) {
[self insertParenthesisFunction:nil];
return;
}
if (theEvent.keyCode == 10) {
[self insertPowerFunction];
return;
}
if ([characters isEqualToString:@"/"]) {
[self insertFractionFunction];
return;
}
NSString *decimalSeparator = [NSRegularExpression escapedPatternForString:[[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator]];
NSMutableCharacterSet *allowedCharacters = [NSMutableCharacterSet alphanumericCharacterSet];
[allowedCharacters addCharactersInString:[NSString stringWithFormat:@"+-*= !%@", decimalSeparator]];
if (characters.length == 1 && [characters stringByTrimmingCharactersInSet:allowedCharacters].length == 0) {
if ([characters isEqualToString:@"*"]) {
characters = @"";
}
[self.expressionStorage replaceItemsInRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame
withElements:@[characters]];
self.selection = MPMakeRangePath([self.selection.location indexPathByIncrementingLastIndex], 0);
} else {
[self interpretKeyEvents:@[theEvent]];
}
}
- (void)insertParenthesisFunction:(id)sender
{
MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame];
MPParenthesisFunction *function = [[MPParenthesisFunction alloc] init];
function.expression = selectedElementsExpression;
[self.expressionStorage replaceItemsInRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame
withElements:@[function]];
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
NSUInteger functionElementIndex = [targetExpression convertIndex:self.selection.location.lastIndex
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame];
NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex];
self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], self.selection.length);
}
- (void)insertPowerFunction
{
MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame];
MPPowerFunction *function = [[MPPowerFunction alloc] init];
function.exponentExpression = selectedElementsExpression;
[self.expressionStorage replaceItemsInRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame
withElements:@[function]];
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
NSUInteger functionElementIndex = [targetExpression convertIndex:self.selection.location.lastIndex
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame];
NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex];
self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], self.selection.length);
}
- (void)insertFractionFunction
{
MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame];
MPFractionFunction *function = [[MPFractionFunction alloc] init];
function.nominatorExpression = selectedElementsExpression;
[self.expressionStorage replaceItemsInRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame
withElements:@[function]];
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
NSUInteger functionElementIndex = [targetExpression convertIndex:self.selection.location.lastIndex
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame];
NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex];
self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:1] indexPathByAddingIndex:0], 0);
}
- (void)insertNewline:(id)sender
{
if (self.target && self.action) {
[self.target performSelector:self.action
withObject:self
afterDelay:0.0];
}
}
- (void)moveRight:(id)sender
{
if (self.selection.length > 0) {
self.selection = MPMakeRangePath(self.selection.maxRangePath, 0);
} else {
NSIndexPath *newSelectionLocation = [self selectionToTheRightOf:self.selection.location
byExtendingSelection:NO
selectWords:NO];
self.selection = MPMakeRangePath(newSelectionLocation, 0);
}
}
- (void)moveLeft:(id)sender
{
if (self.selection.length > 0) {
self.selection = MPMakeRangePath(self.selection.location, 0);
} else {
NSIndexPath *newSelectionLocation = [self selectionToTheLeftOf:self.selection.location
byExtendingSelection:NO
selectWords:NO];
self.selection = MPMakeRangePath(newSelectionLocation, 0);
}
}
- (void)moveWordRight:(id)sender
{
NSIndexPath *location = self.selection.maxRangePath;
NSIndexPath *newSelectionLocation = [self selectionToTheRightOf:location
byExtendingSelection:NO
selectWords:YES];
self.selection = MPMakeRangePath(newSelectionLocation, 0);
}
- (void)moveWordLeft:(id)sender
{
NSIndexPath *location = self.selection.location;
NSIndexPath *newSelectionLocation = [self selectionToTheLeftOf:location
byExtendingSelection:NO
selectWords:YES];
self.selection = MPMakeRangePath(newSelectionLocation, 0);
}
- (void)moveToBeginningOfLine:(id)sender
{
self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:0], 0);
}
- (void)moveToEndOfLine:(id)sender
{
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:targetExpression.countSymbols], 0);
}
- (void)moveLeftAndModifySelection:(id)sender
{
if (self.selection.length == 0) {
self.selectionModifyingStart = YES;
}
NSIndexPath *location = self.selection.location;
NSIndexPath *maxLocation = self.selection.maxRangePath;
if (self.selectionModifyingStart) {
location = [self selectionToTheLeftOf:location
byExtendingSelection:YES
selectWords:NO];
} else {
maxLocation = [self selectionToTheLeftOf:maxLocation
byExtendingSelection:YES
selectWords:NO];
}
self.selection = [self rangePathEnclosingAnchorPath:maxLocation newSelectionPath:location];
}
- (void)moveRightAndModifySelection:(id)sender
{
if (self.selection.length == 0) {
self.selectionModifyingStart = NO;
}
NSIndexPath *location = self.selection.location;
NSIndexPath *maxLocation = self.selection.maxRangePath;
if (self.selectionModifyingStart) {
location = [self selectionToTheRightOf:location
byExtendingSelection:YES
selectWords:NO];
} else {
maxLocation = [self selectionToTheRightOf:maxLocation
byExtendingSelection:YES
selectWords:NO];
}
self.selection = MPMakeRangePath(location, maxLocation.lastIndex-location.lastIndex);
}
- (void)moveWordRightAndModifySelection:(id)sender
{
if (self.selection.length == 0) {
self.selectionModifyingStart = NO;
}
NSIndexPath *location = self.selection.location;
NSIndexPath *maxLocation = self.selection.maxRangePath;
if (self.selectionModifyingStart) {
location = [self selectionToTheRightOf:location
byExtendingSelection:YES
selectWords:YES];
if (location.lastIndex > maxLocation.lastIndex) {
location = [location indexPathByReplacingLastIndexWithIndex:maxLocation.lastIndex];
}
} else {
maxLocation = [self selectionToTheRightOf:maxLocation
byExtendingSelection:YES
selectWords:YES];
}
self.selection = MPMakeRangePath(location, maxLocation.lastIndex-location.lastIndex);
}
- (void)moveWordLeftAndModifySelection:(id)sender
{
if (self.selection.length == 0) {
self.selectionModifyingStart = YES;
}
NSIndexPath *location = self.selection.location;
NSIndexPath *maxLocation = self.selection.maxRangePath;
if (self.selectionModifyingStart) {
location = [self selectionToTheLeftOf:location
byExtendingSelection:YES
selectWords:YES];
} else {
maxLocation = [self selectionToTheLeftOf:maxLocation
byExtendingSelection:YES
selectWords:YES];
if (maxLocation.lastIndex < location.lastIndex) {
maxLocation = [maxLocation indexPathByReplacingLastIndexWithIndex:location.lastIndex];
}
}
self.selection = MPMakeRangePath(location, maxLocation.lastIndex-location.lastIndex);
}
- (void)moveUp:(id)sender
{
NSIndexPath *targetExpressionPath = [self.selection.location indexPathByRemovingLastIndex];
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
if (targetExpression.parent != nil) {
NSIndexPath *functionPath = [targetExpressionPath indexPathByRemovingLastIndex];
MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:functionPath];
NSUInteger newChildIndex = [functionLayout indexOfChildAboveChildAtIndex:targetExpressionPath.lastIndex];
self.selection = MPMakeRangePath([[targetExpressionPath indexPathByReplacingLastIndexWithIndex:newChildIndex] indexPathByAddingIndex:0], 0);
}
}
- (void)moveDown:(id)sender
{
NSIndexPath *targetExpressionPath = [self.selection.location indexPathByRemovingLastIndex];
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
if (targetExpression.parent != nil) {
NSIndexPath *functionPath = [targetExpressionPath indexPathByRemovingLastIndex];
MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:functionPath];
NSUInteger newChildIndex = [functionLayout indexOfChildBelowChildAtIndex:targetExpressionPath.lastIndex];
self.selection = MPMakeRangePath([[targetExpressionPath indexPathByReplacingLastIndexWithIndex:newChildIndex] indexPathByAddingIndex:0], 0);
}
}
- (void)selectAll:(id)sender
{
NSIndexPath *location = [NSIndexPath indexPathWithIndex:0];
self.selection = MPMakeRangePath(location, self.expressionStorage.countSymbols);
}
- (void)deleteBackward:(id)sender
{
if (self.selection.length > 0) {
[self.expressionStorage replaceItemsInRangePath:self.selection
referenceFrame:MPSymbolReferenceFrame
withElements:@[]];
self.selection = MPMakeRangePath(self.selection.location, 0);
} else if (self.selection.location.lastIndex > 0) {
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
id<MPExpressionElement> elementToDelete = [targetExpression elementAtIndex:[targetExpression convertIndex:self.selection.location.lastIndex-1
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame]];
if ([elementToDelete isFunction]) {
self.selection = MPMakeRangePath(self.selection.location.indexPathByDecrementingLastIndex, 1);
} else {
[targetExpression replaceItemsInRange:NSMakeRange(self.selection.location.lastIndex-1, 1)
referenceFrame:MPSymbolReferenceFrame
withElements:@[]];
self.selection = MPMakeRangePath([self.selection.location indexPathByDecrementingLastIndex], 0);
}
} else {
NSIndexPath *targetExpressionPath = [self.selection.location indexPathByRemovingLastIndex];
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
if (targetExpression.parent != nil) {
NSIndexPath *functionPath = [targetExpressionPath indexPathByRemovingLastIndex];
MPFunction *function = [self.expressionStorage elementAtIndexPath:functionPath];
MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:functionPath];
NSIndexSet *remainingIndexes = [functionLayout indexesOfRemainingChildren];
NSMutableArray *remainder = [[NSMutableArray alloc] init];
for (NSUInteger index = remainingIndexes.firstIndex;
index <= remainingIndexes.lastIndex;
index = [remainingIndexes indexGreaterThanIndex:index]) {
MPExpression *expression = [function childAtIndex:index];
[remainder addObjectsFromArray:[expression allItemsInReferenceFrame:MPElementReferenceFrame]];
}
NSIndexPath *newTargetExpressionPath = [functionPath indexPathByRemovingLastIndex];
MPExpression *newTargetExpression = [self.expressionStorage elementAtIndexPath:newTargetExpressionPath];
NSUInteger newSelectionElementIndex = [newTargetExpression convertIndex:functionPath.lastIndex
fromReferenceFrame:MPElementReferenceFrame
toReferenceFrame:MPSymbolReferenceFrame];
NSIndexPath *newSelectionLocation = [functionPath indexPathByReplacingLastIndexWithIndex:newSelectionElementIndex];
[self.expressionStorage replaceItemsInRangePath:MPMakeRangePath(newSelectionLocation, 1)
referenceFrame:MPSymbolReferenceFrame
withElements:remainder];
self.selection = MPMakeRangePath(newSelectionLocation, 0);
}
}
}
#pragma mark Mouse Event Handling
- (void)mouseDown:(NSEvent *)theEvent
{
NSPoint pointInView = [self convertPoint:theEvent.locationInWindow
fromView:nil];
NSPoint expressionOrigin = self.expressionOrigin;
pointInView.x -= expressionOrigin.x;
pointInView.y -= expressionOrigin.y;
NSIndexPath *selectionPath = [self.expressionStorage.rootLayout indexPathForMousePoint:pointInView];
self.mouseAnchor = selectionPath;
self.selection = MPMakeRangePath(selectionPath, 0);
}
- (void)mouseDragged:(NSEvent *)theEvent
{
NSPoint pointInView = [self convertPoint:theEvent.locationInWindow
fromView:nil];
NSPoint expressionOrigin = self.expressionOrigin;
pointInView.x -= expressionOrigin.x;
pointInView.y -= expressionOrigin.y;
NSIndexPath *mouseSelectionPath = [self.expressionStorage.rootLayout indexPathForMousePoint:pointInView];
self.selection = [self rangePathEnclosingAnchorPath:self.mouseAnchor
newSelectionPath:mouseSelectionPath];
}
#pragma mark Drawing Methods
- (void)drawRect:(NSRect)dirtyRect
{
// Draw the background
[super drawRect:dirtyRect];
[[NSColor whiteColor] set];
NSRectFill(dirtyRect);
// Calculate the position of the expression (probably also forces layout of the expression the first time)
NSPoint expressionOrigin = self.expressionOrigin;
NSAffineTransform *transform = [NSAffineTransform transform];
[transform translateXBy:expressionOrigin.x
yBy:expressionOrigin.y];
[transform concat];
// Draw the selection
if (self.caretVisible || self.selection.length > 0) {
if (self.selection.length == 0) {
[[NSColor blackColor] set];
} else {
[[NSColor selectedTextBackgroundColor] set];
}
NSRectFill([self selectionRect]);
}
[transform invert];
[transform concat];
// Draw the expression
[[NSColor textColor] set];
[self.expressionStorage.rootLayout drawAtPoint:expressionOrigin];
}
@end