mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-04-23 21:20:49 +00:00
806 lines
23 KiB
Objective-C
806 lines
23 KiB
Objective-C
/* Copyright (C) 2022 Free Software Foundation, Inc.
|
|
|
|
By: Benjamin Johnson
|
|
Date: 11-11-2022
|
|
This file is part of the GNUstep Library.
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free
|
|
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110 USA.
|
|
*/
|
|
|
|
#import "GSAutoLayoutVFLParser.h"
|
|
#import "GSFastEnumeration.h"
|
|
|
|
struct GSObjectOfPredicate
|
|
{
|
|
NSNumber *priority;
|
|
NSView *view;
|
|
NSLayoutRelation relation;
|
|
CGFloat constant;
|
|
};
|
|
typedef struct GSObjectOfPredicate GSObjectOfPredicate;
|
|
|
|
NSInteger const GS_DEFAULT_VIEW_SPACING = 8;
|
|
NSInteger const GS_DEFAULT_SUPERVIEW_SPACING = 20;
|
|
|
|
@interface GSAutoLayoutVFLParser (PrivateMethods)
|
|
|
|
- (void) parseOrientation;
|
|
|
|
- (NSArray *) parseView;
|
|
|
|
- (NSView *) parseViewName;
|
|
|
|
- (void) addFormattingConstraints:(NSView *)lastView;
|
|
|
|
- (BOOL) isVerticalEdgeFormatLayoutOption:(NSLayoutFormatOptions)options;
|
|
|
|
- (void) assertHasValidFormatLayoutOptions;
|
|
|
|
- (void) addLeadingSuperviewConstraint:(NSNumber *)spacing;
|
|
|
|
- (void) addViewSpacingConstraint:(NSNumber *)spacing
|
|
previousView:(NSView *)previousView;
|
|
|
|
- (void) addLeadingSuperviewConstraint:(NSNumber *)spacing;
|
|
|
|
- (void) addTrailingToSuperviewConstraint:(NSNumber *)spacing;
|
|
|
|
- (NSNumber *) parseLeadingSuperViewConnection;
|
|
|
|
- (NSNumber *) parseConnection;
|
|
|
|
- (NSNumber *) parseSimplePredicate;
|
|
|
|
- (NSArray *) parsePredicateList;
|
|
|
|
- (NSLayoutConstraint *) createConstraintFromParsedPredicate:
|
|
(GSObjectOfPredicate *)predicate;
|
|
|
|
- (void) parseObjectOfPredicate: (GSObjectOfPredicate *)predicate;
|
|
|
|
- (NSLayoutRelation) parseRelation;
|
|
|
|
- (NSNumber *) parsePriority;
|
|
|
|
- (NSNumber *) resolveMetricWithIdentifier:(NSString *)identifier;
|
|
|
|
- (NSView *) resolveViewWithIdentifier:(NSString *)identifier;
|
|
|
|
- (NSNumber *) parseConstant;
|
|
|
|
- (NSString *) parseIdentifier;
|
|
|
|
- (void) parseViewOpen;
|
|
|
|
- (void) parseViewClose;
|
|
|
|
- (BOOL) isValidIdentifier:(NSString *)identifer;
|
|
|
|
- (NSArray *) layoutAttributesForLayoutFormatOptions:
|
|
(NSLayoutFormatOptions)options;
|
|
|
|
- (void) failParseWithMessage:(NSString *)parseErrorMessage;
|
|
|
|
@end
|
|
|
|
@implementation GSAutoLayoutVFLParser
|
|
|
|
- (instancetype) initWithFormat:(NSString *)format
|
|
options:(NSLayoutFormatOptions)options
|
|
metrics:(NSDictionary *)metrics
|
|
views:(NSDictionary *)views
|
|
{
|
|
self = [super init];
|
|
if (self != nil)
|
|
{
|
|
_views = views;
|
|
_metrics = metrics;
|
|
_options = options;
|
|
|
|
_scanner = [NSScanner scannerWithString: format];
|
|
_constraints = [NSMutableArray array];
|
|
_layoutFormatConstraints = [NSMutableArray array];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (NSArray *) parse
|
|
{
|
|
if ([[_scanner string] length] == 0)
|
|
{
|
|
[self failParseWithMessage: @"Cannot parse an empty string"];
|
|
}
|
|
|
|
[self parseOrientation];
|
|
NSNumber *spacingConstant = [self parseLeadingSuperViewConnection];
|
|
NSView *previousView = nil;
|
|
|
|
while (![_scanner isAtEnd])
|
|
{
|
|
NSArray *viewConstraints = [self parseView];
|
|
if (_createLeadingConstraintToSuperview)
|
|
{
|
|
[self addLeadingSuperviewConstraint: spacingConstant];
|
|
_createLeadingConstraintToSuperview = NO;
|
|
}
|
|
|
|
if (previousView != nil)
|
|
{
|
|
[self addViewSpacingConstraint: spacingConstant
|
|
previousView: previousView];
|
|
[self addFormattingConstraints: previousView];
|
|
}
|
|
[_constraints addObjectsFromArray: viewConstraints];
|
|
|
|
spacingConstant = [self parseConnection];
|
|
if ([_scanner scanString: @"|" intoString: NULL])
|
|
{
|
|
[self addTrailingToSuperviewConstraint: spacingConstant];
|
|
}
|
|
previousView = _view;
|
|
}
|
|
|
|
[_constraints addObjectsFromArray: _layoutFormatConstraints];
|
|
|
|
return _constraints;
|
|
}
|
|
|
|
- (void) addFormattingConstraints:(NSView *)lastView
|
|
{
|
|
BOOL hasFormatOptions = (_options & NSLayoutFormatAlignmentMask) > 0;
|
|
if (!hasFormatOptions)
|
|
{
|
|
return;
|
|
}
|
|
[self assertHasValidFormatLayoutOptions];
|
|
|
|
NSArray *attributes = [self layoutAttributesForLayoutFormatOptions:_options];
|
|
FOR_IN(NSNumber*, layoutAttribute, attributes)
|
|
NSLayoutConstraint *formatConstraint =
|
|
[NSLayoutConstraint constraintWithItem: lastView
|
|
attribute: [layoutAttribute integerValue]
|
|
relatedBy: NSLayoutRelationEqual
|
|
toItem: _view
|
|
attribute: [layoutAttribute integerValue]
|
|
multiplier: 1.0
|
|
constant: 0];
|
|
[_layoutFormatConstraints addObject: formatConstraint];
|
|
END_FOR_IN(attributes);
|
|
}
|
|
|
|
- (void) assertHasValidFormatLayoutOptions
|
|
{
|
|
if (_isVerticalOrientation &&
|
|
[self isVerticalEdgeFormatLayoutOption: _options])
|
|
{
|
|
[self failParseWithMessage: @"A vertical alignment format option cannot "
|
|
@"be used with a vertical layout"];
|
|
}
|
|
else if (!_isVerticalOrientation
|
|
&& ![self isVerticalEdgeFormatLayoutOption: _options])
|
|
{
|
|
[self failParseWithMessage: @"A horizontal alignment format option "
|
|
@"cannot be used with a horizontal layout"];
|
|
}
|
|
}
|
|
|
|
- (void) parseOrientation
|
|
{
|
|
if ([_scanner scanString: @"V:" intoString: NULL])
|
|
{
|
|
_isVerticalOrientation = true;
|
|
}
|
|
else
|
|
{
|
|
[_scanner scanString: @"H:" intoString: NULL];
|
|
}
|
|
}
|
|
|
|
- (NSArray *) parseView
|
|
{
|
|
[self parseViewOpen];
|
|
|
|
_view = [self parseViewName];
|
|
NSArray *viewConstraints = [self parsePredicateList];
|
|
[self parseViewClose];
|
|
|
|
return viewConstraints;
|
|
}
|
|
|
|
- (NSView *) parseViewName
|
|
{
|
|
NSString *viewName = nil;
|
|
NSCharacterSet *viewTerminators =
|
|
[NSCharacterSet characterSetWithCharactersInString: @"]("];
|
|
[_scanner scanUpToCharactersFromSet: viewTerminators intoString: &viewName];
|
|
|
|
if (viewName == nil)
|
|
{
|
|
[self failParseWithMessage: @"Failed to parse view name"];
|
|
}
|
|
|
|
if (![self isValidIdentifier: viewName])
|
|
{
|
|
[self failParseWithMessage:
|
|
@"Invalid view name. A view name must be a valid C identifier "
|
|
@"and may only contain letters, numbers and underscores"];
|
|
}
|
|
|
|
return [self resolveViewWithIdentifier: viewName];
|
|
}
|
|
|
|
- (BOOL) isVerticalEdgeFormatLayoutOption:(NSLayoutFormatOptions)options
|
|
{
|
|
if (options & NSLayoutFormatAlignAllTop)
|
|
{
|
|
return YES;
|
|
}
|
|
if (options & NSLayoutFormatAlignAllBaseline)
|
|
{
|
|
return YES;
|
|
}
|
|
if (options & NSLayoutFormatAlignAllFirstBaseline)
|
|
{
|
|
return YES;
|
|
}
|
|
if (options & NSLayoutFormatAlignAllBottom)
|
|
{
|
|
return YES;
|
|
}
|
|
if (options & NSLayoutFormatAlignAllCenterY)
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (void) addViewSpacingConstraint:(NSNumber *)spacing
|
|
previousView:(NSView *)previousView
|
|
{
|
|
CGFloat viewSpacingConstant
|
|
= spacing ? [spacing doubleValue] : GS_DEFAULT_VIEW_SPACING;
|
|
NSLayoutAttribute firstAttribute;
|
|
NSLayoutAttribute secondAttribute;
|
|
NSView *firstItem;
|
|
NSView *secondItem;
|
|
|
|
NSLayoutFormatOptions directionOptions
|
|
= _options & NSLayoutFormatDirectionMask;
|
|
if (_isVerticalOrientation)
|
|
{
|
|
firstAttribute = NSLayoutAttributeTop;
|
|
secondAttribute = NSLayoutAttributeBottom;
|
|
firstItem = _view;
|
|
secondItem = previousView;
|
|
}
|
|
else if (directionOptions & NSLayoutFormatDirectionRightToLeft)
|
|
{
|
|
firstAttribute = NSLayoutAttributeLeft;
|
|
secondAttribute = NSLayoutAttributeRight;
|
|
firstItem = previousView;
|
|
secondItem = _view;
|
|
}
|
|
else if (directionOptions & NSLayoutFormatDirectionLeftToRight)
|
|
{
|
|
firstAttribute = NSLayoutAttributeLeft;
|
|
secondAttribute = NSLayoutAttributeRight;
|
|
firstItem = _view;
|
|
secondItem = previousView;
|
|
}
|
|
else
|
|
{
|
|
firstAttribute = NSLayoutAttributeLeading;
|
|
secondAttribute = NSLayoutAttributeTrailing;
|
|
firstItem = _view;
|
|
secondItem = previousView;
|
|
}
|
|
|
|
NSLayoutConstraint *viewSeparatorConstraint =
|
|
[NSLayoutConstraint constraintWithItem: firstItem
|
|
attribute: firstAttribute
|
|
relatedBy: NSLayoutRelationEqual
|
|
toItem: secondItem
|
|
attribute: secondAttribute
|
|
multiplier: 1.0
|
|
constant: viewSpacingConstant];
|
|
|
|
[_constraints addObject: viewSeparatorConstraint];
|
|
}
|
|
|
|
- (void) addLeadingSuperviewConstraint:(NSNumber *)spacing
|
|
{
|
|
NSLayoutAttribute firstAttribute;
|
|
NSView *firstItem;
|
|
NSView *secondItem;
|
|
|
|
NSLayoutFormatOptions directionOptions
|
|
= _options & NSLayoutFormatDirectionMask;
|
|
if (_isVerticalOrientation)
|
|
{
|
|
firstAttribute = NSLayoutAttributeTop;
|
|
firstItem = _view;
|
|
secondItem = _view.superview;
|
|
}
|
|
else if (directionOptions & NSLayoutFormatDirectionRightToLeft)
|
|
{
|
|
firstAttribute = NSLayoutAttributeRight;
|
|
firstItem = _view.superview;
|
|
secondItem = _view;
|
|
}
|
|
else if (directionOptions & NSLayoutFormatDirectionLeftToRight)
|
|
{
|
|
firstAttribute = NSLayoutAttributeLeft;
|
|
firstItem = _view;
|
|
secondItem = _view.superview;
|
|
}
|
|
else
|
|
{
|
|
firstAttribute = _isVerticalOrientation ? NSLayoutAttributeTop
|
|
: NSLayoutAttributeLeading;
|
|
firstItem = _view;
|
|
secondItem = _view.superview;
|
|
}
|
|
|
|
CGFloat viewSpacingConstant
|
|
= spacing ? [spacing doubleValue] : GS_DEFAULT_SUPERVIEW_SPACING;
|
|
|
|
NSLayoutConstraint *leadingConstraintToSuperview =
|
|
[NSLayoutConstraint constraintWithItem: firstItem
|
|
attribute: firstAttribute
|
|
relatedBy: NSLayoutRelationEqual
|
|
toItem: secondItem
|
|
attribute: firstAttribute
|
|
multiplier: 1.0
|
|
constant: viewSpacingConstant];
|
|
[_constraints addObject: leadingConstraintToSuperview];
|
|
}
|
|
|
|
- (void) addTrailingToSuperviewConstraint:(NSNumber *)spacing
|
|
{
|
|
CGFloat viewSpacingConstant
|
|
= spacing ? [spacing doubleValue] : GS_DEFAULT_SUPERVIEW_SPACING;
|
|
|
|
NSLayoutFormatOptions directionOptions
|
|
= _options & NSLayoutFormatDirectionMask;
|
|
NSLayoutAttribute attribute;
|
|
NSView *firstItem;
|
|
NSView *secondItem;
|
|
|
|
if (_isVerticalOrientation)
|
|
{
|
|
attribute = NSLayoutAttributeBottom;
|
|
firstItem = _view.superview;
|
|
secondItem = _view;
|
|
}
|
|
else if (directionOptions & NSLayoutFormatDirectionRightToLeft)
|
|
{
|
|
attribute = NSLayoutAttributeLeft;
|
|
firstItem = _view;
|
|
secondItem = _view.superview;
|
|
}
|
|
else if (directionOptions & NSLayoutFormatDirectionLeftToRight)
|
|
{
|
|
attribute = NSLayoutAttributeRight;
|
|
firstItem = _view.superview;
|
|
secondItem = _view;
|
|
}
|
|
else
|
|
{
|
|
attribute = NSLayoutAttributeTrailing;
|
|
firstItem = _view.superview;
|
|
secondItem = _view;
|
|
}
|
|
|
|
NSLayoutConstraint *trailingConstraintToSuperview =
|
|
[NSLayoutConstraint constraintWithItem: firstItem
|
|
attribute: attribute
|
|
relatedBy: NSLayoutRelationEqual
|
|
toItem: secondItem
|
|
attribute: attribute
|
|
multiplier: 1.0
|
|
constant: viewSpacingConstant];
|
|
[_constraints addObject: trailingConstraintToSuperview];
|
|
}
|
|
|
|
- (NSNumber *) parseLeadingSuperViewConnection
|
|
{
|
|
BOOL foundSuperview = [_scanner scanString: @"|" intoString: NULL];
|
|
if (!foundSuperview)
|
|
{
|
|
return nil;
|
|
}
|
|
_createLeadingConstraintToSuperview = YES;
|
|
return [self parseConnection];
|
|
}
|
|
|
|
- (NSNumber *) parseConnection
|
|
{
|
|
BOOL foundConnection = [_scanner scanString: @"-" intoString: NULL];
|
|
if (!foundConnection)
|
|
{
|
|
return [NSNumber numberWithDouble: 0];
|
|
}
|
|
|
|
NSNumber *simplePredicateValue = [self parseSimplePredicate];
|
|
BOOL endConnectionFound = [_scanner scanString: @"-" intoString: NULL];
|
|
|
|
if (simplePredicateValue != nil && !endConnectionFound)
|
|
{
|
|
[self failParseWithMessage: @"A connection must end with a '-'"];
|
|
}
|
|
else if (simplePredicateValue == nil && endConnectionFound)
|
|
{
|
|
[self failParseWithMessage: @"Found invalid connection"];
|
|
}
|
|
|
|
return simplePredicateValue;
|
|
}
|
|
|
|
- (NSNumber *) parseSimplePredicate
|
|
{
|
|
float constant;
|
|
BOOL scanConstantResult = [_scanner scanFloat: &constant];
|
|
if (scanConstantResult)
|
|
{
|
|
return [NSNumber numberWithDouble: constant];
|
|
}
|
|
else
|
|
{
|
|
NSString *metricName = nil;
|
|
NSCharacterSet *simplePredicateTerminatorsCharacterSet =
|
|
[NSCharacterSet characterSetWithCharactersInString: @"-[|"];
|
|
BOOL didParseMetricName = [_scanner
|
|
scanUpToCharactersFromSet: simplePredicateTerminatorsCharacterSet
|
|
intoString: &metricName];
|
|
if (!didParseMetricName)
|
|
{
|
|
return nil;
|
|
}
|
|
if (![self isValidIdentifier: metricName])
|
|
{
|
|
[self failParseWithMessage:
|
|
@"Invalid metric identifier. Metric identifiers must be a "
|
|
@"valid C identifier and may only contain letters, "
|
|
@"numbers and underscores"];
|
|
}
|
|
|
|
return [self resolveMetricWithIdentifier: metricName];
|
|
}
|
|
}
|
|
|
|
- (NSArray *) parsePredicateList
|
|
{
|
|
BOOL startsWithPredicateList = [_scanner scanString: @"(" intoString: NULL];
|
|
if (!startsWithPredicateList)
|
|
{
|
|
return [NSArray array];
|
|
}
|
|
|
|
NSMutableArray *viewPredicateConstraints = [NSMutableArray array];
|
|
BOOL shouldParsePredicate = YES;
|
|
while (shouldParsePredicate)
|
|
{
|
|
GSObjectOfPredicate predicate;
|
|
[self parseObjectOfPredicate: &predicate];
|
|
[viewPredicateConstraints
|
|
addObject: [self createConstraintFromParsedPredicate: &predicate]];
|
|
|
|
shouldParsePredicate = [_scanner scanString: @"," intoString: NULL];
|
|
}
|
|
|
|
if (![_scanner scanString: @")" intoString: NULL])
|
|
{
|
|
[self failParseWithMessage: @"A predicate on a view must end with ')'"];
|
|
}
|
|
|
|
return viewPredicateConstraints;
|
|
}
|
|
|
|
- (NSLayoutConstraint *) createConstraintFromParsedPredicate:
|
|
(GSObjectOfPredicate *)predicate
|
|
{
|
|
NSLayoutConstraint *constraint = nil;
|
|
NSLayoutAttribute attribute = _isVerticalOrientation
|
|
? NSLayoutAttributeHeight
|
|
: NSLayoutAttributeWidth;
|
|
if (predicate->view != nil)
|
|
{
|
|
constraint = [NSLayoutConstraint constraintWithItem: _view
|
|
attribute: attribute
|
|
relatedBy: predicate->relation
|
|
toItem: predicate->view
|
|
attribute: attribute
|
|
multiplier: 1.0
|
|
constant: predicate->constant];
|
|
}
|
|
else
|
|
{
|
|
constraint = [NSLayoutConstraint
|
|
constraintWithItem: _view
|
|
attribute: attribute
|
|
relatedBy: predicate->relation
|
|
toItem: nil
|
|
attribute: NSLayoutAttributeNotAnAttribute
|
|
multiplier: 1.0
|
|
constant: predicate->constant];
|
|
}
|
|
|
|
if (predicate->priority)
|
|
{
|
|
[constraint setPriority: [predicate->priority doubleValue]];
|
|
}
|
|
|
|
return constraint;
|
|
}
|
|
|
|
- (void) parseObjectOfPredicate: (GSObjectOfPredicate *)predicate
|
|
{
|
|
NSLayoutRelation relation = [self parseRelation];
|
|
|
|
CGFloat parsedConstant;
|
|
NSView *predicatedView = nil;
|
|
BOOL scanConstantResult = [_scanner scanDouble: &parsedConstant];
|
|
if (!scanConstantResult)
|
|
{
|
|
NSString *identiferName = [self parseIdentifier];
|
|
if (![self isValidIdentifier: identiferName])
|
|
{
|
|
[self failParseWithMessage:
|
|
@"Invalid metric or view identifier. Metric/View "
|
|
@"identifiers must be a valid C identifier and may only "
|
|
@"contain letters, numbers and underscores"];
|
|
}
|
|
|
|
NSNumber *metric = [_metrics objectForKey: identiferName];
|
|
if (metric != nil)
|
|
{
|
|
parsedConstant = [metric doubleValue];
|
|
}
|
|
else if ([_views objectForKey: identiferName])
|
|
{
|
|
parsedConstant = 0;
|
|
predicatedView = [_views objectForKey: identiferName];
|
|
}
|
|
else
|
|
{
|
|
NSString *message = [NSString
|
|
stringWithFormat:
|
|
@"Failed to find constant or metric for identifier '%@'",
|
|
identiferName];
|
|
[self failParseWithMessage: message];
|
|
}
|
|
}
|
|
|
|
NSNumber *priorityValue = [self parsePriority];
|
|
|
|
predicate->priority = priorityValue;
|
|
predicate->relation = relation;
|
|
predicate->constant = parsedConstant;
|
|
predicate->view = predicatedView;
|
|
}
|
|
|
|
- (NSLayoutRelation) parseRelation
|
|
{
|
|
if ([_scanner scanString: @"==" intoString: NULL])
|
|
{
|
|
return NSLayoutRelationEqual;
|
|
}
|
|
else if ([_scanner scanString: @">=" intoString: NULL])
|
|
{
|
|
return NSLayoutRelationGreaterThanOrEqual;
|
|
}
|
|
else if ([_scanner scanString: @"<=" intoString: NULL])
|
|
{
|
|
return NSLayoutRelationLessThanOrEqual;
|
|
}
|
|
else
|
|
{
|
|
return NSLayoutRelationEqual;
|
|
}
|
|
}
|
|
|
|
- (NSNumber *) parsePriority
|
|
{
|
|
NSCharacterSet *priorityMarkerCharacterSet =
|
|
[NSCharacterSet characterSetWithCharactersInString: @"@"];
|
|
BOOL foundPriorityMarker =
|
|
[_scanner scanCharactersFromSet: priorityMarkerCharacterSet
|
|
intoString: NULL];
|
|
if (!foundPriorityMarker)
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
return [self parseConstant];
|
|
}
|
|
|
|
- (NSNumber *) resolveMetricWithIdentifier:(NSString *)identifier
|
|
{
|
|
NSNumber *metric = [_metrics objectForKey: identifier];
|
|
if (metric == nil)
|
|
{
|
|
[self failParseWithMessage: @"Found metric not inside metric dictionary"];
|
|
}
|
|
return metric;
|
|
}
|
|
|
|
- (NSView *) resolveViewWithIdentifier:(NSString *)identifier
|
|
{
|
|
NSView *view = [_views objectForKey: identifier];
|
|
if (view == nil)
|
|
{
|
|
[self failParseWithMessage: @"Found view not inside view dictionary"];
|
|
}
|
|
return view;
|
|
}
|
|
|
|
- (NSNumber *) parseConstant
|
|
{
|
|
CGFloat constant;
|
|
BOOL scanConstantResult = [_scanner scanDouble: &constant];
|
|
if (scanConstantResult)
|
|
{
|
|
return [NSNumber numberWithFloat: constant];
|
|
}
|
|
|
|
NSString *metricName = [self parseIdentifier];
|
|
if (![self isValidIdentifier: metricName])
|
|
{
|
|
[self failParseWithMessage:
|
|
@"Invalid metric identifier. Metric identifiers must be a "
|
|
@"valid C identifier and may only contain letters, numbers "
|
|
@"and underscores"];
|
|
}
|
|
|
|
return [self resolveMetricWithIdentifier: metricName];
|
|
}
|
|
|
|
- (NSString *) parseIdentifier
|
|
{
|
|
NSString *identifierName = nil;
|
|
NSCharacterSet *identifierTerminators =
|
|
[NSCharacterSet characterSetWithCharactersInString: @"),"];
|
|
BOOL scannedIdentifier =
|
|
[_scanner scanUpToCharactersFromSet: identifierTerminators
|
|
intoString: &identifierName];
|
|
if (!scannedIdentifier)
|
|
{
|
|
[self failParseWithMessage: @"Failed to find constant or metric"];
|
|
}
|
|
|
|
return identifierName;
|
|
}
|
|
|
|
- (void) parseViewOpen
|
|
{
|
|
NSCharacterSet *openViewIdentifier =
|
|
[NSCharacterSet characterSetWithCharactersInString: @"["];
|
|
BOOL scannedOpenBracket = [_scanner scanCharactersFromSet: openViewIdentifier
|
|
intoString: NULL];
|
|
if (!scannedOpenBracket)
|
|
{
|
|
[[NSException exceptionWithName: NSInternalInconsistencyException
|
|
reason: @"A view must start with a '['"
|
|
userInfo: nil] raise];
|
|
}
|
|
}
|
|
|
|
- (void) parseViewClose
|
|
{
|
|
NSCharacterSet *closeViewIdentifier =
|
|
[NSCharacterSet characterSetWithCharactersInString: @"]"];
|
|
BOOL scannedCloseBracket =
|
|
[_scanner scanCharactersFromSet: closeViewIdentifier intoString: NULL];
|
|
if (!scannedCloseBracket)
|
|
{
|
|
[[NSException exceptionWithName: NSInternalInconsistencyException
|
|
reason: @"A view must end with a ']'"
|
|
userInfo: nil] raise];
|
|
}
|
|
}
|
|
|
|
- (BOOL) isValidIdentifier:(NSString *)identifer
|
|
{
|
|
NSRegularExpression *cIdentifierRegex = [NSRegularExpression
|
|
regularExpressionWithPattern: @"^[a-zA-Z_][a-zA-Z0-9_]*$"
|
|
options: 0
|
|
error: NULL];
|
|
NSArray *matches =
|
|
[cIdentifierRegex matchesInString: identifer
|
|
options: 0
|
|
range: NSMakeRange (0, identifer.length)];
|
|
|
|
return [matches count] > 0;
|
|
}
|
|
|
|
- (NSArray *) layoutAttributesForLayoutFormatOptions:
|
|
(NSLayoutFormatOptions)options
|
|
{
|
|
NSMutableArray *attributes = [NSMutableArray array];
|
|
|
|
if (options & NSLayoutFormatAlignAllLeft)
|
|
{
|
|
[attributes
|
|
addObject: [NSNumber numberWithInteger: NSLayoutAttributeLeft]];
|
|
}
|
|
if (options & NSLayoutFormatAlignAllRight)
|
|
{
|
|
[attributes
|
|
addObject: [NSNumber numberWithInteger: NSLayoutAttributeRight]];
|
|
}
|
|
if (options & NSLayoutFormatAlignAllTop)
|
|
{
|
|
[attributes addObject: [NSNumber numberWithInteger: NSLayoutAttributeTop]];
|
|
}
|
|
if (options & NSLayoutFormatAlignAllBottom)
|
|
{
|
|
[attributes
|
|
addObject: [NSNumber numberWithInteger: NSLayoutAttributeBottom]];
|
|
}
|
|
if (options & NSLayoutFormatAlignAllLeading)
|
|
{
|
|
[attributes
|
|
addObject: [NSNumber numberWithInteger: NSLayoutAttributeLeading]];
|
|
}
|
|
if (options & NSLayoutFormatAlignAllTrailing)
|
|
{
|
|
[attributes
|
|
addObject: [NSNumber numberWithInteger: NSLayoutAttributeTrailing]];
|
|
}
|
|
if (options & NSLayoutFormatAlignAllCenterX)
|
|
{
|
|
[attributes
|
|
addObject: [NSNumber numberWithInteger: NSLayoutAttributeCenterX]];
|
|
}
|
|
if (options & NSLayoutFormatAlignAllCenterY)
|
|
{
|
|
[attributes
|
|
addObject: [NSNumber numberWithInteger: NSLayoutAttributeCenterY]];
|
|
}
|
|
if (options & NSLayoutFormatAlignAllBaseline)
|
|
{
|
|
[attributes
|
|
addObject: [NSNumber numberWithInteger: NSLayoutAttributeBaseline]];
|
|
}
|
|
if (options & NSLayoutFormatAlignAllFirstBaseline)
|
|
{
|
|
[attributes
|
|
addObject: [NSNumber
|
|
numberWithInteger: NSLayoutAttributeFirstBaseline]];
|
|
}
|
|
|
|
if ([attributes count] == 0)
|
|
{
|
|
[self failParseWithMessage:@"Unrecognized layout formatting option"];
|
|
}
|
|
|
|
return attributes;
|
|
}
|
|
|
|
- (void) failParseWithMessage:(NSString *)parseErrorMessage
|
|
{
|
|
NSException *parseException = [NSException
|
|
exceptionWithName:NSInvalidArgumentException
|
|
reason:[NSString stringWithFormat:
|
|
@"Unable to parse constraint format: %@",
|
|
parseErrorMessage]
|
|
userInfo: nil];
|
|
[parseException raise];
|
|
}
|
|
|
|
@end
|