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