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

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

Powered by Google App Engine
This is Rietveld 408576698