/AppKit/CPTabView.j

http://github.com/cacaodev/cappuccino · Unknown · 571 lines · 462 code · 109 blank · 0 comment · 0 complexity · 58b8c2e866fdeb3e7fa66c513d284f6b MD5 · raw 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. @import "CPBox.j"
  23. @import "CPSegmentedControl.j"
  24. @import "CPTabViewItem.j"
  25. @import "CPView.j"
  26. @typedef CPTabViewType
  27. CPTopTabsBezelBorder = 0;
  28. //CPLeftTabsBezelBorder = 1;
  29. CPBottomTabsBezelBorder = 2;
  30. //CPRightTabsBezelBorder = 3;
  31. CPNoTabsBezelBorder = 4; //Displays no tabs and has a bezeled border.
  32. CPNoTabsLineBorder = 5; //Has no tabs and displays a line border.
  33. CPNoTabsNoBorder = 6; //Displays no tabs and no border.
  34. var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
  35. CPTabViewShouldSelectTabViewItemSelector = 1 << 2,
  36. CPTabViewWillSelectTabViewItemSelector = 1 << 3,
  37. CPTabViewDidChangeNumberOfTabViewItemsSelector = 1 << 4;
  38. @protocol CPTabViewDelegate <CPObject>
  39. @optional
  40. - (BOOL)tabView:(CPTabView)tabView shouldSelectTabViewItem:(CPTabViewItem)tabViewItem;
  41. - (void)tabView:(CPTabView)tabView didSelectTabViewItem:(CPTabViewItem)tabViewItem;
  42. - (void)tabView:(CPTabView)tabView willSelectTabViewItem:(CPTabViewItem)tabViewItem;
  43. - (void)tabViewDidChangeNumberOfTabViewItems:(CPTabView)tabView;
  44. @end
  45. /*!
  46. @ingroup appkit
  47. @class CPTabView
  48. A CPTabView object presents a tabbed interface where each page is one a
  49. complete view hiearchy of its own. The user can navigate between various
  50. pages by clicking on the tab headers.
  51. */
  52. @implementation CPTabView : CPView
  53. {
  54. CPArray _items;
  55. CPSegmentedControl _tabs;
  56. CPBox _box;
  57. CPNumber _selectedIndex;
  58. CPTabViewType _type;
  59. CPFont _font;
  60. id <CPTabViewDelegate> _delegate;
  61. unsigned _delegateSelectors;
  62. }
  63. - (id)initWithFrame:(CGRect)aFrame
  64. {
  65. if (self = [super initWithFrame:aFrame])
  66. {
  67. _items = [CPArray array];
  68. [self _init];
  69. [self setTabViewType:CPTopTabsBezelBorder];
  70. }
  71. return self;
  72. }
  73. - (void)_init
  74. {
  75. _selectedIndex = CPNotFound;
  76. _tabs = [[CPSegmentedControl alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
  77. [_tabs setHitTests:NO];
  78. var height = [_tabs valueForThemeAttribute:@"min-size"].height;
  79. [_tabs setFrameSize:CGSizeMake(0, height)];
  80. _box = [[CPBox alloc] initWithFrame:[self bounds]];
  81. [self setBackgroundColor:[CPColor colorWithCalibratedWhite:0.95 alpha:1.0]];
  82. [self addSubview:_box];
  83. [self addSubview:_tabs];
  84. }
  85. // Adding and Removing Tabs
  86. /*!
  87. Adds a CPTabViewItem to the tab view.
  88. @param aTabViewItem the item to add
  89. */
  90. - (void)addTabViewItem:(CPTabViewItem)aTabViewItem
  91. {
  92. [self insertTabViewItem:aTabViewItem atIndex:[_items count]];
  93. }
  94. /*!
  95. Inserts a CPTabViewItem into the tab view at the specified index.
  96. @param aTabViewItem the item to insert
  97. @param anIndex the index for the item
  98. */
  99. - (void)insertTabViewItem:(CPTabViewItem)aTabViewItem atIndex:(CPUInteger)anIndex
  100. {
  101. [_items insertObject:aTabViewItem atIndex:anIndex];
  102. [self _updateItems];
  103. [self _repositionTabs];
  104. [aTabViewItem _setTabView:self];
  105. if (_delegateSelectors & CPTabViewDidChangeNumberOfTabViewItemsSelector)
  106. [_delegate tabViewDidChangeNumberOfTabViewItems:self];
  107. }
  108. /*!
  109. Removes the specified tab view item from the tab view.
  110. @param aTabViewItem the item to remove
  111. */
  112. - (void)removeTabViewItem:(CPTabViewItem)aTabViewItem
  113. {
  114. var count = [_items count];
  115. for (var i = 0; i < count; i++)
  116. {
  117. if ([_items objectAtIndex:i] === aTabViewItem)
  118. {
  119. [_items removeObjectAtIndex:i];
  120. break;
  121. }
  122. }
  123. [self _updateItems];
  124. [self _repositionTabs];
  125. [aTabViewItem _setTabView:nil];
  126. if (_delegateSelectors & CPTabViewDidChangeNumberOfTabViewItemsSelector)
  127. [_delegate tabViewDidChangeNumberOfTabViewItems:self];
  128. }
  129. // Accessing Tabs
  130. /*!
  131. Returns the index of the specified item
  132. @param aTabViewItem the item to find the index for
  133. @return the index of aTabViewItem or CPNotFound
  134. */
  135. - (int)indexOfTabViewItem:(CPTabViewItem)aTabViewItem
  136. {
  137. return [_items indexOfObjectIdenticalTo:aTabViewItem];
  138. }
  139. /*!
  140. Returns the index of the CPTabViewItem with the specified identifier.
  141. @param anIdentifier the identifier of the item
  142. @return the index of the tab view item identified by anIdentifier, or CPNotFound
  143. */
  144. - (int)indexOfTabViewItemWithIdentifier:(CPString)anIdentifier
  145. {
  146. for (var index = [_items count]; index >= 0; index--)
  147. if ([[_items[index] identifier] isEqual:anIdentifier])
  148. return index;
  149. return CPNotFound;
  150. }
  151. /*!
  152. Returns the number of items in the tab view.
  153. @return the number of tab view items in the receiver
  154. */
  155. - (unsigned)numberOfTabViewItems
  156. {
  157. return [_items count];
  158. }
  159. /*!
  160. Returns the CPTabViewItem at the specified index.
  161. @return a tab view item, or nil
  162. */
  163. - (CPTabViewItem)tabViewItemAtIndex:(CPUInteger)anIndex
  164. {
  165. return [_items objectAtIndex:anIndex];
  166. }
  167. /*!
  168. Returns the array of items that backs this tab view.
  169. @return a copy of the array of items in the receiver
  170. */
  171. - (CPArray)tabViewItems
  172. {
  173. return [_items copy]; // Copy?
  174. }
  175. // Selecting a Tab
  176. /*!
  177. Sets the first tab view item in the array to be displayed to the user.
  178. @param aSender the object making this request
  179. */
  180. - (void)selectFirstTabViewItem:(id)aSender
  181. {
  182. if ([_items count] === 0)
  183. return; // throw?
  184. [self selectTabViewItemAtIndex:0];
  185. }
  186. /*!
  187. Sets the last tab view item in the array to be displayed to the user.
  188. @param aSender the object making this request
  189. */
  190. - (void)selectLastTabViewItem:(id)aSender
  191. {
  192. if ([_items count] === 0)
  193. return; // throw?
  194. [self selectTabViewItemAtIndex:[_items count] - 1];
  195. }
  196. /*!
  197. Sets the next tab item in the array to be displayed.
  198. @param aSender the object making this request
  199. */
  200. - (void)selectNextTabViewItem:(id)aSender
  201. {
  202. if (_selectedIndex === CPNotFound)
  203. return;
  204. var nextIndex = _selectedIndex + 1;
  205. if (nextIndex === [_items count])
  206. // 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:)
  207. return;
  208. [self selectTabViewItemAtIndex:nextIndex];
  209. }
  210. /*!
  211. Selects the previous item in the array for display.
  212. @param aSender the object making this request
  213. */
  214. - (void)selectPreviousTabViewItem:(id)aSender
  215. {
  216. if (_selectedIndex === CPNotFound)
  217. return;
  218. var previousIndex = _selectedIndex - 1;
  219. if (previousIndex < 0)
  220. return; // does nothing. See above.
  221. [self selectTabViewItemAtIndex:previousIndex];
  222. }
  223. /*!
  224. Displays the specified item in the tab view.
  225. @param aTabViewItem the item to display
  226. */
  227. - (void)selectTabViewItem:(CPTabViewItem)aTabViewItem
  228. {
  229. [self selectTabViewItemAtIndex:[self indexOfTabViewItem:aTabViewItem]];
  230. }
  231. /*!
  232. Selects the item at the specified index.
  233. @param anIndex the index of the item to display.
  234. */
  235. - (BOOL)selectTabViewItemAtIndex:(CPUInteger)anIndex
  236. {
  237. if (anIndex === _selectedIndex)
  238. return;
  239. var aTabViewItem = [self tabViewItemAtIndex:anIndex];
  240. if ((_delegateSelectors & CPTabViewShouldSelectTabViewItemSelector) && ![_delegate tabView:self shouldSelectTabViewItem:aTabViewItem])
  241. return NO;
  242. if (_delegateSelectors & CPTabViewWillSelectTabViewItemSelector)
  243. [_delegate tabView:self willSelectTabViewItem:aTabViewItem];
  244. [_tabs selectSegmentWithTag:anIndex];
  245. [self _setSelectedIndex:anIndex];
  246. if (_delegateSelectors & CPTabViewDidSelectTabViewItemSelector)
  247. [_delegate tabView:self didSelectTabViewItem:aTabViewItem];
  248. return YES;
  249. }
  250. /*!
  251. Returns the current item being displayed.
  252. @return the tab view item currenly being displayed by the receiver
  253. */
  254. - (CPTabViewItem)selectedTabViewItem
  255. {
  256. if (_selectedIndex != CPNotFound)
  257. return [_items objectAtIndex:_selectedIndex];
  258. return nil;
  259. }
  260. // Modifying the font
  261. /*!
  262. Returns the font for tab label text.
  263. @return the font for tab label text
  264. */
  265. - (CPFont)font
  266. {
  267. return _font;
  268. }
  269. /*!
  270. Sets the font for tab label text to font.
  271. @param font the font the receiver should use for tab label text
  272. */
  273. - (void)setFont:(CPFont)font
  274. {
  275. if ([_font isEqual:font])
  276. return;
  277. _font = font;
  278. [_tabs setFont:_font];
  279. }
  280. //
  281. /*!
  282. Sets the tab view type.
  283. @param aTabViewType the view type
  284. */
  285. - (void)setTabViewType:(CPTabViewType)aTabViewType
  286. {
  287. if (_type === aTabViewType)
  288. return;
  289. _type = aTabViewType;
  290. if (_type !== CPTopTabsBezelBorder && _type !== CPBottomTabsBezelBorder)
  291. [_tabs removeFromSuperview];
  292. else
  293. [self addSubview:_tabs];
  294. switch (_type)
  295. {
  296. case CPTopTabsBezelBorder:
  297. case CPBottomTabsBezelBorder:
  298. case CPNoTabsBezelBorder:
  299. [_box setBorderType:CPBezelBorder];
  300. break;
  301. case CPNoTabsLineBorder:
  302. [_box setBorderType:CPLineBorder];
  303. break;
  304. case CPNoTabsNoBorder:
  305. [_box setBorderType:CPNoBorder];
  306. break;
  307. }
  308. [self setNeedsLayout];
  309. }
  310. - (void)layoutSubviews
  311. {
  312. // Even if CPTabView's autoresizesSubviews is NO, _tabs and _box has to be laid out.
  313. // This means we can't rely on autoresize masks.
  314. if (_type !== CPTopTabsBezelBorder && _type !== CPBottomTabsBezelBorder)
  315. {
  316. [_box setFrame:[self bounds]];
  317. }
  318. else
  319. {
  320. var aFrame = [self frame],
  321. segmentedHeight = CGRectGetHeight([_tabs frame]),
  322. origin = _type === CPTopTabsBezelBorder ? segmentedHeight / 2 : 0;
  323. [_box setFrame:CGRectMake(0, origin, CGRectGetWidth(aFrame),
  324. CGRectGetHeight(aFrame) - segmentedHeight / 2)];
  325. [self _updateItems];
  326. [self _repositionTabs];
  327. }
  328. }
  329. /*!
  330. Returns the tab view type.
  331. @return the tab view type of the receiver
  332. */
  333. - (CPTabViewType)tabViewType
  334. {
  335. return _type;
  336. }
  337. /*!
  338. Returns the receiver's delegate.
  339. @return the receiver's delegate
  340. */
  341. - (id)delegate
  342. {
  343. return _delegate;
  344. }
  345. /*!
  346. Sets the delegate for this tab view.
  347. @param aDelegate the tab view's delegate
  348. */
  349. - (void)setDelegate:(id <CPTabViewDelegate>)aDelegate
  350. {
  351. if (_delegate == aDelegate)
  352. return;
  353. _delegate = aDelegate;
  354. _delegateSelectors = 0;
  355. if ([_delegate respondsToSelector:@selector(tabView:shouldSelectTabViewItem:)])
  356. _delegateSelectors |= CPTabViewShouldSelectTabViewItemSelector;
  357. if ([_delegate respondsToSelector:@selector(tabView:willSelectTabViewItem:)])
  358. _delegateSelectors |= CPTabViewWillSelectTabViewItemSelector;
  359. if ([_delegate respondsToSelector:@selector(tabView:didSelectTabViewItem:)])
  360. _delegateSelectors |= CPTabViewDidSelectTabViewItemSelector;
  361. if ([_delegate respondsToSelector:@selector(tabViewDidChangeNumberOfTabViewItems:)])
  362. _delegateSelectors |= CPTabViewDidChangeNumberOfTabViewItemsSelector;
  363. }
  364. - (void)setBackgroundColor:(CPColor)aColor
  365. {
  366. [_box setBackgroundColor:aColor];
  367. }
  368. - (CPColor)backgroundColor
  369. {
  370. return [_box backgroundColor];
  371. }
  372. - (void)mouseDown:(CPEvent)anEvent
  373. {
  374. var segmentIndex = [_tabs testSegment:[_tabs convertPoint:[anEvent locationInWindow] fromView:nil]];
  375. if (segmentIndex != CPNotFound && [self selectTabViewItemAtIndex:segmentIndex])
  376. [_tabs trackSegment:anEvent];
  377. }
  378. - (void)_repositionTabs
  379. {
  380. var horizontalCenterOfSelf = CGRectGetWidth([self bounds]) / 2,
  381. verticalCenterOfTabs = CGRectGetHeight([_tabs bounds]) / 2;
  382. if (_type === CPBottomTabsBezelBorder)
  383. [_tabs setCenter:CGPointMake(horizontalCenterOfSelf, CGRectGetHeight([self bounds]) - verticalCenterOfTabs)];
  384. else
  385. [_tabs setCenter:CGPointMake(horizontalCenterOfSelf, verticalCenterOfTabs)];
  386. }
  387. - (void)_setSelectedIndex:(CPNumber)index
  388. {
  389. _selectedIndex = index;
  390. [self _setContentViewFromItem:[_items objectAtIndex:_selectedIndex]];
  391. }
  392. - (void)_setContentViewFromItem:(CPTabViewItem)anItem
  393. {
  394. [_box setContentView:[anItem view]];
  395. }
  396. - (void)_updateItems
  397. {
  398. var count = [_items count];
  399. [_tabs setSegmentCount:count];
  400. for (var i = 0; i < count; i++)
  401. {
  402. [_tabs setLabel:[[_items objectAtIndex:i] label] forSegment:i];
  403. [_tabs setTag:i forSegment:i];
  404. }
  405. if (_selectedIndex === CPNotFound)
  406. [self selectFirstTabViewItem:self];
  407. }
  408. @end
  409. var CPTabViewItemsKey = "CPTabViewItemsKey",
  410. CPTabViewSelectedItemKey = "CPTabViewSelectedItemKey",
  411. CPTabViewTypeKey = "CPTabViewTypeKey",
  412. CPTabViewFontKey = "CPTabViewFontKey",
  413. CPTabViewDelegateKey = "CPTabViewDelegateKey";
  414. @implementation CPTabView (CPCoding)
  415. - (id)initWithCoder:(CPCoder)aCoder
  416. {
  417. if (self = [super initWithCoder:aCoder])
  418. {
  419. [self _init];
  420. _font = [aCoder decodeObjectForKey:CPTabViewFontKey];
  421. [_tabs setFont:_font];
  422. _items = [aCoder decodeObjectForKey:CPTabViewItemsKey];
  423. [_items makeObjectsPerformSelector:@selector(_setTabView:) withObject:self];
  424. [self setDelegate:[aCoder decodeObjectForKey:CPTabViewDelegateKey]];
  425. self.selectOnAwake = [aCoder decodeObjectForKey:CPTabViewSelectedItemKey];
  426. _type = [aCoder decodeIntForKey:CPTabViewTypeKey];
  427. }
  428. return self;
  429. }
  430. - (void)awakeFromCib
  431. {
  432. // This cannot be run in initWithCoder because it might call selectTabViewItem:, which is
  433. // not safe to call before the views of the tab views items are fully decoded.
  434. [self _updateItems];
  435. if (self.selectOnAwake)
  436. {
  437. [self selectTabViewItem:self.selectOnAwake];
  438. delete self.selectOnAwake;
  439. }
  440. var type = _type;
  441. _type = nil;
  442. [self setTabViewType:type];
  443. [self setNeedsLayout];
  444. }
  445. - (void)encodeWithCoder:(CPCoder)aCoder
  446. {
  447. // Don't bother to encode the CPBox. We will recreate it on decode and its content view is already
  448. // stored by the tab view item. Not encoding _box makes the resulting archive smaller and reduces
  449. // the surface for decoding bugs (of which we've had many in tab view).
  450. var subviews = [self subviews];
  451. [_box removeFromSuperview];
  452. [super encodeWithCoder:aCoder];
  453. [self setSubviews:subviews];
  454. [aCoder encodeObject:_items forKey:CPTabViewItemsKey];
  455. var selected = [self selectedTabViewItem];
  456. if (selected)
  457. [aCoder encodeObject:selected forKey:CPTabViewSelectedItemKey];
  458. [aCoder encodeInt:_type forKey:CPTabViewTypeKey];
  459. [aCoder encodeObject:_font forKey:CPTabViewFontKey];
  460. [aCoder encodeConditionalObject:_delegate forKey:CPTabViewDelegateKey];
  461. }
  462. @end