API  0.9.8
 All Classes Files Functions Variables Typedefs Macros Groups Pages
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"];
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  var view = [self superview],
113  subview = self;
114 
115  while (view)
116  {
117  if ([view isKindOfClass:[CPSplitView class]])
118  {
119  var viewIndex = [[view subviews] indexOfObject:subview];
120  [view setButtonBar:self forDividerAtIndex:viewIndex];
121 
122  break;
123  }
124 
125  subview = view;
126  view = [view superview];
127  }
128 }
129 
130 - (void)setButtons:(CPArray)buttons
131 {
132  for (var i = [_buttons count] - 1; i >= 0; i--)
133  {
134  [_buttons[i] removeFromSuperview];
135  [_buttons[i] removeObserver:self forKeyPath:@"hidden"];
136  }
137 
138 
139  _buttons = [CPArray arrayWithArray:buttons];
140 
141  for (var i = [_buttons count] - 1; i >= 0; i--)
142  {
143  [_buttons[i] addObserver:self forKeyPath:@"hidden" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:nil];
144  [_buttons[i] setBordered:YES];
145  }
146 
147  [self setNeedsLayout];
148 }
149 
150 - (CPArray)buttons
151 {
152  return [CPArray arrayWithArray:_buttons];
153 }
154 
155 - (void)setHasResizeControl:(BOOL)shouldHaveResizeControl
156 {
157  if (_hasResizeControl === shouldHaveResizeControl)
158  return;
159 
160  _hasResizeControl = !!shouldHaveResizeControl;
161  [self setNeedsLayout];
162 }
163 
164 - (BOOL)hasResizeControl
165 {
166  return _hasResizeControl;
167 }
168 
169 - (void)setResizeControlIsLeftAligned:(BOOL)shouldBeLeftAligned
170 {
171  if (_resizeControlIsLeftAligned === shouldBeLeftAligned)
172  return;
173 
174  _resizeControlIsLeftAligned = !!shouldBeLeftAligned;
175  [self setNeedsLayout];
176 }
177 
178 - (BOOL)resizeControlIsLeftAligned
179 {
180  return _resizeControlIsLeftAligned;
181 }
182 
183 - (CGRect)resizeControlFrame
184 {
185  var inset = [self currentValueForThemeAttribute:@"resize-control-inset"],
186  size = [self currentValueForThemeAttribute:@"resize-control-size"],
187  currentSize = [self bounds],
188  leftOrigin = _resizeControlIsLeftAligned ? 0 : currentSize.size.width - size.width - inset.right - inset.left;
189 
190  return CGRectMake(leftOrigin, 0, size.width + inset.left + inset.right, size.height + inset.top + inset.bottom);
191 }
192 
193 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
194 {
195  if (aName === "resize-control-view")
196  {
197  var inset = [self currentValueForThemeAttribute:@"resize-control-inset"],
198  size = [self currentValueForThemeAttribute:@"resize-control-size"],
199  currentSize = [self bounds];
200 
201  if (_resizeControlIsLeftAligned)
202  return CGRectMake(inset.left, inset.top, size.width, size.height);
203  else
204  return CGRectMake(currentSize.size.width - size.width - inset.right, inset.top, size.width, size.height);
205  }
206 
207  return [super rectForEphemeralSubviewNamed:aName];
208 }
209 
210 - (CPView)createEphemeralSubviewNamed:(CPString)aName
211 {
212  if (aName === "resize-control-view")
213  return [[CPView alloc] initWithFrame:CGRectMakeZero()];
214 
215  return [super createEphemeralSubviewNamed:aName];
216 }
217 
218 - (void)layoutSubviews
219 {
220  [self setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
221 
222  var normalColor = [self valueForThemeAttribute:@"button-bezel-color" inState:CPThemeStateNormal],
223  highlightedColor = [self valueForThemeAttribute:@"button-bezel-color" inState:CPThemeStateHighlighted],
224  disabledColor = [self valueForThemeAttribute:@"button-bezel-color" inState:CPThemeStateDisabled],
225  textColor = [self valueForThemeAttribute:@"button-text-color" inState:CPThemeStateNormal];
226 
227  var buttonsNotHidden = [CPArray arrayWithArray:_buttons],
228  count = [buttonsNotHidden count];
229 
230  while (count--)
231  {
232  var button = buttonsNotHidden[count];
233 
234  if ([button isHidden])
235  {
236  [button removeFromSuperview];
237  [buttonsNotHidden removeObject:button];
238  }
239  }
240 
241  var currentButtonOffset = _resizeControlIsLeftAligned ? CGRectGetMaxX([self bounds]) + 1 : -1,
242  bounds = [self bounds],
243  height = CGRectGetHeight(bounds) - 1,
244  frameWidth = CGRectGetWidth(bounds),
245  resizeRect = _hasResizeControl ? [self rectForEphemeralSubviewNamed:"resize-control-view"] : CGRectMakeZero(),
246  resizeWidth = CGRectGetWidth(resizeRect),
247  availableWidth = frameWidth - resizeWidth - 1;
248 
249  for (var i = 0, count = [buttonsNotHidden count]; i < count; i++)
250  {
251  var button = buttonsNotHidden[i],
252  width = CGRectGetWidth([button frame]);
253 
254  if (availableWidth > width)
255  availableWidth -= width;
256  else
257  break;
258 
259  if (_resizeControlIsLeftAligned)
260  {
261  [button setFrame:CGRectMake(currentButtonOffset - width, 1, width, height)];
262  currentButtonOffset -= width - 1;
263  }
264  else
265  {
266  [button setFrame:CGRectMake(currentButtonOffset, 1, width, height)];
267  currentButtonOffset += width - 1;
268  }
269 
270  [button setValue:normalColor forThemeAttribute:@"bezel-color" inState:[CPThemeStateNormal, CPThemeStateBordered]];
271  [button setValue:highlightedColor forThemeAttribute:@"bezel-color" inState:[CPThemeStateHighlighted, CPThemeStateBordered, ]];
272  [button setValue:disabledColor forThemeAttribute:@"bezel-color" inState:[CPThemeStateDisabled, CPThemeStateBordered]];
273  [button setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateBordered];
274 
275  // FIXME shouldn't need this
276  [button setValue:normalColor forThemeAttribute:@"bezel-color" inState:[CPThemeStateNormal, CPThemeStateBordered, CPPopUpButtonStatePullsDown]];
277  [button setValue:highlightedColor forThemeAttribute:@"bezel-color" inState:[CPThemeStateHighlighted, CPThemeStateBordered, CPPopUpButtonStatePullsDown]];
278  [button setValue:disabledColor forThemeAttribute:@"bezel-color" inState:[CPThemeStateDisabled, CPThemeStateBordered, CPPopUpButtonStatePullsDown]];
279 
280  [self addSubview:button];
281  }
282 
283  if (_hasResizeControl)
284  {
285  var resizeControlView = [self layoutEphemeralSubviewNamed:@"resize-control-view"
286  positioned:CPWindowAbove
288 
289  [resizeControlView setAutoresizingMask: _resizeControlIsLeftAligned ? CPViewMaxXMargin : CPViewMinXMargin];
290  [resizeControlView setBackgroundColor:[self currentValueForThemeAttribute:@"resize-control-color"]];
291  }
292 }
293 
294 - (void)observeValueForKeyPath:(CPString)keyPath ofObject:(id)object change:(CPDictionary)change context:(id)context
295 {
296  if ([change objectForKey:CPKeyValueChangeOldKey] == [change objectForKey:CPKeyValueChangeNewKey])
297  return;
298 
299  [self setNeedsLayout];
300 }
301 
302 - (void)setFrameSize:(CGSize)aSize
303 {
304  [super setFrameSize:aSize];
305  [self setNeedsLayout];
306 }
307 
308 @end
309 
310 var CPButtonBarHasResizeControlKey = @"CPButtonBarHasResizeControlKey",
311  CPButtonBarResizeControlIsLeftAlignedKey = @"CPButtonBarResizeControlIsLeftAlignedKey",
312  CPButtonBarButtonsKey = @"CPButtonBarButtonsKey";
313 
314 @implementation CPButtonBar (CPCoding)
315 
316 - (void)encodeWithCoder:(CPCoder)aCoder
317 {
318  [super encodeWithCoder:aCoder];
319 
320  [aCoder encodeBool:_hasResizeControl forKey:CPButtonBarHasResizeControlKey];
321  [aCoder encodeBool:_resizeControlIsLeftAligned forKey:CPButtonBarResizeControlIsLeftAlignedKey];
322  [aCoder encodeObject:_buttons forKey:CPButtonBarButtonsKey];
323 }
324 
325 - (id)initWithCoder:(CPCoder)aCoder
326 {
327  if (self = [super initWithCoder:aCoder])
328  {
329  _buttons = [aCoder decodeObjectForKey:CPButtonBarButtonsKey] || [];
330  _hasResizeControl = [aCoder decodeBoolForKey:CPButtonBarHasResizeControlKey];
331  _resizeControlIsLeftAligned = [aCoder decodeBoolForKey:CPButtonBarResizeControlIsLeftAlignedKey];
332  }
333 
334  return self;
335 }
336 
337 @end