API  1.0.0
CPPopUpButton.j
Go to the documentation of this file.
1 /*
2  * CPPopUpButton.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
25 
31 @implementation CPPopUpButton : CPButton
32 {
33  CPUInteger _selectedIndex;
34  CPRectEdge _preferredEdge;
35 }
36 
37 + (CPString)defaultThemeClass
38 {
39  return "popup-button";
40 }
41 
42 + (CPSet)keyPathsForValuesAffectingSelectedIndex
43 {
44  return [CPSet setWithObject:@"objectValue"];
45 }
46 
47 + (CPSet)keyPathsForValuesAffectingSelectedTag
48 {
49  return [CPSet setWithObject:@"objectValue"];
50 }
51 
52 + (CPSet)keyPathsForValuesAffectingSelectedItem
53 {
54  return [CPSet setWithObject:@"objectValue"];
55 }
56 
63 - (id)initWithFrame:(CGRect)aFrame pullsDown:(BOOL)shouldPullDown
64 {
65  self = [super initWithFrame:aFrame];
66 
67  if (self)
68  {
69  [self selectItemAtIndex:CPNotFound];
70 
71  _preferredEdge = CPMaxYEdge;
72 
73  [self setValue:CPImageLeft forThemeAttribute:@"image-position"];
74  [self setValue:CPLeftTextAlignment forThemeAttribute:@"alignment"];
75  [self setValue:CPLineBreakByTruncatingTail forThemeAttribute:@"line-break-mode"];
76 
77  [self setMenu:[[CPMenu alloc] initWithTitle:@""]];
78 
79  [self setPullsDown:shouldPullDown];
80 
81  var options = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld; // | CPKeyValueObservingOptionInitial;
82  [self addObserver:self forKeyPath:@"menu.items" options:options context:nil];
83  [self addObserver:self forKeyPath:@"_firstItem.changeCount" options:options context:nil];
84  [self addObserver:self forKeyPath:@"selectedItem.changeCount" options:options context:nil];
85  }
86 
87  return self;
88 }
89 
90 - (id)initWithFrame:(CGRect)aFrame
91 {
92  return [self initWithFrame:aFrame pullsDown:NO];
93 }
94 
95 // Setting the Type of Menu
96 
105 - (void)setPullsDown:(BOOL)shouldPullDown
106 {
107  if (shouldPullDown)
108  var changed = [self setThemeState:CPPopUpButtonStatePullsDown];
109  else
110  var changed = [self unsetThemeState:CPPopUpButtonStatePullsDown];
111 
112  if (!changed)
113  return;
114 
115  var items = [[self menu] itemArray];
116 
117  if ([items count] <= 0)
118  return;
119 
120  [items[0] setHidden:[self pullsDown]];
121 
123 }
124 
128 - (BOOL)pullsDown
129 {
130  return [self hasThemeState:CPPopUpButtonStatePullsDown];
131 }
132 
133 // Inserting and Deleting Items
134 
138 - (void)addItem:(CPMenuItem)anItem
139 {
140  [[self menu] addItem:anItem];
141 }
142 
147 - (void)addItemWithTitle:(CPString)aTitle
148 {
149  [[self menu] addItemWithTitle:aTitle action:NULL keyEquivalent:nil];
150 }
151 
156 - (void)addItemsWithTitles:(CPArray)titles
157 {
158  var index = 0,
159  count = [titles count];
160 
161  for (; index < count; ++index)
162  [self addItemWithTitle:titles[index]];
163 }
164 
170 - (void)insertItemWithTitle:(CPString)aTitle atIndex:(int)anIndex
171 {
172  var items = [self itemArray],
173  count = [items count];
174 
175  while (count--)
176  if ([items[count] title] == aTitle)
177  [self removeItemAtIndex:count];
178 
179  [[self menu] insertItemWithTitle:aTitle action:NULL keyEquivalent:nil atIndex:anIndex];
180 }
181 
185 - (void)removeAllItems
186 {
187  [[self menu] removeAllItems];
189 }
190 
195 - (void)removeItemWithTitle:(CPString)aTitle
196 {
197  [self removeItemAtIndex:[self indexOfItemWithTitle:aTitle]];
199 }
200 
205 - (void)removeItemAtIndex:(int)anIndex
206 {
207  [[self menu] removeItemAtIndex:anIndex];
209 }
210 
211 // Getting the User's Selection
215 - (CPMenuItem)selectedItem
216 {
217  var indexOfSelectedItem = [self indexOfSelectedItem];
218 
219  if (indexOfSelectedItem < 0 || indexOfSelectedItem > [self numberOfItems] - 1)
220  return nil;
221 
222  return [[self menu] itemAtIndex:indexOfSelectedItem];
223 }
224 
228 - (CPString)titleOfSelectedItem
229 {
230  return [[self selectedItem] title];
231 }
232 
236 - (int)indexOfSelectedItem
237 {
238  return _selectedIndex;
239 }
240 
241 // Setting the Current Selection
246 - (void)selectItem:(CPMenuItem)aMenuItem
247 {
248  [self selectItemAtIndex:[self indexOfItem:aMenuItem]];
249 }
250 
255 - (void)selectItemAtIndex:(CPUInteger)anIndex
256 {
257  [self setObjectValue:anIndex];
258 }
259 
260 - (void)setSelectedIndex:(CPUInteger)anIndex
261 {
262  [self setObjectValue:anIndex];
263 }
264 
265 - (CPUInteger)selectedIndex
266 {
267  return [self objectValue];
268 }
269 
274 - (void)setObjectValue:(id)anIndex
275 {
276  var indexOfSelectedItem = [self objectValue];
277 
278  anIndex = parseInt(+anIndex, 10);
279 
280  if (indexOfSelectedItem === anIndex)
281  return;
282 
283  if (indexOfSelectedItem >= 0 && ![self pullsDown])
284  [[self selectedItem] setState:CPOffState];
285 
286  _selectedIndex = anIndex;
287 
288  if (indexOfSelectedItem >= 0 && ![self pullsDown])
289  [[self selectedItem] setState:CPOnState];
290 
292 }
293 
294 - (id)objectValue
295 {
296  return _selectedIndex;
297 }
298 
303 - (void)selectItemWithTag:(int)aTag
304 {
305  [self selectItemAtIndex:[self indexOfItemWithTag:aTag]];
306 }
307 
312 - (void)selectItemWithTitle:(CPString)aTitle
313 {
314  [self selectItemAtIndex:[self indexOfItemWithTitle:aTitle]];
315 }
316 
317 // Getting Menu Items
318 
322 - (int)numberOfItems
323 {
324  return [[self menu] numberOfItems];
325 }
326 
330 - (CPArray)itemArray
331 {
332  return [[self menu] itemArray];
333 }
334 
339 - (CPMenuItem)itemAtIndex:(CPUInteger)anIndex
340 {
341  return [[self menu] itemAtIndex:anIndex];
342 }
343 
348 - (CPString)itemTitleAtIndex:(CPUInteger)anIndex
349 {
350  return [[[self menu] itemAtIndex:anIndex] title];
351 }
352 
356 - (CPArray)itemTitles
357 {
358  return [[self itemArray] arrayByApplyingBlock:function(item)
359  {
360  return [item title];
361  }];
362 }
363 
368 - (CPMenuItem)itemWithTitle:(CPString)aTitle
369 {
370  var menu = [self menu],
371  itemIndex = [menu indexOfItemWithTitle:aTitle];
372 
373  if (itemIndex === CPNotFound)
374  return nil;
375 
376  return [menu itemAtIndex:itemIndex];
377 }
378 
382 - (CPMenuItem)lastItem
383 {
384  return [[[self menu] itemArray] lastObject];
385 }
386 
387 // Getting the Indices of Menu Items
392 - (int)indexOfItem:(CPMenuItem)aMenuItem
393 {
394  return [[self menu] indexOfItem:aMenuItem];
395 }
396 
401 - (int)indexOfItemWithTag:(int)aTag
402 {
403  return [[self menu] indexOfItemWithTag:aTag];
404 }
405 
410 - (int)indexOfItemWithTitle:(CPString)aTitle
411 {
412  return [[self menu] indexOfItemWithTitle:aTitle];
413 }
414 
421 - (int)indexOfItemWithRepresentedObject:(id)anObject
422 {
423  return [[self menu] indexOfItemWithRepresentedObject:anObject];
424 }
425 
433 - (int)indexOfItemWithTarget:(id)aTarget action:(SEL)anAction
434 {
435  return [[self menu] indexOfItemWithTarget:aTarget action:anAction];
436 }
437 
438 // Setting the Cell Edge to Pop out in Restricted Situations
444 - (CPRectEdge)preferredEdge
445 {
446  return _preferredEdge;
447 }
448 
454 - (void)setPreferredEdge:(CPRectEdge)aRectEdge
455 {
456  _preferredEdge = aRectEdge;
457 }
458 
459 // Setting the Title
464 - (void)setTitle:(CPString)aTitle
465 {
466  if ([self title] === aTitle)
467  return;
468 
469  if ([self pullsDown])
470  {
471  var items = [[self menu] itemArray];
472 
473  if ([items count] <= 0)
474  [self addItemWithTitle:aTitle];
475 
476  else
477  {
478  [items[0] setTitle:aTitle];
480  }
481  }
482  else
483  {
484  var index = [self indexOfItemWithTitle:aTitle];
485 
486  if (index < 0)
487  {
488 
489  // this ist to match cocoa where setting an empty string does not add it but simply clears the title
490  // and sets objectValue to -1
491  if (aTitle === '')
492  [self selectItemAtIndex:-1];
493  else
494  {
495  [self addItemWithTitle:aTitle];
496 
497  index = [self numberOfItems] - 1;
498  }
499  }
500 
501  [self selectItemAtIndex:index];
502  }
503 }
504 
505 // Setting the Image
511 - (void)setImage:(CPImage)anImage
512 {
513  // The Image is set by the currently selected item.
514 }
515 
516 // Setting the State
521 - (void)synchronizeTitleAndSelectedItem
522 {
523  var item = nil;
524 
525  if ([self pullsDown])
526  {
527  var items = [[self menu] itemArray];
528 
529  if ([items count] > 0)
530  item = items[0];
531  }
532  else
533  item = [self selectedItem];
534 
535  [super setImage:[item image]];
536  [super setTitle:[item title]];
537 }
538 
539 - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)aContext
540 {
541  var pullsDown = [self pullsDown];
542 
543  if (!pullsDown && aKeyPath === @"selectedItem.changeCount" ||
544  pullsDown && (aKeyPath === @"_firstItem" || aKeyPath === @"_firstItem.changeCount"))
546 
547  // FIXME: This is due to a bug in KVO, we should never get it for "menu".
548  if (aKeyPath === @"menu")
549  {
550  aKeyPath = @"menu.items";
551 
552  [changes setObject:CPKeyValueChangeSetting forKey:CPKeyValueChangeKindKey];
553  [changes setObject:[[self menu] itemArray] forKey:CPKeyValueChangeNewKey];
554  }
555 
556  if (aKeyPath === @"menu.items")
557  {
558  var changeKind = [changes objectForKey:CPKeyValueChangeKindKey],
559  indexOfSelectedItem = [self indexOfSelectedItem];
560 
561  if (changeKind === CPKeyValueChangeRemoval)
562  {
563  var index = CPNotFound,
564  indexes = [changes objectForKey:CPKeyValueChangeIndexesKey];
565 
566  if ([indexes containsIndex:0] && [self pullsDown])
567  [self _firstItemDidChange];
568 
569  if (![self pullsDown] && [indexes containsIndex:indexOfSelectedItem])
570  {
571  // If the selected item is removed the first item becomes selected.
572  indexOfSelectedItem = 0;
573  }
574  else
575  {
576  // See whether the index has changed, despite the actual item not changing.
577  while ((index = [indexes indexGreaterThanIndex:index]) !== CPNotFound &&
578  index <= indexOfSelectedItem)
579  --indexOfSelectedItem;
580  }
581 
582  [self selectItemAtIndex:indexOfSelectedItem];
583  }
584 
585  else if (changeKind === CPKeyValueChangeReplacement)
586  {
587  var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey];
588 
589  if (pullsDown && [indexes containsIndex:0] ||
590  !pullsDown && [indexes containsIndex:indexOfSelectedItem])
592  }
593 
594  else
595  {
596  // No matter what, we want to prepare the new items.
597  var newItems = [changes objectForKey:CPKeyValueChangeNewKey];
598 
599  [newItems enumerateObjectsUsingBlock:function(aMenuItem)
600  {
601  var action = [aMenuItem action];
602 
603  if (!action)
604  [aMenuItem setAction:action = @selector(_popUpItemAction:)];
605 
606  if (action === @selector(_popUpItemAction:))
607  [aMenuItem setTarget:self];
608  }];
609 
610  if (changeKind === CPKeyValueChangeSetting)
611  {
612  [self _firstItemDidChange];
613 
614  [self selectItemAtIndex:CPNotFound];
615  [self selectItemAtIndex:MIN([newItems count] - 1, indexOfSelectedItem)];
616  }
617 
618  else //if (changeKind === CPKeyValueChangeInsertion)
619  {
620  var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey];
621 
622  if ([self pullsDown] && [indexes containsIndex:0])
623  {
624  [self _firstItemDidChange];
625 
626  if ([self numberOfItems] > 1)
627  {
628  var index = CPNotFound,
629  originalIndex = 0;
630 
631  while ((index = [indexes indexGreaterThanIndex:index]) !== CPNotFound &&
632  index <= originalIndex)
633  ++originalIndex;
634 
635  [[self itemAtIndex:originalIndex] setHidden:NO];
636  }
637  }
638 
639  if (indexOfSelectedItem < 0)
640  [self selectItemAtIndex:0];
641 
642  else
643  {
644  var index = CPNotFound;
645 
646  // See whether the index has changed, despite the actual item not changing.
647  while ((index = [indexes indexGreaterThanIndex:index]) !== CPNotFound &&
648  index <= indexOfSelectedItem)
649  ++indexOfSelectedItem;
650 
651  [self selectItemAtIndex:indexOfSelectedItem];
652  }
653  }
654  }
655  }
656 
657 // [super observeValueForKeyPath:aKeyPath ofObject:anObject change:changes context:aContext];
658 }
659 
660 - (void)mouseDown:(CPEvent)anEvent
661 {
662  if (![self isEnabled] || ![self numberOfItems])
663  return;
664 
665  var menu = [self menu];
666 
667  // Don't reopen the menu based on the same click which caused it to close, e.g. a click on this button.
668  if (menu._lastCloseEvent === anEvent)
669  return;
670 
671  [self highlight:YES];
672 
673  var bounds = [self bounds],
674  minimumWidth = CGRectGetWidth(bounds);
675 
676  // FIXME: setFont: should set the font on the menu.
677  [menu setFont:[self font]];
678 
679  if ([self pullsDown])
680  {
681  var positionedItem = nil,
682  location = CGPointMake(0.0, CGRectGetMaxY(bounds) - 1);
683  }
684  else
685  {
686  var contentRect = [self contentRectForBounds:bounds],
687  positionedItem = [self selectedItem],
688  standardLeftMargin = [_CPMenuWindow _standardLeftMargin] + [_CPMenuItemStandardView _standardLeftMargin],
689  location = CGPointMake(CGRectGetMinX(contentRect) - standardLeftMargin, 0.0);
690 
691  minimumWidth += standardLeftMargin;
692 
693  // To ensure the selected item is highlighted correctly, unset the highlighted item
694  [menu _highlightItemAtIndex:CPNotFound];
695  }
696 
697  [menu setMinimumWidth:minimumWidth];
698 
699  [menu
700  _popUpMenuPositioningItem:positionedItem
701  atLocation:location
702  topY:CGRectGetMinY(bounds)
703  bottomY:CGRectGetMaxY(bounds)
704  inView:self
705  callback:function(aMenu)
706  {
707  [self highlight:NO];
708 
709  var highlightedItem = [aMenu highlightedItem];
710 
711  if ([highlightedItem _isSelectable])
712  [self selectItem:highlightedItem];
713  }];
714 /*
715  else
716  {
717  // This is confusing, I KNOW, so let me explain it to you.
718  // We want the *content* of the selected menu item to overlap the *content* of our pop up.
719  // 1. So calculate where our content is, then calculate where the menu item is.
720  // 2. Move LEFT by whatever indentation we have (offsetWidths, aka, window margin, item margin, etc).
721  // 3. MOVE UP by the difference in sizes of the content and menu item, this will only work if the content is vertically centered.
722  var contentRect = [self convertRect:[self contentRectForBounds:bounds] toView:nil],
723  menuOrigin = [theWindow convertBaseToGlobal:contentRect.origin],
724  menuItemRect = [menuWindow rectForItemAtIndex:_selectedIndex];
725 
726  menuOrigin.x -= CGRectGetMinX(menuItemRect) + [menuWindow overlapOffsetWidth] + [[[menu itemAtIndex:_selectedIndex] _menuItemView] overlapOffsetWidth];
727  menuOrigin.y -= CGRectGetMinY(menuItemRect) + (CGRectGetHeight(menuItemRect) - CGRectGetHeight(contentRect)) / 2.0;
728  }
729 */
730 }
731 
732 - (void)rightMouseDown:(CPEvent)anEvent
733 {
734  // Disable standard CPView behavior which incorrectly displays the menu as a 'context menu'.
735 }
736 
737 - (void)_popUpItemAction:(id)aSender
738 {
739  [self sendAction:[self action] to:[self target]];
740 }
741 
742 - (void)_firstItemDidChange
743 {
744  [self willChangeValueForKey:@"_firstItem"];
745  [self didChangeValueForKey:@"_firstItem"];
746 
747  [[self _firstItem] setHidden:YES];
748 }
749 
750 - (CPMenuItem)_firstItem
751 {
752  if ([self numberOfItems] <= 0)
753  return nil;
754 
755  return [[self menu] itemAtIndex:0];
756 }
757 
758 - (void)takeValueFromKeyPath:(CPString)aKeyPath ofObjects:(CPArray)objects
759 {
760  var count = objects.length,
761  value = [objects[0] valueForKeyPath:aKeyPath];
762 
763  [self selectItemWithTag:value];
764  [self setEnabled:YES];
765 
766  while (count-- > 1)
767  if (value !== [objects[count] valueForKeyPath:aKeyPath])
768  [[self selectedItem] setState:CPOffState];
769 }
770 
771 @end
772 
774 
775 + (Class)_binderClassForBinding:(CPString)aBinding
776 {
777  if (aBinding == CPSelectedIndexBinding ||
778  aBinding == CPSelectedObjectBinding ||
779  aBinding == CPSelectedTagBinding ||
780  aBinding == CPSelectedValueBinding ||
781  aBinding == CPContentBinding ||
782  aBinding == CPContentObjectsBinding ||
783  aBinding == CPContentValuesBinding)
784  {
785  var capitalizedBinding = aBinding.charAt(0).toUpperCase() + aBinding.substr(1);
786 
787  return [CPClassFromString(@"_CPPopUpButton" + capitalizedBinding + "Binder") class];
788  }
789 
790  return [super _binderClassForBinding:aBinding];
791 }
792 
793 + (BOOL)isBindingExclusive:(CPString)aBinding
794 {
795  return (aBinding == CPSelectedIndexBinding ||
796  aBinding == CPSelectedTagBinding ||
797  aBinding == CPSelectedValueBinding);
798 }
799 
800 - (void)_reverseSetBinding
801 {
802  [_CPPopUpButtonSelectionBinder _reverseSetValueFromExclusiveBinderForObject:self];
803 
804  [super _reverseSetBinding];
805 }
806 
807 @end
808 @implementation _CPPopUpButtonContentBinder : CPBinder
809 {
810  id __doxygen__;
811 }
812 
813 - (CPInteger)_getInsertNullOffset
814 {
815  var options = [_info objectForKey:CPOptionsKey];
816 
817  return [options objectForKey:CPInsertsNullPlaceholderBindingOption] ? 1 : 0;
818 }
819 
820 - (CPString)_getNullPlaceholder
821 {
822  var options = [_info objectForKey:CPOptionsKey],
823  placeholder = [options objectForKey:CPNullPlaceholderBindingOption] || @"";
824 
825  if (placeholder === [CPNull null])
826  placeholder = @"";
827 
828  return placeholder;
829 }
830 
831 - (id)transformValue:(CPArray)contentArray withOptions:(CPDictionary)options
832 {
833  // Desactivate the full array transformation forced by super because we don't want this. We want individual transformations (see below).
834  return contentArray;
835 }
836 
837 - (void)setValue:(CPArray)contentArray forBinding:(CPString)aBinding
838 {
839  [self _setContent:contentArray];
840  [self _setContentValuesIfNeeded:contentArray];
841 }
842 
843 - (id)valueForBinding:(CPString)aBinding
844 {
845  return [self _content];
846 }
847 
848 - (void)_setContent:(CPArray)aValue
849 {
850  var count = [aValue count],
851  options = [_info objectForKey:CPOptionsKey],
852  offset = [self _getInsertNullOffset],
853  selectedBindingInfo = [_source infoForBinding:CPSelectedObjectBinding],
854  selectedObject = nil;
855 
856  if (selectedBindingInfo)
857  {
858  var destination = [selectedBindingInfo objectForKey:CPObservedObjectKey],
859  keyPath = [selectedBindingInfo objectForKey:CPObservedKeyPathKey];
860 
861  selectedObject = [destination valueForKeyPath:keyPath];
862  }
863 
864  if (count + offset != [_source numberOfItems])
865  {
866  [_source removeAllItems];
867 
868  if (offset)
869  [_source addItemWithTitle:[self _getNullPlaceholder]];
870 
871  for (var i = 0; i < count; i++)
872  {
873  var item = [[CPMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:nil],
874  itemValue = [aValue objectAtIndex:i];
875 
876  [self _setValue:itemValue forItem:item withOptions:options];
877  [_source addItem:item];
878 
879  // Select this item if it is the one selected by the selected object binding
880  // This is needed if the selected object binding is set before the items
881  // from the content binding
882  if (itemValue === selectedObject)
883  {
884  [_source setSelectedIndex:[_source numberOfItems] - 1];
885  }
886  }
887  }
888  else
889  {
890  for (var i = 0; i < count; i++)
891  {
892  [self _setValue:[aValue objectAtIndex:i] forItem:[_source itemAtIndex:i + offset] withOptions:options];
893  }
894  }
895 }
896 
897 - (void)_setContentValuesIfNeeded:(CPArray)values
898 {
899  var offset = [self _getInsertNullOffset];
900 
901  if (![_source infoForBinding:CPContentValuesBinding])
902  {
903  if (offset)
904  [[_source itemAtIndex:0] setTitle:[self _getNullPlaceholder]];
905 
906  var count = [values count];
907 
908  for (var i = 0; i < count; i++)
909  [[_source itemAtIndex:i + offset] setTitle:[[values objectAtIndex:i] description]];
910  }
911 }
912 
913 - (void)_setValue:(id)aValue forItem:(CPMenuItem)aMenuItem withOptions:(CPDictionary)options
914 {
915  var value = [self _transformValue:aValue withOptions:options];
916  [aMenuItem setRepresentedObject:value];
917 }
918 
919 - (id)_transformValue:(id)aValue withOptions:(CPDictionary)options
920 {
921  return [super transformValue:aValue withOptions:options];
922 }
923 
924 - (CPArray)_content
925 {
926  return [_source valueForKeyPath:@"itemArray.representedObject"];
927 }
928 
929 @end
930 @implementation _CPPopUpButtonContentValuesBinder : _CPPopUpButtonContentBinder
931 {
932  id __doxygen__;
933 }
934 
935 - (void)setValue:(CPArray)aValue forBinding:(CPString)aBinding
936 {
937  [super _setContent:aValue];
938 }
939 
940 - (void)_setValue:(id)aValue forItem:(CPMenuItem)aMenuItem withOptions:(CPDictionary)options
941 {
942  if (aValue === [CPNull null])
943  aValue = nil;
944 
945  var value = [self _transformValue:aValue withOptions:options];
946  [aMenuItem setTitle:value];
947 }
948 
949 - (CPArray)_content
950 {
951  return [_source valueForKeyPath:@"itemArray.title"];
952 }
953 
954 @end
955 @implementation _CPPopUpButtonSelectionBinder : CPBinder
956 {
957  id __doxygen__;
958 }
959 
960 - (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
961 {
962  [self setValue:aValue forBinding:aBinding];
963 }
964 
965 - (CPInteger)_getInsertNullOffset
966 {
967  var options = [[CPBinder infoForBinding:CPContentBinding forObject:_source] objectForKey:CPOptionsKey];
968 
969  return [options objectForKey:CPInsertsNullPlaceholderBindingOption] ? 1 : 0;
970 }
971 
972 @end
973 @implementation _CPPopUpButtonSelectedIndexBinder : _CPPopUpButtonSelectionBinder
974 {
975  id __doxygen__;
976 }
977 
978 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
979 {
980  [_source selectItemAtIndex:aValue + [self _getInsertNullOffset]];
981 }
982 
983 - (id)valueForBinding:(CPString)aBinding
984 {
985  return [_source indexOfSelectedItem] - [self _getInsertNullOffset];
986 }
987 
988 @end
989 @implementation _CPPopUpButtonSelectedObjectBinder : _CPPopUpButtonSelectionBinder
990 {
991  id __doxygen__;
992 }
993 
994 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
995 {
996  var index = [_source indexOfItemWithRepresentedObject:aValue],
997  offset = [self _getInsertNullOffset];
998 
999  // If the content binding has the option CPNullPlaceholderBindingOption and the object to select is nil, select the first item (i.e., the placeholder).
1000  // Other cases to consider:
1001  // 1. no binding:
1002  // 1.1 there's no item with a represented object matching the object to select.
1003  // 1.2 the object to select is nil/CPNull
1004  // 2. there's a binding:
1005  // 2.1 there's a CPNullPlaceholderBindingOption:
1006  // 2.1.1 there's no item with a represented object matching the object to select?
1007  // 2.1.2 the object to select is nil/CPNull
1008  // 2.2 there's no CPNullPlaceholderBindingOption:
1009  // 2.2.1 there's no item with a represented object matching the object to select?
1010  // 2.2.2 the object to select is nil/CPNull
1011  // More cases? Behaviour that depends on array controller settings?
1012 
1013  if (offset === 1 && index === CPNotFound)
1014  index = 0;
1015 
1016  [_source selectItemAtIndex:index];
1017 }
1018 
1019 - (id)valueForBinding:(CPString)aBinding
1020 {
1021  return [[_source selectedItem] representedObject];
1022 }
1023 
1024 @end
1025 @implementation _CPPopUpButtonSelectedTagBinder : _CPPopUpButtonSelectionBinder
1026 {
1027  id __doxygen__;
1028 }
1029 
1030 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
1031 {
1032  [_source selectItemWithTag:aValue];
1033 }
1034 
1035 - (id)valueForBinding:(CPString)aBinding
1036 {
1037  return [[_source selectedItem] tag];
1038 }
1039 
1040 @end
1041 @implementation _CPPopUpButtonSelectedValueBinder : _CPPopUpButtonSelectionBinder
1042 {
1043  id __doxygen__;
1044 }
1045 
1046 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
1047 {
1048  [_source selectItemWithTitle:aValue];
1049 }
1050 
1051 - (id)valueForBinding:(CPString)aBinding
1052 {
1053  return [_source titleOfSelectedItem];
1054 }
1055 
1056 @end
1057 
1058 var DEPRECATED_CPPopUpButtonMenuKey = @"CPPopUpButtonMenuKey",
1059  DEPRECATED_CPPopUpButtonSelectedIndexKey = @"CPPopUpButtonSelectedIndexKey";
1060 
1069 - (id)initWithCoder:(CPCoder)aCoder
1070 {
1071  self = [super initWithCoder:aCoder];
1072 
1073  if (self)
1074  {
1075  // FIXME: (or not?) _title is nulled in - [CPButton initWithCoder:],
1076  // so we need to do this again.
1078 
1079  // FIXME: Remove deprecation leniency for 1.0
1080  if ([aCoder containsValueForKey:DEPRECATED_CPPopUpButtonMenuKey])
1081  {
1082  CPLog.warn(self + " was encoded with an older version of Cappuccino. Please nib2cib the original nib again or open and re-save in Atlas.");
1083 
1084  [self setMenu:[aCoder decodeObjectForKey:DEPRECATED_CPPopUpButtonMenuKey]];
1085  [self setObjectValue:[aCoder decodeObjectForKey:DEPRECATED_CPPopUpButtonSelectedIndexKey]];
1086  }
1087 
1088  var options = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld;/* | CPKeyValueObservingOptionInitial */
1089 
1090  [self addObserver:self forKeyPath:@"menu.items" options:options context:nil];
1091  [self addObserver:self forKeyPath:@"_firstItem.changeCount" options:options context:nil];
1092  [self addObserver:self forKeyPath:@"selectedItem.changeCount" options:options context:nil];
1093  }
1094 
1095  return self;
1096 }
1097 
1098 @end
Definition: CPMenu.h:2
BOOL setThemeState:(ThemeState aState)
Definition: CPView.j:3255
CPDictionary infoForBinding:forObject:(CPString aBinding, [forObject] id anObject)
An object representation of nil.
Definition: CPNull.h:2
var DEPRECATED_CPPopUpButtonSelectedIndexKey
CPArray itemArray()
CPPopUpButtonStatePullsDown
Definition: CPPopUpButton.j:24
void selectItemAtIndex:(CPUInteger anIndex)
CPMaxYEdge
Definition: CPGeometry.j:28
id initWithFrame:(CGRect aFrame)
Definition: CPButton.j:161
CPFont font()
Definition: CPControl.j:899
int indexOfItemWithTag:(int aTag)
id initWithTitle:(CPString aTitle)
Definition: CPMenu.j:257
CPKeyValueChangeReplacement
CGRect bounds()
Definition: CPView.j:1326
void addItemWithTitle:(CPString aTitle)
void setEnabled:(BOOL isEnabled)
Definition: CPControl.j:959
SEL action()
Definition: CPMenuItem.j:246
void setValue:forThemeAttribute:(id aValue, [forThemeAttribute] CPString aName)
Definition: CPView.j:3384
CPKeyValueChangeRemoval
void setImage:(CPImage anImage)
Definition: CPButton.j:372
A mutable key-value pair collection.
Definition: CPDictionary.h:2
CGRect contentRectForBounds:(CGRect bounds)
Definition: CPButton.j:609
CGRect bounds()
Definition: CALayer.j:203
id initWithFrame:pullsDown:(CGRect aFrame, [pullsDown] BOOL shouldPullDown)
Definition: CPPopUpButton.j:63
CPKeyValueChangeSetting
var DEPRECATED_CPPopUpButtonMenuKey
An immutable string (collection of characters).
Definition: CPString.h:2
CPMenuItem selectedItem()
id objectForKey:(id aKey)
Definition: CPDictionary.j:515
Definition: CPImage.h:2
void setPullsDown:(BOOL shouldPullDown)
int indexOfItemWithTitle:(CPString aTitle)
void removeItemAtIndex:(int anIndex)
void setTitle:(CPString aTitle)
Definition: CPButton.j:335
int length()
Definition: CPString.j:186
CPKeyValueObservingOptionNew
int indexOfItem:(CPMenuItem aMenuItem)
void setHidden:(BOOL isHidden)
Definition: CPMenuItem.j:182
void highlight:(BOOL shouldHighlight)
Definition: CPControl.j:980
void setState:(int aState)
Definition: CPMenuItem.j:346
void setRepresentedObject:(id anObject)
Definition: CPMenuItem.j:771
void setObjectValue:(id anIndex)
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
CPNotFound
Definition: CPObjJRuntime.j:62
BOOL unsetThemeState:(ThemeState aState)
Definition: CPView.j:3268
CPMenuItem itemAtIndex:(CPUInteger anIndex)
void setAction:(SEL anAction)
Definition: CPMenuItem.j:238
void synchronizeTitleAndSelectedItem()
id initWithTitle:action:keyEquivalent:(CPString aTitle, [action] SEL anAction, [keyEquivalent] CPString aKeyEquivalent)
Definition: CPMenuItem.j:121
id initWithCoder:(CPCoder aCoder)
Definition: CPButton.j:946
void setTarget:(id aTarget)
Definition: CPMenuItem.j:221
void selectItemWithTag:(int aTag)
int indexOfSelectedItem()
Definition: CPEvent.h:2
void selectItem:(CPMenuItem aMenuItem)
CPString title()
Definition: CPMenuItem.j:273
void setObject:forKey:(id anObject, [forKey] id aKey)
Definition: CPDictionary.j:589
CPMenu menu
id alloc()
Definition: CPObject.j:130
CPKeyValueObservingOptionOld
void setTitle:(CPString aTitle)
Definition: CPMenuItem.j:256