Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(577)

Side by Side Diff: chrome/browser/ui/cocoa/location_bar/action_box_menu_bubble_controller.mm

Issue 11103042: New custom styling for action box menu on Os X. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Answered first set of comments by Scott. Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698