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

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: Check that icon exists before setting it. 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 } // namespace
45
46 @implementation ActionBoxMenuBubbleController
47
48 - (id)initWithBrowser:(Browser*)parentBrowser
49 usingModel:(scoped_ptr<ui::MenuModel>)model
50 anchoredAt:(NSPoint)point {
51 if ((self = [self initWithModel:model.Pass()
52 parentWindow:parentBrowser->window()->GetNativeWindow()
53 anchoredAt:point])) {
54 }
55 return self;
56 }
57
58 - (ui::MenuModel*)model {
59 return model_.get();
60 }
61
62 - (IBAction)itemSelected:(id)sender {
63 // Close the current window and activate the parent browser window, otherwise
64 // the bookmark popup refuses to show.
65 [self close];
66 [BrowserWindowUtils
67 activateWindowForController:[[self parentWindow] windowController]];
68 size_t modelIndex = [sender modelIndex];
69 DCHECK(model_.get());
70 int event_flags = event_utils::EventFlagsFromNSEvent([NSApp currentEvent]);
71 model_->ActivatedAt(modelIndex, event_flags);
72 }
73
74 // Private /////////////////////////////////////////////////////////////////////
75
76 - (id)initWithModel:(scoped_ptr<ui::MenuModel>)model
77 parentWindow:(NSWindow*)parent
78 anchoredAt:(NSPoint)point {
79 // Use an arbitrary height because it will reflect the size of the content.
80 NSRect contentRect = NSMakeRect(0, 0, kBubbleMinWidth, 150);
81 // Create an empty window into which content is placed.
82 scoped_nsobject<InfoBubbleWindow> window(
83 [[InfoBubbleWindow alloc] initWithContentRect:contentRect
84 styleMask:NSBorderlessWindowMask
85 backing:NSBackingStoreBuffered
86 defer:NO]);
87 if ((self = [super initWithWindow:window
88 parentWindow:parent
89 anchoredAt:point])) {
90 model_.reset(model.release());
91
92 [[self bubble] setAlignment:info_bubble::kAlignRightEdgeToAnchorEdge];
93 [[self bubble] setArrowLocation:info_bubble::kNoArrow];
94 [[self bubble] setBackgroundColor:
95 [NSColor colorWithDeviceRed:(251.0f/255.0f)
96 green:(251.0f/255.0f)
97 blue:(251.0f/255.0f)
98 alpha:1.0]];
99 [self performLayout];
100 }
101 return self;
102 }
103
104 - (void)performLayout {
105 NSView* contentView = [[self window] contentView];
106
107 // Reset the array of controllers and remove all the views.
108 items_.reset([[NSMutableArray alloc] init]);
109 [contentView setSubviews:[NSArray array]];
110
111 // Leave some space at the bottom of the menu.
112 CGFloat yOffset = kVerticalPadding;
113
114 // Loop over the items in reverse, constructing the menu items.
115 CGFloat width = kBubbleMinWidth;
116 for (int i = model_->GetItemCount() - 1; i >= 0; --i) {
117 // Create the item controller. Autorelease it because it will be owned
118 // by the |items_| array.
119 ActionBoxMenuItemController* itemController =
120 [[[ActionBoxMenuItemController alloc] initWithModelIndex:i
121 menuController:self] autorelease];
122
123 // Adjust the name field to fit the string.
124 [GTMUILocalizerAndLayoutTweaker sizeToFitView:itemController.nameField];
125
126 // Expand the size of the window if required to fit the menu item.
127 width = std::max(width,
128 NSMaxX([itemController.nameField frame]) + kRightMargin);
129
130 // Add the item to the content view.
131 [[itemController view] setFrameOrigin:NSMakePoint(0, yOffset)];
132 [contentView addSubview:[itemController view]];
133 yOffset += NSHeight([[itemController view] frame]);
134
135 // Keep track of the view controller.
136 [items_ addObject:itemController];
137 }
138
139 // Leave some space at the top of the menu.
140 yOffset += kVerticalPadding;
141
142 // Set the window frame, clamping the width at a sensible max.
143 NSRect frame = [[self window] frame];
144 frame.size.height = yOffset;
145 frame.size.width = std::min(width, kBubbleMaxWidth);
146 [[self window] setFrame:frame display:YES];
147 }
148
149 - (NSMutableArray*)items {
150 return items_.get();
151 }
152
153 - (void)keyDown:(NSEvent*)theEvent {
154 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
155 }
156
157 - (void)moveDown:(id)sender {
158 [self highlightNextItemByDelta:-1];
159 }
160
161 - (void)moveUp:(id)sender {
162 [self highlightNextItemByDelta:1];
163 }
164
165 - (void)highlightNextItemByDelta:(NSInteger)delta {
166 NSUInteger count = [items_ count];
167 if (count == 0)
168 return;
169
170 NSInteger old_index = -1;
171 for (NSUInteger i = 0; i < count; ++i) {
172 if ([[items_ objectAtIndex:i] isHighlighted]) {
173 old_index = i;
174 break;
175 }
176 }
177
178 NSInteger new_index;
179 // If nothing is selected then start at the top if we're going down and start
180 // at the bottom if we're going up.
181 if (old_index == -1)
182 new_index = delta < 0 ? (count - 1) : 0;
183 else
184 new_index = old_index + delta;
185
186 // Cap the index. We don't wrap around to match the behavior of Mac menus.
187 new_index =
188 std::min(std::max(static_cast<NSInteger>(0), new_index),
189 static_cast<NSInteger>(count - 1));
190
191 [self highlightItem:[items_ objectAtIndex:new_index]];
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 iconView = iconView_;
219 @synthesize nameField = nameField_;
220 @synthesize controller = controller_;
221
222 - (id)initWithModelIndex:(size_t)modelIndex
223 menuController:(ActionBoxMenuBubbleController*)controller {
224 if ((self = [super initWithNibName:@"ActionBoxMenuItem"
225 bundle:base::mac::FrameworkBundle()])) {
226 modelIndex_ = modelIndex;
227 controller_ = controller;
228
229 [self loadView];
230
231 gfx::Image icon = gfx::Image();
232
233 if ([controller model]->GetIconAt(modelIndex_, &icon))
234 self.iconView.image = icon.ToNSImage();
235 else
236 self.iconView.image = nil;
237
238 [controller model]->GetIconAt(modelIndex_, &icon);
239
240 self.iconView.image = icon.ToNSImage();
241 self.nameField.stringValue = base::SysUTF16ToNSString(
242 controller.model->GetLabelAt(modelIndex_));
243 }
244 return self;
245 }
246
247 - (void)dealloc {
248 static_cast<ActionBoxMenuItemView*>(self.view).viewController = nil;
249 [super dealloc];
250 }
251
252 - (void)highlightForEventType:(NSEventType)type {
253 switch (type) {
254 case NSMouseEntered:
255 [controller_ highlightItem:self];
256 break;
257
258 case NSMouseExited:
259 [controller_ highlightItem:nil];
260 break;
261
262 default:
263 NOTREACHED();
264 };
265 }
266
267 - (IBAction)itemSelected:(id)sender {
268 [controller_ itemSelected:self];
269 }
270
271 - (void)setIsHighlighted:(BOOL)isHighlighted {
272 if (isHighlighted_ == isHighlighted)
273 return;
274
275 isHighlighted_ = isHighlighted;
276 [[self view] setNeedsDisplay:YES];
277 }
278
279 @end
280
281 // Items from the action box menu //////////////////////////////////////////////
282
283 @implementation ActionBoxMenuItemView
284
285 @synthesize viewController = viewController_;
286
287 - (void)awakeFromNib {
288 [self updateTrackingAreas];
Scott Hess - ex-Googler 2012/10/12 23:02:47 You sure you need this? At this point, I wouldn't
beaudoin 2012/10/13 00:20:55 Done.
289 }
290
291 - (void)updateTrackingAreas {
292 if (trackingArea_.get())
293 [self removeTrackingArea:trackingArea_.get()];
294
295 trackingArea_.reset(
296 [[CrTrackingArea alloc] initWithRect:[self bounds]
297 options:NSTrackingMouseEnteredAndExited |
298 NSTrackingActiveInKeyWindow
299 owner:self
300 userInfo:nil]);
301 [self addTrackingArea:trackingArea_.get()];
302
303 [super updateTrackingAreas];
304 }
305
306 - (BOOL)acceptsFirstResponder {
307 return YES;
308 }
309
310 - (void)mouseDown:(NSEvent*)theEvent {
311 }
312
313 - (void)mouseUp:(NSEvent*)theEvent {
314 [viewController_ itemSelected:self];
315 }
316
317 - (void)mouseEntered:(id)sender {
318 [viewController_ highlightForEventType:[[NSApp currentEvent] type]];
319 [self setNeedsDisplay:YES];
320 }
321
322 - (void)mouseExited:(id)sender {
323 [viewController_ highlightForEventType:[[NSApp currentEvent] type]];
324 [self setNeedsDisplay:YES];
325 }
326
327 - (void)drawRect:(NSRect)dirtyRect {
328 NSColor* backgroundColor = nil;
329 if ([viewController_ isHighlighted]) {
330 backgroundColor = [NSColor colorWithDeviceRed:0.0
331 green:0.0
332 blue:0.0
333 alpha:0.06];
Scott Hess - ex-Googler 2012/10/12 23:02:47 Could probably get by with +colorWithDeviceWhite:0
beaudoin 2012/10/13 00:20:55 As in: The UI designer said 6%? :) Done.
334 } else {
335 backgroundColor = [NSColor clearColor];
336 }
337
338 [backgroundColor set];
339 [NSBezierPath fillRect:[self bounds]];
340 }
341
342 // Make sure the element is focusable for accessibility.
343 - (BOOL)canBecomeKeyView {
344 return YES;
345 }
346
347 - (BOOL)accessibilityIsIgnored {
348 return NO;
349 }
350
351 - (NSArray*)accessibilityAttributeNames {
352 NSMutableArray* attributes =
353 [[super accessibilityAttributeNames] mutableCopy];
Scott Hess - ex-Googler 2012/10/12 23:02:47 I would pull the -autorelease right into here so t
beaudoin 2012/10/13 00:20:55 Done.
354 [attributes addObject:NSAccessibilityTitleAttribute];
355 [attributes addObject:NSAccessibilityEnabledAttribute];
356
357 return [attributes autorelease];
358 }
359
360 - (NSArray*)accessibilityActionNames {
361 NSArray* parentActions = [super accessibilityActionNames];
362 return [parentActions arrayByAddingObject:NSAccessibilityPressAction];
363 }
364
365 - (id)accessibilityAttributeValue:(NSString*)attribute {
366 if ([attribute isEqual:NSAccessibilityRoleAttribute])
367 return NSAccessibilityButtonRole;
368
369 if ([attribute isEqual:NSAccessibilityRoleDescriptionAttribute])
370 return NSAccessibilityRoleDescription(NSAccessibilityButtonRole, nil);
371
372 if ([attribute isEqual:NSAccessibilityEnabledAttribute])
373 return [NSNumber numberWithBool:YES];
374
375 return [super accessibilityAttributeValue:attribute];
376 }
377
378 - (void)accessibilityPerformAction:(NSString*)action {
379 if ([action isEqual:NSAccessibilityPressAction]) {
380 [viewController_ itemSelected:self];
381 return;
382 }
383
384 [super accessibilityPerformAction:action];
385 }
386
387 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698