API  1.0.0
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 
33 
38 
39 
40 @protocol CPTabViewDelegate <CPObject>
41 
42 @optional
43 - (BOOL)tabView:(CPTabView)tabView shouldSelectTabViewItem:(CPTabViewItem)tabViewItem;
44 - (void)tabView:(CPTabView)tabView didSelectTabViewItem:(CPTabViewItem)tabViewItem;
45 - (void)tabView:(CPTabView)tabView willSelectTabViewItem:(CPTabViewItem)tabViewItem;
46 - (void)tabViewDidChangeNumberOfTabViewItems:(CPTabView)tabView;
47 
48 @end
49 
50 
58 @implementation CPTabView : CPView
59 {
60  CPArray _items;
61 
62  _CPSegmentedControl _tabs;
63  _CPTabViewBox _box;
64  CPView _placeholderView;
65 
66  CPTabViewItem _selectedTabViewItem;
67 
68  CPTabViewType _type;
69  CPFont _font;
70 
71  id <CPTabViewDelegate> _delegate;
72  unsigned _delegateSelectors;
73 }
74 
75 - (id)initWithFrame:(CGRect)aFrame
76 {
77  if (self = [super initWithFrame:aFrame])
78  {
79  [self _init];
80  _selectedTabViewItem = nil;
81  [self setTabViewType:CPTopTabsBezelBorder];
82  }
83 
84  return self;
85 }
86 
87 - (void)_init
88 {
89  _tabs = [[_CPSegmentedControl alloc] initWithFrame:CGRectMakeZero()];
90  [_tabs setTabView:self];
91  [_tabs setSegments:[CPArray array]];
92 
93  var height = [_tabs valueForThemeAttribute:@"min-size"].height;
94  [_tabs setFrameSize:CGSizeMake(0, height)];
95 
96  _box = [[_CPTabViewBox alloc] initWithFrame:[self bounds]];
97  [_box setTabView:self];
98  [self setBackgroundColor:[CPColor colorWithCalibratedWhite:0.95 alpha:1.0]];
99 
100  [self addSubview:_box];
101  [self addSubview:_tabs];
102 
103  _placeholderView = nil;
104 }
105 
106 - (CPArray)items
107 {
108  return [_tabs segments];
109 }
110 
111 // Adding and Removing Tabs
116 - (void)addTabViewItem:(CPTabViewItem)aTabViewItem
117 {
118  [self insertTabViewItem:aTabViewItem atIndex:[self numberOfTabViewItems]];
119 }
120 
126 - (void)insertTabViewItem:(CPTabViewItem)aTabViewItem atIndex:(CPUInteger)anIndex
127 {
128  [self _insertTabViewItems:[aTabViewItem] atIndexes:[CPIndexSet indexSetWithIndex:anIndex] canUpdateSelectedTab:YES];
129 }
130 
131 - (void)_insertTabViewItems:(CPArray)tabViewItems atIndexes:(CPIndexSet)indexes canUpdateSelectedTab:(BOOL)canUpdateSelectedTab
132 {
133  var prevItemsCount = [self numberOfTabViewItems];
134 
135  [_tabs insertSegments:tabViewItems atIndexes:indexes];
136  [tabViewItems makeObjectsPerformSelector:@selector(_setTabView:) withObject:self];
137 
138  [self tileWithChangedItem:[tabViewItems firstObject]];
139  [self _reverseSetContent];
140 
141  [self _sendDelegateTabViewDidChangeNumberOfTabViewItems];
142 
143  // Do not allow empty selection if selection bindings are not enabled.
144  if (prevItemsCount == 0 && [self numberOfTabViewItems] > 0 && ![self _isSelectionBinded] && canUpdateSelectedTab)
145  [self _selectTabViewItemAtIndex:0];
146 }
147 
152 - (void)removeTabViewItem:(CPTabViewItem)aTabViewItem
153 {
154  var idx = [[self items] indexOfObjectIdenticalTo:aTabViewItem];
155 
156  if (idx == CPNotFound)
157  return;
158 
159  [_tabs removeSegmentsAtIndexes:[CPIndexSet indexSetWithIndex:idx]];
160  [aTabViewItem _setTabView:nil];
161 
162  [self tileWithChangedItem:nil];
163  [self _didRemoveTabViewItem:aTabViewItem atIndex:idx];
164  [self _reverseSetContent];
165 
166  [self _sendDelegateTabViewDidChangeNumberOfTabViewItems];
167 }
168 
169 - (void)_didRemoveTabViewItem:(CPTabViewItem)aTabViewItem atIndex:(CPInteger)idx
170 {
171  // If the selection is managed by bindings, let the binder do that.
172  if ([self _isSelectionBinded])
173  return;
174 
175  if (_selectedTabViewItem == aTabViewItem)
176  {
177  var didSelect = NO;
178 
179  if (idx > 0)
180  didSelect = [self selectTabViewItemAtIndex:idx - 1];
181  else if ([self numberOfTabViewItems] > 0)
182  didSelect = [self selectTabViewItemAtIndex:0];
183 
184  if (didSelect == NO)
185  _selectedTabViewItem == nil;
186  }
187 }
188 
189 // Accessing Tabs
195 - (int)indexOfTabViewItem:(CPTabViewItem)aTabViewItem
196 {
197  return [[self items] indexOfObjectIdenticalTo:aTabViewItem];
198 }
199 
205 - (int)indexOfTabViewItemWithIdentifier:(CPString)anIdentifier
206 {
207  return [[self items] indexOfObjectPassingTest:function(item, idx, stop)
208  {
209  return [[item identifier] isEqual:anIdentifier];
210  }];
211 }
212 
217 - (unsigned)numberOfTabViewItems
218 {
219  return [[self items] count];
220 }
221 
226 - (CPTabViewItem)tabViewItemAtIndex:(CPUInteger)anIndex
227 {
228  return [[self items] objectAtIndex:anIndex];
229 }
230 
235 - (CPArray)tabViewItems
236 {
237  return [[self items] copy]; // Copy?
238 }
239 
240 // Selecting a Tab
245 - (void)selectFirstTabViewItem:(id)aSender
246 {
247  if ([self numberOfTabViewItems] === 0)
248  return; // throw?
249 
250  [self selectTabViewItemAtIndex:0];
251 }
252 
257 - (void)selectLastTabViewItem:(id)aSender
258 {
259  if ([self numberOfTabViewItems] === 0)
260  return; // throw?
261 
263 }
264 
269 - (void)selectNextTabViewItem:(id)aSender
270 {
271  if (_selectedTabViewItem === nil)
272  return;
273 
274  var nextIndex = [self indexOfTabViewItem:_selectedTabViewItem] + 1;
275 
276  if (nextIndex === [self numberOfTabViewItems])
277  // 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:)
278  return;
279 
280  [self selectTabViewItemAtIndex:nextIndex];
281 }
282 
287 - (void)selectPreviousTabViewItem:(id)aSender
288 {
289  if (_selectedTabViewItem === nil)
290  return;
291 
292  var previousIndex = [self indexOfTabViewItem:_selectedTabViewItem] - 1;
293 
294  if (previousIndex < 0)
295  return; // does nothing. See above.
296 
297  [self selectTabViewItemAtIndex:previousIndex];
298 }
299 
304 - (void)selectTabViewItem:(CPTabViewItem)aTabViewItem
305 {
306  [self selectTabViewItemAtIndex:[self indexOfTabViewItem:aTabViewItem]];
307 }
308 
313 - (BOOL)selectTabViewItemAtIndex:(CPUInteger)anIndex
314 {
315  if (![self _selectTabViewItemAtIndex:anIndex])
316  return NO;
317 
318  [self _reverseSetSelectedIndex];
319 
320  return YES;
321 }
322 
323 // Like selectTabViewItemAtIndex: but without bindings interaction
324 - (BOOL)_selectTabViewItemAtIndex:(CPUInteger)anIndex
325 {
326  var aTabViewItem = [self tabViewItemAtIndex:anIndex];
327 
328  if (aTabViewItem == _selectedTabViewItem)
329  return NO;
330 
331  if (![self _sendDelegateShouldSelectTabViewItem:aTabViewItem])
332  return NO;
333 
334  [self _sendDelegateWillSelectTabViewItem:aTabViewItem];
335 
336  [_tabs setSelectedSegment:anIndex];
337  _selectedTabViewItem = aTabViewItem;
338  [self _loadTabViewItem:aTabViewItem];
339 
340  [self _sendDelegateDidSelectTabViewItem:aTabViewItem];
341 
342  return YES;
343 }
344 
345 - (void)_loadTabViewItem:(CPTabViewItem)aTabViewItem
346 {
347  var controller = [aTabViewItem viewController];
348 
349  if (controller !== nil && ![controller isViewLoaded])
350  {
351  [controller loadViewWithCompletionHandler:function(view, error)
352  {
353  if (error !== nil)
354  {
355  CPLog.warn("Could not load the view for item " + aTabViewItem + ". " + error);
356  }
357  else if (view !== nil)
358  {
359  [aTabViewItem setView:view];
360 
361  if ([self selectedTabViewItem] == aTabViewItem)
362  [self _displayItemView:view];
363  }
364  }];
365  }
366  else
367  {
368  [self _displayItemView:[aTabViewItem view]];
369  }
370 }
371 
376 - (CPTabViewItem)selectedTabViewItem
377 {
378  return _selectedTabViewItem;
379 }
380 
381 // Modifying the font
386 - (CPFont)font
387 {
388  return _font;
389 }
390 
395 - (void)setFont:(CPFont)font
396 {
397  if ([_font isEqual:font])
398  return;
399 
400  _font = font;
401  [_tabs setFont:_font];
402 }
403 
404 //
409 - (void)setTabViewType:(CPTabViewType)aTabViewType
410 {
411  if (_type === aTabViewType)
412  return;
413 
414  _type = aTabViewType;
415 
416  if (_type !== CPTopTabsBezelBorder && _type !== CPBottomTabsBezelBorder)
417  [_tabs removeFromSuperview];
418  else
419  [self addSubview:_tabs];
420 
421  switch (_type)
422  {
425  case CPNoTabsBezelBorder:
426  [_box setBorderType:CPBezelBorder];
427  break;
428  case CPNoTabsLineBorder:
429  [_box setBorderType:CPLineBorder];
430  break;
431  case CPNoTabsNoBorder:
432  [_box setBorderType:CPNoBorder];
433  break;
434  }
435 
436  [self setNeedsLayout];
437 }
438 
439 - (void)tileWithChangedItem:(CPTabViewItem)aTabViewItem
440 {
441  var segment = aTabViewItem ? [self indexOfTabViewItem:aTabViewItem] : 0;
442  [_tabs tileWithChangedSegment:segment];
443 
444  [self setNeedsLayout];
445 }
446 
447 - (void)layoutSubviews
448 {
449  // Even if CPTabView's autoresizesSubviews is NO, _tabs and _box has to be laid out.
450  // This means we can't rely on autoresize masks.
451  if (_type !== CPTopTabsBezelBorder && _type !== CPBottomTabsBezelBorder)
452  {
453  [_box setFrame:[self bounds]];
454  }
455  else
456  {
457  var aFrame = [self frame],
458  segmentedHeight = CGRectGetHeight([_tabs frame]),
459  origin = _type === CPTopTabsBezelBorder ? segmentedHeight / 2 : 0;
460 
461  [_box setFrame:CGRectMake(0, origin, CGRectGetWidth(aFrame),
462  CGRectGetHeight(aFrame) - segmentedHeight / 2)];
463 
464  [self _repositionTabs];
465  }
466 }
467 
472 - (CPTabViewType)tabViewType
473 {
474  return _type;
475 }
476 
481 - (id)delegate
482 {
483  return _delegate;
484 }
485 
490 - (void)setDelegate:(id <CPTabViewDelegate>)aDelegate
491 {
492  if (_delegate == aDelegate)
493  return;
494 
495  _delegate = aDelegate;
496 
497  _delegateSelectors = 0;
498 
499  if ([_delegate respondsToSelector:@selector(tabView:shouldSelectTabViewItem:)])
500  _delegateSelectors |= CPTabViewShouldSelectTabViewItemSelector;
501 
502  if ([_delegate respondsToSelector:@selector(tabView:willSelectTabViewItem:)])
503  _delegateSelectors |= CPTabViewWillSelectTabViewItemSelector;
504 
505  if ([_delegate respondsToSelector:@selector(tabView:didSelectTabViewItem:)])
506  _delegateSelectors |= CPTabViewDidSelectTabViewItemSelector;
507 
508  if ([_delegate respondsToSelector:@selector(tabViewDidChangeNumberOfTabViewItems:)])
510 }
511 
512 - (void)setBackgroundColor:(CPColor)aColor
513 {
514  [_box setBackgroundColor:aColor];
515 }
516 
518 {
519  return [_box backgroundColor];
520 }
521 
522 - (void)_repositionTabs
523 {
524  var horizontalCenterOfSelf = CGRectGetWidth([self bounds]) / 2,
525  verticalCenterOfTabs = CGRectGetHeight([_tabs bounds]) / 2;
526 
527  if (_type === CPBottomTabsBezelBorder)
528  [_tabs setCenter:CGPointMake(horizontalCenterOfSelf, CGRectGetHeight([self bounds]) - verticalCenterOfTabs)];
529  else
530  [_tabs setCenter:CGPointMake(horizontalCenterOfSelf, verticalCenterOfTabs)];
531 }
532 
533 - (void)_displayItemView:(CPView)aView
534 {
535  [_box setContentView:aView];
536 }
537 
538 // DELEGATE METHODS
539 
540 - (BOOL)_sendDelegateShouldSelectTabViewItem:(CPTabViewItem)aTabViewItem
541 {
542  if (_delegateSelectors & CPTabViewShouldSelectTabViewItemSelector)
543  return [_delegate tabView:self shouldSelectTabViewItem:aTabViewItem];
544 
545  return YES;
546 }
547 
548 - (void)_sendDelegateWillSelectTabViewItem:(CPTabViewItem)aTabViewItem
549 {
550  if (_delegateSelectors & CPTabViewWillSelectTabViewItemSelector)
551  [_delegate tabView:self willSelectTabViewItem:aTabViewItem];
552 }
553 
554 - (void)_sendDelegateDidSelectTabViewItem:(CPTabViewItem)aTabViewItem
555 {
556  if (_delegateSelectors & CPTabViewDidSelectTabViewItemSelector)
557  [_delegate tabView:self didSelectTabViewItem:aTabViewItem];
558 }
559 
560 - (void)_sendDelegateTabViewDidChangeNumberOfTabViewItems
561 {
562  if (_delegateSelectors & CPTabViewDidChangeNumberOfTabViewItemsSelector)
563  [_delegate tabViewDidChangeNumberOfTabViewItems:self];
564 }
565 
566 @end
567 
569 
570 + (Class)_binderClassForBinding:(CPString)aBinding
571 {
572  if (aBinding == CPContentBinding)
573  return [_CPTabViewContentBinder class];
574  else if (aBinding == CPSelectionIndexesBinding || aBinding == CPSelectedIndexBinding)
575  return [_CPTabViewSelectionBinder class];
576 
577  return [super _binderClassForBinding:aBinding];
578 }
579 
580 + (BOOL)isBindingExclusive:(CPString)aBinding
581 {
582  return (aBinding == CPSelectionIndexesBinding || aBinding == CPSelectedIndexBinding);
583 }
584 
585 - (void)_reverseSetContent
586 {
587  var theBinder = [self binderForBinding:CPContentBinding];
588  [theBinder reverseSetValueFor:@"items"];
589 }
590 
591 - (void)_reverseSetSelectedIndex
592 {
593  var theBinder = [self binderForBinding:CPSelectionIndexesBinding];
594 
595  if (theBinder !== nil)
596  [theBinder reverseSetValueFor:@"selectionIndexes"];
597  else
598  {
599  theBinder = [self binderForBinding:CPSelectedIndexBinding];
600  [theBinder reverseSetValueFor:@"selectedIndex"];
601  }
602 }
603 
604 - (CPBinder)binderForBinding:(CPString)aBinding
605 {
606  var cls = [[self class] _binderClassForBinding:aBinding];
607  return [cls getBinding:aBinding forObject:self];
608 }
609 
610 - (BOOL)_isSelectionBinded
611 {
612  return [self binderForBinding:CPSelectionIndexesBinding] || [self binderForBinding:CPSelectedIndexBinding];
613 }
614 
615 - (void)setItems:(CPArray)tabViewItems
616 {
617  if ([tabViewItems isEqualToArray:[_tabs segments]])
618  return;
619 
620  [[self items] makeObjectsPerformSelector:@selector(_setTabView:) withObject:nil];
621  [_tabs setSegments:tabViewItems];
622  [tabViewItems makeObjectsPerformSelector:@selector(_setTabView:) withObject:self];
623 
624  [self tileWithChangedItem:nil];
625 
626  // Update the selection because setSegments: did remove all previous segments AND the selection.
627  [_tabs setSelectedSegment:[self indexOfTabViewItem:_selectedTabViewItem]];
628 
629  // should we send delegate methods in bindings mode ?
630  //[self _delegateTabViewDidChangeNumberOfTabViewItems:self];
631 }
632 
633 - (void)_deselectAll
634 {
635  [_tabs setSelectedSegment:-1];
636  _selectedTabViewItem = nil;
637 }
638 
639 - (void)_displayPlaceholder:(CPString)aPlaceholder
640 {
641  if (_placeholderView == nil)
642  {
643  _placeholderView = [[CPView alloc] initWithFrame:CGRectMakeZero()];
644  var textField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
645  [textField setTag:1000];
646  [textField setTextColor:[CPColor whiteColor]];
647  [textField setFont:[CPFont boldFontWithName:@"Geneva" size:18 italic:YES]];
648  [_placeholderView addSubview:textField];
649  }
650 
651  var textField = [_placeholderView viewWithTag:1000];
652  [textField setStringValue:aPlaceholder];
653  [textField sizeToFit];
654 
655  var boxBounds = [_box bounds],
656  textFieldBounds = [textField bounds],
657  origin = CGPointMake(CGRectGetWidth(boxBounds)/2 - CGRectGetWidth(textFieldBounds)/2, CGRectGetHeight(boxBounds)/2 - CGRectGetHeight(textFieldBounds));
658 
659  [textField setFrameOrigin:origin];
660 
661  [self _displayItemView:_placeholderView];
662 }
663 
664 #pragma mark -
665 #pragma mark Override
666 
670 - (BOOL)acceptsFirstMouse:(CPEvent)anEvent
671 {
672  return YES;
673 }
674 
675 @end
676 
677 var _CPTabViewContentBinderNull = @"NO CONTENT";
678 @implementation _CPTabViewContentBinder : CPBinder
679 {
680  id __doxygen__;
681 }
682 
683 - (void)_updatePlaceholdersWithOptions:(CPDictionary)options
684 {
685  [super _updatePlaceholdersWithOptions:options];
686  [self _setPlaceholder:_CPTabViewContentBinderNull forMarker:CPNullMarker isDefault:YES];
687 }
688 
689 - (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
690 {
691  [_source setItems:@[]];
692  [_source _setPlaceholderView:aValue];
693 }
694 
695 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
696 {
697  [_source setItems:aValue];
698 }
699 
700 - (id)valueForBinding:(CPString)aBinding
701 {
702  return [_source items];
703 }
704 
705 @end
706 
707 var _CPTabViewSelectionBinderMultipleValues = @"Multiple Selection",
708  _CPTabViewSelectionBinderNoSelection = @"No Selection";
709 @implementation _CPTabViewSelectionBinder : CPBinder
710 {
711  id __doxygen__;
712 }
713 
714 - (void)_updatePlaceholdersWithOptions:(CPDictionary)options
715 {
716  [super _updatePlaceholdersWithOptions:options];
717 
718  [self _setPlaceholder:_CPTabViewSelectionBinderMultipleValues forMarker:CPMultipleValuesMarker isDefault:YES];
719  [self _setPlaceholder:_CPTabViewSelectionBinderNoSelection forMarker:CPNoSelectionMarker isDefault:YES];
720 }
721 
722 - (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
723 {
724  if (aMarker == CPNoSelectionMarker || aMarker == CPNullMarker)
725  [_source _deselectAll];
726 
727  [_source _displayPlaceholder:aValue];
728 }
729 
730 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
731 {
732  if (aBinding == CPSelectionIndexesBinding)
733  {
734  if (aValue == nil || [aValue count] == 0)
735  {
736  [_source _deselectAll];
737  [_source _displayPlaceholder:_CPTabViewSelectionBinderNoSelection];
738  }
739  else if ([aValue count] > 1)
740  [_source _displayPlaceholder:_CPTabViewSelectionBinderMultipleValues];
741  else if ([aValue firstIndex] < [_source numberOfTabViewItems])
742  [_source _selectTabViewItemAtIndex:[aValue firstIndex]];
743  }
744  else if (aBinding == CPSelectedIndexBinding)
745  {
746  if (aValue == CPNotFound)
747  {
748  [_source _deselectAll];
749  [_source _displayPlaceholder:_CPTabViewSelectionBinderNoSelection];
750  }
751  else if (aValue < [_source numberOfTabViewItems])
752  [_source _selectTabViewItemAtIndex:aValue];
753  }
754 }
755 
756 - (id)valueForBinding:(CPString)aBinding
757 {
758  if (aBinding == CPSelectionIndexesBinding)
759  {
760  var result = [CPIndexSet indexSet],
761  idx = [_source indexOfTabViewItem:[_source selectedTabViewItem]];
762 
763  if (idx !== CPNotFound)
764  [result addIndex:idx];
765 
766  return result;
767  }
768  else if (aBinding == CPSelectedIndexBinding)
769  return [_source indexOfTabViewItem:[_source selectedTabViewItem]];
770 }
771 
772 @end
773 
774 var CPTabViewItemsKey = "CPTabViewItemsKey",
775  CPTabViewSelectedItemKey = "CPTabViewSelectedItemKey",
776  CPTabViewTypeKey = "CPTabViewTypeKey",
777  CPTabViewFontKey = "CPTabViewFontKey",
778  CPTabViewDelegateKey = "CPTabViewDelegateKey";
779 
780 @implementation CPTabView (CPCoding)
781 
782 - (id)initWithCoder:(CPCoder)aCoder
783 {
784  if (self = [super initWithCoder:aCoder])
785  {
786  [self _init];
787 
788  _font = [aCoder decodeObjectForKey:CPTabViewFontKey];
789  [_tabs setFont:_font];
790 
791  var items = [aCoder decodeObjectForKey:CPTabViewItemsKey] || [CPArray array];
792  [self _insertTabViewItems:items atIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [items count])] canUpdateSelectedTab:NO];
793 
794  [self setDelegate:[aCoder decodeObjectForKey:CPTabViewDelegateKey]];
795 
796  _selectedTabViewItem = [aCoder decodeObjectForKey:CPTabViewSelectedItemKey];
797 
798  _type = [aCoder decodeIntForKey:CPTabViewTypeKey];
799  }
800 
801  return self;
802 }
803 
804 - (void)awakeFromCib
805 {
806  [super awakeFromCib];
807 
808  // This cannot be run in initWithCoder because it might call selectTabViewItem:, which is
809  // not safe to call before the views of the tab views items are fully decoded.
810 
811  if (_selectedTabViewItem)
812  {
813  var idx = [self indexOfTabViewItem:_selectedTabViewItem];
814 
815  if (idx !== CPNotFound)
816  {
817  // Temporarily set the selected item to not selected.
818  // It allows the initial selection to be made correctly.
819  _selectedTabViewItem = nil;
820 
821  [self selectTabViewItemAtIndex:idx];
822  }
823  }
824 
825  var type = _type;
826  _type = nil;
827  [self setTabViewType:type];
828 
829  [self setNeedsLayout];
830 }
831 
832 - (void)encodeWithCoder:(CPCoder)aCoder
833 {
834  // Don't bother to encode the CPBox. We will recreate it on decode and its content view is already
835  // stored by the tab view item. Not encoding _box makes the resulting archive smaller and reduces
836  // the surface for decoding bugs (of which we've had many in tab view).
837  var subviews = [self subviews];
838  [_box removeFromSuperview];
839  [super encodeWithCoder:aCoder];
840  [self setSubviews:subviews];
841 
842  [aCoder encodeObject:_items forKey:CPTabViewItemsKey];
843 
844  [aCoder encodeConditionalObject:_selectedTabViewItem forKey:CPTabViewSelectedItemKey];
845 
846  [aCoder encodeInt:_type forKey:CPTabViewTypeKey];
847  [aCoder encodeObject:_font forKey:CPTabViewFontKey];
848 
849  [aCoder encodeConditionalObject:_delegate forKey:CPTabViewDelegateKey];
850 }
851 
852 @end
853 
854 @implementation _CPTabViewBox : CPBox
855 {
856  CPTabView _tabView;
857 }
858 
859 
860 #pragma mark -
861 #pragma mark Override
862 
863 - (CPView)hitTest:(CGPoint)aPoint
864 {
865  // Here we check if we have clicked on the segmentedControl of the tabView or not
866  // If YES, the CPBox should not handle the click
867  var segmentIndex = [_tabView._tabs testSegment:[_tabView._tabs convertPoint:aPoint fromView:[self superview]]];
868 
869  if (segmentIndex != CPNotFound)
870  return nil;
871 
872  return [super hitTest:aPoint];
873 }
874 
875 @end
876 
877 # pragma mark -
878 
879 // This subclass of CPSegmentedControl implements specific behaviour needed by CPTabView, which
880 // differs in some ways from normal CPSegmentedControl habits :
881 //
882 // - all items are enabled
883 // - when you select a tab (mouse down, holding) and drag upon another tab, the second one reacts
884 // (showing a pushed state) and can then be selected by releasing the mouse over it.
885 // - a delegate has the opportunity to accept or not the selection of an item
886 
887 @implementation _CPSegmentedControl : CPSegmentedControl
888 {
889  CPTabView _tabView;
890 }
891 
892 - (void)trackSegment:(CPEvent)anEvent
893 {
894  var type = [anEvent type],
895  location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
896  currentSegment = [self testSegment:location];
897 
898  switch (type)
899  {
900  case CPLeftMouseUp:
901 
902  if (_trackingSegment === CPNotFound)
903  return;
904 
905  if ((_trackingSegment !== _selectedSegment) &&
906  [_tabView _sendDelegateShouldSelectTabViewItem:[_tabView tabViewItemAtIndex:_trackingSegment]])
907  {
908  [self setSelected:YES forSegment:_trackingSegment];
909  _selectedSegment = _trackingSegment;
910  [_tabView selectTabViewItemAtIndex:_selectedSegment];
911  }
912 
913  [self drawSegmentBezel:_trackingSegment highlight:NO];
914 
915  _trackingSegment = CPNotFound;
916 
917  return;
918 
919  case CPLeftMouseDown:
920 
921  if (currentSegment > CPNotFound)
922  {
923  _trackingSegment = currentSegment;
924  [self drawSegmentBezel:_trackingSegment highlight:YES];
925  }
926 
927  break;
928 
929  case CPLeftMouseDragged:
930 
931  if (_trackingSegment !== currentSegment)
932  {
933  if (_trackingSegment > CPNotFound)
934  [self drawSegmentBezel:_trackingSegment highlight:NO];
935 
936  _trackingSegment = currentSegment;
937 
938  if (_trackingSegment > CPNotFound)
939  [self drawSegmentBezel:_trackingSegment highlight:YES];
940  }
941 
942  break;
943  }
944 
945  [CPApp setTarget:self selector:@selector(trackSegment:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
946 }
947 
948 @end
949 
950 
952 
953 @end
Definition: CPFont.h:2
void setView:(CPView aView)
var CPTabViewWillSelectTabViewItemSelector
Definition: CPTabView.j:36
void addSubview:(CPView aSubview)
Definition: CPView.j:536
CGRect frame
int indexOfTabViewItem:(CPTabViewItem aTabViewItem)
Definition: CPTabView.j:195
var CPTabViewItemsKey
Definition: CPTabView.j:774
var isEqual
CPViewController viewController()
CPFont boldFontWithName:size:italic:(CPString aName, [size] float aSize, [italic] BOOL italic)
Definition: CPFont.j:262
CGPoint locationInWindow()
Definition: CPEvent.j:290
CPColor whiteColor()
Definition: CPColor.j:361
id delegate()
Definition: CALayer.j:965
CGRect bounds()
Definition: CPView.j:1326
A collection of unique integers.
Definition: CPIndexSet.h:2
CPEventType type()
Definition: CPEvent.j:325
CPNoTabsNoBorder
Definition: CPTabView.j:31
A mutable key-value pair collection.
Definition: CPDictionary.h:2
CGRect bounds()
Definition: CALayer.j:203
CPTabViewType CPTopTabsBezelBorder
Definition: CPTabView.j:25
CPBottomTabsBezelBorder
Definition: CPTabView.j:27
An immutable string (collection of characters).
Definition: CPString.h:2
CPArray items()
Definition: CPTabView.j:106
CPColor colorWithCalibratedWhite:alpha:(float white, [alpha] float alpha)
Definition: CPColor.j:173
var CPTabViewDelegateKey
Definition: CPTabView.j:778
void encodeWithCoder:(CPCoder aCoder)
Definition: CPView.j:3806
var CPTabViewDidChangeNumberOfTabViewItemsSelector
Definition: CPTabView.j:37
CPNoTabsBezelBorder
Definition: CPTabView.j:29
void setSubviews:(CPArray newSubviews)
Definition: CPView.j:732
CPLeftMouseUp
void setTabViewType:(CPTabViewType aTabViewType)
Definition: CPTabView.j:409
CPColor backgroundColor()
Definition: CALayer.j:629
CPArray subviews()
Definition: CPView.j:519
void setNeedsLayout()
Definition: CPView.j:2748
CPLeftMouseDragged
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
var CPTabViewSelectedItemKey
Definition: CPTabView.j:775
BOOL selectTabViewItemAtIndex:(CPUInteger anIndex)
Definition: CPTabView.j:313
CPNotFound
Definition: CPObjJRuntime.j:62
void tileWithChangedItem:(CPTabViewItem aTabViewItem)
Definition: CPTabView.j:439
var CPTabViewDidSelectTabViewItemSelector
Definition: CPTabView.j:34
CPLeftMouseDown
void setDelegate:(id< CPTabViewDelegate > aDelegate)
Definition: CPTabView.j:490
var CPTabViewFontKey
Definition: CPTabView.j:777
CPArray tabViewItems()
Definition: CPTabView.j:235
id indexSetWithIndexesInRange:(CPRange aRange)
Definition: CPIndexSet.j:60
Definition: CPBox.h:2
Definition: CPEvent.h:2
CGRect frame()
Definition: CPView.j:1046
CPNoTabsLineBorder
Definition: CPTabView.j:30
id indexSetWithIndex:(int anIndex)
Definition: CPIndexSet.j:51
void insertTabViewItem:atIndex:(CPTabViewItem aTabViewItem, [atIndex] CPUInteger anIndex)
Definition: CPTabView.j:126
id indexSet()
Definition: CPIndexSet.j:43
var CPTabViewShouldSelectTabViewItemSelector
Definition: CPTabView.j:35
unsigned numberOfTabViewItems()
Definition: CPTabView.j:217
Definition: CPView.j:137
var CPTabViewTypeKey
Definition: CPTabView.j:776