diff --git a/MathPad/Base.lproj/MPDocument.xib b/MathPad/Base.lproj/MPDocument.xib index 7168019..b4b2d0b 100644 --- a/MathPad/Base.lproj/MPDocument.xib +++ b/MathPad/Base.lproj/MPDocument.xib @@ -1,13 +1,13 @@ - + - - + + @@ -16,17 +16,18 @@ - + + - + - + - + @@ -34,22 +35,22 @@ - - + + - - - - + + + + - + diff --git a/MathPad/Images.xcassets/AppIcon.appiconset/icon_128x128.png b/MathPad/Images.xcassets/AppIcon.appiconset/icon_128x128.png index a0e3d6e..a90c346 100644 Binary files a/MathPad/Images.xcassets/AppIcon.appiconset/icon_128x128.png and b/MathPad/Images.xcassets/AppIcon.appiconset/icon_128x128.png differ diff --git a/MathPad/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/MathPad/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png index 736a7da..50499e9 100644 Binary files a/MathPad/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png and b/MathPad/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png differ diff --git a/MathPad/Images.xcassets/AppIcon.appiconset/icon_16x16.png b/MathPad/Images.xcassets/AppIcon.appiconset/icon_16x16.png index 2cc4576..bd89585 100644 Binary files a/MathPad/Images.xcassets/AppIcon.appiconset/icon_16x16.png and b/MathPad/Images.xcassets/AppIcon.appiconset/icon_16x16.png differ diff --git a/MathPad/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png b/MathPad/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png index e5937a6..589ddb7 100644 Binary files a/MathPad/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png and b/MathPad/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png differ diff --git a/MathPad/Images.xcassets/AppIcon.appiconset/icon_256x256.png b/MathPad/Images.xcassets/AppIcon.appiconset/icon_256x256.png index 736a7da..50499e9 100644 Binary files a/MathPad/Images.xcassets/AppIcon.appiconset/icon_256x256.png and b/MathPad/Images.xcassets/AppIcon.appiconset/icon_256x256.png differ diff --git a/MathPad/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/MathPad/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png index 3bc2102..618b3fd 100644 Binary files a/MathPad/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png and b/MathPad/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png differ diff --git a/MathPad/Images.xcassets/AppIcon.appiconset/icon_32x32.png b/MathPad/Images.xcassets/AppIcon.appiconset/icon_32x32.png index e5937a6..589ddb7 100644 Binary files a/MathPad/Images.xcassets/AppIcon.appiconset/icon_32x32.png and b/MathPad/Images.xcassets/AppIcon.appiconset/icon_32x32.png differ diff --git a/MathPad/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/MathPad/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png index 9b6e5ca..7d8e182 100644 Binary files a/MathPad/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png and b/MathPad/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png differ diff --git a/MathPad/Images.xcassets/AppIcon.appiconset/icon_512x512.png b/MathPad/Images.xcassets/AppIcon.appiconset/icon_512x512.png index 3bc2102..618b3fd 100644 Binary files a/MathPad/Images.xcassets/AppIcon.appiconset/icon_512x512.png and b/MathPad/Images.xcassets/AppIcon.appiconset/icon_512x512.png differ diff --git a/MathPad/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png b/MathPad/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png index 5d170de..5d9487c 100644 Binary files a/MathPad/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png and b/MathPad/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png differ diff --git a/MathPad/MPExpression.m b/MathPad/MPExpression.m index 9b100fa..7d1ae64 100644 --- a/MathPad/MPExpression.m +++ b/MathPad/MPExpression.m @@ -308,10 +308,7 @@ break; case MPTokenReferenceFrame: - [_elements enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - symbolIndex = NSMaxRange(obj.range); - *stop = idx >= index - 1; - }]; + symbolIndex = [self.tokens[index] range].location; break; } @@ -345,22 +342,24 @@ case MPTokenReferenceFrame: { - NSUInteger totalLength = 0; NSUInteger tokenIndex = 0; - id token; - while (totalLength < symbolIndex) { - token = self.tokens[tokenIndex++]; - totalLength = NSMaxRange(token.range); + while (true) { + id token = self.tokens[tokenIndex++]; + if (NSMaxRange(token.range) < symbolIndex || token.range.location > symbolIndex) { + continue; + } + NSUInteger offsetInToken = index - token.range.location; + if (offsetInToken == token.range.length) { + offsetInToken = 0; + tokenIndex++; + } + if (offset) { + *offset = offsetInToken; + } + return tokenIndex; } - NSUInteger offsetInToken = token.range.length - totalLength + symbolIndex; - if (offsetInToken == token.range.length) { - offsetInToken = 0; - tokenIndex++; - } - if (offset) { - *offset = offsetInToken; - } - return tokenIndex; + // Should never get here + return 0; } } } @@ -525,15 +524,10 @@ - (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error { MPExpressionTree *tree = [self parse]; - if ([tree validate:error]) { - return [tree evaluate]; - } - if (error) { - (*error).pathToExpression = self.indexPath; - } - return nil; + return [tree validate:error] ? [tree evaluate] : nil; } + - (MPExpressionTree *)parse { MPTokenStream *tokenStream = [[MPTokenStream alloc] initWithTokens:self.tokens]; diff --git a/MathPad/MPExpressionView.m b/MathPad/MPExpressionView.m index 077ffa4..96d5f5d 100644 --- a/MathPad/MPExpressionView.m +++ b/MathPad/MPExpressionView.m @@ -12,6 +12,7 @@ #import "MPFunctionLayout.h" #import "MPPowerFunction.h" +#import "MPFractionFunction.h" #import "MPRangePath.h" @@ -91,10 +92,8 @@ - (void)restartCaretTimer { - if (self.caretTimer) { - if ([self.caretTimer isValid]) { - [self.caretTimer invalidate]; - } + if (self.caretTimer && [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; @@ -104,7 +103,11 @@ - (void)updateCaret:(NSTimer *)timer { self.caretVisible = !self.caretVisible; - self.needsDisplay = YES; + NSRect updatedRect = self.selectionRect; + NSPoint expressionOrigin = self.expressionOrigin; + updatedRect.origin.x += expressionOrigin.x; + updatedRect.origin.y += expressionOrigin.y; + [self setNeedsDisplayInRect:updatedRect]; } - (NSRect)selectionRect @@ -576,6 +579,10 @@ [self insertPowerFunction]; return; } + if ([characters isEqualToString:@"/"]) { + [self insertFractionFunction]; + return; + } NSString *decimalSeparator = [NSRegularExpression escapedPatternForString:[[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator]]; NSMutableCharacterSet *allowedCharacters = [NSMutableCharacterSet alphanumericCharacterSet]; @@ -630,6 +637,23 @@ self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], self.selection.length); } +- (void)insertFractionFunction +{ + MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection + referenceFrame:MPSymbolReferenceFrame]; + MPFractionFunction *function = [[MPFractionFunction alloc] init]; + function.nominatorExpression = selectedElementsExpression; + [self.expressionStorage replaceItemsInRangePath:self.selection + referenceFrame:MPSymbolReferenceFrame + withElements:@[function]]; + MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]]; + NSUInteger functionElementIndex = [targetExpression convertIndex:self.selection.location.lastIndex + fromReferenceFrame:MPSymbolReferenceFrame + toReferenceFrame:MPElementReferenceFrame]; + NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex]; + self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:1] indexPathByAddingIndex:0], 0); +} + - (void)insertNewline:(id)sender { if (self.target && self.action) { @@ -900,22 +924,22 @@ [transform concat]; // Draw the error - if (self.error) { - [[NSColor redColor] set]; - NSRect rect = [self.expressionStorage.rootLayout boundingRectForRangePath:self.error.rangePath]; - if (self.error.rangePath.length == 0) { - NSBezierPath *bezierPath = [NSBezierPath bezierPath]; - [bezierPath moveToPoint:rect.origin]; - [bezierPath lineToPoint:NSMakePoint(rect.origin.x - 5, rect.origin.y - 5)]; - [bezierPath moveToPoint:rect.origin]; - [bezierPath lineToPoint:NSMakePoint(rect.origin.x + 5, rect.origin.y - 5)]; - bezierPath.lineWidth = 2.0; - [bezierPath stroke]; - } else { - NSRect underlineRect = NSMakeRect(rect.origin.x, rect.origin.y + 2, rect.size.width, 2); - NSRectFill(underlineRect); - } - } +// if (self.error) { +// [[NSColor redColor] set]; +// NSRect rect = [self.expressionStorage.rootLayout boundingRectForRangePath:self.error.rangePath]; +// if (self.error.rangePath.length == 0) { +// NSBezierPath *bezierPath = [NSBezierPath bezierPath]; +// [bezierPath moveToPoint:rect.origin]; +// [bezierPath lineToPoint:NSMakePoint(rect.origin.x - 5, rect.origin.y - 5)]; +// [bezierPath moveToPoint:rect.origin]; +// [bezierPath lineToPoint:NSMakePoint(rect.origin.x + 5, rect.origin.y - 5)]; +// bezierPath.lineWidth = 2.0; +// [bezierPath stroke]; +// } else { +// NSRect underlineRect = NSMakeRect(rect.origin.x, rect.origin.y + 2, rect.size.width, 2); +// NSRectFill(underlineRect); +// } +// } // Draw the selection if (self.caretVisible || self.selection.length > 0) { diff --git a/MathPad/MPFractionFunctionLayout.m b/MathPad/MPFractionFunctionLayout.m index e66e7ac..270e9d9 100644 --- a/MathPad/MPFractionFunctionLayout.m +++ b/MathPad/MPFractionFunctionLayout.m @@ -9,12 +9,12 @@ #import "MPFractionFunctionLayout.h" #import "MPExpressionLayout.h" -#define kFractionFunctionLineWidth 2 -#define kFractionFunctionHorizontalInset 2 +#define kFractionFunctionLineWidth 1.0 +#define kFractionFunctionHorizontalInset 2.0 #define kFractionFunctionNominatorOffset 0 #define kFractionFunctionDenominatorOffset 0 -#define MPFractionMiddle (CTFontGetAscent((CTFontRef)self.font) / 2) +#define MPFractionMiddle (CTFontGetCapHeight((CTFontRef)self.font) / 2) @implementation MPFractionFunctionLayout @@ -41,13 +41,14 @@ - (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index { if (index == 0) { - CGFloat y = MPFractionMiddle + kFractionFunctionLineWidth / 2; - return NSMakePoint(0, y); + NSRect nominatorBounds = [self childLayoutAtIndex:0].bounds; + CGFloat y = MPFractionMiddle + kFractionFunctionLineWidth / 2 - nominatorBounds.origin.y; + return NSMakePoint(kFractionFunctionHorizontalInset, y); } else { NSRect denominatorBounds = [self childLayoutAtIndex:1].bounds; - CGFloat y = CTFontGetAscent((CTFontRef)self.font) / 2 - kFractionFunctionLineWidth / 2; - y -= denominatorBounds.size.height - denominatorBounds.origin.y; - return NSMakePoint(0, y); + CGFloat y = MPFractionMiddle - kFractionFunctionLineWidth / 2; + y -= denominatorBounds.size.height + denominatorBounds.origin.y; + return NSMakePoint(kFractionFunctionHorizontalInset, y); } } @@ -71,16 +72,15 @@ NSRect denominatorBounds = [self childLayoutAtIndex:1].bounds; NSRect bounds; bounds.origin.x = 0; - CGFloat y = CTFontGetAscent((CTFontRef)self.font) / 2 - kFractionFunctionLineWidth / 2; - y -= denominatorBounds.size.height; - bounds.size.width = MAX(nominatorBounds.size.width, denominatorBounds.size.width); + bounds.origin.y = MPFractionMiddle - kFractionFunctionLineWidth / 2 - denominatorBounds.size.height; + bounds.size.width = MAX(nominatorBounds.size.width, denominatorBounds.size.width) + 2 * kFractionFunctionHorizontalInset; bounds.size.height = nominatorBounds.size.height + denominatorBounds.size.height + kFractionFunctionLineWidth; return bounds; } - (void)draw { - CGFloat y = CTFontGetAscent((CTFontRef)self.font) / 2 - kFractionFunctionLineWidth / 2; + CGFloat y = MPFractionMiddle - kFractionFunctionLineWidth / 2; NSRectFill(NSMakeRect(0, y, self.bounds.size.width, kFractionFunctionLineWidth)); } diff --git a/MathPad/MPLayout.h b/MathPad/MPLayout.h index 93c149f..04486f9 100644 --- a/MathPad/MPLayout.h +++ b/MathPad/MPLayout.h @@ -9,8 +9,6 @@ @import Cocoa; #import "MPExpressionStorage.h" -#define MPNull [NSNull null] - @interface MPLayout : NSObject #pragma mark Creation Methods @@ -42,6 +40,7 @@ #pragma mark Calculation and Drawing Methods - (CTLineRef)createLineForString:(NSString *)aString; +- (CTLineRef)createLineForString:(NSString *)aString emphasize:(BOOL)emphasize; - (CTLineRef)createLineForString:(NSString *)aString usingFont:(NSFont *)font; @property (nonatomic, getter = isFlipped) BOOL flipped; diff --git a/MathPad/MPLayout.m b/MathPad/MPLayout.m index 1f4c174..27612f4 100644 --- a/MathPad/MPLayout.m +++ b/MathPad/MPLayout.m @@ -84,7 +84,7 @@ if (index >= _cache.count) { return NO; } - return _cache[index] != MPNull; + return _cache[index] != [NSNull null]; } - (id)cachableObjectForIndex:(NSUInteger)index @@ -102,7 +102,7 @@ - (void)ensureCacheSizeForIndex:(NSUInteger)index { while (index >= _cache.count) { - [_cache addObject:MPNull]; + [_cache addObject:[NSNull null]]; } } @@ -112,7 +112,7 @@ { NSMutableArray *placeholders = [[NSMutableArray alloc] initWithCapacity:replacementLength]; while (placeholders.count < replacementLength) { - [placeholders addObject:MPNull]; + [placeholders addObject:[NSNull null]]; } [_cache replaceObjectsInRange:range withObjectsFromArray:placeholders]; @@ -141,6 +141,12 @@ usingFont:self.font]; } +- (CTLineRef)createLineForString:(NSString *)aString emphasize:(BOOL)emphasize +{ + return [self createLineForString:aString + usingFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]]; +} + - (CTLineRef)createLineForString:(NSString *)aString usingFont:(NSFont *)font { diff --git a/MathPad/MPParenthesisFunctionLayout.m b/MathPad/MPParenthesisFunctionLayout.m index 7373550..5c0816a 100644 --- a/MathPad/MPParenthesisFunctionLayout.m +++ b/MathPad/MPParenthesisFunctionLayout.m @@ -101,17 +101,23 @@ } - (NSAffineTransform *)parenthesisTransform +{ + NSAffineTransform *transform = [NSAffineTransform transform]; + [transform scaleXBy:1 + yBy:self.scaleFactor]; + return transform; +} + +- (CGFloat)scaleFactor { NSRect parensBounds = self.openingParens.bounds; NSRect expressionBounds = [self childLayoutAtIndex:0].bounds; - NSAffineTransform *transform = [NSAffineTransform transform]; - if (expressionBounds.size.height >= parensBounds.size.height) { - return transform; + CGFloat parensHeight = parensBounds.size.height; + CGFloat expressionHeight = expressionBounds.size.height; + if (expressionHeight <= parensHeight) { + return 1.0; } - CGFloat scaleFactor = expressionBounds.size.height / parensBounds.size.height; - [transform scaleXBy:1 - yBy:scaleFactor]; - return transform; + return expressionHeight / parensHeight; } - (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index @@ -154,24 +160,18 @@ return bounds; } -#warning Let the children be drawn automatically -- (BOOL)drawsChildrenManually -{ - return YES; -} - - (void)draw { NSBezierPath *openingParens = self.transformedOpeningParens; MPLayout *expressionLayout = [self childLayoutAtIndex:0]; NSBezierPath *closingParens = self.transformedClosingParens; + CGFloat x = 0; [openingParens fill]; x += openingParens.bounds.size.width; x += MPParenthesisFunctionOpeningParensOffset; - [expressionLayout drawAtPoint:NSMakePoint(x, 0)]; x += expressionLayout.bounds.size.width; x += MPParenthesisFunctionClosingParensOffset; diff --git a/MathPad/MPPowerFunction.m b/MathPad/MPPowerFunction.m index 1b261ee..1af59ac 100644 --- a/MathPad/MPPowerFunction.m +++ b/MathPad/MPPowerFunction.m @@ -35,6 +35,10 @@ MPFunctionAccessorImplementation(ExponentExpression, _exponentExpression) { NSDecimalNumber *base = [self.baseValue evaluate]; NSDecimalNumber *exponent = [[self.exponentExpression parse] evaluate]; + if ([base isEqualToNumber:@(0)] && [exponent isEqualToNumber:@(0)]) { + // The C pow function returns 1 for pow(0, 0). Mathematically this should be undefined. + return [NSDecimalNumber notANumber]; + } return [[NSDecimalNumber alloc] initWithDouble:pow(base.doubleValue, exponent.doubleValue)]; } diff --git a/MathPad/MPProduct.m b/MathPad/MPProduct.m index c54cb49..62959ad 100644 --- a/MathPad/MPProduct.m +++ b/MathPad/MPProduct.m @@ -46,11 +46,18 @@ - (BOOL)validate:(MPParseError *__autoreleasing *)error { + if (_factors.count == 0) { + if (error) { + *error = MPParseError(_range, @"Expected Value"); + } + return NO; + } MPFactor *factor = _factors[0]; if (factor.hasMultiplicationSymbol) { if (error) { *error = MPParseError(factor.multiplicationSymbolRange, @"Unexpected Symbol."); } + return NO; } if (![factor.value validate:error]) { return NO; diff --git a/MathPad/MPValueGroup.m b/MathPad/MPValueGroup.m index 75ca213..a0301b9 100644 --- a/MathPad/MPValueGroup.m +++ b/MathPad/MPValueGroup.m @@ -68,7 +68,6 @@ case MPACosToken: case MPATanToken: { - [tokenStream currentTokenConsumed]; currentValue = [[MPFunctionValue alloc] initWithTokenStream:tokenStream]; } break;