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

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 shess second round of comments. 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/foundation_util.h"
9 #include "base/mac/mac_util.h"
10 #include "base/sys_string_conversions.h"
11 #import "chrome/browser/ui/cocoa/browser_window_utils.h"
12 #import "chrome/browser/ui/cocoa/event_utils.h"
13 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
14 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
15 #include "grit/generated_resources.h"
16 #include "grit/theme_resources.h"
17 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
18 #include "ui/base/models/simple_menu_model.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/gfx/image/image.h"
21
22 @interface ActionBoxMenuBubbleController (Private)
23 - (const NSArray*)items;
24 - (void)keyDown:(NSEvent*)theEvent;
25 - (void)moveDown:(id)sender;
26 - (void)moveUp:(id)sender;
27 - (void)highlightNextItemByDelta:(NSInteger)delta;
28 - (void)highlightItem:(ActionBoxMenuItemController*)newItem;
29 @end
30
31 namespace {
32
33 // Some reasonable values for the menu geometry.
34 const CGFloat kBubbleMinWidth = 175;
35 const CGFloat kBubbleMaxWidth = 800;
36
37 // Distance between the top/bottom of the bubble and the first/last menu item.
38 const CGFloat kVerticalPadding = 7.0;
39
40 // Minimum distance between the right of a menu item and the right border.
41 const CGFloat kRightMargin = 20.0;
42
43 // Alpha of the black rectangle overlayed on the item hovered over.
44 const CGFloat kSelectionAlpha = 0.06;
45
46 } // namespace
47
48 @implementation ActionBoxMenuBubbleController
49
50 - (id)initWithModel:(scoped_ptr<ui::MenuModel>)model
51 parentWindow:(NSWindow*)parent
52 anchoredAt:(NSPoint)point {
53 // Use an arbitrary height because it will reflect the size of the content.
54 NSRect contentRect = NSMakeRect(0, 0, kBubbleMinWidth, 150);
55 // Create an empty window into which content is placed.
56 scoped_nsobject<InfoBubbleWindow> window(
57 [[InfoBubbleWindow alloc] initWithContentRect:contentRect
58 styleMask:NSBorderlessWindowMask
59 backing:NSBackingStoreBuffered
60 defer:NO]);
61 if (self = [super initWithWindow:window
62 parentWindow:parent
63 anchoredAt:point]) {
64 model_.reset(model.release());
65
66 [[self bubble] setAlignment:info_bubble::kAlignRightEdgeToAnchorEdge];
67 [[self bubble] setArrowLocation:info_bubble::kNoArrow];
68 [[self bubble] setBackgroundColor:
69 [NSColor colorWithDeviceWhite:(251.0f/255.0f)
70 alpha:1.0]];
71 [self performLayout];
72 }
73 return self;
74 }
75
76 - (ui::MenuModel*)model {
77 return model_.get();
78 }
79
80 - (IBAction)itemSelected:(id)sender {
81 // Close the current window and activate the parent browser window, otherwise
82 // the bookmark popup refuses to show.
83 [self close];
84 [BrowserWindowUtils
85 activateWindowForController:[[self parentWindow] windowController]];
86 size_t modelIndex = [sender modelIndex];
87 DCHECK(model_.get());
88 int eventFlags = event_utils::EventFlagsFromNSEvent([NSApp currentEvent]);
89 model_->ActivatedAt(modelIndex, eventFlags);
90 }
91
92 // Private /////////////////////////////////////////////////////////////////////
93
94 - (void)performLayout {
95 NSView* contentView = [[self window] contentView];
96
97 // Reset the array of controllers and remove all the views.
98 items_.reset([[NSMutableArray alloc] init]);
99 [contentView setSubviews:[NSArray array]];
100
101 // Leave some space at the bottom of the menu.
102 CGFloat yOffset = kVerticalPadding;
103
104 // Loop over the items in reverse, constructing the menu items.
105 CGFloat width = kBubbleMinWidth;
106 CGFloat minX = NSMinX([contentView bounds]);
107 for (int i = model_->GetItemCount() - 1; i >= 0; --i) {
108 // Create the item controller. Autorelease it because it will be owned
109 // by the |items_| array.
110 scoped_nsobject<ActionBoxMenuItemController> itemController(
111 [[ActionBoxMenuItemController alloc] initWithModelIndex:i
112 menuController:self]);
113
114 // Adjust the name field to fit the string.
115 [GTMUILocalizerAndLayoutTweaker sizeToFitView:[itemController nameField]];
116
117 // Expand the size of the window if required to fit the menu item.
118 width = std::max(width,
119 NSMaxX([[itemController nameField] frame]) - minX + kRightMargin);
120
121 // Add the item to the content view.
122 [[itemController view] setFrameOrigin:NSMakePoint(0, yOffset)];
123 [contentView addSubview:[itemController view]];
124 yOffset += NSHeight([[itemController view] frame]);
125
126 // Keep track of the view controller.
127 [items_ addObject:itemController.get()];
128 }
129
130 // Leave some space at the top of the menu.
131 yOffset += kVerticalPadding;
132
133 // Set the window frame, clamping the width at a sensible max.
134 NSRect frame = [[self window] frame];
135 frame.size.height = yOffset;
136 frame.size.width = std::min(width, kBubbleMaxWidth);
137 [[self window] setFrame:frame display:YES];
138 }
139
140 - (const NSArray*)items {
Scott Hess - ex-Googler 2012/10/15 23:03:23 Man, wish I'd seen that before :-). const is tric
141 return items_.get();
142 }
143
144 - (void)keyDown:(NSEvent*)theEvent {
145 // Interpret all possible key events. In particular, this will answer
146 // moveDown, moveUp and insertNewline so that the menu can be navigated
147 // with keystrokes.
148 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
149 }
150
151 - (void)moveDown:(id)sender {
152 [self highlightNextItemByDelta:-1];
153 }
154
155 - (void)moveUp:(id)sender {
156 [self highlightNextItemByDelta:1];
157 }
158
159 - (void)insertNewline:(id)sender {
160 for (ActionBoxMenuItemController* item in items_.get()) {
161 if ([item isHighlighted]) {
162 [self itemSelected:item];
163 return;
164 }
165 }
Scott Hess - ex-Googler 2012/10/15 21:15:18 I seem to see this basic loop three or four times.
beaudoin 2012/10/15 22:38:39 Done.
166 }
167
168 - (void)highlightNextItemByDelta:(NSInteger)delta {
169 NSUInteger count = [items_ count];
170 if (count == 0)
171 return;
172
173 // If nothing is selected, select the first (resp. last) item when going up
174 // (resp. going down). Otherwise selects the next (resp. previous) item.
175 // This code does not wrap around if something is already selected.
176 NSUInteger startIndex = delta < 0 ? 0 : (count - 1);
177 NSUInteger endIndex = delta < 0 ? (count - 1) : 0;
178 NSUInteger newIndex = endIndex; // Assumes nothing is selected.
179 if ([[items_ objectAtIndex:startIndex] isHighlighted]) {
180 // The capping item is already selected, do not wrap around.
181 newIndex = startIndex;
182 } else {
183 for (NSUInteger i = startIndex; i != endIndex; i -= delta) {
Scott Hess - ex-Googler 2012/10/15 21:15:18 This is 37% too tricky. You don't really care whi
beaudoin 2012/10/15 22:38:39 I think my code was much more elegant. But it's tr
Scott Hess - ex-Googler 2012/10/15 23:03:23 :-). Mostly I was thinking "If I'm in here debugg
184 if ([[items_ objectAtIndex:i - delta] isHighlighted]) {
185 newIndex = i;
186 break;
187 }
188 }
189 }
190
191 [self highlightItem:[items_ objectAtIndex:newIndex]];
192 }
193
194 - (void)highlightItem:(ActionBoxMenuItemController*)newItem {
195 ActionBoxMenuItemController* oldItem = nil;
196 for (ActionBoxMenuItemController* item in items_.get()) {
197 if ([item isHighlighted]) {
198 oldItem = item;
199 break;
200 }
201 }
202
203 if (oldItem == newItem)
204 return;
205
206 [oldItem setIsHighlighted:NO];
207 [newItem setIsHighlighted:YES];
208 }
209
210 @end
211
212 // Menu Item Controller ////////////////////////////////////////////////////////
213
214 @implementation ActionBoxMenuItemController
215
216 @synthesize modelIndex = modelIndex_;
217 @synthesize isHighlighted = isHighlighted_;
218 @synthesize nameField = nameField_;
219
220 - (id)initWithModelIndex:(size_t)modelIndex
221 menuController:(ActionBoxMenuBubbleController*)controller {
222 if ((self = [super initWithNibName:@"ActionBoxMenuItem"
223 bundle:base::mac::FrameworkBundle()])) {
224 modelIndex_ = modelIndex;
225 controller_ = controller;
226
227 [self loadView];
228
229 gfx::Image icon = gfx::Image();
230
231 if (controller.model->GetIconAt(modelIndex_, &icon))
232 iconView_.image = icon.ToNSImage();
233 else
234 iconView_.image = nil;
235 nameField_.stringValue = base::SysUTF16ToNSString(
236 controller.model->GetLabelAt(modelIndex_));
237 }
238 return self;
239 }
240
241 - (void)dealloc {
242 base::mac::ObjCCastStrict<ActionBoxMenuItemView>(
243 self.view).viewController = nil;
244 [super dealloc];
245 }
246
247 - (void)highlightForEventType:(NSEventType)type {
248 switch (type) {
249 case NSMouseEntered:
250 [controller_ highlightItem:self];
251 break;
252
253 case NSMouseExited:
254 [controller_ highlightItem:nil];
255 break;
256
257 default:
258 NOTREACHED();
259 };
260 }
261
262 - (IBAction)itemSelected:(id)sender {
263 [controller_ itemSelected:self];
264 }
265
266 - (void)setIsHighlighted:(BOOL)isHighlighted {
267 if (isHighlighted_ == isHighlighted)
268 return;
269
270 isHighlighted_ = isHighlighted;
271 [[self view] setNeedsDisplay:YES];
272 }
273
274 @end
275
276 // Items from the action box menu //////////////////////////////////////////////
277
278 @implementation ActionBoxMenuItemView
279
280 @synthesize viewController = viewController_;
281
282 - (void)updateTrackingAreas {
283 if (trackingArea_.get())
284 [self removeTrackingArea:trackingArea_.get()];
285
286 trackingArea_.reset(
287 [[CrTrackingArea alloc] initWithRect:[self bounds]
288 options:NSTrackingMouseEnteredAndExited |
289 NSTrackingActiveInKeyWindow
290 owner:self
291 userInfo:nil]);
292 [self addTrackingArea:trackingArea_.get()];
293
294 [super updateTrackingAreas];
295 }
296
297 - (BOOL)acceptsFirstResponder {
298 return YES;
299 }
300
301 - (void)mouseUp:(NSEvent*)theEvent {
302 [viewController_ itemSelected:self];
303 }
304
305 - (void)mouseEntered:(id)sender {
306 [viewController_ highlightForEventType:[[NSApp currentEvent] type]];
Scott Hess - ex-Googler 2012/10/15 21:15:18 I think |sender| is the event you want, here. May
beaudoin 2012/10/15 22:38:39 Done.
307 [self setNeedsDisplay:YES];
Scott Hess - ex-Googler 2012/10/15 21:15:18 The -setNeedsDisplay: probably belong up in -highl
beaudoin 2012/10/15 22:38:39 In fact it's already called by -setIsHighlighted.
308 }
309
310 - (void)mouseExited:(id)sender {
311 [viewController_ highlightForEventType:[[NSApp currentEvent] type]];
312 [self setNeedsDisplay:YES];
313 }
314
315 - (void)drawRect:(NSRect)dirtyRect {
316 NSColor* backgroundColor = nil;
317 if ([viewController_ isHighlighted]) {
318 backgroundColor = [NSColor colorWithDeviceWhite:0.0 alpha:kSelectionAlpha];
319 } else {
320 backgroundColor = [NSColor clearColor];
321 }
322
323 [backgroundColor set];
324 [NSBezierPath fillRect:[self bounds]];
325 }
326
327 // Make sure the element is focusable for accessibility.
328 - (BOOL)canBecomeKeyView {
329 return YES;
330 }
331
332 - (BOOL)accessibilityIsIgnored {
333 return NO;
334 }
335
336 - (NSArray*)accessibilityAttributeNames {
337 NSMutableArray* attributes =
338 [[[super accessibilityAttributeNames] mutableCopy] autorelease];
339 [attributes addObject:NSAccessibilityTitleAttribute];
340 [attributes addObject:NSAccessibilityEnabledAttribute];
341
342 return attributes;
343 }
344
345 - (NSArray*)accessibilityActionNames {
346 NSArray* parentActions = [super accessibilityActionNames];
347 return [parentActions arrayByAddingObject:NSAccessibilityPressAction];
348 }
349
350 - (id)accessibilityAttributeValue:(NSString*)attribute {
351 if ([attribute isEqual:NSAccessibilityRoleAttribute])
352 return NSAccessibilityButtonRole;
353
354 if ([attribute isEqual:NSAccessibilityRoleDescriptionAttribute])
355 return NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil);
356
357 if ([attribute isEqual:NSAccessibilityEnabledAttribute])
358 return [NSNumber numberWithBool:YES];
359
360 return [super accessibilityAttributeValue:attribute];
361 }
362
363 - (void)accessibilityPerformAction:(NSString*)action {
364 if ([action isEqual:NSAccessibilityPressAction]) {
365 [viewController_ itemSelected:self];
366 return;
367 }
368
369 [super accessibilityPerformAction:action];
370 }
371
372 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698