diff --git a/MathPad/MPExpressionLayout.m b/MathPad/MPExpressionLayout.m index 47a3220..98da14c 100644 --- a/MathPad/MPExpressionLayout.m +++ b/MathPad/MPExpressionLayout.m @@ -13,6 +13,12 @@ #import "NSIndexPath+MPAdditions.h" #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) @@ -86,7 +92,7 @@ - (NSRect)generateBounds { 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; for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) { @@ -211,10 +217,23 @@ (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; 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) { 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 stroke]; return; @@ -238,10 +257,16 @@ // Move to the appropriate position 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 CTLineDraw(line, context); - + CFRelease(line); } else { // Let the child layout draw itself diff --git a/MathPad/MPExpressionStorage.m b/MathPad/MPExpressionStorage.m index 872b8ff..b1f3f05 100644 --- a/MathPad/MPExpressionStorage.m +++ b/MathPad/MPExpressionStorage.m @@ -50,6 +50,7 @@ [current clearCacheInRange:rangePath.rangeAtLastIndex replacementLength:replacementLength]; [self.expressionView invalidateIntrinsicContentSize]; + self.expressionView.error = nil; self.expressionView.needsDisplay = YES; } diff --git a/MathPad/MPExpressionView.h b/MathPad/MPExpressionView.h index eb68fe0..99ccf13 100644 --- a/MathPad/MPExpressionView.h +++ b/MathPad/MPExpressionView.h @@ -10,6 +10,8 @@ #import +#import "MPParseError.h" + @class MPExpressionView, MPExpressionStorage, MPExpressionLayout, MPRangePath; @interface MPExpressionView : NSView @@ -23,7 +25,10 @@ @property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage; // @property (nonatomic, getter = isEditable) BOOL editable; +@property (nonatomic) BOOL allowsIntelligentReplacements; + @property (nonatomic, strong) MPRangePath *selection; +@property (nonatomic, strong) MPParseError *error; @property (nonatomic, weak) id target; @property (nonatomic) SEL action; diff --git a/MathPad/MPExpressionView.m b/MathPad/MPExpressionView.m index 8390cee..e3647d5 100644 --- a/MathPad/MPExpressionView.m +++ b/MathPad/MPExpressionView.m @@ -16,12 +16,17 @@ #import "NSIndexPath+MPAdditions.h" #import "MPSumFunction.h" +#import "MPParenthesisFunction.h" +#import "MPParenthesisFunctionLayout.h" +#import "MPFunctionsViewController.h" @interface MPExpressionView () @property (nonatomic, weak) NSButton *functionsButton; +@property (nonatomic, strong) NSPopover *functionsPopover; +@property (nonatomic, strong) MPFunctionsViewController *functionsViewController; @property (nonatomic, strong) NSTimer *caretTimer; @property (nonatomic) NSTimeInterval caretBlinkRate; @@ -303,7 +308,7 @@ - (void)initializeExpressionView { // 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 = expressionStorage; @@ -347,18 +352,40 @@ self.needsDisplay = YES; } +- (void)setError:(MPParseError *)error +{ + _error = error; + self.needsDisplay = YES; +} + #pragma mark Actions - (void)showFunctions:(id)sender { - NSViewController *controller = [[NSViewController alloc] initWithNibName:nil - bundle:nil]; - controller.view = [[NSView alloc] init]; - NSPopover *popover = [[NSPopover alloc] init]; - popover.contentSize = NSMakeSize(100.0, 100.0); - popover.contentViewController = controller; - popover.animates = YES; - popover.behavior = NSPopoverBehaviorSemitransient; - [popover showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxYEdge]; + if (self.functionsPopover == nil || self.functionsViewController == nil) { + self.functionsViewController = [[MPFunctionsViewController alloc] init]; + self.functionsViewController.target = self; + self.functionsViewController.action = @selector(insertFunction:); + self.functionsPopover = [[NSPopover alloc] init]; + self.functionsPopover.contentViewController = self.functionsViewController; + self.functionsPopover.animates = YES; + self.functionsPopover.behavior = NSPopoverBehaviorSemitransient; + } + 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 @@ -372,6 +399,20 @@ 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 { return NO; @@ -415,17 +456,40 @@ - (void)keyDown:(NSEvent *)theEvent { NSString *characters = theEvent.characters; + + if ([characters isEqualToString:@"("]) { + [self insertParenthesisFunction]; + return; + } + + + + NSString *decimalSeparator = [NSRegularExpression escapedPatternForString:[[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator]]; NSMutableCharacterSet *allowedCharacters = [NSMutableCharacterSet alphanumericCharacterSet]; - [allowedCharacters addCharactersInString:@"+-*= "]; + [allowedCharacters addCharactersInString:[NSString stringWithFormat:@"+-*= %@", decimalSeparator]]; + 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.expressionStorage replaceSymbolsInRangePath:self.selection withElements:@[characters]]; self.selection = MPMakeRangePath([self.selection.location indexPathByIncrementingLastIndex], 0); } else { [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 { if (self.target && self.action) { @@ -571,6 +635,30 @@ 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 { NSIndexPath *location = [NSIndexPath indexPathWithIndex:0]; @@ -579,13 +667,42 @@ - (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.expressionStorage replaceSymbolsInRangePath:self.selection 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); + MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]]; + id 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) NSPoint expressionOrigin = self.expressionOrigin; + NSAffineTransform *transform = [NSAffineTransform transform]; + [transform translateXBy:expressionOrigin.x + yBy:expressionOrigin.y]; + [transform concat]; + // Draw the selection if (self.caretVisible || self.selection.length > 0) { if (self.selection.length == 0) { @@ -633,15 +755,12 @@ } else { [[NSColor selectedTextBackgroundColor] set]; } - NSAffineTransform *transform = [NSAffineTransform transform]; - [transform translateXBy:expressionOrigin.x - yBy:expressionOrigin.y]; - [transform concat]; NSRectFill([self selectionRect]); - [transform invert]; - [transform concat]; } + [transform invert]; + [transform concat]; + // Draw the expression [[NSColor textColor] set]; [self.expressionStorage.rootLayout drawAtPoint:expressionOrigin]; diff --git a/MathPad/MPFunctionLayout.h b/MathPad/MPFunctionLayout.h index 3768f59..5698448 100644 --- a/MathPad/MPFunctionLayout.h +++ b/MathPad/MPFunctionLayout.h @@ -26,6 +26,8 @@ #pragma mark Cache Methods - (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index generator:(CTLineRef (^)())generator; +- (id)objectForPrivateCacheIndex:(NSUInteger)index + generator:(id (^)())generator; // Should also implement accessor method for special function type: // - (MPCustomFunction *)customFunction @@ -34,10 +36,12 @@ // } #pragma mark Size and Drawing Methods +- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index; // May 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 -- (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 - (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. - (NSUInteger)indexOfChildBeforeChildAtIndex:(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 \ No newline at end of file diff --git a/MathPad/MPFunctionLayout.m b/MathPad/MPFunctionLayout.m index 3484a26..08ef102 100644 --- a/MathPad/MPFunctionLayout.m +++ b/MathPad/MPFunctionLayout.m @@ -12,6 +12,10 @@ #import "MPSumFunction.h" #import "MPSumFunctionLayout.h" +#import "MPParenthesisFunction.h" +#import "MPParenthesisFunctionLayout.h" + +#import "NSIndexPath+MPAdditions.h" @implementation MPFunctionLayout @@ -22,6 +26,8 @@ Class class = [function class]; if (class == [MPSumFunction class]) { return [[MPSumFunctionLayout alloc] initWithFunction:function parent:parent]; + } else if (class == [MPParenthesisFunction class]) { + return [[MPParenthesisFunctionLayout alloc] initWithFunction:function parent:parent]; } return [[self alloc] initWithFunction:function parent:parent]; @@ -51,6 +57,13 @@ 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 { return [self cachableObjectForIndex:index generator:^id{ @@ -58,11 +71,36 @@ MPExpressionLayout *layout = [[MPExpressionLayout alloc] initWithExpression:child parent:self]; layout.flipped = self.flipped; - layout.usesSmallSize = (index == 0 || index == 1) ? YES : self.usesSmallSize; + layout.usesSmallSize = self.usesSmallSize ? YES : [self childAtIndexUsesSmallSize:index]; 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 { return NSNotFound; @@ -73,4 +111,19 @@ return NSNotFound; } +- (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index +{ + return index; +} + +- (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index +{ + return index; +} + +- (NSIndexSet *)indexesOfRemainingChildren +{ + return [NSIndexSet indexSet]; +} + @end diff --git a/MathPad/MPFunctionsViewController.h b/MathPad/MPFunctionsViewController.h new file mode 100644 index 0000000..a72c1a2 --- /dev/null +++ b/MathPad/MPFunctionsViewController.h @@ -0,0 +1,24 @@ +// +// MPFunctionsViewController.h +// MathPad +// +// Created by Kim Wittenburg on 28.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import + +@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 diff --git a/MathPad/MPFunctionsViewController.m b/MathPad/MPFunctionsViewController.m new file mode 100644 index 0000000..0cefe0e --- /dev/null +++ b/MathPad/MPFunctionsViewController.m @@ -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 diff --git a/MathPad/MPFunctionsViewController.xib b/MathPad/MPFunctionsViewController.xib new file mode 100644 index 0000000..9f5f5c0 --- /dev/null +++ b/MathPad/MPFunctionsViewController.xib @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Choose an Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MathPad/MPLayout.h b/MathPad/MPLayout.h index 0eff489..3f07d49 100644 --- a/MathPad/MPLayout.h +++ b/MathPad/MPLayout.h @@ -42,6 +42,7 @@ #pragma mark Calculation and Drawing Methods - (CTLineRef)createLineForString:(NSString *)aString; +- (CTLineRef)createLineForString:(NSString *)aString usingFont:(NSFont *)font; @property (nonatomic, getter = isFlipped) BOOL flipped; @property (nonatomic) BOOL usesSmallSize; diff --git a/MathPad/MPLayout.m b/MathPad/MPLayout.m index 3904f62..ecc7746 100644 --- a/MathPad/MPLayout.m +++ b/MathPad/MPLayout.m @@ -136,9 +136,16 @@ #pragma mark Calculation and Drawing Methods - (CTLineRef)createLineForString:(NSString *)aString +{ + return [self createLineForString:aString + usingFont:self.font]; +} + +- (CTLineRef)createLineForString:(NSString *)aString + usingFont:(NSFont *)font { NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString - attributes:@{NSFontAttributeName: self.font}]; + attributes:@{NSFontAttributeName: font}]; CFAttributedStringRef attributedString = CFBridgingRetain(text); CTLineRef line = CTLineCreateWithAttributedString(attributedString); CFRelease(attributedString); // TODO: Is this release appropriate? diff --git a/MathPad/MPParenthesisFunction.h b/MathPad/MPParenthesisFunction.h new file mode 100644 index 0000000..31dcfae --- /dev/null +++ b/MathPad/MPParenthesisFunction.h @@ -0,0 +1,15 @@ +// +// MPParenthesisFunction.h +// MathPad +// +// Created by Kim Wittenburg on 17.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import + +@interface MPParenthesisFunction : MPFunction + +@property (nonatomic, strong) MPExpression *expression; + +@end diff --git a/MathPad/MPParenthesisFunction.m b/MathPad/MPParenthesisFunction.m new file mode 100644 index 0000000..c1596fb --- /dev/null +++ b/MathPad/MPParenthesisFunction.m @@ -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 diff --git a/MathPad/MPWhiteView.h b/MathPad/MPWhiteView.h new file mode 100644 index 0000000..01c3d4a --- /dev/null +++ b/MathPad/MPWhiteView.h @@ -0,0 +1,13 @@ +// +// MPWhiteView.h +// MathPad +// +// Created by Kim Wittenburg on 28.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import + +@interface MPWhiteView : NSView + +@end diff --git a/MathPad/MPWhiteView.m b/MathPad/MPWhiteView.m new file mode 100644 index 0000000..2923a9c --- /dev/null +++ b/MathPad/MPWhiteView.m @@ -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 diff --git a/MathPad/NSString+MPExpressionElement.m b/MathPad/NSString+MPExpressionElement.m index ba86447..a8f5214 100644 --- a/MathPad/NSString+MPExpressionElement.m +++ b/MathPad/NSString+MPExpressionElement.m @@ -7,7 +7,6 @@ // #import "NSString+MPExpressionElement.h" -#import "MPElementParser.h" @implementation NSString (MPExpressionElement)