Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2012 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/location_bar/action_box_menu_bubble_controller. h" | |
| 6 | |
| 7 #include "base/mac/bundle_locations.h" | |
| 8 #include "base/mac/mac_util.h" | |
| 9 #include "base/sys_string_conversions.h" | |
| 10 #include "chrome/browser/ui/browser.h" | |
| 11 #include "chrome/browser/ui/browser_window.h" | |
| 12 #import "chrome/browser/ui/cocoa/browser_window_utils.h" | |
| 13 #import "chrome/browser/ui/cocoa/event_utils.h" | |
| 14 #import "chrome/browser/ui/cocoa/info_bubble_view.h" | |
| 15 #import "chrome/browser/ui/cocoa/info_bubble_window.h" | |
| 16 #include "grit/generated_resources.h" | |
| 17 #include "grit/theme_resources.h" | |
| 18 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" | |
| 19 #include "ui/base/models/simple_menu_model.h" | |
| 20 #include "ui/base/resource/resource_bundle.h" | |
| 21 #include "ui/gfx/image/image.h" | |
| 22 | |
| 23 @interface ActionBoxMenuBubbleController (Private) | |
| 24 - (ui::MenuModel*)model; | |
| 25 - (void)keyDown:(NSEvent*)theEvent; | |
| 26 - (void)moveDown:(id)sender; | |
| 27 - (void)moveUp:(id)sender; | |
| 28 - (void)highlightNextItemByDelta:(NSInteger)delta; | |
| 29 - (void)highlightItem:(ActionBoxMenuItemController*)newItem; | |
| 30 @end | |
| 31 | |
| 32 namespace { | |
| 33 | |
| 34 // Some reasonable values for the menu geometry. | |
| 35 const CGFloat kBubbleMinWidth = 175; | |
| 36 const CGFloat kBubbleMaxWidth = 800; | |
| 37 | |
| 38 // Distance between the top/bottom of the bubble and the first/last menu item. | |
| 39 const CGFloat kVerticalPadding = 7.0; | |
| 40 | |
| 41 // Minimum distance between the right of a menu item and the right border. | |
| 42 const CGFloat kRightMargin = 20.0; | |
| 43 | |
| 44 // Alpha of the black rectangle overlayed on the selected item.. | |
|
Scott Hess - ex-Googler
2012/10/15 06:06:35
Double period. While you're there, make the comme
beaudoin
2012/10/15 16:42:57
Done.
| |
| 45 const CGFloat kSelectionAlpha = 0.06; | |
| 46 | |
| 47 } // namespace | |
| 48 | |
| 49 @implementation ActionBoxMenuBubbleController | |
| 50 | |
| 51 - (id)initWithBrowser:(Browser*)parentBrowser | |
| 52 usingModel:(scoped_ptr<ui::MenuModel>)model | |
| 53 anchoredAt:(NSPoint)point { | |
| 54 if ((self = [self initWithModel:model.Pass() | |
| 55 parentWindow:parentBrowser->window()->GetNativeWindow() | |
| 56 anchoredAt:point])) { | |
| 57 } | |
| 58 return self; | |
| 59 } | |
| 60 | |
| 61 - (ui::MenuModel*)model { | |
| 62 return model_.get(); | |
| 63 } | |
| 64 | |
| 65 - (IBAction)itemSelected:(id)sender { | |
| 66 // Close the current window and activate the parent browser window, otherwise | |
| 67 // the bookmark popup refuses to show. | |
| 68 [self close]; | |
| 69 [BrowserWindowUtils | |
| 70 activateWindowForController:[[self parentWindow] windowController]]; | |
| 71 size_t modelIndex = [sender modelIndex]; | |
| 72 DCHECK(model_.get()); | |
| 73 int event_flags = event_utils::EventFlagsFromNSEvent([NSApp currentEvent]); | |
|
Scott Hess - ex-Googler
2012/10/15 06:06:35
You're in Objective-C code, here, so you need even
beaudoin
2012/10/15 16:42:57
event_utils is a common namespace, I assume you me
| |
| 74 model_->ActivatedAt(modelIndex, event_flags); | |
| 75 } | |
| 76 | |
| 77 // Private ///////////////////////////////////////////////////////////////////// | |
| 78 | |
| 79 - (id)initWithModel:(scoped_ptr<ui::MenuModel>)model | |
| 80 parentWindow:(NSWindow*)parent | |
| 81 anchoredAt:(NSPoint)point { | |
| 82 // Use an arbitrary height because it will reflect the size of the content. | |
| 83 NSRect contentRect = NSMakeRect(0, 0, kBubbleMinWidth, 150); | |
|
Scott Hess - ex-Googler
2012/10/15 06:06:35
Is there any reason not to just use NSZeroRect?
beaudoin
2012/10/15 16:42:57
An NSZeroRect gives me this:
[84157:-1400145368:1
Scott Hess - ex-Googler
2012/10/15 21:15:18
OK.
| |
| 84 // Create an empty window into which content is placed. | |
| 85 scoped_nsobject<InfoBubbleWindow> window( | |
| 86 [[InfoBubbleWindow alloc] initWithContentRect:contentRect | |
| 87 styleMask:NSBorderlessWindowMask | |
| 88 backing:NSBackingStoreBuffered | |
| 89 defer:NO]); | |
| 90 if (self = [super initWithWindow:window | |
| 91 parentWindow:parent | |
| 92 anchoredAt:[parent convertBaseToScreen:point]]) { | |
|
Scott Hess - ex-Googler
2012/10/15 06:06:35
This method has a pretty similar signature to the
beaudoin
2012/10/15 16:42:57
Moved the coordinate conversion upstream to plus_d
| |
| 93 model_.reset(model.release()); | |
| 94 | |
| 95 [[self bubble] setAlignment:info_bubble::kAlignRightEdgeToAnchorEdge]; | |
| 96 [[self bubble] setArrowLocation:info_bubble::kNoArrow]; | |
| 97 [[self bubble] setBackgroundColor: | |
| 98 [NSColor colorWithDeviceRed:(251.0f/255.0f) | |
| 99 green:(251.0f/255.0f) | |
| 100 blue:(251.0f/255.0f) | |
|
Scott Hess - ex-Googler
2012/10/15 06:06:35
colorWithDeviceWhite:alpha: seems like it would ma
beaudoin
2012/10/15 16:42:57
Done.
| |
| 101 alpha:1.0]]; | |
| 102 [self performLayout]; | |
| 103 } | |
| 104 return self; | |
| 105 } | |
| 106 | |
| 107 - (void)performLayout { | |
| 108 NSView* contentView = [[self window] contentView]; | |
| 109 | |
| 110 // Reset the array of controllers and remove all the views. | |
| 111 items_.reset([[NSMutableArray alloc] init]); | |
| 112 [contentView setSubviews:[NSArray array]]; | |
| 113 | |
| 114 // Leave some space at the bottom of the menu. | |
| 115 CGFloat yOffset = kVerticalPadding; | |
| 116 | |
| 117 // Loop over the items in reverse, constructing the menu items. | |
| 118 CGFloat width = kBubbleMinWidth; | |
| 119 for (int i = model_->GetItemCount() - 1; i >= 0; --i) { | |
|
Scott Hess - ex-Googler
2012/10/15 06:06:35
Is that really returning an int? I'd have expecte
beaudoin
2012/10/15 16:42:57
Totally agree, but it's an int!
Ref: http://code.g
| |
| 120 // Create the item controller. Autorelease it because it will be owned | |
| 121 // by the |items_| array. | |
| 122 ActionBoxMenuItemController* itemController = | |
| 123 [[[ActionBoxMenuItemController alloc] initWithModelIndex:i | |
| 124 menuController:self] autorelease]; | |
|
Scott Hess - ex-Googler
2012/10/15 06:06:35
Generally use scoped_nsobject<> rather than -autor
beaudoin
2012/10/15 16:42:57
Done.
| |
| 125 | |
| 126 // Adjust the name field to fit the string. | |
| 127 [GTMUILocalizerAndLayoutTweaker sizeToFitView:itemController.nameField]; | |
| 128 | |
| 129 // Expand the size of the window if required to fit the menu item. | |
| 130 width = std::max(width, | |
| 131 NSMaxX([itemController.nameField frame]) + kRightMargin); | |
|
Scott Hess - ex-Googler
2012/10/15 06:06:35
Is this accumulating the max width, or the max x-c
beaudoin
2012/10/15 16:42:57
Brings up a question: when does [someView bounds]
Scott Hess - ex-Googler
2012/10/15 21:15:18
It never happens except when it happens. Usually
| |
| 132 | |
| 133 // Add the item to the content view. | |
| 134 [[itemController view] setFrameOrigin:NSMakePoint(0, yOffset)]; | |
| 135 [contentView addSubview:[itemController view]]; | |
| 136 yOffset += NSHeight([[itemController view] frame]); | |
| 137 | |
| 138 // Keep track of the view controller. | |
| 139 [items_ addObject:itemController]; | |
| 140 } | |
| 141 | |
| 142 // Leave some space at the top of the menu. | |
| 143 yOffset += kVerticalPadding; | |
| 144 | |
| 145 // Set the window frame, clamping the width at a sensible max. | |
| 146 NSRect frame = [[self window] frame]; | |
| 147 frame.size.height = yOffset; | |
| 148 frame.size.width = std::min(width, kBubbleMaxWidth); | |
| 149 [[self window] setFrame:frame display:YES]; | |
| 150 } | |
| 151 | |
| 152 - (NSMutableArray*)items { | |
|
Scott Hess - ex-Googler
2012/10/15 06:06:35
Do you expect the caller to modify it?
beaudoin
2012/10/15 16:42:57
Done.
| |
| 153 return items_.get(); | |
| 154 } | |
| 155 | |
| 156 - (void)keyDown:(NSEvent*)theEvent { | |
| 157 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; | |
|
Scott Hess - ex-Googler
2012/10/15 06:06:35
This is unusual enough that it deserves a comment
beaudoin
2012/10/15 16:42:57
Taken from AvatarMenuBubbleController, documented
| |
| 158 } | |
| 159 | |
| 160 - (void)moveDown:(id)sender { | |
| 161 [self highlightNextItemByDelta:-1]; | |
| 162 } | |
| 163 | |
| 164 - (void)moveUp:(id)sender { | |
| 165 [self highlightNextItemByDelta:1]; | |
| 166 } | |
| 167 | |
| 168 - (void)highlightNextItemByDelta:(NSInteger)delta { | |
| 169 NSUInteger count = [items_ count]; | |
| 170 if (count == 0) | |
| 171 return; | |
| 172 | |
| 173 NSInteger old_index = -1; | |
| 174 for (NSUInteger i = 0; i < count; ++i) { | |
| 175 if ([[items_ objectAtIndex:i] isHighlighted]) { | |
| 176 old_index = i; | |
|
Scott Hess - ex-Googler
2012/10/15 06:06:35
Casting missing, here, but I don't think you need
beaudoin
2012/10/15 16:42:57
Simplified that entire method quite a bit.
Done.
| |
| 177 break; | |
| 178 } | |
| 179 } | |
| 180 | |
| 181 NSInteger new_index; | |
| 182 // If nothing is selected then start at the top if we're going down and start | |
| 183 // at the bottom if we're going up. | |
| 184 if (old_index == -1) | |
| 185 new_index = delta < 0 ? (count - 1) : 0; | |
| 186 else | |
| 187 new_index = old_index + delta; | |
| 188 | |
| 189 // Cap the index. We don't wrap around to match the behavior of Mac menus. | |
| 190 new_index = | |
| 191 std::min(std::max(static_cast<NSInteger>(0), new_index), | |
| 192 static_cast<NSInteger>(count - 1)); | |
| 193 | |
| 194 [self highlightItem:[items_ objectAtIndex:new_index]]; | |
| 195 } | |
| 196 | |
| 197 - (void)highlightItem:(ActionBoxMenuItemController*)newItem { | |
| 198 ActionBoxMenuItemController* oldItem = nil; | |
| 199 for (ActionBoxMenuItemController* item in items_.get()) { | |
| 200 if ([item isHighlighted]) { | |
| 201 oldItem = item; | |
| 202 break; | |
| 203 } | |
| 204 } | |
| 205 | |
| 206 if (oldItem == newItem) | |
| 207 return; | |
| 208 | |
| 209 [oldItem setIsHighlighted:NO]; | |
| 210 [newItem setIsHighlighted:YES]; | |
| 211 } | |
| 212 | |
| 213 @end | |
| 214 | |
| 215 // Menu Item Controller //////////////////////////////////////////////////////// | |
| 216 | |
| 217 @implementation ActionBoxMenuItemController | |
| 218 | |
| 219 @synthesize modelIndex = modelIndex_; | |
| 220 @synthesize isHighlighted = isHighlighted_; | |
| 221 @synthesize nameField = nameField_; | |
| 222 | |
| 223 - (id)initWithModelIndex:(size_t)modelIndex | |
| 224 menuController:(ActionBoxMenuBubbleController*)controller { | |
| 225 if ((self = [super initWithNibName:@"ActionBoxMenuItem" | |
| 226 bundle:base::mac::FrameworkBundle()])) { | |
| 227 modelIndex_ = modelIndex; | |
| 228 controller_ = controller; | |
| 229 | |
| 230 [self loadView]; | |
| 231 | |
| 232 gfx::Image icon = gfx::Image(); | |
| 233 | |
| 234 if (controller.model->GetIconAt(modelIndex_, &icon)) | |
| 235 iconView_.image = icon.ToNSImage(); | |
| 236 else | |
| 237 iconView_.image = nil; | |
| 238 nameField_.stringValue = base::SysUTF16ToNSString( | |
| 239 controller.model->GetLabelAt(modelIndex_)); | |
| 240 } | |
| 241 return self; | |
| 242 } | |
| 243 | |
| 244 - (void)dealloc { | |
| 245 static_cast<ActionBoxMenuItemView*>(self.view).viewController = nil; | |
|
Scott Hess - ex-Googler
2012/10/15 06:06:35
There's an objective-c cast somewhere in base/mac.
beaudoin
2012/10/15 16:42:57
Switched to ObjCCastStrict.
It was taken from Ava
Scott Hess - ex-Googler
2012/10/15 21:15:18
I'm kind of imagining that I shouldn't go poking a
| |
| 246 [super dealloc]; | |
| 247 } | |
| 248 | |
| 249 - (void)highlightForEventType:(NSEventType)type { | |
| 250 switch (type) { | |
| 251 case NSMouseEntered: | |
| 252 [controller_ highlightItem:self]; | |
| 253 break; | |
| 254 | |
| 255 case NSMouseExited: | |
| 256 [controller_ highlightItem:nil]; | |
| 257 break; | |
| 258 | |
| 259 default: | |
| 260 NOTREACHED(); | |
| 261 }; | |
| 262 } | |
| 263 | |
| 264 - (IBAction)itemSelected:(id)sender { | |
| 265 [controller_ itemSelected:self]; | |
| 266 } | |
| 267 | |
| 268 - (void)setIsHighlighted:(BOOL)isHighlighted { | |
| 269 if (isHighlighted_ == isHighlighted) | |
| 270 return; | |
| 271 | |
| 272 isHighlighted_ = isHighlighted; | |
| 273 [[self view] setNeedsDisplay:YES]; | |
| 274 } | |
| 275 | |
| 276 @end | |
| 277 | |
| 278 // Items from the action box menu ////////////////////////////////////////////// | |
| 279 | |
| 280 @implementation ActionBoxMenuItemView | |
| 281 | |
| 282 @synthesize viewController = viewController_; | |
| 283 | |
| 284 - (void)updateTrackingAreas { | |
| 285 if (trackingArea_.get()) | |
| 286 [self removeTrackingArea:trackingArea_.get()]; | |
| 287 | |
| 288 trackingArea_.reset( | |
| 289 [[CrTrackingArea alloc] initWithRect:[self bounds] | |
| 290 options:NSTrackingMouseEnteredAndExited | | |
| 291 NSTrackingActiveInKeyWindow | |
| 292 owner:self | |
| 293 userInfo:nil]); | |
| 294 [self addTrackingArea:trackingArea_.get()]; | |
| 295 | |
| 296 [super updateTrackingAreas]; | |
| 297 } | |
| 298 | |
| 299 - (BOOL)acceptsFirstResponder { | |
| 300 return YES; | |
| 301 } | |
| 302 | |
| 303 - (void)mouseUp:(NSEvent*)theEvent { | |
| 304 [viewController_ itemSelected:self]; | |
| 305 } | |
| 306 | |
| 307 - (void)mouseEntered:(id)sender { | |
| 308 [viewController_ highlightForEventType:[[NSApp currentEvent] type]]; | |
| 309 [self setNeedsDisplay:YES]; | |
| 310 } | |
| 311 | |
| 312 - (void)mouseExited:(id)sender { | |
| 313 [viewController_ highlightForEventType:[[NSApp currentEvent] type]]; | |
| 314 [self setNeedsDisplay:YES]; | |
| 315 } | |
| 316 | |
| 317 - (void)drawRect:(NSRect)dirtyRect { | |
| 318 NSColor* backgroundColor = nil; | |
| 319 if ([viewController_ isHighlighted]) { | |
| 320 backgroundColor = [NSColor colorWithDeviceWhite:0.0 alpha:kSelectionAlpha]; | |
| 321 } else { | |
| 322 backgroundColor = [NSColor clearColor]; | |
| 323 } | |
| 324 | |
| 325 [backgroundColor set]; | |
| 326 [NSBezierPath fillRect:[self bounds]]; | |
| 327 } | |
| 328 | |
| 329 // Make sure the element is focusable for accessibility. | |
| 330 - (BOOL)canBecomeKeyView { | |
| 331 return YES; | |
| 332 } | |
| 333 | |
| 334 - (BOOL)accessibilityIsIgnored { | |
| 335 return NO; | |
| 336 } | |
| 337 | |
| 338 - (NSArray*)accessibilityAttributeNames { | |
| 339 NSMutableArray* attributes = | |
| 340 [[[super accessibilityAttributeNames] mutableCopy] autorelease]; | |
| 341 [attributes addObject:NSAccessibilityTitleAttribute]; | |
| 342 [attributes addObject:NSAccessibilityEnabledAttribute]; | |
| 343 | |
| 344 return attributes; | |
| 345 } | |
| 346 | |
| 347 - (NSArray*)accessibilityActionNames { | |
| 348 NSArray* parentActions = [super accessibilityActionNames]; | |
| 349 return [parentActions arrayByAddingObject:NSAccessibilityPressAction]; | |
| 350 } | |
| 351 | |
| 352 - (id)accessibilityAttributeValue:(NSString*)attribute { | |
| 353 if ([attribute isEqual:NSAccessibilityRoleAttribute]) | |
| 354 return NSAccessibilityButtonRole; | |
| 355 | |
| 356 if ([attribute isEqual:NSAccessibilityRoleDescriptionAttribute]) | |
| 357 return NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil); | |
| 358 | |
| 359 if ([attribute isEqual:NSAccessibilityEnabledAttribute]) | |
| 360 return [NSNumber numberWithBool:YES]; | |
| 361 | |
| 362 return [super accessibilityAttributeValue:attribute]; | |
| 363 } | |
| 364 | |
| 365 - (void)accessibilityPerformAction:(NSString*)action { | |
| 366 if ([action isEqual:NSAccessibilityPressAction]) { | |
| 367 [viewController_ itemSelected:self]; | |
| 368 return; | |
| 369 } | |
| 370 | |
| 371 [super accessibilityPerformAction:action]; | |
| 372 } | |
| 373 | |
| 374 @end | |
| OLD | NEW |