API  0.9.8
 All Classes Files Functions Variables Typedefs Macros Groups Pages
CPTabView.j
Go to the documentation of this file.
1 /*
2  * CPTabView.j
3  * AppKit
4  *
5  * Created by Derek Hammer.
6  * Copyright 2010, Derek Hammer.
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 @typedef CPTabViewType
26 //CPLeftTabsBezelBorder = 1;
28 //CPRightTabsBezelBorder = 3;
29 CPNoTabsBezelBorder = 4; //Displays no tabs and has a bezeled border.
30 CPNoTabsLineBorder = 5; //Has no tabs and displays a line border.
31 CPNoTabsNoBorder = 6; //Displays no tabs and no border.
32 
37 
38 
40 
41 @optional
42 - (BOOL)tabView:(CPTabView)tabView shouldSelectTabViewItem:(CPTabViewItem)tabViewItem;
43 - (void)tabView:(CPTabView)tabView didSelectTabViewItem:(CPTabViewItem)tabViewItem;
44 - (void)tabView:(CPTabView)tabView willSelectTabViewItem:(CPTabViewItem)tabViewItem;
45 - (void)tabViewDidChangeNumberOfTabViewItems:(CPTabView)tabView;
46 
47 @end
48 
49 
57 @implementation CPTabView : CPView
58 {
59  CPArray _items;
60 
61  CPSegmentedControl _tabs;
62  CPBox _box;
63 
64  CPNumber _selectedIndex;
65 
66  CPTabViewType _type;
67  CPFont _font;
68 
69  id <CPTabViewDelegate> _delegate;
70  unsigned _delegateSelectors;
71 }
72 
73 - (id)initWithFrame:(CGRect)aFrame
74 {
75  if (self = [super initWithFrame:aFrame])
76  {
77  _items = [CPArray array];
78 
79  [self _init];
80  [self setTabViewType:CPTopTabsBezelBorder];
81  }
82 
83  return self;
84 }
85 
86 - (void)_init
87 {
88  _selectedIndex = CPNotFound;
89 
90  _tabs = [[CPSegmentedControl alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
91  [_tabs setHitTests:NO];
92 
93  var height = [_tabs valueForThemeAttribute:@"min-size"].height;
94  [_tabs setFrameSize:CGSizeMake(0, height)];
95 
96  _box = [[CPBox alloc] initWithFrame:[self bounds]];
97  [self setBackgroundColor:[CPColor colorWithCalibratedWhite:0.95 alpha:1.0]];
98 
99  [self addSubview:_box];
100  [self addSubview:_tabs];
101 }
102 
103 // Adding and Removing Tabs
108 - (void)addTabViewItem:(CPTabViewItem)aTabViewItem
109 {
110  [self insertTabViewItem:aTabViewItem atIndex:[_items count]];
111 }
112 
118 - (void)insertTabViewItem:(CPTabViewItem)aTabViewItem atIndex:(CPUInteger)anIndex
119 {
120  [_items insertObject:aTabViewItem atIndex:anIndex];
121 
122  [self _updateItems];
123  [self _repositionTabs];
124 
125  [aTabViewItem _setTabView:self];
126 
127  if (_delegateSelectors & CPTabViewDidChangeNumberOfTabViewItemsSelector)
128  [_delegate tabViewDidChangeNumberOfTabViewItems:self];
129 }
130 
135 - (void)removeTabViewItem:(CPTabViewItem)aTabViewItem
136 {
137  var count = [_items count];
138  for (var i = 0; i < count; i++)
139  {
140  if ([_items objectAtIndex:i] === aTabViewItem)
141  {
142  [_items removeObjectAtIndex:i];
143  break;
144  }
145  }
146 
147  [self _updateItems];
148  [self _repositionTabs];
149 
150  [aTabViewItem _setTabView:nil];
151 
152  if (_delegateSelectors & CPTabViewDidChangeNumberOfTabViewItemsSelector)
153  [_delegate tabViewDidChangeNumberOfTabViewItems:self];
154 }
155 
156 // Accessing Tabs
162 - (int)indexOfTabViewItem:(CPTabViewItem)aTabViewItem
163 {
164  return [_items indexOfObjectIdenticalTo:aTabViewItem];
165 }
166 
172 - (int)indexOfTabViewItemWithIdentifier:(CPString)anIdentifier
173 {
174  for (var index = [_items count]; index >= 0; index--)
175  if ([[_items[index] identifier] isEqual:anIdentifier])
176  return index;
177 
178  return CPNotFound;
179 }
180 
185 - (unsigned)numberOfTabViewItems
186 {
187  return [_items count];
188 }
189 
194 - (CPTabViewItem)tabViewItemAtIndex:(CPUInteger)anIndex
195 {
196  return [_items objectAtIndex:anIndex];
197 }
198 
203 - (CPArray)tabViewItems
204 {
205  return [_items copy]; // Copy?
206 }
207 
208 // Selecting a Tab
213 - (void)selectFirstTabViewItem:(id)aSender
214 {
215  if ([_items count] === 0)
216  return; // throw?
217 
218  [self selectTabViewItemAtIndex:0];
219 }
220 
225 - (void)selectLastTabViewItem:(id)aSender
226 {
227  if ([_items count] === 0)
228  return; // throw?
229 
230  [self selectTabViewItemAtIndex:[_items count] - 1];
231 }
232 
237 - (void)selectNextTabViewItem:(id)aSender
238 {
239  if (_selectedIndex === CPNotFound)
240  return;
241 
242  var nextIndex = _selectedIndex + 1;
243 
244  if (nextIndex === [_items count])
245  // does nothing. According to spec at (http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Reference/ApplicationKit/Classes/NSTabView_Class/Reference/Reference.html#//apple_ref/occ/instm/NSTabView/selectNextTabViewItem:)
246  return;
247 
248  [self selectTabViewItemAtIndex:nextIndex];
249 }
250 
255 - (void)selectPreviousTabViewItem:(id)aSender
256 {
257  if (_selectedIndex === CPNotFound)
258  return;
259 
260  var previousIndex = _selectedIndex - 1;
261 
262  if (previousIndex < 0)
263  return; // does nothing. See above.
264 
265  [self selectTabViewItemAtIndex:previousIndex];
266 }
267 
272 - (void)selectTabViewItem:(CPTabViewItem)aTabViewItem
273 {
274  [self selectTabViewItemAtIndex:[self indexOfTabViewItem:aTabViewItem]];
275 }
276 
281 - (BOOL)selectTabViewItemAtIndex:(CPUInteger)anIndex
282 {
283  if (anIndex === _selectedIndex)
284  return;
285 
286  var aTabViewItem = [self tabViewItemAtIndex:anIndex];
287 
288  if ((_delegateSelectors & CPTabViewShouldSelectTabViewItemSelector) && ![_delegate tabView:self shouldSelectTabViewItem:aTabViewItem])
289  return NO;
290 
291  if (_delegateSelectors & CPTabViewWillSelectTabViewItemSelector)
292  [_delegate tabView:self willSelectTabViewItem:aTabViewItem];
293 
294  [_tabs selectSegmentWithTag:anIndex];
295  [self _setSelectedIndex:anIndex];
296 
297  if (_delegateSelectors & CPTabViewDidSelectTabViewItemSelector)
298  [_delegate tabView:self didSelectTabViewItem:aTabViewItem];
299 
300  return YES;
301 }
302 
307 - (CPTabViewItem)selectedTabViewItem
308 {
309  if (_selectedIndex != CPNotFound)
310  return [_items objectAtIndex:_selectedIndex];
311 
312  return nil;
313 }
314 
315 // Modifying the font
320 - (CPFont)font
321 {
322  return _font;
323 }
324 
329 - (void)setFont:(CPFont)font
330 {
331  if ([_font isEqual:font])
332  return;
333 
334  _font = font;
335  [_tabs setFont:_font];
336 }
337 
338 //
343 - (void)setTabViewType:(CPTabViewType)aTabViewType
344 {
345  if (_type === aTabViewType)
346  return;
347 
348  _type = aTabViewType;
349 
350  if (_type !== CPTopTabsBezelBorder && _type !== CPBottomTabsBezelBorder)
351  [_tabs removeFromSuperview];
352  else
353  [self addSubview:_tabs];
354 
355  switch (_type)
356  {
359  case CPNoTabsBezelBorder:
360  [_box setBorderType:CPBezelBorder];
361  break;
362  case CPNoTabsLineBorder:
363  [_box setBorderType:CPLineBorder];
364  break;
365  case CPNoTabsNoBorder:
366  [_box setBorderType:CPNoBorder];
367  break;
368  }
369 
370  [self setNeedsLayout];
371 }
372 
373 - (void)layoutSubviews
374 {
375  // Even if CPTabView's autoresizesSubviews is NO, _tabs and _box has to be laid out.
376  // This means we can't rely on autoresize masks.
377  if (_type !== CPTopTabsBezelBorder && _type !== CPBottomTabsBezelBorder)
378  {
379  [_box setFrame:[self bounds]];
380  }
381  else
382  {
383  var aFrame = [self frame],
384  segmentedHeight = CGRectGetHeight([_tabs frame]),
385  origin = _type === CPTopTabsBezelBorder ? segmentedHeight / 2 : 0;
386 
387  [_box setFrame:CGRectMake(0, origin, CGRectGetWidth(aFrame),
388  CGRectGetHeight(aFrame) - segmentedHeight / 2)];
389 
390  [self _updateItems];
391  [self _repositionTabs];
392  }
393 }
394 
399 - (CPTabViewType)tabViewType
400 {
401  return _type;
402 }
403 
408 - (id)delegate
409 {
410  return _delegate;
411 }
412 
417 - (void)setDelegate:(id <CPTabViewDelegate>)aDelegate
418 {
419  if (_delegate == aDelegate)
420  return;
421 
422  _delegate = aDelegate;
423 
424  _delegateSelectors = 0;
425 
426  if ([_delegate respondsToSelector:@selector(tabView:shouldSelectTabViewItem:)])
427  _delegateSelectors |= CPTabViewShouldSelectTabViewItemSelector;
428 
429  if ([_delegate respondsToSelector:@selector(tabView:willSelectTabViewItem:)])
430  _delegateSelectors |= CPTabViewWillSelectTabViewItemSelector;
431 
432  if ([_delegate respondsToSelector:@selector(tabView:didSelectTabViewItem:)])
433  _delegateSelectors |= CPTabViewDidSelectTabViewItemSelector;
434 
435  if ([_delegate respondsToSelector:@selector(tabViewDidChangeNumberOfTabViewItems:)])
437 }
438 
439 - (void)setBackgroundColor:(CPColor)aColor
440 {
441  [_box setBackgroundColor:aColor];
442 }
443 
445 {
446  return [_box backgroundColor];
447 }
448 
449 - (void)mouseDown:(CPEvent)anEvent
450 {
451  var segmentIndex = [_tabs testSegment:[_tabs convertPoint:[anEvent locationInWindow] fromView:nil]];
452 
453  if (segmentIndex != CPNotFound && [self selectTabViewItemAtIndex:segmentIndex])
454  [_tabs trackSegment:anEvent];
455 }
456 
457 - (void)_repositionTabs
458 {
459  var horizontalCenterOfSelf = CGRectGetWidth([self bounds]) / 2,
460  verticalCenterOfTabs = CGRectGetHeight([_tabs bounds]) / 2;
461 
462  if (_type === CPBottomTabsBezelBorder)
463  [_tabs setCenter:CGPointMake(horizontalCenterOfSelf, CGRectGetHeight([self bounds]) - verticalCenterOfTabs)];
464  else
465  [_tabs setCenter:CGPointMake(horizontalCenterOfSelf, verticalCenterOfTabs)];
466 }
467 
468 - (void)_setSelectedIndex:(CPNumber)index
469 {
470  _selectedIndex = index;
471  [self _setContentViewFromItem:[_items objectAtIndex:_selectedIndex]];
472 }
473 
474 - (void)_setContentViewFromItem:(CPTabViewItem)anItem
475 {
476  [_box setContentView:[anItem view]];
477 }
478 
479 - (void)_updateItems
480 {
481  var count = [_items count];
482  [_tabs setSegmentCount:count];
483 
484  for (var i = 0; i < count; i++)
485  {
486  [_tabs setLabel:[[_items objectAtIndex:i] label] forSegment:i];
487  [_tabs setTag:i forSegment:i];
488  }
489 
490  if (_selectedIndex === CPNotFound)
491  [self selectFirstTabViewItem:self];
492 }
493 
494 @end
495 
496 var CPTabViewItemsKey = "CPTabViewItemsKey",
497  CPTabViewSelectedItemKey = "CPTabViewSelectedItemKey",
498  CPTabViewTypeKey = "CPTabViewTypeKey",
499  CPTabViewFontKey = "CPTabViewFontKey",
500  CPTabViewDelegateKey = "CPTabViewDelegateKey";
501 
502 @implementation CPTabView (CPCoding)
503 
504 - (id)initWithCoder:(CPCoder)aCoder
505 {
506  if (self = [super initWithCoder:aCoder])
507  {
508  [self _init];
509 
510  _font = [aCoder decodeObjectForKey:CPTabViewFontKey];
511  [_tabs setFont:_font];
512 
513  _items = [aCoder decodeObjectForKey:CPTabViewItemsKey];
514  [_items makeObjectsPerformSelector:@selector(_setTabView:) withObject:self];
515 
516  [self setDelegate:[aCoder decodeObjectForKey:CPTabViewDelegateKey]];
517 
518  self.selectOnAwake = [aCoder decodeObjectForKey:CPTabViewSelectedItemKey];
519  _type = [aCoder decodeIntForKey:CPTabViewTypeKey];
520  }
521 
522  return self;
523 }
524 
525 - (void)awakeFromCib
526 {
527  // This cannot be run in initWithCoder because it might call selectTabViewItem:, which is
528  // not safe to call before the views of the tab views items are fully decoded.
529  [self _updateItems];
530 
531  if (self.selectOnAwake)
532  {
533  [self selectTabViewItem:self.selectOnAwake];
534  delete self.selectOnAwake;
535  }
536 
537  var type = _type;
538  _type = nil;
539  [self setTabViewType:type];
540 
541  [self setNeedsLayout];
542 }
543 
544 - (void)encodeWithCoder:(CPCoder)aCoder
545 {
546  // Don't bother to encode the CPBox. We will recreate it on decode and its content view is already
547  // stored by the tab view item. Not encoding _box makes the resulting archive smaller and reduces
548  // the surface for decoding bugs (of which we've had many in tab view).
549  var subviews = [self subviews];
550  [_box removeFromSuperview];
551  [super encodeWithCoder:aCoder];
552  [self setSubviews:subviews];
553 
554  [aCoder encodeObject:_items forKey:CPTabViewItemsKey];
555 
556  var selected = [self selectedTabViewItem];
557  if (selected)
558  [aCoder encodeObject:selected forKey:CPTabViewSelectedItemKey];
559 
560  [aCoder encodeInt:_type forKey:CPTabViewTypeKey];
561  [aCoder encodeObject:_font forKey:CPTabViewFontKey];
562 
563  [aCoder encodeConditionalObject:_delegate forKey:CPTabViewDelegateKey];
564 }
565 
566 @end