Archived
1

Added Function Chooser as a Popover

Improved Evaluation
Added Parenthesis Function
This commit is contained in:
Kim Wittenburg
2014-09-28 23:52:29 +02:00
parent 1c8b7e3c12
commit 19a40c2907
16 changed files with 746 additions and 32 deletions

View File

@@ -13,6 +13,12 @@
#import "NSIndexPath+MPAdditions.h" #import "NSIndexPath+MPAdditions.h"
#define kMPEmptyBoxWidth (self.usesSmallSize ? 2.0 : 3.0) #define kMPEmptyBoxWidth (self.usesSmallSize ? 2.0 : 3.0)
#define kMPEmptyBoxHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font))
#define kMPEmptyBoxYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)))
#define kMPEmptyBoxDrawingWidth kMPEmptyBoxWidth
#define kMPEmptyBoxDrawingHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font))
#define kMPEmptyBoxDrawingYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)/2))
@interface MPExpressionLayout (MPLineGeneration) @interface MPExpressionLayout (MPLineGeneration)
@@ -86,7 +92,7 @@
- (NSRect)generateBounds - (NSRect)generateBounds
{ {
if (self.expression.numberOfElements == 0) { if (self.expression.numberOfElements == 0) {
return NSMakeRect(0, [self.font descender], kMPEmptyBoxWidth, self.fontSize); return NSMakeRect(0, kMPEmptyBoxYOrigin, kMPEmptyBoxWidth, kMPEmptyBoxHeight);
} }
CGFloat x = 0, y = 0, width = 0, height = 0; CGFloat x = 0, y = 0, width = 0, height = 0;
for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) { for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) {
@@ -211,10 +217,23 @@
(CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
CGContextSaveGState(context); CGContextSaveGState(context);
#ifdef MPDEBUG_DRAW_ORIGIN
[[NSColor blueColor] set];
[[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(-2, -2, 4, 4)] fill];
#endif
[[NSColor textColor] set];
#ifdef MPDEBUG_DRAW_BOUNDS
[[NSColor greenColor] set];
[[NSBezierPath bezierPathWithRect:self.bounds] stroke];
[[NSColor textColor] set];
#endif
if (self.expression.numberOfElements == 0) { if (self.expression.numberOfElements == 0) {
CGContextRestoreGState(context); CGContextRestoreGState(context);
NSBezierPath *path = [NSBezierPath bezierPathWithRect:NSMakeRect(0, 0 + self.font.descender, kMPEmptyBoxWidth, self.fontSize)]; NSBezierPath *path = [NSBezierPath bezierPathWithRect:NSMakeRect(0, kMPEmptyBoxDrawingYOrigin, kMPEmptyBoxDrawingWidth, kMPEmptyBoxDrawingHeight)];
path.lineWidth = 0.5; path.lineWidth = 0.5;
[path stroke]; [path stroke];
return; return;
@@ -238,10 +257,16 @@
// Move to the appropriate position // Move to the appropriate position
CGContextSetTextPosition(context, x, 0); CGContextSetTextPosition(context, x, 0);
#ifdef MPDEBUG_DRAW_BASELINE
[[NSColor redColor] set];
NSRectFill(NSMakeRect(x, -1, elementBounds.size.width, 1));
[[NSColor textColor] set];
#endif
// Perform the drawing // Perform the drawing
CTLineDraw(line, context); CTLineDraw(line, context);
CFRelease(line); CFRelease(line);
} else { } else {
// Let the child layout draw itself // Let the child layout draw itself

View File

@@ -50,6 +50,7 @@
[current clearCacheInRange:rangePath.rangeAtLastIndex [current clearCacheInRange:rangePath.rangeAtLastIndex
replacementLength:replacementLength]; replacementLength:replacementLength];
[self.expressionView invalidateIntrinsicContentSize]; [self.expressionView invalidateIntrinsicContentSize];
self.expressionView.error = nil;
self.expressionView.needsDisplay = YES; self.expressionView.needsDisplay = YES;
} }

View File

@@ -10,6 +10,8 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import "MPParseError.h"
@class MPExpressionView, MPExpressionStorage, MPExpressionLayout, MPRangePath; @class MPExpressionView, MPExpressionStorage, MPExpressionLayout, MPRangePath;
@interface MPExpressionView : NSView @interface MPExpressionView : NSView
@@ -23,7 +25,10 @@
@property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage; @property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage;
// @property (nonatomic, getter = isEditable) BOOL editable; // @property (nonatomic, getter = isEditable) BOOL editable;
@property (nonatomic) BOOL allowsIntelligentReplacements;
@property (nonatomic, strong) MPRangePath *selection; @property (nonatomic, strong) MPRangePath *selection;
@property (nonatomic, strong) MPParseError *error;
@property (nonatomic, weak) id target; @property (nonatomic, weak) id target;
@property (nonatomic) SEL action; @property (nonatomic) SEL action;

View File

@@ -16,12 +16,17 @@
#import "NSIndexPath+MPAdditions.h" #import "NSIndexPath+MPAdditions.h"
#import "MPSumFunction.h" #import "MPSumFunction.h"
#import "MPParenthesisFunction.h"
#import "MPParenthesisFunctionLayout.h"
#import "MPFunctionsViewController.h"
@interface MPExpressionView () @interface MPExpressionView ()
@property (nonatomic, weak) NSButton *functionsButton; @property (nonatomic, weak) NSButton *functionsButton;
@property (nonatomic, strong) NSPopover *functionsPopover;
@property (nonatomic, strong) MPFunctionsViewController *functionsViewController;
@property (nonatomic, strong) NSTimer *caretTimer; @property (nonatomic, strong) NSTimer *caretTimer;
@property (nonatomic) NSTimeInterval caretBlinkRate; @property (nonatomic) NSTimeInterval caretBlinkRate;
@@ -303,7 +308,7 @@
- (void)initializeExpressionView - (void)initializeExpressionView
{ {
// Setup the Expression Storage // Setup the Expression Storage
MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] initWithElements:@[@"12345", [[MPSumFunction alloc] init], [[MPSumFunction alloc] init]]]; MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] init];
expressionStorage.expressionView = self; expressionStorage.expressionView = self;
_expressionStorage = expressionStorage; _expressionStorage = expressionStorage;
@@ -347,18 +352,40 @@
self.needsDisplay = YES; self.needsDisplay = YES;
} }
- (void)setError:(MPParseError *)error
{
_error = error;
self.needsDisplay = YES;
}
#pragma mark Actions #pragma mark Actions
- (void)showFunctions:(id)sender - (void)showFunctions:(id)sender
{ {
NSViewController *controller = [[NSViewController alloc] initWithNibName:nil if (self.functionsPopover == nil || self.functionsViewController == nil) {
bundle:nil]; self.functionsViewController = [[MPFunctionsViewController alloc] init];
controller.view = [[NSView alloc] init]; self.functionsViewController.target = self;
NSPopover *popover = [[NSPopover alloc] init]; self.functionsViewController.action = @selector(insertFunction:);
popover.contentSize = NSMakeSize(100.0, 100.0); self.functionsPopover = [[NSPopover alloc] init];
popover.contentViewController = controller; self.functionsPopover.contentViewController = self.functionsViewController;
popover.animates = YES; self.functionsPopover.animates = YES;
popover.behavior = NSPopoverBehaviorSemitransient; self.functionsPopover.behavior = NSPopoverBehaviorSemitransient;
[popover showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxYEdge]; }
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 replaceSymbolsInRangePath:self.selection withElements:@[function]];
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:[targetExpression indexOfElementAtLocation:self.selection.location.lastIndex
offset:NULL]];
MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:functionPath];
self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:functionLayout.indexOfLeadingChild] indexPathByAddingIndex:0], 0);
} }
#pragma mark NSView Stuff #pragma mark NSView Stuff
@@ -372,6 +399,20 @@
return YES; 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 - (BOOL)isFlipped
{ {
return NO; return NO;
@@ -415,17 +456,40 @@
- (void)keyDown:(NSEvent *)theEvent - (void)keyDown:(NSEvent *)theEvent
{ {
NSString *characters = theEvent.characters; NSString *characters = theEvent.characters;
if ([characters isEqualToString:@"("]) {
[self insertParenthesisFunction];
return;
}
NSString *decimalSeparator = [NSRegularExpression escapedPatternForString:[[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator]];
NSMutableCharacterSet *allowedCharacters = [NSMutableCharacterSet alphanumericCharacterSet]; NSMutableCharacterSet *allowedCharacters = [NSMutableCharacterSet alphanumericCharacterSet];
[allowedCharacters addCharactersInString:@"+-*= "]; [allowedCharacters addCharactersInString:[NSString stringWithFormat:@"+-*= %@", decimalSeparator]];
if (characters.length == 1 && [characters stringByTrimmingCharactersInSet:allowedCharacters].length == 0) { if (characters.length == 1 && [characters stringByTrimmingCharactersInSet:allowedCharacters].length == 0) {
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]]; [self.expressionStorage replaceSymbolsInRangePath:self.selection withElements:@[characters]];
[targetExpression replaceSymbolsInRange:self.selection.rangeAtLastIndex withElements:@[characters]];
self.selection = MPMakeRangePath([self.selection.location indexPathByIncrementingLastIndex], 0); self.selection = MPMakeRangePath([self.selection.location indexPathByIncrementingLastIndex], 0);
} else { } else {
[self interpretKeyEvents:@[theEvent]]; [self interpretKeyEvents:@[theEvent]];
} }
} }
- (void)insertParenthesisFunction
{
MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection];
MPParenthesisFunction *function = [[MPParenthesisFunction alloc] init];
function.expression = selectedElementsExpression;
[self.expressionStorage replaceSymbolsInRangePath:self.selection
withElements:@[function]];
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:[targetExpression indexOfElementAtLocation:self.selection.location.lastIndex
offset:NULL]];
self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], self.selection.length);
}
- (void)insertNewline:(id)sender - (void)insertNewline:(id)sender
{ {
if (self.target && self.action) { if (self.target && self.action) {
@@ -571,6 +635,30 @@
self.selection = MPMakeRangePath(location, maxLocation.lastIndex-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 - (void)selectAll:(id)sender
{ {
NSIndexPath *location = [NSIndexPath indexPathWithIndex:0]; NSIndexPath *location = [NSIndexPath indexPathWithIndex:0];
@@ -579,13 +667,42 @@
- (void)deleteBackward:(id)sender - (void)deleteBackward:(id)sender
{ {
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
if (self.selection.length > 0) { if (self.selection.length > 0) {
[targetExpression replaceSymbolsInRange:self.selection.rangeAtLastIndex withElements:@[]]; [self.expressionStorage replaceSymbolsInRangePath:self.selection withElements:@[]];
self.selection = MPMakeRangePath(self.selection.location, 0); self.selection = MPMakeRangePath(self.selection.location, 0);
} else if (self.selection.location.lastIndex > 0) { } else if (self.selection.location.lastIndex > 0) {
[targetExpression replaceSymbolsInRange:NSMakeRange(self.selection.location.lastIndex-1, 1) withElements:@[]]; MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
self.selection = MPMakeRangePath([self.selection.location indexPathByDecrementingLastIndex], 0); id <MPExpressionElement> elementToDelete = [targetExpression elementAtIndex:[targetExpression indexOfElementAtLocation:self.selection.location.lastIndex-1
offset:NULL]];
if ([elementToDelete isFunction]) {
self.selection = MPMakeRangePath(self.selection.location.indexPathByDecrementingLastIndex, 1);
} else {
[targetExpression replaceSymbolsInRange:NSMakeRange(self.selection.location.lastIndex-1, 1) 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]) {
[remainder addObjectsFromArray:[function childAtIndex:index].elements];
}
NSIndexPath *newTargetExpressionPath = [functionPath indexPathByRemovingLastIndex];
MPExpression *newTargetExpression = [self.expressionStorage elementAtIndexPath:newTargetExpressionPath];
NSIndexPath *newSelectionLocation = [functionPath indexPathByReplacingLastIndexWithIndex:[newTargetExpression locationOfElementAtIndex:functionPath.lastIndex]];
[self.expressionStorage replaceSymbolsInRangePath:MPMakeRangePath(functionPath, 1) withElements:remainder];
self.selection = MPMakeRangePath(newSelectionLocation, 0);
}
} }
} }
@@ -626,6 +743,11 @@
// Calculate the position of the expression (probably also forces layout of the expression the first time) // Calculate the position of the expression (probably also forces layout of the expression the first time)
NSPoint expressionOrigin = self.expressionOrigin; NSPoint expressionOrigin = self.expressionOrigin;
NSAffineTransform *transform = [NSAffineTransform transform];
[transform translateXBy:expressionOrigin.x
yBy:expressionOrigin.y];
[transform concat];
// Draw the selection // Draw the selection
if (self.caretVisible || self.selection.length > 0) { if (self.caretVisible || self.selection.length > 0) {
if (self.selection.length == 0) { if (self.selection.length == 0) {
@@ -633,15 +755,12 @@
} else { } else {
[[NSColor selectedTextBackgroundColor] set]; [[NSColor selectedTextBackgroundColor] set];
} }
NSAffineTransform *transform = [NSAffineTransform transform];
[transform translateXBy:expressionOrigin.x
yBy:expressionOrigin.y];
[transform concat];
NSRectFill([self selectionRect]); NSRectFill([self selectionRect]);
[transform invert];
[transform concat];
} }
[transform invert];
[transform concat];
// Draw the expression // Draw the expression
[[NSColor textColor] set]; [[NSColor textColor] set];
[self.expressionStorage.rootLayout drawAtPoint:expressionOrigin]; [self.expressionStorage.rootLayout drawAtPoint:expressionOrigin];

View File

@@ -26,6 +26,8 @@
#pragma mark Cache Methods #pragma mark Cache Methods
- (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index - (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index
generator:(CTLineRef (^)())generator; generator:(CTLineRef (^)())generator;
- (id)objectForPrivateCacheIndex:(NSUInteger)index
generator:(id (^)())generator;
// Should also implement accessor method for special function type: // Should also implement accessor method for special function type:
// - (MPCustomFunction *)customFunction // - (MPCustomFunction *)customFunction
@@ -34,10 +36,12 @@
// } // }
#pragma mark Size and Drawing Methods #pragma mark Size and Drawing Methods
- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index; // May be implemented
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index; // To be implemented - (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index; // To be implemented
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point; // To be implemented - (NSIndexPath *)indexPathForMousePoint:(NSPoint)point;
- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point; // To be implemented
- (NSRect)generateBounds; // To be implemented - (NSRect)generateBounds; // To be implemented
- (void)drawAtPoint:(NSPoint)point; // To be implemented - (void)draw; // To be implemented
// Specify the child that is used when the cursor enters the function from the left or right respectively // Specify the child that is used when the cursor enters the function from the left or right respectively
- (NSUInteger)indexOfLeadingChild; // To be implemented - (NSUInteger)indexOfLeadingChild; // To be implemented
@@ -46,5 +50,8 @@
// The index of the child before (left) or after (right) the child at @c index. return NSNotFound if there is no child before or after @c index. // The index of the child before (left) or after (right) the child at @c index. return NSNotFound if there is no child before or after @c index.
- (NSUInteger)indexOfChildBeforeChildAtIndex:(NSUInteger)index; // May be implemented - (NSUInteger)indexOfChildBeforeChildAtIndex:(NSUInteger)index; // May be implemented
- (NSUInteger)indexOfChildAfterChildAtIndex:(NSUInteger)index; // May be implemented - (NSUInteger)indexOfChildAfterChildAtIndex:(NSUInteger)index; // May be implemented
- (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index; // May be implemented
- (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index; // May be implemented
- (NSIndexSet *)indexesOfRemainingChildren; // May be implemented
@end @end

View File

@@ -12,6 +12,10 @@
#import "MPSumFunction.h" #import "MPSumFunction.h"
#import "MPSumFunctionLayout.h" #import "MPSumFunctionLayout.h"
#import "MPParenthesisFunction.h"
#import "MPParenthesisFunctionLayout.h"
#import "NSIndexPath+MPAdditions.h"
@implementation MPFunctionLayout @implementation MPFunctionLayout
@@ -22,6 +26,8 @@
Class class = [function class]; Class class = [function class];
if (class == [MPSumFunction class]) { if (class == [MPSumFunction class]) {
return [[MPSumFunctionLayout alloc] initWithFunction:function parent:parent]; return [[MPSumFunctionLayout alloc] initWithFunction:function parent:parent];
} else if (class == [MPParenthesisFunction class]) {
return [[MPParenthesisFunctionLayout alloc] initWithFunction:function parent:parent];
} }
return [[self alloc] initWithFunction:function return [[self alloc] initWithFunction:function
parent:parent]; parent:parent];
@@ -51,6 +57,13 @@
return (__bridge CTLineRef)(lineObject); return (__bridge CTLineRef)(lineObject);
} }
- (id)objectForPrivateCacheIndex:(NSUInteger)index
generator:(id (^)())generator
{
NSUInteger actualIndex = self.function.numberOfChildren + index;
return [self cachableObjectForIndex:actualIndex generator:generator];
}
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index - (MPLayout *)childLayoutAtIndex:(NSUInteger)index
{ {
return [self cachableObjectForIndex:index generator:^id{ return [self cachableObjectForIndex:index generator:^id{
@@ -58,11 +71,36 @@
MPExpressionLayout *layout = [[MPExpressionLayout alloc] initWithExpression:child MPExpressionLayout *layout = [[MPExpressionLayout alloc] initWithExpression:child
parent:self]; parent:self];
layout.flipped = self.flipped; layout.flipped = self.flipped;
layout.usesSmallSize = (index == 0 || index == 1) ? YES : self.usesSmallSize; layout.usesSmallSize = self.usesSmallSize ? YES : [self childAtIndexUsesSmallSize:index];
return layout; return layout;
}]; }];
} }
- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index
{
return NO;
}
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point
{
// A single index is used to communicate back wether the
// selection should be before or after the function.
// A 0 means before, a 1 means after.
for (NSUInteger index = 0; index < self.function.numberOfChildren; index++) {
MPLayout *childLayout = [self childLayoutAtIndex:index];
NSRect childBounds = childLayout.bounds;
NSPoint childOffset = [self offsetOfChildLayoutAtIndex:index];
childBounds.origin.x += childOffset.x;
childBounds.origin.y += childOffset.y;
if (NSMouseInRect(point, childBounds, self.flipped)) {
NSPoint pointInChild = NSMakePoint(point.x - childOffset.x, point.y - childOffset.y);
NSIndexPath *subPath = [childLayout indexPathForMousePoint:pointInChild];
return [subPath indexPathByPreceedingIndex:index];
}
}
return [self indexPathForLocalMousePoint:point];
}
- (NSUInteger)indexOfChildBeforeChildAtIndex:(NSUInteger)index - (NSUInteger)indexOfChildBeforeChildAtIndex:(NSUInteger)index
{ {
return NSNotFound; return NSNotFound;
@@ -73,4 +111,19 @@
return NSNotFound; return NSNotFound;
} }
- (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index
{
return index;
}
- (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index
{
return index;
}
- (NSIndexSet *)indexesOfRemainingChildren
{
return [NSIndexSet indexSet];
}
@end @end

View File

@@ -0,0 +1,24 @@
//
// MPFunctionsViewController.h
// MathPad
//
// Created by Kim Wittenburg on 28.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class MPFunctionsCollectionView;
@interface MPFunctionsViewController : NSViewController
- (id)init;
@property (weak) IBOutlet MPFunctionsCollectionView *collectionView;
@property (nonatomic, strong) NSArray *functionPrototypes;
@property (nonatomic, strong) NSString *currentDescription;
@property (nonatomic, weak) id target;
@property (nonatomic) SEL action; // 1 argument: The function to insert
@end

View File

@@ -0,0 +1,252 @@
//
// MPFunctionsViewController.m
// MathPad
//
// Created by Kim Wittenburg on 28.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPFunctionsViewController.h"
#import "MPFunction.h"
#import "MPFunctionLayout.h"
#import "MPSumFunction.h"
#import "MPParenthesisFunction.h"
@class MPFunctionTemplatePrototype;
@interface MPFunctionsCollectionView : NSCollectionView
@property (nonatomic, weak) MPFunctionTemplatePrototype *hoverItem;
@property (nonatomic, weak) id target;
@property (nonatomic) SEL action;
@end
@interface MPFunctionTemplateView : NSView
@property (nonatomic, strong) MPFunction *functionTemplate;
@property (readonly, nonatomic, strong) MPFunctionLayout *functionTemplateLayout;
@property (nonatomic, strong) NSTrackingArea *trackingArea;
@property (nonatomic) BOOL mouseOver;
@end
@interface MPFunctionTemplatePrototype : NSCollectionViewItem
@end
@implementation MPFunctionsCollectionView
- (void)mouseDown:(NSEvent *)theEvent
{
}
- (void)mouseUp:(NSEvent *)theEvent
{
NSPoint pointInView = [self convertPoint:theEvent.locationInWindow
fromView:nil];
for (NSUInteger index = 0; index < self.content.count; index++) {
NSCollectionViewItem *viewItem = [self itemAtIndex:index];
if (NSMouseInRect(pointInView, viewItem.view.frame, self.isFlipped)) {
if (self.target && self.action) {
[self.target performSelector:self.action
withObject:[viewItem.representedObject copy]
afterDelay:0.0];
}
break;
}
}
}
@end
@implementation MPFunctionTemplateView
- (void)updateTrackingAreas
{
if (!self.trackingArea) {
self.trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds
options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInKeyWindow|NSTrackingInVisibleRect
owner:self
userInfo:nil];
[self addTrackingArea:self.trackingArea];
}
[super updateTrackingAreas];
}
- (void)mouseEntered:(NSEvent *)theEvent
{
self.mouseOver = YES;
self.needsDisplay = YES;
}
- (void)mouseExited:(NSEvent *)theEvent
{
self.mouseOver = NO;
self.needsDisplay = YES;
}
- (void)setFunctionTemplate:(MPFunction *)functionTemplate
{
_functionTemplate = functionTemplate;
_functionTemplateLayout = nil;
}
@synthesize functionTemplateLayout = _functionTemplateLayout;
- (MPFunctionLayout *)functionTemplateLayout
{
if (!_functionTemplateLayout) {
_functionTemplateLayout = [MPFunctionLayout functionLayoutForFunction:self.functionTemplate
parent:nil];
_functionTemplateLayout.usesSmallSize = YES;
}
return _functionTemplateLayout;
}
- (NSSize)intrinsicContentSize
{
return [self.functionTemplateLayout bounds].size;
}
- (BOOL)isOpaque
{
return NO;
}
- (void)drawRect:(NSRect)dirtyRect
{
if (self.mouseOver) {
[[NSColor selectedTextBackgroundColor] set];
NSBezierPath *background = [NSBezierPath bezierPathWithRoundedRect:self.bounds
xRadius:10
yRadius:10];
[background fill];
}
[[NSColor textColor] set];
NSPoint origin = NSMakePoint((self.bounds.size.width - self.functionTemplateLayout.bounds.size.width) / 2, (self.bounds.size.height - self.functionTemplateLayout.bounds.size.height) / 2);
origin.y -= self.functionTemplateLayout.bounds.origin.y;
[self.functionTemplateLayout drawAtPoint:origin];
}
@end
@implementation MPFunctionTemplatePrototype
static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMouseOverContext";
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
MPFunctionTemplateView *view = (MPFunctionTemplateView *)self.view;
[view addObserver:self
forKeyPath:@"mouseOver"
options:0
context:MPFunctionTemplateViewMouseOverContext];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == MPFunctionTemplateViewMouseOverContext) {
MPFunctionTemplateView *view = (MPFunctionTemplateView *)self.view;
((MPFunctionsCollectionView *)self.collectionView).hoverItem = view.mouseOver ? self : nil;
} else {
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
- (void)setRepresentedObject:(id)representedObject
{
[super setRepresentedObject:representedObject];
((MPFunctionTemplateView *)self.view).functionTemplate = representedObject;
}
@end
@interface MPFunctionsViewController ()
@end
@implementation MPFunctionsViewController
- (id)init
{
return [self initWithNibName:@"MPFunctionsViewController"
bundle:[NSBundle bundleForClass:[self class]]];
}
- (id)initWithNibName:(NSString *)nibNameOrNil
bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.functionPrototypes = @[[[MPSumFunction alloc] init], [[MPParenthesisFunction alloc] init]];
}
return self;
}
static void *MPCollectionViewHoverItemChangeContext = @"MPCollectionViewHoverItemChangeContext";
- (void)loadView
{
[super loadView];
[self.collectionView addObserver:self
forKeyPath:@"hoverItem"
options:0
context:MPCollectionViewHoverItemChangeContext];
self.collectionView.target = self.target;
self.collectionView.action = self.action;
}
- (void)setTarget:(id)target
{
_target = target;
if (self.collectionView) {
self.collectionView.target = target;
}
}
- (void)setAction:(SEL)action
{
_action = action;
if (self.collectionView) {
self.collectionView.action = action;
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == MPCollectionViewHoverItemChangeContext) {
self.currentDescription = [[self.collectionView.hoverItem.representedObject class] performSelector:@selector(localizedFunctionName)];
} else {
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
@end

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6245" systemVersion="13F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6245"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MPFunctionsViewController">
<connections>
<outlet property="collectionView" destination="QmA-Ge-oq3" id="mYG-bs-5TG"/>
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="Hz6-mo-xeY" customClass="MPWhiteView">
<rect key="frame" x="0.0" y="0.0" width="320" height="185"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9w0-UQ-Fqx">
<rect key="frame" x="10" y="25" width="300" height="150"/>
<clipView key="contentView" copiesOnScroll="NO" id="Z8C-m4-8Mb">
<rect key="frame" x="1" y="1" width="248" height="158"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView selectable="YES" id="QmA-Ge-oq3" customClass="MPFunctionsCollectionView">
<rect key="frame" x="0.0" y="0.0" width="248" height="158"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="primaryBackgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<connections>
<binding destination="-2" name="content" keyPath="functionPrototypes" id="sPD-4B-zL5"/>
<outlet property="itemPrototype" destination="4Wf-e2-Nmy" id="v9O-tD-Y7U"/>
</connections>
</collectionView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="umr-K7-88h">
<rect key="frame" x="1" y="144" width="233" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="MEs-Qt-Wze">
<rect key="frame" x="234" y="1" width="15" height="143"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="D89-Gi-YJm">
<rect key="frame" x="-2" y="0.0" width="324" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Description" id="xrL-Xy-aRm">
<font key="font" metaFont="system"/>
<color key="textColor" name="disabledControlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="currentDescription" id="0Vx-OA-1Zy">
<dictionary key="options">
<string key="NSNullPlaceholder">Choose an Element</string>
</dictionary>
</binding>
</connections>
</textField>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="D89-Gi-YJm" secondAttribute="bottom" id="6QP-Uy-i2A"/>
<constraint firstItem="9w0-UQ-Fqx" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="10" id="GOC-rC-AE6"/>
<constraint firstItem="9w0-UQ-Fqx" firstAttribute="centerX" secondItem="D89-Gi-YJm" secondAttribute="centerX" id="Qk9-hA-Jcj"/>
<constraint firstItem="9w0-UQ-Fqx" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="10" id="RFi-H1-0CV"/>
<constraint firstItem="D89-Gi-YJm" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="Rup-Ep-vEV"/>
<constraint firstItem="D89-Gi-YJm" firstAttribute="top" secondItem="9w0-UQ-Fqx" secondAttribute="bottom" constant="8" symbolic="YES" id="xGL-ZL-fb1"/>
<constraint firstAttribute="trailing" secondItem="9w0-UQ-Fqx" secondAttribute="trailing" constant="10" id="zb3-nc-Iai"/>
</constraints>
<point key="canvasLocation" x="-101" y="140.5"/>
</customView>
<collectionViewItem id="4Wf-e2-Nmy" customClass="MPFunctionTemplatePrototype">
<connections>
<outlet property="view" destination="dE9-Vu-psk" id="hX8-fz-7FR"/>
</connections>
</collectionViewItem>
<view id="dE9-Vu-psk" customClass="MPFunctionTemplateView">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<point key="canvasLocation" x="119" y="-114.5"/>
</view>
</objects>
</document>

View File

@@ -42,6 +42,7 @@
#pragma mark Calculation and Drawing Methods #pragma mark Calculation and Drawing Methods
- (CTLineRef)createLineForString:(NSString *)aString; - (CTLineRef)createLineForString:(NSString *)aString;
- (CTLineRef)createLineForString:(NSString *)aString usingFont:(NSFont *)font;
@property (nonatomic, getter = isFlipped) BOOL flipped; @property (nonatomic, getter = isFlipped) BOOL flipped;
@property (nonatomic) BOOL usesSmallSize; @property (nonatomic) BOOL usesSmallSize;

View File

@@ -136,9 +136,16 @@
#pragma mark Calculation and Drawing Methods #pragma mark Calculation and Drawing Methods
- (CTLineRef)createLineForString:(NSString *)aString - (CTLineRef)createLineForString:(NSString *)aString
{
return [self createLineForString:aString
usingFont:self.font];
}
- (CTLineRef)createLineForString:(NSString *)aString
usingFont:(NSFont *)font
{ {
NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString
attributes:@{NSFontAttributeName: self.font}]; attributes:@{NSFontAttributeName: font}];
CFAttributedStringRef attributedString = CFBridgingRetain(text); CFAttributedStringRef attributedString = CFBridgingRetain(text);
CTLineRef line = CTLineCreateWithAttributedString(attributedString); CTLineRef line = CTLineCreateWithAttributedString(attributedString);
CFRelease(attributedString); // TODO: Is this release appropriate? CFRelease(attributedString); // TODO: Is this release appropriate?

View File

@@ -0,0 +1,15 @@
//
// MPParenthesisFunction.h
// MathPad
//
// Created by Kim Wittenburg on 17.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import <MathKit/MathKit.h>
@interface MPParenthesisFunction : MPFunction
@property (nonatomic, strong) MPExpression *expression;
@end

View File

@@ -0,0 +1,90 @@
//
// MPParenthesisFunction.m
// MathPad
//
// Created by Kim Wittenburg on 17.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPParenthesisFunction.h"
#import "MPExpressionEvaluator.h"
@implementation MPParenthesisFunction
+ (NSString *)localizedFunctionName
{
return NSLocalizedString(@"Parenthesis", @"Name of Parenthesis Function");
}
- (instancetype)init
{
self = [super init];
if (self) {
_expression = [[MPExpression alloc] init];
_expression.parent = self;
}
return self;
}
- (void)setExpression:(MPExpression *)expression
{
_expression.parent = nil;
_expression = expression;
_expression.parent = self;
}
- (NSUInteger)numberOfChildren
{
return 1;
}
- (MPExpression *)childAtIndex:(NSUInteger)index
{
return index == 0 ? self.expression : nil;
}
- (void)setChild:(MPExpression *)child
atIndex:(NSUInteger)index
{
if (index == 0) {
self.expression = child;
}
}
- (NSArray *)children
{
return @[self.expression];
}
- (MPTerm *)parseWithError:(MPParseError *__autoreleasing *)error
{
MPExpressionEvaluator *evaluator = self.expression.evaluator;
return [evaluator parseExpectingVariable:NO
error:error];
}
- (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error
{
return [self.expression evaluateWithError:error];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"(%@)", self.expression.description];
}
- (NSUInteger)hash
{
#warning Unimplemented Method
return [super hash] * self.expression.hash;
}
- (id)copyWithZone:(NSZone *)zone
{
MPParenthesisFunction *copy = [[MPParenthesisFunction allocWithZone:zone] init];
copy.expression = self.expression.copy;
return copy;
}
@end

13
MathPad/MPWhiteView.h Normal file
View File

@@ -0,0 +1,13 @@
//
// MPWhiteView.h
// MathPad
//
// Created by Kim Wittenburg on 28.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface MPWhiteView : NSView
@end

20
MathPad/MPWhiteView.m Normal file
View File

@@ -0,0 +1,20 @@
//
// MPWhiteView.m
// MathPad
//
// Created by Kim Wittenburg on 28.09.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPWhiteView.h"
@implementation MPWhiteView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
[[NSColor whiteColor] set];
NSRectFill(dirtyRect);
}
@end

View File

@@ -7,7 +7,6 @@
// //
#import "NSString+MPExpressionElement.h" #import "NSString+MPExpressionElement.h"
#import "MPElementParser.h"
@implementation NSString (MPExpressionElement) @implementation NSString (MPExpressionElement)