Archived
1

Added Lots of Documentation

Added some nice to haves
Improved and Unified General Code Layout
This commit is contained in:
Kim Wittenburg
2015-01-04 02:54:27 +01:00
parent 152b981e24
commit 7438fd1f95
83 changed files with 2282 additions and 416 deletions

View File

@@ -9,15 +9,63 @@
#import "MPTerm.h"
/*!
@header
This file contains the <code>MPElementaryFunction</code> class.
*/
@class MPElementaryFunctionTerm;
/*!
@class MPElementaryFunctionTerm
@abstract A <code>MPElementaryFunction</code> implements various
mathematical functions that have a valid text representation.
*/
@interface MPElementaryFunctionTerm : MPTerm
/*!
@method initWithFunctionIdentifier:term:
@abstract Initializes a function term using the specified textual
representation of the function and a term as its value.
@param function
The textual representation of the function to use. Valid values
are <code>"sin"</code>, <code>"cos"</code>, <code>"tan"</code>,
<code>"asin"</code>, <code>"arcsin"</code>, <code>"acos"</code>,
<code>"arccos"</code>, <code>"atan"</code>,
<code>"arctan"</code>, <code>"log"</code>, <code>"lg"</code> and
<code>"ln"</code>.
@param term
The term that should be evaluated using <code>function</code>.
@return A new <code>MPElementaryFunctionTerm</code> instance.
*/
- (instancetype)initWithFunctionIdentifier:(NSString *)function
term:(MPTerm *)term; /* designated initializer */
/*!
@property functionIdentifier
@abstract The identifier of the receiver.
@discussion The identifier of a function is the string of letters it is
mathematically defined of (e.g. <code>"sin"</code> for the sine
function).
*/
@property (readonly, nonatomic, copy) NSString *functionIdentifier;
/*!
@property term
@abstract The receiver's term.
@discussion Depending on the actual function the value of the
<code>term</code> may be converted from radians into degrees.
*/
@property (readonly, nonatomic, strong) MPTerm *term;
@end

View File

@@ -9,13 +9,10 @@
#import "MPElementaryFunctionTerm.h"
#import "MPParsedExpression.h"
#import "MPPowerFunction.h"
#import "MPProductTerm.h"
#import "MPToken.h"
#import "MPExpression.h"
#import "MPMathRules.h"
@implementation MPElementaryFunctionTerm {
NSDecimalNumber *(^_function)(NSDecimalNumber *);
}
@@ -53,16 +50,18 @@
} else if ([function isEqualToString:@"ln"]) {
func = &log;
} else {
NSAssert(true, @"function must be one of (sin, cos, tan, asin, acos, atan, lg, log, ln).");
NSAssert(true, @"function must be one of (sin, cos, tan, asin, arcsin, acos, arccos, atan, arctan, lg, log, ln).");
}
[self setFunction:func
takesArcValue:takesArc
returnsArcValue:returnsArc];
_term = term;
_functionIdentifier = function.copy;
}
return self;
}
- (void)setFunction:(double (*)(double))function
takesArcValue:(BOOL)takesArc
returnsArcValue:(BOOL)returnsArc
@@ -80,6 +79,7 @@
};
}
- (NSDecimalNumber *)convertToRadiantsIfNecessary:(NSDecimalNumber *)degrees
{
if ([MPMathRules sharedRules].isUsingDegrees) {
@@ -90,6 +90,7 @@
}
}
- (NSDecimalNumber *)convertToDegreesIfNecessary:(NSDecimalNumber *)radiants
{
if ([MPMathRules sharedRules].isUsingDegrees) {
@@ -100,13 +101,24 @@
}
}
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
NSDecimalNumber *value = [self.term evaluate:error];
if (!value) {
return nil;
}
return _function(value);
NSDecimalNumber *result = _function(value);
if ([result isEqualToNumber:[NSDecimalNumber notANumber]]) {
result = nil;
if (error) {
NSString *errorDescription = [NSString stringWithFormat:NSLocalizedString(@"%@ is not defined for the value %@.", nil), self.functionIdentifier, [value descriptionWithLocale:[NSLocale currentLocale]]];
*error = [NSError errorWithDomain:MPMathKitErrorDomain
code:102
userInfo:@{NSLocalizedDescriptionKey: errorDescription}];
}
}
return result;
}
@end

View File

@@ -7,21 +7,117 @@
//
/*!
@header
This file contains the <code>MPEvaluationContext</code> class.
The evaluation context is organized in so called levels. Every level of a
context contains a unique set of variables. Only the highest level is
accessible. Before any lower level can be accessed the higher ones have to be
discarded. A higher level can not define variables that are already defined in
a lower level. Similarly variables can only be undefined if they are defined at
the current level. Discarding (popping) a level always undefines all variables
defined on that level.
*/
@class MPEvaluationContext;
/*!
@class MPEvaluationContext
@abstract The evaluation context maintains a map of variables and
associates them with a certain level.
@discussion Different levels are nested using <code>@link
//apple_ref/occ/instm/MPEvaluationContext/push@/link</code> and
<code>@link
//apple_ref/occ/instm/MPEvaluationContext/pop@/link</code>
messages.
<code>MPEvaluationContext</code> is a singletone class. There can
only exist one instance (the shared instance) at any time.
*/
@interface MPEvaluationContext : NSObject
/*!
@method sharedContext
@abstract Returns the shared evaluation context.
*/
+ (MPEvaluationContext *)sharedContext;
/*!
@method push
@abstract Pushes a new level on context.
*/
- (void)push;
/*!
@method pop
@abstract Pops a level off the context.
@discussion Popping a level also undefines all variables that were defined in
that level.
*/
- (void)pop;
/*!
@method defineVariable:value:
@abstract Defines a variable with the specified value at the current level.
@discussion The variable can only be defined if it has not been defined
previously in a lower level. If the variable has been defined at
the current level its value will be overwritten to the specified
one.
@param variable
The name of the variable. Typically this is a one-letter string.
@param value
The value of the variable.
@return <code>YES</code> if the variable was defined successfully,
<code>NO</code> if it is already defined in a lower level.
*/
- (BOOL)defineVariable:(NSString *)variable
value:(NSDecimalNumber *)value;
/*!
@method undefineVariable:
@abstract Undefines a variable that has previously been defined.
@discussion Variables can only be undefined if they have been defined at the
same level. If the variable to be undefined was defined at a
different level this method does nothing.
Normally there is very little reason to call this method since
all variables defined on one level are automatically undefined
when the level is popped.
@param variable
The variable to be undefined.
*/
- (void)undefineVariable:(NSString *)variable;
/*!
@method valueForVariable:
@abstract Returns the value for the specified variable.
@discussion If the <code>variable</code> has not been defined previously this
method returns <code>nil</code>.
@param variable
The variable whose value is to be retrieved.
@return The value <code>variable</code> was defined with or
<code>nil</code> if it was not defined.
*/
- (NSDecimalNumber *)valueForVariable:(NSString *)variable;
@end

View File

@@ -8,13 +8,21 @@
#import "MPEvaluationContext.h"
@interface MPEvaluationContext ()
@property (nonatomic, strong) NSMutableArray *stack;
@end
@implementation MPEvaluationContext
static MPEvaluationContext *sharedContext;
+ (MPEvaluationContext *)sharedContext
{
if (!sharedContext) {
@@ -23,6 +31,7 @@ static MPEvaluationContext *sharedContext;
return sharedContext;
}
- (instancetype)init
{
if (sharedContext) {
@@ -40,16 +49,19 @@ static MPEvaluationContext *sharedContext;
return self;
}
- (void)push
{
[self.stack addObject:[[NSMutableDictionary alloc] init]];
}
- (void)pop
{
[self.stack removeLastObject];
}
- (BOOL)defineVariable:(NSString *)variable
value:(NSDecimalNumber *)value
{
@@ -62,17 +74,20 @@ static MPEvaluationContext *sharedContext;
return YES;
}
- (void)undefineVariable:(NSString *)variable
{
NSMutableDictionary *currentBindings = self.stack.lastObject;
[currentBindings removeObjectForKey:variable];
}
- (BOOL)isVariableDefined:(NSString *)variable
{
return [self valueForVariable:variable] != nil;
}
- (NSDecimalNumber *)valueForVariable:(NSString *)variable
{
NSUInteger currentIndex = self.stack.count;

View File

@@ -10,6 +10,9 @@
/*!
@header
This file contains the <code>MPExpression</code> class and the
<code>MPExpressionElement</code> protocol.
The <code>MPExpression</code> class is used to represent a mathematical
expression. It is used in the storage layer of the MathKit Expression System. An
instance of the <code>MPExpression</code> class stores all contents related to
@@ -108,6 +111,58 @@
*/
/*!
@const MPIllegalElementException
@abstract Name for an exception that is raised if an invalid element is
added to an expression.
@discussion This exception may be raised during initialization of an
expression or when the expression is mutated. This exception
contains the invalid element in its <code>userInfo</code>
dictionary. You can query it using the
<code>MPIllegalElementExceptionElementKey</code> key.
*/
FOUNDATION_EXPORT NSString *const MPIllegalElementException;
/*!
@const MPIllegalElementExceptionElementKey
@abstract Predefined key for an invalid element that caused a
<code>MPIllegalElementException</code> to be raised.
@discussion The invalid element can be of any type. Numbers and structs are
wrapped in an <code>NSNumber</code> or <code>NSValue</code>
instance respectively.
*/
FOUNDATION_EXPORT NSString *const MPIllegalElementExceptionElementKey;
/*!
@typedef MPReferenceFrame
@abstract A reference frame specifies the way an <i>item</i> is to be
interpreted.
@constant MPElementReferenceFrame
Specifies that items should be interpreted as elements.
@constant MPSymbolReferenceFrame
Specifies that items should be interpreted as symbols.
@constant MPTokenReferenceFrame
Specifies that items should be interpreted as tokens.
*/
typedef enum {
MPElementReferenceFrame,
MPSymbolReferenceFrame,
MPTokenReferenceFrame
} MPReferenceFrame;
@class MPExpression, MPFunction, MPRangePath, MPParsedExpression;
@protocol MPExpressionElement, MPToken;
/*!
@protocol MPExpressionElement
@abstract This protocol defines the functionality an element in an
@@ -161,58 +216,6 @@
/*!
@const MPIllegalElementException
@abstract Name for an exception that is raised if an invalid element is
added to an expression.
@discussion This exception may be raised during initialization of an
expression or when the expression is mutated. This exception
contains the invalid element in its <code>userInfo</code>
dictionary. You can query it using the
<code>MPIllegalElementExceptionElementKey</code> key.
*/
FOUNDATION_EXPORT NSString *const MPIllegalElementException;
/*!
@const MPIllegalElementExceptionElementKey
@abstract Predefined key for an invalid element that caused a
<code>MPIllegalElementException</code> to be raised.
@discussion The invalid element can be of any type. Numbers and structs are
wrapped in an <code>NSNumber</code> or <code>NSValue</code>
instance respectively.
*/
FOUNDATION_EXPORT NSString *const MPIllegalElementExceptionElementKey;
/*!
@typedef MPReferenceFrame
@abstract A reference frame specifies the way an <i>item</i> is to be
interpreted.
@constant MPElementReferenceFrame
Specifies that items should be interpreted as elements.
@constant MPSymbolReferenceFrame
Specifies that items should be interpreted as symbols.
@constant MPTokenReferenceFrame
Specifies that items should be interpreted as tokens.
*/
typedef enum {
MPElementReferenceFrame,
MPSymbolReferenceFrame,
MPTokenReferenceFrame
} MPReferenceFrame;
@class MPExpression, MPFunction, MPRangePath, MPParsedExpression;
@protocol MPExpressionElement, MPToken;
/*!
@class MPExpression
@abstract A <code>MPExpression</code> instance represents a mathematical

View File

@@ -21,11 +21,11 @@
#import "NSIndexPath+MPAdditions.h"
NSString *const MPIllegalElementException = @"Illegal Element Exception";
NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptionElementKey";
@interface MPExpression () {
NSMutableArray * _elements;
}
@@ -299,7 +299,6 @@ NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptio
}
#warning If multiple equal expressions exist errors may occur...
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element
{
return [_elements indexOfObject:element];
@@ -656,7 +655,7 @@ NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptio
- (NSString *)description
{
#warning Bad Implementation
#warning Incomplete Implementation
NSMutableString *description = [[NSMutableString alloc] init];
NSUInteger index = 0;
for (id element in _elements) {

View File

@@ -9,14 +9,53 @@
#import "MPLayout.h"
/*!
@header
This file contains the <code>MPExpressionLayout</code> class.
*/
@class MPExpressionLayout, MPExpression, MPFunctionLayout;
/*!
@class MPExpressionLayout
@abstract An expression layout draws an <code>@link
//apple_ref/occ/cl/MPExpression@/link</code>.
@discussion For more information on the layout system see the documentation
on the <code>@link //apple_ref/occ/cl/MPLayout@/link</code>
class.
*/
@interface MPExpressionLayout : MPLayout
/*!
@method initWithExpression:parent:
@abstract Initializes an expression layout with the specified expression
and parent layout.
@discussion To initialize a layout for the root expression of an expression
tree specify <code>nil</code> as the <code>parent</code>.
@param expression
The expression that sould be represented by the receiver. Must
not be <code>ni</code>.
@param parent
The parent layout of the receiver. Specify <code>nil</code> to
initalize a root expression layout.
@return A newly initialized expression layout.
*/
- (instancetype)initWithExpression:(MPExpression *)expression
parent:(MPFunctionLayout *)parent;
/*!
@property expression
@abstract The expression represented by the receiver.
*/
@property (readonly, nonatomic, weak) MPExpression *expression;
@end

View File

@@ -9,21 +9,23 @@
#import "MPExpressionLayout.h"
#import "MPExpression.h"
#import "MPExpression.h"
#import "MPPowerFunction.h"
#import "MPFunction.h"
#import "MPFunctionLayout.h"
#import "MPPowerFunctionLayout.h"
#import "MPToken.h"
#import "NSIndexPath+MPAdditions.h"
@interface MPExpressionLayout (MPLineGeneration)
- (CTLineRef)lineForElementAtIndex:(NSUInteger)index;
@end
@implementation MPExpressionLayout (MPLineGeneration)
- (CTLineRef)lineForElementAtIndex:(NSUInteger)index
@@ -60,9 +62,13 @@
@end
@implementation MPExpressionLayout
# pragma mark Creation Methods
- (instancetype)initWithExpression:(MPExpression *)expression
parent:(MPFunctionLayout *)parent
{
@@ -73,12 +79,16 @@
return self;
}
#pragma mark Cache Methods
- (NSUInteger)numberOfChildren
{
return self.expression.countElements;
}
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index
{
id cachedObject = [self cachableObjectForIndex:index generator:^id{
@@ -95,6 +105,7 @@
return nil;
}
- (NSRect)boundsOfElementAtIndex:(NSUInteger)index
{
id symbol = [self.expression elementAtIndex:index];
@@ -109,7 +120,10 @@
}
}
#pragma mark Drawing Methods
- (NSRect)generateBounds
{
if (self.expression.countElements == 0) {
@@ -125,6 +139,7 @@
return NSMakeRect(x, y, width, height);
}
- (NSRect)boundingRectForRange:(NSRange)range
{
NSUInteger startOffset;
@@ -184,6 +199,7 @@
return NSMakeRect(x, self.bounds.origin.y, width, self.bounds.size.height);
}
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
{
CGFloat x = 0;
@@ -193,6 +209,7 @@
return NSMakePoint(x, 0);
}
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point
{
NSUInteger currentPosition = 0;
@@ -235,11 +252,13 @@
}
}
- (BOOL)drawsChildrenManually
{
return YES;
}
- (void)draw
{
// Get the current context

View File

@@ -9,6 +9,8 @@
/*!
@header
This file contains the <code>MPExpressionParser</code> class.
The <code>MPExpressionParser</code> converts a <code>@link
MPExpression@/link</code> instance into a <code>@link
//apple_ref/occ/cl/MPParsedExpression@/link</code> instance. The rules that are

View File

@@ -8,25 +8,27 @@
#import "MPExpressionParser.h"
#import "MPExpression.h"
#import "MPTerm.h"
#import "MPToken.h"
#import "MPExpression.h"
#import "MPParsedExpression.h"
#import "MPTerm.h"
#import "MPSumTerm.h"
#import "MPProductTerm.h"
#import "MPFactorialTerm.h"
#import "MPElementaryFunctionTerm.h"
#import "MPFunctionTerm.h"
#import "MPPowerTerm.h"
#import "MPParsedExpression.h"
#import "MPNegatedTerm.h"
#import "MPFunctionTerm.h"
#import "MPElementaryFunctionTerm.h"
#import "MPNumber.h"
#import "MPVariable.h"
#import "MPFactorialTerm.h"
#import "MPPowerTerm.h"
#define success() state = 0
#define fail() self.errorTokenIndex = self.currentTokenIndex; state = -1
@interface MPExpressionParser ()
@property (nonatomic, strong) NSArray *tokens;
@@ -46,6 +48,8 @@
@end
@implementation MPExpressionParser
- (instancetype)initWithExpression:(MPExpression *)expression
@@ -58,12 +62,14 @@
return self;
}
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors
{
return [self parseExpectingVariableDefinition:NO
errors:errors];
}
- (MPParsedExpression *)parseExpectingVariableDefinition:(BOOL)flag
errors:(NSArray *__autoreleasing *)errors
{
@@ -120,6 +126,7 @@
return result;
}
- (void)runParserMachine
{
NSInteger state = 1;
@@ -251,6 +258,7 @@
}
}
- (void)skipWhitespaces
{
while ([self currentToken] != nil && [self currentToken].tokenType == MPWhitespaceToken) {
@@ -258,6 +266,7 @@
}
}
- (id<MPToken>)currentToken
{
if (self.currentTokenIndex >= self.tokens.count) {
@@ -266,11 +275,13 @@
return self.tokens[self.currentTokenIndex];
}
- (void)nextToken
{
self.currentTokenIndex++;
}
- (NSMutableArray *)errors
{
if (!_errors) {
@@ -279,6 +290,7 @@
return _errors;
}
- (NSMutableArray *)summands
{
if (!_summands) {
@@ -287,6 +299,7 @@
return _summands;
}
- (NSMutableArray *)factors
{
if (!_factors) {
@@ -295,6 +308,7 @@
return _factors;
}
- (NSMutableArray *)functionStack
{
if (!_functionStack) {
@@ -303,6 +317,7 @@
return _functionStack;
}
- (NSMutableArray *)valueGroup
{
if (!_valueGroup) {
@@ -311,6 +326,7 @@
return _valueGroup;
}
- (BOOL)parseOperatorList:(id<MPToken>)token // Returns YES if list is overall negative
{
NSString *operatorString = [[token.stringValue stringByReplacingOccurrencesOfString:@" " withString:@""]
@@ -318,17 +334,20 @@
return (operatorString.length & 1) == 1;
}
- (NSDecimalNumber *)parseNumber:(id<MPToken>)token
{
return [NSDecimalNumber decimalNumberWithString:token.stringValue
locale:[NSLocale currentLocale]];
}
- (NSString *)parseVariable:(id<MPToken>)token
{
return token.stringValue;
}
- (void)collapseSummand
{
if (self.factors.count > 0) {
@@ -342,6 +361,7 @@
self.summandNegative = NO;
}
- (void)collapseFactor
{
if (self.valueGroup.count > 0) {
@@ -360,6 +380,7 @@
self.factorNegative = NO;
}
- (void)runSuffixMachine
{
BOOL checkMore = YES;
@@ -388,6 +409,7 @@
}
}
- (void)addErrorWithCode:(NSInteger)code
localizedDescription:(NSString *)description
useRange:(BOOL)flag
@@ -408,6 +430,7 @@
MPErrorRangeKey: [NSValue valueWithRange:errorRange]}]];
}
- (BOOL)errorOccured
{
[self skipWhitespaces];

View File

@@ -9,13 +9,51 @@
#import "MPExpression.h"
/*!
@header
This file contains the <code>MPExpressionStorage</code> class.
*/
@class MPExpressionStorage, MPExpressionView, MPExpressionLayout;
/*!
@class MPExpressionStorage
@abstract An expression storage manages the contents of an expression view
and notifies it when the underlying expression changes.
*/
@interface MPExpressionStorage : MPExpression
@property (nonatomic, weak) MPExpressionView *expressionView; // Do not set
/*!
@property expressionView
@abstract The receiver's expression view.
@discussion The expression view is the view that displays the contents of the
expression storage. Normally you should not call the setter of
this property. It is automatically assigned when the <code>@link
//apple_ref/occ/instp/MPExpressionView/expressionStorage@/link</code>
property of the <code>@link
//apple_ref/occ/cl/MPExpressionView@/link</code> class is set.
When this property is set the receiver assumes that the
displaying expression view has changed. It then adapts to the
properties of the new expression view and discards the root
layout.
*/
@property (nonatomic, weak) MPExpressionView *expressionView;
/*!
@property rootLayout
@abstract The receiver's root layout.
@discussion The root layout is the root node of the layout tree that draws
the receiver's contents. For more information see the
documentation on the <code>@link
//apple_ref/occ/cl/MPLayout@/link</code> class.
*/
@property (nonatomic, strong) MPExpressionLayout *rootLayout;
@end

View File

@@ -9,17 +9,22 @@
#import "MPExpressionStorage.h"
#import "MPExpressionView.h"
#import "MPLayout.h"
#import "MPExpressionLayout.h"
#import "MPRangePath.h"
@interface MPExpressionStorage ()
@property (nonatomic, strong) NSLayoutManager *layoutManager;
@property (nonatomic, strong) NSTextContainer *textContainer;
@property (nonatomic, strong) NSTextStorage *textStorage;
@end
@implementation MPExpressionStorage
- (instancetype)initWithElements:(NSArray *)elements
@@ -31,6 +36,7 @@
return self;
}
- (void)setExpressionView:(MPExpressionView *)expressionView
{
_expressionView = expressionView;
@@ -38,6 +44,7 @@
self.rootLayout.flipped = expressionView.flipped;
}
- (void)changedElementsInRangePath:(MPRangePath *)rangePath
replacementLength:(NSUInteger)replacementLength
{

View File

@@ -7,6 +7,12 @@
//
/*!
@header
This file contains the <code>MPExpressionTokenizer</code> class.
*/
@class MPExpressionTokenizer, MPExpression;

View File

@@ -52,7 +52,7 @@
@"((?:\\d+%@(?!\\d+))|(?:(?:\\d*%@){2,}\\d*)|%@(?!\\d+))|" // Substitute with decimal separator 3 times
@"((?:\\d+(?:%@\\d+)?)|(?:%@\\d+))|" // Substitute with decimal separator 2 times
@"(sin|cos|tan|asin|arcsin|acos|arccos|atan|arctan|lg|log|ln)|"
@"([A-Za-z])|"
@"([A-Za-zπ])|"
@"(!)|"
@"(=)|"
@"(\\s+)"

View File

@@ -6,68 +6,195 @@
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
// TODO: Undo/Redo + Delegate (evaluateExpressionView:evaluate...)
/*!
@header
This file contains the <code>MPExpressionView</code> class.
<h2>The MathKit Expression System</h2>
MathKit contains class es that make up the so called <i>expression system</i>.
The expression system is divided into three layers: the model, the view and the
controller layer. The <code>MPExpressionView</code> class represents the view
layer and interacts with the Cocoa <code>NSView</code> system. The model is
represented by the classes <code>@link
//apple_ref/occ/cl/MPExpression@/link</code> and <code>@link
//apple_ref/occ/cl/MPFunction@/link</code>. The controller layer between the two
consists of the class <code>@link //apple_ref/occ/cl/MPLayout@/link</code> and
its subclasses. The purpose of the expression system is presenting expressions
to the user and offering possibilities to manipulate and work with expressions.
*/
@class MPExpressionView, MPExpressionStorage, MPFunction, MPRangePath;
//@protocol MPExpressionViewDelegate;
/*!
@class MPExpressionView
@abstract The <code>MPExpressionView</code> class is the front-end class of
the MathKit expression system.
@discussion The class draws the expression managed by the the back-end class
<code>@link //apple_ref/occ/cl/MPExpressionStorage@/link</code>
and is the interface between Application Kit's view system and
the MathKit expression system.
*/
@interface MPExpressionView : NSView <NSUserInterfaceValidations>
#pragma mark Creation Methods
/*!
@methodgroup Creation Methods
*/
/*!
@method initWithFrame:
@abstract Initializes a <code>MPExpressionView</code> instance.
@discussion This method sets up all layers of the expression system including
an empty expression storage.
This method is the designated initializer of the
<code>MPExpressionView</code> class.
@param frameRect
The frame rectangle for the created view.
@return An initialized <code>MPExpressionView</code> or <code>nil</code>
if the object could not be created.
*/
- (id)initWithFrame:(NSRect)frameRect;
#pragma mark Properties
/*!
@methodgroup Properties
*/
@property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage;
//@property (nonatomic, weak) id<MPExpressionViewDelegate> delegate;
/*!
@property expressionStorage
@abstract The receiver's expression storage.
@discussion The expression storage maintains the contents of the receiver.
User interactions change the underlying data. If the expression
storage or the underlying expression change the expression
storage updates the receiver.
*/
@property (nonatomic, strong) MPExpressionStorage *expressionStorage;
/*!
@property selection
@abstract The receiver's selection.
*/
@property (nonatomic, strong) MPRangePath *selection;
/*!
@property mathError
@abstract If set the receiver will display the localized description of the
specified error.
@discussion There can only be one math error at a time. This property should
not be set simutaneously with the <code>@link
//apple_ref/occ/instp/MPExpressionView/syntaxErrors@/link</code>.
*/
@property (nonatomic, strong) NSError *mathError;
/*!
@property syntaxErrors
@abstract If set the receiver will display a dropdown list of syntax
errors.
@discussion The array must only contain <code>NSError</code> instances. In
addition to that every instance must contain a valid
<code>NSIndexPath</code> for the <code>@link
//apple_ref/c/data/MPPathToExpressionKey@/link</code> in its
<code>userDict</code> acompanied by a <code>NSRange</code>
wrapped in a <code>NSValue</code> instance for the <code>@link
//apple_ref/c/data/MPErrorRangeKey@/link</code>.
If the user selects an item from the dropdown list the part of
the expression displayed by the receiver will be selected that is
specified by the values in the corresponding <code>NSError</code>
instance.
*/
@property (nonatomic, strong) NSArray *syntaxErrors;
/*!
@property target
@abstract The target object to receive action messages from the receiver.
*/
@property (nonatomic, weak) id target;
/*!
@property action
@abstract The receiver's action method to the specified selector.
@discussion The action method is invoked when the user presses <i>enter</i>
in the receiver. Typically this is understood as evaluation
request.
*/
@property (nonatomic) SEL action;
//@property (nonatomic) BOOL editable;
//@property (nonatomic) BOOL selectable;
#pragma mark Actions
// Radians - Degrees
/*!
@methodgroup Actions
*/
/*!
@method switchToRadians:
@abstract Tells the receiver that it should switch the interpretation of
trigonometric function values to radians.
@param sender
Typically the object that invoked the method.
*/
- (IBAction)switchToRadians:(id)sender;
/*!
@method switchToDegrees:
@abstract Tells the receiver that it should switch the interpretation of
trigonometric function values to degrees.
@param sender
Typically the object that invoked the method.
*/
- (IBAction)switchToDegrees:(id)sender;
/*!
@method switchRadiansDegrees:
@abstract Tells the receiver that it should switch the interpretation of
trigonometric function values.
@discussion If the interpretation is currently degrees it is changed to
radians or vice versa.
@param sender
Typically the object that invoked the method.
*/
- (IBAction)switchRadiansDegrees:(id)sender;
// Functions
/*!
@method toggleFunctionsPopover:
@abstract Tells the receiver to toggle the visibility of the functions
popover.
@discussion From the functions popover the user can select and insert
functions into the displayed expression
@param sender
Typically the object that invoked the method.
*/
- (IBAction)toggleFunctionsPopover:(id)sender;
@end
//@protocol MPExpressionViewDelegate <NSObject>
//@optional
//
//#pragma mark Editing
//- (MPRangePath *)expressionView:(MPExpressionView *)expressionView willChangeSelectionFromRangePath:(MPRangePath *)oldSelection toRangePath:(MPRangePath *)newSelection;
//- (void)expressionView:(MPExpressionView *)expressionView didChangeSelectionFromRangePath:(MPRangePath *)oldSelection toRangePath:(MPRangePath *)newSelection;
//
//- (BOOL)expressionView:(MPExpressionView *)expressionView shouldChangeSymbolsInRangePath:(MPRangePath *)rangePath replacementElements:(NSArray *)replacement;
//- (BOOL)expressionView:(MPExpressionView *)expressionView shouldInsertFunction:(MPFunction *)function replacingRangePath:(MPRangePath *)currentSelection;
//- (void)expressionViewDidChange:(MPExpressionView *)expressionView;
//
//- (BOOL)expressionViewShouldBeginEditing:(MPExpressionView *)expressionView;
//- (BOOL)expressionViewShouldEndEditing:(MPExpressionView *)expressionView;
//- (void)expressionViewDidBeginEditing:(MPExpressionView *)expressionView;
//- (void)expressionDidEndEditing:(MPExpressionView *)expressionView;
//
//// TODO: Errors...
//#pragma mark Evaluation
//
//- (BOOL)expressionViewShouldEvaluate:(MPExpressionView *)expressionView;
//- (void)expressionViewDidEvaluate:(MPExpressionView *)expressionView;
//
//@end

View File

@@ -8,26 +8,25 @@
#import "MPExpressionView.h"
#import "MPExpression.h"
#import "MPExpression.h"
#import "MPParsedExpression.h"
#import "MPPowerFunction.h"
#import "MPParenthesisFunction.h"
#import "MPFractionFunction.h"
#import "MPToken.h"
#import "MPRangePath.h"
#import "MPMathRules.h"
#import "MPExpressionStorage.h"
#import "MPExpressionLayout.h"
#import "MPFunctionLayout.h"
#import "MPFunctionsViewController.h"
#import "MPFunctionLayout.h"
#import "MPFractionFunction.h"
#import "MPParenthesisFunction.h"
#import "MPPowerFunction.h"
#import "MPParsedExpression.h"
#import "MPMathRules.h"
#import "MPToken.h"
#import "MPRangePath.h"
#import "NSIndexPath+MPAdditions.h"
@interface MPExpressionView ()
@property (nonatomic, strong) NSButton *functionsButton;
@@ -58,6 +57,7 @@
@interface MPExpressionView (MPSelectionHelper)
- (void)restartCaretTimer;
- (void)updateCaret:(NSTimer *)timer;
@@ -101,6 +101,7 @@
[self updateCaret:self.caretTimer];
}
- (void)updateCaret:(NSTimer *)timer
{
self.caretVisible = !self.caretVisible;
@@ -111,6 +112,7 @@
[self setNeedsDisplayInRect:updatedRect];
}
- (NSRect)selectionRect
{
NSRect selectionRect = [self.expressionStorage.rootLayout boundingRectForRangePath:self.selection];
@@ -120,6 +122,7 @@
return selectionRect;
}
- (NSIndexPath *)selectionToTheRightOf:(NSIndexPath *)selectionPath
byExtendingSelection:(BOOL)extendingSelection
selectWords:(BOOL)selectWords
@@ -186,6 +189,7 @@
return [selectionPath indexPathByReplacingLastIndexWithIndex:locationInTarget];
}
- (NSIndexPath *)selectionToTheLeftOf:(NSIndexPath *)selectionPath
byExtendingSelection:(BOOL)extendingSelection
selectWords:(BOOL)selectWords
@@ -257,6 +261,7 @@
return [selectionPath indexPathByReplacingLastIndexWithIndex:locationInTarget];
}
- (MPRangePath *)rangePathEnclosingAnchorPath:(NSIndexPath *)anchorPath
newSelectionPath:(NSIndexPath *)newSelectionPath
{
@@ -312,9 +317,13 @@
#pragma mark -
@implementation MPExpressionView
#pragma mark Creation Methods
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
@@ -324,6 +333,7 @@
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
@@ -333,6 +343,7 @@
return self;
}
- (void)initializeExpressionView
{
// Setup the Expression Storage
@@ -348,6 +359,7 @@
[self restartCaretTimer];
}
- (void)initializeButtons
{
NSButton *functionsButton = self.functionsButton;
@@ -368,6 +380,7 @@
constant:0]];
}
- (void)initializeErrorsViews
{
@@ -394,34 +407,42 @@
views:variableBindings]];
}
#pragma mark - NSView Configuration
- (BOOL)acceptsFirstResponder
{
return YES;
}
- (BOOL)canBecomeKeyView
{
return YES;
}
- (BOOL)isOpaque
{
return YES;
}
- (void)resetCursorRects
{
[self addCursorRect:self.bounds
cursor:[NSCursor IBeamCursor]];
}
- (BOOL)becomeFirstResponder
{
[self restartCaretTimer];
return [super becomeFirstResponder];
}
- (BOOL)resignFirstResponder
{
[self.caretTimer invalidate];
@@ -430,12 +451,14 @@
return [super resignFirstResponder];
}
- (void)setFrame:(NSRect)frameRect
{
[self setNeedsLayout:YES];
[super setFrame:frameRect];
}
- (NSSize)intrinsicContentSize
{
NSSize size = self.expressionStorage.rootLayout.bounds.size;
@@ -443,7 +466,10 @@
return size;
}
#pragma mark - Properties
- (void)setExpressionStorage:(MPExpressionStorage *)expressionStorage
{
_expressionStorage.expressionView = nil;
@@ -452,6 +478,7 @@
[self invalidateIntrinsicContentSize];
}
- (void)setSelection:(MPRangePath *)selection
{
_selection = selection;
@@ -459,6 +486,7 @@
self.needsDisplay = YES;
}
- (void)setSyntaxErrors:(NSArray *)syntaxErrors
{
_syntaxErrors = syntaxErrors;
@@ -491,12 +519,14 @@
}
}
- (void)setMathError:(NSError *)mathError
{
_mathError = mathError;
self.mathErrorTextField.stringValue = mathError != nil ? mathError.localizedDescription : @"";
}
- (NSButton *)functionsButton
{
if (!_functionsButton) {
@@ -520,6 +550,7 @@
return _functionsButton;
}
- (NSPopUpButton *)syntaxErrorsPopUpButton
{
if (!_syntaxErrorsPopUpButton) {
@@ -539,6 +570,7 @@
return _syntaxErrorsPopUpButton;
}
- (NSTextField *)mathErrorTextField
{
if (!_mathErrorTextField) {
@@ -555,6 +587,7 @@
return _mathErrorTextField;
}
- (void)didSelectError:(NSPopUpButton *)sender
{
NSError *error = self.syntaxErrors[sender.indexOfSelectedItem-1];
@@ -564,7 +597,10 @@
self.selection = MPMakeRangePath(pathToExpression, errorRange.length);
}
#pragma mark - Mouse Event Handling
- (void)mouseDown:(NSEvent *)theEvent
{
NSPoint pointInView = [self convertPoint:theEvent.locationInWindow
@@ -577,6 +613,7 @@
self.selection = MPMakeRangePath(selectionPath, 0);
}
- (void)mouseDragged:(NSEvent *)theEvent
{
NSPoint pointInView = [self convertPoint:theEvent.locationInWindow
@@ -590,7 +627,10 @@
newSelectionPath:mouseSelectionPath];
}
#pragma mark Key Event Handling
- (void)keyDown:(NSEvent *)theEvent
{
NSString *characters = theEvent.characters;
@@ -617,6 +657,15 @@
[allowedCharacters addCharactersInString:[NSString stringWithFormat:@"+-*= !%@", decimalSeparator]];
if (characters.length == 1 && [characters stringByTrimmingCharactersInSet:allowedCharacters].length == 0) {
if ([characters isEqualTo:@"i"]) {
if (self.selection.location.lastIndex > 0) {
id<MPExpressionElement> symbol = [self.expressionStorage symbolAtIndex:self.selection.location.lastIndex-1];
if ([symbol isEqual:@"p"]) {
self.selection = MPMakeRangePath([self.selection.location indexPathByDecrementingLastIndex], self.selection.length+1);
characters = @"π";
}
}
}
if ([characters isEqualToString:@"*"]) {
characters = @"⋅";
}
@@ -629,22 +678,28 @@
}
}
#pragma mark - Actions
- (void)switchToDegrees:(id)sender
{
[MPMathRules sharedRules].isUsingDegrees = YES;
}
- (void)switchToRadians:(id)sender
{
[MPMathRules sharedRules].isUsingDegrees = NO;
}
- (void)switchRadiansDegrees:(id)sender
{
[MPMathRules sharedRules].isUsingDegrees = ![MPMathRules sharedRules].isUsingDegrees;
}
- (IBAction)toggleFunctionsPopover:(id)sender
{
if (self.functionsPopover == nil || self.functionsViewController == nil) {
@@ -663,6 +718,7 @@
}
}
- (void)insertFunction:(MPFunction *)function
{
[self.functionsPopover close];
@@ -678,8 +734,10 @@
self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:functionLayout.indexOfLeadingChild] indexPathByAddingIndex:0], 0);
}
#pragma mark Editing Actions
- (void)insertParenthesisFunction:(id)sender
{
MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection
@@ -698,6 +756,7 @@
self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], self.selection.length);
}
- (void)insertClosingParenthesis
{
if (self.selection.length > 0) {
@@ -721,6 +780,7 @@
}
}
- (void)insertPowerFunction
{
MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection
@@ -739,6 +799,7 @@
self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], self.selection.length);
}
- (void)insertFractionFunction
{
if (self.selection.length == 0) {
@@ -792,6 +853,7 @@
}
}
- (void)insertNewline:(id)sender
{
if (self.target && self.action) {
@@ -801,6 +863,7 @@
}
}
- (void)deleteBackward:(id)sender
{
if (self.selection.length > 0) {
@@ -853,6 +916,7 @@
}
}
- (void)delete:(id)sender
{
[self.expressionStorage replaceItemsInRangePath:self.selection
@@ -861,8 +925,10 @@
self.selection = MPMakeRangePath(self.selection.location, 0);
}
#pragma mark Selection Actions
- (void)moveRight:(id)sender
{
if (self.selection.length > 0) {
@@ -875,6 +941,7 @@
}
}
- (void)moveLeft:(id)sender
{
if (self.selection.length > 0) {
@@ -887,6 +954,7 @@
}
}
- (void)moveWordRight:(id)sender
{
NSIndexPath *location = self.selection.maxRangePath;
@@ -896,6 +964,7 @@
self.selection = MPMakeRangePath(newSelectionLocation, 0);
}
- (void)moveWordLeft:(id)sender
{
NSIndexPath *location = self.selection.location;
@@ -905,17 +974,20 @@
self.selection = MPMakeRangePath(newSelectionLocation, 0);
}
- (void)moveToBeginningOfLine:(id)sender
{
self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:0], 0);
}
- (void)moveToEndOfLine:(id)sender
{
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:targetExpression.countSymbols], 0);
}
- (void)moveLeftAndModifySelection:(id)sender
{
if (self.selection.length == 0) {
@@ -935,6 +1007,7 @@
self.selection = [self rangePathEnclosingAnchorPath:maxLocation newSelectionPath:location];
}
- (void)moveRightAndModifySelection:(id)sender
{
if (self.selection.length == 0) {
@@ -955,6 +1028,7 @@
self.selection = MPMakeRangePath(location, maxLocation.lastIndex-location.lastIndex);
}
- (void)moveWordRightAndModifySelection:(id)sender
{
if (self.selection.length == 0) {
@@ -977,6 +1051,7 @@
self.selection = MPMakeRangePath(location, maxLocation.lastIndex-location.lastIndex);
}
- (void)moveWordLeftAndModifySelection:(id)sender
{
if (self.selection.length == 0) {
@@ -999,6 +1074,7 @@
self.selection = MPMakeRangePath(location, maxLocation.lastIndex-location.lastIndex);
}
- (void)moveUp:(id)sender
{
NSIndexPath *targetExpressionPath = [self.selection.location indexPathByRemovingLastIndex];
@@ -1011,6 +1087,7 @@
}
}
- (void)moveDown:(id)sender
{
NSIndexPath *targetExpressionPath = [self.selection.location indexPathByRemovingLastIndex];
@@ -1023,13 +1100,17 @@
}
}
- (void)selectAll:(id)sender
{
NSIndexPath *location = [NSIndexPath indexPathWithIndex:0];
self.selection = MPMakeRangePath(location, self.expressionStorage.countSymbols);
}
#pragma mark Drawing Methods
- (void)drawRect:(NSRect)dirtyRect
{
// Draw the background
@@ -1066,6 +1147,7 @@
#pragma mark - User Interface Validations
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)anItem
{
BOOL degrees = [MPMathRules sharedRules].isUsingDegrees;

View File

@@ -9,6 +9,12 @@
#import "MPTerm.h"
/*!
@header
This file contains the <code>MPFactorialTerm</code> class.
*/
@class MPFactorialTerm;

View File

@@ -8,6 +8,8 @@
#import "MPFactorialTerm.h"
@implementation MPFactorialTerm
- (instancetype)initWithTerm:(MPTerm *)term
@@ -20,6 +22,7 @@
return self;
}
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
NSDecimalNumber *value = [self.term evaluate:error];

View File

@@ -9,6 +9,12 @@
#import "MPFunction.h"
/*!
@header
This file contains the <code>MPFractionFunction</code> class.
*/
@class MPFractionFunction, MPExpression;

View File

@@ -11,21 +11,26 @@
#import "MPFractionTerm.h"
#import "MPExpression.h"
@implementation MPFractionFunction
MPFunctionAccessorImplementation(NominatorExpression, _nominatorExpression)
MPFunctionAccessorImplementation(DenominatorExpression, _denominatorExpression)
- (NSArray *)childrenAccessors
{
return @[@"nominatorExpression", @"denominatorExpression"];
}
- (Class)functionTermClass
{
return [MPFractionTerm class];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"%@ / %@", self.nominatorExpression, self.denominatorExpression];

View File

@@ -9,12 +9,32 @@
#import "MPFunctionLayout.h"
/*!
@header
This file contains the <code>MPFractionFunctionLayout</code> class.
*/
@class MPFractionFunctionLayout, MPFractionFunction;
/*!
@class MPFractionFunctionLayout
@abstract A fraction function layout displays a <code>@link
//apple_ref/occ/cl/MPFractionFunction@/link</code>.
@discussion The nominator is displayed above the denominator. Between the two
children a horizontal bar is drawn.
*/
@interface MPFractionFunctionLayout : MPFunctionLayout
/*!
@method fractionFunction
@abstract Returns the <code>@link
//apple_ref/occ/cl/MPFractionFunction@/link</code> represented by
the receiver.
*/
- (MPFractionFunction *)fractionFunction;
@end

View File

@@ -10,6 +10,7 @@
#import "MPFractionFunction.h"
#define kFractionFunctionLineWidth 1.0
#define kFractionFunctionHorizontalInset 2.0
#define kFractionFunctionNominatorOffset 0
@@ -17,6 +18,8 @@
#define MPFractionMiddle (CTFontGetCapHeight((CTFontRef)self.font) / 2)
@implementation MPFractionFunctionLayout
- (MPFractionFunction *)fractionFunction
@@ -24,21 +27,25 @@
return (MPFractionFunction *)self.function;
}
- (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index
{
return 1;
}
- (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index
{
return 0;
}
- (NSIndexSet *)indexesOfRemainingChildren
{
return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)];
}
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
{
NSRect bounds = self.bounds;
@@ -54,11 +61,13 @@
}
}
- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index
{
return YES;
}
- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point
{
if (point.x < self.bounds.size.width / 2) {
@@ -68,6 +77,7 @@
}
}
- (NSRect)generateBounds
{
NSRect nominatorBounds = [self childLayoutAtIndex:0].bounds;
@@ -80,6 +90,7 @@
return bounds;
}
- (void)draw
{
CGFloat y = MPFractionMiddle - kFractionFunctionLineWidth / 2;

View File

@@ -9,10 +9,24 @@
#import "MPFunctionTerm.h"
/*!
@header
This file contains the <code>MPFractionTerm</code> class.
*/
@class MPFractionTerm;
/*!
@class MPFractionTerm
@abstract Represens a <code>@link
//apple_ref/occ/cl/MPFractionFunction@/link</code>.
@discussion A fraction is evaluating by dividing the nominator by the
denominator.
*/
@interface MPFractionTerm : MPFunctionTerm
@end

View File

@@ -10,6 +10,8 @@
#import "MPParsedExpression.h"
@implementation MPFractionTerm
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error

View File

@@ -10,6 +10,12 @@
#import "MPToken.h"
/*!
@header
This file contains the <code>MPFunction(MPToken) category</code>.
*/
/*!
@category MPFunction (MPToken)

View File

@@ -11,6 +11,8 @@
/*!
@header
This file contains the <code>MPFunction</code> class.
<code>MPFunction</code> is a half abstract class that is designed to be
subclassed. Subclasses of the <code>MPFunction</code> class (subsequently called
<i>functions</i>) need to regard the following guidelines:
@@ -41,7 +43,6 @@
*/
/*!
@define MPFunctionAccessorImplementation
@abstract This macro implements the setter of a previously declared

View File

@@ -18,7 +18,6 @@
@implementation MPFunction
#pragma mark Creation Methods

View File

@@ -9,53 +9,334 @@
#import "MPLayout.h"
/*!
@header
This file contains the <code>MPFunctionLayout</code> class.
The <code>MPFunctionLayout</code> class is an abstract class that implements
many of the methods from the layout system. Concrete subclasses implement the
actual layout of a specific function and its children. For that reason
<code>MPFunctionLayout</code> features a private cache. In that cache any
subclass can store objects so they do not need to be recreated everytime the
function is rendered.
*/
@class MPFunctionLayout, MPFunction, MPExpressionLayout;
/*!
@class MPFunctionLayout
@abstract A function layout represents a <code>@link
//apple_ref/occ/cl/MPFunction@/link</code>.
@discussion <code>MPFunctionLayout</code> is an abstract class that
implements many required methods of the layout system. For more
information about the layout system see the documentation on the
<code>@link //apple_ref/occ/cl/MPLayout@/link</code> class.
*/
@interface MPFunctionLayout : MPLayout
/*!
@method functionLayoutForFunction:parent:
@abstract Creates a function layout that can draw the specified function.
@discussion This method is the preferred whay to construct function layouts.
@param function
The function to create a layout for. Must not be
<code>nil</code>.
@param parent
The parent of the created function layout. May be
<code>nil</code>.
@return A newly created function layout.
*/
+ (MPFunctionLayout *)functionLayoutForFunction:(MPFunction *)function
parent:(MPExpressionLayout *)parent;
@property (readonly, nonatomic, weak) MPFunction *function;
@end
@interface MPFunctionLayout (MPSubclassOverride)
/*!
@method initWithFunction:parent:
@abstract Initializes the receiver with the specified function and parent.
@discussion This method should not be called direcly. It is however possible
do perform custom initialization in sublcasses by overriding this method.
This method is the designated initializer of the
<code>MPFunctionLayout</code> class.
@param function
The function represented by the receiver. Must not be
<code>nil</code>.
@param parent
The parent of the receiver or <code>nil</code> if it does not
have one.
@return A newly initialized <code>MPFunctionLayout</code> instance.
*/
- (instancetype)initWithFunction:(MPFunction *)function
parent:(MPExpressionLayout *)parent;
#pragma mark Cache Methods
/*!
@property function
@abstract The function represented by the receiver.
*/
@property (readonly, nonatomic, weak) MPFunction *function;
/*!
@method lineForPrivateCacheIndex:generator:
@abstract Returns the cached line for the specified private index.
@discussion This is a convenience method that calls <code>@link
//apple_ref/occ/instm/MPFunctionLayout/objectForPrivateCacheIndex:generator:@/link</code>.
See the documentation on that method for details.
@param index
The index of the line to retrieve. Private cache indexes start at <code>0</code>.
@param generator
A block that generates a line in case there is no cached one.
@return The cached line or the newly generated one if there was no cache value.
*/
- (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index
generator:(CTLineRef (^)())generator;
/*!
@method objectForPrivateCacheIndex:generator:
@abstract Returns the cached value for the specified private index.
@discussion If there is no object in the private cache for the specified
index the specified <code>generator</code> is invoked. The result
of the <code>generator</code> is then stored at the specified
<code>index</code> and returned.
@param index
The index of the private cache item to retrieve. Private cache
indexes start at <code>0</code>.
@param generator
A block that generates an object in case there is no cached one.
@return The cached object or the newly generated one if there was no
cache value.
*/
- (id)objectForPrivateCacheIndex:(NSUInteger)index
generator:(id (^)())generator;
// Should also implement accessor method for special function type:
// - (MPCustomFunction *)customFunction
// {
// return (MPCustomFunction *)self.function;
// }
@end
#pragma mark Size and Drawing Methods
/*!
@category MPFunctionLayout (MPSubclassOverride)
@abstract The methods defined in this category must be implemented by any
concrete subclass of <code>MPFunctionLayout</code>.
*/
@interface MPFunctionLayout (MPSubclassOverride)
/* Apart from the methods below it is recommended (although not required) to
* implement a method in the following form:
* - (CustomFunctionType *)customFunction
* {
* return (CustomFunctionType *)self.function;
* }
*/
/*!
@method childAtIndexUsesSmallSize:
@abstract Determines whether the child expression at the specified index
should be drawn using the small font.
@discussion This method may be overridden to change the default behaviour.
@param index
The index of the child whose size is to be determined.
@return <code>YES</code> if the receiver's child at the specified
<code>index</code> should use the small font, <code>NO</code>
otherwise. The default implementation returns <code>NO</code>.
*/
- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index; // May be implemented
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index; // To be implemented
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point;
- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point; // To be implemented
- (NSRect)generateBounds; // 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
- (NSUInteger)indexOfTrailingChild; // To be implemented
// 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
/*!
@method offsetOfChildLayoutAtIndex:
@abstract Implementation requirement from superclass. See <code>@link
//apple_ref/occ/instm/MPLayout(MPSubclassImplement)/offsetOfChildLayoutAtIndex:@/link</code>
for details.
*/
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index;
/*!
@method indexPathForLocalMousePoint:
@abstract Performs hit testing.
@discussion This method is called when the user selected a point in a
function that is not in any of its children. The return value of
this method identifies the position relative to the function
represented by the receiver where the caret is to be placed.
This method must be implemented by subclasses.
@param point
The location the user clicked at relative to the receiver.
@return An index path identifying the new caret position relative to the
function represented by the receiver. Use an index path
containing a single index to place the caret before or after the
represented function (<code>0</code> means before, <code>1</code>
means after).
*/
- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point;
/*!
@method generateBounds
@abstract Implementation requirement from superclass. See <code>@link
//apple_ref/occ/instm/MPLayout(MPSubclassImplement)/generateBounds@/link</code>
for details.
*/
- (NSRect)generateBounds;
/*!
@method draw
@abstract Implementation requirement from superclass. See <code>@link
//apple_ref/occ/instm/MPLayout(MPSubclassImplement)/draw@/link</code>
for details.
*/
- (void)draw;
/*!
@method indexOfLeadingChild
@abstract Identifies the index of the receiver's child that will get
selected when the cursor <i>enters</i> the function in the
direction of reading.
@discussion You may implement this method do override the default behaviour.
@return The index of the leading child. The default implementation
returns <code>0</code>.
*/
- (NSUInteger)indexOfLeadingChild;
/*!
@method indexOfTrailingChild
@abstract Identifies the index of the receiver's child that will get
selected when the cursor <i>enters</i> the function in the
direction opposite to reading.
@discussion You may implement this method do override the default behaviour.
@return The index of the trailing child. The default implementation
returns <code>0</code>.
*/
- (NSUInteger)indexOfTrailingChild;
/*!
@method indexOfChildBeforeChildAtIndex:
@abstract Identifies the index of the receiver's child that will get
selected when the cursor <i>leaves</i> the child at the specified
index in the direction opposite to reading.
@discussion Return <code>NSNotFound</code> from this method if the function
should be <i>left</i> when the child at the specified index is
<i>left</i>.
You may implement this method to override the default behaviour.
@param index
The index of the child the caret <i>left</i>.
@return The index of the child that is to be <i>entered</i> or
<code>NSNotFound</code> if there are no more children. The
default implementation returns <code>NSNotFound</code>.
*/
- (NSUInteger)indexOfChildBeforeChildAtIndex:(NSUInteger)index;
/*!
@method indexOfChildAfterChildAtIndex:
@abstract Identifies the index of the receiver's child that will get
selected when the cursor <i>leaves</i> the child at the specified
index in the direction of reading.
@discussion Return <code>NSNotFound</code> from this method if the function
should be <i>left</i> when the child at the specified index is
<i>left</i>.
You may implement this method to override the default behaviour.
@param index
The index of the child the caret <i>left</i>.
@return The index of the child that is to be <i>entered</i> or
<code>NSNotFound</code> if there are no more children.
*/
- (NSUInteger)indexOfChildAfterChildAtIndex:(NSUInteger)index;
/*!
@method indexOfChildBelowChildAtIndex:
@abstract Returns the index of the child that is graphically positioned
below the child at the specified index.
@discussion If there are no other children below the one at the specified
index this method should return <code>index</code>.
You may implement this method to override the default behaviour.
@param index
The index of the child whose neighbor is to be determined.
@return The index of the child directly below the one at the specified
index. The default implementation returns <code>index</code>.
*/
- (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index;
/*!
@method indexOfChildAboveChildAtIndex:
@abstract Returns the index of the child that is graphically positioned
above the child at the specified index.
@discussion If there are no other children above the one at the specified
index this method should return <code>index</code>.
You may implement this method to override the default behaviour.
@param index
The index of the child whose neighbor is to be determined.
@return The index of the child directly above the one at the specified
index. The default implementation returns <code>index</code>.
*/
- (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index;
/*!
@method indexesOfRemainingChildren
@abstract Returns the indexes of the receiver's children that should
replace the receiver if it is deleted.
@discussion You may implement this method to override the default behaviour.
@return The indexes of the children that replace the receiver when it is
deleted. The default implementation returns an empty index set.
*/
- (NSIndexSet *)indexesOfRemainingChildren;
@end

View File

@@ -8,24 +8,28 @@
#import "MPFunctionLayout.h"
#import "MPExpression.h"
#import "MPExpressionLayout.h"
#import "MPFunction.h"
#import "MPSumFunction.h"
#import "MPFractionFunction.h"
#import "MPParenthesisFunction.h"
#import "MPPowerFunction.h"
#import "MPFractionFunction.h"
#import "MPSumFunction.h"
#import "MPExpressionLayout.h"
#import "MPSumFunctionLayout.h"
#import "MPFractionFunctionLayout.h"
#import "MPParenthesisFunctionLayout.h"
#import "MPPowerFunctionLayout.h"
#import "MPFractionFunctionLayout.h"
#import "MPSumFunctionLayout.h"
#import "NSIndexPath+MPAdditions.h"
@implementation MPFunctionLayout
#pragma mark Creation Methods
+ (MPFunctionLayout *)functionLayoutForFunction:(MPFunction *)function
parent:(MPExpressionLayout *)parent
{
@@ -43,6 +47,7 @@
parent:parent];
}
- (instancetype)initWithFunction:(MPFunction *)function
parent:(MPExpressionLayout *)parent
{
@@ -53,7 +58,9 @@
return self;
}
#pragma mark Cache Methods
- (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index
generator:(CTLineRef (^)())generator
{
@@ -67,6 +74,7 @@
return (__bridge CTLineRef)(lineObject);
}
- (id)objectForPrivateCacheIndex:(NSUInteger)index
generator:(id (^)())generator
{
@@ -74,11 +82,13 @@
return [self cachableObjectForIndex:actualIndex generator:generator];
}
- (NSUInteger)numberOfChildren
{
return self.function.numberOfChildren;
}
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index
{
return [self cachableObjectForIndex:index generator:^id{
@@ -91,11 +101,13 @@
}];
}
- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index
{
return NO;
}
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point
{
// A single index is used to communicate back wether the
@@ -116,36 +128,43 @@
return [self indexPathForLocalMousePoint:point];
}
- (NSUInteger)indexOfLeadingChild
{
return 0;
}
- (NSUInteger)indexOfTrailingChild
{
return 0;
}
- (NSUInteger)indexOfChildBeforeChildAtIndex:(NSUInteger)index
{
return NSNotFound;
}
- (NSUInteger)indexOfChildAfterChildAtIndex:(NSUInteger)index
{
return NSNotFound;
}
- (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index
{
return index;
}
- (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index
{
return index;
}
- (NSIndexSet *)indexesOfRemainingChildren
{
return [NSIndexSet indexSet];

View File

@@ -8,6 +8,28 @@
#import "MPTerm.h"
/*!
@header
This file contains the <code>MPFunctionTerm</code> class.
*/
/*!
@define MPEvaluateExpression
@abstract Evaluates the receiver's child at the specified index and assigns
the evaluation result to a variable.
@discussion If errors occur during evaluation this macro returns
<code>nil</code>. Also there has to exist a <code>NSError *__autoreleasing *</code> pointer called <code>error</code>.
@param var
The name of the variable that will be declared in this macro. It
will be of the type <code>NSDecimalNumber *</code> and contain
the result of the evaluation.
@param index
The index of the child to be evaluated.
*/
#define MPEvaluateExpression(var, index) NSDecimalNumber *var = [[self expressionAtIndex:index] evaluate:error]; if (var == nil) return nil
@@ -15,11 +37,62 @@
@class MPFunctionTerm, MPFunction, MPParsedExpression;
/*!
@class MPFunctionTerm
@abstract A function term represents the term of a generic function.
@discussion This class is an abstract class that can not be evaluated. It is
the common superclass of all terms that represent <code>@link
//apple_ref/occ/cl/MPFunction@/link</code> instances.
*/
@interface MPFunctionTerm : MPTerm
/*!
@method initWithFunction:errors:
@abstract Initializes a <code>MPFunctionTerm</code> from the specified
<code>@link //apple_ref/occ/cl/MPFunction@/link</code> instance.
@discussion This method parses the children of the specified
<code>function</code> and stores the respective parsed
expressions. They are accessible using the <code>@link
//apple_ref/occ/instm/MPFunctionTerm/expressionAtIndex:@/link</code>
method.
Whether the children should contain a variable definition is
determined using the <code>@link
//apple_ref/occ/instm/MPFunction/expectsVariableDefinitionInChildAtIndex:@/link</code>
@param function
The function to initialize the receiver with. This method parses
the function's children. If any of them contain syntax errors
they are returned indirectly through the <code>errors</code>
parameter.
@param errors
If any of the <code>function</code>'s children contain syntax
errors they are returned indirectly through this parameter. In
that case the method returns <code>nil</code>. If there are no
errors the parameter is not modified. This parameter is never set
to an empty array.
@return A new <code>MPFunctionTerm</code> instance.
*/
- (instancetype)initWithFunction:(MPFunction *)function
errors:(NSArray *__autoreleasing *)errors; /* designated initializer */
/*!
@method expressionAtIndex:
@abstract Returns a <code>@link
//apple_ref/occ/cl/MPParsedExpression@/link</code> instance that
represents the child at <code>anIndex</code> of the function that
is represented by the receiver.
@param anIndex
The index of the child.
@return A parsed expression that represents th
*/
- (MPParsedExpression *)expressionAtIndex:(NSUInteger)anIndex;
@end

View File

@@ -9,10 +9,6 @@
#import "MPFunctionTerm.h"
#import "MPFunction.h"
#import "MPExpression.h"
#import "MPParsedExpression.h"
#import "MPToken.h"
@@ -64,6 +60,7 @@
return self;
}
- (MPParsedExpression *)expressionAtIndex:(NSUInteger)anIndex
{
return [self.parsedExpressions objectAtIndex:anIndex];

View File

@@ -7,19 +7,74 @@
//
/*!
@header
This file contains the <code>MPFunctionsViewController</code> class.
*/
@class MPFunctionsViewController, MPFunctionsCollectionView;
/*!
@class MPFunctionsViewController
@abstract Controls a view from which the user can select function to be
inserted into a <code>@link
//apple_ref/occ/cl/MPExpressionView@/link</code>.
@discussion The view contain a <code>NSCollectionView</code> displaying the
prototypes an a <code>NSTextField</code> displaying a description
of the currently selected prototype.
*/
@interface MPFunctionsViewController : NSViewController
- (id)init;
/*!
@property collectionView
@abstract The <code>NSCollectionView</code> that displays the function
prototypes.
*/
@property (weak) IBOutlet NSCollectionView *collectionView;
@property (weak) IBOutlet MPFunctionsCollectionView *collectionView;
/*!
@property functionPrototypes
@abstract Contains the <code>@link
//apple_ref/occ/cl/MPFunction@/link</code> objects that get
inserted into an expression if selected.
@discussion Every object in the array must be an <code>@link
//apple_ref/occ/cl/MPFunction@/link</code> instance.
*/
@property (nonatomic, strong) NSArray *functionPrototypes;
/*!
@property currentDescription
@abstract The string that describes the function prototype that is
currently selected.
@discussion If this string is empty a placeholder value will be displayed.
*/
@property (nonatomic, strong) NSString *currentDescription;
/*!
@property target
@abstract The target object to receive action messages from the receiver.
*/
@property (nonatomic, weak) id target;
@property (nonatomic) SEL action; // 1 argument: The function to insert
/*!
@property action
@abstract The receiver's action method to the specified selector.
@discussion The action method is invoked when the user selects one of the
function prototypes in the collection view. The prototype that
was selected is passed as the one and only argument to
the specified selector.
*/
@property (nonatomic) SEL action;
@end

View File

@@ -8,18 +8,21 @@
#import "MPFunctionsViewController.h"
#import "MPExpression.h"
#import "NSString+MPExpressionElement.h"
#import "MPFunction.h"
#import "MPSumFunction.h"
#import "MPFractionFunction.h"
#import "MPParenthesisFunction.h"
#import "MPPowerFunction.h"
#import "MPFractionFunction.h"
#import "MPSumFunction.h"
#import "MPFunctionLayout.h"
#import "NSString+MPExpressionElement.h"
@class MPFunctionsCollectionView, MPFunctionTemplateView, MPFunctionTemplateItem;
@interface MPFunctionsCollectionView : NSCollectionView
@property (nonatomic, weak) MPFunctionTemplateItem *hoverItem;
@@ -30,6 +33,7 @@
@end
@interface MPFunctionTemplateView : NSView
@property (nonatomic, strong) MPFunction *functionTemplate;
@@ -42,16 +46,20 @@
@interface MPFunctionTemplateItem : NSCollectionViewItem
@property (nonatomic, copy) NSString *templateName;
@end
@implementation MPFunctionsCollectionView
- (void)mouseDown:(NSEvent *)theEvent
{
}
- (void)mouseUp:(NSEvent *)theEvent
{
NSPoint pointInView = [self convertPoint:theEvent.locationInWindow
@@ -87,25 +95,30 @@
[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
{
@@ -117,16 +130,19 @@
return _functionTemplateLayout;
}
- (NSSize)intrinsicContentSize
{
return [self.functionTemplateLayout bounds].size;
}
- (BOOL)isOpaque
{
return NO;
}
- (void)drawRect:(NSRect)dirtyRect
{
if (self.mouseOver) {
@@ -150,11 +166,13 @@
static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMouseOverContext";
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder
{
return [super awakeAfterUsingCoder:aDecoder];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
@@ -171,6 +189,7 @@ static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMo
}
}
- (void)setRepresentedObject:(id)representedObject
{
MPFunctionTemplateView *view = (MPFunctionTemplateView *)self.view;
@@ -191,6 +210,8 @@ static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMo
@end
@implementation MPFunctionsViewController
- (id)init
@@ -199,6 +220,7 @@ static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMo
bundle:[NSBundle bundleForClass:[self class]]];
}
- (void)awakeFromNib
{
MPFunction *sumFunction = [[MPSumFunction alloc] init];
@@ -227,40 +249,45 @@ static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMo
forKeyPath:@"hoverItem"
options:0
context:MPCollectionViewHoverItemChangeContext];
self.collectionView.target = self.target;
self.collectionView.action = self.action;
((MPFunctionsCollectionView *)self.collectionView).target = self.target;
((MPFunctionsCollectionView *)self.collectionView).action = self.action;
}
static void *MPCollectionViewHoverItemChangeContext = @"MPCollectionViewHoverItemChangeContext";
- (void)setView:(NSView *)view
{
[super setView:view];
}
- (void)setTarget:(id)target
{
_target = target;
if (self.collectionView) {
self.collectionView.target = target;
((MPFunctionsCollectionView *)self.collectionView).target = target;
}
}
- (void)setAction:(SEL)action
{
_action = action;
if (self.collectionView) {
self.collectionView.action = action;
((MPFunctionsCollectionView *)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.templateName;
self.currentDescription = ((MPFunctionsCollectionView *)self.collectionView).hoverItem.templateName;
} else {
[super observeValueForKeyPath:keyPath
ofObject:object

View File

@@ -6,12 +6,82 @@
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
/*!
@header
This file contains the <code>MPLayout</code> class.
The <code>MPLayout</code> class renders an expression to be displayed in an
<code>@link //apple_ref/occ/cl/MPExpressionView@/link</code>. There are multiple
subclasses of <code>MPLayout</code> the most important of which are <code>@link
//apple_ref/occ/cl/MPExpressionLayout@/link</code> and <code>@link
//apple_ref/occ/cl/MPFunctionLayout@/link</code> for rendering expressions and
functions respectively.
Both types of layouts can occur in two different sizes: normal and small. The
root expression ueses the normal size. Depending on the concrete subclass of
<code>@link //apple_ref/occ/cl/MPFunctionLayout@/link</code> that is used a
function's children (and any subsequent children) may be rendered using the
small size. Also there are two different fonts available for layouts: a normal
and a special font. The special font should only be used to emphasize special
content that uses the normal font.
If an expression is empty an empty box is rendered instead. That box (called the
<i>empty box</i> uses the font metrics of the empty expression it represents.
That means it uses different sizes depending on whether the expression uses a
small or normal sized font. Also it is drawn a little smaller than the actual
box's height is because it looks nicer. Depending on your needs you can query
the metrics of the empty box using the <code>kMPEmptyBox...</code> or
<code>kMPEmptyBoxDrawing...</code> macros. These macros must not be used outside
of the <code>MPLayout</code> class or any of it subclasses.
*/
/*!
@define kMPEmptyBoxWidth
@abstract The width of the empty box.
*/
#define kMPEmptyBoxWidth (self.usesSmallSize ? 2.0 : 3.0)
/*!
@define kMPEmptyBoxHeight
@abstract The actual height of the empty box.
*/
#define kMPEmptyBoxHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font))
/*!
@define kMPEmptyBoxYOrigin
@abstract The actual vertical origin of the empty box.
@discussion The vertical origin is a negative value to be compliant with the
metrics of strings.
*/
#define kMPEmptyBoxYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)))
/*!
@define kMPEmptyBoxDrawingWidth
@abstract The with the empty box is rendered with.
*/
#define kMPEmptyBoxDrawingWidth kMPEmptyBoxWidth
/*!
@define kMPEmptyBoxDrawingHeight
@abstract The height the empty box is drawn with.
*/
#define kMPEmptyBoxDrawingHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font))
/*!
@define kMPEmptyBoxDrawingYOrigin
@abstract The vertical origin the empty box is drawn at.
@discussion The vertical origin is a negative value to be compliant with the
metrics of strings.
*/
#define kMPEmptyBoxDrawingYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)/2))
@@ -19,58 +89,548 @@
@class MPLayout, MPRangePath;
/*!
@class MPLayout
@abstract This is an abstract class that defines the basic methods that
must be implemented by other layout classes. It also defines some
useful helper methods.
@discussion The <code>MPLayout</code> class maintains a generic cache. It is
used to store child layouts and actual cache information.
Every layout instance represents either an expression or a
function. The instance's children represent the children of the
represented object.
*/
@interface MPLayout : NSObject
#pragma mark Creation Methods
/*!
@methodgroup Creation Methods
*/
/*!
@method init
@abstract Initializes the receiver.
@discussion This method should only be used to initialize the expression
layout that renders the root layout.
This method is the designated initializer of the
<code>MPLayout</code> class.
@return A newly initialized <code>MPLayout</code> instance.
*/
- (instancetype)init;
/*!
@method initWithParent:
@abstract Initializes the receiver with the specified parent.
@discussion This method should generally be used when initializing layout
instances. In most cases it should be called at some point in the
implementation of the <code>@link
//apple_ref/occ/instm/MPLayout(MPSubclassImplement)/childLayoutAtIndex:@/link</code>
method.
@param parent
The receiver's parent.
@return A newly initialized <code>MPLayout</code> instance.
*/
- (instancetype)initWithParent:(MPLayout *)parent;
#pragma mark Properties
- (NSFont *)normalFontWithSize:(CGFloat)size;
- (NSFont *)specialFontWithSize:(CGFloat)size;
- (CGFloat)contextInferredFontSize;
- (CGFloat)normalFontSize;
- (CGFloat)smallFontSize;
#pragma mark Properties
/*!
@methodgroup Properties
*/
/*!
@method font
@abstract Returns the receiver's context inferred font.
@discussion The context inferred font is the normal font using the context
inferred font size. Normally this method is used for any textual
content of a layout.
@return The font the receiver should use for rendering textual content.
*/
- (NSFont *)font;
/*!
@method normalFontWithSize:
@abstract Returns a <code>NSFont</code> object that represents the normal
font in the specified <code>size</code>.
@discussion Instead of this method you want to use <code>@link
//apple_ref/occ/instm/MPLayout/font@/link</code> instead in most
cases.
@param size
The size of the font.
@return A <code>NSFont</code> object that can be used to render textual
content of the receiver in the specified <code>size</code>.
*/
- (NSFont *)normalFontWithSize:(CGFloat)size;
/*!
@method specialFontWithSize:
@abstract Returns a <code>NSFont</code> object that represents the special
font in the specified <code>size</code>.
@discussion Use fonts returned by this method only if you need to emphasize
text that is surrounded by other text that uses the normal font.
@param size
The size of the font.
@return A <code>NSFont</code> object that can be used to render special
textual content of the receiver in the specified
<code>size</code>.
*/
- (NSFont *)specialFontWithSize:(CGFloat)size;
/*!
@method contextInferredFontSize
@abstract Returns the appropriate font size for the receiver based on the
context it will be rendered in.
*/
- (CGFloat)contextInferredFontSize;
/*!
@method normalFontSize
@abstract Returns the font size that is used in the root expression.
*/
- (CGFloat)normalFontSize;
/*!
@method smallFontSize
@abstract Returns the font size that is used in expressions that are using
the small size.
*/
- (CGFloat)smallFontSize;
#pragma mark Cache Tree
/*!
@methodgroup Cache Tree
*/
/*!
@property parent
@abstract The receiver's parent layout.
@discussion The parent layout is most likely the one that created the
receiver and is responsible for setting its attributes
appropriately based on the rendering context.
*/
@property (readonly, nonatomic, weak) MPLayout *parent;
#pragma mark Cache Methods
// Querying Caches
/*!
@methodgroup Cache Methods
*/
/*!
@method chacableObjectForIndex:generator:
@abstract Returns the cached object for the specified <code>index</code> or
creates one if it does not exist.
@discussion This method only returns <code>nil</code> if the
<code>generator</code> returns <code>nil</code>.
@param index
The index of the cached object to retrieve.
@param generator
This block is executed if there is no cached object at
<code>index</code>. The result of this block is then cached at
the <code>index</code>.
@return The cached object for <code>index</code> or the result of the
<code>generator</code> if there is no cached object.
*/
- (id)cachableObjectForIndex:(NSUInteger)index
generator:(id(^)())generator;
// Clearing Caches
/*!
@method clearCacheInRange:replacementLength:
@abstract Removes the objects in the specified from the cache and moves all
objects at subsequent indexes.
@discussion The objects at subsequent indexes are moved
<code>replacementLength - range.length</code> places. If
<code>replacementLength</code> is greater than
<code>range.length</code> they are moved to smaller indexes.
@param range
The range of objects to be removed.
@param replacementLength
The number of indexes replacing the ones specified by
<code>range</code>.
*/
- (void)clearCacheInRange:(NSRange)range
replacementLength:(NSUInteger)replacementLength;
/*!
@method invalidate
@abstract Invalidates the receiver.
@discussion Invalidating the receiver causes him to discard all cached
information about itself and send a <code>invalidate</code>
message to its parent. The cached information about the
receiver's children is <b>not</b> discarded. Use <code>@link
//apple_ref/occ/instm/MPLayout/clearCacheInRange:replacementLength:@/link</code>
for that purpose.
*/
- (void)invalidate;
/*!
@method childLayoutAtIndexPath:
@abstract Returns the child layout at the specified index path.
@discussion If the <code>indexPath</code> is empty the receiver is returned.
If the child layout the index path specifies is not yet created
this method should do so and add it to the cache of the
respective parent layout.
@param indexPath
The index path of the child.
@return The child at the specified index path.
*/
- (MPLayout *)childLayoutAtIndexPath:(NSIndexPath *)indexPath;
#pragma mark Calculation and Drawing Methods
#pragma mark Rendering Methods
/*!
@methodgroup Rendering Methods
*/
/*!
@method createLineForString:
@abstract Renders the specified string.
@discussion This method uses the normal font and the context inferred font
size to render the string. In most cases this is the appropriate
method for rendering textual content in a layout.
@param aString
The string to be rendered.
@return A <code>CTLineRef</code> representing the rendered string.
*/
- (CTLineRef)createLineForString:(NSString *)aString;
/*!
@method createLineForString:emphasize:
@abstract Renders the specified string.
@discussion This method uses either the normal or the special font depending
on the <code>emphasize</code> flag. It uses the context inferred
font size.
@param aString
The string to be rendered.
@param emphasize
A flag indicating whether the special font should be used.
@return A <code>CTLineRef</code> representing the rendered string.
*/
- (CTLineRef)createLineForString:(NSString *)aString emphasize:(BOOL)emphasize;
/*!
@method createLineForString:usingFont:
@abstract Renders the specified string in the specified font.
@discussion In most cases you do should prefer <code>@link
//apple_ref/occ/instm/MPLayout/createLineForString:@/link</code>
or <code>@link
//apple_ref/occ/instm/MPLayout/createLineForString:emphasize:@/link</code>
over this method.
@param aString
The string to be rendered.
@param font
The font to render <code>aString</code> in.
@return A <code>CTLineRef</code> representing the rendered string.
*/
- (CTLineRef)createLineForString:(NSString *)aString usingFont:(NSFont *)font;
/*!
@property flipped
@abstract This property indicates whether the receiver uses a flipped
layout.
@discussion The value of this property should be the same in the receiver,
its parent and all of its children.
*/
@property (nonatomic, getter = isFlipped) BOOL flipped;
/*!
@property usesSmallSize
@abstract This property indicates whether the receiver uses the small font
size.
@discussion This property is used to determine the <code>@link
//apple_ref/occ/instm/MPLayout/contextInferredFontSize@/link</code>.
If this value is <code>YES</code> all of the receiver's children
should also use the small size. If not the children may or may
not (depending on the actual layout implementation) use the small
size.
*/
@property (nonatomic) BOOL usesSmallSize;
/*!
@method bounds
@abstract Returns the receiver's bounds.
@discussion If the receiver has cached bounds it will return the cached
value. The cached value can be removed using the <code>@link
//apple_ref/occ/instm/MPLayout/invalidate@/link</code> method. If
there is no cached value this method will generate a new cached
value by calling the <code>@link
//apple_ref/occ/instm/MPLayout(MPSubclassImplement)/generateBounds@/link</code>
method.
@return The receiver's bounds.
*/
- (NSRect)bounds;
/*!
@method boundingRectForRangePath:
@abstract Returns the rect relative to the origin of the receiver's bounds
that contains the symbols identified by the specified range path.
@discussion Use this method to determine where the user's selection should be
drawn at.
@param rangePath
The range path whose bounds are needed.
@return The rectangle containing the symbols identified by
<code>rangePath</code>.
*/
- (NSRect)boundingRectForRangePath:(MPRangePath *)rangePath; /* if rangePath.length is 0 the returned rect will have a width of 0 */
/*!
@method drawAtPoint:
@abstract Draws the receiver at the specified point.
@discussion If the receiver returns <code>NO</code> from its <code>@link
//apple_ref/occ/instm/MPLayout(MPSubclassImplement)/drawsChildrenManually@/link</code>
method this method also draws every single child of the receiver.
The location the children are drawn at is determined by the
<code>@link
//apple_ref/occ/instm/MPLayout(MPSubclassImplement)/offsetOfChildLayoutAtIndex:@/link</code>
method.
@param point
The point the receiver is to be drawn at.
*/
- (void)drawAtPoint:(NSPoint)point;
@end
/*!
@category MPLayout (MPSubclassImplement)
@abstract The methods in this category must be implemented by any concrete
subclass of <code>MPLayout</code>.
*/
@interface MPLayout (MPSubclassImplement)
/*!
@method numberOfChildren
@abstract Returns the number of sublayouts of the receiver.
@discussion This method must be implemented by subclasses.
*/
- (NSUInteger)numberOfChildren;
/*!
@method drawsChildrenManually
@abstract Returns whether the receiver wants to take over responsibility of
drawing its children.
@discussion If you return <code>YES</code> from this method
<code>MPLayout</code> does not draw the receiver's children. The
receiver's <code>//apple_ref/occ/intfm/MPLayout/draw</code>
method has to implement that functionality. If you return
<code>NO</code> from this method the receiver must not draw its
children in the <code>//apple_ref/occ/intfm/MPLayout/draw</code>
method.
This method may be implemented by subclasses to opt in and change
the default behaviour.
@return <code>YES</code> if the receiver draws its children
automatically, <code>NO</code> otherwise. The default
implementation returns <code>NO</code>.
*/
- (BOOL)drawsChildrenManually;
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index; // To be implemented
- (NSRect)generateBounds; // To be implemented
- (NSRect)boundingRectForRange:(NSRange)range; // To be implemented, use rangePath instead, this one has wrong origin
/*!
@method childLayoutAtIndex:
@abstract Returns the receiver's child at the specified index.
@discussion As long as the specified <code>index</code> does not exceed the
receiver's number of children, this method should always return a
value based on the following criteria:
1. If the receiver represents a <code>@link
//apple_ref/occ/cl/MPFunction@/link</code> instance it should
always return a valid <code>MPLayout</code> instance.
2. If the receiver represents a <code>@link
//apple_ref/occ/cl/MPExpression@/link</code> instance it
returns only a valid <code>MPLayout</code> instance if the
represented expression's element at <code>index</code> is a
function.
This method should cache any generated child layout and use the
cache whenever possible in order to reduce resources needed for
rendering.
This method must be implemented by subclasses.
@param index
The index of the requested child layout.
@return A <code>MPLayout</code> instance representing the respective
child of the object that is represented by the receiver, or
<code>nil</code> if the child represents a string element in an
expression.
*/
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index;
/*!
@method generateBounds
@abstract Calculates the receiever's bounds.
@discussion This method should not use any cached values. Caching of the
calculated bounds is done automatically by the
<code>MPLayout</code> class. This method is only called if the
receiving <code>MPLayout</code> instance has been invalidated and
the receiver's bounds have been requested.
This method must be implemented by subclasses.
@return The receiver's bounds.
*/
- (NSRect)generateBounds;
/*!
@method boundingRectForRange:
@abstract Returns the rectangle that encloses the symbols in the specified
range.
@discussion This method is called to calculate the rectangle that encloses
the user's selection. If the specified <code>range</code> has a
length of <code>0</code> the returned rectangle should be the
bounds of the caret (which has a width of <code>0</code>). The
height and y-origin of the returned rectangle should be equal to
the respective values of the receiver.
This method must be implemented by subclasses.
@param range
The range of the selection whose bounds should be calculated.
@return A rectangle enclosing the user's selection.
*/
- (NSRect)boundingRectForRange:(NSRange)range;
/*!
@method offsetOfChildLayoutAtIndex:
@abstract Returns the location of the child layout at the specified index.
@discussion This method must be implemented by subclasses even if the
subclass returns <code>YES</code> from the <code>@link
//apple_ref/occ/instm/MPLayout(MPSubclassImplement)/drawsChildrenManually@/link</code>
method.
@param index
The index of the child whose location is to be calculated.
@return The location of the child layout at the specified index relative
to the receiver.
*/
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index;
/*!
@method indexPathForMousePoint:
@abstract Performs hit testing.
@discussion This method tests the specified <code>point</code> against all of
its children and returns the index path (relative to the index
path of the receiver) that identifies the location inside the
expression tree the user selected. The specified point must be
inside of the receiver and must be specified relatively to the
receiver. If the specified point is not inside any of the
receiver's children but in the receiver itself the returned index
path must contain one index specifiying the location in the
receiver.
This method must be implemented by subclasses.
@param point
The point inside the receiver that is to be tested.
@return The index path that points to the point the user selected. All
indexes in the path except for the last one are specified in the
element reference frame. The last index is specified in the
symbol reference frame.
*/
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point;
- (void)draw; // To be implemented
/*!
@method draw
@abstract Draws the receiver.
@discussion If the receiver returns <code>YES</code> from its <code>@link
//apple_ref/occ/instm/MPLayout(MPSubclassImplement)/drawsChildrenManually@/link</code>
method the implementation of this method must also draw the
receiver's children.
This method must be implemented by subclasses. If the receiver
does not have anything to draw except for its children you should
implement this method with an empty method body.
*/
- (void)draw;
@end

View File

@@ -11,20 +11,25 @@
#import "MPRangePath.h"
#import "NSIndexPath+MPAdditions.h"
@interface MPLayout ()
// Querying and Storing Caches
- (BOOL)hasCacheForElementAtIndex:(NSUInteger)index;
- (void)ensureCacheSizeForIndex:(NSUInteger)index;
@end
@implementation MPLayout {
NSMutableArray *_cache;
NSRect _cachedBounds;
}
#pragma mark Creation Methods
- (id)init
{
self = [super init];
@@ -35,6 +40,7 @@
return self;
}
- (instancetype)initWithParent:(MPLayout *)parent
{
self = [self init];
@@ -44,41 +50,51 @@
return self;
}
#pragma mark Properties
- (NSFont *)normalFontWithSize:(CGFloat)size
{
return [NSFont fontWithName:@"CMU Serif"
size:size];
}
- (NSFont *)specialFontWithSize:(CGFloat)size
{
return [NSFont fontWithName:@"CMU Serif Italic"
size:size];
}
- (CGFloat)contextInferredFontSize
{
return self.usesSmallSize ? self.smallFontSize : self.normalFontSize;
}
- (CGFloat)normalFontSize
{
return 18.0;
}
- (CGFloat)smallFontSize
{
return 12.0;
}
- (NSFont *)font
{
return [self normalFontWithSize:self.contextInferredFontSize];
}
#pragma mark Cache Tree
// Querying and Storing Caches
- (BOOL)hasCacheForElementAtIndex:(NSUInteger)index
{
if (index >= _cache.count) {
@@ -87,6 +103,7 @@
return _cache[index] != [NSNull null];
}
- (id)cachableObjectForIndex:(NSUInteger)index
generator:(id (^)())generator
{
@@ -99,6 +116,7 @@
return object;
}
- (void)ensureCacheSizeForIndex:(NSUInteger)index
{
while (index >= _cache.count) {
@@ -106,7 +124,7 @@
}
}
// Clearing Caches
- (void)clearCacheInRange:(NSRange)range
replacementLength:(NSUInteger)replacementLength
{
@@ -119,12 +137,14 @@
[self invalidate];
}
- (void)invalidate
{
_cachedBounds = NSZeroRect;
[self.parent invalidate];
}
- (MPLayout *)childLayoutAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.length == 0) {
@@ -134,19 +154,24 @@
return [child childLayoutAtIndexPath:[indexPath indexPathByRemovingFirstIndex]];
}
#pragma mark Calculation and Drawing Methods
- (CTLineRef)createLineForString:(NSString *)aString
{
return [self createLineForString:aString
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
{
@@ -158,6 +183,7 @@
return line;
}
- (NSRect)bounds
{
if (NSEqualRects(_cachedBounds, NSZeroRect)) {
@@ -166,6 +192,7 @@
return _cachedBounds;
}
- (NSRect)boundingRectForRangePath:(MPRangePath *)rangePath
{
if (rangePath.location.length == 1) {
@@ -180,11 +207,13 @@
return bounds;
}
- (BOOL)drawsChildrenManually
{
return NO;
}
- (void)drawAtPoint:(NSPoint)point
{
NSAffineTransform *transform = [NSAffineTransform transform];

View File

@@ -7,25 +7,49 @@
//
/*!
@header
This file contains the <code>MPMathRules</code> class.
*/
FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthKey;
FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthInMultiplicationKey;
/*!
@const MPMathRulesIsUsingDegreesKey
@abstract Predefined key that is used to store the properties of the
<code>MPMathRules</code> class in the <code>NSUserDefaults</code>
database.
*/
FOUNDATION_EXPORT NSString *MPMathRulesIsUsingDegreesKey;
@class MPMathRules;
/*!
@class MPMathRules
@abstract The math rules class stores global settings concerning
mathematical interpretations
@discussion The <code>MPMathRules</code> class is a singletone class. There
can only exist one instance (the shared instance) at any time.
*/
@interface MPMathRules : NSObject
/*!
@method sharedRules
@abstract Returnes the shared <code>MPMathRules</code> instance.
*/
+ (MPMathRules *)sharedRules;
@property (nonatomic, getter = isUsingUserDefaultValues) BOOL usingUserDefaultValues;
// Default value uses the key "..." in NSUserDefaults or ... if the key does not exist.
@property (nonatomic) NSUInteger maximumOperatorChainLength; // +--++-5 -> Chain length: 6, 2 default (sign of number and operator) (0 is invalid?)
@property (nonatomic) NSUInteger maximumOperatorChainLengthInMultiplication; // Default: 1, 0 means actually 0
/*!
@property isUsingDegrees
@abstract Specifies whether trigonometric functions use degree values.
@discussion If set to <code>NO</code> radians are used instead. The default
value is <code>NO</code>.
*/
@property (nonatomic) BOOL isUsingDegrees;
@end

View File

@@ -8,14 +8,16 @@
#import "MPMathRules.h"
NSString *MPMathRulesMaximumOperatorChainLengthKey = @"MPMathRulesMaximumOperatorChainLengthKey";
NSString *MPMathRulesMaximumOperatorChainLengthInMultiplicationKey = @"MPMathRulesMaximumOperatorChainLengthInMultiplicationKey";
NSString *MPMathRulesIsUsingDegreesKey = @"MPMathRulesIsUsingDegreesKey";
@implementation MPMathRules
static MPMathRules *sharedRules;
+ (MPMathRules *)sharedRules
{
if (!sharedRules) {
@@ -24,6 +26,7 @@ static MPMathRules *sharedRules;
return sharedRules;
}
- (instancetype)init
{
if (sharedRules) {
@@ -31,53 +34,21 @@ static MPMathRules *sharedRules;
}
self = [super init];
if (self) {
_usingUserDefaultValues = YES;
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSNumber *userDefaultsMaximumOperatorChainLength = [userDefaults objectForKey:MPMathRulesMaximumOperatorChainLengthKey];
NSNumber *userDefaultsMaximumOperatorChainLengthInMultiplication = [userDefaults objectForKey:MPMathRulesMaximumOperatorChainLengthInMultiplicationKey];
NSNumber *userDefaultsIsUsingDegrees = [userDefaults objectForKey:MPMathRulesIsUsingDegreesKey];
_maximumOperatorChainLength = userDefaultsMaximumOperatorChainLength != nil ? userDefaultsMaximumOperatorChainLength.unsignedIntegerValue : 2;
_maximumOperatorChainLengthInMultiplication = userDefaultsMaximumOperatorChainLengthInMultiplication != nil ? userDefaultsMaximumOperatorChainLengthInMultiplication.unsignedIntegerValue : 1;
_isUsingDegrees = userDefaultsIsUsingDegrees.boolValue;
}
return self;
}
- (void)setUsingUserDefaultValues:(BOOL)usingUserDefaultValues
- (BOOL)isUsingDegrees
{
_usingUserDefaultValues = usingUserDefaultValues;
// Save the current values
self.maximumOperatorChainLength = self.maximumOperatorChainLength;
self.maximumOperatorChainLengthInMultiplication = self.maximumOperatorChainLengthInMultiplication;
self.isUsingDegrees = self.isUsingDegrees;
return [[NSUserDefaults standardUserDefaults] boolForKey:MPMathRulesIsUsingDegreesKey];
}
- (void)setMaximumOperatorChainLength:(NSUInteger)maximumOperatorChainLength
{
_maximumOperatorChainLength = maximumOperatorChainLength;
if (self.isUsingUserDefaultValues) {
[[NSUserDefaults standardUserDefaults] setObject:@(maximumOperatorChainLength)
forKey:MPMathRulesMaximumOperatorChainLengthKey];
}
}
- (void)setMaximumOperatorChainLengthInMultiplication:(NSUInteger)maximumOperatorChainLengthInMultiplication
{
_maximumOperatorChainLengthInMultiplication = maximumOperatorChainLengthInMultiplication;
if (self.isUsingUserDefaultValues) {
[[NSUserDefaults standardUserDefaults] setObject:@(maximumOperatorChainLengthInMultiplication)
forKey:MPMathRulesMaximumOperatorChainLengthInMultiplicationKey];
}
}
- (void)setIsUsingDegrees:(BOOL)isUsingDegrees
{
_isUsingDegrees = isUsingDegrees;
if (self.isUsingUserDefaultValues) {
[[NSUserDefaults standardUserDefaults] setBool:isUsingDegrees
[[NSUserDefaults standardUserDefaults] setBool:isUsingDegrees
forKey:MPMathRulesIsUsingDegreesKey];
}
}
@end

View File

@@ -9,6 +9,12 @@
#import "MPTerm.h"
/*!
@header
This file contains the <code>MPNegatedTerm</code> class.
*/
@class MPNegatedTerm;
@@ -20,7 +26,6 @@
*/
@interface MPNegatedTerm : MPTerm
/*!
@method initWithTerm:
@abstract Initializes a new negated term.
@@ -34,8 +39,8 @@
/*!
@property term
@abstract The receiver's term.
@property term
@abstract The receiver's term.
*/
@property (readonly, nonatomic, strong) MPTerm *term;

View File

@@ -8,6 +8,8 @@
#import "MPNegatedTerm.h"
@implementation MPNegatedTerm
- (instancetype)initWithTerm:(MPTerm *)term
@@ -20,6 +22,7 @@
return self;
}
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
NSDecimalNumber *value = [self.term evaluate:error];

View File

@@ -9,14 +9,44 @@
#import "MPTerm.h"
/*!
@header
This file contains the <code>MPNumber</code> class.
*/
@class MPNumber;
/*!
@class MPNumber
@abstract This class represents a number that evaluates to itself.
@discussion Numbers include integers as well as floating point numbers. They
have to be representable as a decimal number literal (e.g.
<code>3.4</code>). Numbers that have periods or are irrational
are not implemented by this class.
*/
@interface MPNumber : MPTerm
/*!
@method initWithNumber:
@abstract Initializes a number term with the specified <code>number</code>.
@param number
The number that the term should evaluate to. Must not be
<code>nil</code>.
@return A new <code>MPNumberTerm</code> instance.
*/
- (instancetype)initWithNumber:(NSDecimalNumber *)number; /* designated initializer */
/*!
@property number
@abstract The receiver's number.
*/
@property (readonly, nonatomic, strong) NSDecimalNumber *number;
@end

View File

@@ -8,9 +8,7 @@
#import "MPNumber.h"
#import "MPParsedExpression.h"
#import "MPToken.h"
@implementation MPNumber
@@ -24,6 +22,7 @@
return self;
}
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
return self.number;

View File

@@ -9,6 +9,12 @@
#import "MPFunction.h"
/*!
@header
This file contains the <code>MPParenthesisFunction</code> class.
*/
@class MPParenthesisFunction, MPExpression;

View File

@@ -18,16 +18,19 @@
MPFunctionAccessorImplementation(Expression, _expression)
- (NSArray *)childrenAccessors
{
return @[@"expression"];
}
- (Class)functionTermClass
{
return [MPParenthesisTerm class];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"(%@)", self.expression.description];

View File

@@ -9,12 +9,32 @@
#import "MPFunctionLayout.h"
/*!
@header
This file contains the <code>MPParenthesisFunctionLayout</code> class.
*/
@class MPParenthesisFunctionLayout, MPParenthesisFunction;
/*!
@class MPParenthesisFunctionLayout
@abstract A parenthesis function layout displays a <code>@link
//apple_ref/occ/cl/MPParenthesisFunction@/link</code>.
@discussion The child of the parenthesis function is encapsulated by standard
parenthesis that are scaled to the smae height as the child.
*/
@interface MPParenthesisFunctionLayout : MPFunctionLayout
/*!
@method parenthesisFunction
@abstract Returns the <code>@link
//apple_ref/occ/cl/MPParenthesisFunction@/link</code> represented
by the receiver.
*/
- (MPParenthesisFunction *)parenthesisFunction;
@end

View File

@@ -10,9 +10,12 @@
#import "MPParenthesisFunction.h"
#define MPParenthesisFunctionOpeningParensOffset 2
#define MPParenthesisFunctionClosingParensOffset 0
@interface MPParenthesisFunctionLayout ()
- (NSBezierPath *)openingParens;
@@ -25,6 +28,8 @@
@end
@implementation MPParenthesisFunctionLayout
- (MPParenthesisFunction *)parenthesisFunction
@@ -32,6 +37,7 @@
return (MPParenthesisFunction *)self.function;
}
- (NSBezierPath *)openingParens
{
NSBezierPath *parens = [self objectForPrivateCacheIndex:0 generator:^id{
@@ -60,6 +66,7 @@
return parens;
}
- (NSBezierPath *)closingParens
{
NSBezierPath *parens = [self objectForPrivateCacheIndex:1 generator:^id{
@@ -88,6 +95,7 @@
return parens;
}
- (NSBezierPath *)transformedOpeningParens
{
NSBezierPath *parens = self.openingParens.copy;
@@ -95,6 +103,7 @@
return parens;
}
- (NSBezierPath *)transformedClosingParens
{
NSBezierPath *parens = self.closingParens.copy;
@@ -102,6 +111,7 @@
return parens;
}
- (NSAffineTransform *)parenthesisTransform
{
NSAffineTransform *transform = [NSAffineTransform transform];
@@ -110,6 +120,7 @@
return transform;
}
- (CGFloat)scaleFactor
{
NSRect parensBounds = self.openingParens.bounds;
@@ -122,22 +133,25 @@
return expressionHeight / parensHeight;
}
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
{
return NSMakePoint(self.openingParens.bounds.size.width + MPParenthesisFunctionOpeningParensOffset, 0);
}
- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point
{
#warning Missing Implementation
return [NSIndexPath indexPathWithIndex:0];
}
- (NSIndexSet *)indexesOfRemainingChildren
{
return [NSIndexSet indexSetWithIndex:0];
}
- (NSRect)generateBounds
{
NSRect openingParensBounds = self.transformedOpeningParens.bounds;
@@ -159,6 +173,7 @@
return bounds;
}
- (void)draw
{
NSBezierPath *openingParens = self.transformedOpeningParens;

View File

@@ -9,10 +9,24 @@
#import "MPFunctionTerm.h"
/*!
@header
This file contains the <code>MPParenthesisTerm</code> class.
*/
@class MPParenthesisTerm;
/*!
@class MPParenthesisTerm
@abstract Represents a <code>@link
//apple_ref/occ/cl/MPParenthesisFunction@/link</code>.
@discussion A parenthesis function encapsulates a term and thus prioritizes
it in evaluation.
*/
@interface MPParenthesisTerm : MPFunctionTerm
@end

View File

@@ -10,6 +10,8 @@
#import "MPParsedExpression.h"
@implementation MPParenthesisTerm
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error

View File

@@ -7,8 +7,10 @@
//
@class MPParsedExpression, MPTerm;
/*!
@header
This file contains the <code>MPParsedExpression</code> class.
*/
/*!
@@ -50,6 +52,10 @@ FOUNDATION_EXPORT NSString *const MPPathToExpressionKey;
FOUNDATION_EXPORT NSString *const MPErrorRangeKey;
@class MPParsedExpression, MPTerm;
/*!
@class MPParsedExpression
@abstract A parsed expression represents an expression whose syntax is
@@ -93,7 +99,7 @@ FOUNDATION_EXPORT NSString *const MPErrorRangeKey;
@discussion This method is a convenience method for evaluating the receiver's
<code>@link
//apple_ref/occ/intfp/MPParsedExpression/term@/link</code>.
//apple_ref/occ/instp/MPParsedExpression/term@/link</code>.
@param error
If an error occured during evaluation it will be returned

View File

@@ -9,13 +9,15 @@
#import "MPParsedExpression.h"
#import "MPToken.h"
#import "MPTerm.h"
NSString *const MPMathKitErrorDomain = @"MPMathKitErrorDomain";
NSString *const MPPathToExpressionKey = @"MPPathToExpressionKey";
NSString *const MPErrorRangeKey = @"MPErrorRangeKey";
@implementation MPParsedExpression
- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error

View File

@@ -9,6 +9,12 @@
#import "MPFunction.h"
/*!
@header
This file contains the <code>MPPowerFunction</code> class.
*/
@class MPPowerFunction, MPExpression;

View File

@@ -10,15 +10,19 @@
#import "MPExpression.h"
@implementation MPPowerFunction
MPFunctionAccessorImplementation(ExponentExpression, _exponentExpression)
- (NSArray *)childrenAccessors
{
return @[@"exponentExpression"];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"^%@", self.exponentExpression.description];

View File

@@ -9,14 +9,46 @@
#import "MPFunctionLayout.h"
/*!
@header
This file contains the <code>MPPowerFunctionLayout</code> class.
*/
@class MPPowerFunctionLayout, MPPowerFunction;
/*!
@class MPPowerFunctionLayout
@abstract A power function layout displays a <code>@link
//apple_ref/occ/cl/MPPowerFunction@/link</code>.
@discussion A power function layout draws its child at the top right of the
base. Because of this the power function layout has a special
property that gets set during the drawing of an expression:
<code>@link
//apple_ref/occ/instp/MPPowerFunctionLayout/baseBounds@/link</code>.
*/
@interface MPPowerFunctionLayout : MPFunctionLayout
/*!
@property baseBounds
@abstract The bounds of the expression that is the base of the receiving
power function layout.
@discussion This value should be considered very volatile. It may change even
if the power function itself didn't. This value is guaranteed to
stay the same only during a single cycle of drawing.
*/
@property (nonatomic) NSRect baseBounds;
/*!
@method powerFunction
@abstract Returns the <code>@link
//apple_ref/occ/cl/MPPowerFunction@/link</code> represented by
the receiver.
*/
- (MPPowerFunction *)powerFunction;
@end

View File

@@ -10,9 +10,12 @@
#import "MPPowerFunction.h"
#define kPowerFunctionExponentXOffset 1
#define kPowerFunctionTrailingOffset 2
@implementation MPPowerFunctionLayout
- (NSRect)baseBounds
@@ -23,16 +26,19 @@
return _baseBounds;
}
- (MPPowerFunction *)powerFunction
{
return (MPPowerFunction *)self.function;
}
- (NSIndexSet *)indexesOfRemainingChildren
{
return [NSIndexSet indexSetWithIndex:0];
}
#warning Broken Power Layout
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
{
@@ -41,16 +47,19 @@
return NSMakePoint(kPowerFunctionExponentXOffset, y);
}
- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index
{
return YES;
}
- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point
{
return [[NSIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0];
}
- (NSRect)generateBounds
{
NSRect exponentBounds = [self childLayoutAtIndex:0].bounds;
@@ -59,9 +68,8 @@
return NSMakeRect(0, 0, width, height);
}
- (void)draw
{
[[NSBezierPath bezierPathWithRect:self.bounds] stroke];
}
{}
@end

View File

@@ -9,12 +9,33 @@
#import "MPFunctionTerm.h"
/*!
@header
This file contains the <code>MPPowerTerm</code> class.
*/
@class MPPowerTerm, MPTerm;
/*!
@class MPPowerTerm
@abstract Represents a <code>@link
//apple_ref/occ/cl/MPPowerFunction@/link</code>.
@discussion A power function is evaluated using the C <code>pow</code>
function.
*/
@interface MPPowerTerm : MPFunctionTerm
/*!
@property baseTerm
@abstract The base of the power.
@discussion This value is set during the evaluation of the power term and
should be considered very volatile.
*/
@property (nonatomic, strong) MPTerm *baseTerm;
@end

View File

@@ -7,8 +7,11 @@
//
#import "MPPowerTerm.h"
#import "MPParsedExpression.h"
@implementation MPPowerTerm
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error

View File

@@ -9,6 +9,12 @@
#import "MPTerm.h"
/*!
@header
This file contains the <code>MPProductTerm</code> class.
*/
@class MPProductTerm;
@@ -22,7 +28,6 @@
*/
@interface MPProductTerm : MPTerm
/*!
@method initWithFactors:
@abstract Initializes a new product term with the specified

View File

@@ -8,17 +8,7 @@
#import "MPProductTerm.h"
#import "MPParsedExpression.h"
#import "MPPowerFunction.h"
#import "MPFunctionTerm.h"
#import "MPElementaryFunctionTerm.h"
#import "MPNumber.h"
#import "MPFactorialTerm.h"
#import "MPPowerTerm.h"
#import "MPVariable.h"
#import "MPToken.h"
@implementation MPProductTerm
@@ -33,6 +23,7 @@
return self;
}
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
NSDecimalNumber *value = [NSDecimalNumber one];

View File

@@ -11,6 +11,8 @@
/*!
@header
This file contains the <code>MPRangePath</code> class.
The <code>MPRangePath</code> combines an <code>NSIndexPath</code> with a
<code>NSRange</code>. A range path is used in a tree structure to identify a
number of subsequent nodes. This is achieved by combining a range path that
@@ -36,6 +38,7 @@
#define MPMakeRangePath(loc, len) [MPRangePath rangePathWithLocation:(loc) length:(len)]
@class MPRangePath, MPExpression;
@@ -268,7 +271,6 @@
@interface MPExpression (MPRangeExtension)
/*!
@method subexpressionWithRangePath:
@abstract Creates a new expression with the symbols in the specified range

View File

@@ -12,7 +12,6 @@
@implementation MPRangePath
#pragma mark Creation Methods

View File

@@ -9,6 +9,12 @@
#import "MPFunction.h"
/*!
@header
This file contains the <code>MPSumFunction</code> class.
*/
@class MPSumFunction, MPExpression;

View File

@@ -15,7 +15,6 @@
@implementation MPSumFunction
MPFunctionAccessorImplementation(StartExpression, _startExpression)
MPFunctionAccessorImplementation(TargetExpression, _targetExpression)
MPFunctionAccessorImplementation(SumExpression, _sumExpression)
@@ -26,11 +25,13 @@ MPFunctionAccessorImplementation(SumExpression, _sumExpression)
return @[@"startExpression", @"targetExpression", @"sumExpression"];
}
- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)index
{
return index == 0;
}
- (Class)functionTermClass
{
return [MPSumFunctionTerm class];

View File

@@ -9,12 +9,33 @@
#import "MPFunctionLayout.h"
/*!
@header
This file contains the <code>MPSumFunctionLayout</code> class.
*/
@class MPSumFunctionLayout, MPSumFunction;
/*!
@class MPSumFunctionLayout
@abstract A sum function layout displays a <code>@link
//apple_ref/occ/cl/MPSumFunction@/link</code>.
@discussion A sum is drawn using a capital greeg sigma (∑) The three children
are placed around it: The start expression below, target
expression above and sum expression to the right of it.
*/
@interface MPSumFunctionLayout : MPFunctionLayout
/*!
@method sumFunction
@abstract Returns the <code>@link
//apple_ref/occ/cl/MPSumFunction@/link</code> represented by the
receiver.
*/
- (MPSumFunction *)sumFunction;
@end

View File

@@ -9,14 +9,16 @@
#import "MPSumFunctionLayout.h"
#import "MPSumFunction.h"
#import "NSIndexPath+MPAdditions.h"
#define kSumFunctionStartExpressionOffset 0
#define kSumFunctionTargetExpressionOffset 0
#define kSumFunctionSumExpressionOffset 3
#define kSumFunctionTrailingOffset 5
@implementation MPSumFunctionLayout
- (MPSumFunction *)sumFunction
@@ -24,16 +26,19 @@
return (MPSumFunction *)self.function;
}
- (NSUInteger)indexOfLeadingChild
{
return 2;
}
- (NSUInteger)indexOfTrailingChild
{
return 2;
}
- (NSUInteger)indexOfChildAfterChildAtIndex:(NSUInteger)index
{
if (index != 2) {
@@ -42,21 +47,25 @@
return [super indexOfChildAfterChildAtIndex:index];
}
- (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index
{
return 0;
}
- (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index
{
return 1;
}
- (NSIndexSet *)indexesOfRemainingChildren
{
return [NSIndexSet indexSetWithIndex:2];
}
- (CTLineRef)line
{
CTLineRef line = [self lineForPrivateCacheIndex:0 generator:^CTLineRef{
@@ -67,6 +76,7 @@
return line;
}
- (NSRect)localLineBounds
{
NSRect lineBounds = CTLineGetBoundsWithOptions(self.line, 0);
@@ -77,6 +87,7 @@
return NSMakeRect(xPosition, lineBounds.origin.y, lineBounds.size.width, lineBounds.size.height);
}
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
{
NSRect childBounds = [self childLayoutAtIndex:index].bounds;
@@ -101,11 +112,13 @@
return offset;
}
- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index
{
return (index == 0 || index == 1) ? YES : self.usesSmallSize;
}
- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point
{
if (point.x < CTLineGetBoundsWithOptions(self.line, 0).size.width / 2) {
@@ -115,6 +128,7 @@
}
}
- (NSRect)generateBounds
{
NSRect lineBounds = CTLineGetBoundsWithOptions(self.line, 0);
@@ -139,6 +153,7 @@
return bounds;
}
- (void)draw
{
// Get the current context

View File

@@ -8,11 +8,30 @@
#import "MPFunctionTerm.h"
/*!
@header
This file contains the <code>MPSumFunctionTerm</code> class.
*/
@class MPSumFunctionTerm;
/*!
@class MPSumFunctionTerm
@abstract Represents and evaluates a <code>@link
//apple_ref/occ/cl/MPSumFunction@/link</code>.
@discussion A sum function evaluates its sum term <code>n</code> times. How
often it actually is evaluated depends on the boundary
expressions (start and target). Both are inclusive.
A sum function also features a variable that contains the current
value of the iteration. The variable is defined in the start
expression and incremented by <code>1</code> after every
iteration.
*/
@interface MPSumFunctionTerm : MPFunctionTerm
@end

View File

@@ -10,6 +10,8 @@
#import "MPParsedExpression.h"
@implementation MPSumFunctionTerm
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error

View File

@@ -9,6 +9,12 @@
#import "MPTerm.h"
/*!
@header
This file contains the <code>MPSumTerm</code> class.
*/
@class MPSumTerm;

View File

@@ -8,10 +8,7 @@
#import "MPSumTerm.h"
#import "MPParsedExpression.h"
#import "MPProductTerm.h"
#import "MPToken.h"
@implementation MPSumTerm
@@ -26,6 +23,7 @@
return self;
}
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
NSDecimalNumber *value = [NSDecimalNumber zero];

View File

@@ -9,6 +9,8 @@
/*!
@header
This file contains the <code>MPTerm</code> class.
The <code>MPTerm</code> class is used to evaluate a mathematical expression. A
term is a purely mathematical representation of a part of an expression. A term
can be defined with three rules:
@@ -66,6 +68,7 @@
#define ReturnNilIfNil(test) if (test == nil) return nil
@class MPTerm;

View File

@@ -9,9 +9,10 @@
#import "MPTerm.h"
#import "MPParsedExpression.h"
#import "MPSumTerm.h"
#import "MPEvaluationContext.h"
@implementation MPTerm
- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error
@@ -22,6 +23,7 @@
return result;
}
- (BOOL)defineVariable:(NSString *)variableName
value:(NSDecimalNumber *)value
error:(NSError *__autoreleasing *)error
@@ -39,6 +41,7 @@
return couldDefineVariable;
}
- (NSDecimalNumber *)valueForVariable:(NSString *)variableName
error:(NSError *__autoreleasing *)error
{
@@ -54,6 +57,7 @@
return value;
}
- (void)undefineVariable:(NSString *)variableName
{
[[MPEvaluationContext sharedContext] undefineVariable:variableName];

View File

@@ -9,6 +9,8 @@
/*!
@header
This file contains the <code>MPToken</code> class and protocol.
One way to represent a mathematical expression using the <code>@link
MPExpression@/link</code> class is a sequence of tokens. A token is a logical
unit of input. The different types of units are identified by a @link
@@ -30,10 +32,6 @@ while ((token = [self nextToken]).tokenType != MPEOFToken) {
*/
@class MPToken;
@protocol MPToken;
/*!
@typedef MPTokenType
@abstract The type of a token identifies its behaviour in a mathematical
@@ -103,6 +101,10 @@ typedef enum {
@class MPToken;
@protocol MPToken;
/*!
@protocol MPToken
@abstract Tokens represent logical units in an expresion.

View File

@@ -9,14 +9,44 @@
#import "MPTerm.h"
/*!
@header
This file contains the <code>MPVariable</code> class.
*/
@class MPVariable;
/*!
@class MPVariable
@abstract This class represents a variable.
@discussion Variables are evaluated in the <code>@link
//apple_ref/occ/cl/MPEvaluationContext@/link</code> and generate
errors if they are not defined.
*/
@interface MPVariable : MPTerm
/*!
@method initWithVariableName:
@abstract Initializes a <code>MPVariable</code> with the specified
<code>variableName</code>
@param variableName
The name of the variable. Must not be <code>nil</code> and must
be at least one character long.
@return A new <code>MPVariable</code> instance.
*/
- (instancetype)initWithVariableName:(NSString *)variableName; /* designated initializer */
/*!
@property variableName
@abstract The receiver's variable name.
*/
@property (readonly, nonatomic, strong) NSString *variableName;
@end

View File

@@ -8,12 +8,7 @@
#import "MPVariable.h"
#import "MPParsedExpression.h"
#import "MPToken.h"
#import "MPExpression.h"
#import "MPEvaluationContext.h"
@implementation MPVariable
@@ -22,11 +17,13 @@
self = [super init];
if (self) {
NSAssert(variableName != nil, @"variableName must not be nil.");
NSAssert(variableName.length > 0, @"variableName must be at least one character long.");
_variableName = variableName;
}
return self;
}
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
return [self valueForVariable:self.variableName

View File

@@ -7,10 +7,20 @@
//
/*!
@header
This file contains the <code>MPWhiteView</code> class.
*/
@class MPWhiteView;
/*!
@class MPWhiteView
@abstract A <code>NSView</code> that draws a white square in its frame.
*/
@interface MPWhiteView : NSView
@end

View File

@@ -6,20 +6,19 @@
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPExpression.h"
#import "MPExpression.h"
#import "NSString+MPExpressionElement.h"
#import "MPFunction.h"
#import "MPSumFunction.h"
#import "MPParenthesisFunction.h"
#import "MPPowerFunction.h"
#import "MPFractionFunction.h"
#import "MPToken.h"
#import "MPFunction+MPToken.h"
#import "MPFractionFunction.h"
#import "MPParenthesisFunction.h"
#import "MPPowerFunction.h"
#import "MPSumFunction.h"
#import "MPParsedExpression.h"
#import "MPTerm.h"
#import "MPEvaluationContext.h"
#import "MPMathRules.h"
@@ -27,5 +26,13 @@
#import "NSIndexPath+MPAdditions.h"
#import "NSRegularExpression+MPParsingAdditions.h"
#import "MPExpressionStorage.h"
#import "MPExpressionView.h"
#import "MPExpressionStorage.h"
#import "MPLayout.h"
#import "MPExpressionLayout.h"
#import "MPFunctionLayout.h"
#import "MPFractionFunctionLayout.h"
#import "MPParenthesisFunctionLayout.h"
#import "MPPowerFunctionLayout.h"
#import "MPSumFunctionLayout.h"

View File

@@ -7,6 +7,12 @@
//
/*!
@header
This file contains the <code>NSIndexPath(MPAdditions)</code> category.
*/
/*!
@category NSIndexPath (MPAdditions)
@@ -15,7 +21,6 @@
*/
@interface NSIndexPath (MPAdditions)
/*!
@property firstIndex
@abstract The first index from the receiver.

View File

@@ -12,7 +12,6 @@
@implementation NSIndexPath (MPAdditions)
- (NSUInteger)firstIndex
{
return [self indexAtPosition:0];

View File

@@ -7,6 +7,13 @@
//
/*!
@header
This file contains the <code>NSRegularExpression(MPParsingAdditions)</code>
category.
*/
/*!
@category NSRegularExpression (MPParsingAdditions)
@@ -15,7 +22,6 @@
*/
@interface NSRegularExpression (MPParsingAdditions)
/*!
@method firstMathInString:
@abstract Returns the first match of the regular expression within the

View File

@@ -9,11 +9,18 @@
#import "MPExpression.h"
/*!
@header
This file contains the <code>NSString(MPExpressionElement)</code> category.
*/
/*!
@category NSString (MPExpressionElement)
@abstract This category adds <code>@link MPExpressionElement@/link</code>
protocol conformance to the <code>NSString</code> class.
@abstract This category adds <code>@link
//apple_ref/occ/intf/MPExpressionElement@/link</code> protocol
conformance to the <code>NSString</code> class.
*/
@interface NSString (MPExpressionElement) <MPExpressionElement>