1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 sc_require('views/button');
  9 
 10 /**
 11   @class
 12 
 13   SelectView has a functionality similar to that of SelectField
 14 
 15   Clicking the SelectView button displays a menu pane with a
 16   list of items. The selected item will be displayed on the button.
 17   User has the option of enabling checkbox for the selected menu item.
 18 
 19   @extends SC.ButtonView
 20   @version 1.0
 21   @author Alex Iskander, Mohammed Ashik
 22 */
 23 SC.SelectView = SC.ButtonView.extend(
 24 /** @scope SC.SelectView.prototype */ {
 25 
 26   /**
 27     @field
 28     @type Boolean
 29     @default YES
 30   */
 31   acceptsFirstResponder: function () {
 32     return this.get('isEnabledInPane');
 33   }.property('isEnabledInPane'),
 34 
 35   /**
 36     If true, titles will be escaped to avoid scripting attacks.
 37 
 38     @type Boolean
 39     @default YES
 40   */
 41   escapeHTML: YES,
 42   escapeHTMLBindingDefault: SC.Binding.oneWay().bool(),
 43 
 44   /**
 45     An array of items that will be form the menu you want to show.
 46 
 47     @type Array
 48     @default []
 49   */
 50   items: [],
 51 
 52   /** @private */
 53   itemsBindingDefault: SC.Binding.multiple(),
 54 
 55   /**
 56     If you set this to a non-null value, then the name shown for each
 57     menu item will be pulled from the object using the named property.
 58     if this is null, the collection items themselves will be used.
 59 
 60     @type String
 61     @default null
 62   */
 63   itemTitleKey: null,
 64 
 65   /**
 66     If you set this to a non-null value, then the value of this key will
 67     be used to sort the items.  If this is not set, then itemTitleKey will
 68     be used.
 69 
 70     @type String
 71     @default null
 72   */
 73   itemSortKey: null,
 74 
 75   /**
 76      Set this to a non-null value to use a key from the passed set of items
 77      as the value for the options popup.  If you don't set this, then the
 78      items themselves will be used as the value.
 79 
 80      @type String
 81      @default null
 82   */
 83   itemValueKey: null,
 84 
 85   /**
 86      Key used to extract icons from the items array
 87 
 88      @type String
 89      @default null
 90   */
 91   itemIconKey: null,
 92 
 93   /**
 94     Key to use to identify separators.
 95 
 96     @type String
 97     @default "isSeparator"
 98   */
 99   itemSeparatorKey: "isSeparator",
100 
101   /**
102     Key used to indicate if the item is to be enabled.
103 
104     @type String
105     @default "isEnabled"
106   */
107   itemIsEnabledKey: "isEnabled",
108 
109   /**
110     @type Boolean
111     @default YES
112   */
113   localize: YES,
114 
115   /**
116     if true, it means that no sorting will occur, items will appear
117     in the same order as in the array
118 
119     @type Boolean
120     @default YES
121   */
122   disableSort: YES,
123 
124   /**
125     @type Array
126     @default ['sc-select-button']
127     @see SC.View#classNames
128   */
129   classNames: ['sc-select-view'],
130 
131   /**
132     Menu attached to the SelectView.
133 
134     @type SC.View
135     @default SC.MenuView
136   */
137   menu: null,
138 
139   /**
140     List of actual menu items, handed off to the menu view.
141 
142     @property
143     @private
144     @type:{Array}
145   */
146   _itemList: [],
147 
148   /**
149     Property to set the index of the selected menu item. This in turn
150     is used to calculate the preferMatrix.
151 
152     @property
153     @type Number
154     @default null
155     @private
156   */
157   _itemIdx: null,
158 
159   /**
160      Current Value of the SelectView
161 
162      @type Object
163      @default null
164   */
165   value: null,
166 
167   /**
168     if this property is set to 'YES', a checkbox is shown next to the
169     selected menu item.
170 
171     @type Boolean
172     @default YES
173   */
174   checkboxEnabled: YES,
175 
176   /**
177     if this property is set to 'YES', a checkbox is shown next to the
178     selected menu item.
179 
180     @type Boolean
181     @default YES
182   */
183   showCheckbox: YES,
184 
185   /**
186     Set this to non-null to place an empty option at the top of the menu.
187 
188     @property
189     @type String
190     @default null
191   */
192   emptyName: null,
193 
194   /**
195     Default value of the select button.
196      This will be the first item from the menu item list.
197 
198     @private
199   */
200   _defaultVal: null,
201 
202   /**
203     Default title of the select button.
204      This will be the title corresponding to the _defaultVal.
205 
206     @private
207   */
208   _defaultTitle: null,
209 
210   /**
211     Default icon of the select button.
212      This will be the icon corresponding to the _defaultVal.
213 
214     @private
215   */
216   _defaultIcon: null,
217 
218   /**
219     The button theme will be popup
220 
221     @type String
222     @default 'popup'
223     @readOnly
224   */
225   theme: 'popup',
226 
227   /**
228     @type SC.Array
229     @default ['icon', 'value','controlSize']
230     @see SC.View#displayProperties
231   */
232   displayProperties: ['icon', 'value','controlSize', 'escapeHTML', 'emptyName'],
233 
234   /**
235     Prefer matrix to position the select button menu such that the
236     selected item for the menu item will appear aligned to the
237     the button. The value at the second index(0) changes based on the
238     position(index) of the menu item in the menu pane.
239 
240     @type Array
241     @default null
242   */
243   preferMatrix: null,
244 
245   /**
246     Property to set the menu item height. This in turn is used for
247     the calculation of prefMatrix.
248 
249     @type Number
250     @default 20
251   */
252   CUSTOM_MENU_ITEM_HEIGHT: 20,
253 
254   /**
255     Binds the button's selection state to the menu's visibility.
256 
257     @private
258   */
259   isActiveBinding: '*menu.isVisibleInWindow',
260 
261   /**
262     If this property is set to 'YES', the menu pane will be positioned
263     below the anchor.
264 
265     @type Boolean
266     @default NO
267   */
268   isDefaultPosition: NO,
269 
270   /**
271     lastMenuWidth is the width of the last menu which was created from
272     the items of this select button.
273 
274     @private
275   */
276   lastMenuWidth: null,
277 
278   /**
279     Example view used for menu items.
280 
281     @type SC.View
282     @default null
283   */
284   exampleView: null,
285 
286   /**
287     customView menu offset width
288 
289     @type Number
290     @default 0
291   */
292   customViewMenuOffsetWidth: 0,
293 
294   /**
295     This is a property for enabling/disabling ellipsis
296 
297     @type Boolean
298     @default YES
299   */
300   needsEllipsis: YES,
301 
302   /**
303     This property allows you at add extra padding to the height
304     of the menu pane.
305 
306     @type Number
307     @default 0
308   */
309   menuPaneHeightPadding: 0,
310 
311   /**
312     The amount of space to add to the calculated width of the menu item strings to
313     determine the width of the menu pane.
314 
315     @type Number
316     @default 35
317   */
318   menuItemPadding: 35,
319 
320   /**
321     @type Boolean
322     @default NO
323   */
324   isContextMenuEnabled: NO,
325 
326   /**
327     This is a property to enable/disable focus rings in buttons.
328     For select_button, we are making it a default.
329 
330     @type Boolean
331     @default YES
332     @see SC.ButtonView#supportFocusRing
333   */
334   supportFocusRing: YES,
335 
336   /**
337   * @private
338   * Overwritten to calculate the content corresponding to items configured at creation
339   */
340   init: function() {
341     sc_super();
342 
343     this.itemsDidChange();
344   },
345 
346   /**
347     Left Alignment based on the size of the button
348 
349     @private
350   */
351   leftAlign: function () {
352     switch (this.get('controlSize')) {
353       case SC.TINY_CONTROL_SIZE:
354         return SC.SelectView.TINY_OFFSET_X;
355       case SC.SMALL_CONTROL_SIZE:
356         return SC.SelectView.SMALL_OFFSET_X;
357       case SC.REGULAR_CONTROL_SIZE:
358         return SC.SelectView.REGULAR_OFFSET_X;
359       case SC.LARGE_CONTROL_SIZE:
360         return SC.SelectView.LARGE_OFFSET_X;
361       case SC.HUGE_CONTROL_SIZE:
362         return SC.SelectView.HUGE_OFFSET_X;
363     }
364     return 0;
365   }.property('controlSize'),
366 
367   /**
368     override this method to implement your own sorting of the menu. By
369     default, menu items are sorted using the value shown or the sortKey
370 
371     @param {SC.Array} objects the unsorted array of objects to display.
372     @returns {SC.Array} sorted array of objects
373   */
374   sortObjects: function (objects) {
375     if (!this.get('disableSort')){
376       var nameKey = this.get('itemSortKey') || this.get('itemTitleKey');
377       objects = objects.sort(function (a,b) {
378         if (nameKey) {
379           a = a.get ? a.get(nameKey) : a[nameKey];
380           b = b.get ? b.get(nameKey) : b[nameKey];
381         }
382         return (a<b) ? -1 : ((a>b) ? 1 : 0);
383       });
384     }
385     return objects;
386   },
387 
388   /**
389     Observer called whenever the items collection or an element of this collection changes
390 
391     @private
392   */
393   itemsDidChange: function() {
394     var escapeHTML, items, len, nameKey, iconKey, valueKey, separatorKey, showCheckbox,
395       currentSelectedVal, shouldLocalize, isSeparator, itemList, isChecked,
396       idx, name, icon, value, item, itemEnabled, isEnabledKey, emptyName, isSameRecord;
397 
398     items = this.get('items') || [];
399     items = this.sortObjects(items);
400     len = items.length;
401 
402     //Get the nameKey, iconKey and valueKey set by the user
403     nameKey = this.get('itemTitleKey');
404     iconKey = this.get('itemIconKey');
405     valueKey = this.get('itemValueKey');
406     separatorKey = this.get('itemSeparatorKey');
407     showCheckbox = this.get('showCheckbox');
408     isEnabledKey = this.get('itemIsEnabledKey');
409     escapeHTML = this.get('escapeHTML');
410 
411     //get the current selected value
412     currentSelectedVal = this.get('value');
413 
414     // get the localization flag.
415     shouldLocalize = this.get('localize');
416 
417     //itemList array to set the menu items
418     itemList = [];
419 
420     //to set the 'checkbox' property of menu items
421     isChecked = YES;
422 
423     //index for finding the first item in the list
424     idx = 0;
425 
426     // Add the empty name to the list if applicable
427     emptyName = this.get('emptyName');
428 
429     if (!SC.none(emptyName)) {
430       emptyName = shouldLocalize ? SC.String.loc(emptyName) : emptyName;
431       emptyName = escapeHTML ? SC.RenderContext.escapeHTML(emptyName) : emptyName;
432 
433       item = SC.Object.create({
434         isSeparator: NO,
435         title: emptyName,
436         icon: null,
437         value: null,
438         isEnabled: YES,
439         checkbox: NO,
440         target: this,
441         action: 'displaySelectedItem'
442       });
443 
444       if (SC.none(currentSelectedVal)) {
445         this.set('title', emptyName);
446       }
447 
448       //Set the items in the itemList array
449       itemList.push(item);
450     }
451 
452     items.forEach(function (object) {
453       if (object || object === 0) {
454         //@if (debug)
455         // TODO: Remove in 1.11 and 2.0
456         // Help the developer if they were relying on the previously misleading
457         // default value of itemSeparatorKey.  We need to ensure that the change
458         // is backwards compatible with apps prior to 1.10.
459         if (separatorKey === 'isSeparator') {
460           if ((object.get && SC.none(object.get('isSeparator')) && object.get('separator')) || (SC.none(object.isSeparator) && object.separator)) {
461             SC.warn("Developer Warning: the default value of itemSeparatorKey has been changed from 'separator' to 'isSeparator' to match the documentation.  Please update your select item properties to 'isSeparator: YES' to remove this warning.");
462             if (object.set) { object.set('isSeparator', object.get('separator')); }
463             else { object.isSeparator = object.separator; }
464           }
465         }
466         //@endif
467 
468         // get the separator
469         isSeparator = separatorKey ? (object.get ? object.get(separatorKey) : object[separatorKey]) : NO;
470 
471         if (isSeparator) {
472           item = SC.Object.create({
473             isSeparator: true,
474             value: '_sc_separator_item'
475           });
476         } else {
477           //Get the name value. If value key is not specified convert obj
478           //to string
479           name = nameKey ? (object.get ?
480             object.get(nameKey) : object[nameKey]) : object.toString();
481 
482           //@if (debug)
483           // Help the developer if they don't define a matching itemTitleKey.
484           if (!name) {
485             SC.warn("Developer Warning: SC.SelectView: Every item, other than separator items, should have the '%@' property defined!".fmt(nameKey));
486             name = '';
487           }
488           //@endif
489           // localize name if specified.
490           name = shouldLocalize ? SC.String.loc(name) : name;
491           name = escapeHTML ? SC.RenderContext.escapeHTML(name) : name;
492 
493           //Get the icon value
494           icon = iconKey ? (object.get ?
495             object.get(iconKey) : object[iconKey]) : null;
496           if (SC.none(object[iconKey])) icon = null;
497 
498           // get the value using the valueKey or the object
499           value = valueKey ? (object.get ?
500             object.get(valueKey) : object[valueKey]) : object;
501 
502           if (!SC.none(currentSelectedVal) && !SC.none(value)) {
503 
504             // If the objects in question are records, we should just their storeKeys
505             isSameRecord = false;
506             if (SC.kindOf(currentSelectedVal, SC.Record) && SC.kindOf(value, SC.Record)) {
507               isSameRecord = currentSelectedVal.get('storeKey') === value.get('storeKey');
508             }
509 
510             if (currentSelectedVal === value || isSameRecord) {
511               this.set('title', name);
512               this.set('icon', icon);
513             }
514           }
515 
516           //Check if the item is currentSelectedItem or not
517           if (value === this.get('value')) {
518 
519             //set the _itemIdx - To change the prefMatrix accordingly.
520             this.set('_itemIdx', idx);
521             isChecked = !showCheckbox ? NO : YES;
522           } else {
523             isChecked = NO;
524           }
525 
526           // Check if the item is enabled
527           itemEnabled = (object.get ? object.get(isEnabledKey) : object[isEnabledKey]);
528           if (NO !== itemEnabled) itemEnabled = YES;
529 
530           // Set the first non-separator selectable item from the list as the
531           // default selected item
532           if (SC.none(this._defaultVal) && itemEnabled) {
533             this._defaultVal = value;
534             this._defaultTitle = name;
535             this._defaultIcon = icon;
536           }
537 
538           item = SC.Object.create({
539             action: 'displaySelectedItem',
540             title: name,
541             icon: icon,
542             value: value,
543             isEnabled: itemEnabled,
544             checkbox: isChecked,
545             target: this
546           });
547         }
548 
549         //Set the items in the itemList array
550         itemList.push(item);
551 
552       }
553 
554       idx += 1;
555 
556     }, this );
557 
558     this.set('_itemList', itemList);
559 
560     var value = this.get('value');
561     if (SC.none(value)) {
562       if (SC.none(emptyName)) {
563         this.set('value', this._defaultVal);
564         this.set('title', this._defaultTitle);
565         this.set('icon', this._defaultIcon);
566       }
567       else this.set('title', emptyName);
568     }
569 
570     //Set the preference matrix for the menu pane
571     this.changeSelectPreferMatrix();
572   }.observes( '*items.[]' ),
573 
574   /**
575     @private
576     @param {DOMMouseEvent} evt mouseup event that triggered the action
577   */
578   _action: function (evt) {
579     var buttonLabel, menuWidth, scrollWidth, lastMenuWidth, offsetWidth,
580       items, elementOffsetWidth, largestMenuWidth, item, idx,
581       value, itemList, menuControlSize, menuHeightPadding, customView,
582       menu, itemsLength, itemIdx, escapeHTML;
583 
584     buttonLabel = this.$('.sc-button-label')[0];
585 
586     var menuWidthOffset = SC.SelectView.MENU_WIDTH_OFFSET;
587     if (!this.get('isDefaultPosition')) {
588       switch (this.get('controlSize')) {
589         case SC.TINY_CONTROL_SIZE:
590           menuWidthOffset += SC.SelectView.TINY_POPUP_MENU_WIDTH_OFFSET;
591           break;
592         case SC.SMALL_CONTROL_SIZE:
593           menuWidthOffset += SC.SelectView.SMALL_POPUP_MENU_WIDTH_OFFSET;
594           break;
595         case SC.REGULAR_CONTROL_SIZE:
596           menuWidthOffset += SC.SelectView.REGULAR_POPUP_MENU_WIDTH_OFFSET;
597           break;
598         case SC.LARGE_CONTROL_SIZE:
599           menuWidthOffset += SC.SelectView.LARGE_POPUP_MENU_WIDTH_OFFSET;
600           break;
601         case SC.HUGE_CONTROL_SIZE:
602           menuWidthOffset += SC.SelectView.HUGE_POPUP_MENU_WIDTH_OFFSET;
603           break;
604       }
605     }
606     // Get the length of the text on the button in pixels
607     menuWidth = this.get('layer').offsetWidth + menuWidthOffset;
608 
609     // Get the length of the text on the button in pixels
610     menuWidth = this.get('layer').offsetWidth;
611     scrollWidth = buttonLabel.scrollWidth;
612     lastMenuWidth = this.get('lastMenuWidth');
613     if (scrollWidth) {
614        // Get the original width of the label in the button
615        offsetWidth = buttonLabel.offsetWidth;
616        if (scrollWidth && offsetWidth) {
617           menuWidth = menuWidth + scrollWidth - offsetWidth;
618        }
619     }
620     if (!lastMenuWidth || (menuWidth > lastMenuWidth)) {
621       lastMenuWidth = menuWidth;
622     }
623 
624     items = this.get('_itemList');
625 
626     var customViewClassName = this.get('customViewClassName');
627     // var customViewMenuOffsetWidth = this.get('customViewMenuOffsetWidth');
628     var className = 'sc-view sc-pane sc-panel sc-palette sc-picker sc-menu select-button sc-scroll-view sc-menu-scroll-view sc-container-view menuContainer sc-button-view sc-menu-item sc-regular-size';
629     className = customViewClassName ? (className + ' ' + customViewClassName) : className;
630 
631     SC.prepareStringMeasurement("", className);
632     for (idx = 0, itemsLength = items.length; idx < itemsLength; ++idx) {
633       //getting the width of largest menu item
634       item = items.objectAt(idx);
635       elementOffsetWidth = SC.measureString(item.title).width;
636 
637       if (!largestMenuWidth || (elementOffsetWidth > largestMenuWidth)) {
638         largestMenuWidth = elementOffsetWidth;
639       }
640     }
641     SC.teardownStringMeasurement();
642 
643     lastMenuWidth = (largestMenuWidth + this.menuItemPadding > lastMenuWidth) ?
644                       largestMenuWidth + this.menuItemPadding : lastMenuWidth;
645 
646     // Get the window size width and compare with the lastMenuWidth.
647     // If it is greater than windows width then reduce the maxwidth by 25px
648     // so that the ellipsis property is enabled by default
649     var maxWidth = SC.RootResponder.responder.get('currentWindowSize').width;
650     if (lastMenuWidth > maxWidth) {
651       lastMenuWidth = (maxWidth - 25);
652     }
653 
654     this.set('lastMenuWidth',lastMenuWidth);
655     value = this.get('value');
656     itemList = this.get('_itemList');
657     menuControlSize = this.get('controlSize');
658     menuHeightPadding = this.get('menuPaneHeightPadding');
659     escapeHTML = this.get('escapeHTML');
660 
661     // get the user defined custom view
662     customView = this.get('exampleView') || SC.MenuItemView.extend({ escapeHTML: escapeHTML });
663 
664     menu  = SC.MenuPane.create({
665 
666       /**
667         Class name - select-button-item
668       */
669       classNames: ['select-button'],
670 
671       /**
672         The menu items are set from the itemList property of SelectView
673 
674         @property
675       */
676       items: itemList,
677 
678       /**
679         Example view which will be used to create the Menu Items
680 
681         @default SC.MenuItemView
682         @type SC.View
683       */
684       exampleView: customView,
685 
686       /**
687         This property enables all the items and makes them selectable.
688 
689         @property
690       */
691       isEnabled: YES,
692 
693       menuHeightPadding: menuHeightPadding,
694 
695       preferType: SC.PICKER_MENU,
696       itemHeightKey: 'height',
697       layout: { width: lastMenuWidth },
698       controlSize: menuControlSize,
699       itemWidth: lastMenuWidth
700     });
701 
702     // no menu to toggle... bail...
703     if (!menu) return NO;
704     menu.popup(this, this.preferMatrix);
705     this.set('menu', menu);
706 
707     itemIdx = this._itemIdx;
708     if (!SC.empty(itemIdx) && itemIdx > -1) {
709     // if there is an item selected, make it the first responder
710       customView = menu.menuItemViewForContentIndex(itemIdx);
711       if (customView) { customView.becomeFirstResponder(); }
712     }
713 
714     this.set('isActive', YES);
715     return YES;
716   },
717 
718   /** @private
719      Action method for the select button menu items
720   */
721   displaySelectedItem: function (menuView) {
722     var currentItem = menuView.get("selectedItem");
723 
724     this.set("value", currentItem.get("value"));
725   },
726 
727   /** @private Each time the value changes, update each item's checkbox property and update our display properties. */
728   valueDidChange: function () {
729     var itemList = this._itemList,
730       showCheckbox = this.get('showCheckbox'),
731       value = this.get('value');
732 
733     // Find the newly selected item (if any).
734     for (var i = 0, len = itemList.length; i < len; i++) {
735       var item = itemList[i],
736         isChecked = false;
737 
738       if (value === item.get('value')) {
739         isChecked = showCheckbox;
740         this.set("title", item.get("title"));
741         this.set("icon", item.get("icon"));
742         this.set("_itemIdx", item.get("contentIndex"));
743       }
744 
745       item.set('checkbox', isChecked);
746     }
747 
748   }.observes('value'),
749 
750   /** @private
751      Set the "top" attribute in the prefer matrix property which will
752      position menu such that the selected item in the menu will be
753      place aligned to the item on the button when menu is opened.
754   */
755   changeSelectPreferMatrix: function () {
756     var controlSizeTuning = 0, customMenuItemHeight = 0;
757     switch (this.get('controlSize')) {
758       case SC.TINY_CONTROL_SIZE:
759         controlSizeTuning = SC.SelectView.TINY_OFFSET_Y;
760         customMenuItemHeight = SC.MenuPane.TINY_MENU_ITEM_HEIGHT;
761         break;
762       case SC.SMALL_CONTROL_SIZE:
763         controlSizeTuning = SC.SelectView.SMALL_OFFSET_Y;
764         customMenuItemHeight = SC.MenuPane.SMALL_MENU_ITEM_HEIGHT;
765         break;
766       case SC.REGULAR_CONTROL_SIZE:
767         controlSizeTuning = SC.SelectView.REGULAR_OFFSET_Y;
768         customMenuItemHeight = SC.MenuPane.REGULAR_MENU_ITEM_HEIGHT;
769         break;
770       case SC.LARGE_CONTROL_SIZE:
771         controlSizeTuning = SC.SelectView.LARGE_OFFSET_Y;
772         customMenuItemHeight = SC.MenuPane.LARGE_MENU_ITEM_HEIGHT;
773         break;
774       case SC.HUGE_CONTROL_SIZE:
775         controlSizeTuning = SC.SelectView.HUGE_OFFSET_Y;
776         customMenuItemHeight = SC.MenuPane.HUGE_MENU_ITEM_HEIGHT;
777         break;
778     }
779 
780     var preferMatrixAttributeTop = controlSizeTuning,
781       itemIdx = this.get('_itemIdx'),
782       leftAlign = this.get('leftAlign'), defPreferMatrix, tempPreferMatrix;
783 
784     if (this.get('isDefaultPosition')) {
785       defPreferMatrix = [1, 0, 3];
786       this.set('preferMatrix', defPreferMatrix);
787     } else {
788       if (itemIdx) {
789         preferMatrixAttributeTop = itemIdx * customMenuItemHeight +
790           controlSizeTuning;
791       }
792       tempPreferMatrix = [leftAlign, -preferMatrixAttributeTop, 2];
793       this.set('preferMatrix', tempPreferMatrix);
794     }
795   },
796 
797   /**
798     @private
799     Holding down the button should display the menu pane.
800   */
801   mouseDown: function (evt) {
802     // Fast path, reject secondary clicks.
803     if (evt.which !== 1) return false;
804 
805     if (!this.get('isEnabledInPane')) return YES; // handled event, but do nothing
806     this.set('isActive', YES);
807     this._isMouseDown = YES;
808     this.becomeFirstResponder();
809     this._action();
810     return YES;
811   },
812 
813   /** @private
814     Because we responded YES to the mouseDown event, we have responsibility
815     for handling the corresponding mouseUp event.
816 
817     However, the user may click on this button, then drag the mouse down to a
818     menu item, and release the mouse over the menu item. We therefore need to
819     delegate any mouseUp events to the menu's menu item, if one is selected.
820 
821     We also need to differentiate between a single click and a click and hold.
822     If the user clicks and holds, we want to close the menu when they release.
823     Otherwise, we should wait until they click on the menu's modal pane before
824     removing our active state.
825 
826     @param {SC.Event} evt
827     @returns {Boolean}
828   */
829   mouseUp: function (evt) {
830     var menu = this.get('menu'), targetMenuItem;
831 
832     if (menu) {
833       targetMenuItem = menu.getPath('rootMenu.targetMenuItem');
834 
835       if (targetMenuItem && menu.get('mouseHasEntered')) {
836         // Have the menu item perform its action.
837         // If the menu returns NO, it had no action to
838         // perform, so we should close the menu immediately.
839         if (!targetMenuItem.performAction()) menu.remove();
840       } else {
841         // If the user waits more than 200ms between mouseDown and mouseUp,
842         // we can assume that they are clicking and dragging to the menu item,
843         // and we should close the menu if they mouseup anywhere not inside
844         // the menu.
845         if (evt.timeStamp - this._mouseDownTimestamp > 400) {
846           menu.remove();
847         }
848       }
849     }
850 
851     // Reset state.
852     this._isMouseDown = NO;
853     this.set("isActive", NO);
854     return YES;
855   },
856 
857   /** @private
858     Override mouseExited to not remove the active state on mouseexit.
859   */
860   mouseExited: function () {
861     return YES;
862   },
863 
864   /**
865     @private
866     Handle Key event - Down arrow key
867   */
868   keyDown: function (event) {
869     if ( this.interpretKeyEvents(event) ) {
870       return YES;
871     }
872     else {
873       return sc_super();
874     }
875   },
876 
877   /**
878     @private
879     Pressing the Up or Down arrow key should display the menu pane. Pressing escape should
880     resign first responder.
881   */
882   moveUp: function(evt) {
883     this._action();
884     return YES;
885   },
886   /** @private */
887   moveDown: function(evt) {
888     this._action();
889     return YES;
890   },
891   cancel: function(evt) {
892     this.resignFirstResponder();
893   },
894 
895   /** @private
896     Override the button isSelectedDidChange function in order to not perform any action
897     on selecting the select_button
898   */
899   _button_isSelectedDidChange: function () {
900 
901   }.observes('isSelected')
902 
903 });
904 
905 /**
906   @static
907   @type Number
908   @default 0
909 */
910 SC.SelectView.TINY_OFFSET_X = 0;
911 
912 /**
913   @static
914   @type Number
915   @default 0
916 */
917 SC.SelectView.TINY_OFFSET_Y = 0;
918 
919 /**
920   @static
921   @type Number
922   @default 0
923 */
924 SC.SelectView.TINY_POPUP_MENU_WIDTH_OFFSET = 0;
925 
926 
927 /**
928   @static
929   @type Number
930   @default -18
931 */
932 SC.SelectView.SMALL_OFFSET_X = -18;
933 
934 /**
935   @static
936   @type Number
937   @default 3
938 */
939 SC.SelectView.SMALL_OFFSET_Y = 3;
940 
941 /**
942   @static
943   @type Number
944   @default 7
945 */
946 SC.SelectView.SMALL_POPUP_MENU_WIDTH_OFFSET = 7;
947 
948 
949 /**
950   @static
951   @type Number
952   @default -17
953 */
954 SC.SelectView.REGULAR_OFFSET_X = -17;
955 
956 /**
957   @static
958   @type Number
959   @default 1
960 */
961 SC.SelectView.REGULAR_OFFSET_Y = 0;
962 
963 /**
964   @static
965   @type Number
966   @default 4
967 */
968 SC.SelectView.REGULAR_POPUP_MENU_WIDTH_OFFSET = 4;
969 
970 
971 /**
972   @static
973   @type Number
974   @default -17
975 */
976 SC.SelectView.LARGE_OFFSET_X = -17;
977 
978 /**
979   @static
980   @type Number
981   @default 6
982 */
983 SC.SelectView.LARGE_OFFSET_Y = 6;
984 
985 /**
986   @static
987   @type Number
988   @default 3
989 */
990 SC.SelectView.LARGE_POPUP_MENU_WIDTH_OFFSET = 3;
991 
992 
993 /**
994   @static
995   @type Number
996   @default 0
997 */
998 SC.SelectView.HUGE_OFFSET_X = 0;
999 
1000 /**
1001   @static
1002   @type Number
1003   @default 0
1004 */
1005 SC.SelectView.HUGE_OFFSET_Y = 0;
1006 
1007 /**
1008   @static
1009   @type Number
1010   @default 0
1011 */
1012 SC.SelectView.HUGE_POPUP_MENU_WIDTH_OFFSET = 0;
1013 
1014 
1015 /**
1016   @static
1017   @type Number
1018   @default -2
1019 */
1020 SC.SelectView.MENU_WIDTH_OFFSET = -2;
1021