// // MPExpressionView.m // MathPad // // Created by Kim Wittenburg on 17.04.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // #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; @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 *)selection selectWords:(BOOL)flag; - (NSIndexPath *)selectionToTheLeftOf:(NSIndexPath *)selection selectWords:(BOOL)flag; - (MPRangePath *)rangePathEnclosingAnchor:(NSIndexPath *)anchor newSelection:(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(0, 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; } - (NSIndexPath *)selectionToTheRightOf:(NSIndexPath *)selection selectWords:(BOOL)flag { MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[selection indexPathByRemovingLastIndex]]; NSUInteger locationInTarget = selection.lastIndex; NSUInteger locationInElement; NSUInteger targetElementIndex = [targetExpression indexOfElementAtSymbolLocation:locationInTarget offset:&locationInElement]; id targetElement; if (targetElementIndex < targetExpression.numberOfElements) { targetElement = [targetExpression elementAtIndex:targetElementIndex]; } if (locationInElement == 0 && !flag) { NSLog(@"Move to next"); // Move to next element } else if (locationInTarget < targetExpression.length) { if (flag) { locationInTarget = [targetExpression locationOfElementAtIndex:targetElementIndex+1]; } else { locationInTarget++; } } return [selection indexPathByReplacingLastIndexWithIndex:locationInTarget]; } - (NSIndexPath *)selectionToTheLeftOf:(NSIndexPath *)selection selectWords:(BOOL)flag { MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[selection indexPathByRemovingLastIndex]]; NSUInteger locationInTarget = selection.lastIndex; NSUInteger locationInElement; NSUInteger targetElementIndex = [targetExpression indexOfElementAtSymbolLocation:locationInTarget offset:&locationInElement]; if (locationInElement == 0 && !flag) { // Move to previous element } else if (locationInTarget > 0) { if (flag) { if (locationInElement == 0) { targetElementIndex--; } locationInTarget = [targetExpression locationOfElementAtIndex:targetElementIndex]; } else { locationInTarget--; } } return [selection indexPathByReplacingLastIndexWithIndex:locationInTarget]; } - (MPRangePath *)rangePathEnclosingAnchor:(NSIndexPath *)anchor newSelection:(NSIndexPath *)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] initWithElements:@[@"12345", [[MPSumFunction alloc] init]]]; expressionStorage.expressionView = self; _expressionStorage = expressionStorage; // Setup the Functions Button NSButton *button = [[NSButton alloc] initWithFrame:NSZeroRect]; button.buttonType = NSSwitchButton; [button setTitle:@"Functions"]; self.functionsButton = button; [self addSubview:self.functionsButton]; // Setup Selection 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; NSMutableCharacterSet *allowedCharacters = [NSMutableCharacterSet alphanumericCharacterSet]; [allowedCharacters addCharactersInString:@"+-*= "]; if (characters.length == 1 && [characters stringByTrimmingCharactersInSet:allowedCharacters].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)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 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 selectWords:NO]; self.selection = MPMakeRangePath(newSelectionLocation, 0); } } - (void)moveWordRight:(id)sender { NSIndexPath *location = self.selection.maxRangePath; NSIndexPath *newSelectionLocation = [self selectionToTheRightOf:location selectWords:YES]; self.selection = MPMakeRangePath(newSelectionLocation, 0); } - (void)moveWordLeft:(id)sender { NSIndexPath *location = self.selection.location; NSIndexPath *newSelectionLocation = [self selectionToTheLeftOf:location 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.length], 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 selectWords:NO]; } else { maxLocation = [self selectionToTheLeftOf:maxLocation selectWords:NO]; } self.selection = MPMakeRangePath(location, maxLocation.lastIndex-location.lastIndex); } - (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 selectWords:NO]; } else { maxLocation = [self selectionToTheRightOf:maxLocation 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 selectWords:YES]; if (location.lastIndex > maxLocation.lastIndex) { location = [location indexPathByReplacingLastIndexWithIndex:maxLocation.lastIndex]; } } else { maxLocation = [self selectionToTheRightOf:maxLocation 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 selectWords:YES]; } else { maxLocation = [self selectionToTheLeftOf:maxLocation selectWords:YES]; if (maxLocation.lastIndex < location.lastIndex) { maxLocation = [maxLocation indexPathByReplacingLastIndexWithIndex:location.lastIndex]; } } self.selection = MPMakeRangePath(location, maxLocation.lastIndex-location.lastIndex); } - (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.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 rangePathEnclosingAnchor:self.mouseAnchor newSelection: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; // 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