Home | History | Annotate | Download | only in cocoa
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #import "chrome/browser/ui/cocoa/menu_button.h"
      6 
      7 #include "base/logging.h"
      8 #include "base/memory/scoped_nsobject.h"
      9 #import "chrome/browser/ui/cocoa/clickhold_button_cell.h"
     10 
     11 @interface MenuButton (Private)
     12 - (void)showMenu:(BOOL)isDragging;
     13 - (void)clickShowMenu:(id)sender;
     14 - (void)dragShowMenu:(id)sender;
     15 @end  // @interface MenuButton (Private)
     16 
     17 @implementation MenuButton
     18 
     19 @synthesize openMenuOnClick = openMenuOnClick_;
     20 
     21 // Overrides:
     22 
     23 + (Class)cellClass {
     24   return [ClickHoldButtonCell class];
     25 }
     26 
     27 - (id)init {
     28   if ((self = [super init]))
     29     [self configureCell];
     30   return self;
     31 }
     32 
     33 - (id)initWithCoder:(NSCoder*)decoder {
     34   if ((self = [super initWithCoder:decoder]))
     35     [self configureCell];
     36   return self;
     37 }
     38 
     39 - (id)initWithFrame:(NSRect)frameRect {
     40   if ((self = [super initWithFrame:frameRect]))
     41     [self configureCell];
     42   return self;
     43 }
     44 
     45 - (void)dealloc {
     46   self.attachedMenu = nil;
     47   [super dealloc];
     48 }
     49 
     50 - (void)awakeFromNib {
     51   [self configureCell];
     52 }
     53 
     54 - (void)setCell:(NSCell*)cell {
     55   [super setCell:cell];
     56   [self configureCell];
     57 }
     58 
     59 // Accessors and mutators:
     60 
     61 - (NSMenu*)attachedMenu {
     62   return attachedMenu_.get();
     63 }
     64 
     65 - (void)setAttachedMenu:(NSMenu*)menu {
     66   attachedMenu_.reset([menu retain]);
     67   [[self cell] setEnableClickHold:(menu != nil)];
     68 }
     69 
     70 - (void)setOpenMenuOnClick:(BOOL)enabled {
     71   openMenuOnClick_ = enabled;
     72   if (enabled) {
     73     [[self cell] setClickHoldTimeout:0.0];  // Make menu trigger immediately.
     74     [[self cell] setAction:@selector(clickShowMenu:)];
     75     [[self cell] setTarget:self];
     76   } else {
     77     [[self cell] setClickHoldTimeout:0.25];  // Default value.
     78   }
     79 }
     80 
     81 @end  // @implementation MenuButton
     82 
     83 @implementation MenuButton (Private)
     84 
     85 // Reset various settings of the button and its associated |ClickHoldButtonCell|
     86 // to the standard state which provides reasonable defaults.
     87 - (void)configureCell {
     88   ClickHoldButtonCell* cell = [self cell];
     89   DCHECK([cell isKindOfClass:[ClickHoldButtonCell class]]);
     90   [cell setClickHoldAction:@selector(dragShowMenu:)];
     91   [cell setClickHoldTarget:self];
     92   [cell setEnableClickHold:([self attachedMenu] != nil)];
     93 }
     94 
     95 // Actually show the menu (in the correct location). |isDragging| indicates
     96 // whether the mouse button is still down or not.
     97 - (void)showMenu:(BOOL)isDragging {
     98   if (![self attachedMenu]) {
     99     LOG(WARNING) << "No menu available.";
    100     if (isDragging) {
    101       // If we're dragging, wait for mouse up.
    102       [NSApp nextEventMatchingMask:NSLeftMouseUpMask
    103                          untilDate:[NSDate distantFuture]
    104                             inMode:NSEventTrackingRunLoopMode
    105                            dequeue:YES];
    106     }
    107     return;
    108   }
    109 
    110   // TODO(viettrungluu): We have some fudge factors below to make things line up
    111   // (approximately). I wish I knew how to get rid of them. (Note that our view
    112   // is flipped, and that frame should be in our coordinates.) The y/height is
    113   // very odd, since it doesn't seem to respond to changes the way that it
    114   // should. I don't understand it.
    115   NSRect frame = [self convertRect:[self frame]
    116                           fromView:[self superview]];
    117   frame.origin.x -= 2.0;
    118   frame.size.height += 10.0;
    119 
    120   // Make our pop-up button cell and set things up. This is, as of 10.5, the
    121   // official Apple-recommended hack. Later, perhaps |-[NSMenu
    122   // popUpMenuPositioningItem:atLocation:inView:]| may be a better option.
    123   // However, using a pulldown has the benefit that Cocoa automatically places
    124   // the menu correctly even when we're at the edge of the screen (including
    125   // "dragging upwards" when the button is close to the bottom of the screen).
    126   // A |scoped_nsobject| local variable cannot be used here because
    127   // Accessibility on 10.5 grabs the NSPopUpButtonCell without retaining it, and
    128   // uses it later. (This is fixed in 10.6.)
    129   if (!popUpCell_.get()) {
    130     popUpCell_.reset([[NSPopUpButtonCell alloc] initTextCell:@""
    131                                                    pullsDown:YES]);
    132   }
    133   DCHECK(popUpCell_.get());
    134   [popUpCell_ setMenu:[self attachedMenu]];
    135   [popUpCell_ selectItem:nil];
    136   [popUpCell_ attachPopUpWithFrame:frame
    137                             inView:self];
    138   [popUpCell_ performClickWithFrame:frame
    139                              inView:self];
    140 }
    141 
    142 // Called when the button is clicked and released. (Shouldn't happen with
    143 // timeout of 0, though there may be some strange pointing devices out there.)
    144 - (void)clickShowMenu:(id)sender {
    145   // This should only be called if openMenuOnClick has been set (which hooks
    146   // up this target-action).
    147   DCHECK(openMenuOnClick_);
    148   [self showMenu:NO];
    149 }
    150 
    151 // Called when the button is clicked and dragged/held.
    152 - (void)dragShowMenu:(id)sender {
    153   // We shouldn't get here unless the menu is enabled.
    154   DCHECK([self attachedMenu]);
    155   [self showMenu:YES];
    156 }
    157 
    158 @end  // @implementation MenuButton (Private)
    159