Archived
1

Improved Model

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

View File

@@ -27,12 +27,13 @@
3BBBA35E1903FD3600824E74 /* MPRangePath.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B87E35C1900933200259938 /* MPRangePath.m */; };
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 */,
);

View File

@@ -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>

View File

@@ -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

View File

@@ -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;
}

View File

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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
{

View File

@@ -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

View File

@@ -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
{
}

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -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

View File

@@ -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
{

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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