API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPToolbar.j
Go to the documentation of this file.
1 /*
2  * CPToolbar.j
3  * AppKit
4  *
5  * Portions based on NSToolbar.m (11/10/2008) in Cocotron (http://www.cocotron.org/)
6  * Copyright (c) 2006-2007 Christopher J. W. Lloyd
7  *
8  * Created by Francisco Tolmasky.
9  * Copyright 2008, 280 North, Inc.
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2.1 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with this library; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24  */
25 
26 
27 
28 
29 /*
30  @global
31  @group CPToolbarDisplayMode
32 */
34 /*
35  @global
36  @group CPToolbarDisplayMode
37 */
39 /*
40  @global
41  @group CPToolbarDisplayMode
42 */
44 /*
45  @global
46  @group CPToolbarDisplayMode
47 */
49 
50 
54 
57 
85 @implementation CPToolbar : CPObject
86 {
87  CPString _identifier;
88  CPToolbarDisplayMode _displayMode;
89  BOOL _showsBaselineSeparator;
90  BOOL _allowsUserCustomization;
91  BOOL _isVisible;
92  int _sizeMode;
93  int _desiredHeight;
94 
95  id _delegate;
96 
97  CPArray _itemIdentifiers;
98 
99  CPDictionary _identifiedItems;
100  CPArray _defaultItems;
101  CPArray _allowedItems;
102  CPArray _selectableItems;
103 
104  CPArray _items;
105  CPArray _itemsSortedByVisibilityPriority;
106 
107  CPView _toolbarView;
108  CPWindow _window;
109 }
110 
111 /* @ignore */
112 + (void)initialize
113 {
114  if (self !== [CPToolbar class])
115  return;
116 
119 }
120 
121 /* @ignore */
122 + (void)_addToolbar:(CPToolbar)toolbar forIdentifier:(CPString)identifier
123 {
124  var toolbarsSharingIdentifier = [CPToolbarsByIdentifier objectForKey:identifier];
125 
126  if (!toolbarsSharingIdentifier)
127  {
128  toolbarsSharingIdentifier = []
129  [CPToolbarsByIdentifier setObject:toolbarsSharingIdentifier forKey:identifier];
130  }
131 
132  [toolbarsSharingIdentifier addObject:toolbar];
133 }
134 
135 /*
136  Temporary theme attributes until we've figured out which CPView these theme attributes should
137  belong to.
138  @ignore
139 */
140 + (id)_themeAttributes
141 {
142  // "regular-size-height" is used if no item has a non-zero min size and sizeMode == CPToolbarSizeModeRegular.
143  // "small-size-height" is used if no item has a non-zero min size and sizeMode == CPToolbarSizeModeSmall.
144  return [CPDictionary dictionaryWithObjects:[_CGInsetMake(4.0, 4.0, 4.0, TOOLBAR_ITEM_MARGIN), 59.0, 46.0]
145  forKeys:[@"content-inset", @"regular-size-height", @"small-size-height"]];
146 }
147 
148 - (id)init
149 {
150  return [self initWithIdentifier:@""];
151 }
152 
158 - (id)initWithIdentifier:(CPString)anIdentifier
159 {
160  self = [super init];
161 
162  if (self)
163  {
164  _items = [];
165 
166  _identifier = anIdentifier;
167  _isVisible = YES;
168  _sizeMode = CPToolbarSizeModeDefault;
169  _desiredHeight = 0;
170 
171  [CPToolbar _addToolbar:self forIdentifier:_identifier];
172  }
173 
174  return self;
175 }
176 
177 
181 - (void)setDisplayMode:(CPToolbarDisplayMode)aDisplayMode
182 {
183 
184 }
185 
189 - (CPString)identifier
190 {
191  return _identifier;
192 }
193 
197 - (id)delegate
198 {
199  return _delegate;
200 }
201 
205 - (BOOL)isVisible
206 {
207  return _isVisible;
208 }
209 
214 - (void)setVisible:(BOOL)aFlag
215 {
216  if (_isVisible === aFlag)
217  return;
218 
219  _isVisible = aFlag;
220 
221  [_window _noteToolbarChanged];
222 }
223 
224 - (void)setSizeMode:(CPToolbarSizeMode)aSize
225 {
226  if (aSize === _sizeMode)
227  return;
228  _sizeMode = aSize;
229 
230  [[self _toolbarView] setFrame:[self _toolbarViewFrame]];
231  [_window _noteToolbarChanged];
232 }
233 
234 - (CPWindow)_window
235 {
236  return _window;
237 }
238 
239 - (void)_setWindow:(CPWindow)aWindow
240 {
241  if (_window)
242  [[CPNotificationCenter defaultCenter] removeObserver:self object:_window];
243 
244  _window = aWindow;
245 
246  if (_window)
247  [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_autoValidateVisibleItems) name:_CPWindowDidChangeFirstResponderNotification object:aWindow];
248 }
249 
254 - (void)setDelegate:(id)aDelegate
255 {
256  if (_delegate === aDelegate)
257  return;
258 
259  _delegate = aDelegate;
260 
261  [self _reloadToolbarItems];
262 }
263 
264 - (void)setDisplayMode:(CPToolbarDisplayMode)aDisplayMode
265 {
266  if (_displayMode === aDisplayMode)
267  return;
268  _displayMode = aDisplayMode;
269 
270  [self _reloadToolbarItems];
271 }
272 
273 /* @ignore */
274 - (void)_loadConfiguration
275 {
276 
277 }
278 
279 - (CGRect)_toolbarViewFrame
280 {
281  var height = _desiredHeight || (_sizeMode != CPToolbarSizeModeSmall ? [self _valueForThemeAttribute:@"regular-size-height"] : [self _valueForThemeAttribute:@"small-size-height"]);
282  return CPRectMake(0.0, 0.0, 1200.0, height);
283 }
284 
285 /*
286  Temporary theme attributes until we've figured out which CPView these theme attributes should
287  belong to.
288  @ignore
289 */
290 - (id)_valueForThemeAttribute:(CPString)attributeName
291 {
292  return [[[self class] _themeAttributes] valueForKey:attributeName];
293 }
294 
295 /* @ignore */
296 - (CPView)_toolbarView
297 {
298  if (!_toolbarView)
299  {
300  _toolbarView = [[_CPToolbarView alloc] initWithFrame:[self _toolbarViewFrame]];
301 
302  [_toolbarView setToolbar:self];
303  [_toolbarView setAutoresizingMask:CPViewWidthSizable];
304  [_toolbarView reloadToolbarItems];
305  }
306 
307  return _toolbarView;
308 }
309 
310 /* @ignore */
311 - (void)_reloadToolbarItems
312 {
313  // As of OS X 10.5 (Leopard), toolbar items can be set in IB and a
314  // toolbar delegate is optional. Toolbar items can be combined from
315  // both IB and a delegate (see Apple's NSToolbar guide for IB, for more details).
316 
317  // _defaultItems may have been loaded from Cib
318  _itemIdentifiers = [_defaultItems valueForKey:@"itemIdentifier"] || [];
319 
320  if ([_delegate respondsToSelector:@selector(toolbarDefaultItemIdentifiers:)])
321  {
322  var itemIdentifiersFromDelegate = [_delegate toolbarDefaultItemIdentifiers:self];
323 
324  // If we get items both from the Cib and from the delegate method, put the delegate items before the
325  // Cib ones.
326  if (itemIdentifiersFromDelegate)
327  _itemIdentifiers = [itemIdentifiersFromDelegate arrayByAddingObjectsFromArray:_itemIdentifiers];
328  }
329  // If we didn't load from a cib and the delegate hasn't been set yet, we'll just work with an empty list
330  // at this point.
331 
332  var index = 0,
333  count = [_itemIdentifiers count];
334 
335  _items = [];
336 
337  for (; index < count; ++index)
338  {
339  var identifier = _itemIdentifiers[index],
340  item = [CPToolbarItem _standardItemWithItemIdentifier:identifier];
341 
342  // May come from a Cib.
343  if (!item)
344  item = [_identifiedItems objectForKey:identifier];
345 
346  if (!item && _delegate)
347  item = [_delegate toolbar:self itemForItemIdentifier:identifier willBeInsertedIntoToolbar:YES];
348 
349  item = [item copy];
350 
351  if (item === nil)
353  reason:@"Toolbar delegate " + _delegate + " returned nil toolbar item for identifier \"" + identifier + "\""];
354 
355  item._toolbar = self;
356 
357  [_items addObject:item];
358  }
359 
360 // _items = [[self _defaultToolbarItems] mutableCopy];
361 
362  // Store items sorted by priority. We want items to be removed first at the end of the array,
363  // items to be removed last at the front.
364 
365  _itemsSortedByVisibilityPriority = [_items sortedArrayUsingFunction:_CPToolbarItemVisibilityPriorityCompare context:NULL];
366 
367  [_toolbarView reloadToolbarItems];
368 }
369 
373 - (CPArray)items
374 {
375  return _items;
376 }
377 
381 - (CPArray)visibleItems
382 {
383  return [_toolbarView visibleItems];
384 }
385 
389 - (CPArray)itemsSortedByVisibilityPriority
390 {
391  return _itemsSortedByVisibilityPriority;
392 }
393 
398 - (void)validateVisibleItems
399 {
400  [self _validateVisibleItems:NO]
401 }
402 
403 - (void)_autoValidateVisibleItems
404 {
405  [self _validateVisibleItems:YES]
406 }
407 
408 - (void)_validateVisibleItems:(BOOL)isAutovalidation
409 {
410  var toolbarItems = [self visibleItems],
411  count = [toolbarItems count];
412 
413  while (count--)
414  {
415  var item = [toolbarItems objectAtIndex:count];
416  if (!isAutovalidation || [item autovalidates])
417  [item validate];
418  }
419 }
420 
421 /* @ignore */
422 - (id)_itemForItemIdentifier:(CPString)identifier willBeInsertedIntoToolbar:(BOOL)toolbar
423 {
424  var item = [_identifiedItems objectForKey:identifier];
425  if (!item)
426  {
427  item = [CPToolbarItem _standardItemWithItemIdentifier:identifier];
428  if (_delegate && !item)
429  {
430  item = [[_delegate toolbar:self itemForItemIdentifier:identifier willBeInsertedIntoToolbar:toolbar] copy];
431  if (!item)
433  reason:@"Toolbar delegate " + _delegate + " returned nil toolbar item for identifier " + identifier];
434  }
435 
436  [_identifiedItems setObject:item forKey:identifier];
437  }
438 
439  return item;
440 }
441 
442 /* @ignore */
443 - (id)_itemsWithIdentifiers:(CPArray)identifiers
444 {
445  var items = [];
446  for (var i = 0; i < identifiers.length; i++)
447  [items addObject:[self _itemForItemIdentifier:identifiers[i] willBeInsertedIntoToolbar:NO]];
448 
449  return items;
450 }
451 
452 /* @ignore */
453 - (id)_defaultToolbarItems
454 {
455  if (!_defaultItems && [_delegate respondsToSelector:@selector(toolbarDefaultItemIdentifiers:)])
456  {
457  _defaultItems = [];
458 
459  var identifiers = [_delegate toolbarDefaultItemIdentifiers:self],
460  index = 0,
461  count = [identifiers count];
462 
463  for (; index < count; ++index)
464  [_defaultItems addObject:[self _itemForItemIdentifier:identifiers[index] willBeInsertedIntoToolbar:NO]];
465  }
466 
467  return _defaultItems;
468 }
469 
474 - (void)toolbarItemDidChange:(CPToolbarItem)anItem
475 {
476  if ([_identifiedItems objectForKey:[anItem itemIdentifier]])
477  [_identifiedItems setObject:anItem forKey:[anItem itemIdentifier]];
478 
479  var index = 0,
480  count = [_items count];
481 
482  for (; index <= count; ++index)
483  {
484  var item = _items[index];
485 
486  if ([item itemIdentifier] === [anItem itemIdentifier])
487  {
488  _items[index] = anItem;
489  _itemsSortedByVisibilityPriority = [_items sortedArrayUsingFunction:_CPToolbarItemVisibilityPriorityCompare context:NULL];
490 
491  [_toolbarView reloadToolbarItems];
492  }
493  }
494 }
495 
496 @end
497 
498 
499 var CPToolbarIdentifierKey = @"CPToolbarIdentifierKey",
500  CPToolbarDisplayModeKey = @"CPToolbarDisplayModeKey",
501  CPToolbarShowsBaselineSeparatorKey = @"CPToolbarShowsBaselineSeparatorKey",
502  CPToolbarAllowsUserCustomizationKey = @"CPToolbarAllowsUserCustomizationKey",
503  CPToolbarIsVisibleKey = @"CPToolbarIsVisibleKey",
504  CPToolbarDelegateKey = @"CPToolbarDelegateKey",
505  CPToolbarIdentifiedItemsKey = @"CPToolbarIdentifiedItemsKey",
506  CPToolbarDefaultItemsKey = @"CPToolbarDefaultItemsKey",
507  CPToolbarAllowedItemsKey = @"CPToolbarAllowedItemsKey",
508  CPToolbarSelectableItemsKey = @"CPToolbarSelectableItemsKey",
509  CPToolbarSizeModeKey = @"CPToolbarSizeModeKey";
510 
511 @implementation CPToolbar (CPCoding)
512 
513 /*
514  Initializes the toolbar by unarchiving data from \c aCoder.
515  @param aCoder the coder containing the archived CPToolbar.
516 */
517 - (id)initWithCoder:(CPCoder)aCoder
518 {
519  self = [super init];
520 
521  if (self)
522  {
523  _identifier = [aCoder decodeObjectForKey:CPToolbarIdentifierKey];
524  _displayMode = [aCoder decodeIntForKey:CPToolbarDisplayModeKey];
525  _showsBaselineSeparator = [aCoder decodeBoolForKey:CPToolbarShowsBaselineSeparatorKey];
526  _allowsUserCustomization = [aCoder decodeBoolForKey:CPToolbarAllowsUserCustomizationKey];
527  _isVisible = [aCoder decodeBoolForKey:CPToolbarIsVisibleKey];
528  _sizeMode = [aCoder decodeIntForKey:CPToolbarSizeModeKey];
529 
530  _identifiedItems = [aCoder decodeObjectForKey:CPToolbarIdentifiedItemsKey];
531  _defaultItems = [aCoder decodeObjectForKey:CPToolbarDefaultItemsKey];
532  _allowedItems = [aCoder decodeObjectForKey:CPToolbarAllowedItemsKey];
533  _selectableItems = [aCoder decodeObjectForKey:CPToolbarSelectableItemsKey];
534 
535  [[_identifiedItems allValues] makeObjectsPerformSelector:@selector(_setToolbar:) withObject:self];
536 
537  _items = [];
538 
539  [CPToolbar _addToolbar:self forIdentifier:_identifier];
540 
541  // This won't come from a Cib, but can come from manual encoding.
542  [self setDelegate:[aCoder decodeObjectForKey:CPToolbarDelegateKey]];
543 
544  // Because we don't know if a delegate will be set later (it is optional
545  // as of OS X 10.5), we need to call -_reloadToolbarItems here.
546  // In order to load any toolbar items that may have been configured in the
547  // Cib. Unfortunately this means that if there is a delegate
548  // specified, it will be read later and the resulting call to -setDelegate:
549  // will cause -_reloadToolbarItems] to run again :-(
550  // FIXME: Can we make this better?
551 
552  // Do this at the end of the run loop to allow all the cib-stuff to
553  // finish (establishing connections, etc.).
555  performSelector:@selector(_reloadToolbarItems)
556  target:self
557  argument:nil
558  order:0 modes:[CPDefaultRunLoopMode]];
559  }
560 
561  return self;
562 }
563 
564 /*
565  Archives this toolbar into the provided coder.
566  @param aCoder the coder to which the toolbar's instance data will be written.
567 */
568 - (void)encodeWithCoder:(CPCoder)aCoder
569 {
570  [aCoder encodeObject:_identifier forKey:CPToolbarIdentifierKey];
571  [aCoder encodeInt:_displayMode forKey:CPToolbarDisplayModeKey];
572  [aCoder encodeBool:_showsBaselineSeparator forKey:CPToolbarShowsBaselineSeparatorKey];
573  [aCoder encodeBool:_allowsUserCustomization forKey:CPToolbarAllowsUserCustomizationKey];
574  [aCoder encodeBool:_isVisible forKey:CPToolbarIsVisibleKey];
575  [aCoder encodeInt:_sizeMode forKey:CPToolbarSizeModeKey]
576 
577  [aCoder encodeObject:_identifiedItems forKey:CPToolbarIdentifiedItemsKey];
578  [aCoder encodeObject:_defaultItems forKey:CPToolbarDefaultItemsKey];
579  [aCoder encodeObject:_allowedItems forKey:CPToolbarAllowedItemsKey];
580  [aCoder encodeObject:_selectableItems forKey:CPToolbarSelectableItemsKey];
581 
582  [aCoder encodeConditionalObject:_delegate forKey:CPToolbarDelegateKey];
583 }
584 
585 @end
586 
587 
588 var _CPToolbarViewBackgroundColor = nil,
589  _CPToolbarViewExtraItemsImage = nil,
590  _CPToolbarViewExtraItemsAlternateImage = nil;
591 
594 
595 var _CPToolbarItemInfoMake = function(anIndex, aView, aLabel, aMinWidth)
596 {
597  return { index:anIndex, view:aView, label:aLabel, minWidth:aMinWidth };
598 };
599 
600 /* @ignore */
601 @implementation _CPToolbarView : CPView
602 {
603  CPToolbar _toolbar;
604 
605  CPIndexSet _flexibleWidthIndexes;
606  CPIndexSet _visibleFlexibleWidthIndexes;
607 
608  CPDictionary _itemInfos;
609  JSObject _viewsForToolbarItems;
610 
611  CPArray _visibleItems;
612  CPArray _invisibleItems;
613 
614  CPPopUpButton _additionalItemsButton;
615  CPColor _labelColor;
616  CPColor _labelShadowColor;
617 
618  float _minWidth;
619 
620  BOOL _FIXME_isHUD;
621 }
622 
623 + (void)initialize
624 {
625  if (self !== [_CPToolbarView class])
626  return;
627 
628  var bundle = [CPBundle bundleForClass:self];
629 
630  _CPToolbarViewExtraItemsImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"_CPToolbarView/_CPToolbarViewExtraItemsImage.png"] size:CPSizeMake(10.0, 15.0)];
631 
632  _CPToolbarViewExtraItemsAlternateImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"_CPToolbarView/_CPToolbarViewExtraItemsAlternateImage.png"] size:_CGSizeMake(10.0, 15.0)];
633 }
634 
635 - (id)initWithFrame:(CGRect)aFrame
636 {
637  self = [super initWithFrame:aFrame];
638 
639  if (self)
640  {
641  _minWidth = 0;
642 
643  _labelColor = [CPColor blackColor];
644  _labelShadowColor = [CPColor colorWithWhite:1.0 alpha:0.75];
645 
646  _additionalItemsButton = [[CPPopUpButton alloc] initWithFrame:_CGRectMake(0.0, 0.0, 10.0, 15.0) pullsDown:YES];
647  [_additionalItemsButton setBordered:NO];
648 
649  [_additionalItemsButton setImagePosition:CPImageOnly];
650  [[_additionalItemsButton menu] setShowsStateColumn:NO];
651  [[_additionalItemsButton menu] setAutoenablesItems:NO];
652 
653  [_additionalItemsButton setAlternateImage:_CPToolbarViewExtraItemsAlternateImage];
654  }
655 
656  return self;
657 }
658 
659 - (void)setToolbar:(CPToolbar)aToolbar
660 {
661  _toolbar = aToolbar;
662 }
663 
664 - (CPToolbar)toolbar
665 {
666  return _toolbar;
667 }
668 
669 - (void)FIXME_setIsHUD:(BOOL)shouldBeHUD
670 {
671  if (_FIXME_isHUD === shouldBeHUD)
672  return;
673 
674  _FIXME_isHUD = shouldBeHUD;
675 
676  var items = [_toolbar items],
677  count = [items count];
678 
679  while (count--)
680  [[self viewForItem:items[count]] FIXME_setIsHUD:shouldBeHUD];
681 }
682 
683 // This *should* be roughly O(3N) = O(N)
684 - (void)resizeSubviewsWithOldSize:(CGSize)aSize
685 {
686  [self tile];
687 }
688 
689 - (_CPToolbarItemView)viewForItem:(CPToolbarItem)anItem
690 {
691  return _viewsForToolbarItems[[anItem UID]] || nil;
692 }
693 
694 - (void)tile
695 {
696  // We begin by recalculating the visible items.
697  var items = [_toolbar items],
698  itemsWidth = _CGRectGetWidth([self bounds]),
699  minWidth = _minWidth,
700  // FIXME: This should be a CPSet.
701  invisibleItemsSortedByPriority = [];
702 
703  _visibleItems = items;
704 
705  // We only have hidden items if our actual width is smaller than our
706  // minimum width for hiding items.
707  if (itemsWidth < minWidth)
708  {
709  itemsWidth -= TOOLBAR_EXTRA_ITEMS_WIDTH;
710 
711  _visibleItems = [_visibleItems copy];
712 
713  var itemsSortedByVisibilityPriority = [_toolbar itemsSortedByVisibilityPriority],
714  count = itemsSortedByVisibilityPriority.length;
715 
716  // Remove items until we fit:
717  // The assumption here is that there are more visible items than there are
718  // invisible items, if not it would be faster to add items until we *no
719  // longer fit*.
720  while (minWidth > itemsWidth && count)
721  {
722  var item = itemsSortedByVisibilityPriority[--count],
723  view = [self viewForItem:item];
724 
725  minWidth -= [view minSize].width + TOOLBAR_ITEM_MARGIN;
726 
727  [_visibleItems removeObjectIdenticalTo:item];
728  [invisibleItemsSortedByPriority addObject:item];
729 
730  [view setHidden:YES];
731  [view FIXME_setIsHUD:_FIXME_isHUD];
732  }
733  }
734 
735  // FIXME: minHeight?
736  var count = [items count],
737  height = 0.0;
738 
739  while (count--)
740  {
741  var view = [self viewForItem:items[count]],
742  minSize = [view minSize];
743 
744  if (height < minSize.height)
745  height = minSize.height;
746  }
747 
748  // We'll figure out the proper height for the toolbar depending on its items.
749  // If nothing has a minimum size we'll use the standard toolbar size for the
750  // sizeMode, indicated by a 0 _desiredHeight.
751  var contentInset = [_toolbar _valueForThemeAttribute:@"content-inset"],
752  newDesiredHeight = height ? height + contentInset.top + contentInset.bottom : 0;
753 
754  if (newDesiredHeight != _toolbar._desiredHeight)
755  {
756  // FIXME Probably the toolbar view shouldn't be telling the toolbar which height it should be. Maybe refactor
757  // to just have the toolbar scan the toolbar items whenever the items change.
758  _toolbar._desiredHeight = newDesiredHeight;
759 
760  [self setFrame:[_toolbar _toolbarViewFrame]];
761  [_toolbar._window _noteToolbarChanged];
762  // The above will cause tile to be called again.
763  return;
764  }
765 
766  // Determine all the items that have flexible width.
767  // Also determine the height of the toolbar.
768  var count = _visibleItems.length,
769  flexibleItemIndexes = [CPIndexSet indexSet];
770 
771  while (count--)
772  {
773  var item = _visibleItems[count],
774  view = [self viewForItem:item],
775  minSize = [view minSize];
776 
777  if (minSize.width !== [view maxSize].width)
778  [flexibleItemIndexes addIndex:count];
779 
780  // FIXME: Is this still necessary? (probably not since we iterate them all below).
781  // If the item doesn't have flexible width, then make sure it's set to the
782  // static width (min==max). This handles the case where the user did setView:
783  // with a view of a different size than minSize/maxSize
784  else
785  [view setFrameSize:_CGSizeMake(minSize.width, height)];
786 
787  [view setHidden:NO];
788  }
789 
790  var remainingSpace = itemsWidth - minWidth,
791  proportionate = 0.0;
792 
793  // Continue to distribute space proportionately while we have it,
794  // and there are flexible items left that want it. (Those with max
795  // widths may eventually not want it anymore).
796  while (remainingSpace && [flexibleItemIndexes count])
797  {
798  // Divy out the space.
799  proportionate += remainingSpace / [flexibleItemIndexes count];
800 
801  // Reset the remaining space to 0
802  remainingSpace = 0.0;
803 
804  var index = CPNotFound;
805 
806  while ((index = [flexibleItemIndexes indexGreaterThanIndex:index]) !== CPNotFound)
807  {
808  var item = _visibleItems[index],
809  view = [self viewForItem:item],
810  proposedWidth = [view minSize].width + proportionate,
811  constrainedWidth = MIN(proposedWidth, [view maxSize].width);
812 
813  if (constrainedWidth < proposedWidth)
814  {
815  [flexibleItemIndexes removeIndex:index];
816 
817  remainingSpace += proposedWidth - constrainedWidth;
818  }
819 
820  [view setFrameSize:_CGSizeMake(constrainedWidth, height)];
821  }
822  }
823 
824  // Now that all the visible items are the correct width, give them their final frames.
825  var index = 0,
826  count = _visibleItems.length,
827  x = contentInset.left,
828  contentInset = [_toolbar _valueForThemeAttribute:@"content-inset"],
829  y = contentInset.top;
830 
831  for (; index < count; ++index)
832  {
833  var view = [self viewForItem:_visibleItems[index]],
834  viewWidth = _CGRectGetWidth([view frame]);
835 
836  [view setFrame:_CGRectMake(x, y, viewWidth, height)];
837 
838  x += viewWidth + TOOLBAR_ITEM_MARGIN;
839  }
840 
841  var needsAdditionalItemsButton = NO;
842 
843  if ([invisibleItemsSortedByPriority count])
844  {
845  var index = 0,
846  count = [items count];
847 
848  _invisibleItems = [];
849 
850  for (; index < count; ++index)
851  {
852  var item = items[index];
853 
854  if ([invisibleItemsSortedByPriority indexOfObjectIdenticalTo:item] !== CPNotFound)
855  {
856  [_invisibleItems addObject:item];
857 
858  var identifier = [item itemIdentifier];
859 
860  if (identifier !== CPToolbarSpaceItemIdentifier &&
861  identifier !== CPToolbarFlexibleSpaceItemIdentifier &&
862  identifier !== CPToolbarSeparatorItemIdentifier)
863  needsAdditionalItemsButton = YES;
864  }
865  }
866  }
867 
868  if (needsAdditionalItemsButton)
869  {
870  [_additionalItemsButton setFrameOrigin:_CGPointMake(itemsWidth + 5.0, (_CGRectGetHeight([self bounds]) - _CGRectGetHeight([_additionalItemsButton frame])) / 2.0)];
871 
872  [self addSubview:_additionalItemsButton];
873 
874  [_additionalItemsButton removeAllItems];
875 
876  [_additionalItemsButton addItemWithTitle:@"Additional Items"];
877  [[_additionalItemsButton itemArray][0] setImage:_CPToolbarViewExtraItemsImage];
878 
879  var index = 0,
880  count = [_invisibleItems count],
881  hasNonSeparatorItem = NO;
882 
883  for (; index < count; ++index)
884  {
885  var item = _invisibleItems[index],
886  identifier = [item itemIdentifier];
887 
888  if (identifier === CPToolbarSpaceItemIdentifier ||
890  continue;
891 
892  if (identifier === CPToolbarSeparatorItemIdentifier)
893  {
894  if (hasNonSeparatorItem)
895  [_additionalItemsButton addItem:[CPMenuItem separatorItem]];
896 
897  continue;
898  }
899 
900  hasNonSeparatorItem = YES;
901 
902  var menuItem = [[CPMenuItem alloc] initWithTitle:[item label] action:@selector(didSelectMenuItem:) keyEquivalent:nil];
903 
904  [menuItem setRepresentedObject:item];
905  [menuItem setImage:[item image]];
906  [menuItem setTarget:self];
907  [menuItem setEnabled:[item isEnabled]];
908 
909  [_additionalItemsButton addItem:menuItem];
910  }
911  }
912  else
913  [_additionalItemsButton removeFromSuperview];
914 }
915 
916 /*
917  Used privately.
918  @ignore
919 */
920 
921 - (void)didSelectMenuItem:(id)aSender
922 {
923  var toolbarItem = [aSender representedObject];
924 
925  [CPApp sendAction:[toolbarItem action] to:[toolbarItem target] from:toolbarItem];
926 }
927 
928 - (void)reloadToolbarItems
929 {
930  // Get rid of all our current subviews.
931  var subviews = [self subviews],
932  count = subviews.length;
933 
934  while (count--)
935  [subviews[count] removeFromSuperview];
936 
937  // Populate with new subviews.
938  var items = [_toolbar items],
939  index = 0;
940 
941  count = items.length;
942 
943  _minWidth = TOOLBAR_ITEM_MARGIN;
944  _viewsForToolbarItems = { };
945 
946  for (; index < count; ++index)
947  {
948  var item = items[index],
949  view = [[_CPToolbarItemView alloc] initWithToolbarItem:item toolbar:self];
950 
951  _viewsForToolbarItems[[item UID]] = view;
952 
953  if ([item toolTip] && [view respondsToSelector:@selector(setToolTip:)])
954  [view setToolTip:[item toolTip]];
955 
956  [self addSubview:view];
957 
958  _minWidth += [view minSize].width + TOOLBAR_ITEM_MARGIN;
959  }
960 
961  [self tile];
962 }
963 
964 @end
965 
966 /* @ignore */
967 var _CPToolbarItemVisibilityPriorityCompare = function(lhs, rhs)
968 {
969  var lhsVisibilityPriority = [lhs visibilityPriority],
970  rhsVisibilityPriority = [rhs visibilityPriority];
971 
972  if (lhsVisibilityPriority == rhsVisibilityPriority)
973  return CPOrderedSame;
974 
975  if (lhsVisibilityPriority > rhsVisibilityPriority)
976  return CPOrderedAscending;
977 
978  return CPOrderedDescending;
979 };
980 
981 var LABEL_MARGIN = 2.0;
982 
983 @implementation _CPToolbarItemView : CPControl
984 {
985  CGSize _minSize;
986  CGSize _maxSize;
987  CGSize _labelSize;
988 
989  CPToolbarItem _toolbarItem;
990  CPToolbar _toolbar;
991 
992  CPImageView _imageView;
993  CPView _view;
994 
995  CPTextField _labelField;
996 
997  BOOL _FIXME_isHUD;
998 }
999 
1000 - (id)initWithToolbarItem:(CPToolbarItem)aToolbarItem toolbar:(CPToolbar)aToolbar
1001 {
1002  self = [super init];
1003 
1004  if (self)
1005  {
1006  _toolbarItem = aToolbarItem;
1007 
1008  _labelField = [[CPTextField alloc] initWithFrame:_CGRectMakeZero()];
1009 
1010  [_labelField setFont:[CPFont systemFontOfSize:11.0]];
1011  [_labelField setTextColor:[self FIXME_labelColor]];
1012  [_labelField setTextShadowColor:[self FIXME_labelShadowColor]];
1013  [_labelField setTextShadowOffset:_CGSizeMake(0.0, 1.0)];
1014  [_labelField setAutoresizingMask:CPViewWidthSizable | CPViewMinXMargin];
1015 
1016  [self addSubview:_labelField];
1017 
1018  [self updateFromItem];
1019 
1020  _toolbar = aToolbar;
1021 
1022  var keyPaths = [@"label", @"image", @"alternateImage", @"minSize", @"maxSize", @"target", @"action", @"enabled"],
1023  index = 0,
1024  count = [keyPaths count];
1025 
1026  for (; index < count; ++index)
1027  [_toolbarItem
1028  addObserver:self
1029  forKeyPath:keyPaths[index]
1030  options:0
1031  context:NULL];
1032  }
1033 
1034  return self;
1035 }
1036 
1037 - (void)FIXME_setIsHUD:(BOOL)shouldBeHUD
1038 {
1039  _FIXME_isHUD = shouldBeHUD;
1040  [_labelField setTextColor:[self FIXME_labelColor]];
1041  [_labelField setTextShadowColor:[self FIXME_labelShadowColor]];
1042 }
1043 
1044 - (void)updateFromItem
1045 {
1046  var identifier = [_toolbarItem itemIdentifier];
1047 
1048  if (identifier === CPToolbarSpaceItemIdentifier ||
1049  identifier === CPToolbarFlexibleSpaceItemIdentifier ||
1050  identifier === CPToolbarSeparatorItemIdentifier)
1051  {
1052  [_view removeFromSuperview];
1053  [_imageView removeFromSuperview];
1054 
1055  _minSize = [_toolbarItem minSize];
1056  _maxSize = [_toolbarItem maxSize];
1057 
1058  if (identifier === CPToolbarSeparatorItemIdentifier)
1059  {
1060  _view = [[CPView alloc] initWithFrame:_CGRectMake(0.0, 0.0, 2.0, 32.0)];
1061 
1062  // FIXME: Get rid of this old API!!!
1063  sizes = {};
1064  sizes[@"CPToolbarItemSeparator"] = [_CGSizeMake(2.0, 26.0), _CGSizeMake(2.0, 1.0), _CGSizeMake(2.0, 26.0)];
1065  [_view setBackgroundColor:_CPControlThreePartImagePattern(YES, sizes, @"CPToolbarItem", @"Separator")];
1066 
1067  [self addSubview:_view];
1068  }
1069 
1070  return;
1071  }
1072 
1073  [self setTarget:[_toolbarItem target]];
1074  [self setAction:[_toolbarItem action]];
1075 
1076  var view = [_toolbarItem view] || nil;
1077 
1078  if (view !== _view)
1079  {
1080  if (!view)
1081  [_view removeFromSuperview];
1082 
1083  else
1084  {
1085  [self addSubview:view];
1086  [_imageView removeFromSuperview];
1087  }
1088 
1089  _view = view;
1090  }
1091 
1092  if (!_view)
1093  {
1094  if (!_imageView)
1095  {
1096  _imageView = [[CPImageView alloc] initWithFrame:[self bounds]];
1097 
1098  [_imageView setImageScaling:CPImageScaleProportionallyDown];
1099 
1100  [self addSubview:_imageView];
1101  }
1102 
1103  [_imageView setImage:[_toolbarItem image]];
1104  }
1105 
1106  var minSize = [_toolbarItem minSize],
1107  maxSize = [_toolbarItem maxSize];
1108 
1109  [_labelField setStringValue:[_toolbarItem label]];
1110  [_labelField sizeToFit]; // FIXME
1111 
1112  [self setEnabled:[_toolbarItem isEnabled]];
1113 
1114  _labelSize = [_labelField frame].size;
1115 
1116  var iconOnly = [[_toolbarItem toolbar] displayMode] === CPToolbarDisplayModeIconOnly,
1117  labelOnly = [[_toolbarItem toolbar] displayMode] === CPToolbarDisplayModeLabelOnly;
1118  [_labelField setHidden:iconOnly];
1119  [_view setHidden:labelOnly];
1120 
1121  _minSize = _CGSizeMake(MAX(_labelSize.width, minSize.width), (labelOnly ? 0 : minSize.height) + (iconOnly ? 0 : _labelSize.height + LABEL_MARGIN));
1122  _maxSize = _CGSizeMake(MAX(_labelSize.width, maxSize.width), 100000000.0);
1123 
1124  [_toolbar tile];
1125 }
1126 
1127 - (void)layoutSubviews
1128 {
1129  var identifier = [_toolbarItem itemIdentifier];
1130 
1131  if (identifier === CPToolbarSpaceItemIdentifier ||
1132  identifier === CPToolbarFlexibleSpaceItemIdentifier)
1133  return;
1134 
1135  var bounds = [self bounds],
1136  width = _CGRectGetWidth(bounds);
1137 
1138  if (identifier === CPToolbarSeparatorItemIdentifier)
1139  return [_view setFrame:_CGRectMake(ROUND((width - 2.0) / 2.0), 0.0, 2.0, _CGRectGetHeight(bounds))];
1140 
1141  // The view is centred in the available space above the label.
1142  var view = _view || _imageView,
1143  itemMaxSize = [_toolbarItem maxSize],
1144  iconOnly = [[_toolbarItem toolbar] displayMode] === CPToolbarDisplayModeIconOnly,
1145  height = _CGRectGetHeight(bounds) - (iconOnly ? 0 : _labelSize.height),
1146  viewWidth = MIN(itemMaxSize.width, width),
1147  viewHeight = MIN(itemMaxSize.height, height);
1148 
1149  [view setFrame:_CGRectMake(ROUND((width - viewWidth) / 2.0),
1150  ROUND((height - viewHeight) / 2.0),
1151  viewWidth,
1152  viewHeight)];
1153 
1154  // Label is always drawn at the bottom of the view. So if the view is really tall but the icon is tiny, the icon is centred above the label while the label remains on the bottom.
1155  [_labelField setFrameOrigin:_CGPointMake(ROUND((width - _labelSize.width) / 2.0), _CGRectGetHeight(bounds) - _labelSize.height)];
1156 }
1157 
1158 - (void)mouseDown:(CPEvent)anEvent
1159 {
1160  if ([_toolbarItem view])
1161  return [[self nextResponder] mouseDown:anEvent];
1162 
1163  var identifier = [_toolbarItem itemIdentifier];
1164 
1165  if (identifier === CPToolbarSpaceItemIdentifier ||
1166  identifier === CPToolbarFlexibleSpaceItemIdentifier ||
1167  identifier === CPToolbarSeparatorItemIdentifier)
1168  return [[self nextResponder] mouseDown:anEvent];
1169 
1170  [super mouseDown:anEvent];
1171 }
1172 
1173 - (void)setEnabled:(BOOL)shouldBeEnabled
1174 {
1175  // Tiling is very expensive so try to avoid it. The CPToolbarItem should already be careful to not notifying about its enabled state needlessly.
1176  if ([self isEnabled] === shouldBeEnabled)
1177  return;
1178 
1179  [super setEnabled:shouldBeEnabled];
1180 
1181  if (shouldBeEnabled)
1182  {
1183  [_imageView setAlphaValue:1.0];
1184  [_labelField setAlphaValue:1.0];
1185  }
1186  else
1187  {
1188  [_imageView setAlphaValue:0.5];
1189  [_labelField setAlphaValue:0.5];
1190  }
1191 
1192  [_toolbar tile];
1193 }
1194 
1195 - (CPColor)FIXME_labelColor
1196 {
1197  if (_FIXME_isHUD)
1198  return [CPColor whiteColor];
1199 
1200  return [CPColor blackColor];
1201 }
1202 
1203 - (CPColor)FIXME_labelShadowColor
1204 {
1205  if (_FIXME_isHUD)
1206  return [self isHighlighted] ? [CPColor colorWithWhite:1.0 alpha:0.5] : [CPColor clearColor];
1207 
1208  return [self isHighlighted] ? [CPColor colorWithWhite:0.0 alpha:0.3] : [CPColor colorWithWhite:1.0 alpha:0.75];
1209 }
1210 
1211 - (void)setHighlighted:(BOOL)shouldBeHighlighted
1212 {
1213  [super setHighlighted:shouldBeHighlighted];
1214 
1215  if (shouldBeHighlighted)
1216  {
1217  var alternateImage = [_toolbarItem alternateImage];
1218 
1219  if (alternateImage)
1220  [_imageView setImage:alternateImage];
1221 
1222  [_labelField setTextShadowOffset:_CGSizeMakeZero()];
1223  }
1224  else
1225  {
1226  var image = [_toolbarItem image];
1227 
1228  if (image)
1229  [_imageView setImage:image];
1230 
1231  [_labelField setTextShadowOffset:_CGSizeMake(0.0, 1.0)];
1232  }
1233 
1234  [_labelField setTextShadowColor:[self FIXME_labelShadowColor]];
1235 }
1236 
1237 - (void)sendAction:(SEL)anAction to:(id)aSender
1238 {
1239  [CPApp sendAction:anAction to:aSender from:_toolbarItem];
1240 }
1241 
1242 - (void)observeValueForKeyPath:(CPString)aKeyPath
1243  ofObject:(id)anObject
1244  change:(CPDictionary)aChange
1245  context:(id)aContext
1246 {
1247  if (aKeyPath === "enabled")
1248  [self setEnabled:[anObject isEnabled]];
1249 
1250  else if (aKeyPath === @"target")
1251  [self setTarget:[anObject target]];
1252 
1253  else if (aKeyPath === @"action")
1254  [self setAction:[anObject action]];
1255 
1256  else
1257  [self updateFromItem];
1258 }
1259 
1260 @end
1261 
1263 
1267 - (CPToolbarDisplayMode)displayMode
1268 {
1269  return _displayMode;
1270 }
1271 
1275 - (void)setDisplayMode:(CPToolbarDisplayMode)aValue
1276 {
1277  _displayMode = aValue;
1278 }
1279 
1283 - (int)sizeMode
1284 {
1285  return _sizeMode;
1286 }
1287 
1291 - (void)setSizeMode:(int)aValue
1292 {
1293  _sizeMode = aValue;
1294 }
1295 
1296 @end