API  1.0.0
CPButtonBar.j
Go to the documentation of this file.
1 /*
2  * CPButtonBar.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2009, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
30 
31 @implementation CPButtonBar : CPView
32 {
33  BOOL _hasResizeControl;
34  BOOL _resizeControlIsLeftAligned;
35  CPArray _buttons;
36 }
37 
38 + (id)plusButton
39 {
40  var button = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, 35, 25)],
41  image = [[CPTheme defaultTheme] valueForAttributeWithName:@"button-image-plus" forClass:[CPButtonBar class]];
42 
43  [button setBordered:NO];
44  [button setImage:image];
45  [button setImagePosition:CPImageOnly];
46 
47  return button;
48 }
49 
50 + (id)minusButton
51 {
52  var button = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, 35, 25)],
53  image = [[CPTheme defaultTheme] valueForAttributeWithName:@"button-image-minus" forClass:[CPButtonBar class]];
54 
55  [button setBordered:NO];
56  [button setImage:image];
57  [button setImagePosition:CPImageOnly];
58 
59  return button;
60 }
61 
62 + (id)actionPopupButton
63 {
64  var button = [[CPPopUpButton alloc] initWithFrame:CGRectMake(0, 0, 35, 25)],
65  image = [[CPTheme defaultTheme] valueForAttributeWithName:@"button-image-action" forClass:[CPButtonBar class]];
66 
67  [button addItemWithTitle:nil];
68  [[button lastItem] setImage:image];
69  [button setImagePosition:CPImageOnly];
70  [button setValue:CGInsetMake(0, 0, 0, 0) forThemeAttribute:"content-inset" inState:CPPopUpButtonStatePullsDown];
71 
72  [button setPullsDown:YES];
73 
74  return button;
75 }
76 
77 + (CPString)defaultThemeClass
78 {
79  return @"button-bar";
80 }
81 
82 + (CPDictionary)themeAttributes
83 {
84  return @{
85  @"resize-control-inset": CGInsetMake(0.0, 0.0, 0.0, 0.0),
86  @"resize-control-size": CGSizeMakeZero(),
87  @"resize-control-color": [CPNull null],
88  @"bezel-color": [CPNull null],
89  @"button-bezel-color": [CPNull null],
90  @"button-text-color": [CPNull null],
91  @"button-image-plus": [CPNull null],
92  @"button-image-minus": [CPNull null],
93  @"button-image-action": [CPNull null],
94  };
95 }
96 
97 - (id)initWithFrame:(CGRect)aFrame
98 {
99  self = [super initWithFrame:aFrame];
100 
101  if (self)
102  {
103  _buttons = [];
104  [self setNeedsLayout];
105  }
106 
107  return self;
108 }
109 
110 - (void)awakeFromCib
111 {
112  [super awakeFromCib];
113 
114  var view = [self superview],
115  subview = self;
116 
117  while (view)
118  {
119  if ([view isKindOfClass:[CPSplitView class]])
120  {
121  var viewIndex = [[view subviews] indexOfObject:subview];
122  [view setButtonBar:self forDividerAtIndex:viewIndex];
123 
124  break;
125  }
126 
127  subview = view;
128  view = [view superview];
129  }
130 }
131 
132 - (void)setButtons:(CPArray)buttons
133 {
134  for (var i = [_buttons count] - 1; i >= 0; i--)
135  {
136  [_buttons[i] removeFromSuperview];
137  [_buttons[i] removeObserver:self forKeyPath:@"hidden"];
138  }
139 
140 
141  _buttons = [CPArray arrayWithArray:buttons];
142 
143  for (var i = [_buttons count] - 1; i >= 0; i--)
144  {
145  [_buttons[i] addObserver:self forKeyPath:@"hidden" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:nil];
146  [_buttons[i] setBordered:YES];
147  }
148 
149  [self setNeedsLayout];
150 }
151 
152 - (CPArray)buttons
153 {
154  return [CPArray arrayWithArray:_buttons];
155 }
156 
157 - (void)setHasResizeControl:(BOOL)shouldHaveResizeControl
158 {
159  if (_hasResizeControl === shouldHaveResizeControl)
160  return;
161 
162  _hasResizeControl = !!shouldHaveResizeControl;
163  [self setNeedsLayout];
164 }
165 
166 - (BOOL)hasResizeControl
167 {
168  return _hasResizeControl;
169 }
170 
171 - (void)setResizeControlIsLeftAligned:(BOOL)shouldBeLeftAligned
172 {
173  if (_resizeControlIsLeftAligned === shouldBeLeftAligned)
174  return;
175 
176  _resizeControlIsLeftAligned = !!shouldBeLeftAligned;
177  [self setNeedsLayout];
178 }
179 
180 - (BOOL)resizeControlIsLeftAligned
181 {
182  return _resizeControlIsLeftAligned;
183 }
184 
185 - (CGRect)resizeControlFrame
186 {
187  var inset = [self currentValueForThemeAttribute:@"resize-control-inset"],
188  size = [self currentValueForThemeAttribute:@"resize-control-size"],
189  currentSize = [self bounds],
190  leftOrigin = _resizeControlIsLeftAligned ? 0 : currentSize.size.width - size.width - inset.right - inset.left;
191 
192  return CGRectMake(leftOrigin, 0, size.width + inset.left + inset.right, size.height + inset.top + inset.bottom);
193 }
194 
195 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
196 {
197  if (aName === "resize-control-view")
198  {
199  var inset = [self currentValueForThemeAttribute:@"resize-control-inset"],
200  size = [self currentValueForThemeAttribute:@"resize-control-size"],
201  currentSize = [self bounds];
202 
203  if (_resizeControlIsLeftAligned)
204  return CGRectMake(inset.left, inset.top, size.width, size.height);
205  else
206  return CGRectMake(currentSize.size.width - size.width - inset.right, inset.top, size.width, size.height);
207  }
208 
209  return [super rectForEphemeralSubviewNamed:aName];
210 }
211 
212 - (CPView)createEphemeralSubviewNamed:(CPString)aName
213 {
214  if (aName === "resize-control-view")
215  return [[CPView alloc] initWithFrame:CGRectMakeZero()];
216 
217  return [super createEphemeralSubviewNamed:aName];
218 }
219 
220 - (void)layoutSubviews
221 {
222  [self setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
223 
224  var normalColor = [self valueForThemeAttribute:@"button-bezel-color" inState:CPThemeStateNormal],
225  highlightedColor = [self valueForThemeAttribute:@"button-bezel-color" inState:CPThemeStateHighlighted],
226  disabledColor = [self valueForThemeAttribute:@"button-bezel-color" inState:CPThemeStateDisabled],
227  textColor = [self valueForThemeAttribute:@"button-text-color" inState:CPThemeStateNormal];
228 
229  var buttonsNotHidden = [CPArray arrayWithArray:_buttons],
230  count = [buttonsNotHidden count];
231 
232  while (count--)
233  {
234  var button = buttonsNotHidden[count];
235 
236  if ([button isHidden])
237  {
238  [button removeFromSuperview];
239  [buttonsNotHidden removeObject:button];
240  }
241  }
242 
243  var currentButtonOffset = _resizeControlIsLeftAligned ? CGRectGetMaxX([self bounds]) + 1 : -1,
244  bounds = [self bounds],
245  height = CGRectGetHeight(bounds) - 1,
246  frameWidth = CGRectGetWidth(bounds),
247  resizeRect = _hasResizeControl ? [self rectForEphemeralSubviewNamed:"resize-control-view"] : CGRectMakeZero(),
248  resizeWidth = CGRectGetWidth(resizeRect),
249  availableWidth = frameWidth - resizeWidth - 1;
250 
251  for (var i = 0, count = [buttonsNotHidden count]; i < count; i++)
252  {
253  var button = buttonsNotHidden[i],
254  width = CGRectGetWidth([button frame]);
255 
256  if (availableWidth > width)
257  availableWidth -= width;
258  else
259  break;
260 
261  if (_resizeControlIsLeftAligned)
262  {
263  [button setFrame:CGRectMake(currentButtonOffset - width, 1, width, height)];
264  currentButtonOffset -= width - 1;
265  }
266  else
267  {
268  [button setFrame:CGRectMake(currentButtonOffset, 1, width, height)];
269  currentButtonOffset += width - 1;
270  }
271 
272  [button setValue:normalColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateNormal, CPThemeStateBordered]];
273  [button setValue:highlightedColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateHighlighted, CPThemeStateBordered, ]];
274  [button setValue:disabledColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateDisabled, CPThemeStateBordered]];
275  [button setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateBordered];
276 
277  // FIXME shouldn't need this
278  [button setValue:normalColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateNormal, CPThemeStateBordered, CPPopUpButtonStatePullsDown]];
279  [button setValue:highlightedColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateHighlighted, CPThemeStateBordered, CPPopUpButtonStatePullsDown]];
280  [button setValue:disabledColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateDisabled, CPThemeStateBordered, CPPopUpButtonStatePullsDown]];
281 
282  [self addSubview:button];
283  }
284 
285  if (_hasResizeControl)
286  {
287  var resizeControlView = [self layoutEphemeralSubviewNamed:@"resize-control-view"
288  positioned:CPWindowAbove
290 
291  [resizeControlView setAutoresizingMask: _resizeControlIsLeftAligned ? CPViewMaxXMargin : CPViewMinXMargin];
292  [resizeControlView setBackgroundColor:[self currentValueForThemeAttribute:@"resize-control-color"]];
293  }
294 }
295 
296 - (void)observeValueForKeyPath:(CPString)keyPath ofObject:(id)object change:(CPDictionary)change context:(id)context
297 {
298  if ([change objectForKey:CPKeyValueChangeOldKey] == [change objectForKey:CPKeyValueChangeNewKey])
299  return;
300 
301  [self setNeedsLayout];
302 }
303 
304 - (void)setFrameSize:(CGSize)aSize
305 {
306  [super setFrameSize:aSize];
307  [self setNeedsLayout];
308 }
309 
310 @end
311 
312 var CPButtonBarHasResizeControlKey = @"CPButtonBarHasResizeControlKey",
313  CPButtonBarResizeControlIsLeftAlignedKey = @"CPButtonBarResizeControlIsLeftAlignedKey",
314  CPButtonBarButtonsKey = @"CPButtonBarButtonsKey";
315 
316 @implementation CPButtonBar (CPCoding)
317 
318 - (void)encodeWithCoder:(CPCoder)aCoder
319 {
320  [super encodeWithCoder:aCoder];
321 
322  [aCoder encodeBool:_hasResizeControl forKey:CPButtonBarHasResizeControlKey];
323  [aCoder encodeBool:_resizeControlIsLeftAligned forKey:CPButtonBarResizeControlIsLeftAlignedKey];
324  [aCoder encodeObject:_buttons forKey:CPButtonBarButtonsKey];
325 }
326 
327 - (id)initWithCoder:(CPCoder)aCoder
328 {
329  if (self = [super initWithCoder:aCoder])
330  {
331  _buttons = [aCoder decodeObjectForKey:CPButtonBarButtonsKey] || [];
332  _hasResizeControl = [aCoder decodeBoolForKey:CPButtonBarHasResizeControlKey];
333  _resizeControlIsLeftAligned = [aCoder decodeBoolForKey:CPButtonBarResizeControlIsLeftAlignedKey];
334  }
335 
336  return self;
337 }
338 
339 @end
id initWithFrame:(CGRect aFrame)
Definition: CPView.j:351
CPKeyValueChangeOldKey
BOOL isHidden()
Definition: CALayer.j:597
void addSubview:(CPView aSubview)
Definition: CPView.j:536
CGRect frame
An object representation of nil.
Definition: CPNull.h:2
CPPopUpButtonStatePullsDown
Definition: CPPopUpButton.j:24
int width
CGRect bounds()
Definition: CPView.j:1326
var CPButtonBarHasResizeControlKey
Definition: CPButtonBar.j:312
A mutable key-value pair collection.
Definition: CPDictionary.h:2
var CPButtonBarResizeControlIsLeftAlignedKey
Definition: CPButtonBar.j:313
CGRect bounds()
Definition: CALayer.j:203
CPView createEphemeralSubviewNamed:(CPString aViewName)
Definition: CPView.j:3397
An immutable string (collection of characters).
Definition: CPString.h:2
CPNull null()
Definition: CPNull.j:51
CGRect rectForEphemeralSubviewNamed:(CPString aName)
Definition: CPButtonBar.j:195
void encodeWithCoder:(CPCoder aCoder)
Definition: CPView.j:3806
CPKeyValueObservingOptionNew
var CPButtonBarButtonsKey
Definition: CPButtonBar.j:314
CPTheme defaultTheme()
Definition: CPTheme.j:44
void setNeedsLayout()
Definition: CPView.j:2748
CPKeyValueChangeNewKey
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
Definition: CPTheme.h:2
void setBackgroundColor:(CPColor aColor)
Definition: CPView.j:1947
void setFrameSize:(CGSize aSize)
Definition: CPView.j:1124
CPView superview()
Definition: CPView.j:510
id valueForAttributeWithName:forClass:(CPString aName, [forClass] id aClass)
Definition: CPTheme.j:218
CPView layoutEphemeralSubviewNamed:positioned:relativeToEphemeralSubviewNamed:(CPString aViewName, [positioned] CPWindowOrderingMode anOrderingMode, [relativeToEphemeralSubviewNamed] CPString relativeToViewName)
Definition: CPView.j:3407
CPKeyValueObservingOptionOld
CGRect rectForEphemeralSubviewNamed:(CPString aViewName)
Definition: CPView.j:3402
Definition: CPView.j:137