// // MPExpressionView.m // MathPad // // Created by Kim Wittenburg on 17.04.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // #warning X-Origin is not working yet #import "MPExpressionView.h" #import "MPExpressionStorage.h" #import "MPExpressionLayout.h" #import "MPRangePath.h" #import "NSIndexPath+MPAdditions.h" #import "MPSumFunction.h" @interface MPExpressionView () @property (nonatomic, weak) NSButton *functionsButton; @property (nonatomic, strong) NSTimer *caretTimer; @property (nonatomic) NSTimeInterval caretBlinkRate; @property (nonatomic) BOOL caretVisible; @property (nonatomic, getter = isSelectionModifyingStart) BOOL selectionModifyingStart; @end @interface MPExpressionView (MPDrawing) - (NSPoint)expressionOrigin; @end @interface MPExpressionView (MPSelection) - (void)restartCaretTimer; - (void)updateCaret:(NSTimer *)timer; - (NSRect)selectionRect; - (void)selectRangePathWithLocation:(NSIndexPath *)location length:(NSUInteger)length; @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(self.bounds.origin.x, self.bounds.origin.y + y); } @end @implementation MPExpressionView (MPSelection) - (void)restartCaretTimer { if (self.caretTimer) { if ([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; self.needsDisplay = YES; } - (NSRect)selectionRect { NSRect selectionRect = [self.expressionStorage.rootLayout boundingRectForRangePath:self.selection]; if (self.selection.length == 0) { selectionRect.size.width = 1; } return selectionRect; } - (void)selectRangePathWithLocation:(NSIndexPath *)location length:(NSUInteger)length { MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[location indexPathByRemovingLastIndex]]; NSUInteger locationIndex = location.lastIndex; if (locationIndex > targetExpression.length) { locationIndex = targetExpression.length; } NSUInteger lastSelectedIndex = location.lastIndex + length; if (lastSelectedIndex > targetExpression.length) { lastSelectedIndex = targetExpression.length; } self.selection = MPMakeRangePath([location indexPathByReplacingLastIndexWithIndex:locationIndex],lastSelectedIndex - locationIndex); } @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 { MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] initWithElements:@[@"12345", [[MPSumFunction alloc] init], [[MPSumFunction alloc] init]]]; expressionStorage.expressionView = self; _expressionStorage = expressionStorage; NSRect frame = NSMakeRect(10, 10, 500, 500); NSButton *button = [[NSButton alloc] initWithFrame:frame]; button.buttonType = NSSwitchButton; [button setTitle:@"Functions"]; self.functionsButton = button; [self addSubview:self.functionsButton]; self.selection = [[MPRangePath alloc] initWithRange:NSMakeRange(0, 0)]; self.caretBlinkRate = 1.0; [self restartCaretTimer]; } #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; } #pragma mark NSView Stuff - (BOOL)acceptsFirstResponder { return YES; } - (BOOL)canBecomeKeyView { return YES; } - (BOOL)isFlipped { return NO; } - (BOOL)isOpaque { return YES; } - (void)layout { NSSize buttonSize = [self.functionsButton fittingSize]; self.functionsButton.frame = NSMakeRect(self.bounds.size.width - buttonSize.width, (self.bounds.size.height - buttonSize.height) / 2, buttonSize.width, buttonSize.height); [super layout]; } - (NSSize)intrinsicContentSize { // return NSMakeSize(500, 500); // return self.bounds.size; return self.expressionStorage.rootLayout.bounds.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.length == 1 && [characters stringByTrimmingCharactersInSet:[NSCharacterSet decimalDigitCharacterSet]].length == 0) { MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]]; [targetExpression replaceSymbolsInRange:self.selection.rangeAtLastIndex withElements:@[characters]]; self.selection = MPMakeRangePath([self.selection.location indexPathByIncrementingLastIndex], 0); } else { [self interpretKeyEvents:@[theEvent]]; } } - (void)moveRight:(id)sender { if (self.selection.length > 0) { self.selection = MPMakeRangePath(self.selection.maxRangePath, 0); } else { NSIndexPath *newSelectionLocation = [self.selection.location indexPathByIncrementingLastIndex]; [self selectRangePathWithLocation:newSelectionLocation length:0]; } } - (void)moveLeft:(id)sender { if (self.selection.length > 0) { self.selection = MPMakeRangePath(self.selection.location, 0); [self selectRangePathWithLocation:self.selection.location length:0]; } else { NSUInteger selectionIndex = self.selection.location.lastIndex; if (selectionIndex > 0) { --selectionIndex; } NSIndexPath *newSelectionLocation = [self.selection.location indexPathByReplacingLastIndexWithIndex:selectionIndex]; [self selectRangePathWithLocation:newSelectionLocation length:0]; } } - (void)moveWordRight:(id)sender { NSIndexPath *location; if (self.selection.length > 0) { location = self.selection.maxRangePath; } else { location = self.selection.location; } MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[location indexPathByRemovingLastIndex]]; NSUInteger locationInTarget = NSMaxRange(self.selection.rangeAtLastIndex); NSUInteger offset; NSUInteger elementIndex = [targetExpression indexOfElementAtSymbolLocation:locationInTarget offset:&offset]; NSUInteger newLocation = locationInTarget + [targetExpression elementAtIndex:elementIndex].length - offset; [self selectRangePathWithLocation:[location indexPathByReplacingLastIndexWithIndex:newLocation] length:0]; } - (void)moveWordLeft:(id)sender { MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]]; NSUInteger offset; NSUInteger elementIndex = [targetExpression indexOfElementAtSymbolLocation:self.selection.location.lastIndex offset:&offset]; NSUInteger newLocation = self.selection.location.lastIndex; if (offset > 0) { newLocation -= newLocation - offset > 0 ? offset : newLocation; } else if (newLocation > 0) { newLocation -= [targetExpression elementAtIndex:elementIndex-1].length; } self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:newLocation], 0); } - (void)moveToBeginningOfLine:(id)sender { self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:0], 0); } - (void)moveToEndOfLine:(id)sender { } - (void)moveLeftAndModifySelection:(id)sender { if (self.selection.length == 0) { self.selectionModifyingStart = YES; } NSUInteger start = self.selection.location.lastIndex; NSUInteger length = self.selection.length; if (self.selectionModifyingStart) { if (start > 0) { --start; ++length; } } else { --length; } self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:start], length); } - (void)moveRightAndModifySelection:(id)sender { if (self.selection.length == 0) { self.selectionModifyingStart = NO; } NSUInteger start = self.selection.location.lastIndex; NSUInteger length = self.selection.length; if (self.selectionModifyingStart) { ++start; --length; } else { ++length; } [self selectRangePathWithLocation:[self.selection.location indexPathByReplacingLastIndexWithIndex:start] length:length]; } - (void)moveWordRightAndModifySelection:(id)sender { } - (void)moveWordLeftAndModifySelection:(id)sender { } - (void)selectAll:(id)sender { NSIndexPath *location = [NSIndexPath indexPathWithIndex:0]; self.selection = MPMakeRangePath(location, self.expressionStorage.length); } - (void)deleteBackward:(id)sender { MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]]; if (self.selection.length > 0) { [targetExpression replaceSymbolsInRange:self.selection.rangeAtLastIndex withElements:@[]]; self.selection = MPMakeRangePath(self.selection.location, 0); } else if (self.selection.location.lastIndex > 0) { [targetExpression replaceSymbolsInRange:NSMakeRange(self.selection.location.lastIndex-1, 1) withElements:@[]]; self.selection = MPMakeRangePath([self.selection.location indexPathByDecrementingLastIndex], 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.selection = MPMakeRangePath(selectionPath, 0); } #pragma mark Drawing Methods - (void)drawRect:(NSRect)dirtyRect { // Draw the background [super drawRect:dirtyRect]; [[NSColor whiteColor] set]; NSRectFill(self.bounds); // Calculate the position of the expression (probably also forces layout of the expression the first time) NSPoint expressionOrigin = self.expressionOrigin; // Draw the selection if (self.caretVisible || self.selection.length > 0) { if (self.selection.length == 0) { [[NSColor blackColor] set]; } else { [[NSColor selectedTextBackgroundColor] set]; } NSAffineTransform *transform = [NSAffineTransform transform]; [transform translateXBy:expressionOrigin.x yBy:expressionOrigin.y]; [transform concat]; NSRectFill([self selectionRect]); [transform invert]; [transform concat]; } // Draw the expression [[NSColor textColor] set]; [self.expressionStorage.rootLayout drawAtPoint:expressionOrigin]; } @end