SameBoy/HexFiend/HFLayoutRepresenter.m

362 lines
13 KiB
Objective-C

//
// HFRepresenterLayoutView.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFLayoutRepresenter.h>
@interface HFRepresenterLayoutViewInfo : NSObject {
@public
HFRepresenter *rep;
NSView *view;
NSPoint layoutPosition;
NSRect frame;
NSUInteger autoresizingMask;
}
@end
@implementation HFRepresenterLayoutViewInfo
- (NSString *)description {
return [NSString stringWithFormat:@"<%@ : %@>", view, NSStringFromRect(frame)];
}
@end
@implementation HFLayoutRepresenter
static NSInteger sortByLayoutPosition(id a, id b, void *self) {
USE(self);
NSPoint pointA = [a layoutPosition];
NSPoint pointB = [b layoutPosition];
if (pointA.y < pointB.y) return -1;
else if (pointA.y > pointB.y) return 1;
else if (pointA.x < pointB.x) return -1;
else if (pointA.x > pointB.x) return 1;
else return 0;
}
- (NSArray *)arraysOfLayoutInfos {
if (! representers) return nil;
NSMutableArray *result = [NSMutableArray array];
NSArray *reps = [representers sortedArrayUsingFunction:sortByLayoutPosition context:self];
NSMutableArray *currentReps = [NSMutableArray array];
CGFloat currentRepY = - CGFLOAT_MAX;
FOREACH(HFRepresenter*, rep, reps) {
HFRepresenterLayoutViewInfo *info = [[HFRepresenterLayoutViewInfo alloc] init];
info->rep = rep;
info->view = [rep view];
info->frame = [info->view frame];
info->layoutPosition = [rep layoutPosition];
info->autoresizingMask = [info->view autoresizingMask];
if (info->layoutPosition.y != currentRepY && [currentReps count] > 0) {
[result addObject:[[currentReps copy] autorelease]];
[currentReps removeAllObjects];
}
currentRepY = info->layoutPosition.y;
[currentReps addObject:info];
[info release];
}
if ([currentReps count]) [result addObject:[[currentReps copy] autorelease]];
return result;
}
- (NSRect)boundsRectForLayout {
NSRect result = [[self view] bounds];
/* Sometimes when we are not yet in a window, we get wonky bounds, so be paranoid. */
if (result.size.width < 0 || result.size.height < 0) result = NSZeroRect;
return result;
}
- (CGFloat)_computeMinHeightForLayoutInfos:(NSArray *)infos {
CGFloat result = 0;
HFASSERT(infos != NULL);
HFASSERT([infos count] > 0);
FOREACH(HFRepresenterLayoutViewInfo *, info, infos) {
if (! (info->autoresizingMask & NSViewHeightSizable)) result = MAX(result, NSHeight([info->view frame]));
}
return result;
}
- (void)_applyYLocation:(CGFloat)yLocation andMinHeight:(CGFloat)height toInfos:(NSArray *)layoutInfos {
FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) {
info->frame.origin.y = yLocation;
if (info->autoresizingMask & NSViewHeightSizable) info->frame.size.height = height;
}
}
- (void)_layoutInfosHorizontally:(NSArray *)infos inRect:(NSRect)layoutRect withBytesPerLine:(NSUInteger)bytesPerLine {
CGFloat nextX = NSMinX(layoutRect);
NSUInteger numHorizontallyResizable = 0;
FOREACH(HFRepresenterLayoutViewInfo *, info, infos) {
CGFloat minWidth = [info->rep minimumViewWidthForBytesPerLine:bytesPerLine];
info->frame.origin.x = nextX;
info->frame.size.width = minWidth;
nextX += minWidth;
numHorizontallyResizable += !! (info->autoresizingMask & NSViewWidthSizable);
}
CGFloat remainingWidth = NSMaxX(layoutRect) - nextX;
if (numHorizontallyResizable > 0 && remainingWidth > 0) {
NSView *view = [self view];
CGFloat remainingPixels = [view convertSize:NSMakeSize(remainingWidth, 0) toView:nil].width;
HFASSERT(remainingPixels > 0);
CGFloat pixelsPerView = HFFloor(HFFloor(remainingPixels) / (CGFloat)numHorizontallyResizable);
if (pixelsPerView > 0) {
CGFloat pointsPerView = [view convertSize:NSMakeSize(pixelsPerView, 0) fromView:nil].width;
CGFloat pointsAdded = 0;
FOREACH(HFRepresenterLayoutViewInfo *, info, infos) {
info->frame.origin.x += pointsAdded;
if (info->autoresizingMask & NSViewWidthSizable) {
info->frame.size.width += pointsPerView;
pointsAdded += pointsPerView;
}
}
}
}
}
- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine {
CGFloat result = 0;
NSArray *arraysOfLayoutInfos = [self arraysOfLayoutInfos];
FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) {
CGFloat minWidthForRow = 0;
FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) {
minWidthForRow += [info->rep minimumViewWidthForBytesPerLine:bytesPerLine];
}
result = MAX(result, minWidthForRow);
}
return result;
}
- (NSUInteger)_computeBytesPerLineForArraysOfLayoutInfos:(NSArray *)arraysOfLayoutInfos forLayoutInRect:(NSRect)layoutRect {
/* The granularity is our own granularity (probably 1), LCMed with the granularities of all other representers */
NSUInteger granularity = [self byteGranularity];
FOREACH(HFRepresenter *, representer, representers) {
granularity = HFLeastCommonMultiple(granularity, [representer byteGranularity]);
}
HFASSERT(granularity >= 1);
NSUInteger newNumGranules = (NSUIntegerMax - 1) / granularity;
FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) {
NSUInteger maxKnownGood = 0, minKnownBad = newNumGranules + 1;
while (maxKnownGood + 1 < minKnownBad) {
CGFloat requiredSpace = 0;
NSUInteger proposedNumGranules = maxKnownGood + (minKnownBad - maxKnownGood)/2;
NSUInteger proposedBytesPerLine = proposedNumGranules * granularity;
FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) {
requiredSpace += [info->rep minimumViewWidthForBytesPerLine:proposedBytesPerLine];
if (requiredSpace > NSWidth(layoutRect)) break;
}
if (requiredSpace > NSWidth(layoutRect)) minKnownBad = proposedNumGranules;
else maxKnownGood = proposedNumGranules;
}
newNumGranules = maxKnownGood;
}
return MAX(1u, newNumGranules) * granularity;
}
- (BOOL)_anyLayoutInfoIsVerticallyResizable:(NSArray *)vals {
HFASSERT(vals != NULL);
FOREACH(HFRepresenterLayoutViewInfo *, info, vals) {
if (info->autoresizingMask & NSViewHeightSizable) return YES;
}
return NO;
}
- (BOOL)_addVerticalHeight:(CGFloat)heightPoints andOffset:(CGFloat)offsetPoints toLayoutInfos:(NSArray *)layoutInfos {
BOOL isVerticallyResizable = [self _anyLayoutInfoIsVerticallyResizable:layoutInfos];
CGFloat totalHeight = [self _computeMinHeightForLayoutInfos:layoutInfos] + heightPoints;
FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) {
info->frame.origin.y += offsetPoints;
if (isVerticallyResizable) {
if (info->autoresizingMask & NSViewHeightSizable) {
info->frame.size.height = totalHeight;
}
else {
CGFloat diff = totalHeight - info->frame.size.height;
HFASSERT(diff >= 0);
info->frame.origin.y += HFFloor(diff);
}
}
}
return isVerticallyResizable;
}
- (void)_distributeVerticalSpace:(CGFloat)space toArraysOfLayoutInfos:(NSArray *)arraysOfLayoutInfos {
HFASSERT(space >= 0);
HFASSERT(arraysOfLayoutInfos != NULL);
NSUInteger consumers = 0;
FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) {
if ([self _anyLayoutInfoIsVerticallyResizable:layoutInfos]) consumers++;
}
if (consumers > 0) {
NSView *view = [self view];
CGFloat availablePixels = [view convertSize:NSMakeSize(0, space) toView:nil].height;
HFASSERT(availablePixels > 0);
CGFloat pixelsPerView = HFFloor(HFFloor(availablePixels) / (CGFloat)consumers);
CGFloat pointsPerView = [view convertSize:NSMakeSize(0, pixelsPerView) fromView:nil].height;
CGFloat yOffset = 0;
if (pointsPerView > 0) {
FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) {
if ([self _addVerticalHeight:pointsPerView andOffset:yOffset toLayoutInfos:layoutInfos]) {
yOffset += pointsPerView;
}
}
}
}
}
- (void)performLayout {
HFController *controller = [self controller];
if (! controller) return;
if (! representers) return;
NSArray *arraysOfLayoutInfos = [self arraysOfLayoutInfos];
if (! [arraysOfLayoutInfos count]) return;
NSUInteger transaction = [controller beginPropertyChangeTransaction];
NSRect layoutRect = [self boundsRectForLayout];
NSUInteger bytesPerLine;
if (maximizesBytesPerLine) bytesPerLine = [self _computeBytesPerLineForArraysOfLayoutInfos:arraysOfLayoutInfos forLayoutInRect:layoutRect];
else bytesPerLine = [controller bytesPerLine];
CGFloat yPosition = NSMinY(layoutRect);
FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) {
HFASSERT([layoutInfos count] > 0);
CGFloat minHeight = [self _computeMinHeightForLayoutInfos:layoutInfos];
[self _applyYLocation:yPosition andMinHeight:minHeight toInfos:layoutInfos];
yPosition += minHeight;
[self _layoutInfosHorizontally:layoutInfos inRect:layoutRect withBytesPerLine:bytesPerLine];
}
CGFloat remainingVerticalSpace = NSMaxY(layoutRect) - yPosition;
if (remainingVerticalSpace > 0) {
[self _distributeVerticalSpace:remainingVerticalSpace toArraysOfLayoutInfos:arraysOfLayoutInfos];
}
FOREACH(NSArray *, layoutInfoArray, arraysOfLayoutInfos) {
FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfoArray) {
[info->view setFrame:info->frame];
}
}
[controller endPropertyChangeTransaction:transaction];
}
- (NSArray *)representers {
return representers ? [[representers copy] autorelease] : @[];
}
- (instancetype)init {
self = [super init];
maximizesBytesPerLine = YES;
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewFrameDidChangeNotification object:[self view]];
[representers release];
[super dealloc];
}
- (void)encodeWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
[super encodeWithCoder:coder];
[coder encodeObject:representers forKey:@"HFRepresenters"];
[coder encodeBool:maximizesBytesPerLine forKey:@"HFMaximizesBytesPerLine"];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
self = [super initWithCoder:coder];
representers = [[coder decodeObjectForKey:@"HFRepresenters"] retain];
maximizesBytesPerLine = [coder decodeBoolForKey:@"HFMaximizesBytesPerLine"];
NSView *view = [self view];
[view setPostsFrameChangedNotifications:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameChanged:) name:NSViewFrameDidChangeNotification object:view];
return self;
}
- (void)addRepresenter:(HFRepresenter *)representer {
REQUIRE_NOT_NULL(representer);
if (! representers) representers = [[NSMutableArray alloc] init];
HFASSERT([representers indexOfObjectIdenticalTo:representer] == NSNotFound);
[representers addObject:representer];
HFASSERT([[representer view] superview] != [self view]);
[[self view] addSubview:[representer view]];
[self performLayout];
}
- (void)removeRepresenter:(HFRepresenter *)representer {
REQUIRE_NOT_NULL(representer);
HFASSERT([representers indexOfObjectIdenticalTo:representer] != NSNotFound);
NSView *view = [representer view];
HFASSERT([view superview] == [self view]);
[view removeFromSuperview];
[representers removeObjectIdenticalTo:representer];
[self performLayout];
}
- (void)frameChanged:(NSNotification *)note {
USE(note);
[self performLayout];
}
- (void)initializeView {
NSView *view = [self view];
[view setPostsFrameChangedNotifications:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameChanged:) name:NSViewFrameDidChangeNotification object:view];
}
- (NSView *)createView {
return [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];
}
- (void)setMaximizesBytesPerLine:(BOOL)val {
maximizesBytesPerLine = val;
}
- (BOOL)maximizesBytesPerLine {
return maximizesBytesPerLine;
}
- (NSUInteger)maximumBytesPerLineForLayoutInProposedWidth:(CGFloat)proposedWidth {
NSArray *arraysOfLayoutInfos = [self arraysOfLayoutInfos];
if (! [arraysOfLayoutInfos count]) return 0;
NSRect layoutRect = [self boundsRectForLayout];
layoutRect.size.width = proposedWidth;
NSUInteger bytesPerLine = [self _computeBytesPerLineForArraysOfLayoutInfos:arraysOfLayoutInfos forLayoutInRect:layoutRect];
return bytesPerLine;
}
- (CGFloat)minimumViewWidthForLayoutInProposedWidth:(CGFloat)proposedWidth {
NSUInteger bytesPerLine;
if ([self maximizesBytesPerLine]) {
bytesPerLine = [self maximumBytesPerLineForLayoutInProposedWidth:proposedWidth];
} else {
bytesPerLine = [[self controller] bytesPerLine];
}
CGFloat newWidth = [self minimumViewWidthForBytesPerLine:bytesPerLine];
return newWidth;
}
- (void)controllerDidChange:(HFControllerPropertyBits)bits {
[super controllerDidChange:bits];
if (bits & (HFControllerViewSizeRatios | HFControllerBytesPerColumn | HFControllerByteGranularity)) {
[self performLayout];
}
}
@end