API  1.0.0
CPAccordionView.j
Go to the documentation of this file.
1 /*
2  * CPAccordionView.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 
25 
31 @implementation CPAccordionViewItem : CPObject
32 {
33  CPString _identifier;
34  CPView _view;
35  CPString _label;
36 }
37 
38 - (id)init
39 {
40  return [self initWithIdentifier:@""];
41 }
42 
47 - (id)initWithIdentifier:(CPString)anIdentifier
48 {
49  self = [super init];
50 
51  if (self)
52  [self setIdentifier:anIdentifier];
53 
54  return self;
55 }
56 
57 @end
58 
77 @implementation CPAccordionView : CPView
78 {
79  CPInteger _dirtyItemIndex;
80  CPView _itemHeaderPrototype;
81 
82  CPMutableArray _items;
83  CPMutableArray _itemViews;
84  CPIndexSet _expandedItemIndexes;
85 }
90 - (id)initWithFrame:(CGRect)aFrame
91 {
92  self = [super initWithFrame:aFrame];
93 
94  if (self)
95  {
96  _items = [];
97  _itemViews = [];
98  _expandedItemIndexes = [CPIndexSet indexSet];
99 
100  [self setItemHeaderPrototype:[[CPButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 24.0)]];
101  }
102 
103  return self;
104 }
105 
106 - (void)setItemHeaderPrototype:(CPView)aView
107 {
108  _itemHeaderPrototype = aView;
109 }
110 
111 - (CPView)itemHeaderPrototype
112 {
113  return _itemHeaderPrototype;
114 }
115 
116 - (CPArray)items
117 {
118  return _items;
119 }
125 - (void)addItem:(CPAccordionViewItem)anItem
126 {
127  [self insertItem:anItem atIndex:_items.length];
128 }
129 
130 - (void)insertItem:(CPAccordionViewItem)anItem atIndex:(CPInteger)anIndex
131 {
132  // FIXME: SHIFT ITEMS RIGHT
133  [_expandedItemIndexes addIndex:anIndex];
134 
135  var itemView = [[_CPAccordionItemView alloc] initWithAccordionView:self];
136 
137  [itemView setIndex:anIndex];
138  [itemView setLabel:[anItem label]];
139  [itemView setContentView:[anItem view]];
140 
141  [self addSubview:itemView];
142 
143  [_items insertObject:anItem atIndex:anIndex];
144  [_itemViews insertObject:itemView atIndex:anIndex];
145 
146  [self _invalidateItemsStartingAtIndex:anIndex];
147 
148  [self setNeedsLayout];
149 }
150 
151 - (void)removeItem:(CPAccordionViewItem)anItem
152 {
153  [self removeItemAtIndex:[_items indexOfObjectIdenticalTo:anItem]];
154 }
155 
156 - (void)removeItemAtIndex:(CPInteger)anIndex
157 {
158  // SHIFT ITEMS LEFT
159  [_expandedItemIndexes removeIndex:anIndex];
160 
161  [_itemViews[anIndex] removeFromSuperview];
162 
163  [_items removeObjectAtIndex:anIndex];
164  [_itemViews removeObjectAtIndex:anIndex];
165 
166  [self _invalidateItemsStartingAtIndex:anIndex];
167 
168  [self setNeedsLayout];
169 }
170 
171 - (void)removeAllItems
172 {
173  var count = _items.length;
174 
175  while (count--)
176  [self removeItemAtIndex:count];
177 }
178 
179 - (void)expandItemAtIndex:(CPInteger)anIndex
180 {
181  if (![_itemViews[anIndex] isCollapsed])
182  return;
183 
184  [_expandedItemIndexes addIndex:anIndex];
185  [_itemViews[anIndex] setCollapsed:NO];
186 
187  [self _invalidateItemsStartingAtIndex:anIndex];
188 }
189 
190 - (void)collapseItemAtIndex:(CPInteger)anIndex
191 {
192  if ([_itemViews[anIndex] isCollapsed])
193  return;
194 
195  [_expandedItemIndexes removeIndex:anIndex];
196  [_itemViews[anIndex] setCollapsed:YES];
197 
198  [self _invalidateItemsStartingAtIndex:anIndex];
199 }
200 
201 - (void)toggleItemAtIndex:(CPInteger)anIndex
202 {
203  var itemView = _itemViews[anIndex];
204 
205  if ([itemView isCollapsed])
206  [self expandItemAtIndex:anIndex];
207 
208  else
209  [self collapseItemAtIndex:anIndex];
210 }
211 
212 - (CPIndexSet)expandedItemIndexes
213 {
214  return _expandedItemIndexes;
215 }
216 
217 - (CPIndexSet)collapsedItemIndexes
218 {
219  var indexSet = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, _items.length)];
220 
221  [indexSet removeIndexes:_expandedItemIndexes];
222 
223  return indexSet;
224 }
225 
226 - (void)setEnabled:(BOOL)isEnabled forItemAtIndex:(CPInteger)anIndex
227 {
228  var itemView = _itemViews[anIndex];
229  if (!itemView)
230  return;
231 
232  if (!isEnabled)
233  [self collapseItemAtIndex:anIndex];
234  else
235  [self expandItemAtIndex:anIndex];
236 
237  [itemView setEnabled:isEnabled];
238 }
239 
240 - (void)_invalidateItemsStartingAtIndex:(CPInteger)anIndex
241 {
242  if (_dirtyItemIndex === CPNotFound)
243  _dirtyItemIndex = anIndex;
244 
245  _dirtyItemIndex = MIN(_dirtyItemIndex, anIndex);
246 
247  [self setNeedsLayout];
248 }
249 
250 - (void)setFrameSize:(CGSize)aSize
251 {
252  var width = CGRectGetWidth([self frame]);
253 
254  [super setFrameSize:aSize];
255 
256  if (width !== CGRectGetWidth([self frame]))
257  [self _invalidateItemsStartingAtIndex:0];
258 }
259 
260 - (void)layoutSubviews
261 {
262  if (_items.length <= 0)
263  return [self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), 0.0)];
264 
265  if (_dirtyItemIndex === CPNotFound)
266  return;
267 
268  _dirtyItemIndex = MIN(_dirtyItemIndex, _items.length - 1);
269 
270  var index = _dirtyItemIndex,
271  count = _itemViews.length,
272  width = CGRectGetWidth([self bounds]),
273  y = index > 0 ? CGRectGetMaxY([_itemViews[index - 1] frame]) : 0.0;
274 
275  // Do this now (instead of after looping), so that if we are made dirty again in the middle we don't blow this value away.
276  _dirtyItemIndex = CPNotFound;
277 
278  for (; index < count; ++index)
279  {
280  var itemView = _itemViews[index];
281 
282  [itemView setFrameY:y width:width];
283 
284  y = CGRectGetMaxY([itemView frame]);
285  }
286 
287  [self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), y)];
288 }
289 
290 @end
291 
292 @implementation _CPAccordionItemView : CPView
293 {
294  CPAccordionView _accordionView;
295 
296  BOOL _isCollapsed;
297  CPInteger _index;
298  CPView _headerView;
299  CPView _contentView;
300 }
301 
302 - (id)initWithAccordionView:(CPAccordionView)anAccordionView
303 {
304  self = [super initWithFrame:CGRectMakeZero()];
305 
306  if (self)
307  {
308  _accordionView = anAccordionView;
309  _isCollapsed = NO;
310 
311  var bounds = [self bounds];
312 
313  _headerView = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:[_accordionView itemHeaderPrototype]]];
314 
315  if ([_headerView respondsToSelector:@selector(setTarget:)] && [_headerView respondsToSelector:@selector(setAction:)])
316  {
317  [_headerView setTarget:self];
318  [_headerView setAction:@selector(toggle:)];
319  }
320 
321  [self addSubview:_headerView];
322  }
323 
324  return self;
325 }
326 
327 - (void)toggle:(id)aSender
328 {
329  [_accordionView toggleItemAtIndex:[self index]];
330 }
331 
332 - (void)setLabel:(CPString)aLabel
333 {
334  if ([_headerView respondsToSelector:@selector(setTitle:)])
335  [_headerView setTitle:aLabel];
336 
337  else if ([_headerView respondsToSelector:@selector(setLabel:)])
338  [_headerView setLabel:aLabel];
339 
340  else if ([_headerView respondsToSelector:@selector(setStringValue:)])
341  [_headerView setStringValue:aLabel];
342 }
343 
344 - (void)setEnabled:(BOOL)isEnabled
345 {
346  if ([_headerView respondsToSelector:@selector(setEnabled:)])
347  [_headerView setEnabled:isEnabled];
348 }
349 
350 - (void)setContentView:(CPView)aView
351 {
352  if (_contentView === aView)
353  return;
354 
355  [_contentView removeObserver:self forKeyPath:@"frame"];
356 
357  [_contentView removeFromSuperview];
358 
359  _contentView = aView;
360 
361  [_contentView addObserver:self forKeyPath:@"frame" options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:NULL];
362 
363  [self addSubview:_contentView];
364 
365  [_accordionView _invalidateItemsStartingAtIndex:[self index]];
366 }
367 
368 - (void)setFrameY:(float)aY width:(float)aWidth
369 {
370  var headerHeight = CGRectGetHeight([_headerView frame]);
371 
372  // Size to fit or something?
373  [_headerView setFrameSize:CGSizeMake(aWidth, headerHeight)];
374  [_contentView setFrameOrigin:CGPointMake(0.0, headerHeight)];
375 
376  if ([self isCollapsed])
377  [self setFrame:CGRectMake(0.0, aY, aWidth, headerHeight)];
378 
379  else
380  {
381  var contentHeight = CGRectGetHeight([_contentView frame]);
382 
383  [_contentView setFrameSize:CGSizeMake(aWidth, contentHeight)];
384  [self setFrame:CGRectMake(0.0, aY, aWidth, contentHeight + headerHeight)];
385  }
386 }
387 
388 - (void)resizeSubviewsWithOldSize:(CGSize)aSize
389 {
390 }
391 
392 - (void)observeValueForKeyPath:(CPString)aKeyPath
393  ofObject:(id)anObject
394  change:(CPDictionary)aChange
395  context:(id)aContext
396 {
397  if (aKeyPath === "frame" && !CGRectEqualToRect([aChange objectForKey:CPKeyValueChangeOldKey], [aChange objectForKey:CPKeyValueChangeNewKey]))
398  [_accordionView _invalidateItemsStartingAtIndex:[self index]];
399 /*
400  else if (aKeyPath === "itemHeaderPrototype")
401  {
402 
403  }
404 */
405 }
406 
407 @end
408 
410 
415 {
416  return _identifier;
417 }
418 
422 - (void)setIdentifier:(CPString)aValue
423 {
424  _identifier = aValue;
425 }
426 
430 - (CPView)view
431 {
432  return _view;
433 }
434 
438 - (void)setView:(CPView)aValue
439 {
440  _view = aValue;
441 }
442 
447 {
448  return _label;
449 }
450 
454 - (void)setLabel:(CPString)aValue
455 {
456  _label = aValue;
457 }
458 
459 @end
id initWithFrame:(CGRect aFrame)
Definition: CPView.j:351
CPKeyValueChangeOldKey
void addSubview:(CPView aSubview)
Definition: CPView.j:536
id init()
Definition: CALayer.j:126
FrameUpdater prototype identifier
CGRect frame
void setItemHeaderPrototype:(CPView aView)
void collapseItemAtIndex:(CPInteger anIndex)
void insertItem:atIndex:(CPAccordionViewItem anItem, [atIndex] CPInteger anIndex)
CPString label
int width
A collection of unique integers.
Definition: CPIndexSet.h:2
Unarchives objects created using CPKeyedArchiver.
void removeItemAtIndex:(CPInteger anIndex)
A mutable key-value pair collection.
Definition: CPDictionary.h:2
CGRect bounds()
Definition: CALayer.j:203
Implements keyed archiving of object graphs (e.g. for storing data).
An immutable string (collection of characters).
Definition: CPString.h:2
void expandItemAtIndex:(CPInteger anIndex)
void setNeedsLayout()
Definition: CPView.j:2748
void setIdentifier:(CPString aValue)
CPKeyValueChangeNewKey
void setFrameSize:(CGSize aSize)
CPNotFound
Definition: CPObjJRuntime.j:62
id unarchiveObjectWithData:(CPData aData)
id init()
Definition: CPObject.j:145
void setFrameSize:(CGSize aSize)
Definition: CPView.j:1124
id indexSetWithIndexesInRange:(CPRange aRange)
Definition: CPIndexSet.j:60
CGRect frame()
Definition: CPView.j:1046
id initWithIdentifier:(CPString anIdentifier)
id indexSet()
Definition: CPIndexSet.j:43
Definition: CPView.j:137
CPData archivedDataWithRootObject:(id anObject)