![]() |
API 0.9.5
|
00001 /* 00002 * CPAccordionView.j 00003 * AppKit 00004 * 00005 * Created by Francisco Tolmasky. 00006 * Copyright 2009, 280 North, Inc. 00007 * 00008 * This library is free software; you can redistribute it and/or 00009 * modify it under the terms of the GNU Lesser General Public 00010 * License as published by the Free Software Foundation; either 00011 * version 2.1 of the License, or (at your option) any later version. 00012 * 00013 * This library is distributed in the hope that it will be useful, 00014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 * Lesser General Public License for more details. 00017 * 00018 * You should have received a copy of the GNU Lesser General Public 00019 * License along with this library; if not, write to the Free Software 00020 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00021 */ 00022 00023 00024 00025 00032 @implementation CPAccordionViewItem : CPObject 00033 { 00034 CPString _identifier; 00035 CPView _view; 00036 CPString _label; 00037 } 00038 00039 - (id)init 00040 { 00041 return [self initWithIdentifier:@""]; 00042 } 00043 00048 - (id)initWithIdentifier:(CPString)anIdentifier 00049 { 00050 self = [super init]; 00051 00052 if (self) 00053 [self setIdentifier:anIdentifier]; 00054 00055 return self; 00056 } 00057 00058 @end 00059 00079 @implementation CPAccordionView : CPView 00080 { 00081 CPInteger _dirtyItemIndex; 00082 CPView _itemHeaderPrototype; 00083 00084 CPMutableArray _items; 00085 CPMutableArray _itemViews; 00086 CPIndexSet _expandedItemIndexes; 00087 } 00092 - (id)initWithFrame:(CGRect)aFrame 00093 { 00094 self = [super initWithFrame:aFrame]; 00095 00096 if (self) 00097 { 00098 _items = []; 00099 _itemViews = []; 00100 _expandedItemIndexes = [CPIndexSet indexSet]; 00101 00102 [self setItemHeaderPrototype:[[CPButton alloc] initWithFrame:_CGRectMake(0.0, 0.0, 100.0, 24.0)]]; 00103 } 00104 00105 return self; 00106 } 00107 00108 - (void)setItemHeaderPrototype:(CPView)aView 00109 { 00110 _itemHeaderPrototype = aView; 00111 } 00112 00113 - (CPView)itemHeaderPrototype 00114 { 00115 return _itemHeaderPrototype; 00116 } 00117 00118 - (CPArray)items 00119 { 00120 return _items; 00121 } 00127 - (void)addItem:(CPAccordionViewItem)anItem 00128 { 00129 [self insertItem:anItem atIndex:_items.length]; 00130 } 00131 00132 - (void)insertItem:(CPAccordionViewItem)anItem atIndex:(CPInteger)anIndex 00133 { 00134 // FIXME: SHIFT ITEMS RIGHT 00135 [_expandedItemIndexes addIndex:anIndex]; 00136 00137 var itemView = [[_CPAccordionItemView alloc] initWithAccordionView:self]; 00138 00139 [itemView setIndex:anIndex]; 00140 [itemView setLabel:[anItem label]]; 00141 [itemView setContentView:[anItem view]]; 00142 00143 [self addSubview:itemView]; 00144 00145 [_items insertObject:anItem atIndex:anIndex]; 00146 [_itemViews insertObject:itemView atIndex:anIndex]; 00147 00148 [self _invalidateItemsStartingAtIndex:anIndex]; 00149 00150 [self setNeedsLayout]; 00151 } 00152 00153 - (void)removeItem:(CPAccordionViewItem)anItem 00154 { 00155 [self removeItemAtIndex:[_items indexOfObjectIdenticalTo:anItem]]; 00156 } 00157 00158 - (void)removeItemAtIndex:(CPInteger)anIndex 00159 { 00160 // SHIFT ITEMS LEFT 00161 [_expandedItemIndexes removeIndex:anIndex]; 00162 00163 [_itemViews[anIndex] removeFromSuperview]; 00164 00165 [_items removeObjectAtIndex:anIndex]; 00166 [_itemViews removeObjectAtIndex:anIndex]; 00167 00168 [self _invalidateItemsStartingAtIndex:anIndex]; 00169 00170 [self setNeedsLayout]; 00171 } 00172 00173 - (void)removeAllItems 00174 { 00175 var count = _items.length; 00176 00177 while (count--) 00178 [self removeItemAtIndex:count]; 00179 } 00180 00181 - (void)expandItemAtIndex:(CPInteger)anIndex 00182 { 00183 if (![_itemViews[anIndex] isCollapsed]) 00184 return; 00185 00186 [_expandedItemIndexes addIndex:anIndex]; 00187 [_itemViews[anIndex] setCollapsed:NO]; 00188 00189 [self _invalidateItemsStartingAtIndex:anIndex]; 00190 } 00191 00192 - (void)collapseItemAtIndex:(CPInteger)anIndex 00193 { 00194 if ([_itemViews[anIndex] isCollapsed]) 00195 return; 00196 00197 [_expandedItemIndexes removeIndex:anIndex]; 00198 [_itemViews[anIndex] setCollapsed:YES]; 00199 00200 [self _invalidateItemsStartingAtIndex:anIndex]; 00201 } 00202 00203 - (void)toggleItemAtIndex:(CPInteger)anIndex 00204 { 00205 var itemView = _itemViews[anIndex]; 00206 00207 if ([itemView isCollapsed]) 00208 [self expandItemAtIndex:anIndex]; 00209 00210 else 00211 [self collapseItemAtIndex:anIndex]; 00212 } 00213 00214 - (CPIndexSet)expandedItemIndexes 00215 { 00216 return _expandedItemIndexes; 00217 } 00218 00219 - (CPIndexSet)collapsedItemIndexes 00220 { 00221 var indexSet = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, _items.length)]; 00222 00223 [indexSet removeIndexes:_expandedIndexes]; 00224 00225 return indexSet; 00226 } 00227 00228 - (void)setEnabled:(BOOL)isEnabled forItemAtIndex:(CPInteger)anIndex 00229 { 00230 var itemView = _itemViews[anIndex]; 00231 if (!itemView) 00232 return; 00233 00234 if (!isEnabled) 00235 [self collapseItemAtIndex:anIndex]; 00236 else 00237 [self expandItemAtIndex:anIndex]; 00238 00239 [itemView setEnabled:isEnabled]; 00240 } 00241 00242 - (void)_invalidateItemsStartingAtIndex:(CPInteger)anIndex 00243 { 00244 if (_dirtyItemIndex === CPNotFound) 00245 _dirtyItemIndex = anIndex; 00246 00247 _dirtyItemIndex = MIN(_dirtyItemIndex, anIndex); 00248 00249 [self setNeedsLayout]; 00250 } 00251 00252 - (void)setFrameSize:(CGSize)aSize 00253 { 00254 var width = _CGRectGetWidth([self frame]); 00255 00256 [super setFrameSize:aSize]; 00257 00258 if (width !== _CGRectGetWidth([self frame])) 00259 [self _invalidateItemsStartingAtIndex:0]; 00260 } 00261 00262 - (void)layoutSubviews 00263 { 00264 if (_items.length <= 0) 00265 return [self setFrameSize:_CGSizeMake(_CGRectGetWidth([self frame]), 0.0)]; 00266 00267 if (_dirtyItemIndex === CPNotFound) 00268 return; 00269 00270 _dirtyItemIndex = MIN(_dirtyItemIndex, _items.length - 1); 00271 00272 var index = _dirtyItemIndex, 00273 count = _itemViews.length, 00274 width = _CGRectGetWidth([self bounds]), 00275 y = index > 0 ? CGRectGetMaxY([_itemViews[index - 1] frame]) : 0.0; 00276 00277 // 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. 00278 _dirtyItemIndex = CPNotFound; 00279 00280 for (; index < count; ++index) 00281 { 00282 var itemView = _itemViews[index]; 00283 00284 [itemView setFrameY:y width:width]; 00285 00286 y = CGRectGetMaxY([itemView frame]); 00287 } 00288 00289 [self setFrameSize:_CGSizeMake(_CGRectGetWidth([self frame]), y)]; 00290 } 00291 00292 @end 00293 00294 @implementation _CPAccordionItemView : CPView 00295 { 00296 CPAccordionView _accordionView; 00297 00298 BOOL _isCollapsed; 00299 CPInteger _index; 00300 CPView _headerView; 00301 CPView _contentView; 00302 } 00303 00304 - (id)initWithAccordionView:(CPAccordionView)anAccordionView 00305 { 00306 self = [super initWithFrame:_CGRectMakeZero()]; 00307 00308 if (self) 00309 { 00310 _accordionView = anAccordionView; 00311 _isCollapsed = NO; 00312 00313 var bounds = [self bounds]; 00314 00315 _headerView = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:[_accordionView itemHeaderPrototype]]]; 00316 00317 if ([_headerView respondsToSelector:@selector(setTarget:)] && [_headerView respondsToSelector:@selector(setAction:)]) 00318 { 00319 [_headerView setTarget:self]; 00320 [_headerView setAction:@selector(toggle:)]; 00321 } 00322 00323 [self addSubview:_headerView]; 00324 } 00325 00326 return self; 00327 } 00328 00329 - (void)toggle:(id)aSender 00330 { 00331 [_accordionView toggleItemAtIndex:[self index]]; 00332 } 00333 00334 - (void)setLabel:(CPString)aLabel 00335 { 00336 if ([_headerView respondsToSelector:@selector(setTitle:)]) 00337 [_headerView setTitle:aLabel]; 00338 00339 else if ([_headerView respondsToSelector:@selector(setLabel:)]) 00340 [_headerView setLabel:aLabel]; 00341 00342 else if ([_headerView respondsToSelector:@selector(setStringValue:)]) 00343 [_headerView setStringValue:aLabel]; 00344 } 00345 00346 - (void)setEnabled:(BOOL)isEnabled 00347 { 00348 if ([_headerView respondsToSelector:@selector(setEnabled:)]) 00349 [_headerView setEnabled:isEnabled]; 00350 } 00351 00352 - (void)setContentView:(CPView)aView 00353 { 00354 if (_contentView === aView) 00355 return; 00356 00357 [_contentView removeObserver:self forKeyPath:@"frame"]; 00358 00359 [_contentView removeFromSuperview]; 00360 00361 _contentView = aView; 00362 00363 [_contentView addObserver:self forKeyPath:@"frame" options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:NULL]; 00364 00365 [self addSubview:_contentView]; 00366 00367 [_accordionView _invalidateItemsStartingAtIndex:[self index]]; 00368 } 00369 00370 - (void)setFrameY:(float)aY width:(float)aWidth 00371 { 00372 var headerHeight = _CGRectGetHeight([_headerView frame]); 00373 00374 // Size to fit or something? 00375 [_headerView setFrameSize:_CGSizeMake(aWidth, headerHeight)]; 00376 [_contentView setFrameOrigin:_CGPointMake(0.0, headerHeight)]; 00377 00378 if ([self isCollapsed]) 00379 [self setFrame:_CGRectMake(0.0, aY, aWidth, headerHeight)]; 00380 00381 else 00382 { 00383 var contentHeight = _CGRectGetHeight([_contentView frame]); 00384 00385 [_contentView setFrameSize:_CGSizeMake(aWidth, contentHeight)]; 00386 [self setFrame:_CGRectMake(0.0, aY, aWidth, contentHeight + headerHeight)]; 00387 } 00388 } 00389 00390 - (void)resizeSubviewsWithOldSize:(CGSize)aSize 00391 { 00392 } 00393 00394 - (void)observeValueForKeyPath:(CPString)aKeyPath 00395 ofObject:(id)anObject 00396 change:(CPDictionary)aChange 00397 context:(id)aContext 00398 { 00399 if (aKeyPath === "frame" && !CGRectEqualToRect([aChange objectForKey:CPKeyValueChangeOldKey], [aChange objectForKey:CPKeyValueChangeNewKey])) 00400 [_accordionView _invalidateItemsStartingAtIndex:[self index]]; 00401 /* 00402 else if (aKeyPath === "itemHeaderPrototype") 00403 { 00404 00405 } 00406 */ 00407 } 00408 00409 @end 00410 00411 @implementation CPAccordionViewItem (CPSynthesizedAccessors) 00412 00416 - (CPString)identifier 00417 { 00418 return _identifier; 00419 } 00420 00424 - (void)setIdentifier:(CPString)aValue 00425 { 00426 _identifier = aValue; 00427 } 00428 00432 - (CPView)view 00433 { 00434 return _view; 00435 } 00436 00440 - (void)setView:(CPView)aValue 00441 { 00442 _view = aValue; 00443 } 00444 00448 - (CPString)label 00449 { 00450 return _label; 00451 } 00452 00456 - (void)setLabel:(CPString)aValue 00457 { 00458 _label = aValue; 00459 } 00460 00461 @end