Archived
1
This repository has been archived on 2022-08-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
mathpad/MathPad/MPExpressionLayout.m
Kim Wittenburg f4f924bd71 Cleaned Imports
2014-11-08 01:05:08 +01:00

279 lines
9.7 KiB
Objective-C

//
// MPExpressionLayout.m
// MathPad
//
// Created by Kim Wittenburg on 07.08.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
#import "MPExpressionLayout.h"
#import "MPExpression.h"
#import "MPExpressionElement.h"
#import "MPPowerFunction.h"
#import "MPFunctionLayout.h"
#import "MPPowerFunctionLayout.h"
#import "NSIndexPath+MPAdditions.h"
@interface MPExpressionLayout (MPLineGeneration)
- (CTLineRef)lineForElementAtIndex:(NSUInteger)index;
@end
@implementation MPExpressionLayout (MPLineGeneration)
- (CTLineRef)lineForElementAtIndex:(NSUInteger)index
{
id element = [self.expression elementAtIndex:index];
if (![element isString]) {
return NULL;
}
NSString *string = element;
id lineObject = [self cachableObjectForIndex:index generator:^id{
CTLineRef line = [self createLineForString:string];
return CFBridgingRelease(line);
}];
return (__bridge CTLineRef)lineObject;
}
@end
@implementation MPExpressionLayout
# pragma mark Creation Methods
- (instancetype)initWithExpression:(MPExpression *)expression
parent:(MPFunctionLayout *)parent
{
self = [super initWithParent:parent];
if (self) {
_expression = expression;
}
return self;
}
#pragma mark Cache Methods
- (NSUInteger)numberOfChildren
{
return self.expression.countElements;
}
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index
{
id cachedObject = [self cachableObjectForIndex:index generator:^id{
MPFunction *function = (MPFunction *)[self.expression elementAtIndex:index];
MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunction:function
parent:self];
layout.flipped = self.flipped;
layout.usesSmallSize = self.usesSmallSize;
return layout;
}];
if ([cachedObject isKindOfClass:[MPLayout class]]) {
return cachedObject;
}
return nil;
}
- (NSRect)boundsOfElementAtIndex:(NSUInteger)index
{
id symbol = [self.expression elementAtIndex:index];
if ([symbol isString]) {
CTLineRef line = [self lineForElementAtIndex:index];
CFRetain(line);
CGRect bounds = CTLineGetBoundsWithOptions(line, 0);
CFRelease(line);
return bounds;
} else {
return [self childLayoutAtIndex:index].bounds;
}
}
#pragma mark Drawing Methods
- (NSRect)generateBounds
{
if (self.expression.countElements == 0) {
return NSMakeRect(0, kMPEmptyBoxYOrigin, kMPEmptyBoxWidth, kMPEmptyBoxHeight);
}
CGFloat x = 0, y = 0, width = 0, height = 0;
for (NSUInteger index = 0; index < self.expression.countElements; 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);
}
- (NSRect)boundingRectForRange:(NSRange)range
{
NSUInteger startOffset;
NSUInteger startElementIndex = [self.expression convertIndex:range.location
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame
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.countElements) { // 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 convertIndex:NSMaxRange(range)
fromReferenceFrame:MPSymbolReferenceFrame
toReferenceFrame:MPElementReferenceFrame
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.countElements; index++) {
NSRect elementBounds = [self boundsOfElementAtIndex:index];
NSPoint elementOffset = [self offsetOfChildLayoutAtIndex:index];
elementBounds.origin.x += elementOffset.x;
elementBounds.origin.y += elementOffset.y;
// Only the horizontal location is to consider for hit testing
elementBounds.size.height = CGFLOAT_MAX;
id<MPExpressionElement> element = [self.expression elementAtIndex:index];
if (NSMouseInRect(point, elementBounds, self.flipped)) {
NSPoint pointInElement = NSMakePoint(point.x - elementOffset.x, point.y + elementOffset.y);
if ([element isString]) {
CTLineRef line = [self lineForElementAtIndex:index];
CFRetain(line);
CFIndex localIndex = CTLineGetStringIndexForPosition(line, pointInElement);
CFRelease(line);
return [NSIndexPath indexPathWithIndex:currentPosition+localIndex];
} else {
NSIndexPath *subPath = [[self childLayoutAtIndex:index] indexPathForMousePoint:pointInElement];
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.countSymbols];
}
}
- (BOOL)drawsChildrenManually
{
return YES;
}
- (void)draw
{
// Get the current context
CGContextRef context =
(CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
CGContextSaveGState(context);
[[NSColor textColor] set];
if (self.expression.countElements == 0) {
CGContextRestoreGState(context);
NSBezierPath *path = [NSBezierPath bezierPathWithRect:NSMakeRect(0, kMPEmptyBoxDrawingYOrigin, kMPEmptyBoxDrawingWidth, kMPEmptyBoxDrawingHeight)];
path.lineWidth = 0.5;
[path stroke];
return;
}
// Set the text matrix
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// Track the x position
CGFloat x = 0;
for (NSUInteger index = 0; index < self.expression.countElements; index++) {
// The current element
id element = [self.expression elementAtIndex:index];
NSRect elementBounds = [self boundsOfElementAtIndex:index];
if ([element isString]) {
// Get the line to draw
CTLineRef line = [self lineForElementAtIndex:index];
CFRetain(line);
// Move to the appropriate position
CGContextSetTextPosition(context, x, 0);
// Perform the drawing
CTLineDraw(line, context);
CFRelease(line);
if (index < self.expression.countElements-1 && [[self.expression elementAtIndex:index+1] isKindOfClass:[MPPowerFunction class]]) {
MPPowerFunctionLayout *layout = (MPPowerFunctionLayout *)[self childLayoutAtIndex:index+1];
layout.baseBounds = elementBounds;
}
} else {
// Let the child layout draw itself
MPLayout *layout = [self childLayoutAtIndex:index];
[layout drawAtPoint:NSMakePoint(x, 0)];
}
x += elementBounds.size.width;
}
CGContextRestoreGState(context);
}
@end