Archived
1

Improved Model

Added Keyboard Selection Support
Added Mouse Selection Support
Added Keyboard Editing Support
Corrected Some Bugs
Abstracted the Layout System further
Added Functions Button (test)
This commit is contained in:
Kim Wittenburg
2014-08-31 15:41:17 +02:00
parent 9aa4bca234
commit 4a3ea0cede
23 changed files with 885 additions and 262 deletions

View File

@@ -27,12 +27,13 @@
3BBBA35E1903FD3600824E74 /* MPRangePath.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B87E35C1900933200259938 /* MPRangePath.m */; }; 3BBBA35E1903FD3600824E74 /* MPRangePath.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B87E35C1900933200259938 /* MPRangePath.m */; };
3BBBA35F1903FD3600824E74 /* MPRangePath.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B87E35C1900933200259938 /* MPRangePath.m */; }; 3BBBA35F1903FD3600824E74 /* MPRangePath.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B87E35C1900933200259938 /* MPRangePath.m */; };
3BBBA3951905704200824E74 /* MPRangeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BBBA3941905704200824E74 /* MPRangeTests.m */; }; 3BBBA3951905704200824E74 /* MPRangeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BBBA3941905704200824E74 /* MPRangeTests.m */; };
3BC4660B19B2425A0033F13A /* MPDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3BF9978318DE623E009CF6C4 /* MPDocument.xib */; };
3BC4661419B245C60033F13A /* Fonts in Resources */ = {isa = PBXBuildFile; fileRef = 3BC4661319B245C60033F13A /* Fonts */; };
3BF9976F18DE623E009CF6C4 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BF9976E18DE623E009CF6C4 /* Cocoa.framework */; }; 3BF9976F18DE623E009CF6C4 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BF9976E18DE623E009CF6C4 /* Cocoa.framework */; };
3BF9977918DE623E009CF6C4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3BF9977718DE623E009CF6C4 /* InfoPlist.strings */; }; 3BF9977918DE623E009CF6C4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3BF9977718DE623E009CF6C4 /* InfoPlist.strings */; };
3BF9977B18DE623E009CF6C4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BF9977A18DE623E009CF6C4 /* main.m */; }; 3BF9977B18DE623E009CF6C4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BF9977A18DE623E009CF6C4 /* main.m */; };
3BF9977F18DE623E009CF6C4 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 3BF9977D18DE623E009CF6C4 /* Credits.rtf */; }; 3BF9977F18DE623E009CF6C4 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 3BF9977D18DE623E009CF6C4 /* Credits.rtf */; };
3BF9978218DE623E009CF6C4 /* MPDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BF9978118DE623E009CF6C4 /* MPDocument.m */; }; 3BF9978218DE623E009CF6C4 /* MPDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BF9978118DE623E009CF6C4 /* MPDocument.m */; };
3BF9978518DE623E009CF6C4 /* MPDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3BF9978318DE623E009CF6C4 /* MPDocument.xib */; };
3BF9978818DE623E009CF6C4 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3BF9978618DE623E009CF6C4 /* MainMenu.xib */; }; 3BF9978818DE623E009CF6C4 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3BF9978618DE623E009CF6C4 /* MainMenu.xib */; };
3BF9978A18DE623E009CF6C4 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3BF9978918DE623E009CF6C4 /* Images.xcassets */; }; 3BF9978A18DE623E009CF6C4 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3BF9978918DE623E009CF6C4 /* Images.xcassets */; };
3BF9979118DE623E009CF6C4 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BF9979018DE623E009CF6C4 /* XCTest.framework */; }; 3BF9979118DE623E009CF6C4 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BF9979018DE623E009CF6C4 /* XCTest.framework */; };
@@ -80,6 +81,7 @@
3BBBA3591903EA9B00824E74 /* MPModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPModel.h; sourceTree = "<group>"; }; 3BBBA3591903EA9B00824E74 /* MPModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPModel.h; sourceTree = "<group>"; };
3BBBA38419047FC900824E74 /* MPView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPView.h; sourceTree = "<group>"; }; 3BBBA38419047FC900824E74 /* MPView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPView.h; sourceTree = "<group>"; };
3BBBA3941905704200824E74 /* MPRangeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRangeTests.m; sourceTree = "<group>"; }; 3BBBA3941905704200824E74 /* MPRangeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRangeTests.m; sourceTree = "<group>"; };
3BC4661319B245C60033F13A /* Fonts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Fonts; sourceTree = "<group>"; };
3BF9976B18DE623E009CF6C4 /* MathPad.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MathPad.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3BF9976B18DE623E009CF6C4 /* MathPad.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MathPad.app; sourceTree = BUILT_PRODUCTS_DIR; };
3BF9976E18DE623E009CF6C4 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 3BF9976E18DE623E009CF6C4 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
3BF9977118DE623E009CF6C4 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 3BF9977118DE623E009CF6C4 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
@@ -179,6 +181,8 @@
children = ( children = (
3BF9978318DE623E009CF6C4 /* MPDocument.xib */, 3BF9978318DE623E009CF6C4 /* MPDocument.xib */,
3BF9978618DE623E009CF6C4 /* MainMenu.xib */, 3BF9978618DE623E009CF6C4 /* MainMenu.xib */,
3BF9978918DE623E009CF6C4 /* Images.xcassets */,
3BC4661319B245C60033F13A /* Fonts */,
); );
name = Resources; name = Resources;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -268,7 +272,6 @@
3B87E351190082BB00259938 /* View */, 3B87E351190082BB00259938 /* View */,
3B87E352190082C000259938 /* Controller */, 3B87E352190082C000259938 /* Controller */,
3B87E353190082E200259938 /* Resources */, 3B87E353190082E200259938 /* Resources */,
3BF9978918DE623E009CF6C4 /* Images.xcassets */,
3BF9977518DE623E009CF6C4 /* Supporting Files */, 3BF9977518DE623E009CF6C4 /* Supporting Files */,
); );
path = MathPad; path = MathPad;
@@ -384,7 +387,8 @@
files = ( files = (
3BF9977918DE623E009CF6C4 /* InfoPlist.strings in Resources */, 3BF9977918DE623E009CF6C4 /* InfoPlist.strings in Resources */,
3BF9978A18DE623E009CF6C4 /* Images.xcassets in Resources */, 3BF9978A18DE623E009CF6C4 /* Images.xcassets in Resources */,
3BF9978518DE623E009CF6C4 /* MPDocument.xib in Resources */, 3BC4660B19B2425A0033F13A /* MPDocument.xib in Resources */,
3BC4661419B245C60033F13A /* Fonts in Resources */,
3BF9977F18DE623E009CF6C4 /* Credits.rtf in Resources */, 3BF9977F18DE623E009CF6C4 /* Credits.rtf in Resources */,
3BF9978818DE623E009CF6C4 /* MainMenu.xib in Resources */, 3BF9978818DE623E009CF6C4 /* MainMenu.xib in Resources */,
); );

View File

@@ -14,37 +14,23 @@
<customObject id="-3" userLabel="Application"/> <customObject id="-3" userLabel="Application"/>
<window title="MathPad" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="xOd-HO-29H" userLabel="Window"> <window title="MathPad" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="xOd-HO-29H" userLabel="Window">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/> <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <rect key="contentRect" x="525" y="411" width="507" height="251"/>
<rect key="contentRect" x="133" y="235" width="507" height="240"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1178"/> <rect key="screenRect" x="0.0" y="0.0" width="1920" height="1178"/>
<value key="minSize" type="size" width="94" height="86"/> <value key="minSize" type="size" width="500" height="200"/>
<view key="contentView" id="gIp-Ho-8D9"> <view key="contentView" id="gIp-Ho-8D9">
<rect key="frame" x="0.0" y="0.0" width="507" height="240"/> <rect key="frame" x="0.0" y="0.0" width="507" height="251"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="lcd-Ip-jjR" customClass="MPExpressionView"> <customView translatesAutoresizingMaskIntoConstraints="NO" id="lcd-Ip-jjR" customClass="MPExpressionView">
<rect key="frame" x="20" y="124" width="467" height="96"/> <rect key="frame" x="20" y="20" width="467" height="211"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</customView> </customView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="IMg-L0-qdu">
<rect key="frame" x="209" y="13" width="88" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Change" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Xxz-j2-fsI">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="changeExpression:" target="-2" id="k8U-3Y-8Ch"/>
</connections>
</button>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstAttribute="trailing" secondItem="lcd-Ip-jjR" secondAttribute="trailing" constant="20" symbolic="YES" id="3tX-J3-Wte"/> <constraint firstItem="lcd-Ip-jjR" firstAttribute="leading" secondItem="gIp-Ho-8D9" secondAttribute="leading" constant="20" symbolic="YES" id="8Li-0i-x3o"/>
<constraint firstItem="IMg-L0-qdu" firstAttribute="top" secondItem="lcd-Ip-jjR" secondAttribute="bottom" constant="83" id="62S-rU-IWO"/> <constraint firstItem="lcd-Ip-jjR" firstAttribute="top" secondItem="gIp-Ho-8D9" secondAttribute="top" constant="20" symbolic="YES" id="MPQ-lK-4d0"/>
<constraint firstItem="lcd-Ip-jjR" firstAttribute="leading" secondItem="gIp-Ho-8D9" secondAttribute="leading" constant="20" symbolic="YES" id="XN3-k3-tOU"/> <constraint firstAttribute="bottom" secondItem="lcd-Ip-jjR" secondAttribute="bottom" constant="20" symbolic="YES" id="hi3-fp-zJn"/>
<constraint firstItem="lcd-Ip-jjR" firstAttribute="top" secondItem="gIp-Ho-8D9" secondAttribute="top" constant="20" symbolic="YES" id="gqS-BG-xpS"/> <constraint firstAttribute="trailing" secondItem="lcd-Ip-jjR" secondAttribute="trailing" constant="20" symbolic="YES" id="xiK-zs-2rs"/>
<constraint firstItem="IMg-L0-qdu" firstAttribute="centerX" secondItem="lcd-Ip-jjR" secondAttribute="centerX" id="sCM-Pj-4wd"/>
<constraint firstAttribute="bottom" secondItem="IMg-L0-qdu" secondAttribute="bottom" constant="20" symbolic="YES" id="tNe-R6-QlG"/>
</constraints> </constraints>
</view> </view>
<connections> <connections>

View File

@@ -216,6 +216,32 @@
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element; - (NSUInteger)indexOfElement:(id<MPExpressionElement>)element;
/*!
@method indexOfElementAtSymbolLocation:offset
@brief Calculates the index of the element the specified location points
to.
@discussion The @c location is in the length reference frame whereas the
returned value is an element index. This method converts from
the former to the latter.
If the location exceeds the receiver's bounds a @c
NSRangeException will be raised.
@param location
The location of which you want the corresponding element index.
@param offset
An output parameter that gets set to the offst into the symbol
whose index is returned. If location for example points to the
symbol @c '2' in the string element @c '123' the offset @c would
be set to @c 1.
@return The index of the element the location points to.
*/
- (NSUInteger)indexOfElementAtSymbolLocation:(NSUInteger)location offset:(out NSUInteger *)offset;
/*! /*!
@method replaceSymbolsInRange:withElements: @method replaceSymbolsInRange:withElements:
@brief Replaces the elements in the given range with the contents of the @brief Replaces the elements in the given range with the contents of the
@@ -242,7 +268,6 @@
*/ */
- (void)replaceSymbolsInRange:(NSRange)range - (void)replaceSymbolsInRange:(NSRange)range
withElements:(NSArray *)elements; withElements:(NSArray *)elements;
// TODO: - (NSUInteger)indexOfElementAtSymbolLocation:(NSUInteger)location;
#warning Evaluating must possibly return error #warning Evaluating must possibly return error
- (double)doubleValue; // Evaluates Expression - (double)doubleValue; // Evaluates Expression
@@ -304,6 +329,18 @@
#pragma mark Working With the Expression Tree #pragma mark Working With the Expression Tree
/*!
@method rootExpression
@brief Returns the root expression from the receiver's expression tree.
@discussion The root expression is the ultimate parent of all expressions and
functions in the expression tree.
@return The root expression from the receiver's expression tree.
*/
- (MPExpression *)rootExpression;
/*! /*!
@method elementAtIndexPath: @method elementAtIndexPath:
@brief Returns the element at the specified index path. @brief Returns the element at the specified index path.
@@ -511,6 +548,19 @@
*/ */
- (NSArray *)elements; - (NSArray *)elements;
// TODO: - (NSMutableArray *)mutableElements;
/*!
@method mutableElements
@brief Returns a proxy mutable array object that responds to all methods
defined by @c NSMutableArray.
@discussion Mutations on the proxy object also change the receiver. The proxy
object does not respond to coding methods. Copying the proxy
object will not duplicate it.
@return A proxy object that responds to all methods defined by @c
NSMutableArray.
*/
// - (NSMutableArray *)mutableElements;
@end @end

View File

@@ -20,13 +20,10 @@
@interface MPExpression (MPExpressionPrivate) @interface MPExpression (MPExpressionPrivate)
- (NSUInteger)lengthOfElements:(NSArray *)elements; - (NSUInteger)lengthOfElements:(NSArray *)elements;
- (NSUInteger)indexOfElementAtLocation:(NSUInteger)location;
- (void)validateElements:(NSArray *)elements; - (void)validateElements:(NSArray *)elements;
- (BOOL)splitElementsAtLocation:(NSUInteger)location - (BOOL)splitElementsAtLocation:(NSUInteger)location
insertionIndex:(out NSUInteger *)insertionIndex; insertionIndex:(out NSUInteger *)insertionIndex;
- (NSUInteger)calculateSplitOffsetForSplitLocation:(NSUInteger)location
inElementAtIndex:(out NSUInteger *)elementIndex;
@end @end
@@ -41,13 +38,6 @@
return length; return length;
} }
- (NSUInteger)indexOfElementAtLocation:(NSUInteger)location
{
NSUInteger index = 0;
[self calculateSplitOffsetForSplitLocation:location inElementAtIndex:&index];
return index;
}
- (void)validateElements:(NSArray *)elements - (void)validateElements:(NSArray *)elements
{ {
for (id element in elements) { for (id element in elements) {
@@ -66,18 +56,14 @@
*insertionIndex = 0; *insertionIndex = 0;
return NO; return NO;
} }
NSUInteger splitElementIndex;
NSUInteger splitOffset = [self calculateSplitOffsetForSplitLocation:location NSUInteger splitOffset;
inElementAtIndex:&splitElementIndex]; NSUInteger splitElementIndex = [self indexOfElementAtSymbolLocation:location
id<MPExpressionElement> splitElement = self.elements[splitElementIndex]; offset:&splitOffset];
if (splitOffset == splitElement.length) {
splitOffset = 0;
splitElementIndex++;
}
if (splitOffset != 0) { if (splitOffset != 0) {
NSString *stringElement = (NSString *)splitElement; NSString *splitElement = (NSString *)self.elements[splitElementIndex];
NSString *leftPart = [stringElement substringToIndex:splitOffset]; NSString *leftPart = [splitElement substringToIndex:splitOffset];
NSString *rightPart = [stringElement substringFromIndex:splitOffset]; NSString *rightPart = [splitElement substringFromIndex:splitOffset];
[self.elements replaceObjectsInRange:NSMakeRange(splitElementIndex, 1) [self.elements replaceObjectsInRange:NSMakeRange(splitElementIndex, 1)
withObjectsFromArray:@[leftPart, rightPart]]; withObjectsFromArray:@[leftPart, rightPart]];
++splitElementIndex; ++splitElementIndex;
@@ -86,24 +72,6 @@
return splitOffset != 0; return splitOffset != 0;
} }
- (NSUInteger)calculateSplitOffsetForSplitLocation:(NSUInteger)location
inElementAtIndex:(out NSUInteger *)elementIndex
{
NSUInteger length = 0;
NSUInteger index = 0;
NSUInteger elementLength = 0;
for (id<MPExpressionElement> element in self.elements) {
elementLength = element.length;
length += elementLength;
if (length >= location) {
break;
}
++index;
}
*elementIndex = index;
return elementLength - (length - location);
}
@end @end
@implementation MPExpression { @implementation MPExpression {
@@ -215,6 +183,41 @@
return [self.elements indexOfObject:element]; return [self.elements indexOfObject:element];
} }
- (NSUInteger)indexOfElementAtSymbolLocation:(NSUInteger)location
offset:(out NSUInteger *)offset
{
if (location == 0) {
if (offset != NULL) {
*offset = 0;
}
return 0;
}
// Calculating elementIndex and splitOffset
NSUInteger totalLength = 0;
NSUInteger elementIndex = 0;
NSUInteger elementLength = 0;
for (id<MPExpressionElement> element in self.elements) {
elementLength = element.length;
totalLength += elementLength;
if (totalLength >= location) {
break;
}
++elementIndex;
}
NSUInteger splitOffset = elementLength - (totalLength - location);
id<MPExpressionElement> element = self.elements[elementIndex];
if (splitOffset == element.length) {
splitOffset = 0;
elementIndex++;
}
if (offset != NULL) {
*offset = splitOffset;
}
return elementIndex;
}
- (void)replaceSymbolsInRange:(NSRange)range - (void)replaceSymbolsInRange:(NSRange)range
withElements:(NSArray *)elements withElements:(NSArray *)elements
{ {
@@ -276,7 +279,7 @@
} }
#pragma mark Basic NSObject Methods #pragma mark Basic NSObject Methods
/*
- (BOOL)isEqual:(id)object - (BOOL)isEqual:(id)object
{ {
if (self == object) { if (self == object) {
@@ -295,7 +298,7 @@
{ {
return [self.elements isEqualToArray:anExpression.elements]; return [self.elements isEqualToArray:anExpression.elements];
} }
*/
- (NSString *)description - (NSString *)description
{ {
#warning Bad Implementation #warning Bad Implementation
@@ -359,6 +362,14 @@
@implementation MPExpression (MPExpressionExtension) @implementation MPExpression (MPExpressionExtension)
#pragma mark Working With the Expression Tree #pragma mark Working With the Expression Tree
- (MPExpression *)rootExpression
{
if (self.parent == nil) {
return self;
}
return [self.parent rootExpression];
}
- (id)elementAtIndexPath:(NSIndexPath *)indexPath - (id)elementAtIndexPath:(NSIndexPath *)indexPath
{ {
if (indexPath.length == 0) { if (indexPath.length == 0) {
@@ -369,7 +380,7 @@
return element; return element;
} }
if ([element isFunction]) { if ([element isFunction]) {
return [(MPFunction *)element elementAtIndexPath:[indexPath indexPathByRemovingLastIndex]]; return [(MPFunction *)element elementAtIndexPath:[indexPath indexPathByRemovingFirstIndex]];
} }
return nil; return nil;
} }

View File

@@ -12,7 +12,7 @@
@interface MPExpressionLayout : MPLayout @interface MPExpressionLayout : MPLayout
- (instancetype)initRootLayoutWithExpressionStorage:(MPExpressionStorage *)expressionStorage; - (instancetype)initRootLayoutWithExpression:(MPExpression *)expression;
@property (readonly, nonatomic, weak) MPExpression *expression; @property (readonly, nonatomic, weak) MPExpression *expression;

View File

@@ -9,6 +9,10 @@
#import "MPExpressionLayout.h" #import "MPExpressionLayout.h"
#import "MPFunctionLayout.h" #import "MPFunctionLayout.h"
#import "NSIndexPath+MPAdditions.h"
#define kMPEmptyBoxWidth (self.usesSmallSize ? 2.0 : 3.0)
@interface MPExpressionLayout (MPLineGeneration) @interface MPExpressionLayout (MPLineGeneration)
- (CTLineRef)lineForElementAtIndex:(NSUInteger)index; - (CTLineRef)lineForElementAtIndex:(NSUInteger)index;
@@ -34,9 +38,8 @@
- (CTLineRef)createLineForString:(NSString *)aString - (CTLineRef)createLineForString:(NSString *)aString
{ {
NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString
attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Lucida Grande" size:18.0]}]; attributes:@{NSFontAttributeName: self.font}];
CFAttributedStringRef attributedString = CFBridgingRetain(text); CFAttributedStringRef attributedString = CFBridgingRetain(text);
CTLineRef line = CTLineCreateWithAttributedString(attributedString); CTLineRef line = CTLineCreateWithAttributedString(attributedString);
CFRelease(attributedString); // TODO: Is this release appropriate? CFRelease(attributedString); // TODO: Is this release appropriate?
@@ -48,49 +51,38 @@
@implementation MPExpressionLayout @implementation MPExpressionLayout
# pragma mark Creation Methods # pragma mark Creation Methods
- (instancetype)initRootLayoutWithExpressionStorage:(MPExpressionStorage *)expressionStorage - (instancetype)initRootLayoutWithExpression:(MPExpression *)expression
{ {
self = [super init]; self = [super init];
if (self) { if (self) {
_expressionStorage = expressionStorage; _expression = expression;
_expression = expressionStorage;
} }
return self; return self;
} }
- (instancetype)initWithPath:(NSIndexPath *)path - (instancetype)initWithElementAtPath:(NSIndexPath *)path
inRootExpression:(MPExpression *)rootExpression
parent:(MPLayout *)parent parent:(MPLayout *)parent
{ {
self = [super initWithPath:path self = [super initWithElementAtPath:path
inRootExpression:rootExpression
parent:parent]; parent:parent];
if (self) { if (self) {
_expression = [parent.expressionStorage elementAtIndexPath:path]; _expression = [rootExpression elementAtIndexPath:path];
} }
return self; return self;
} }
#pragma mark Properties
@synthesize expressionStorage = _expressionStorage;
- (MPExpressionStorage *)expressionStorage
{
if (_expressionStorage) {
return _expressionStorage;
}
return self.parent.expressionStorage;
}
//- (MPExpression *)expression
//{
// return [self.expressionStorage elementAtIndexPath:self.path];
//}
#pragma mark Cache Methods #pragma mark Cache Methods
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index - (MPLayout *)childLayoutAtIndex:(NSUInteger)index
{ {
id cachedObject = [self cachableObjectForIndex:index generator:^id{ id cachedObject = [self cachableObjectForIndex:index generator:^id{
NSIndexPath *indexPath = [self.expression.indexPath indexPathByAddingIndex:index]; NSIndexPath *indexPath = [self.expression.indexPath indexPathByAddingIndex:index];
MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunctionAtIndexPath:indexPath MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunctionAtIndexPath:indexPath
inRootExpression:self.expression.rootExpression
parent:self]; parent:self];
layout.flipped = self.flipped;
layout.usesSmallSize = self.usesSmallSize;
return layout; return layout;
}]; }];
if ([cachedObject isKindOfClass:[MPLayout class]]) { if ([cachedObject isKindOfClass:[MPLayout class]]) {
@@ -99,51 +91,165 @@
return nil; return nil;
} }
- (NSSize)sizeForElementAtIndex:(NSUInteger)index - (NSRect)boundsOfElementAtIndex:(NSUInteger)index
{ {
id symbol = [self.expression elementAtIndex:index]; id symbol = [self.expression elementAtIndex:index];
if ([symbol isString]) { if ([symbol isString]) {
CTLineRef line = [self lineForElementAtIndex:index]; CTLineRef line = [self lineForElementAtIndex:index];
CFRetain(line); CFRetain(line);
CGRect bounds = CTLineGetBoundsWithOptions(line, /*kCTLineBoundsUseOpticalBounds*/ 0); CGRect bounds = CTLineGetBoundsWithOptions(line, 0);
CFRelease(line); CFRelease(line);
return bounds.size; return bounds;
} else { } else {
return [self childLayoutAtIndex:index].size; return [self childLayoutAtIndex:index].bounds;
} }
} }
#pragma mark Drawing Methods #pragma mark Drawing Methods
- (NSSize)generateSize - (NSRect)generateBounds
{ {
CGFloat width = 0, height = 0; if (self.expression.numberOfElements == 0) {
for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) { return NSMakeRect(0, [self.font descender], kMPEmptyBoxWidth, self.fontSize);
NSSize elementSize = [self sizeForElementAtIndex:index];
width += elementSize.width;
height = MAX(height, elementSize.height);
} }
return NSMakeSize(width, height); CGFloat x = 0, y = 0, width = 0, height = 0;
for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) {
NSRect elementBounds = [self boundsOfElementAtIndex:index];
width += elementBounds.size.width;
height = MAX(height, elementBounds.size.height);
y = MIN(y, elementBounds.origin.y);
}
return NSMakeRect(x, y, width, height);
} }
- (void)drawAtPoint:(NSPoint)point - (NSRect)boundingRectForRange:(NSRange)range
{
NSUInteger startOffset;
NSUInteger startElementIndex = [self.expression indexOfElementAtSymbolLocation:range.location
offset:&startOffset];
// Calculate x position
CGFloat x = 0, width = 0;
for (NSUInteger index = 0; index < startElementIndex; index++) {
x += [self boundsOfElementAtIndex:index].size.width;
}
if (startOffset > 0) {
CTLineRef line = [self lineForElementAtIndex:startElementIndex];
CFRetain(line);
CGFloat xOffset = CTLineGetOffsetForStringIndex(line, startOffset, NULL);
x += xOffset;
width += CTLineGetBoundsWithOptions(line, 0).size.width - xOffset;
CFRelease(line);
} else if (startElementIndex < self.expression.numberOfElements) { // Otherwise the selection is after the last symbol
width += [self boundsOfElementAtIndex:startElementIndex].size.width;
}
// If we search the caret position we are done
if (range.length == 0) {
return NSMakeRect(x, self.bounds.origin.y, 0, self.bounds.size.height);
}
NSUInteger endOffset;
NSUInteger endElementIndex = [self.expression indexOfElementAtSymbolLocation:NSMaxRange(range)
offset:&endOffset];
// Selection is inside of one string element
if (startElementIndex == endElementIndex) {
CTLineRef line = [self lineForElementAtIndex:endElementIndex];
CFRetain(line);
CGFloat xStart = CTLineGetOffsetForStringIndex(line, startOffset, NULL);
CGFloat xEnd = CTLineGetOffsetForStringIndex(line, endOffset, NULL);
width = xEnd - xStart;
CFRelease(line);
return NSMakeRect(x, self.bounds.origin.y, width, self.bounds.size.height);
}
// Calculate width
for (NSUInteger index = startElementIndex + 1; index < endElementIndex; index++) {
width += [self boundsOfElementAtIndex:index].size.width;
}
if (endOffset > 0) {
CTLineRef line = [self lineForElementAtIndex:endElementIndex];
CFRetain(line);
width += CTLineGetOffsetForStringIndex(line, endOffset, NULL);
CFRelease(line);
}
return NSMakeRect(x, self.bounds.origin.y, width, self.bounds.size.height);
}
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
{
CGFloat x = 0;
for (NSUInteger i = 0; i < index; i++) {
x += [self boundsOfElementAtIndex:i].size.width;
}
return NSMakePoint(x, 0);
}
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point
{
NSUInteger currentPosition = 0;
for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) {
NSRect elementBounds = [self boundsOfElementAtIndex:index];
NSPoint elementOffset = [self offsetOfChildLayoutAtIndex:index];
elementBounds.origin.x += elementOffset.x;
elementBounds.origin.y += elementOffset.y;
id<MPExpressionElement> element = [self.expression elementAtIndex:index];
if (NSMouseInRect(point, elementBounds, self.flipped)) {
if ([element isString]) {
CTLineRef line = [self lineForElementAtIndex:index];
CFRetain(line);
CFIndex localIndex = CTLineGetStringIndexForPosition(line, point);
CFRelease(line);
return [NSIndexPath indexPathWithIndex:currentPosition+localIndex];
} else {
NSPoint pointInFunction = NSMakePoint(point.x - elementOffset.x, point.y + elementOffset.y);
NSIndexPath *subPath = [[self childLayoutAtIndex:index] indexPathForMousePoint:pointInFunction];
if (subPath.length == 1) {
// A single index is used to communicate back wether the
// selection should be before or after the function.
// A 0 means before, a 1 means after.
return [NSIndexPath indexPathWithIndex:currentPosition + [subPath indexAtPosition:0]];
} else {
return [subPath indexPathByPreceedingIndex:index];
}
}
}
currentPosition += element.length;
}
if (point.x < self.bounds.size.width / 2) {
return [NSIndexPath indexPathWithIndex:0];
} else {
return [NSIndexPath indexPathWithIndex:self.expression.length];
}
}
- (void)draw
{ {
// Get the current context // Get the current context
CGContextRef context = CGContextRef context =
(CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
CGContextSaveGState(context); CGContextSaveGState(context);
if (self.expression.numberOfElements == 0) {
CGContextRestoreGState(context);
NSBezierPath *path = [NSBezierPath bezierPathWithRect:NSMakeRect(0, 0 + self.font.descender, kMPEmptyBoxWidth, self.fontSize)];
path.lineWidth = 0.5;
[path stroke];
return;
}
// Set the text matrix // Set the text matrix
CGContextSetTextMatrix(context, CGAffineTransformIdentity); CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// Track the x position // Track the x position
CGFloat x = point.x; CGFloat x = 0;
for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) { for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) {
// The current element // The current element
id element = [self.expression elementAtIndex:index]; id element = [self.expression elementAtIndex:index];
NSSize elementSize = [self sizeForElementAtIndex:index]; NSRect elementBounds = [self boundsOfElementAtIndex:index];
CGFloat dy = (self.size.height - elementSize.height) / 2;
CGFloat y = point.y + dy;
if ([element isString]) { if ([element isString]) {
// Get the line to draw // Get the line to draw
@@ -151,7 +257,7 @@
CFRetain(line); CFRetain(line);
// Move to the appropriate position // Move to the appropriate position
CGContextSetTextPosition(context, x, y); CGContextSetTextPosition(context, x, 0);
// Perform the drawing // Perform the drawing
CTLineDraw(line, context); CTLineDraw(line, context);
@@ -160,9 +266,9 @@
} else { } else {
// Let the child layout draw itself // Let the child layout draw itself
MPLayout *layout = [self childLayoutAtIndex:index]; MPLayout *layout = [self childLayoutAtIndex:index];
[layout drawAtPoint:NSMakePoint(x, y)]; [layout drawAtPoint:NSMakePoint(x, 0)];
} }
x += elementSize.width; x += elementBounds.size.width;
} }
CGContextRestoreGState(context); CGContextRestoreGState(context);
} }

View File

@@ -12,14 +12,9 @@
@interface MPExpressionStorage : MPExpression @interface MPExpressionStorage : MPExpression
- (instancetype)initWithExpressionView:(MPExpressionView *)expressionView - (instancetype)initWithElements:(NSArray *)elements;
elements:(NSArray *)elements;
@property (readonly, nonatomic, weak) MPExpressionView *expressionView; @property (nonatomic, weak) MPExpressionView *expressionView; // Do not set
@property (nonatomic, strong) MPExpressionLayout *rootLayout; @property (nonatomic, strong) MPExpressionLayout *rootLayout;
- (NSLayoutManager *)layoutManager;
- (NSTextContainer *)textContainer;
- (NSTextStorage *)textStorage;
@end @end

View File

@@ -20,53 +20,20 @@
@implementation MPExpressionStorage @implementation MPExpressionStorage
- (instancetype)initWithExpressionView:(MPExpressionView *)expressionView elements:(NSArray *)elements
{
self = [self initWithElements:elements];
if (self) {
_expressionView = expressionView;
}
return self;
}
- (instancetype)initWithElements:(NSArray *)elements - (instancetype)initWithElements:(NSArray *)elements
{ {
self = [super initWithElements:elements]; self = [super initWithElements:elements];
if (self) { if (self) {
_rootLayout = [[MPExpressionLayout alloc] initRootLayoutWithExpressionStorage:self]; _rootLayout = [[MPExpressionLayout alloc] initRootLayoutWithExpression:self];
} }
return self; return self;
} }
- (NSLayoutManager *)layoutManager - (void)setExpressionView:(MPExpressionView *)expressionView
{ {
[self ensureTextSystemObjects]; _expressionView = expressionView;
return _layoutManager; self.rootLayout = [[MPExpressionLayout alloc] initRootLayoutWithExpression:self];
} self.rootLayout.flipped = expressionView.isFlipped;
- (NSTextContainer *)textContainer
{
[self ensureTextSystemObjects];
return _textContainer;
}
- (NSTextStorage *)textStorage
{
[self ensureTextSystemObjects];
return _textStorage;
}
- (void)ensureTextSystemObjects
{
if (_layoutManager == nil || _textContainer == nil || _textStorage == nil) {
_textStorage = [[NSTextStorage alloc] init];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[layoutManager addTextContainer:textContainer];
[_textStorage addLayoutManager:layoutManager];
_textContainer = textContainer;
_layoutManager = layoutManager;
}
} }
- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath - (void)didChangeElementsInRangePath:(MPRangePath *)rangePath
@@ -76,11 +43,13 @@
return; return;
} }
MPLayout *current = self.rootLayout; MPLayout *current = self.rootLayout;
for (NSUInteger index = 1; index < rangePath.location.length-1; index++) { for (NSUInteger position = 0; position < rangePath.location.length-1; position++) {
NSUInteger index = [rangePath.location indexAtPosition:position];
current = [current childLayoutAtIndex:index]; current = [current childLayoutAtIndex:index];
} }
[current clearCacheInRange:rangePath.rangeAtLastIndex [current clearCacheInRange:rangePath.rangeAtLastIndex
replacementLength:replacementLength]; replacementLength:replacementLength];
[self.expressionView invalidateIntrinsicContentSize];
self.expressionView.needsDisplay = YES; self.expressionView.needsDisplay = YES;
} }

View File

@@ -23,7 +23,6 @@
@property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage; @property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage;
@property (nonatomic, getter = isEditable) BOOL editable; @property (nonatomic, getter = isEditable) BOOL editable;
//@property (nonatomic, strong) MPRangePath *selection; @property (nonatomic, strong) MPRangePath *selection;
@property (nonatomic, strong) NSIndexPath *caretLocation;
@end @end

View File

@@ -6,34 +6,109 @@
// Copyright (c) 2014 Kim Wittenburg. All rights reserved. // Copyright (c) 2014 Kim Wittenburg. All rights reserved.
// //
#warning X-Origin is not working yet
#import "MPExpressionView.h" #import "MPExpressionView.h"
#import "MPExpressionStorage.h" #import "MPExpressionStorage.h"
#import "MPExpressionLayout.h" #import "MPExpressionLayout.h"
#import "MPRangePath.h"
#import "NSIndexPath+MPAdditions.h"
#import "MPSumFunction.h" #import "MPSumFunction.h"
@interface MPExpressionView (MPCursor) @interface MPExpressionView ()
@property (nonatomic, weak) NSButton *functionsButton;
@property (nonatomic, strong) NSTimer *caretTimer;
@property (nonatomic) NSTimeInterval caretBlinkRate;
@property (nonatomic) BOOL caretVisible;
@property (nonatomic, getter = isSelectionModifyingStart) BOOL selectionModifyingStart;
@end
@interface MPExpressionView (MPDrawing)
- (NSPoint)expressionOrigin;
@end
@interface MPExpressionView (MPSelection)
- (void)restartCaretTimer;
- (void)updateCaret:(NSTimer *)timer;
- (NSRect)selectionRect;
- (void)selectRangePathWithLocation:(NSIndexPath *)location length:(NSUInteger)length;
@end
@implementation MPExpressionView (MPDrawing)
- (NSPoint)expressionOrigin
{
NSRect expressionBounds = [self.expressionStorage.rootLayout bounds];
CGFloat y = (self.bounds.size.height - expressionBounds.size.height) / 2 + fabs(expressionBounds.origin.y);
return NSMakePoint(self.bounds.origin.x, self.bounds.origin.y + y);
}
@end
@implementation MPExpressionView (MPSelection)
- (void)restartCaretTimer
{
if (self.caretTimer) {
if ([self.caretTimer isValid]) {
[self.caretTimer invalidate];
}
}
self.caretTimer = [NSTimer scheduledTimerWithTimeInterval:self.caretBlinkRate/2 target:self selector:@selector(updateCaret:) userInfo:nil repeats:YES];
self.caretVisible = NO;
[self updateCaret:self.caretTimer];
}
- (void)updateCaret:(NSTimer *)timer
{
self.caretVisible = !self.caretVisible;
self.needsDisplay = YES;
}
- (NSRect)selectionRect
{
NSRect selectionRect = [self.expressionStorage.rootLayout boundingRectForRangePath:self.selection];
if (self.selection.length == 0) {
selectionRect.size.width = 1;
}
return selectionRect;
}
- (void)selectRangePathWithLocation:(NSIndexPath *)location length:(NSUInteger)length
{
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[location indexPathByRemovingLastIndex]];
NSUInteger locationIndex = location.lastIndex;
if (locationIndex > targetExpression.length) {
locationIndex = targetExpression.length;
}
NSUInteger lastSelectedIndex = location.lastIndex + length;
if (lastSelectedIndex > targetExpression.length) {
lastSelectedIndex = targetExpression.length;
}
self.selection = MPMakeRangePath([location indexPathByReplacingLastIndexWithIndex:locationIndex],lastSelectedIndex - locationIndex);
}
@end @end
@implementation MPExpressionView @implementation MPExpressionView
#pragma mark Creation Methods #pragma mark Creation Methods
- (instancetype)init
{
self = [super init];
if (self) {
[self initializeObjects];
}
return self;
}
- (id)initWithFrame:(NSRect)frame - (id)initWithFrame:(NSRect)frame
{ {
self = [super initWithFrame:frame]; self = [super initWithFrame:frame];
if (self) { if (self) {
[self initializeObjects]; [self initializeExpressionView];
} }
return self; return self;
} }
@@ -42,25 +117,44 @@
{ {
self = [super initWithCoder:aDecoder]; self = [super initWithCoder:aDecoder];
if (self) { if (self) {
[self initializeObjects]; [self initializeExpressionView];
} }
return self; return self;
} }
- (void)initializeObjects - (void)initializeExpressionView
{ {
MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] initWithExpressionView:self elements:@[@"12345", [[MPSumFunction alloc] init], [[MPSumFunction alloc] init]]]; MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] initWithElements:@[@"12345", [[MPSumFunction alloc] init], [[MPSumFunction alloc] init]]];
expressionStorage.expressionView = self;
_expressionStorage = expressionStorage; _expressionStorage = expressionStorage;
_caretLocation = [[NSIndexPath alloc] initWithIndex:0]; NSRect frame = NSMakeRect(10, 10, 500, 500);
NSButton *button = [[NSButton alloc] initWithFrame:frame];
button.buttonType = NSSwitchButton;
[button setTitle:@"Functions"];
self.functionsButton = button;
[self addSubview:self.functionsButton];
self.selection = [[MPRangePath alloc] initWithRange:NSMakeRange(0, 0)];
self.caretBlinkRate = 1.0;
[self restartCaretTimer];
} }
#pragma mark Properties #pragma mark Properties
- (void)setExpressionStorage:(MPExpressionStorage *)expressionStorage
- (BOOL)isFlipped
{ {
return NO; _expressionStorage.expressionView = nil;
_expressionStorage = expressionStorage;;
_expressionStorage.expressionView = self;
[self invalidateIntrinsicContentSize];
} }
- (void)setSelection:(MPRangePath *)selection
{
_selection = selection;
[self restartCaretTimer];
self.needsDisplay = YES;
}
#pragma mark NSView Stuff
- (BOOL)acceptsFirstResponder - (BOOL)acceptsFirstResponder
{ {
return YES; return YES;
@@ -71,29 +165,226 @@
return YES; return YES;
} }
#pragma mark Event Handling - (BOOL)isFlipped
{
return NO;
}
- (BOOL)isOpaque
{
return YES;
}
- (void)layout
{
NSSize buttonSize = [self.functionsButton fittingSize];
self.functionsButton.frame = NSMakeRect(self.bounds.size.width - buttonSize.width,
(self.bounds.size.height - buttonSize.height) / 2,
buttonSize.width,
buttonSize.height);
[super layout];
}
- (NSSize)intrinsicContentSize
{
// return NSMakeSize(500, 500);
// return self.bounds.size;
return self.expressionStorage.rootLayout.bounds.size;
}
- (void)resetCursorRects
{
[self addCursorRect:self.bounds
cursor:[NSCursor IBeamCursor]];
}
#pragma mark Key Event Handling
- (void)keyDown:(NSEvent *)theEvent - (void)keyDown:(NSEvent *)theEvent
{ {
NSString *characters = theEvent.characters;
if (characters.length == 1 && [characters stringByTrimmingCharactersInSet:[NSCharacterSet decimalDigitCharacterSet]].length == 0) {
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
[targetExpression replaceSymbolsInRange:self.selection.rangeAtLastIndex withElements:@[characters]];
self.selection = MPMakeRangePath([self.selection.location indexPathByIncrementingLastIndex], 0);
} else {
[self interpretKeyEvents:@[theEvent]]; [self interpretKeyEvents:@[theEvent]];
} }
}
- (void)moveRight:(id)sender - (void)moveRight:(id)sender
{ {
[self.expressionStorage deleteElementsInRange:NSMakeRange(0, 1)]; if (self.selection.length > 0) {
self.selection = MPMakeRangePath(self.selection.maxRangePath, 0);
} else {
NSIndexPath *newSelectionLocation = [self.selection.location indexPathByIncrementingLastIndex];
[self selectRangePathWithLocation:newSelectionLocation
length:0];
}
}
- (void)moveLeft:(id)sender
{
if (self.selection.length > 0) {
self.selection = MPMakeRangePath(self.selection.location, 0);
[self selectRangePathWithLocation:self.selection.location length:0];
} else {
NSUInteger selectionIndex = self.selection.location.lastIndex;
if (selectionIndex > 0) {
--selectionIndex;
}
NSIndexPath *newSelectionLocation = [self.selection.location indexPathByReplacingLastIndexWithIndex:selectionIndex];
[self selectRangePathWithLocation:newSelectionLocation
length:0];
}
}
- (void)moveWordRight:(id)sender
{
NSIndexPath *location;
if (self.selection.length > 0) {
location = self.selection.maxRangePath;
} else {
location = self.selection.location;
}
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[location indexPathByRemovingLastIndex]];
NSUInteger locationInTarget = NSMaxRange(self.selection.rangeAtLastIndex);
NSUInteger offset;
NSUInteger elementIndex = [targetExpression indexOfElementAtSymbolLocation:locationInTarget
offset:&offset];
NSUInteger newLocation = locationInTarget + [targetExpression elementAtIndex:elementIndex].length - offset;
[self selectRangePathWithLocation:[location indexPathByReplacingLastIndexWithIndex:newLocation]
length:0];
}
- (void)moveWordLeft:(id)sender
{
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
NSUInteger offset;
NSUInteger elementIndex = [targetExpression indexOfElementAtSymbolLocation:self.selection.location.lastIndex offset:&offset];
NSUInteger newLocation = self.selection.location.lastIndex;
if (offset > 0) {
newLocation -= newLocation - offset > 0 ? offset : newLocation;
} else if (newLocation > 0) {
newLocation -= [targetExpression elementAtIndex:elementIndex-1].length;
}
self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:newLocation], 0);
}
- (void)moveToBeginningOfLine:(id)sender
{
self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:0], 0);
}
- (void)moveToEndOfLine:(id)sender
{
}
- (void)moveLeftAndModifySelection:(id)sender
{
if (self.selection.length == 0) {
self.selectionModifyingStart = YES;
}
NSUInteger start = self.selection.location.lastIndex;
NSUInteger length = self.selection.length;
if (self.selectionModifyingStart) {
if (start > 0) {
--start;
++length;
}
} else {
--length;
}
self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:start], length);
}
- (void)moveRightAndModifySelection:(id)sender
{
if (self.selection.length == 0) {
self.selectionModifyingStart = NO;
}
NSUInteger start = self.selection.location.lastIndex;
NSUInteger length = self.selection.length;
if (self.selectionModifyingStart) {
++start;
--length;
} else {
++length;
}
[self selectRangePathWithLocation:[self.selection.location indexPathByReplacingLastIndexWithIndex:start]
length:length];
}
- (void)moveWordRightAndModifySelection:(id)sender
{
}
- (void)moveWordLeftAndModifySelection:(id)sender
{
}
- (void)selectAll:(id)sender
{
NSIndexPath *location = [NSIndexPath indexPathWithIndex:0];
self.selection = MPMakeRangePath(location, self.expressionStorage.length);
}
- (void)deleteBackward:(id)sender
{
MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
if (self.selection.length > 0) {
[targetExpression replaceSymbolsInRange:self.selection.rangeAtLastIndex withElements:@[]];
self.selection = MPMakeRangePath(self.selection.location, 0);
} else if (self.selection.location.lastIndex > 0) {
[targetExpression replaceSymbolsInRange:NSMakeRange(self.selection.location.lastIndex-1, 1) withElements:@[]];
self.selection = MPMakeRangePath([self.selection.location indexPathByDecrementingLastIndex], 0);
}
}
#pragma mark Mouse Event Handling
- (void)mouseDown:(NSEvent *)theEvent
{
NSPoint pointInView = [self convertPoint:theEvent.locationInWindow
fromView:nil];
NSPoint expressionOrigin = self.expressionOrigin;
pointInView.x -= expressionOrigin.x;
pointInView.y -= expressionOrigin.y;
NSIndexPath *selectionPath = [self.expressionStorage.rootLayout indexPathForMousePoint:pointInView];
self.selection = MPMakeRangePath(selectionPath, 0);
} }
#pragma mark Drawing Methods #pragma mark Drawing Methods
- (void)drawRect:(NSRect)dirtyRect - (void)drawRect:(NSRect)dirtyRect
{ {
// Draw the background
[super drawRect:dirtyRect]; [super drawRect:dirtyRect];
[[NSColor whiteColor] set]; [[NSColor whiteColor] set];
NSRectFill(self.bounds); NSRectFill(self.bounds);
// Calculate the position of the expression (probably also forces layout of the expression the first time)
NSPoint expressionOrigin = self.expressionOrigin;
// Draw the selection
if (self.caretVisible || self.selection.length > 0) {
if (self.selection.length == 0) {
[[NSColor blackColor] set]; [[NSColor blackColor] set];
NSSize expressionSize = [self.expressionStorage.rootLayout size]; } else {
CGFloat y = (self.bounds.size.height - expressionSize.height) / 2; [[NSColor selectedTextBackgroundColor] set];
NSPoint point = NSMakePoint(self.bounds.origin.x, self.bounds.origin.y + y); }
[self.expressionStorage.rootLayout drawAtPoint:point]; NSAffineTransform *transform = [NSAffineTransform transform];
[transform translateXBy:expressionOrigin.x
yBy:expressionOrigin.y];
[transform concat];
NSRectFill([self selectionRect]);
[transform invert];
[transform concat];
}
// Draw the expression
[[NSColor textColor] set];
[self.expressionStorage.rootLayout drawAtPoint:expressionOrigin];
} }
@end @end

View File

@@ -22,6 +22,7 @@
#pragma mark Working With the Expression Tree #pragma mark Working With the Expression Tree
@property (nonatomic, weak) MPExpression *parent; // Documentation: Do not set @property (nonatomic, weak) MPExpression *parent; // Documentation: Do not set
- (MPExpression *)rootExpression;
- (NSIndexPath *)indexPath; - (NSIndexPath *)indexPath;
- (NSUInteger)numberOfChildren; // Override - (NSUInteger)numberOfChildren; // Override

View File

@@ -24,6 +24,11 @@
} }
#pragma mark Working With the Expression Tree #pragma mark Working With the Expression Tree
- (MPExpression *)rootExpression
{
return [self.parent rootExpression];
}
- (NSIndexPath *)indexPath - (NSIndexPath *)indexPath
{ {
NSUInteger selfIndex = [self.parent indexOfElement:self]; NSUInteger selfIndex = [self.parent indexOfElement:self];
@@ -60,7 +65,7 @@
{ {
NSUInteger index = 0; NSUInteger index = 0;
for (; index < [self numberOfChildren]; index++) { for (; index < [self numberOfChildren]; index++) {
if ([[self childAtIndex:index] isEqualToExpression:child]) { if ([self childAtIndex:index] == child) {
return index; return index;
} }
} }
@@ -106,6 +111,7 @@
} }
#pragma mark Working With Functions #pragma mark Working With Functions
/*
- (BOOL)isEqual:(id)object - (BOOL)isEqual:(id)object
{ {
if (self == object) { if (self == object) {
@@ -123,7 +129,7 @@
- (BOOL)isEqualToFunction:(MPFunction *)aFunction - (BOOL)isEqualToFunction:(MPFunction *)aFunction
{ {
return [aFunction isMemberOfClass:[MPFunction class]] && [self isMemberOfClass:[MPFunction class]]; return [aFunction isMemberOfClass:[MPFunction class]] && [self isMemberOfClass:[MPFunction class]];
} }*/
- (NSString *)description - (NSString *)description
{ {

View File

@@ -12,6 +12,7 @@
@interface MPFunctionLayout : MPLayout @interface MPFunctionLayout : MPLayout
+ (MPFunctionLayout *)functionLayoutForFunctionAtIndexPath:(NSIndexPath *)path + (MPFunctionLayout *)functionLayoutForFunctionAtIndexPath:(NSIndexPath *)path
inRootExpression:(MPExpression *)rootExpression
parent:(MPExpressionLayout *)parent; parent:(MPExpressionLayout *)parent;
@property (readonly, nonatomic, weak) MPFunction *function; @property (readonly, nonatomic, weak) MPFunction *function;
@@ -31,7 +32,9 @@
// } // }
#pragma mark Size and Drawing Methods #pragma mark Size and Drawing Methods
- (NSSize)generateSize; // To be implemented - (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index; // To be implemented
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point; // To be implemented
- (NSRect)generateBounds; // To be implemented
- (void)drawAtPoint:(NSPoint)point; // To be implemented - (void)drawAtPoint:(NSPoint)point; // To be implemented
@end @end

View File

@@ -17,33 +17,34 @@
#pragma mark Creation Methods #pragma mark Creation Methods
+ (MPFunctionLayout *)functionLayoutForFunctionAtIndexPath:(NSIndexPath *)path + (MPFunctionLayout *)functionLayoutForFunctionAtIndexPath:(NSIndexPath *)path
inRootExpression:(MPExpression *)rootExpression
parent:(MPExpressionLayout *)parent parent:(MPExpressionLayout *)parent
{ {
MPFunction *function = [parent.expressionStorage elementAtIndexPath:path]; MPFunction *function = [rootExpression elementAtIndexPath:path];
Class class = [function class]; Class class = [function class];
if (class == [MPSumFunction class]) { if (class == [MPSumFunction class]) {
return [[MPSumFunctionLayout alloc] initWithPath:path parent:parent]; return [[MPSumFunctionLayout alloc] initWithElementAtPath:path
inRootExpression:rootExpression
parent:parent];
} }
return [[self alloc] initWithPath:path parent:parent]; return [[self alloc] initWithElementAtPath:path
inRootExpression:rootExpression
parent:parent];
} }
- (instancetype)initWithPath:(NSIndexPath *)path - (instancetype)initWithElementAtPath:(NSIndexPath *)path
inRootExpression:(MPExpression *)rootExpression
parent:(MPLayout *)parent parent:(MPLayout *)parent
{ {
self = [super initWithPath:path self = [super initWithElementAtPath:path
inRootExpression:rootExpression
parent:parent]; parent:parent];
if (self) { if (self) {
_function = [parent.expressionStorage elementAtIndexPath:path]; _function = [rootExpression elementAtIndexPath:path];
} }
return self; return self;
} }
#pragma mark Properties
//- (MPFunction *)function
//{
// return [self.expressionStorage elementAtIndexPath:self.path];
//}
#pragma mark Cache Methods #pragma mark Cache Methods
- (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index - (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index
generator:(CTLineRef (^)())generator generator:(CTLineRef (^)())generator
@@ -62,26 +63,22 @@
{ {
return [self cachableObjectForIndex:index generator:^id{ return [self cachableObjectForIndex:index generator:^id{
NSIndexPath *childPath = [self.function.indexPath indexPathByAddingIndex:index]; NSIndexPath *childPath = [self.function.indexPath indexPathByAddingIndex:index];
MPExpressionLayout *layout = [[MPExpressionLayout alloc] initWithPath:childPath MPExpressionLayout *layout = [[MPExpressionLayout alloc] initWithElementAtPath:childPath
inRootExpression:self.function.rootExpression
parent:self]; parent:self];
layout.flipped = self.flipped;
layout.usesSmallSize = (index == 0 || index == 1) ? YES : self.usesSmallSize;
return layout; return layout;
}]; }];
} }
#pragma mark Size and Drawing Methods #pragma mark Size and Drawing Methods
- (NSRect)generateBounds
- (NSSize)sizeForChildAtIndex:(NSUInteger)index
{ {
MPLayout *childLayout = [self childLayoutAtIndex:index]; return NSZeroRect;
return [childLayout size];
} }
- (NSSize)generateSize - (void)draw
{
return NSZeroSize;
}
- (void)drawAtPoint:(NSPoint)point
{ {
} }

View File

@@ -15,11 +15,17 @@
#pragma mark Creation Methods #pragma mark Creation Methods
- (instancetype)init; - (instancetype)init;
- (instancetype)initWithPath:(NSIndexPath *)path - (instancetype)initWithElementAtPath:(NSIndexPath *)path
inRootExpression:(MPExpression *)rootExpression
parent:(MPLayout *)parent; parent:(MPLayout *)parent;
#pragma mark Text System Objects #pragma mark Properties
@property (readonly, nonatomic, weak) MPExpressionStorage *expressionStorage; - (NSFont *)font;
- (CGFloat)fontSize;
- (NSFont *)normalFont;
- (CGFloat)normalFontSize;
- (NSFont *)smallFont;
- (CGFloat)smallFontSize;
#pragma mark Cache Tree #pragma mark Cache Tree
@property (readonly, nonatomic, weak) MPLayout *parent; @property (readonly, nonatomic, weak) MPLayout *parent;
@@ -35,14 +41,21 @@
- (void)invalidate; - (void)invalidate;
#pragma mark Calculation and Drawing Methods #pragma mark Calculation and Drawing Methods
// TODO: Implement Small Size @property (nonatomic, getter = isFlipped) BOOL flipped;
// @property (nonatomic) BOOL usesSmallSize; @property (nonatomic) BOOL usesSmallSize;
- (NSSize)size; - (NSRect)bounds;
- (NSRect)boundingRectForRangePath:(MPRangePath *)rangePath; /* if rangePath.length is 0 the returned rect will have a width of 0 */
- (void)drawAtPoint:(NSPoint)point; - (void)drawAtPoint:(NSPoint)point;
@end @end
@interface MPLayout (MPSubclassImplement) @interface MPLayout (MPSubclassImplement)
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index; // To be implemented - (MPLayout *)childLayoutAtIndex:(NSUInteger)index; // To be implemented
- (NSSize)generateSize; // To be implemented - (NSRect)generateBounds; // To be implemented
- (NSRect)boundingRectForRange:(NSRange)range; // To be implemented, use rangePath instead, this one has wrong origin
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index;
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point;
- (void)draw; // To be implemented
@end @end

View File

@@ -7,6 +7,9 @@
// //
#import "MPLayout.h" #import "MPLayout.h"
#import "MPRangePath.h"
#import "NSIndexPath+MPAdditions.h"
@interface MPLayout () @interface MPLayout ()
@@ -18,7 +21,7 @@
@implementation MPLayout { @implementation MPLayout {
NSMutableArray *_cache; NSMutableArray *_cache;
NSSize _cachedSize; NSRect _cachedBounds;
} }
#pragma mark Creation Methods #pragma mark Creation Methods
@@ -27,12 +30,13 @@
self = [super init]; self = [super init];
if (self) { if (self) {
_cache = [[NSMutableArray alloc] init]; _cache = [[NSMutableArray alloc] init];
_cachedSize = NSZeroSize; _cachedBounds = NSZeroRect;
} }
return self; return self;
} }
- (id)initWithPath:(NSIndexPath *)path - (instancetype)initWithElementAtPath:(NSIndexPath *)path
inRootExpression:(MPExpression *)rootExpression
parent:(MPLayout *)parent parent:(MPLayout *)parent
{ {
self = [self init]; self = [self init];
@@ -42,10 +46,37 @@
return self; return self;
} }
#pragma Text System Objects #pragma mark Properties
- (MPExpressionStorage *)expressionStorage - (NSFont *)font
{ {
return self.parent.expressionStorage; return self.usesSmallSize ? self.smallFont : self.normalFont;
}
- (CGFloat)fontSize
{
return self.usesSmallSize ? self.smallFontSize : self.normalFontSize;
}
- (NSFont *)normalFont
{
return [NSFont fontWithName:@"CMU Serif"
size:self.fontSize];
}
- (CGFloat)normalFontSize
{
return 18.0;
}
- (NSFont *)smallFont
{
return [NSFont fontWithName:@"CMU Serif"
size:self.smallFontSize];
}
- (CGFloat)smallFontSize
{
return 12.0;
} }
#pragma mark Cache Tree #pragma mark Cache Tree
@@ -92,22 +123,42 @@
- (void)invalidate - (void)invalidate
{ {
_cachedSize = NSZeroSize; _cachedBounds = NSZeroRect;
[self.parent invalidate]; [self.parent invalidate];
} }
#pragma mark Calculation and Drawing Methods #pragma mark Calculation and Drawing Methods
- (NSSize)size - (NSRect)bounds
{ {
if (NSEqualSizes(_cachedSize, NSZeroSize)) { if (NSEqualRects(_cachedBounds, NSZeroRect)) {
_cachedSize = [self generateSize]; _cachedBounds = [self generateBounds];
} }
return _cachedSize; return _cachedBounds;
}
- (NSRect)boundingRectForRangePath:(MPRangePath *)rangePath
{
if (rangePath.location.length == 1) {
return [self boundingRectForRange:rangePath.rangeAtLastIndex];
}
NSUInteger nextIndex = [rangePath.location indexAtPosition:0];
NSIndexPath *newLocation = [rangePath.location indexPathByRemovingFirstIndex];
MPRangePath *newRangePath = [[MPRangePath alloc] initWithLocation:newLocation length:rangePath.length];
NSRect bounds = [[self childLayoutAtIndex:nextIndex] boundingRectForRangePath:newRangePath];
NSPoint offset = [self offsetOfChildLayoutAtIndex:nextIndex];
bounds.origin = NSMakePoint(bounds.origin.x + offset.x, bounds.origin.y + offset.y);
return bounds;
} }
- (void)drawAtPoint:(NSPoint)point - (void)drawAtPoint:(NSPoint)point
{ {
NSAffineTransform *transform = [NSAffineTransform transform];
[transform translateXBy:point.x
yBy:point.y];
[transform concat];
[self draw];
[transform invert];
[transform concat];
} }
@end @end
@@ -119,14 +170,24 @@
return nil; return nil;
} }
- (NSSize)sizeForChildAtIndex:(NSUInteger)index - (NSRect)generateBounds
{ {
return NSZeroSize; return NSZeroRect;
} }
- (NSSize)generateSize - (NSRect)boundingRectForRange:(NSRange)range
{ {
return NSZeroSize; return NSZeroRect;
}
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
{
return NSZeroPoint;
}
- (void)draw
{
} }
@end @end

View File

@@ -10,6 +10,8 @@
#import "MPExpression.h" #import "MPExpression.h"
#define MPMakeRangePath(loc, len) [MPRangePath rangePathWithLocation:(loc) length:(len)]
@class MPRangePath, MPExpression; @class MPRangePath, MPExpression;
@interface MPRangePath : NSObject <NSCopying, NSCoding> @interface MPRangePath : NSObject <NSCopying, NSCoding>

View File

@@ -13,8 +13,8 @@
@interface MPSumFunction : MPFunction @interface MPSumFunction : MPFunction
@property (nonatomic, strong) MPExpression *startExpression; @property (nonatomic, strong) MPExpression *startExpression; // Index 0
@property (nonatomic, strong) MPExpression *targetExpression; @property (nonatomic, strong) MPExpression *targetExpression; // Index 1
@property (nonatomic, strong) MPExpression *sumExpression; @property (nonatomic, strong) MPExpression *sumExpression; // Index 2
@end @end

View File

@@ -104,6 +104,7 @@
} }
#pragma mark Working With Functions #pragma mark Working With Functions
/*
- (BOOL)isEqualToFunction:(MPFunction *)aFunction - (BOOL)isEqualToFunction:(MPFunction *)aFunction
{ {
if (![aFunction isKindOfClass:[MPSumFunction class]]) { if (![aFunction isKindOfClass:[MPSumFunction class]]) {
@@ -117,7 +118,7 @@
return NO; return NO;
} }
return [self.sumExpression isEqualToExpression:sumFunction.sumExpression]; return [self.sumExpression isEqualToExpression:sumFunction.sumExpression];
} }*/
- (NSString *)description - (NSString *)description
{ {

View File

@@ -9,6 +9,13 @@
#import "MPSumFunctionLayout.h" #import "MPSumFunctionLayout.h"
#import "MPSumFunction.h" #import "MPSumFunction.h"
#import "NSIndexPath+MPAdditions.h"
#define kSumFunctionStartExpressionOffset 3
#define kSumFunctionTargetExpressionOffset 0
#define kSumFunctionSumExpressionOffset 3
#define kSumFunctionTrailingOffset 5
@implementation MPSumFunctionLayout @implementation MPSumFunctionLayout
- (MPSumFunction *)sumFunction - (MPSumFunction *)sumFunction
@@ -21,7 +28,7 @@
CTLineRef line = [self lineForPrivateCacheIndex:0 generator:^CTLineRef{ CTLineRef line = [self lineForPrivateCacheIndex:0 generator:^CTLineRef{
NSAttributedString *text = NSAttributedString *text =
[[NSAttributedString alloc] initWithString:@"∑" [[NSAttributedString alloc] initWithString:@"∑"
attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Lucida Grande" size:18.0]}]; attributes:@{NSFontAttributeName: self.font}];
CFAttributedStringRef attributedString = CFBridgingRetain(text); CFAttributedStringRef attributedString = CFBridgingRetain(text);
CTLineRef line = CTLineCreateWithAttributedString(attributedString); CTLineRef line = CTLineCreateWithAttributedString(attributedString);
CFRelease(attributedString); // TODO: Is this release appropriate CFRelease(attributedString); // TODO: Is this release appropriate
@@ -30,16 +37,85 @@
return line; return line;
} }
- (NSSize)generateSize - (NSRect)localLineBounds
{ {
CTLineRef line = [self line]; NSRect lineBounds = CTLineGetBoundsWithOptions(self.line, 0);
CFRetain(line); CGFloat width = MAX(MAX([self childLayoutAtIndex:0].bounds.size.width, [self childLayoutAtIndex:1].bounds.size.width), lineBounds.size.width);
CGSize size = CTLineGetBoundsWithOptions(line, /*kCTLineBoundsUseOpticalBounds*/0).size; CGFloat xPosition = (width - lineBounds.size.width) / 2;
CFRelease(line); return NSMakeRect(xPosition, lineBounds.origin.y, lineBounds.size.width, lineBounds.size.height);
return size;
} }
- (void)drawAtPoint:(NSPoint)point - (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
{
NSRect childBounds = [self childLayoutAtIndex:index].bounds;
NSRect localLineBounds = [self localLineBounds];
NSPoint offset;
if (index == 0) {
// Start Expression
offset.x = localLineBounds.origin.x + localLineBounds.size.width / 2 - childBounds.size.width / 2;
offset.y = -kSumFunctionStartExpressionOffset - childBounds.size.height;
} else if (index == 1) {
// Target Expression
offset.x = localLineBounds.origin.x + localLineBounds.size.width / 2 - childBounds.size.width / 2;
offset.y = localLineBounds.size.height + kSumFunctionTargetExpressionOffset;
} else {
// Sum Expression
MPLayout *startExpressionLayout = [self childLayoutAtIndex:0];
MPLayout *targetExpressionLayout = [self childLayoutAtIndex:1];
CGFloat sumWidth = MAX(MAX(localLineBounds.origin.x + localLineBounds.size.width, startExpressionLayout.bounds.size.width), targetExpressionLayout.bounds.size.width);
offset.x = sumWidth + kSumFunctionSumExpressionOffset;
offset.y = 0;
}
return offset;
}
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point
{
// A single index is used to communicate back wether the
// selection should be before or after the function.
// A 0 means before, a 1 means after.
for (NSUInteger index = 0; index < self.function.numberOfChildren; index++) {
MPLayout *childLayout = [self childLayoutAtIndex:index];
NSRect childBounds = childLayout.bounds;
NSPoint childOffset = [self offsetOfChildLayoutAtIndex:index];
childBounds.origin.x += childOffset.x;
childBounds.origin.y += childOffset.y;
if (NSMouseInRect(point, childBounds, self.flipped)) {
NSPoint pointInChild = NSMakePoint(point.x + childOffset.x, point.y + childOffset.y);
NSIndexPath *subPath = [childLayout indexPathForMousePoint:pointInChild];
return [subPath indexPathByPreceedingIndex:index];
}
}
if (point.x < CTLineGetBoundsWithOptions(self.line, 0).size.width / 2) {
return [NSIndexPath indexPathWithIndex:0];
} else {
return [NSIndexPath indexPathWithIndex:1];
}
}
- (NSRect)generateBounds
{
NSRect lineBounds = CTLineGetBoundsWithOptions(self.line, 0);
NSRect startExpressionBounds = [self childLayoutAtIndex:0].bounds;
NSRect targetExpressionBounds = [self childLayoutAtIndex:1].bounds;
NSRect sumExpressionBounds = [self childLayoutAtIndex:2].bounds;
NSRect bounds = lineBounds;
bounds.size.width = MAX(lineBounds.size.width, startExpressionBounds.size.width);
bounds.size.height += startExpressionBounds.size.height + kSumFunctionStartExpressionOffset;
bounds.size.width = MAX(bounds.size.width, targetExpressionBounds.size.width);
bounds.size.height += targetExpressionBounds.size.height + kSumFunctionTargetExpressionOffset;
bounds.size.width += kSumFunctionSumExpressionOffset + sumExpressionBounds.size.width;
bounds.size.height = MAX(bounds.size.height, sumExpressionBounds.size.height);
bounds.origin.y -= targetExpressionBounds.size.height + kSumFunctionStartExpressionOffset;
bounds.size.width += kSumFunctionTrailingOffset;
return bounds;
}
- (void)draw
{ {
// Get the current context // Get the current context
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
@@ -47,14 +123,33 @@
// Set the text matrix // Set the text matrix
CGContextSetTextMatrix(context, CGAffineTransformIdentity); CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// Get the line of text // Draw the sum symbol
CTLineRef line = [self line]; CTLineRef line = [self line];
CFRetain(line); CFRetain(line);
NSRect localLineBounds = [self localLineBounds];
// Draw the line CGContextSetTextPosition(context, localLineBounds.origin.x, 0);
CGContextSetTextPosition(context, point.x, point.y);
CTLineDraw(line, context); CTLineDraw(line, context);
// Draw the start function
MPLayout *startExpressionLayout = [self childLayoutAtIndex:0];
NSPoint startExpressionLocation = NSMakePoint(localLineBounds.origin.x + localLineBounds.size.width / 2, 0);
startExpressionLocation.x -= startExpressionLayout.bounds.size.width / 2;
startExpressionLocation.y -= startExpressionLayout.bounds.size.height + kSumFunctionStartExpressionOffset;
[startExpressionLayout drawAtPoint:startExpressionLocation];
// Draw the target function
MPLayout *targetExpressionLayout = [self childLayoutAtIndex:1];
NSPoint targetExpressionLocation = NSMakePoint(localLineBounds.origin.x + localLineBounds.size.width / 2, localLineBounds.size.height);
targetExpressionLocation.x -= targetExpressionLayout.bounds.size.width / 2;
targetExpressionLocation.y += kSumFunctionTargetExpressionOffset;
[targetExpressionLayout drawAtPoint:targetExpressionLocation];
// Draw the sum function
MPLayout *sumExpressionLayout = [self childLayoutAtIndex:2];
CGFloat sumWidth = MAX(MAX(localLineBounds.origin.x + localLineBounds.size.width, startExpressionLayout.bounds.size.width), targetExpressionLayout.bounds.size.width);
sumWidth += kSumFunctionSumExpressionOffset;
[sumExpressionLayout drawAtPoint:NSMakePoint(sumWidth, 0)];
CFRelease(line); CFRelease(line);
} }

View File

@@ -51,6 +51,8 @@
<string>Copyright © 2014 Kim Wittenburg. All rights reserved.</string> <string>Copyright © 2014 Kim Wittenburg. All rights reserved.</string>
<key>NSMainNibFile</key> <key>NSMainNibFile</key>
<string>MainMenu</string> <string>MainMenu</string>
<key>ATSApplicationFontsPath</key>
<string>Fonts</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>
<string>NSApplication</string> <string>NSApplication</string>
</dict> </dict>

View File

@@ -10,6 +10,10 @@
@interface NSIndexPath (MPAdditions) @interface NSIndexPath (MPAdditions)
- (NSUInteger)lastIndex;
- (NSIndexPath *)indexPathByReplacingLastIndexWithIndex:(NSUInteger)index;
/*! /*!
@method indexPathByRemovingFirstIndex @method indexPathByRemovingFirstIndex
@brief Provides an index path with the indexes in the receiving index path, excluding the first one. @brief Provides an index path with the indexes in the receiving index path, excluding the first one.
@@ -27,4 +31,7 @@
*/ */
- (NSIndexPath *)indexPathByPreceedingIndex:(NSUInteger)index; - (NSIndexPath *)indexPathByPreceedingIndex:(NSUInteger)index;
- (NSIndexPath *)indexPathByIncrementingLastIndex;
- (NSIndexPath *)indexPathByDecrementingLastIndex;
@end @end

View File

@@ -10,6 +10,16 @@
@implementation NSIndexPath (MPAdditions) @implementation NSIndexPath (MPAdditions)
- (NSUInteger)lastIndex
{
return [self indexAtPosition:self.length-1];
}
- (NSIndexPath *)indexPathByReplacingLastIndexWithIndex:(NSUInteger)index
{
return [[self indexPathByRemovingLastIndex] indexPathByAddingIndex:index];
}
- (NSIndexPath *)indexPathByRemovingFirstIndex - (NSIndexPath *)indexPathByRemovingFirstIndex
{ {
if (self.length <= 1) { if (self.length <= 1) {
@@ -34,4 +44,18 @@
return [[NSIndexPath alloc] initWithIndexes:newIndexes length:self.length+1]; return [[NSIndexPath alloc] initWithIndexes:newIndexes length:self.length+1];
} }
- (NSIndexPath *)indexPathByIncrementingLastIndex
{
NSUInteger lastIndex = [self lastIndex];
lastIndex++;
return [[self indexPathByRemovingLastIndex] indexPathByAddingIndex:lastIndex];
}
- (NSIndexPath *)indexPathByDecrementingLastIndex
{
NSUInteger lastIndex = [self lastIndex];
lastIndex--;
return [[self indexPathByRemovingLastIndex] indexPathByAddingIndex:lastIndex];
}
@end @end