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:
@@ -27,12 +27,13 @@
|
||||
3BBBA35E1903FD3600824E74 /* 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 */; };
|
||||
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 */; };
|
||||
3BF9977918DE623E009CF6C4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3BF9977718DE623E009CF6C4 /* InfoPlist.strings */; };
|
||||
3BF9977B18DE623E009CF6C4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BF9977A18DE623E009CF6C4 /* main.m */; };
|
||||
3BF9977F18DE623E009CF6C4 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 3BF9977D18DE623E009CF6C4 /* Credits.rtf */; };
|
||||
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 */; };
|
||||
3BF9978A18DE623E009CF6C4 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3BF9978918DE623E009CF6C4 /* Images.xcassets */; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
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; };
|
||||
@@ -179,6 +181,8 @@
|
||||
children = (
|
||||
3BF9978318DE623E009CF6C4 /* MPDocument.xib */,
|
||||
3BF9978618DE623E009CF6C4 /* MainMenu.xib */,
|
||||
3BF9978918DE623E009CF6C4 /* Images.xcassets */,
|
||||
3BC4661319B245C60033F13A /* Fonts */,
|
||||
);
|
||||
name = Resources;
|
||||
sourceTree = "<group>";
|
||||
@@ -268,7 +272,6 @@
|
||||
3B87E351190082BB00259938 /* View */,
|
||||
3B87E352190082C000259938 /* Controller */,
|
||||
3B87E353190082E200259938 /* Resources */,
|
||||
3BF9978918DE623E009CF6C4 /* Images.xcassets */,
|
||||
3BF9977518DE623E009CF6C4 /* Supporting Files */,
|
||||
);
|
||||
path = MathPad;
|
||||
@@ -384,7 +387,8 @@
|
||||
files = (
|
||||
3BF9977918DE623E009CF6C4 /* InfoPlist.strings 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 */,
|
||||
3BF9978818DE623E009CF6C4 /* MainMenu.xib in Resources */,
|
||||
);
|
||||
|
||||
@@ -14,37 +14,23 @@
|
||||
<customObject id="-3" userLabel="Application"/>
|
||||
<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"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="133" y="235" width="507" height="240"/>
|
||||
<rect key="contentRect" x="525" y="411" width="507" height="251"/>
|
||||
<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">
|
||||
<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"/>
|
||||
<subviews>
|
||||
<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"/>
|
||||
</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>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="lcd-Ip-jjR" secondAttribute="trailing" constant="20" symbolic="YES" id="3tX-J3-Wte"/>
|
||||
<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="leading" secondItem="gIp-Ho-8D9" secondAttribute="leading" constant="20" symbolic="YES" id="XN3-k3-tOU"/>
|
||||
<constraint firstItem="lcd-Ip-jjR" firstAttribute="top" secondItem="gIp-Ho-8D9" secondAttribute="top" constant="20" symbolic="YES" id="gqS-BG-xpS"/>
|
||||
<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"/>
|
||||
<constraint firstItem="lcd-Ip-jjR" firstAttribute="leading" secondItem="gIp-Ho-8D9" secondAttribute="leading" constant="20" symbolic="YES" id="8Li-0i-x3o"/>
|
||||
<constraint firstItem="lcd-Ip-jjR" firstAttribute="top" secondItem="gIp-Ho-8D9" secondAttribute="top" constant="20" symbolic="YES" id="MPQ-lK-4d0"/>
|
||||
<constraint firstAttribute="bottom" secondItem="lcd-Ip-jjR" secondAttribute="bottom" constant="20" symbolic="YES" id="hi3-fp-zJn"/>
|
||||
<constraint firstAttribute="trailing" secondItem="lcd-Ip-jjR" secondAttribute="trailing" constant="20" symbolic="YES" id="xiK-zs-2rs"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
|
||||
@@ -216,6 +216,32 @@
|
||||
- (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:
|
||||
@brief Replaces the elements in the given range with the contents of the
|
||||
@@ -242,7 +268,6 @@
|
||||
*/
|
||||
- (void)replaceSymbolsInRange:(NSRange)range
|
||||
withElements:(NSArray *)elements;
|
||||
// TODO: - (NSUInteger)indexOfElementAtSymbolLocation:(NSUInteger)location;
|
||||
|
||||
#warning Evaluating must possibly return error
|
||||
- (double)doubleValue; // Evaluates Expression
|
||||
@@ -304,6 +329,18 @@
|
||||
#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:
|
||||
@brief Returns the element at the specified index path.
|
||||
@@ -511,6 +548,19 @@
|
||||
*/
|
||||
- (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
|
||||
|
||||
@@ -20,13 +20,10 @@
|
||||
@interface MPExpression (MPExpressionPrivate)
|
||||
|
||||
- (NSUInteger)lengthOfElements:(NSArray *)elements;
|
||||
- (NSUInteger)indexOfElementAtLocation:(NSUInteger)location;
|
||||
|
||||
- (void)validateElements:(NSArray *)elements;
|
||||
- (BOOL)splitElementsAtLocation:(NSUInteger)location
|
||||
insertionIndex:(out NSUInteger *)insertionIndex;
|
||||
- (NSUInteger)calculateSplitOffsetForSplitLocation:(NSUInteger)location
|
||||
inElementAtIndex:(out NSUInteger *)elementIndex;
|
||||
|
||||
@end
|
||||
|
||||
@@ -41,13 +38,6 @@
|
||||
return length;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfElementAtLocation:(NSUInteger)location
|
||||
{
|
||||
NSUInteger index = 0;
|
||||
[self calculateSplitOffsetForSplitLocation:location inElementAtIndex:&index];
|
||||
return index;
|
||||
}
|
||||
|
||||
- (void)validateElements:(NSArray *)elements
|
||||
{
|
||||
for (id element in elements) {
|
||||
@@ -66,18 +56,14 @@
|
||||
*insertionIndex = 0;
|
||||
return NO;
|
||||
}
|
||||
NSUInteger splitElementIndex;
|
||||
NSUInteger splitOffset = [self calculateSplitOffsetForSplitLocation:location
|
||||
inElementAtIndex:&splitElementIndex];
|
||||
id<MPExpressionElement> splitElement = self.elements[splitElementIndex];
|
||||
if (splitOffset == splitElement.length) {
|
||||
splitOffset = 0;
|
||||
splitElementIndex++;
|
||||
}
|
||||
|
||||
NSUInteger splitOffset;
|
||||
NSUInteger splitElementIndex = [self indexOfElementAtSymbolLocation:location
|
||||
offset:&splitOffset];
|
||||
if (splitOffset != 0) {
|
||||
NSString *stringElement = (NSString *)splitElement;
|
||||
NSString *leftPart = [stringElement substringToIndex:splitOffset];
|
||||
NSString *rightPart = [stringElement substringFromIndex:splitOffset];
|
||||
NSString *splitElement = (NSString *)self.elements[splitElementIndex];
|
||||
NSString *leftPart = [splitElement substringToIndex:splitOffset];
|
||||
NSString *rightPart = [splitElement substringFromIndex:splitOffset];
|
||||
[self.elements replaceObjectsInRange:NSMakeRange(splitElementIndex, 1)
|
||||
withObjectsFromArray:@[leftPart, rightPart]];
|
||||
++splitElementIndex;
|
||||
@@ -86,24 +72,6 @@
|
||||
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
|
||||
|
||||
@implementation MPExpression {
|
||||
@@ -215,6 +183,41 @@
|
||||
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
|
||||
withElements:(NSArray *)elements
|
||||
{
|
||||
@@ -276,7 +279,7 @@
|
||||
}
|
||||
|
||||
#pragma mark Basic NSObject Methods
|
||||
|
||||
/*
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self == object) {
|
||||
@@ -295,7 +298,7 @@
|
||||
{
|
||||
return [self.elements isEqualToArray:anExpression.elements];
|
||||
}
|
||||
|
||||
*/
|
||||
- (NSString *)description
|
||||
{
|
||||
#warning Bad Implementation
|
||||
@@ -359,6 +362,14 @@
|
||||
@implementation MPExpression (MPExpressionExtension)
|
||||
|
||||
#pragma mark Working With the Expression Tree
|
||||
- (MPExpression *)rootExpression
|
||||
{
|
||||
if (self.parent == nil) {
|
||||
return self;
|
||||
}
|
||||
return [self.parent rootExpression];
|
||||
}
|
||||
|
||||
- (id)elementAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.length == 0) {
|
||||
@@ -369,7 +380,7 @@
|
||||
return element;
|
||||
}
|
||||
if ([element isFunction]) {
|
||||
return [(MPFunction *)element elementAtIndexPath:[indexPath indexPathByRemovingLastIndex]];
|
||||
return [(MPFunction *)element elementAtIndexPath:[indexPath indexPathByRemovingFirstIndex]];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
@interface MPExpressionLayout : MPLayout
|
||||
|
||||
- (instancetype)initRootLayoutWithExpressionStorage:(MPExpressionStorage *)expressionStorage;
|
||||
- (instancetype)initRootLayoutWithExpression:(MPExpression *)expression;
|
||||
|
||||
@property (readonly, nonatomic, weak) MPExpression *expression;
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#import "MPExpressionLayout.h"
|
||||
#import "MPFunctionLayout.h"
|
||||
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
#define kMPEmptyBoxWidth (self.usesSmallSize ? 2.0 : 3.0)
|
||||
|
||||
@interface MPExpressionLayout (MPLineGeneration)
|
||||
|
||||
- (CTLineRef)lineForElementAtIndex:(NSUInteger)index;
|
||||
@@ -34,9 +38,8 @@
|
||||
|
||||
- (CTLineRef)createLineForString:(NSString *)aString
|
||||
{
|
||||
|
||||
NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString
|
||||
attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Lucida Grande" size:18.0]}];
|
||||
attributes:@{NSFontAttributeName: self.font}];
|
||||
CFAttributedStringRef attributedString = CFBridgingRetain(text);
|
||||
CTLineRef line = CTLineCreateWithAttributedString(attributedString);
|
||||
CFRelease(attributedString); // TODO: Is this release appropriate?
|
||||
@@ -48,49 +51,38 @@
|
||||
@implementation MPExpressionLayout
|
||||
|
||||
# pragma mark Creation Methods
|
||||
- (instancetype)initRootLayoutWithExpressionStorage:(MPExpressionStorage *)expressionStorage
|
||||
- (instancetype)initRootLayoutWithExpression:(MPExpression *)expression
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_expressionStorage = expressionStorage;
|
||||
_expression = expressionStorage;
|
||||
_expression = expression;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPath:(NSIndexPath *)path
|
||||
parent:(MPLayout *)parent
|
||||
- (instancetype)initWithElementAtPath:(NSIndexPath *)path
|
||||
inRootExpression:(MPExpression *)rootExpression
|
||||
parent:(MPLayout *)parent
|
||||
{
|
||||
self = [super initWithPath:path
|
||||
parent:parent];
|
||||
self = [super initWithElementAtPath:path
|
||||
inRootExpression:rootExpression
|
||||
parent:parent];
|
||||
if (self) {
|
||||
_expression = [parent.expressionStorage elementAtIndexPath:path];
|
||||
_expression = [rootExpression elementAtIndexPath:path];
|
||||
}
|
||||
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
|
||||
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index
|
||||
{
|
||||
id cachedObject = [self cachableObjectForIndex:index generator:^id{
|
||||
NSIndexPath *indexPath = [self.expression.indexPath indexPathByAddingIndex:index];
|
||||
MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunctionAtIndexPath:indexPath
|
||||
inRootExpression:self.expression.rootExpression
|
||||
parent:self];
|
||||
layout.flipped = self.flipped;
|
||||
layout.usesSmallSize = self.usesSmallSize;
|
||||
return layout;
|
||||
}];
|
||||
if ([cachedObject isKindOfClass:[MPLayout class]]) {
|
||||
@@ -99,51 +91,165 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSSize)sizeForElementAtIndex:(NSUInteger)index
|
||||
- (NSRect)boundsOfElementAtIndex:(NSUInteger)index
|
||||
{
|
||||
id symbol = [self.expression elementAtIndex:index];
|
||||
if ([symbol isString]) {
|
||||
CTLineRef line = [self lineForElementAtIndex:index];
|
||||
CFRetain(line);
|
||||
CGRect bounds = CTLineGetBoundsWithOptions(line, /*kCTLineBoundsUseOpticalBounds*/ 0);
|
||||
CGRect bounds = CTLineGetBoundsWithOptions(line, 0);
|
||||
CFRelease(line);
|
||||
return bounds.size;
|
||||
return bounds;
|
||||
} else {
|
||||
return [self childLayoutAtIndex:index].size;
|
||||
return [self childLayoutAtIndex:index].bounds;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Drawing Methods
|
||||
- (NSSize)generateSize
|
||||
- (NSRect)generateBounds
|
||||
{
|
||||
CGFloat width = 0, height = 0;
|
||||
for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) {
|
||||
NSSize elementSize = [self sizeForElementAtIndex:index];
|
||||
width += elementSize.width;
|
||||
height = MAX(height, elementSize.height);
|
||||
if (self.expression.numberOfElements == 0) {
|
||||
return NSMakeRect(0, [self.font descender], kMPEmptyBoxWidth, self.fontSize);
|
||||
}
|
||||
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
|
||||
CGContextRef context =
|
||||
(CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
|
||||
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
|
||||
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
|
||||
|
||||
// Track the x position
|
||||
CGFloat x = point.x;
|
||||
|
||||
CGFloat x = 0;
|
||||
|
||||
for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) {
|
||||
// The current element
|
||||
id element = [self.expression elementAtIndex:index];
|
||||
NSSize elementSize = [self sizeForElementAtIndex:index];
|
||||
CGFloat dy = (self.size.height - elementSize.height) / 2;
|
||||
CGFloat y = point.y + dy;
|
||||
NSRect elementBounds = [self boundsOfElementAtIndex:index];
|
||||
|
||||
if ([element isString]) {
|
||||
// Get the line to draw
|
||||
@@ -151,7 +257,7 @@
|
||||
CFRetain(line);
|
||||
|
||||
// Move to the appropriate position
|
||||
CGContextSetTextPosition(context, x, y);
|
||||
CGContextSetTextPosition(context, x, 0);
|
||||
|
||||
// Perform the drawing
|
||||
CTLineDraw(line, context);
|
||||
@@ -160,9 +266,9 @@
|
||||
} else {
|
||||
// Let the child layout draw itself
|
||||
MPLayout *layout = [self childLayoutAtIndex:index];
|
||||
[layout drawAtPoint:NSMakePoint(x, y)];
|
||||
[layout drawAtPoint:NSMakePoint(x, 0)];
|
||||
}
|
||||
x += elementSize.width;
|
||||
x += elementBounds.size.width;
|
||||
}
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
@@ -12,14 +12,9 @@
|
||||
|
||||
@interface MPExpressionStorage : MPExpression
|
||||
|
||||
- (instancetype)initWithExpressionView:(MPExpressionView *)expressionView
|
||||
elements:(NSArray *)elements;
|
||||
- (instancetype)initWithElements:(NSArray *)elements;
|
||||
|
||||
@property (readonly, nonatomic, weak) MPExpressionView *expressionView;
|
||||
@property (nonatomic, weak) MPExpressionView *expressionView; // Do not set
|
||||
@property (nonatomic, strong) MPExpressionLayout *rootLayout;
|
||||
|
||||
- (NSLayoutManager *)layoutManager;
|
||||
- (NSTextContainer *)textContainer;
|
||||
- (NSTextStorage *)textStorage;
|
||||
|
||||
@end
|
||||
|
||||
@@ -20,53 +20,20 @@
|
||||
|
||||
@implementation MPExpressionStorage
|
||||
|
||||
- (instancetype)initWithExpressionView:(MPExpressionView *)expressionView elements:(NSArray *)elements
|
||||
{
|
||||
self = [self initWithElements:elements];
|
||||
if (self) {
|
||||
_expressionView = expressionView;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithElements:(NSArray *)elements
|
||||
{
|
||||
self = [super initWithElements:elements];
|
||||
if (self) {
|
||||
_rootLayout = [[MPExpressionLayout alloc] initRootLayoutWithExpressionStorage:self];
|
||||
_rootLayout = [[MPExpressionLayout alloc] initRootLayoutWithExpression:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSLayoutManager *)layoutManager
|
||||
- (void)setExpressionView:(MPExpressionView *)expressionView
|
||||
{
|
||||
[self ensureTextSystemObjects];
|
||||
return _layoutManager;
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
_expressionView = expressionView;
|
||||
self.rootLayout = [[MPExpressionLayout alloc] initRootLayoutWithExpression:self];
|
||||
self.rootLayout.flipped = expressionView.isFlipped;
|
||||
}
|
||||
|
||||
- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath
|
||||
@@ -76,11 +43,13 @@
|
||||
return;
|
||||
}
|
||||
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 clearCacheInRange:rangePath.rangeAtLastIndex
|
||||
replacementLength:replacementLength];
|
||||
[self.expressionView invalidateIntrinsicContentSize];
|
||||
self.expressionView.needsDisplay = YES;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
@property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage;
|
||||
|
||||
@property (nonatomic, getter = isEditable) BOOL editable;
|
||||
//@property (nonatomic, strong) MPRangePath *selection;
|
||||
@property (nonatomic, strong) NSIndexPath *caretLocation;
|
||||
@property (nonatomic, strong) MPRangePath *selection;
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,34 +6,109 @@
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#warning X-Origin is not working yet
|
||||
|
||||
#import "MPExpressionView.h"
|
||||
#import "MPExpressionStorage.h"
|
||||
#import "MPExpressionLayout.h"
|
||||
|
||||
#import "MPRangePath.h"
|
||||
|
||||
#import "NSIndexPath+MPAdditions.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
|
||||
|
||||
@implementation MPExpressionView
|
||||
|
||||
#pragma mark Creation Methods
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self initializeObjects];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(NSRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self initializeObjects];
|
||||
[self initializeExpressionView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -42,25 +117,44 @@
|
||||
{
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
[self initializeObjects];
|
||||
[self initializeExpressionView];
|
||||
}
|
||||
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;
|
||||
_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
|
||||
|
||||
- (BOOL)isFlipped
|
||||
- (void)setExpressionStorage:(MPExpressionStorage *)expressionStorage
|
||||
{
|
||||
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
|
||||
{
|
||||
return YES;
|
||||
@@ -71,29 +165,226 @@
|
||||
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
|
||||
{
|
||||
[self interpretKeyEvents:@[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]];
|
||||
}
|
||||
}
|
||||
|
||||
- (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
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect
|
||||
{
|
||||
// Draw the background
|
||||
[super drawRect:dirtyRect];
|
||||
[[NSColor whiteColor] set];
|
||||
NSRectFill(self.bounds);
|
||||
[[NSColor blackColor] set];
|
||||
NSSize expressionSize = [self.expressionStorage.rootLayout size];
|
||||
CGFloat y = (self.bounds.size.height - expressionSize.height) / 2;
|
||||
NSPoint point = NSMakePoint(self.bounds.origin.x, self.bounds.origin.y + y);
|
||||
[self.expressionStorage.rootLayout drawAtPoint:point];
|
||||
|
||||
// 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];
|
||||
} else {
|
||||
[[NSColor selectedTextBackgroundColor] set];
|
||||
}
|
||||
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
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#pragma mark Working With the Expression Tree
|
||||
@property (nonatomic, weak) MPExpression *parent; // Documentation: Do not set
|
||||
- (MPExpression *)rootExpression;
|
||||
- (NSIndexPath *)indexPath;
|
||||
|
||||
- (NSUInteger)numberOfChildren; // Override
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
}
|
||||
|
||||
#pragma mark Working With the Expression Tree
|
||||
- (MPExpression *)rootExpression
|
||||
{
|
||||
return [self.parent rootExpression];
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPath
|
||||
{
|
||||
NSUInteger selfIndex = [self.parent indexOfElement:self];
|
||||
@@ -60,7 +65,7 @@
|
||||
{
|
||||
NSUInteger index = 0;
|
||||
for (; index < [self numberOfChildren]; index++) {
|
||||
if ([[self childAtIndex:index] isEqualToExpression:child]) {
|
||||
if ([self childAtIndex:index] == child) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
@@ -106,6 +111,7 @@
|
||||
}
|
||||
|
||||
#pragma mark Working With Functions
|
||||
/*
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self == object) {
|
||||
@@ -123,7 +129,7 @@
|
||||
- (BOOL)isEqualToFunction:(MPFunction *)aFunction
|
||||
{
|
||||
return [aFunction isMemberOfClass:[MPFunction class]] && [self isMemberOfClass:[MPFunction class]];
|
||||
}
|
||||
}*/
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
@interface MPFunctionLayout : MPLayout
|
||||
|
||||
+ (MPFunctionLayout *)functionLayoutForFunctionAtIndexPath:(NSIndexPath *)path
|
||||
inRootExpression:(MPExpression *)rootExpression
|
||||
parent:(MPExpressionLayout *)parent;
|
||||
|
||||
@property (readonly, nonatomic, weak) MPFunction *function;
|
||||
@@ -31,7 +32,9 @@
|
||||
// }
|
||||
|
||||
#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
|
||||
|
||||
@end
|
||||
@@ -17,33 +17,34 @@
|
||||
|
||||
#pragma mark Creation Methods
|
||||
+ (MPFunctionLayout *)functionLayoutForFunctionAtIndexPath:(NSIndexPath *)path
|
||||
inRootExpression:(MPExpression *)rootExpression
|
||||
parent:(MPExpressionLayout *)parent
|
||||
{
|
||||
MPFunction *function = [parent.expressionStorage elementAtIndexPath:path];
|
||||
MPFunction *function = [rootExpression elementAtIndexPath:path];
|
||||
Class class = [function 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
|
||||
parent:(MPLayout *)parent
|
||||
- (instancetype)initWithElementAtPath:(NSIndexPath *)path
|
||||
inRootExpression:(MPExpression *)rootExpression
|
||||
parent:(MPLayout *)parent
|
||||
{
|
||||
self = [super initWithPath:path
|
||||
parent:parent];
|
||||
self = [super initWithElementAtPath:path
|
||||
inRootExpression:rootExpression
|
||||
parent:parent];
|
||||
if (self) {
|
||||
_function = [parent.expressionStorage elementAtIndexPath:path];
|
||||
_function = [rootExpression elementAtIndexPath:path];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Properties
|
||||
//- (MPFunction *)function
|
||||
//{
|
||||
// return [self.expressionStorage elementAtIndexPath:self.path];
|
||||
//}
|
||||
|
||||
#pragma mark Cache Methods
|
||||
- (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index
|
||||
generator:(CTLineRef (^)())generator
|
||||
@@ -62,26 +63,22 @@
|
||||
{
|
||||
return [self cachableObjectForIndex:index generator:^id{
|
||||
NSIndexPath *childPath = [self.function.indexPath indexPathByAddingIndex:index];
|
||||
MPExpressionLayout *layout = [[MPExpressionLayout alloc] initWithPath:childPath
|
||||
parent:self];
|
||||
MPExpressionLayout *layout = [[MPExpressionLayout alloc] initWithElementAtPath:childPath
|
||||
inRootExpression:self.function.rootExpression
|
||||
parent:self];
|
||||
layout.flipped = self.flipped;
|
||||
layout.usesSmallSize = (index == 0 || index == 1) ? YES : self.usesSmallSize;
|
||||
return layout;
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark Size and Drawing Methods
|
||||
|
||||
- (NSSize)sizeForChildAtIndex:(NSUInteger)index
|
||||
- (NSRect)generateBounds
|
||||
{
|
||||
MPLayout *childLayout = [self childLayoutAtIndex:index];
|
||||
return [childLayout size];
|
||||
return NSZeroRect;
|
||||
}
|
||||
|
||||
- (NSSize)generateSize
|
||||
{
|
||||
return NSZeroSize;
|
||||
}
|
||||
|
||||
- (void)drawAtPoint:(NSPoint)point
|
||||
- (void)draw
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -15,11 +15,17 @@
|
||||
|
||||
#pragma mark Creation Methods
|
||||
- (instancetype)init;
|
||||
- (instancetype)initWithPath:(NSIndexPath *)path
|
||||
parent:(MPLayout *)parent;
|
||||
- (instancetype)initWithElementAtPath:(NSIndexPath *)path
|
||||
inRootExpression:(MPExpression *)rootExpression
|
||||
parent:(MPLayout *)parent;
|
||||
|
||||
#pragma mark Text System Objects
|
||||
@property (readonly, nonatomic, weak) MPExpressionStorage *expressionStorage;
|
||||
#pragma mark Properties
|
||||
- (NSFont *)font;
|
||||
- (CGFloat)fontSize;
|
||||
- (NSFont *)normalFont;
|
||||
- (CGFloat)normalFontSize;
|
||||
- (NSFont *)smallFont;
|
||||
- (CGFloat)smallFontSize;
|
||||
|
||||
#pragma mark Cache Tree
|
||||
@property (readonly, nonatomic, weak) MPLayout *parent;
|
||||
@@ -35,14 +41,21 @@
|
||||
- (void)invalidate;
|
||||
|
||||
#pragma mark Calculation and Drawing Methods
|
||||
// TODO: Implement Small Size
|
||||
// @property (nonatomic) BOOL usesSmallSize;
|
||||
- (NSSize)size;
|
||||
@property (nonatomic, getter = isFlipped) BOOL flipped;
|
||||
@property (nonatomic) BOOL usesSmallSize;
|
||||
- (NSRect)bounds;
|
||||
|
||||
- (NSRect)boundingRectForRangePath:(MPRangePath *)rangePath; /* if rangePath.length is 0 the returned rect will have a width of 0 */
|
||||
|
||||
- (void)drawAtPoint:(NSPoint)point;
|
||||
|
||||
@end
|
||||
|
||||
@interface MPLayout (MPSubclassImplement)
|
||||
- (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
|
||||
@@ -7,6 +7,9 @@
|
||||
//
|
||||
|
||||
#import "MPLayout.h"
|
||||
#import "MPRangePath.h"
|
||||
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
@interface MPLayout ()
|
||||
|
||||
@@ -18,7 +21,7 @@
|
||||
|
||||
@implementation MPLayout {
|
||||
NSMutableArray *_cache;
|
||||
NSSize _cachedSize;
|
||||
NSRect _cachedBounds;
|
||||
}
|
||||
|
||||
#pragma mark Creation Methods
|
||||
@@ -27,13 +30,14 @@
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_cache = [[NSMutableArray alloc] init];
|
||||
_cachedSize = NSZeroSize;
|
||||
_cachedBounds = NSZeroRect;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithPath:(NSIndexPath *)path
|
||||
parent:(MPLayout *)parent
|
||||
- (instancetype)initWithElementAtPath:(NSIndexPath *)path
|
||||
inRootExpression:(MPExpression *)rootExpression
|
||||
parent:(MPLayout *)parent
|
||||
{
|
||||
self = [self init];
|
||||
if (self) {
|
||||
@@ -42,10 +46,37 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma Text System Objects
|
||||
- (MPExpressionStorage *)expressionStorage
|
||||
#pragma mark Properties
|
||||
- (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
|
||||
@@ -92,22 +123,42 @@
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
_cachedSize = NSZeroSize;
|
||||
_cachedBounds = NSZeroRect;
|
||||
[self.parent invalidate];
|
||||
}
|
||||
|
||||
#pragma mark Calculation and Drawing Methods
|
||||
- (NSSize)size
|
||||
- (NSRect)bounds
|
||||
{
|
||||
if (NSEqualSizes(_cachedSize, NSZeroSize)) {
|
||||
_cachedSize = [self generateSize];
|
||||
if (NSEqualRects(_cachedBounds, NSZeroRect)) {
|
||||
_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
|
||||
{
|
||||
|
||||
NSAffineTransform *transform = [NSAffineTransform transform];
|
||||
[transform translateXBy:point.x
|
||||
yBy:point.y];
|
||||
[transform concat];
|
||||
[self draw];
|
||||
[transform invert];
|
||||
[transform concat];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -119,14 +170,24 @@
|
||||
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
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
#import "MPExpression.h"
|
||||
|
||||
#define MPMakeRangePath(loc, len) [MPRangePath rangePathWithLocation:(loc) length:(len)]
|
||||
|
||||
@class MPRangePath, MPExpression;
|
||||
|
||||
@interface MPRangePath : NSObject <NSCopying, NSCoding>
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
@interface MPSumFunction : MPFunction
|
||||
|
||||
@property (nonatomic, strong) MPExpression *startExpression;
|
||||
@property (nonatomic, strong) MPExpression *targetExpression;
|
||||
@property (nonatomic, strong) MPExpression *sumExpression;
|
||||
@property (nonatomic, strong) MPExpression *startExpression; // Index 0
|
||||
@property (nonatomic, strong) MPExpression *targetExpression; // Index 1
|
||||
@property (nonatomic, strong) MPExpression *sumExpression; // Index 2
|
||||
|
||||
@end
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
}
|
||||
|
||||
#pragma mark Working With Functions
|
||||
/*
|
||||
- (BOOL)isEqualToFunction:(MPFunction *)aFunction
|
||||
{
|
||||
if (![aFunction isKindOfClass:[MPSumFunction class]]) {
|
||||
@@ -117,7 +118,7 @@
|
||||
return NO;
|
||||
}
|
||||
return [self.sumExpression isEqualToExpression:sumFunction.sumExpression];
|
||||
}
|
||||
}*/
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
#import "MPSumFunctionLayout.h"
|
||||
#import "MPSumFunction.h"
|
||||
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
#define kSumFunctionStartExpressionOffset 3
|
||||
#define kSumFunctionTargetExpressionOffset 0
|
||||
#define kSumFunctionSumExpressionOffset 3
|
||||
#define kSumFunctionTrailingOffset 5
|
||||
|
||||
@implementation MPSumFunctionLayout
|
||||
|
||||
- (MPSumFunction *)sumFunction
|
||||
@@ -21,7 +28,7 @@
|
||||
CTLineRef line = [self lineForPrivateCacheIndex:0 generator:^CTLineRef{
|
||||
NSAttributedString *text =
|
||||
[[NSAttributedString alloc] initWithString:@"∑"
|
||||
attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Lucida Grande" size:18.0]}];
|
||||
attributes:@{NSFontAttributeName: self.font}];
|
||||
CFAttributedStringRef attributedString = CFBridgingRetain(text);
|
||||
CTLineRef line = CTLineCreateWithAttributedString(attributedString);
|
||||
CFRelease(attributedString); // TODO: Is this release appropriate
|
||||
@@ -30,31 +37,119 @@
|
||||
return line;
|
||||
}
|
||||
|
||||
- (NSSize)generateSize
|
||||
- (NSRect)localLineBounds
|
||||
{
|
||||
CTLineRef line = [self line];
|
||||
CFRetain(line);
|
||||
CGSize size = CTLineGetBoundsWithOptions(line, /*kCTLineBoundsUseOpticalBounds*/0).size;
|
||||
CFRelease(line);
|
||||
return size;
|
||||
NSRect lineBounds = CTLineGetBoundsWithOptions(self.line, 0);
|
||||
CGFloat width = MAX(MAX([self childLayoutAtIndex:0].bounds.size.width, [self childLayoutAtIndex:1].bounds.size.width), lineBounds.size.width);
|
||||
CGFloat xPosition = (width - lineBounds.size.width) / 2;
|
||||
return NSMakeRect(xPosition, lineBounds.origin.y, lineBounds.size.width, lineBounds.size.height);
|
||||
}
|
||||
|
||||
- (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
|
||||
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
|
||||
// Set the text matrix
|
||||
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
|
||||
|
||||
// Get the line of text
|
||||
// Draw the sum symbol
|
||||
CTLineRef line = [self line];
|
||||
CFRetain(line);
|
||||
|
||||
// Draw the line
|
||||
CGContextSetTextPosition(context, point.x, point.y);
|
||||
NSRect localLineBounds = [self localLineBounds];
|
||||
CGContextSetTextPosition(context, localLineBounds.origin.x, 0);
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
<string>Copyright © 2014 Kim Wittenburg. All rights reserved.</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>ATSApplicationFontsPath</key>
|
||||
<string>Fonts</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
</dict>
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
|
||||
@interface NSIndexPath (MPAdditions)
|
||||
|
||||
- (NSUInteger)lastIndex;
|
||||
|
||||
- (NSIndexPath *)indexPathByReplacingLastIndexWithIndex:(NSUInteger)index;
|
||||
|
||||
/*!
|
||||
@method indexPathByRemovingFirstIndex
|
||||
@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 *)indexPathByIncrementingLastIndex;
|
||||
- (NSIndexPath *)indexPathByDecrementingLastIndex;
|
||||
|
||||
@end
|
||||
|
||||
@@ -10,6 +10,16 @@
|
||||
|
||||
@implementation NSIndexPath (MPAdditions)
|
||||
|
||||
- (NSUInteger)lastIndex
|
||||
{
|
||||
return [self indexAtPosition:self.length-1];
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathByReplacingLastIndexWithIndex:(NSUInteger)index
|
||||
{
|
||||
return [[self indexPathByRemovingLastIndex] indexPathByAddingIndex:index];
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathByRemovingFirstIndex
|
||||
{
|
||||
if (self.length <= 1) {
|
||||
@@ -34,4 +44,18 @@
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user