OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 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/toolbar_controller.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "app/l10n_util.h" | |
10 #include "app/l10n_util_mac.h" | |
11 #include "app/mac/nsimage_cache.h" | |
12 #include "app/resource_bundle.h" | |
13 #include "base/mac/mac_util.h" | |
14 #include "base/singleton.h" | |
15 #include "base/sys_string_conversions.h" | |
16 #include "chrome/app/chrome_command_ids.h" | |
17 #include "chrome/browser/autocomplete/autocomplete.h" | |
18 #include "chrome/browser/autocomplete/autocomplete_classifier.h" | |
19 #include "chrome/browser/autocomplete/autocomplete_edit_view.h" | |
20 #include "chrome/browser/autocomplete/autocomplete_match.h" | |
21 #include "chrome/browser/background_page_tracker.h" | |
22 #include "chrome/browser/net/url_fixer_upper.h" | |
23 #include "chrome/browser/prefs/pref_service.h" | |
24 #include "chrome/browser/profiles/profile.h" | |
25 #include "chrome/browser/search_engines/template_url_model.h" | |
26 #include "chrome/browser/tab_contents/tab_contents.h" | |
27 #include "chrome/browser/themes/browser_theme_provider.h" | |
28 #include "chrome/browser/upgrade_detector.h" | |
29 #include "chrome/browser/ui/browser.h" | |
30 #include "chrome/browser/ui/browser_window.h" | |
31 #import "chrome/browser/ui/cocoa/accelerators_cocoa.h" | |
32 #import "chrome/browser/ui/cocoa/back_forward_menu_controller.h" | |
33 #import "chrome/browser/ui/cocoa/background_gradient_view.h" | |
34 #import "chrome/browser/ui/cocoa/encoding_menu_controller_delegate_mac.h" | |
35 #import "chrome/browser/ui/cocoa/extensions/browser_action_button.h" | |
36 #import "chrome/browser/ui/cocoa/extensions/browser_actions_container_view.h" | |
37 #import "chrome/browser/ui/cocoa/extensions/browser_actions_controller.h" | |
38 #import "chrome/browser/ui/cocoa/gradient_button_cell.h" | |
39 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h" | |
40 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h" | |
41 #import "chrome/browser/ui/cocoa/menu_button.h" | |
42 #import "chrome/browser/ui/cocoa/menu_controller.h" | |
43 #import "chrome/browser/ui/cocoa/reload_button.h" | |
44 #import "chrome/browser/ui/cocoa/toolbar_view.h" | |
45 #import "chrome/browser/ui/cocoa/view_id_util.h" | |
46 #import "chrome/browser/ui/cocoa/wrench_menu/wrench_menu_controller.h" | |
47 #include "chrome/browser/ui/toolbar/toolbar_model.h" | |
48 #include "chrome/browser/ui/toolbar/wrench_menu_model.h" | |
49 #include "chrome/common/notification_details.h" | |
50 #include "chrome/common/notification_observer.h" | |
51 #include "chrome/common/notification_service.h" | |
52 #include "chrome/common/notification_type.h" | |
53 #include "chrome/common/pref_names.h" | |
54 #include "gfx/rect.h" | |
55 #include "grit/chromium_strings.h" | |
56 #include "grit/generated_resources.h" | |
57 #include "grit/theme_resources.h" | |
58 #include "ui/base/models/accelerator_cocoa.h" | |
59 #include "ui/base/models/menu_model.h" | |
60 | |
61 namespace { | |
62 | |
63 // Names of images in the bundle for buttons. | |
64 NSString* const kBackButtonImageName = @"back_Template.pdf"; | |
65 NSString* const kForwardButtonImageName = @"forward_Template.pdf"; | |
66 NSString* const kReloadButtonReloadImageName = @"reload_Template.pdf"; | |
67 NSString* const kReloadButtonStopImageName = @"stop_Template.pdf"; | |
68 NSString* const kHomeButtonImageName = @"home_Template.pdf"; | |
69 NSString* const kWrenchButtonImageName = @"tools_Template.pdf"; | |
70 | |
71 // Height of the toolbar in pixels when the bookmark bar is closed. | |
72 const CGFloat kBaseToolbarHeight = 35.0; | |
73 | |
74 // The minimum width of the location bar in pixels. | |
75 const CGFloat kMinimumLocationBarWidth = 100.0; | |
76 | |
77 // The duration of any animation that occurs within the toolbar in seconds. | |
78 const CGFloat kAnimationDuration = 0.2; | |
79 | |
80 // The amount of left padding that the wrench menu should have. | |
81 const CGFloat kWrenchMenuLeftPadding = 3.0; | |
82 | |
83 } // namespace | |
84 | |
85 @interface ToolbarController(Private) | |
86 - (void)addAccessibilityDescriptions; | |
87 - (void)initCommandStatus:(CommandUpdater*)commands; | |
88 - (void)prefChanged:(std::string*)prefName; | |
89 - (BackgroundGradientView*)backgroundGradientView; | |
90 - (void)toolbarFrameChanged; | |
91 - (void)pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:(BOOL)animate; | |
92 - (void)maintainMinimumLocationBarWidth; | |
93 - (void)adjustBrowserActionsContainerForNewWindow:(NSNotification*)notification; | |
94 - (void)browserActionsContainerDragged:(NSNotification*)notification; | |
95 - (void)browserActionsContainerDragFinished:(NSNotification*)notification; | |
96 - (void)browserActionsVisibilityChanged:(NSNotification*)notification; | |
97 - (void)adjustLocationSizeBy:(CGFloat)dX animate:(BOOL)animate; | |
98 - (void)badgeWrenchMenuIfNeeded; | |
99 @end | |
100 | |
101 namespace ToolbarControllerInternal { | |
102 | |
103 // A C++ delegate that handles the accelerators in the wrench menu. | |
104 class WrenchAcceleratorDelegate : public ui::AcceleratorProvider { | |
105 public: | |
106 virtual bool GetAcceleratorForCommandId(int command_id, | |
107 ui::Accelerator* accelerator_generic) { | |
108 // Downcast so that when the copy constructor is invoked below, the key | |
109 // string gets copied, too. | |
110 ui::AcceleratorCocoa* out_accelerator = | |
111 static_cast<ui::AcceleratorCocoa*>(accelerator_generic); | |
112 AcceleratorsCocoa* keymap = AcceleratorsCocoa::GetInstance(); | |
113 const ui::AcceleratorCocoa* accelerator = | |
114 keymap->GetAcceleratorForCommand(command_id); | |
115 if (accelerator) { | |
116 *out_accelerator = *accelerator; | |
117 return true; | |
118 } | |
119 return false; | |
120 } | |
121 }; | |
122 | |
123 // A class registered for C++ notifications. This is used to detect changes in | |
124 // preferences and upgrade available notifications. Bridges the notification | |
125 // back to the ToolbarController. | |
126 class NotificationBridge : public NotificationObserver { | |
127 public: | |
128 explicit NotificationBridge(ToolbarController* controller) | |
129 : controller_(controller) { | |
130 registrar_.Add(this, NotificationType::UPGRADE_RECOMMENDED, | |
131 NotificationService::AllSources()); | |
132 registrar_.Add(this, | |
133 NotificationType::BACKGROUND_PAGE_TRACKER_CHANGED, | |
134 NotificationService::AllSources()); | |
135 } | |
136 | |
137 // Overridden from NotificationObserver: | |
138 virtual void Observe(NotificationType type, | |
139 const NotificationSource& source, | |
140 const NotificationDetails& details) { | |
141 switch (type.value) { | |
142 case NotificationType::PREF_CHANGED: | |
143 [controller_ prefChanged:Details<std::string>(details).ptr()]; | |
144 break; | |
145 case NotificationType::BACKGROUND_PAGE_TRACKER_CHANGED: | |
146 // fall-through | |
147 case NotificationType::UPGRADE_RECOMMENDED: | |
148 [controller_ badgeWrenchMenuIfNeeded]; | |
149 break; | |
150 default: | |
151 NOTREACHED(); | |
152 } | |
153 } | |
154 | |
155 private: | |
156 ToolbarController* controller_; // weak, owns us | |
157 | |
158 NotificationRegistrar registrar_; | |
159 }; | |
160 | |
161 } // namespace ToolbarControllerInternal | |
162 | |
163 @implementation ToolbarController | |
164 | |
165 - (id)initWithModel:(ToolbarModel*)model | |
166 commands:(CommandUpdater*)commands | |
167 profile:(Profile*)profile | |
168 browser:(Browser*)browser | |
169 resizeDelegate:(id<ViewResizer>)resizeDelegate | |
170 nibFileNamed:(NSString*)nibName { | |
171 DCHECK(model && commands && profile && [nibName length]); | |
172 if ((self = [super initWithNibName:nibName | |
173 bundle:base::mac::MainAppBundle()])) { | |
174 toolbarModel_ = model; | |
175 commands_ = commands; | |
176 profile_ = profile; | |
177 browser_ = browser; | |
178 resizeDelegate_ = resizeDelegate; | |
179 hasToolbar_ = YES; | |
180 hasLocationBar_ = YES; | |
181 | |
182 // Register for notifications about state changes for the toolbar buttons | |
183 commandObserver_.reset(new CommandObserverBridge(self, commands)); | |
184 commandObserver_->ObserveCommand(IDC_BACK); | |
185 commandObserver_->ObserveCommand(IDC_FORWARD); | |
186 commandObserver_->ObserveCommand(IDC_RELOAD); | |
187 commandObserver_->ObserveCommand(IDC_HOME); | |
188 commandObserver_->ObserveCommand(IDC_BOOKMARK_PAGE); | |
189 } | |
190 return self; | |
191 } | |
192 | |
193 - (id)initWithModel:(ToolbarModel*)model | |
194 commands:(CommandUpdater*)commands | |
195 profile:(Profile*)profile | |
196 browser:(Browser*)browser | |
197 resizeDelegate:(id<ViewResizer>)resizeDelegate { | |
198 if ((self = [self initWithModel:model | |
199 commands:commands | |
200 profile:profile | |
201 browser:browser | |
202 resizeDelegate:resizeDelegate | |
203 nibFileNamed:@"Toolbar"])) { | |
204 } | |
205 return self; | |
206 } | |
207 | |
208 | |
209 - (void)dealloc { | |
210 // Unset ViewIDs of toolbar elements. | |
211 // ViewIDs of |toolbarView|, |reloadButton_|, |locationBar_| and | |
212 // |browserActionsContainerView_| are handled by themselves. | |
213 view_id_util::UnsetID(backButton_); | |
214 view_id_util::UnsetID(forwardButton_); | |
215 view_id_util::UnsetID(homeButton_); | |
216 view_id_util::UnsetID(wrenchButton_); | |
217 | |
218 // Make sure any code in the base class which assumes [self view] is | |
219 // the "parent" view continues to work. | |
220 hasToolbar_ = YES; | |
221 hasLocationBar_ = YES; | |
222 | |
223 [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
224 | |
225 if (trackingArea_.get()) | |
226 [[self view] removeTrackingArea:trackingArea_.get()]; | |
227 [super dealloc]; | |
228 } | |
229 | |
230 // Called after the view is done loading and the outlets have been hooked up. | |
231 // Now we can hook up bridges that rely on UI objects such as the location | |
232 // bar and button state. | |
233 - (void)awakeFromNib { | |
234 // A bug in AppKit (<rdar://7298597>, <http://openradar.me/7298597>) causes | |
235 // images loaded directly from nibs in a framework to not get their "template" | |
236 // flags set properly. Thus, despite the images being set on the buttons in | |
237 // the xib, we must set them in code. | |
238 [backButton_ setImage:app::mac::GetCachedImageWithName(kBackButtonImageName)]; | |
239 [forwardButton_ setImage: | |
240 app::mac::GetCachedImageWithName(kForwardButtonImageName)]; | |
241 [reloadButton_ setImage: | |
242 app::mac::GetCachedImageWithName(kReloadButtonReloadImageName)]; | |
243 [homeButton_ setImage: | |
244 app::mac::GetCachedImageWithName(kHomeButtonImageName)]; | |
245 [wrenchButton_ setImage: | |
246 app::mac::GetCachedImageWithName(kWrenchButtonImageName)]; | |
247 [self badgeWrenchMenuIfNeeded]; | |
248 | |
249 [wrenchButton_ setOpenMenuOnClick:YES]; | |
250 | |
251 [backButton_ setShowsBorderOnlyWhileMouseInside:YES]; | |
252 [forwardButton_ setShowsBorderOnlyWhileMouseInside:YES]; | |
253 [reloadButton_ setShowsBorderOnlyWhileMouseInside:YES]; | |
254 [homeButton_ setShowsBorderOnlyWhileMouseInside:YES]; | |
255 [wrenchButton_ setShowsBorderOnlyWhileMouseInside:YES]; | |
256 | |
257 [self initCommandStatus:commands_]; | |
258 locationBarView_.reset(new LocationBarViewMac(locationBar_, | |
259 commands_, toolbarModel_, | |
260 profile_, browser_)); | |
261 [locationBar_ setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]]; | |
262 // Register pref observers for the optional home and page/options buttons | |
263 // and then add them to the toolbar based on those prefs. | |
264 notificationBridge_.reset( | |
265 new ToolbarControllerInternal::NotificationBridge(self)); | |
266 PrefService* prefs = profile_->GetPrefs(); | |
267 showHomeButton_.Init(prefs::kShowHomeButton, prefs, | |
268 notificationBridge_.get()); | |
269 showPageOptionButtons_.Init(prefs::kShowPageOptionsButtons, prefs, | |
270 notificationBridge_.get()); | |
271 [self showOptionalHomeButton]; | |
272 [self installWrenchMenu]; | |
273 | |
274 // Create the controllers for the back/forward menus. | |
275 backMenuController_.reset([[BackForwardMenuController alloc] | |
276 initWithBrowser:browser_ | |
277 modelType:BACK_FORWARD_MENU_TYPE_BACK | |
278 button:backButton_]); | |
279 forwardMenuController_.reset([[BackForwardMenuController alloc] | |
280 initWithBrowser:browser_ | |
281 modelType:BACK_FORWARD_MENU_TYPE_FORWARD | |
282 button:forwardButton_]); | |
283 | |
284 // For a popup window, the toolbar is really just a location bar | |
285 // (see override for [ToolbarController view], below). When going | |
286 // fullscreen, we remove the toolbar controller's view from the view | |
287 // hierarchy. Calling [locationBar_ removeFromSuperview] when going | |
288 // fullscreen causes it to get released, making us unhappy | |
289 // (http://crbug.com/18551). We avoid the problem by incrementing | |
290 // the retain count of the location bar; use of the scoped object | |
291 // helps us remember to release it. | |
292 locationBarRetainer_.reset([locationBar_ retain]); | |
293 trackingArea_.reset( | |
294 [[NSTrackingArea alloc] initWithRect:NSZeroRect // Ignored | |
295 options:NSTrackingMouseMoved | | |
296 NSTrackingInVisibleRect | | |
297 NSTrackingMouseEnteredAndExited | | |
298 NSTrackingActiveAlways | |
299 owner:self | |
300 userInfo:nil]); | |
301 NSView* toolbarView = [self view]; | |
302 [toolbarView addTrackingArea:trackingArea_.get()]; | |
303 | |
304 // If the user has any Browser Actions installed, the container view for them | |
305 // may have to be resized depending on the width of the toolbar frame. | |
306 [toolbarView setPostsFrameChangedNotifications:YES]; | |
307 [[NSNotificationCenter defaultCenter] | |
308 addObserver:self | |
309 selector:@selector(toolbarFrameChanged) | |
310 name:NSViewFrameDidChangeNotification | |
311 object:toolbarView]; | |
312 | |
313 // Set ViewIDs for toolbar elements which don't have their dedicated class. | |
314 // ViewIDs of |toolbarView|, |reloadButton_|, |locationBar_| and | |
315 // |browserActionsContainerView_| are handled by themselves. | |
316 view_id_util::SetID(backButton_, VIEW_ID_BACK_BUTTON); | |
317 view_id_util::SetID(forwardButton_, VIEW_ID_FORWARD_BUTTON); | |
318 view_id_util::SetID(homeButton_, VIEW_ID_HOME_BUTTON); | |
319 view_id_util::SetID(wrenchButton_, VIEW_ID_APP_MENU); | |
320 | |
321 [self addAccessibilityDescriptions]; | |
322 } | |
323 | |
324 - (void)addAccessibilityDescriptions { | |
325 // Set accessibility descriptions. http://openradar.appspot.com/7496255 | |
326 NSString* description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_BACK); | |
327 [[backButton_ cell] | |
328 accessibilitySetOverrideValue:description | |
329 forAttribute:NSAccessibilityDescriptionAttribute]; | |
330 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_FORWARD); | |
331 [[forwardButton_ cell] | |
332 accessibilitySetOverrideValue:description | |
333 forAttribute:NSAccessibilityDescriptionAttribute]; | |
334 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_RELOAD); | |
335 [[reloadButton_ cell] | |
336 accessibilitySetOverrideValue:description | |
337 forAttribute:NSAccessibilityDescriptionAttribute]; | |
338 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_HOME); | |
339 [[homeButton_ cell] | |
340 accessibilitySetOverrideValue:description | |
341 forAttribute:NSAccessibilityDescriptionAttribute]; | |
342 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_LOCATION); | |
343 [[locationBar_ cell] | |
344 accessibilitySetOverrideValue:description | |
345 forAttribute:NSAccessibilityDescriptionAttribute]; | |
346 description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_APP); | |
347 [[wrenchButton_ cell] | |
348 accessibilitySetOverrideValue:description | |
349 forAttribute:NSAccessibilityDescriptionAttribute]; | |
350 } | |
351 | |
352 - (void)mouseExited:(NSEvent*)theEvent { | |
353 [[hoveredButton_ cell] setMouseInside:NO animate:YES]; | |
354 [hoveredButton_ release]; | |
355 hoveredButton_ = nil; | |
356 } | |
357 | |
358 - (NSButton*)hoverButtonForEvent:(NSEvent*)theEvent { | |
359 NSButton* targetView = (NSButton*)[[self view] | |
360 hitTest:[theEvent locationInWindow]]; | |
361 | |
362 // Only interpret the view as a hoverButton_ if it's both button and has a | |
363 // button cell that cares. GradientButtonCell derived cells care. | |
364 if (([targetView isKindOfClass:[NSButton class]]) && | |
365 ([[targetView cell] | |
366 respondsToSelector:@selector(setMouseInside:animate:)])) | |
367 return targetView; | |
368 return nil; | |
369 } | |
370 | |
371 - (void)mouseMoved:(NSEvent*)theEvent { | |
372 NSButton* targetView = [self hoverButtonForEvent:theEvent]; | |
373 if (hoveredButton_ != targetView) { | |
374 [[hoveredButton_ cell] setMouseInside:NO animate:YES]; | |
375 [[targetView cell] setMouseInside:YES animate:YES]; | |
376 [hoveredButton_ release]; | |
377 hoveredButton_ = [targetView retain]; | |
378 } | |
379 } | |
380 | |
381 - (void)mouseEntered:(NSEvent*)event { | |
382 [self mouseMoved:event]; | |
383 } | |
384 | |
385 - (LocationBarViewMac*)locationBarBridge { | |
386 return locationBarView_.get(); | |
387 } | |
388 | |
389 - (void)focusLocationBar:(BOOL)selectAll { | |
390 if (locationBarView_.get()) | |
391 locationBarView_->FocusLocation(selectAll ? true : false); | |
392 } | |
393 | |
394 // Called when the state for a command changes to |enabled|. Update the | |
395 // corresponding UI element. | |
396 - (void)enabledStateChangedForCommand:(NSInteger)command enabled:(BOOL)enabled { | |
397 NSButton* button = nil; | |
398 switch (command) { | |
399 case IDC_BACK: | |
400 button = backButton_; | |
401 break; | |
402 case IDC_FORWARD: | |
403 button = forwardButton_; | |
404 break; | |
405 case IDC_HOME: | |
406 button = homeButton_; | |
407 break; | |
408 } | |
409 [button setEnabled:enabled]; | |
410 } | |
411 | |
412 // Init the enabled state of the buttons on the toolbar to match the state in | |
413 // the controller. | |
414 - (void)initCommandStatus:(CommandUpdater*)commands { | |
415 [backButton_ setEnabled:commands->IsCommandEnabled(IDC_BACK) ? YES : NO]; | |
416 [forwardButton_ | |
417 setEnabled:commands->IsCommandEnabled(IDC_FORWARD) ? YES : NO]; | |
418 [reloadButton_ setEnabled:YES]; | |
419 [homeButton_ setEnabled:commands->IsCommandEnabled(IDC_HOME) ? YES : NO]; | |
420 } | |
421 | |
422 - (void)updateToolbarWithContents:(TabContents*)tab | |
423 shouldRestoreState:(BOOL)shouldRestore { | |
424 locationBarView_->Update(tab, shouldRestore ? true : false); | |
425 | |
426 [locationBar_ updateCursorAndToolTipRects]; | |
427 | |
428 if (browserActionsController_.get()) { | |
429 [browserActionsController_ update]; | |
430 } | |
431 } | |
432 | |
433 - (void)setStarredState:(BOOL)isStarred { | |
434 locationBarView_->SetStarred(isStarred ? true : false); | |
435 } | |
436 | |
437 - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force { | |
438 [reloadButton_ setIsLoading:isLoading force:force]; | |
439 } | |
440 | |
441 - (void)setHasToolbar:(BOOL)toolbar hasLocationBar:(BOOL)locBar { | |
442 [self view]; // Force nib loading. | |
443 | |
444 hasToolbar_ = toolbar; | |
445 | |
446 // If there's a toolbar, there must be a location bar. | |
447 DCHECK((toolbar && locBar) || !toolbar); | |
448 hasLocationBar_ = toolbar ? YES : locBar; | |
449 | |
450 // Decide whether to hide/show based on whether there's a location bar. | |
451 [[self view] setHidden:!hasLocationBar_]; | |
452 | |
453 // Make location bar not editable when in a pop-up. | |
454 locationBarView_->SetEditable(toolbar); | |
455 } | |
456 | |
457 - (NSView*)view { | |
458 if (hasToolbar_) | |
459 return [super view]; | |
460 return locationBar_; | |
461 } | |
462 | |
463 // (Private) Returns the backdrop to the toolbar. | |
464 - (BackgroundGradientView*)backgroundGradientView { | |
465 // We really do mean |[super view]|; see our override of |-view|. | |
466 DCHECK([[super view] isKindOfClass:[BackgroundGradientView class]]); | |
467 return (BackgroundGradientView*)[super view]; | |
468 } | |
469 | |
470 - (id)customFieldEditorForObject:(id)obj { | |
471 if (obj == locationBar_) { | |
472 // Lazilly construct Field editor, Cocoa UI code always runs on the | |
473 // same thread, so there shoudn't be a race condition here. | |
474 if (autocompleteTextFieldEditor_.get() == nil) { | |
475 autocompleteTextFieldEditor_.reset( | |
476 [[AutocompleteTextFieldEditor alloc] init]); | |
477 } | |
478 | |
479 // This needs to be called every time, otherwise notifications | |
480 // aren't sent correctly. | |
481 DCHECK(autocompleteTextFieldEditor_.get()); | |
482 [autocompleteTextFieldEditor_.get() setFieldEditor:YES]; | |
483 return autocompleteTextFieldEditor_.get(); | |
484 } | |
485 return nil; | |
486 } | |
487 | |
488 // Returns an array of views in the order of the outlets above. | |
489 - (NSArray*)toolbarViews { | |
490 return [NSArray arrayWithObjects:backButton_, forwardButton_, reloadButton_, | |
491 homeButton_, wrenchButton_, locationBar_, | |
492 browserActionsContainerView_, nil]; | |
493 } | |
494 | |
495 // Moves |rect| to the right by |delta|, keeping the right side fixed by | |
496 // shrinking the width to compensate. Passing a negative value for |deltaX| | |
497 // moves to the left and increases the width. | |
498 - (NSRect)adjustRect:(NSRect)rect byAmount:(CGFloat)deltaX { | |
499 NSRect frame = NSOffsetRect(rect, deltaX, 0); | |
500 frame.size.width -= deltaX; | |
501 return frame; | |
502 } | |
503 | |
504 // Show or hide the home button based on the pref. | |
505 - (void)showOptionalHomeButton { | |
506 // Ignore this message if only showing the URL bar. | |
507 if (!hasToolbar_) | |
508 return; | |
509 BOOL hide = showHomeButton_.GetValue() ? NO : YES; | |
510 if (hide == [homeButton_ isHidden]) | |
511 return; // Nothing to do, view state matches pref state. | |
512 | |
513 // Always shift the text field by the width of the home button minus one pixel | |
514 // since the frame edges of each button are right on top of each other. When | |
515 // hiding the button, reverse the direction of the movement (to the left). | |
516 CGFloat moveX = [homeButton_ frame].size.width - 1.0; | |
517 if (hide) | |
518 moveX *= -1; // Reverse the direction of the move. | |
519 | |
520 [locationBar_ setFrame:[self adjustRect:[locationBar_ frame] | |
521 byAmount:moveX]]; | |
522 [homeButton_ setHidden:hide]; | |
523 } | |
524 | |
525 // Install the menu wrench buttons. Calling this repeatedly is inexpensive so it | |
526 // can be done every time the buttons are shown. | |
527 - (void)installWrenchMenu { | |
528 if (wrenchMenuModel_.get()) | |
529 return; | |
530 acceleratorDelegate_.reset( | |
531 new ToolbarControllerInternal::WrenchAcceleratorDelegate()); | |
532 | |
533 wrenchMenuModel_.reset(new WrenchMenuModel( | |
534 acceleratorDelegate_.get(), browser_)); | |
535 [wrenchMenuController_ setModel:wrenchMenuModel_.get()]; | |
536 [wrenchMenuController_ setUseWithPopUpButtonCell:YES]; | |
537 [wrenchButton_ setAttachedMenu:[wrenchMenuController_ menu]]; | |
538 } | |
539 | |
540 - (WrenchMenuController*)wrenchMenuController { | |
541 return wrenchMenuController_; | |
542 } | |
543 | |
544 - (void)badgeWrenchMenuIfNeeded { | |
545 | |
546 int badgeResource = 0; | |
547 if (UpgradeDetector::GetInstance()->notify_upgrade()) { | |
548 badgeResource = IDR_UPDATE_BADGE; | |
549 } else if (BackgroundPageTracker::GetInstance()-> | |
550 GetUnacknowledgedBackgroundPageCount() > 0) { | |
551 badgeResource = IDR_BACKGROUND_BADGE; | |
552 } else { | |
553 // No badge - clear the badge if one is already set. | |
554 if ([[wrenchButton_ cell] overlayImage]) | |
555 [[wrenchButton_ cell] setOverlayImage:nil]; | |
556 return; | |
557 } | |
558 | |
559 NSImage* badge = | |
560 ResourceBundle::GetSharedInstance().GetNativeImageNamed(badgeResource); | |
561 NSImage* wrenchImage = | |
562 app::mac::GetCachedImageWithName(kWrenchButtonImageName); | |
563 NSSize wrenchImageSize = [wrenchImage size]; | |
564 NSSize badgeSize = [badge size]; | |
565 | |
566 scoped_nsobject<NSImage> overlayImage( | |
567 [[NSImage alloc] initWithSize:wrenchImageSize]); | |
568 | |
569 // Draw badge in the upper right corner of the button. | |
570 NSPoint overlayPosition = | |
571 NSMakePoint(wrenchImageSize.width - badgeSize.width, | |
572 wrenchImageSize.height - badgeSize.height); | |
573 | |
574 [overlayImage lockFocus]; | |
575 [badge drawAtPoint:overlayPosition | |
576 fromRect:NSZeroRect | |
577 operation:NSCompositeSourceOver | |
578 fraction:1.0]; | |
579 [overlayImage unlockFocus]; | |
580 | |
581 [[wrenchButton_ cell] setOverlayImage:overlayImage]; | |
582 } | |
583 | |
584 - (void)prefChanged:(std::string*)prefName { | |
585 if (!prefName) return; | |
586 if (*prefName == prefs::kShowHomeButton) { | |
587 [self showOptionalHomeButton]; | |
588 } | |
589 } | |
590 | |
591 - (void)createBrowserActionButtons { | |
592 if (!browserActionsController_.get()) { | |
593 browserActionsController_.reset([[BrowserActionsController alloc] | |
594 initWithBrowser:browser_ | |
595 containerView:browserActionsContainerView_]); | |
596 [[NSNotificationCenter defaultCenter] | |
597 addObserver:self | |
598 selector:@selector(browserActionsContainerDragged:) | |
599 name:kBrowserActionGrippyDraggingNotification | |
600 object:browserActionsController_]; | |
601 [[NSNotificationCenter defaultCenter] | |
602 addObserver:self | |
603 selector:@selector(browserActionsContainerDragFinished:) | |
604 name:kBrowserActionGrippyDragFinishedNotification | |
605 object:browserActionsController_]; | |
606 [[NSNotificationCenter defaultCenter] | |
607 addObserver:self | |
608 selector:@selector(browserActionsVisibilityChanged:) | |
609 name:kBrowserActionVisibilityChangedNotification | |
610 object:browserActionsController_]; | |
611 [[NSNotificationCenter defaultCenter] | |
612 addObserver:self | |
613 selector:@selector(adjustBrowserActionsContainerForNewWindow:) | |
614 name:NSWindowDidBecomeKeyNotification | |
615 object:[[self view] window]]; | |
616 } | |
617 CGFloat containerWidth = [browserActionsContainerView_ isHidden] ? 0.0 : | |
618 NSWidth([browserActionsContainerView_ frame]); | |
619 if (containerWidth > 0.0) | |
620 [self adjustLocationSizeBy:(containerWidth * -1) animate:NO]; | |
621 } | |
622 | |
623 - (void)adjustBrowserActionsContainerForNewWindow: | |
624 (NSNotification*)notification { | |
625 [self toolbarFrameChanged]; | |
626 [[NSNotificationCenter defaultCenter] | |
627 removeObserver:self | |
628 name:NSWindowDidBecomeKeyNotification | |
629 object:[[self view] window]]; | |
630 } | |
631 | |
632 - (void)browserActionsContainerDragged:(NSNotification*)notification { | |
633 CGFloat locationBarWidth = NSWidth([locationBar_ frame]); | |
634 locationBarAtMinSize_ = locationBarWidth <= kMinimumLocationBarWidth; | |
635 [browserActionsContainerView_ setCanDragLeft:!locationBarAtMinSize_]; | |
636 [browserActionsContainerView_ setGrippyPinned:locationBarAtMinSize_]; | |
637 [self adjustLocationSizeBy: | |
638 [browserActionsContainerView_ resizeDeltaX] animate:NO]; | |
639 } | |
640 | |
641 - (void)browserActionsContainerDragFinished:(NSNotification*)notification { | |
642 [browserActionsController_ resizeContainerAndAnimate:YES]; | |
643 [self pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:YES]; | |
644 } | |
645 | |
646 - (void)browserActionsVisibilityChanged:(NSNotification*)notification { | |
647 [self pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:NO]; | |
648 } | |
649 | |
650 - (void)pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:(BOOL)animate { | |
651 CGFloat locationBarXPos = NSMaxX([locationBar_ frame]); | |
652 CGFloat leftDistance; | |
653 | |
654 if ([browserActionsContainerView_ isHidden]) { | |
655 CGFloat edgeXPos = [wrenchButton_ frame].origin.x; | |
656 leftDistance = edgeXPos - locationBarXPos - kWrenchMenuLeftPadding; | |
657 } else { | |
658 NSRect containerFrame = animate ? | |
659 [browserActionsContainerView_ animationEndFrame] : | |
660 [browserActionsContainerView_ frame]; | |
661 | |
662 leftDistance = containerFrame.origin.x - locationBarXPos; | |
663 } | |
664 if (leftDistance != 0.0) | |
665 [self adjustLocationSizeBy:leftDistance animate:animate]; | |
666 } | |
667 | |
668 - (void)maintainMinimumLocationBarWidth { | |
669 CGFloat locationBarWidth = NSWidth([locationBar_ frame]); | |
670 locationBarAtMinSize_ = locationBarWidth <= kMinimumLocationBarWidth; | |
671 if (locationBarAtMinSize_) { | |
672 CGFloat dX = kMinimumLocationBarWidth - locationBarWidth; | |
673 [self adjustLocationSizeBy:dX animate:NO]; | |
674 } | |
675 } | |
676 | |
677 - (void)toolbarFrameChanged { | |
678 // Do nothing if the frame changes but no Browser Action Controller is | |
679 // present. | |
680 if (!browserActionsController_.get()) | |
681 return; | |
682 | |
683 [self maintainMinimumLocationBarWidth]; | |
684 | |
685 if (locationBarAtMinSize_) { | |
686 // Once the grippy is pinned, leave it until it is explicity un-pinned. | |
687 [browserActionsContainerView_ setGrippyPinned:YES]; | |
688 NSRect containerFrame = [browserActionsContainerView_ frame]; | |
689 // Determine how much the container needs to move in case it's overlapping | |
690 // with the location bar. | |
691 CGFloat dX = NSMaxX([locationBar_ frame]) - containerFrame.origin.x; | |
692 containerFrame = NSOffsetRect(containerFrame, dX, 0); | |
693 containerFrame.size.width -= dX; | |
694 [browserActionsContainerView_ setFrame:containerFrame]; | |
695 } else if (!locationBarAtMinSize_ && | |
696 [browserActionsContainerView_ grippyPinned]) { | |
697 // Expand out the container until it hits the saved size, then unpin the | |
698 // grippy. | |
699 // Add 0.1 pixel so that it doesn't hit the minimum width codepath above. | |
700 CGFloat dX = NSWidth([locationBar_ frame]) - | |
701 (kMinimumLocationBarWidth + 0.1); | |
702 NSRect containerFrame = [browserActionsContainerView_ frame]; | |
703 containerFrame = NSOffsetRect(containerFrame, -dX, 0); | |
704 containerFrame.size.width += dX; | |
705 CGFloat savedContainerWidth = [browserActionsController_ savedWidth]; | |
706 if (NSWidth(containerFrame) >= savedContainerWidth) { | |
707 containerFrame = NSOffsetRect(containerFrame, | |
708 NSWidth(containerFrame) - savedContainerWidth, 0); | |
709 containerFrame.size.width = savedContainerWidth; | |
710 [browserActionsContainerView_ setGrippyPinned:NO]; | |
711 } | |
712 [browserActionsContainerView_ setFrame:containerFrame]; | |
713 [self pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:NO]; | |
714 } | |
715 } | |
716 | |
717 - (void)adjustLocationSizeBy:(CGFloat)dX animate:(BOOL)animate { | |
718 // Ensure that the location bar is in its proper place. | |
719 NSRect locationFrame = [locationBar_ frame]; | |
720 locationFrame.size.width += dX; | |
721 | |
722 if (!animate) { | |
723 [locationBar_ setFrame:locationFrame]; | |
724 return; | |
725 } | |
726 | |
727 [NSAnimationContext beginGrouping]; | |
728 [[NSAnimationContext currentContext] setDuration:kAnimationDuration]; | |
729 [[locationBar_ animator] setFrame:locationFrame]; | |
730 [NSAnimationContext endGrouping]; | |
731 } | |
732 | |
733 - (NSPoint)bookmarkBubblePoint { | |
734 return locationBarView_->GetBookmarkBubblePoint(); | |
735 } | |
736 | |
737 - (CGFloat)desiredHeightForCompression:(CGFloat)compressByHeight { | |
738 // With no toolbar, just ignore the compression. | |
739 return hasToolbar_ ? kBaseToolbarHeight - compressByHeight : | |
740 NSHeight([locationBar_ frame]); | |
741 } | |
742 | |
743 - (void)setDividerOpacity:(CGFloat)opacity { | |
744 BackgroundGradientView* view = [self backgroundGradientView]; | |
745 [view setShowsDivider:(opacity > 0 ? YES : NO)]; | |
746 | |
747 // We may not have a toolbar view (e.g., popup windows only have a location | |
748 // bar). | |
749 if ([view isKindOfClass:[ToolbarView class]]) { | |
750 ToolbarView* toolbarView = (ToolbarView*)view; | |
751 [toolbarView setDividerOpacity:opacity]; | |
752 } | |
753 } | |
754 | |
755 - (BrowserActionsController*)browserActionsController { | |
756 return browserActionsController_.get(); | |
757 } | |
758 | |
759 // (URLDropTargetController protocol) | |
760 - (void)dropURLs:(NSArray*)urls inView:(NSView*)view at:(NSPoint)point { | |
761 // TODO(viettrungluu): This code is more or less copied from the code in | |
762 // |TabStripController|. I'll refactor this soon to make it common and expand | |
763 // its capabilities (e.g., allow text DnD). | |
764 if ([urls count] < 1) { | |
765 NOTREACHED(); | |
766 return; | |
767 } | |
768 | |
769 // TODO(viettrungluu): dropping multiple URLs? | |
770 if ([urls count] > 1) | |
771 NOTIMPLEMENTED(); | |
772 | |
773 // Get the first URL and fix it up. | |
774 GURL url(URLFixerUpper::FixupURL( | |
775 base::SysNSStringToUTF8([urls objectAtIndex:0]), std::string())); | |
776 | |
777 browser_->GetSelectedTabContents()->OpenURL(url, GURL(), CURRENT_TAB, | |
778 PageTransition::TYPED); | |
779 } | |
780 | |
781 // (URLDropTargetController protocol) | |
782 - (void)dropText:(NSString*)text inView:(NSView*)view at:(NSPoint)point { | |
783 // TODO(viettrungluu): This code is more or less copied from the code in | |
784 // |TabStripController|. I'll refactor this soon to make it common and expand | |
785 // its capabilities (e.g., allow text DnD). | |
786 | |
787 // If the input is plain text, classify the input and make the URL. | |
788 AutocompleteMatch match; | |
789 browser_->profile()->GetAutocompleteClassifier()->Classify( | |
790 base::SysNSStringToWide(text), | |
791 std::wstring(), false, &match, NULL); | |
792 GURL url(match.destination_url); | |
793 | |
794 browser_->GetSelectedTabContents()->OpenURL(url, GURL(), CURRENT_TAB, | |
795 PageTransition::TYPED); | |
796 } | |
797 | |
798 // (URLDropTargetController protocol) | |
799 - (void)indicateDropURLsInView:(NSView*)view at:(NSPoint)point { | |
800 // Do nothing. | |
801 } | |
802 | |
803 // (URLDropTargetController protocol) | |
804 - (void)hideDropURLsIndicatorInView:(NSView*)view { | |
805 // Do nothing. | |
806 } | |
807 | |
808 @end | |
OLD | NEW |