API  0.9.10
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 
38 {
39  return "popup-button";
40 }
41 
43 {
44  return [CPSet setWithObject:@"objectValue"];
45 }
46 
48 {
49  return [CPSet setWithObject:@"objectValue"];
50 }
51 
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 
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
216 {
218 
219  if (indexOfSelectedItem < 0 || indexOfSelectedItem > [self numberOfItems] - 1)
220  return nil;
221 
222  return [[self menu] itemAtIndex:indexOfSelectedItem];
223 }
224 
229 {
230  return [[self selectedItem] title];
231 }
232 
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 
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 
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 
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  [self addItemWithTitle:aTitle];
489 
490  index = [self numberOfItems] - 1;
491  }
492 
493  [self selectItemAtIndex:index];
494  }
495 }
496 
497 // Setting the Image
503 - (void)setImage:(CPImage)anImage
504 {
505  // The Image is set by the currently selected item.
506 }
507 
508 // Setting the State
514 {
515  var item = nil;
516 
517  if ([self pullsDown])
518  {
519  var items = [[self menu] itemArray];
520 
521  if ([items count] > 0)
522  item = items[0];
523  }
524  else
525  item = [self selectedItem];
526 
527  [super setImage:[item image]];
528  [super setTitle:[item title]];
529 }
530 
531 - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)aContext
532 {
533  var pullsDown = [self pullsDown];
534 
535  if (!pullsDown && aKeyPath === @"selectedItem.changeCount" ||
536  pullsDown && (aKeyPath === @"_firstItem" || aKeyPath === @"_firstItem.changeCount"))
538 
539  // FIXME: This is due to a bug in KVO, we should never get it for "menu".
540  if (aKeyPath === @"menu")
541  {
542  aKeyPath = @"menu.items";
543 
544  [changes setObject:CPKeyValueChangeSetting forKey:CPKeyValueChangeKindKey];
545  [changes setObject:[[self menu] itemArray] forKey:CPKeyValueChangeNewKey];
546  }
547 
548  if (aKeyPath === @"menu.items")
549  {
550  var changeKind = [changes objectForKey:CPKeyValueChangeKindKey],
552 
553  if (changeKind === CPKeyValueChangeRemoval)
554  {
555  var index = CPNotFound,
556  indexes = [changes objectForKey:CPKeyValueChangeIndexesKey];
557 
558  if ([indexes containsIndex:0] && [self pullsDown])
559  [self _firstItemDidChange];
560 
561  if (![self pullsDown] && [indexes containsIndex:indexOfSelectedItem])
562  {
563  // If the selected item is removed the first item becomes selected.
564  indexOfSelectedItem = 0;
565  }
566  else
567  {
568  // See whether the index has changed, despite the actual item not changing.
569  while ((index = [indexes indexGreaterThanIndex:index]) !== CPNotFound &&
570  index <= indexOfSelectedItem)
572  }
573 
574  [self selectItemAtIndex:indexOfSelectedItem];
575  }
576 
577  else if (changeKind === CPKeyValueChangeReplacement)
578  {
579  var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey];
580 
581  if (pullsDown && [indexes containsIndex:0] ||
582  !pullsDown && [indexes containsIndex:indexOfSelectedItem])
584  }
585 
586  else
587  {
588  // No matter what, we want to prepare the new items.
589  var newItems = [changes objectForKey:CPKeyValueChangeNewKey];
590 
591  [newItems enumerateObjectsUsingBlock:function(aMenuItem)
592  {
593  var action = [aMenuItem action];
594 
595  if (!action)
596  [aMenuItem setAction:action = @selector(_popUpItemAction:)];
597 
598  if (action === @selector(_popUpItemAction:))
599  [aMenuItem setTarget:self];
600  }];
601 
602  if (changeKind === CPKeyValueChangeSetting)
603  {
604  [self _firstItemDidChange];
605 
606  [self selectItemAtIndex:CPNotFound];
607  [self selectItemAtIndex:MIN([newItems count] - 1, indexOfSelectedItem)];
608  }
609 
610  else //if (changeKind === CPKeyValueChangeInsertion)
611  {
612  var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey];
613 
614  if ([self pullsDown] && [indexes containsIndex:0])
615  {
616  [self _firstItemDidChange];
617 
618  if ([self numberOfItems] > 1)
619  {
620  var index = CPNotFound,
621  originalIndex = 0;
622 
623  while ((index = [indexes indexGreaterThanIndex:index]) !== CPNotFound &&
624  index <= originalIndex)
625  ++originalIndex;
626 
627  [[self itemAtIndex:originalIndex] setHidden:NO];
628  }
629  }
630 
631  if (indexOfSelectedItem < 0)
632  [self selectItemAtIndex:0];
633 
634  else
635  {
636  var index = CPNotFound;
637 
638  // See whether the index has changed, despite the actual item not changing.
639  while ((index = [indexes indexGreaterThanIndex:index]) !== CPNotFound &&
640  index <= indexOfSelectedItem)
642 
643  [self selectItemAtIndex:indexOfSelectedItem];
644  }
645  }
646  }
647  }
648 
649 // [super observeValueForKeyPath:aKeyPath ofObject:anObject change:changes context:aContext];
650 }
651 
652 - (void)mouseDown:(CPEvent)anEvent
653 {
654  if (![self isEnabled] || ![self numberOfItems])
655  return;
656 
657  var menu = [self menu];
658 
659  // Don't reopen the menu based on the same click which caused it to close, e.g. a click on this button.
660  if (menu._lastCloseEvent === anEvent)
661  return;
662 
663  [self highlight:YES];
664 
665  var bounds = [self bounds],
666  minimumWidth = CGRectGetWidth(bounds);
667 
668  // FIXME: setFont: should set the font on the menu.
669  [menu setFont:[self font]];
670 
671  if ([self pullsDown])
672  {
673  var positionedItem = nil,
674  location = CGPointMake(0.0, CGRectGetMaxY(bounds) - 1);
675  }
676  else
677  {
678  var contentRect = [self contentRectForBounds:bounds],
679  positionedItem = [self selectedItem],
680  standardLeftMargin = [_CPMenuWindow _standardLeftMargin] + [_CPMenuItemStandardView _standardLeftMargin],
681  location = CGPointMake(CGRectGetMinX(contentRect) - standardLeftMargin, 0.0);
682 
683  minimumWidth += standardLeftMargin;
684 
685  // To ensure the selected item is highlighted correctly, unset the highlighted item
686  [menu _highlightItemAtIndex:CPNotFound];
687  }
688 
689  [menu setMinimumWidth:minimumWidth];
690 
691  [menu
692  _popUpMenuPositioningItem:positionedItem
693  atLocation:location
694  topY:CGRectGetMinY(bounds)
695  bottomY:CGRectGetMaxY(bounds)
696  inView:self
697  callback:function(aMenu)
698  {
699  [self highlight:NO];
700 
701  var highlightedItem = [aMenu highlightedItem];
702 
703  if ([highlightedItem _isSelectable])
704  [self selectItem:highlightedItem];
705  }];
706 /*
707  else
708  {
709  // This is confusing, I KNOW, so let me explain it to you.
710  // We want the *content* of the selected menu item to overlap the *content* of our pop up.
711  // 1. So calculate where our content is, then calculate where the menu item is.
712  // 2. Move LEFT by whatever indentation we have (offsetWidths, aka, window margin, item margin, etc).
713  // 3. MOVE UP by the difference in sizes of the content and menu item, this will only work if the content is vertically centered.
714  var contentRect = [self convertRect:[self contentRectForBounds:bounds] toView:nil],
715  menuOrigin = [theWindow convertBaseToGlobal:contentRect.origin],
716  menuItemRect = [menuWindow rectForItemAtIndex:_selectedIndex];
717 
718  menuOrigin.x -= CGRectGetMinX(menuItemRect) + [menuWindow overlapOffsetWidth] + [[[menu itemAtIndex:_selectedIndex] _menuItemView] overlapOffsetWidth];
719  menuOrigin.y -= CGRectGetMinY(menuItemRect) + (CGRectGetHeight(menuItemRect) - CGRectGetHeight(contentRect)) / 2.0;
720  }
721 */
722 }
723 
724 - (void)rightMouseDown:(CPEvent)anEvent
725 {
726  // Disable standard CPView behavior which incorrectly displays the menu as a 'context menu'.
727 }
728 
729 - (void)_popUpItemAction:(id)aSender
730 {
731  [self sendAction:[self action] to:[self target]];
732 }
733 
734 - (void)_firstItemDidChange
735 {
736  [self willChangeValueForKey:@"_firstItem"];
737  [self didChangeValueForKey:@"_firstItem"];
738 
739  [[self _firstItem] setHidden:YES];
740 }
741 
742 - (CPMenuItem)_firstItem
743 {
744  if ([self numberOfItems] <= 0)
745  return nil;
746 
747  return [[self menu] itemAtIndex:0];
748 }
749 
750 - (void)takeValueFromKeyPath:(CPString)aKeyPath ofObjects:(CPArray)objects
751 {
752  var count = objects.length,
753  value = [objects[0] valueForKeyPath:aKeyPath];
754 
755  [self selectItemWithTag:value];
756  [self setEnabled:YES];
757 
758  while (count-- > 1)
759  if (value !== [objects[count] valueForKeyPath:aKeyPath])
760  [[self selectedItem] setState:CPOffState];
761 }
762 
763 @end
764 
766 
767 + (Class)_binderClassForBinding:(CPString)aBinding
768 {
769  if (aBinding == CPSelectedIndexBinding ||
770  aBinding == CPSelectedObjectBinding ||
771  aBinding == CPSelectedTagBinding ||
772  aBinding == CPSelectedValueBinding ||
773  aBinding == CPContentBinding ||
774  aBinding == CPContentObjectsBinding ||
775  aBinding == CPContentValuesBinding)
776  {
777  var capitalizedBinding = aBinding.charAt(0).toUpperCase() + aBinding.substr(1);
778 
779  return [CPClassFromString(@"_CPPopUpButton" + capitalizedBinding + "Binder") class];
780  }
781 
782  return [super _binderClassForBinding:aBinding];
783 }
784 
785 + (BOOL)isBindingExclusive:(CPString)aBinding
786 {
787  return (aBinding == CPSelectedIndexBinding ||
788  aBinding == CPSelectedTagBinding ||
789  aBinding == CPSelectedValueBinding);
790 }
791 
792 - (void)_reverseSetBinding
793 {
794  [_CPPopUpButtonSelectionBinder _reverseSetValueFromExclusiveBinderForObject:self];
795 
796  [super _reverseSetBinding];
797 }
798 
799 @end
800 @implementation _CPPopUpButtonContentBinder : CPBinder
801 {
802  id __doxygen__;
803 }
804 
805 - (CPInteger)_getInsertNullOffset
806 {
807  var options = [_info objectForKey:CPOptionsKey];
808 
809  return [options objectForKey:CPInsertsNullPlaceholderBindingOption] ? 1 : 0;
810 }
811 
812 - (CPString)_getNullPlaceholder
813 {
814  var options = [_info objectForKey:CPOptionsKey],
815  placeholder = [options objectForKey:CPNullPlaceholderBindingOption] || @"";
816 
817  if (placeholder === [CPNull null])
818  placeholder = @"";
819 
820  return placeholder;
821 }
822 
823 - (id)transformValue:(CPArray)contentArray withOptions:(CPDictionary)options
824 {
825  // Desactivate the full array transformation forced by super because we don't want this. We want individual transformations (see below).
826  return contentArray;
827 }
828 
829 - (void)setValue:(CPArray)contentArray forBinding:(CPString)aBinding
830 {
831  [self _setContent:contentArray];
832  [self _setContentValuesIfNeeded:contentArray];
833 }
834 
835 - (id)valueForBinding:(CPString)aBinding
836 {
837  return [self _content];
838 }
839 
840 - (void)_setContent:(CPArray)aValue
841 {
842  var count = [aValue count],
843  options = [_info objectForKey:CPOptionsKey],
844  offset = [self _getInsertNullOffset],
845  selectedBindingInfo = [_source infoForBinding:CPSelectedObjectBinding],
846  selectedObject = nil;
847 
848  if (selectedBindingInfo)
849  {
850  var destination = [selectedBindingInfo objectForKey:CPObservedObjectKey],
851  keyPath = [selectedBindingInfo objectForKey:CPObservedKeyPathKey];
852 
853  selectedObject = [destination valueForKeyPath:keyPath];
854  }
855 
856  if (count + offset != [_source numberOfItems])
857  {
858  [_source removeAllItems];
859 
860  if (offset)
861  [_source addItemWithTitle:[self _getNullPlaceholder]];
862 
863  for (var i = 0; i < count; i++)
864  {
865  var item = [[CPMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:nil],
866  itemValue = [aValue objectAtIndex:i];
867 
868  [self _setValue:itemValue forItem:item withOptions:options];
869  [_source addItem:item];
870 
871  // Select this item if it is the one selected by the selected object binding
872  // This is needed if the selected object binding is set before the items
873  // from the content binding
874  if (itemValue === selectedObject)
875  {
876  [_source setSelectedIndex:[_source numberOfItems] - 1];
877  }
878  }
879  }
880  else
881  {
882  for (var i = 0; i < count; i++)
883  {
884  [self _setValue:[aValue objectAtIndex:i] forItem:[_source itemAtIndex:i + offset] withOptions:options];
885  }
886  }
887 }
888 
889 - (void)_setContentValuesIfNeeded:(CPArray)values
890 {
891  var offset = [self _getInsertNullOffset];
892 
893  if (![_source infoForBinding:CPContentValuesBinding])
894  {
895  if (offset)
896  [[_source itemAtIndex:0] setTitle:[self _getNullPlaceholder]];
897 
898  var count = [values count];
899 
900  for (var i = 0; i < count; i++)
901  [[_source itemAtIndex:i + offset] setTitle:[[values objectAtIndex:i] description]];
902  }
903 }
904 
905 - (void)_setValue:(id)aValue forItem:(CPMenuItem)aMenuItem withOptions:(CPDictionary)options
906 {
907  var value = [self _transformValue:aValue withOptions:options];
908  [aMenuItem setRepresentedObject:value];
909 }
910 
911 - (id)_transformValue:(id)aValue withOptions:(CPDictionary)options
912 {
913  return [super transformValue:aValue withOptions:options];
914 }
915 
916 - (CPArray)_content
917 {
918  return [_source valueForKeyPath:@"itemArray.representedObject"];
919 }
920 
921 @end
922 @implementation _CPPopUpButtonContentValuesBinder : _CPPopUpButtonContentBinder
923 {
924  id __doxygen__;
925 }
926 
927 - (void)setValue:(CPArray)aValue forBinding:(CPString)aBinding
928 {
929  [super _setContent:aValue];
930 }
931 
932 - (void)_setValue:(id)aValue forItem:(CPMenuItem)aMenuItem withOptions:(CPDictionary)options
933 {
934  if (aValue === [CPNull null])
935  aValue = nil;
936 
937  var value = [self _transformValue:aValue withOptions:options];
938  [aMenuItem setTitle:value];
939 }
940 
941 - (CPArray)_content
942 {
943  return [_source valueForKeyPath:@"itemArray.title"];
944 }
945 
946 @end
947 @implementation _CPPopUpButtonSelectionBinder : CPBinder
948 {
949  id __doxygen__;
950 }
951 
952 - (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
953 {
954  [self setValue:aValue forBinding:aBinding];
955 }
956 
957 - (CPInteger)_getInsertNullOffset
958 {
959  var options = [[CPBinder infoForBinding:CPContentBinding forObject:_source] objectForKey:CPOptionsKey];
960 
961  return [options objectForKey:CPInsertsNullPlaceholderBindingOption] ? 1 : 0;
962 }
963 
964 @end
965 @implementation _CPPopUpButtonSelectedIndexBinder : _CPPopUpButtonSelectionBinder
966 {
967  id __doxygen__;
968 }
969 
970 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
971 {
972  [_source selectItemAtIndex:aValue + [self _getInsertNullOffset]];
973 }
974 
975 - (id)valueForBinding:(CPString)aBinding
976 {
977  return [_source indexOfSelectedItem] - [self _getInsertNullOffset];
978 }
979 
980 @end
981 @implementation _CPPopUpButtonSelectedObjectBinder : _CPPopUpButtonSelectionBinder
982 {
983  id __doxygen__;
984 }
985 
986 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
987 {
988  var index = [_source indexOfItemWithRepresentedObject:aValue],
989  offset = [self _getInsertNullOffset];
990 
991  // If the content binding has the option CPNullPlaceholderBindingOption and the object to select is nil, select the first item (i.e., the placeholder).
992  // Other cases to consider:
993  // 1. no binding:
994  // 1.1 there's no item with a represented object matching the object to select.
995  // 1.2 the object to select is nil/CPNull
996  // 2. there's a binding:
997  // 2.1 there's a CPNullPlaceholderBindingOption:
998  // 2.1.1 there's no item with a represented object matching the object to select?
999  // 2.1.2 the object to select is nil/CPNull
1000  // 2.2 there's no CPNullPlaceholderBindingOption:
1001  // 2.2.1 there's no item with a represented object matching the object to select?
1002  // 2.2.2 the object to select is nil/CPNull
1003  // More cases? Behaviour that depends on array controller settings?
1004 
1005  if (offset === 1 && index === CPNotFound)
1006  index = 0;
1007 
1008  [_source selectItemAtIndex:index];
1009 }
1010 
1011 - (id)valueForBinding:(CPString)aBinding
1012 {
1013  return [[_source selectedItem] representedObject];
1014 }
1015 
1016 @end
1017 @implementation _CPPopUpButtonSelectedTagBinder : _CPPopUpButtonSelectionBinder
1018 {
1019  id __doxygen__;
1020 }
1021 
1022 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
1023 {
1024  [_source selectItemWithTag:aValue];
1025 }
1026 
1027 - (id)valueForBinding:(CPString)aBinding
1028 {
1029  return [[_source selectedItem] tag];
1030 }
1031 
1032 @end
1033 @implementation _CPPopUpButtonSelectedValueBinder : _CPPopUpButtonSelectionBinder
1034 {
1035  id __doxygen__;
1036 }
1037 
1038 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
1039 {
1040  [_source selectItemWithTitle:aValue];
1041 }
1042 
1043 - (id)valueForBinding:(CPString)aBinding
1044 {
1045  return [_source titleOfSelectedItem];
1046 }
1047 
1048 @end
1049 
1050 var DEPRECATED_CPPopUpButtonMenuKey = @"CPPopUpButtonMenuKey",
1051  DEPRECATED_CPPopUpButtonSelectedIndexKey = @"CPPopUpButtonSelectedIndexKey";
1052 
1061 - (id)initWithCoder:(CPCoder)aCoder
1062 {
1063  self = [super initWithCoder:aCoder];
1064 
1065  if (self)
1066  {
1067  // FIXME: (or not?) _title is nulled in - [CPButton initWithCoder:],
1068  // so we need to do this again.
1070 
1071  // FIXME: Remove deprecation leniency for 1.0
1072  if ([aCoder containsValueForKey:DEPRECATED_CPPopUpButtonMenuKey])
1073  {
1074  CPLog.warn(self + " was encoded with an older version of Cappuccino. Please nib2cib the original nib again or open and re-save in Atlas.");
1075 
1076  [self setMenu:[aCoder decodeObjectForKey:DEPRECATED_CPPopUpButtonMenuKey]];
1077  [self setObjectValue:[aCoder decodeObjectForKey:DEPRECATED_CPPopUpButtonSelectedIndexKey]];
1078  }
1079 
1080  var options = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld;/* | CPKeyValueObservingOptionInitial */
1081 
1082  [self addObserver:self forKeyPath:@"menu.items" options:options context:nil];
1083  [self addObserver:self forKeyPath:@"_firstItem.changeCount" options:options context:nil];
1084  [self addObserver:self forKeyPath:@"selectedItem.changeCount" options:options context:nil];
1085  }
1086 
1087  return self;
1088 }
1089 
1090 @end
Definition: CPMenu.h:2
BOOL isEnabled()
Definition: CPControl.j:970
BOOL setThemeState:(ThemeState aState)
Definition: CPView.j:3214
CPDictionary infoForBinding:forObject:(CPString aBinding, [forObject] id anObject)
CPRectEdge preferredEdge()
An object representation of nil.
Definition: CPNull.h:2
var DEPRECATED_CPPopUpButtonSelectedIndexKey
CPArray itemArray()
CPString title()
Definition: CPButton.j:351
CPPopUpButtonStatePullsDown
Definition: CPPopUpButton.j:24
void selectItemAtIndex:(CPUInteger anIndex)
CPMaxYEdge
Definition: CPGeometry.j:28
CPContentObjectsBinding
id initWithFrame:(CGRect aFrame)
Definition: CPButton.j:161
CPFont font()
Definition: CPControl.j:899
CPMenuItem lastItem()
CPString titleOfSelectedItem()
int indexOfItemWithTag:(int aTag)
id initWithTitle:(CPString aTitle)
Definition: CPMenu.j:257
CPKeyValueChangeReplacement
CGRect bounds()
Definition: CPView.j:1302
void addItemWithTitle:(CPString aTitle)
void setEnabled:(BOOL isEnabled)
Definition: CPControl.j:959
CPContentValuesBinding
CPSet keyPathsForValuesAffectingSelectedTag()
Definition: CPPopUpButton.j:47
SEL action()
Definition: CPMenuItem.j:246
void setValue:forThemeAttribute:(id aValue, [forThemeAttribute] CPString aName)
Definition: CPView.j:3343
CPKeyValueChangeRemoval
CPContentBinding
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
CPSelectedValueBinding
CPUInteger selectedIndex()
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()
CPSet keyPathsForValuesAffectingSelectedIndex()
Definition: CPPopUpButton.j:42
id objectForKey:(id aKey)
Definition: CPDictionary.j:515
Definition: CPImage.h:2
void setPullsDown:(BOOL shouldPullDown)
BOOL sendAction:to:(SEL anAction, [to] id anObject)
Definition: CPControl.j:319
SEL action()
Definition: CPControl.j:290
int indexOfItemWithTitle:(CPString aTitle)
void removeItemAtIndex:(int anIndex)
CPSelectedTagBinding
void setTitle:(CPString aTitle)
Definition: CPButton.j:335
CPArray itemTitles()
int length()
Definition: CPString.j:186
CPKeyValueObservingOptionNew
int indexOfItem:(CPMenuItem aMenuItem)
CPSelectedIndexBinding
void setHidden:(BOOL isHidden)
Definition: CPMenuItem.j:182
id target()
Definition: CPControl.j:308
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:3227
CPMenuItem itemAtIndex:(CPUInteger anIndex)
CPSet keyPathsForValuesAffectingSelectedItem()
Definition: CPPopUpButton.j:52
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)
CPSelectedObjectBinding
CPString title()
Definition: CPMenuItem.j:273
void setObject:forKey:(id anObject, [forKey] id aKey)
Definition: CPDictionary.j:589
CPString defaultThemeClass()
Definition: CPPopUpButton.j:37
CPMenu menu
id alloc()
Definition: CPObject.j:130
CPKeyValueObservingOptionOld
void setTitle:(CPString aTitle)
Definition: CPMenuItem.j:256
FrameUpdater prototype description