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 */; };
|
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 */,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user