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 #import "chrome/browser/ui/cocoa/clickhold_button_cell.h"
      9 #import "chrome/browser/ui/cocoa/nsview_additions.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 @synthesize openMenuOnRightClick = openMenuOnRightClick_;
     21 
     22 // Overrides:
     23 
     24 + (Class)cellClass {
     25   return [ClickHoldButtonCell class];
     26 }
     27 
     28 - (id)init {
     29   if ((self = [super init]))
     30     [self configureCell];
     31   return self;
     32 }
     33 
     34 - (id)initWithCoder:(NSCoder*)decoder {
     35   if ((self = [super initWithCoder:decoder]))
     36     [self configureCell];
     37   return self;
     38 }
     39 
     40 - (id)initWithFrame:(NSRect)frameRect {
     41   if ((self = [super initWithFrame:frameRect]))
     42     [self configureCell];
     43   return self;
     44 }
     45 
     46 - (void)dealloc {
     47   self.attachedMenu = nil;
     48   [super dealloc];
     49 }
     50 
     51 - (void)awakeFromNib {
     52   [self configureCell];
     53 }
     54 
     55 - (void)setCell:(NSCell*)cell {
     56   [super setCell:cell];
     57   [self configureCell];
     58 }
     59 
     60 - (void)rightMouseDown:(NSEvent*)theEvent {
     61   if (!openMenuOnRightClick_) {
     62     [super rightMouseDown:theEvent];
     63     return;
     64   }
     65 
     66   [self clickShowMenu:self];
     67 }
     68 
     69 // Accessors and mutators:
     70 
     71 - (NSMenu*)attachedMenu {
     72   return attachedMenu_.get();
     73 }
     74 
     75 - (void)setAttachedMenu:(NSMenu*)menu {
     76   attachedMenu_.reset([menu retain]);
     77   [[self cell] setEnableClickHold:(menu != nil)];
     78 }
     79 
     80 - (void)setOpenMenuOnClick:(BOOL)enabled {
     81   openMenuOnClick_ = enabled;
     82   if (enabled) {
     83     [[self cell] setClickHoldTimeout:0.0];  // Make menu trigger immediately.
     84     [[self cell] setAction:@selector(clickShowMenu:)];
     85     [[self cell] setTarget:self];
     86   } else {
     87     [[self cell] setClickHoldTimeout:0.25];  // Default value.
     88   }
     89 }
     90 
     91 - (void)setOpenMenuOnRightClick:(BOOL)enabled {
     92   openMenuOnRightClick_ = enabled;
     93 }
     94 
     95 - (NSRect)menuRect {
     96   return [self bounds];
     97 }
     98 
     99 @end  // @implementation MenuButton
    100 
    101 @implementation MenuButton (Private)
    102 
    103 // Reset various settings of the button and its associated |ClickHoldButtonCell|
    104 // to the standard state which provides reasonable defaults.
    105 - (void)configureCell {
    106   ClickHoldButtonCell* cell = [self cell];
    107   DCHECK([cell isKindOfClass:[ClickHoldButtonCell class]]);
    108   [cell setClickHoldAction:@selector(dragShowMenu:)];
    109   [cell setClickHoldTarget:self];
    110   [cell setEnableClickHold:([self attachedMenu] != nil)];
    111 }
    112 
    113 // Actually show the menu (in the correct location). |isDragging| indicates
    114 // whether the mouse button is still down or not.
    115 - (void)showMenu:(BOOL)isDragging {
    116   if (![self attachedMenu]) {
    117     LOG(WARNING) << "No menu available.";
    118     if (isDragging) {
    119       // If we're dragging, wait for mouse up.
    120       [NSApp nextEventMatchingMask:NSLeftMouseUpMask
    121                          untilDate:[NSDate distantFuture]
    122                             inMode:NSEventTrackingRunLoopMode
    123                            dequeue:YES];
    124     }
    125     return;
    126   }
    127 
    128   // TODO(viettrungluu): We have some fudge factors below to make things line up
    129   // (approximately). I wish I knew how to get rid of them. (Note that our view
    130   // is flipped, and that frame should be in our coordinates.) The y/height is
    131   // very odd, since it doesn't seem to respond to changes the way that it
    132   // should. I don't understand it.
    133   NSRect frame = [self menuRect];
    134   frame.origin.x -= 2.0;
    135   frame.size.height -= 19.0 - NSHeight(frame);
    136 
    137   // Make our pop-up button cell and set things up. This is, as of 10.5, the
    138   // official Apple-recommended hack. Later, perhaps |-[NSMenu
    139   // popUpMenuPositioningItem:atLocation:inView:]| may be a better option.
    140   // However, using a pulldown has the benefit that Cocoa automatically places
    141   // the menu correctly even when we're at the edge of the screen (including
    142   // "dragging upwards" when the button is close to the bottom of the screen).
    143   // A |scoped_nsobject| local variable cannot be used here because
    144   // Accessibility on 10.5 grabs the NSPopUpButtonCell without retaining it, and
    145   // uses it later. (This is fixed in 10.6.)
    146   if (!popUpCell_.get()) {
    147     popUpCell_.reset([[NSPopUpButtonCell alloc] initTextCell:@""
    148                                                    pullsDown:YES]);
    149   }
    150   DCHECK(popUpCell_.get());
    151   [popUpCell_ setMenu:[self attachedMenu]];
    152   [popUpCell_ selectItem:nil];
    153   [popUpCell_ attachPopUpWithFrame:frame inView:self];
    154   [popUpCell_ performClickWithFrame:frame inView:self];
    155 
    156   // Once the menu is dismissed send a mouseExited event if necessary. If the
    157   // menu action caused the super view to resize then we won't automatically
    158   // get a mouseExited event so we need to do this manually.
    159   // See http://crbug.com/82456
    160   if (![self cr_isMouseInView]) {
    161     if ([[self cell] respondsToSelector:@selector(mouseExited:)])
    162       [[self cell] mouseExited:nil];
    163   }
    164 }
    165 
    166 // Called when the button is clicked and released. (Shouldn't happen with
    167 // timeout of 0, though there may be some strange pointing devices out there.)
    168 - (void)clickShowMenu:(id)sender {
    169   // This should only be called if openMenuOnClick has been set (which hooks
    170   // up this target-action).
    171   DCHECK(openMenuOnClick_ || openMenuOnRightClick_);
    172   [self showMenu:NO];
    173 }
    174 
    175 // Called when the button is clicked and dragged/held.
    176 - (void)dragShowMenu:(id)sender {
    177   // We shouldn't get here unless the menu is enabled.
    178   DCHECK([self attachedMenu]);
    179   [self showMenu:YES];
    180 }
    181 
    182 @end  // @implementation MenuButton (Private)
    183