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
2014-09-07 16:49:36 +02:00

488 lines
16 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 "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<MPExpressionElement> 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