mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-04-22 16:10:48 +00:00
Merge pull request #161 from BennyKJohnson/constraint-vfl-support
Implement constraintsWithVisualFormat method on NSLayoutConstraint
This commit is contained in:
commit
8ccc89b83d
5 changed files with 874 additions and 5 deletions
|
@ -157,7 +157,12 @@ APPKIT_EXPORT_CLASS
|
|||
|
||||
- (NSLayoutAnchor *) secondAnchor;
|
||||
|
||||
- (NSLayoutPriority) priority;
|
||||
#if GS_HAS_DECLARED_PROPERTIES
|
||||
@property NSLayoutPriority priority;
|
||||
#else
|
||||
- (NSLayoutPriority) priority;
|
||||
- (void) setPriority: (NSLayoutPriority)priority;
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -349,7 +349,8 @@ GSXibLoader.m \
|
|||
GSXibLoading.m \
|
||||
GSXibKeyedUnarchiver.m \
|
||||
GSXib5KeyedUnarchiver.m \
|
||||
GSHelpAttachment.m
|
||||
GSHelpAttachment.m \
|
||||
GSAutoLayoutVFLParser.m
|
||||
|
||||
# Turn off NSMenuItem warning that NSMenuItem conforms to <NSObject>,
|
||||
# but does not implement <NSObject>'s methods itself (it inherits
|
||||
|
@ -658,7 +659,8 @@ GSWindowDecorationView.h \
|
|||
GSXibElement.h \
|
||||
GSXibLoading.h \
|
||||
GSXibKeyedUnarchiver.h \
|
||||
GSHelpAttachment.h
|
||||
GSHelpAttachment.h \
|
||||
GSAutoLayoutVFLParser.h
|
||||
|
||||
libgnustep-gui_HEADER_FILES = ${GUI_HEADERS}
|
||||
|
||||
|
|
47
Source/GSAutoLayoutVFLParser.h
Normal file
47
Source/GSAutoLayoutVFLParser.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/* 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 <Foundation/Foundation.h>
|
||||
#import "AppKit/NSView.h"
|
||||
#import "AppKit/NSLayoutConstraint.h"
|
||||
|
||||
@interface GSAutoLayoutVFLParser : NSObject
|
||||
{
|
||||
NSDictionary *_views;
|
||||
NSLayoutFormatOptions _options;
|
||||
NSDictionary *_metrics;
|
||||
NSScanner *_scanner;
|
||||
NSMutableArray *_constraints;
|
||||
NSMutableArray *_layoutFormatConstraints;
|
||||
NSView *_view;
|
||||
BOOL _createLeadingConstraintToSuperview;
|
||||
BOOL _isVerticalOrientation;
|
||||
}
|
||||
|
||||
- (instancetype) initWithFormat:(NSString *)format
|
||||
options:(NSLayoutFormatOptions)options
|
||||
metrics:(NSDictionary *)metrics
|
||||
views:(NSDictionary *)views;
|
||||
|
||||
- (NSArray *) parse;
|
||||
|
||||
@end
|
806
Source/GSAutoLayoutVFLParser.m
Normal file
806
Source/GSAutoLayoutVFLParser.m
Normal file
|
@ -0,0 +1,806 @@
|
|||
/* 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
|
|
@ -32,6 +32,7 @@
|
|||
#import "AppKit/NSLayoutConstraint.h"
|
||||
#import "AppKit/NSWindow.h"
|
||||
#import "AppKit/NSApplication.h"
|
||||
#import "GSAutoLayoutVFLParser.h"
|
||||
|
||||
static NSMutableArray *activeConstraints = nil;
|
||||
// static NSNotificationCenter *nc = nil;
|
||||
|
@ -236,8 +237,16 @@ static NSMutableArray *activeConstraints = nil;
|
|||
metrics: (NSDictionary *)metrics
|
||||
views: (NSDictionary *)views
|
||||
{
|
||||
NSMutableArray *array = [NSMutableArray arrayWithCapacity: 10];
|
||||
return array;
|
||||
GSAutoLayoutVFLParser *parser = [[GSAutoLayoutVFLParser alloc]
|
||||
initWithFormat: fmt
|
||||
options: opt
|
||||
metrics: metrics
|
||||
views: views];
|
||||
NSArray *constraints = [parser parse];
|
||||
|
||||
RELEASE(parser);
|
||||
|
||||
return constraints;
|
||||
}
|
||||
|
||||
- (instancetype) initWithItem: (id)firstItem
|
||||
|
|
Loading…
Reference in a new issue