Improved Model
Added Keyboard Selection Support Added Mouse Selection Support Added Keyboard Editing Support Corrected Some Bugs Abstracted the Layout System further Added Functions Button (test)
This commit is contained in:
@@ -6,34 +6,109 @@
|
||||
// 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 (MPCursor)
|
||||
@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
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self initializeObjects];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(NSRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self initializeObjects];
|
||||
[self initializeExpressionView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -42,25 +117,44 @@
|
||||
{
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
[self initializeObjects];
|
||||
[self initializeExpressionView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initializeObjects
|
||||
- (void)initializeExpressionView
|
||||
{
|
||||
MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] initWithExpressionView:self elements:@[@"12345", [[MPSumFunction alloc] init], [[MPSumFunction alloc] init]]];
|
||||
MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] initWithElements:@[@"12345", [[MPSumFunction alloc] init], [[MPSumFunction alloc] init]]];
|
||||
expressionStorage.expressionView = self;
|
||||
_expressionStorage = expressionStorage;
|
||||
_caretLocation = [[NSIndexPath alloc] initWithIndex:0];
|
||||
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
|
||||
|
||||
- (BOOL)isFlipped
|
||||
- (void)setExpressionStorage:(MPExpressionStorage *)expressionStorage
|
||||
{
|
||||
return NO;
|
||||
_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;
|
||||
@@ -71,29 +165,226 @@
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark Event Handling
|
||||
- (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
|
||||
{
|
||||
[self interpretKeyEvents:@[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
|
||||
{
|
||||
[self.expressionStorage deleteElementsInRange:NSMakeRange(0, 1)];
|
||||
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);
|
||||
[[NSColor blackColor] set];
|
||||
NSSize expressionSize = [self.expressionStorage.rootLayout size];
|
||||
CGFloat y = (self.bounds.size.height - expressionSize.height) / 2;
|
||||
NSPoint point = NSMakePoint(self.bounds.origin.x, self.bounds.origin.y + y);
|
||||
[self.expressionStorage.rootLayout drawAtPoint:point];
|
||||
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user