API 0.9.5
AppKit/CPAccordionView.j
Go to the documentation of this file.
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
 All Classes Files Functions Variables Defines