00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 @import <Foundation/CPArray.j>
00024 @import <Foundation/CPObject.j>
00025 @import <Foundation/CPKeyValueObserving.j>
00026 @import <Foundation/CPIndexSet.j>
00027 @import <Foundation/CPString.j>
00028
00029 @import <AppKit/CPView.j>
00030
00031 #import "CoreGraphics/CGGeometry.h"
00032
00033
00034 @implementation CPAccordionViewItem : CPObject
00035 {
00036 CPString _identifier @accessors(property=identifier);
00037 CPView _view @accessors(property=view);
00038 CPString _label @accessors(property=label);
00039 }
00040
00041 - (id)init
00042 {
00043 return [self initWithIdentifier:@""];
00044 }
00045
00046 - (id)initWithIdentifier:(CPString)anIdentifier
00047 {
00048 self = [super init];
00049
00050 if (self)
00051 [self setIdentifier:anIdentifier];
00052
00053 return self;
00054 }
00055
00056 @end
00057
00058 @implementation CPAccordionView : CPView
00059 {
00060 CPInteger _dirtyItemIndex;
00061 CPView _itemHeaderPrototype;
00062
00063 CPMutableArray _items;
00064 CPMutableArray _itemViews;
00065 CPIndexSet _expandedItemIndexes;
00066 }
00067
00068 - (id)initWithFrame:(CGRect)aFrame
00069 {
00070 self = [super initWithFrame:aFrame];
00071
00072 if (self)
00073 {
00074 _items = [];
00075 _itemViews = [];
00076 _expandedItemIndexes = [CPIndexSet indexSet];
00077
00078 [self setItemHeaderPrototype:[[CPButton alloc] initWithFrame:_CGRectMake(0.0, 0.0, 100.0, 24.0)]];
00079 }
00080
00081 return self;
00082 }
00083
00084 - (void)setItemHeaderPrototype:(CPView)aView
00085 {
00086 _itemHeaderPrototype = aView;
00087 }
00088
00089 - (CPView)itemHeaderPrototype
00090 {
00091 return _itemHeaderPrototype;
00092 }
00093
00094 - (CPArray)items
00095 {
00096 return _items;
00097 }
00098
00099 - (void)addItem:(CPAccordionItem)anItem
00100 {
00101 [self insertItem:anItem atIndex:_items.length];
00102 }
00103
00104 - (void)insertItem:(CPAccordionItem)anItem atIndex:(CPInteger)anIndex
00105 {
00106
00107 [_expandedItemIndexes addIndex:anIndex];
00108
00109 var itemView = [[_CPAccordionItemView alloc] initWithAccordionView:self];
00110
00111 [itemView setIndex:anIndex];
00112 [itemView setLabel:[anItem label]];
00113 [itemView setContentView:[anItem view]];
00114
00115 [self addSubview:itemView];
00116
00117 [_items insertObject:anItem atIndex:anIndex];
00118 [_itemViews insertObject:itemView atIndex:anIndex];
00119
00120 [self _invalidateItemsStartingAtIndex:anIndex];
00121
00122 [self setNeedsLayout];
00123 }
00124
00125 - (void)removeItem:(CPAccordionItem)anItem
00126 {
00127 [self removeItemAtIndex:[_items indexOfObjectIdenticalTo:anItem]];
00128 }
00129
00130 - (void)removeItemAtIndex:(CPInteger)anIndex
00131 {
00132
00133 [_expandedItemIndexes removeIndex:anIndex];
00134
00135 [_itemViews[anIndex] removeFromSuperview];
00136
00137 [_items removeObjectAtIndex:anIndex];
00138 [_itemViews removeObjectAtIndex:anIndex];
00139
00140 [self _invalidateItemsStartingAtIndex:anIndex];
00141
00142 [self setNeedsLayout];
00143 }
00144
00145 - (void)removeAllItems
00146 {
00147 var count = _items.length;
00148
00149 while (count--)
00150 [self removeItemAtIndex:count];
00151 }
00152
00153 - (void)expandItemAtIndex:(CPInteger)anIndex
00154 {
00155 if (![_itemViews[anIndex] isCollapsed])
00156 return;
00157
00158 [_expandedItemIndexes addIndex:anIndex];
00159 [_itemViews[anIndex] setCollapsed:NO];
00160
00161 [self _invalidateItemsStartingAtIndex:anIndex];
00162 }
00163
00164 - (void)collapseItemAtIndex:(CPInteger)anIndex
00165 {
00166 if ([_itemViews[anIndex] isCollapsed])
00167 return;
00168
00169 [_expandedItemIndexes removeIndex:anIndex];
00170 [_itemViews[anIndex] setCollapsed:YES];
00171
00172 [self _invalidateItemsStartingAtIndex:anIndex];
00173 }
00174
00175 - (void)toggleItemAtIndex:(CPInteger)anIndex
00176 {
00177 var itemView = _itemViews[anIndex];
00178
00179 if ([itemView isCollapsed])
00180 [self expandItemAtIndex:anIndex];
00181
00182 else
00183 [self collapseItemAtIndex:anIndex];
00184 }
00185
00186 - (CPIndexSet)expandedItemIndexes
00187 {
00188 return _expandedItemIndexes;
00189 }
00190
00191 - (CPIndexSet)collapsedItemIndexes
00192 {
00193 var indexSet = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, _items.length)];
00194
00195 [indexSet removeIndexes:_expandedIndexes];
00196
00197 return indexSet;
00198 }
00199
00200 - (void)setEnabled:(BOOL)isEnabled forItemAtIndex:(CPInteger)anIndex
00201 {
00202 var itemView = _itemViews[anIndex];
00203 if (!itemView)
00204 return;
00205
00206 if (!isEnabled)
00207 [self collapseItemAtIndex:anIndex];
00208 else
00209 [self expandItemAtIndex:anIndex];
00210
00211 [itemView setEnabled:isEnabled];
00212 }
00213
00214 - (void)_invalidateItemsStartingAtIndex:(CPInteger)anIndex
00215 {
00216 if (_dirtyItemIndex === CPNotFound)
00217 _dirtyItemIndex = anIndex;
00218
00219 _dirtyItemIndex = MIN(_dirtyItemIndex, anIndex);
00220
00221 [self setNeedsLayout];
00222 }
00223
00224 - (void)setFrameSize:(CGSize)aSize
00225 {
00226 var width = _CGRectGetWidth([self frame]);
00227
00228 [super setFrameSize:aSize];
00229
00230 if (width !== _CGRectGetWidth([self frame]))
00231 [self _invalidateItemsStartingAtIndex:0];
00232 }
00233
00234 - (void)layoutSubviews
00235 {
00236 if (_items.length <= 0)
00237 return [self setFrameSize:_CGSizeMake(_CGRectGetWidth([self frame]), 0.0)];
00238
00239 if (_dirtyItemIndex === CPNotFound)
00240 return;
00241
00242 _dirtyItemIndex = MIN(_dirtyItemIndex, _items.length - 1);
00243
00244 var index = _dirtyItemIndex,
00245 count = _itemViews.length,
00246 width = _CGRectGetWidth([self bounds]),
00247 y = index > 0 ? CGRectGetMaxY([_itemViews[index - 1] frame]) : 0.0;
00248
00249
00250 _dirtyItemIndex = CPNotFound;
00251
00252 for (; index < count; ++index)
00253 {
00254 var itemView = _itemViews[index];
00255
00256 [itemView setFrameY:y width:width];
00257
00258 y = CGRectGetMaxY([itemView frame]);
00259 }
00260
00261 [self setFrameSize:_CGSizeMake(_CGRectGetWidth([self frame]), y)];
00262 }
00263
00264 @end
00265
00266 @implementation _CPAccordionItemView : CPView
00267 {
00268 CPAccordionView _accordionView;
00269
00270 BOOL _isCollapsed @accessors(getter=isCollapsed, setter=setCollapsed:);
00271 CPInteger _index @accessors(property=index);
00272 CPView _headerView;
00273 CPView _contentView;
00274 }
00275
00276 - (id)initWithAccordionView:(CPAccordionView)anAccordionView
00277 {
00278 self = [super initWithFrame:_CGRectMakeZero()];
00279
00280 if (self)
00281 {
00282 _accordionView = anAccordionView;
00283 _isCollapsed = NO;
00284
00285 var bounds = [self bounds];
00286
00287 _headerView = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:[_accordionView itemHeaderPrototype]]];
00288
00289 if ([_headerView respondsToSelector:@selector(setTarget:)] && [_headerView respondsToSelector:@selector(setAction:)])
00290 {
00291 [_headerView setTarget:self];
00292 [_headerView setAction:@selector(toggle:)];
00293 }
00294
00295 [self addSubview:_headerView];
00296 }
00297
00298 return self;
00299 }
00300
00301 - (void)toggle:(id)aSender
00302 {
00303 [_accordionView toggleItemAtIndex:[self index]];
00304 }
00305
00306 - (void)setLabel:(CPString)aLabel
00307 {
00308 if ([_headerView respondsToSelector:@selector(setTitle:)])
00309 [_headerView setTitle:aLabel];
00310
00311 else if ([_headerView respondsToSelector:@selector(setLabel:)])
00312 [_headerView setLabel:aLabel];
00313
00314 else if ([_headerView respondsToSelector:@selector(setStringValue:)])
00315 [_headerView setStringValue:aLabel];
00316 }
00317
00318 - (void)setEnabled:(BOOL)isEnabled
00319 {
00320 if ([_headerView respondsToSelector:@selector(setEnabled:)])
00321 [_headerView setEnabled:isEnabled];
00322 }
00323
00324 - (void)setContentView:(CPView)aView
00325 {
00326 if (_contentView === aView)
00327 return;
00328
00329 [_contentView removeObserver:self forKeyPath:@"frame"];
00330
00331 [_contentView removeFromSuperview];
00332
00333 _contentView = aView;
00334
00335 [_contentView addObserver:self forKeyPath:@"frame" options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:NULL];
00336
00337 [self addSubview:_contentView];
00338
00339 [_accordionView _invalidateItemsStartingAtIndex:[self index]];
00340 }
00341
00342 - (void)setFrameY:(float)aY width:(float)aWidth
00343 {
00344 var headerHeight = _CGRectGetHeight([_headerView frame]);
00345
00346
00347 [_headerView setFrameSize:_CGSizeMake(aWidth, headerHeight)];
00348 [_contentView setFrameOrigin:_CGPointMake(0.0, headerHeight)];
00349
00350 if ([self isCollapsed])
00351 [self setFrame:_CGRectMake(0.0, aY, aWidth, headerHeight)];
00352
00353 else
00354 {
00355 var contentHeight = _CGRectGetHeight([_contentView frame]);
00356
00357 [_contentView setFrameSize:_CGSizeMake(aWidth, contentHeight)];
00358 [self setFrame:_CGRectMake(0.0, aY, aWidth, contentHeight + headerHeight)];
00359 }
00360 }
00361
00362 - (void)resizeSubviewsWithOldSize:(CGSize)aSize
00363 {
00364 }
00365
00366 - (void)observeValueForKeyPath:(CPString)aKeyPath
00367 ofObject:(id)anObject
00368 change:(CPDictionary)aChange
00369 context:(id)aContext
00370 {
00371 if (aKeyPath === "frame" && !CGRectEqualToRect([aChange objectForKey:CPKeyValueChangeOldKey], [aChange objectForKey:CPKeyValueChangeNewKey]))
00372 [_accordionView _invalidateItemsStartingAtIndex:[self index]];
00373
00374
00375
00376
00377
00378
00379 }
00380
00381 @end