mirror of
https://github.com/kennethreitz-archive/gitx.git
synced 2026-06-05 23:40:18 +00:00
1050 lines
38 KiB
Objective-C
1050 lines
38 KiB
Objective-C
//
|
|
// MGScopeBar.m
|
|
// MGScopeBar
|
|
//
|
|
// Created by Matt Gemmell on 15/03/2008.
|
|
// Copyright 2008 Instinctive Code.
|
|
//
|
|
|
|
#import "MGScopeBar.h"
|
|
#import "MGRecessedPopUpButtonCell.h"
|
|
|
|
|
|
#define SCOPE_BAR_H_INSET 8.0 // inset on left and right
|
|
#define SCOPE_BAR_HEIGHT 25.0 // used in -sizeToFit
|
|
#define SCOPE_BAR_START_COLOR_GRAY [NSColor colorWithCalibratedWhite:0.75 alpha:1.0] // bottom color of gray gradient
|
|
#define SCOPE_BAR_END_COLOR_GRAY [NSColor colorWithCalibratedWhite:0.90 alpha:1.0] // top color of gray gradient
|
|
#define SCOPE_BAR_START_COLOR_BLUE [NSColor colorWithCalibratedRed:0.71 green:0.75 blue:0.81 alpha:1.0] // bottom color of blue gradient
|
|
#define SCOPE_BAR_END_COLOR_BLUE [NSColor colorWithCalibratedRed:0.80 green:0.82 blue:0.87 alpha:1.0] // top color of blue gradient
|
|
#define SCOPE_BAR_BORDER_COLOR [NSColor colorWithCalibratedWhite:0.69 alpha:1.0] // bottom line's color
|
|
#define SCOPE_BAR_BORDER_WIDTH 1.0 // bottom line's width
|
|
|
|
#define SCOPE_BAR_SEPARATOR_COLOR [NSColor colorWithCalibratedWhite:0.52 alpha:1.0] // color of vertical-line separators between groups
|
|
#define SCOPE_BAR_SEPARATOR_WIDTH 1.0 // width of vertical-line separators between groups
|
|
#define SCOPE_BAR_SEPARATOR_HEIGHT 16.0 // separators are vertically centered in the bar
|
|
|
|
#define SCOPE_BAR_LABEL_COLOR [NSColor colorWithCalibratedWhite:0.45 alpha:1.0] // color of groups' labels
|
|
#define SCOPE_BAR_FONTSIZE 12.0 // font-size of labels and buttons
|
|
#define SCOPE_BAR_ITEM_SPACING 6.0 // spacing between buttons/separators/labels
|
|
#define SCOPE_BAR_BUTTON_IMAGE_SIZE 16.0 // size of buttons' images (width and height)
|
|
|
|
#define SCOPE_BAR_HIDE_POPUP_BG YES // whether the bezel background of an NSPopUpButton is hidden when none of its menu-items are selected.
|
|
|
|
// Appearance metrics. These were chosen to mimic the Finder's "Find" (Spotlight / Smart Group / etc) window's scope-bar.
|
|
#define MENU_PADDING 25.0 // how much wider a popup-button is than a regular button with the same title.
|
|
#define MENU_MIN_WIDTH 60.0 // minimum width a popup-button can be narrowed to.
|
|
|
|
// NSPopUpButton titles used for groups which allow multiple selection.
|
|
#define POPUP_TITLE_EMPTY_SELECTION NSLocalizedString(@"(None)", nil) // title used when no items in the popup are selected.
|
|
#define POPUP_TITLE_MULTIPLE_SELECTION NSLocalizedString(@"(Multiple)", nil) // title used when multiple items in the popup are selected.
|
|
|
|
|
|
// ---- end of configurable settings ---- //
|
|
|
|
|
|
// Keys for internal use.
|
|
#define GROUP_IDENTIFIERS @"Identifiers" // NSMutableArray of identifier strings.
|
|
#define GROUP_BUTTONS @"Buttons" // NSMutableArray of either NSButtons or NSMenuItems, one per item.
|
|
#define GROUP_SELECTION_MODE @"SelectionMode" // MGScopeBarGroupSelectionMode (int) as NSNumber.
|
|
#define GROUP_MENU_MODE @"MenuMode" // BOOL, YES if group is collected in a popup-menu, else NO.
|
|
#define GROUP_POPUP_BUTTON @"PopupButton" // NSPopUpButton (only present if group is in menu-mode).
|
|
#define GROUP_HAS_SEPARATOR @"HasSeparator" // BOOL, YES if group has a separator before it.
|
|
#define GROUP_HAS_LABEL @"HasLabel" // BOOL, YES if group has a label.
|
|
#define GROUP_LABEL_FIELD @"LabelField" // NSTextField for the label (optional; only if group has a label)
|
|
#define GROUP_TOTAL_BUTTONS_WIDTH @"TotalButtonsWidth" // Width of all buttons in a group plus spacings between them (doesn't include label etc)
|
|
#define GROUP_WIDEST_BUTTON_WIDTH @"WidestButtonWidth" // Width of widest button, used when making popup-menus.
|
|
#define GROUP_CUMULATIVE_WIDTH @"CumulativeWidth" // Width from left of leftmost group to right of this group (all groups fully expanded).
|
|
|
|
|
|
@interface MGScopeBar (MGPrivateMethods)
|
|
|
|
- (IBAction)scopeButtonClicked:(id)sender;
|
|
- (NSButton *)getButtonForItem:(NSString *)identifier inGroup:(int)groupNumber; // returns relevant button/menu-item
|
|
- (void)updateSelectedState:(BOOL)selected forItem:(NSString *)identifier inGroup:(int)groupNumber informDelegate:(BOOL)inform;
|
|
- (NSButton *)buttonForItem:(NSString *)identifier inGroup:(int)groupNumber
|
|
withTitle:(NSString *)title image:(NSImage *)image; // creates a new NSButton
|
|
- (NSMenuItem *)menuItemForItem:(NSString *)identifier inGroup:(int)groupNumber
|
|
withTitle:(NSString *)title image:(NSImage *)image; // creates a new NSMenuitem
|
|
- (NSPopUpButton *)popupButtonForGroup:(NSDictionary *)group;
|
|
- (void)setControl:(NSObject *)control forIdentifier:(NSString *)identifier inGroup:(int)groupNumber;
|
|
- (void)updateMenuTitleForGroupAtIndex:(int)groupNumber;
|
|
|
|
@end
|
|
|
|
|
|
@implementation MGScopeBar
|
|
|
|
|
|
#pragma mark Setup and teardown
|
|
|
|
|
|
- (id)initWithFrame:(NSRect)frame
|
|
{
|
|
self = [super initWithFrame:frame];
|
|
if (self) {
|
|
_smartResizeEnabled = YES;
|
|
// Everything else is reset in -reloadData.
|
|
}
|
|
return self;
|
|
}
|
|
|
|
|
|
- (void)dealloc
|
|
{
|
|
delegate = nil;
|
|
if (_accessoryView) {
|
|
[_accessoryView removeFromSuperview];
|
|
_accessoryView = nil; // weak ref
|
|
}
|
|
[_separatorPositions release];
|
|
[_groups release];
|
|
[_identifiers release];
|
|
[_selectedItems release];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
|
|
#pragma mark Data management
|
|
|
|
|
|
- (void)reloadData
|
|
{
|
|
// Resize if necessary.
|
|
[self sizeToFit];
|
|
|
|
// Remove any old objects.
|
|
if (_accessoryView) {
|
|
[_accessoryView removeFromSuperview];
|
|
_accessoryView = nil; // weak ref
|
|
}
|
|
|
|
NSArray *subviews = [[self subviews] copy]; // so we don't mutate the collection we're iterating over.
|
|
[subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
|
[subviews release]; // because copies are retained.
|
|
|
|
[_separatorPositions release];
|
|
_separatorPositions = nil;
|
|
[_groups release];
|
|
_groups = nil;
|
|
[_identifiers release];
|
|
_identifiers = nil;
|
|
[_selectedItems release];
|
|
_selectedItems = nil;
|
|
_firstCollapsedGroup = NSNotFound;
|
|
_lastWidth = NSNotFound;
|
|
_totalGroupsWidth = 0;
|
|
_totalGroupsWidthForPopups = 0;
|
|
|
|
// Configure contents via delegate.
|
|
if (self.delegate && [delegate conformsToProtocol:@protocol(MGScopeBarDelegate)]) {
|
|
int numGroups = [delegate numberOfGroupsInScopeBar:self];
|
|
|
|
if (numGroups > 0) {
|
|
_separatorPositions = [[NSMutableArray alloc] initWithCapacity:numGroups];
|
|
_groups = [[NSMutableArray alloc] initWithCapacity:numGroups];
|
|
_identifiers = [[NSMutableDictionary alloc] initWithCapacity:0];
|
|
_selectedItems = [[NSMutableArray alloc] initWithCapacity:numGroups];
|
|
|
|
int xCoord = SCOPE_BAR_H_INSET;
|
|
NSRect ctrlRect = NSZeroRect;
|
|
BOOL providesImages = [delegate respondsToSelector:@selector(scopeBar:imageForItem:inGroup:)];
|
|
|
|
int groupNum;
|
|
for (groupNum = 0; groupNum < numGroups; groupNum++) {
|
|
// Add separator if appropriate.
|
|
BOOL addSeparator = (groupNum > 0); // default behavior.
|
|
if ([delegate respondsToSelector:@selector(scopeBar:showSeparatorBeforeGroup:)]) {
|
|
addSeparator = [delegate scopeBar:self showSeparatorBeforeGroup:groupNum];
|
|
}
|
|
if (addSeparator) {
|
|
[_separatorPositions addObject:[NSNumber numberWithInt:xCoord]];
|
|
xCoord += SCOPE_BAR_SEPARATOR_WIDTH + SCOPE_BAR_ITEM_SPACING;
|
|
|
|
_totalGroupsWidth += SCOPE_BAR_SEPARATOR_WIDTH + SCOPE_BAR_ITEM_SPACING;
|
|
_totalGroupsWidthForPopups += SCOPE_BAR_SEPARATOR_WIDTH + SCOPE_BAR_ITEM_SPACING;
|
|
} else {
|
|
[_separatorPositions addObject:[NSNull null]];
|
|
}
|
|
|
|
// Add label if appropriate.
|
|
NSString *groupLabel = [delegate scopeBar:self labelForGroup:groupNum];
|
|
NSTextField *labelField = nil;
|
|
BOOL hasLabel = NO;
|
|
if (groupLabel && [groupLabel length] > 0) {
|
|
hasLabel = YES;
|
|
ctrlRect = NSMakeRect(xCoord, 6, 15, 50);
|
|
labelField = [[NSTextField alloc] initWithFrame:ctrlRect];
|
|
[labelField setStringValue:groupLabel];
|
|
[labelField setEditable:NO];
|
|
[labelField setBordered:NO];
|
|
[labelField setDrawsBackground:NO];
|
|
[labelField setTextColor:SCOPE_BAR_LABEL_COLOR];
|
|
[labelField setFont:[NSFont boldSystemFontOfSize:SCOPE_BAR_FONTSIZE]];
|
|
[labelField sizeToFit];
|
|
ctrlRect.size = [labelField frame].size;
|
|
[labelField setFrame:ctrlRect];
|
|
[self addSubview:labelField];
|
|
[labelField release];
|
|
|
|
xCoord += ctrlRect.size.width + SCOPE_BAR_ITEM_SPACING;
|
|
|
|
_totalGroupsWidth += ctrlRect.size.width + SCOPE_BAR_ITEM_SPACING;
|
|
_totalGroupsWidthForPopups += ctrlRect.size.width + SCOPE_BAR_ITEM_SPACING;
|
|
}
|
|
|
|
// Create group information for use during interaction.
|
|
NSArray *identifiers = [delegate scopeBar:self itemIdentifiersForGroup:groupNum];
|
|
NSMutableArray *usedIdentifiers = [NSMutableArray arrayWithCapacity:[identifiers count]];
|
|
NSMutableArray *buttons = [NSMutableArray arrayWithCapacity:[identifiers count]];
|
|
MGScopeBarGroupSelectionMode selMode = [delegate scopeBar:self selectionModeForGroup:groupNum];
|
|
if (selMode != MGRadioSelectionMode && selMode != MGMultipleSelectionMode) {
|
|
// Sanity check, since this is just an int.
|
|
selMode = MGRadioSelectionMode;
|
|
}
|
|
NSMutableDictionary *groupInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
usedIdentifiers, GROUP_IDENTIFIERS,
|
|
buttons, GROUP_BUTTONS,
|
|
[NSNumber numberWithInt:selMode], GROUP_SELECTION_MODE,
|
|
[NSNumber numberWithBool:NO], GROUP_MENU_MODE,
|
|
[NSNumber numberWithBool:hasLabel], GROUP_HAS_LABEL,
|
|
[NSNumber numberWithBool:addSeparator], GROUP_HAS_SEPARATOR,
|
|
nil];
|
|
if (hasLabel) {
|
|
[groupInfo setObject:labelField forKey:GROUP_LABEL_FIELD];
|
|
}
|
|
[_groups addObject:groupInfo];
|
|
[_selectedItems addObject:[NSMutableArray arrayWithCapacity:0]];
|
|
|
|
// Add buttons for this group.
|
|
float widestButtonWidth = 0;
|
|
float totalButtonsWidth = 0;
|
|
for (NSString *itemID in identifiers) {
|
|
if (![usedIdentifiers containsObject:itemID]) {
|
|
[usedIdentifiers addObject:itemID];
|
|
} else {
|
|
// Identifier already used for this group; skip it.
|
|
continue;
|
|
}
|
|
|
|
NSString *title = [delegate scopeBar:self titleOfItem:itemID inGroup:groupNum];
|
|
NSImage *image = nil;
|
|
if (providesImages) {
|
|
image = [delegate scopeBar:self imageForItem:itemID inGroup:groupNum];
|
|
}
|
|
NSButton *button = [self buttonForItem:itemID inGroup:groupNum withTitle:title image:image];
|
|
|
|
ctrlRect = [button frame];
|
|
ctrlRect.origin.x = xCoord;
|
|
[button setFrame:ctrlRect];
|
|
[self addSubview:button];
|
|
[buttons addObject:button];
|
|
|
|
// Adjust x-coordinate for next item in the bar.
|
|
xCoord += ctrlRect.size.width + SCOPE_BAR_ITEM_SPACING;
|
|
|
|
// Update total and widest button widths.
|
|
if (totalButtonsWidth > 0) {
|
|
// Add spacing before this item, since it's not the first in the group.
|
|
totalButtonsWidth += SCOPE_BAR_ITEM_SPACING;
|
|
}
|
|
totalButtonsWidth += ctrlRect.size.width;
|
|
if (ctrlRect.size.width > widestButtonWidth) {
|
|
widestButtonWidth = ctrlRect.size.width;
|
|
}
|
|
}
|
|
|
|
// Add the accumulated buttons' width and the widest button's width to groupInfo.
|
|
[groupInfo setObject:[NSNumber numberWithFloat:totalButtonsWidth] forKey:GROUP_TOTAL_BUTTONS_WIDTH];
|
|
[groupInfo setObject:[NSNumber numberWithFloat:widestButtonWidth] forKey:GROUP_WIDEST_BUTTON_WIDTH];
|
|
|
|
_totalGroupsWidth += totalButtonsWidth;
|
|
_totalGroupsWidthForPopups += widestButtonWidth + MENU_PADDING;
|
|
|
|
float cumulativeWidth = _totalGroupsWidth + (groupNum * SCOPE_BAR_ITEM_SPACING);
|
|
[groupInfo setObject:[NSNumber numberWithFloat:cumulativeWidth] forKey:GROUP_CUMULATIVE_WIDTH];
|
|
|
|
// If this is a radio-mode group, select the first item automatically.
|
|
if (selMode == MGRadioSelectionMode) {
|
|
[self updateSelectedState:YES forItem:[identifiers objectAtIndex:0] inGroup:groupNum informDelegate:YES];
|
|
}
|
|
}
|
|
|
|
_totalGroupsWidth += ((numGroups - 1) * SCOPE_BAR_ITEM_SPACING);
|
|
_totalGroupsWidthForPopups += ((numGroups - 1) * SCOPE_BAR_ITEM_SPACING);
|
|
}
|
|
|
|
// Add accessoryView, if provided.
|
|
if ([delegate respondsToSelector:@selector(accessoryViewForScopeBar:)]) {
|
|
_accessoryView = [delegate accessoryViewForScopeBar:self];
|
|
if (_accessoryView) {
|
|
// Remove NSViewMaxXMargin flag from resizing mask, if present.
|
|
NSUInteger mask = [_accessoryView autoresizingMask];
|
|
if (mask & NSViewMaxXMargin) {
|
|
mask &= ~NSViewMaxXMargin;
|
|
}
|
|
|
|
// Add NSViewMinXMargin flag to resizing mask, if not present.
|
|
if (!(mask & NSViewMinXMargin)) {
|
|
mask = (mask | NSViewMinXMargin);
|
|
}
|
|
|
|
// Update view sizing mask.
|
|
[_accessoryView setAutoresizingMask:mask];
|
|
|
|
// Adjust frame appropriately.
|
|
NSRect frame = [_accessoryView frame];
|
|
frame.origin.x = round(NSMaxX([self bounds]) - (frame.size.width + SCOPE_BAR_H_INSET));
|
|
frame.origin.y = round(((SCOPE_BAR_HEIGHT - frame.size.height) / 2.0));
|
|
[_accessoryView setFrame:frame];
|
|
|
|
// Add as subview.
|
|
[self addSubview:_accessoryView];
|
|
}
|
|
}
|
|
|
|
// Layout subviews appropriately.
|
|
[self adjustSubviews];
|
|
}
|
|
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
|
|
|
|
#pragma mark Utility methods
|
|
|
|
|
|
- (void)sizeToFit
|
|
{
|
|
NSRect frame = [self frame];
|
|
if (frame.size.height != SCOPE_BAR_HEIGHT) {
|
|
float delta = SCOPE_BAR_HEIGHT - frame.size.height;
|
|
frame.size.height += delta;
|
|
frame.origin.y -= delta;
|
|
[self setFrame:frame];
|
|
}
|
|
}
|
|
|
|
|
|
- (void)adjustSubviews
|
|
{
|
|
if (!_smartResizeEnabled) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
We need to work out which groups we can show fully expanded, and which must be collapsed into popup-buttons.
|
|
Any kind of frame-change may have happened, so we need to take care to create or remove buttons or popup-buttons as needed.
|
|
*/
|
|
|
|
// Bail out if we have nothing to do.
|
|
if (!_groups || [_groups count] == 0) {
|
|
return;
|
|
}
|
|
|
|
// Obtain current width of view.
|
|
float viewWidth = [self bounds].size.width;
|
|
|
|
// Abort if there hasn't been any genuine change in width.
|
|
if ((viewWidth == _lastWidth) && (_lastWidth != NSNotFound)) {
|
|
return;
|
|
}
|
|
|
|
// Determine whether we got narrower or wider.
|
|
float narrower = ((_lastWidth == NSNotFound) || (viewWidth < _lastWidth));
|
|
|
|
// Find width available for showing groups.
|
|
float availableWidth = viewWidth - (SCOPE_BAR_H_INSET * 2.0);
|
|
if (_accessoryView) {
|
|
// Account for _accessoryView, leaving a normal amount of spacing to the left of it.
|
|
availableWidth -= ([_accessoryView frame].size.width + SCOPE_BAR_ITEM_SPACING);
|
|
}
|
|
|
|
BOOL shouldAdjustPopups = (availableWidth < _totalGroupsWidthForPopups);
|
|
NSInteger oldFirstCollapsedGroup = _firstCollapsedGroup;
|
|
|
|
// Work out which groups we should now check for collapsibility/expandability.
|
|
NSEnumerator *groupsEnumerator = nil;
|
|
NSRange enumRange;
|
|
BOOL proceed = YES;
|
|
|
|
if (narrower) {
|
|
// Got narrower, so work backwards from oldFirstCollapsedGroup (excluding that group, since it's already collapsed),
|
|
// checking to see if we need to collapse any more groups to the left.
|
|
enumRange = NSMakeRange(0, oldFirstCollapsedGroup);
|
|
// If no groups were previously collapsed, work backwards from the last group (including that group).
|
|
if (oldFirstCollapsedGroup == NSNotFound) {
|
|
enumRange.length = [_groups count];
|
|
}
|
|
groupsEnumerator = [[_groups subarrayWithRange:enumRange] reverseObjectEnumerator];
|
|
|
|
} else {
|
|
// Got wider, so work forwards from oldFirstCollapsedGroup (including that group) checking to see if we can
|
|
// expand any groups into full buttons.
|
|
enumRange = NSMakeRange(oldFirstCollapsedGroup, [_groups count] - oldFirstCollapsedGroup);
|
|
// If no groups were previously collapsed, we have nothing to do here.
|
|
if (oldFirstCollapsedGroup == NSNotFound) {
|
|
proceed = NO;
|
|
}
|
|
if (proceed) {
|
|
groupsEnumerator = [[_groups subarrayWithRange:enumRange] objectEnumerator];
|
|
}
|
|
}
|
|
|
|
// Get the current occupied width within this view.
|
|
float currentOccupiedWidth = 0;
|
|
NSDictionary *group = [_groups objectAtIndex:0];
|
|
BOOL menuMode = [[group objectForKey:GROUP_MENU_MODE] boolValue];
|
|
NSButton *firstButton = nil;
|
|
if (menuMode) {
|
|
firstButton = [group objectForKey:GROUP_POPUP_BUTTON];
|
|
} else {
|
|
firstButton = [[group objectForKey:GROUP_BUTTONS] objectAtIndex:0];
|
|
}
|
|
float leftLimit = NSMinX([firstButton frame]);
|
|
// Account for label in first group, if present.
|
|
if ([[group objectForKey:GROUP_HAS_LABEL] boolValue]) {
|
|
NSTextField *label = (NSTextField *)[group objectForKey:GROUP_LABEL_FIELD];
|
|
leftLimit -= (SCOPE_BAR_ITEM_SPACING + [label frame].size.width);
|
|
}
|
|
|
|
group = [_groups lastObject];
|
|
menuMode = [[group objectForKey:GROUP_MENU_MODE] boolValue];
|
|
NSButton *lastButton = nil;
|
|
if (menuMode) {
|
|
lastButton = [group objectForKey:GROUP_POPUP_BUTTON];
|
|
} else {
|
|
lastButton = [[group objectForKey:GROUP_BUTTONS] lastObject];
|
|
}
|
|
float rightLimit = NSMaxX([lastButton frame]);
|
|
currentOccupiedWidth = rightLimit - leftLimit;
|
|
|
|
// Work out whether we need to try collapsing groups at all, if we're narrowing.
|
|
// We have already handled the case of not requiring to expand groups if we're widening, above.
|
|
if (proceed && narrower) {
|
|
if (availableWidth >= currentOccupiedWidth) {
|
|
// We still have enough room for what we're showing; no change needed.
|
|
proceed = NO;
|
|
}
|
|
}
|
|
|
|
if (proceed) {
|
|
// Disable screen updates.
|
|
NSDisableScreenUpdates();
|
|
|
|
// See how many further groups we can expand or contract.
|
|
float theoreticalOccupiedWidth = currentOccupiedWidth;
|
|
for (NSDictionary *groupInfo in groupsEnumerator) {
|
|
BOOL complete = NO;
|
|
float expandedWidth = [[groupInfo objectForKey:GROUP_TOTAL_BUTTONS_WIDTH] floatValue];
|
|
float contractedWidth = [[groupInfo objectForKey:GROUP_WIDEST_BUTTON_WIDTH] floatValue] + MENU_PADDING;
|
|
|
|
if (narrower) {
|
|
// We're narrowing. See if collapsing this group brings us within availableWidth.
|
|
if (((theoreticalOccupiedWidth - expandedWidth) + contractedWidth) <= availableWidth) {
|
|
// We're now within width constraints, so we're done iterating.
|
|
complete = YES;
|
|
} // else, continue trying to to collapse groups.
|
|
theoreticalOccupiedWidth = ((theoreticalOccupiedWidth - expandedWidth) + contractedWidth);
|
|
|
|
} else {
|
|
// We're widening. See if we can expand this group and still be within availableWidth.
|
|
if (((theoreticalOccupiedWidth - contractedWidth) + expandedWidth) > availableWidth) {
|
|
// We'd be too wide if we expanded this group. Terminate iteration without updating _firstCollapsedGroup.
|
|
//DLog(@"We'd be too wide if we expanded right now");
|
|
break;
|
|
} // else, continue trying to expand groups.
|
|
theoreticalOccupiedWidth = ((theoreticalOccupiedWidth - contractedWidth) + expandedWidth);
|
|
//DLog(@"We can continue expanding");
|
|
}
|
|
|
|
// Update _firstCollapsedGroup appropriately.
|
|
if (_firstCollapsedGroup == NSNotFound) {
|
|
_firstCollapsedGroup = ((narrower) ? [_groups count] : -1);
|
|
oldFirstCollapsedGroup = _firstCollapsedGroup;
|
|
}
|
|
_firstCollapsedGroup += ((narrower) ? -1 : 1);
|
|
|
|
// Terminate if we now fit the available space as best we can.
|
|
if (complete) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Work out how many groups we need to actually change.
|
|
NSRange changedRange = NSMakeRange(0, [_groups count]);
|
|
BOOL adjusting = YES;
|
|
//DLog(@"Old firstCollapsedGroup: %d, new: %d", oldFirstCollapsedGroup, _firstCollapsedGroup);
|
|
if (_firstCollapsedGroup != oldFirstCollapsedGroup) {
|
|
if (narrower) {
|
|
// Narrower. _firstCollapsedGroup will be less (earlier) than oldFirstCollapsedGroup.
|
|
changedRange.location = _firstCollapsedGroup;
|
|
changedRange.length = (oldFirstCollapsedGroup - _firstCollapsedGroup);
|
|
} else {
|
|
// Wider. _firstCollapsedGroup will be greater (later) than oldFirstCollapsedGroup.
|
|
changedRange.location = oldFirstCollapsedGroup;
|
|
changedRange.length = (_firstCollapsedGroup - oldFirstCollapsedGroup);
|
|
}
|
|
} else {
|
|
// _firstCollapsedGroup and oldFirstCollapsedGroup are the same; nothing needs changed.
|
|
adjusting = NO;
|
|
}
|
|
|
|
// If a change is required, ensure that each group is expanded or contracted as appropriate.
|
|
if (adjusting || shouldAdjustPopups) {
|
|
//DLog(@"Got %@ - modifying groups %@", ((narrower) ? @"narrower" : @"wider"), NSStringFromRange(changedRange));
|
|
NSInteger nextXCoord = NSNotFound;
|
|
if (adjusting) {
|
|
int i;
|
|
for (i = changedRange.location; i < NSMaxRange(changedRange); i++) {
|
|
NSMutableDictionary *groupInfo = [_groups objectAtIndex:i];
|
|
|
|
if (nextXCoord == NSNotFound) {
|
|
BOOL menuMode = [[groupInfo objectForKey:GROUP_MENU_MODE] boolValue];
|
|
NSButton *firstButton = nil;
|
|
if (!menuMode) {
|
|
firstButton = [[groupInfo objectForKey:GROUP_BUTTONS] objectAtIndex:0];
|
|
} else {
|
|
firstButton = [groupInfo objectForKey:GROUP_POPUP_BUTTON];
|
|
}
|
|
nextXCoord = [firstButton frame].origin.x;
|
|
} else {
|
|
// Add group-spacing, separator and label as appropriate.
|
|
nextXCoord += SCOPE_BAR_ITEM_SPACING;
|
|
if ([[groupInfo objectForKey:GROUP_HAS_SEPARATOR] boolValue]) {
|
|
nextXCoord += (SCOPE_BAR_SEPARATOR_WIDTH + SCOPE_BAR_ITEM_SPACING);
|
|
}
|
|
if ([[groupInfo objectForKey:GROUP_HAS_LABEL] boolValue]) {
|
|
NSTextField *labelField = (NSTextField *)[groupInfo objectForKey:GROUP_LABEL_FIELD];
|
|
float labelWidth = [labelField frame].size.width;
|
|
nextXCoord += (labelWidth + SCOPE_BAR_ITEM_SPACING);
|
|
}
|
|
}
|
|
|
|
NSPopUpButton *popup = nil;
|
|
if (narrower) {
|
|
// Remove buttons.
|
|
NSArray *buttons = [groupInfo objectForKey:GROUP_BUTTONS];
|
|
[buttons makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
|
|
|
// Create popup and add it to this view.
|
|
popup = [self popupButtonForGroup:groupInfo];
|
|
NSRect popupFrame = [popup frame];
|
|
popupFrame.origin.x = nextXCoord;
|
|
[popup setFrame:popupFrame];
|
|
[groupInfo setObject:popup forKey:GROUP_POPUP_BUTTON];
|
|
[self addSubview:popup positioned:NSWindowBelow relativeTo:_accessoryView];
|
|
nextXCoord += popupFrame.size.width;
|
|
|
|
// Ensure popup has appropriate title.
|
|
[self updateMenuTitleForGroupAtIndex:i];
|
|
|
|
} else {
|
|
// Remove and release popup.
|
|
popup = [groupInfo objectForKey:GROUP_POPUP_BUTTON];
|
|
[popup removeFromSuperview];
|
|
[groupInfo removeObjectForKey:GROUP_POPUP_BUTTON];
|
|
|
|
// Replace menuItems with buttons.
|
|
float buttonX = nextXCoord;
|
|
NSMutableArray *menuItems = [groupInfo objectForKey:GROUP_BUTTONS];
|
|
NSArray *selectedItems = [_selectedItems objectAtIndex:i];
|
|
int i;
|
|
for (i = 0; i < [menuItems count]; i++) {
|
|
NSMenuItem *menuItem = [menuItems objectAtIndex:i];
|
|
NSString *itemIdentifier = [menuItem representedObject];
|
|
NSButton *button = [self buttonForItem:itemIdentifier
|
|
inGroup:[menuItem tag]
|
|
withTitle:[menuItem title]
|
|
image:[menuItem image]];
|
|
NSRect buttonFrame = [button frame];
|
|
buttonFrame.origin.x = buttonX;
|
|
[button setFrame:buttonFrame];
|
|
if ([selectedItems containsObject:itemIdentifier]) {
|
|
[button setState:NSOnState];
|
|
}
|
|
[self addSubview:button positioned:NSWindowBelow relativeTo:_accessoryView];
|
|
[menuItems replaceObjectAtIndex:i withObject:button];
|
|
buttonX += [button frame].size.width + SCOPE_BAR_ITEM_SPACING;
|
|
}
|
|
nextXCoord = (buttonX - SCOPE_BAR_ITEM_SPACING);
|
|
}
|
|
|
|
// Update GROUP_MENU_MODE for this group.
|
|
[groupInfo setObject:[NSNumber numberWithBool:narrower] forKey:GROUP_MENU_MODE];
|
|
}
|
|
}
|
|
|
|
// Modify positions/sizes of groups and separators as required.
|
|
float startIndex = MIN(changedRange.location, _firstCollapsedGroup);
|
|
float xCoord = 0;
|
|
float perGroupDelta = 0;
|
|
if (shouldAdjustPopups) {
|
|
perGroupDelta = ((_totalGroupsWidthForPopups - availableWidth) / [_groups count]);
|
|
}
|
|
int i;
|
|
for (i = startIndex; i < [_groups count]; i++) {
|
|
NSDictionary *groupInfo = [_groups objectAtIndex:i];
|
|
BOOL menuMode = [[groupInfo objectForKey:GROUP_MENU_MODE] boolValue];
|
|
|
|
// Further contract or expand popups if appropriate.
|
|
if (shouldAdjustPopups) {
|
|
float fullPopupWidth = [[groupInfo objectForKey:GROUP_WIDEST_BUTTON_WIDTH] floatValue] + MENU_PADDING;
|
|
float popupWidth = fullPopupWidth - perGroupDelta;
|
|
popupWidth = MAX(popupWidth, MENU_MIN_WIDTH);
|
|
popupWidth = MIN(popupWidth, fullPopupWidth);
|
|
|
|
NSPopUpButton *button = [groupInfo objectForKey:GROUP_POPUP_BUTTON];
|
|
NSRect buttonRect = [button frame];
|
|
buttonRect.size.width = popupWidth;
|
|
[button setFrame:buttonRect];
|
|
}
|
|
|
|
// Reposition groups appropriately.
|
|
if (i > startIndex) {
|
|
// Reposition separator if present.
|
|
if ([[groupInfo objectForKey:GROUP_HAS_SEPARATOR] boolValue]) {
|
|
[_separatorPositions replaceObjectAtIndex:i withObject:[NSNumber numberWithInt:xCoord]];
|
|
xCoord += (SCOPE_BAR_SEPARATOR_WIDTH + SCOPE_BAR_ITEM_SPACING);
|
|
}
|
|
|
|
// Reposition label if present.
|
|
if ([[groupInfo objectForKey:GROUP_HAS_LABEL] boolValue]) {
|
|
NSTextField *label = [groupInfo objectForKey:GROUP_LABEL_FIELD];
|
|
NSRect labelFrame = [label frame];
|
|
labelFrame.origin.x = xCoord;
|
|
[label setFrame:labelFrame];
|
|
xCoord = NSMaxX(labelFrame) + SCOPE_BAR_ITEM_SPACING;
|
|
}
|
|
|
|
// Reposition buttons or popup.
|
|
if (menuMode) {
|
|
NSPopUpButton *button = [groupInfo objectForKey:GROUP_POPUP_BUTTON];
|
|
NSRect buttonRect = [button frame];
|
|
buttonRect.origin.x = xCoord;
|
|
[button setFrame:buttonRect];
|
|
xCoord = NSMaxX(buttonRect) + SCOPE_BAR_ITEM_SPACING;
|
|
|
|
} else {
|
|
NSArray *buttons = [groupInfo objectForKey:GROUP_BUTTONS];
|
|
for (NSButton *button in buttons) {
|
|
NSRect buttonRect = [button frame];
|
|
buttonRect.origin.x = xCoord;
|
|
[button setFrame:buttonRect];
|
|
xCoord = NSMaxX(buttonRect) + SCOPE_BAR_ITEM_SPACING;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// Set up initial value of xCoord.
|
|
NSButton *button = nil;
|
|
if (menuMode) {
|
|
button = [groupInfo objectForKey:GROUP_POPUP_BUTTON];
|
|
} else {
|
|
button = [[groupInfo objectForKey:GROUP_BUTTONS] lastObject];
|
|
}
|
|
xCoord = NSMaxX([button frame]) + SCOPE_BAR_ITEM_SPACING;
|
|
}
|
|
}
|
|
|
|
// Reset _firstCollapsedGroup to NSNotFound if necessary.
|
|
if (!narrower) {
|
|
if (_firstCollapsedGroup >= [_groups count]) {
|
|
_firstCollapsedGroup = NSNotFound;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Re-enable screen updates.
|
|
NSEnableScreenUpdates();
|
|
}
|
|
|
|
// Take note of our width for comparison next time.
|
|
_lastWidth = viewWidth;
|
|
}
|
|
|
|
|
|
- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize
|
|
{
|
|
[super resizeSubviewsWithOldSize:oldBoundsSize];
|
|
[self adjustSubviews];
|
|
}
|
|
|
|
|
|
- (NSButton *)getButtonForItem:(NSString *)identifier inGroup:(int)groupNumber
|
|
{
|
|
NSButton *button = nil;
|
|
NSArray *group = [_identifiers objectForKey:identifier];
|
|
if (group && [group count] > groupNumber) {
|
|
NSObject *element = [group objectAtIndex:groupNumber];
|
|
if (element != [NSNull null]) {
|
|
button = (NSButton *)element;
|
|
}
|
|
}
|
|
|
|
return button;
|
|
}
|
|
|
|
|
|
- (NSButton *)buttonForItem:(NSString *)identifier inGroup:(int)groupNumber
|
|
withTitle:(NSString *)title image:(NSImage *)image
|
|
{
|
|
NSRect ctrlRect = NSMakeRect(0, 0, 50, 20); // arbitrary size; will be resized later.
|
|
NSButton *button = [[NSButton alloc] initWithFrame:ctrlRect];
|
|
[button setTitle:title];
|
|
[[button cell] setRepresentedObject:identifier];
|
|
[button setTag:groupNumber];
|
|
[button setFont:[NSFont boldSystemFontOfSize:SCOPE_BAR_FONTSIZE]];
|
|
[button setTarget:self];
|
|
[button setAction:@selector(scopeButtonClicked:)];
|
|
[button setBezelStyle:NSRecessedBezelStyle];
|
|
[button setButtonType:NSPushOnPushOffButton];
|
|
[[button cell] setHighlightsBy:NSCellIsBordered | NSCellIsInsetButton];
|
|
[button setShowsBorderOnlyWhileMouseInside:YES];
|
|
if (image) {
|
|
[image setSize:NSMakeSize(SCOPE_BAR_BUTTON_IMAGE_SIZE, SCOPE_BAR_BUTTON_IMAGE_SIZE)];
|
|
[button setImagePosition:NSImageLeft];
|
|
[button setImage:image];
|
|
}
|
|
[button sizeToFit];
|
|
ctrlRect = [button frame];
|
|
ctrlRect.origin.y = floor(([self frame].size.height - ctrlRect.size.height) / 2.0);
|
|
[button setFrame:ctrlRect];
|
|
|
|
[self setControl:button forIdentifier:identifier inGroup:groupNumber];
|
|
|
|
return [button autorelease];
|
|
}
|
|
|
|
|
|
- (NSMenuItem *)menuItemForItem:(NSString *)identifier inGroup:(int)groupNumber
|
|
withTitle:(NSString *)title image:(NSImage *)image
|
|
{
|
|
NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:title action:@selector(scopeButtonClicked:) keyEquivalent:@""];
|
|
[menuItem setTarget:self];
|
|
[menuItem setImage:image];
|
|
[menuItem setRepresentedObject:identifier];
|
|
[menuItem setTag:groupNumber];
|
|
|
|
[self setControl:menuItem forIdentifier:identifier inGroup:groupNumber];
|
|
|
|
return [menuItem autorelease];
|
|
}
|
|
|
|
|
|
- (NSPopUpButton *)popupButtonForGroup:(NSDictionary *)group
|
|
{
|
|
float popWidth = floor([[group objectForKey:GROUP_WIDEST_BUTTON_WIDTH] floatValue] + MENU_PADDING);
|
|
NSRect popFrame = NSMakeRect(0, 0, popWidth, 20); // arbitrary height.
|
|
NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:popFrame pullsDown:NO];
|
|
|
|
// Since we're not using the selected item's title, we need to specify a NSMenuItem for the title.
|
|
BOOL multiSelect = ([[group objectForKey:GROUP_SELECTION_MODE] intValue] == MGMultipleSelectionMode);
|
|
if (multiSelect) {
|
|
MGRecessedPopUpButtonCell *cell = [[MGRecessedPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
|
|
[popup setCell:cell];
|
|
[cell release];
|
|
|
|
[[popup cell] setUsesItemFromMenu:NO];
|
|
NSMenuItem *titleItem = [[NSMenuItem alloc] init];
|
|
[[popup cell] setMenuItem:titleItem];
|
|
[titleItem release];
|
|
}
|
|
|
|
// Configure appearance and behaviour.
|
|
[popup setFont:[NSFont boldSystemFontOfSize:SCOPE_BAR_FONTSIZE]];
|
|
[popup setBezelStyle:NSRecessedBezelStyle];
|
|
[popup setButtonType:NSPushOnPushOffButton];
|
|
[[popup cell] setHighlightsBy:NSCellIsBordered | NSCellIsInsetButton];
|
|
[popup setShowsBorderOnlyWhileMouseInside:NO];
|
|
[[popup cell] setAltersStateOfSelectedItem:NO];
|
|
[[popup cell] setArrowPosition:NSPopUpArrowAtBottom];
|
|
[popup setPreferredEdge:NSMaxXEdge];
|
|
|
|
// Add appropriate items.
|
|
[popup removeAllItems];
|
|
NSMutableArray *buttons = [group objectForKey:GROUP_BUTTONS];
|
|
int i;
|
|
for (i = 0; i < [buttons count]; i++) {
|
|
NSButton *button = (NSButton *)[buttons objectAtIndex:i];
|
|
NSMenuItem *menuItem = [self menuItemForItem:[[button cell] representedObject]
|
|
inGroup:[button tag]
|
|
withTitle:[button title]
|
|
image:[button image]];
|
|
[menuItem setState:[button state]];
|
|
[buttons replaceObjectAtIndex:i withObject:menuItem];
|
|
[[popup menu] addItem:menuItem];
|
|
}
|
|
|
|
// Vertically center the popup within our frame.
|
|
if (!multiSelect) {
|
|
[popup sizeToFit];
|
|
}
|
|
popFrame = [popup frame];
|
|
popFrame.origin.y = ceil(([self frame].size.height - popFrame.size.height) / 2.0);
|
|
[popup setFrame:popFrame];
|
|
|
|
return [popup autorelease];
|
|
}
|
|
|
|
|
|
- (void)setControl:(NSObject *)control forIdentifier:(NSString *)identifier inGroup:(int)groupNumber
|
|
{
|
|
if (!_identifiers) {
|
|
_identifiers = [[NSMutableDictionary alloc] initWithCapacity:0];
|
|
}
|
|
|
|
NSMutableArray *identArray = [_identifiers objectForKey:identifier];
|
|
if (!identArray) {
|
|
identArray = [[[NSMutableArray alloc] initWithCapacity:groupNumber + 1] autorelease];
|
|
[_identifiers setObject:identArray forKey:identifier];
|
|
}
|
|
|
|
int count = [identArray count];
|
|
if (groupNumber >= count) {
|
|
// Pad identArray with nulls if appropriate, so this control lies at index groupNumber.
|
|
int i;
|
|
for (i = count; i < groupNumber; i++) {
|
|
[identArray addObject:[NSNull null]];
|
|
}
|
|
[identArray addObject:control];
|
|
} else {
|
|
[identArray replaceObjectAtIndex:groupNumber withObject:control];
|
|
}
|
|
}
|
|
|
|
|
|
- (void)updateMenuTitleForGroupAtIndex:(int)groupNumber
|
|
{
|
|
// Ensure that this group's popup (if present) has the correct title,
|
|
// accounting for the group's selection-mode and selected item(s).
|
|
|
|
if (groupNumber < 0 || groupNumber >= [_groups count]) {
|
|
return;
|
|
}
|
|
|
|
NSDictionary *group = [_groups objectAtIndex:groupNumber];
|
|
if (group) {
|
|
NSPopUpButton *popup = [group objectForKey:GROUP_POPUP_BUTTON];
|
|
if (popup) {
|
|
NSArray *groupSelection = [_selectedItems objectAtIndex:groupNumber];
|
|
int numSelected = [groupSelection count];
|
|
if (numSelected == 0) {
|
|
// No items selected.
|
|
[popup setTitle:POPUP_TITLE_EMPTY_SELECTION];
|
|
[[[popup cell] menuItem] setImage:nil];
|
|
|
|
} else if (numSelected > 1) {
|
|
// Multiple items selected.
|
|
[popup setTitle:POPUP_TITLE_MULTIPLE_SELECTION];
|
|
[[[popup cell] menuItem] setImage:nil];
|
|
|
|
} else {
|
|
// One item selected.
|
|
NSString *identifier = [groupSelection objectAtIndex:0];
|
|
NSArray *items = [group objectForKey:GROUP_BUTTONS];
|
|
NSMenuItem *item = nil;
|
|
for (NSMenuItem *thisItem in items) {
|
|
if ([[thisItem representedObject] isEqualToString:identifier]) {
|
|
item = thisItem;
|
|
break;
|
|
}
|
|
}
|
|
if (item) {
|
|
[popup setTitle:[item title]];
|
|
[[[popup cell] menuItem] setImage:[item image]];
|
|
}
|
|
}
|
|
|
|
if (SCOPE_BAR_HIDE_POPUP_BG) {
|
|
BOOL hasBackground = [[popup cell] isBordered];
|
|
if (numSelected == 0 && hasBackground) {
|
|
[[popup cell] setBordered:NO];
|
|
} else if (!hasBackground) {
|
|
[[popup cell] setBordered:YES];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#pragma mark Drawing
|
|
|
|
|
|
- (void)drawRect:(NSRect)rect
|
|
{
|
|
// Draw gradient background.
|
|
NSGradient *gradient = [[[NSGradient alloc] initWithStartingColor:SCOPE_BAR_START_COLOR_GRAY
|
|
endingColor:SCOPE_BAR_END_COLOR_GRAY] autorelease];
|
|
[gradient drawInRect:[self bounds] angle:90.0];
|
|
|
|
// Draw border.
|
|
NSRect lineRect = [self bounds];
|
|
lineRect.size.height = SCOPE_BAR_BORDER_WIDTH;
|
|
[SCOPE_BAR_BORDER_COLOR set];
|
|
NSRectFill(lineRect);
|
|
|
|
// Draw separators.
|
|
if ([_separatorPositions count] > 0) {
|
|
[SCOPE_BAR_SEPARATOR_COLOR set];
|
|
NSRect sepRect = NSMakeRect(0, 0, SCOPE_BAR_SEPARATOR_WIDTH, SCOPE_BAR_SEPARATOR_HEIGHT);
|
|
sepRect.origin.y = (([self bounds].size.height - sepRect.size.height) / 2.0);
|
|
for (NSObject *sepPosn in _separatorPositions) {
|
|
if (sepPosn != [NSNull null]) {
|
|
sepRect.origin.x = [(NSNumber *)sepPosn intValue];
|
|
NSRectFill(sepRect);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#pragma mark Interaction
|
|
|
|
|
|
- (IBAction)scopeButtonClicked:(id)sender
|
|
{
|
|
NSButton *button = (NSButton *)sender;
|
|
BOOL menuMode = [sender isKindOfClass:[NSMenuItem class]];
|
|
NSString *identifier = [((menuMode) ? sender : [sender cell]) representedObject];
|
|
int groupNumber = [sender tag];
|
|
BOOL nowSelected = YES;
|
|
if (menuMode) {
|
|
// MenuItem. Ensure item has appropriate state.
|
|
nowSelected = ![[_selectedItems objectAtIndex:groupNumber] containsObject:identifier];
|
|
[sender setState:((nowSelected) ? NSOnState : NSOffState)];
|
|
} else {
|
|
// Button. Item will already have appropriate state.
|
|
nowSelected = ([button state] != NSOffState);
|
|
}
|
|
[self setSelected:nowSelected forItem:identifier inGroup:groupNumber];
|
|
}
|
|
|
|
|
|
#pragma mark Accessors and properties
|
|
|
|
|
|
- (void)setSelected:(BOOL)selected forItem:(NSString *)identifier inGroup:(int)groupNumber
|
|
{
|
|
// Change state of other items in group appropriately, informing delegate if possible.
|
|
// First we find the appropriate group-info for the item's identifier.
|
|
if (identifier && groupNumber >= 0 && groupNumber < [_groups count]) {
|
|
NSDictionary *group = [_groups objectAtIndex:groupNumber];
|
|
BOOL nowSelected = selected;
|
|
BOOL informDelegate = YES;
|
|
|
|
if (group) {
|
|
[group retain];
|
|
NSDisableScreenUpdates();
|
|
|
|
// We found the group which this item belongs to. Obtain selection-mode and identifiers.
|
|
MGScopeBarGroupSelectionMode selMode = [[group objectForKey:GROUP_SELECTION_MODE] intValue];
|
|
BOOL radioMode = (selMode == MGRadioSelectionMode);
|
|
|
|
if (radioMode) {
|
|
// This is a radio-mode group. Ensure this item isn't already selected.
|
|
NSArray *groupSelections = [[_selectedItems objectAtIndex:groupNumber] copy];
|
|
|
|
if (nowSelected) {
|
|
// Before selecting this item, we first need to deselect any other selected items in this group.
|
|
for (NSString *selectedIdentifier in groupSelections) {
|
|
// Reselect the just-deselected item without informing the delegate, since nothing really changed.
|
|
[self updateSelectedState:NO forItem:selectedIdentifier inGroup:groupNumber informDelegate:NO];
|
|
}
|
|
} else {
|
|
// Prevent deselection if this item is already selected.
|
|
if ([groupSelections containsObject:identifier]) {
|
|
nowSelected = YES;
|
|
informDelegate = NO;
|
|
}
|
|
}
|
|
[groupSelections release];
|
|
}
|
|
|
|
// Change selected state of this item.
|
|
[self updateSelectedState:nowSelected forItem:identifier inGroup:groupNumber informDelegate:informDelegate];
|
|
|
|
// Update popup-menu's title if appropriate.
|
|
if ([[group objectForKey:GROUP_MENU_MODE] boolValue]) {
|
|
[self updateMenuTitleForGroupAtIndex:groupNumber];
|
|
}
|
|
|
|
[group release];
|
|
NSEnableScreenUpdates();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- (void)updateSelectedState:(BOOL)selected forItem:(NSString *)identifier inGroup:(int)groupNumber informDelegate:(BOOL)inform
|
|
{
|
|
// This method simply updates the selected state of the item's control, maintains selectedItems, and informs the delegate.
|
|
// All management of dependencies (such as deselecting other selected items in a radio-selection-mode group) is performed
|
|
// in the setSelected:forItem:inGroup: method.
|
|
|
|
// Determine whether we can inform the delegate about this change.
|
|
SEL stateChangedSel = @selector(scopeBar:selectedStateChanged:forItem:inGroup:);
|
|
BOOL responds = (delegate && [delegate respondsToSelector:stateChangedSel]);
|
|
|
|
// Ensure selected status of item's control reflects desired value.
|
|
NSButton *button = [self getButtonForItem:identifier inGroup:groupNumber];
|
|
if (selected && [button state] == NSOffState) {
|
|
[button setState:NSOnState];
|
|
} else if (!selected && [button state] != NSOffState) {
|
|
[button setState:NSOffState];
|
|
}
|
|
|
|
// Maintain _selectedItems appropriately.
|
|
if (_selectedItems && [_selectedItems count] > groupNumber) {
|
|
NSMutableArray *groupSelections = [_selectedItems objectAtIndex:groupNumber];
|
|
BOOL alreadySelected = [groupSelections containsObject:identifier];
|
|
if (selected && !alreadySelected) {
|
|
[groupSelections addObject:identifier];
|
|
} else if (!selected && alreadySelected) {
|
|
[groupSelections removeObject:identifier];
|
|
}
|
|
}
|
|
|
|
// Inform delegate about this change if possible.
|
|
if (inform && responds) {
|
|
[delegate scopeBar:self selectedStateChanged:selected forItem:identifier inGroup:groupNumber];
|
|
}
|
|
}
|
|
|
|
|
|
- (NSArray *)selectedItems
|
|
{
|
|
return [[_selectedItems copy] autorelease];
|
|
}
|
|
|
|
|
|
- (void)setDelegate:(id)newDelegate
|
|
{
|
|
if (delegate != newDelegate) {
|
|
delegate = newDelegate;
|
|
[self reloadData];
|
|
}
|
|
}
|
|
|
|
|
|
- (BOOL)smartResizeEnabled
|
|
{
|
|
return _smartResizeEnabled;
|
|
}
|
|
|
|
|
|
- (void)setSmartResizeEnabled:(BOOL)enabled
|
|
{
|
|
if (enabled != _smartResizeEnabled) {
|
|
_smartResizeEnabled = enabled;
|
|
[self reloadData];
|
|
}
|
|
}
|
|
|
|
|
|
@synthesize delegate;
|
|
|
|
|
|
@end
|