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