toolbar_view.cc revision 3f50c38dc070f4bb515c1b64450dae14f316474e
1// Copyright (c) 2010 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#include "chrome/browser/views/toolbar_view.h"
6
7#include "app/l10n_util.h"
8#include "app/resource_bundle.h"
9#include "base/i18n/number_formatting.h"
10#include "base/utf_string_conversions.h"
11#include "chrome/app/chrome_command_ids.h"
12#include "chrome/browser/accessibility/browser_accessibility_state.h"
13#include "chrome/browser/background_page_tracker.h"
14#include "chrome/browser/metrics/user_metrics.h"
15#include "chrome/browser/prefs/pref_service.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/browser/themes/browser_theme_provider.h"
18#include "chrome/browser/ui/browser.h"
19#include "chrome/browser/ui/browser_window.h"
20#include "chrome/browser/ui/toolbar/wrench_menu_model.h"
21#include "chrome/browser/ui/view_ids.h"
22#include "chrome/browser/ui/views/browser_actions_container.h"
23#include "chrome/browser/ui/views/event_utils.h"
24#include "chrome/browser/ui/views/frame/browser_view.h"
25#include "chrome/browser/upgrade_detector.h"
26#include "chrome/common/notification_service.h"
27#include "chrome/common/pref_names.h"
28#include "gfx/canvas.h"
29#include "gfx/canvas_skia.h"
30#include "gfx/skbitmap_operations.h"
31#include "grit/chromium_strings.h"
32#include "grit/generated_resources.h"
33#include "grit/theme_resources.h"
34#include "views/controls/button/button_dropdown.h"
35#include "views/focus/view_storage.h"
36#include "views/widget/tooltip_manager.h"
37#include "views/window/non_client_view.h"
38#include "views/window/window.h"
39
40#if defined(OS_CHROMEOS)
41#include "chrome/browser/chromeos/cros/cros_library.h"
42#include "chrome/browser/chromeos/cros/update_library.h"
43#include "chrome/browser/chromeos/dom_ui/wrench_menu_ui.h"
44#include "views/controls/menu/menu_2.h"
45#endif
46#include "chrome/browser/views/wrench_menu.h"
47
48#if defined(OS_WIN)
49#include "chrome/browser/enumerate_modules_model_win.h"
50#endif
51
52// The space between items is 4 px in general.
53const int ToolbarView::kStandardSpacing = 4;
54// The top of the toolbar has an edge we have to skip over in addition to the 4
55// px of spacing.
56const int ToolbarView::kVertSpacing = kStandardSpacing + 1;
57// The edge graphics have some built-in spacing/shadowing, so we have to adjust
58// our spacing to make it still appear to be 4 px.
59static const int kEdgeSpacing = ToolbarView::kStandardSpacing - 1;
60// The buttons to the left of the omnibox are close together.
61static const int kButtonSpacing = 1;
62
63static const int kStatusBubbleWidth = 480;
64
65// The length of time to run the upgrade notification animation (the time it
66// takes one pulse to run its course and go back to its original brightness).
67static const int kPulseDuration = 2000;
68
69// How long to wait between pulsating the upgrade notifier.
70static const int kPulsateEveryMs = 8000;
71
72static const int kPopupTopSpacingNonGlass = 3;
73static const int kPopupBottomSpacingNonGlass = 2;
74static const int kPopupBottomSpacingGlass = 1;
75
76// Top margin for the wrench menu badges (badge is placed in the upper right
77// corner of the wrench menu).
78static const int kBadgeTopMargin = 2;
79
80static SkBitmap* kPopupBackgroundEdge = NULL;
81
82////////////////////////////////////////////////////////////////////////////////
83// ToolbarView, public:
84
85ToolbarView::ToolbarView(Browser* browser)
86    : model_(browser->toolbar_model()),
87      back_(NULL),
88      forward_(NULL),
89      reload_(NULL),
90#if defined(OS_CHROMEOS)
91      feedback_(NULL),
92#endif
93      home_(NULL),
94      location_bar_(NULL),
95      browser_actions_(NULL),
96      app_menu_(NULL),
97      profile_(NULL),
98      browser_(browser),
99      profiles_menu_contents_(NULL),
100      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
101      destroyed_flag_(NULL) {
102  SetID(VIEW_ID_TOOLBAR);
103
104  browser_->command_updater()->AddCommandObserver(IDC_BACK, this);
105  browser_->command_updater()->AddCommandObserver(IDC_FORWARD, this);
106  browser_->command_updater()->AddCommandObserver(IDC_HOME, this);
107
108  display_mode_ = browser->SupportsWindowFeature(Browser::FEATURE_TABSTRIP) ?
109      DISPLAYMODE_NORMAL : DISPLAYMODE_LOCATION;
110
111  if (!kPopupBackgroundEdge) {
112    kPopupBackgroundEdge = ResourceBundle::GetSharedInstance().GetBitmapNamed(
113        IDR_LOCATIONBG_POPUPMODE_EDGE);
114  }
115
116  if (!IsUpgradeRecommended()) {
117    registrar_.Add(this, NotificationType::UPGRADE_RECOMMENDED,
118                   NotificationService::AllSources());
119  }
120  registrar_.Add(this, NotificationType::MODULE_INCOMPATIBILITY_DETECTED,
121                 NotificationService::AllSources());
122  registrar_.Add(this,
123                 NotificationType::BACKGROUND_PAGE_TRACKER_CHANGED,
124                 NotificationService::AllSources());
125}
126
127ToolbarView::~ToolbarView() {
128  if (destroyed_flag_)
129    *destroyed_flag_ = true;
130
131  // NOTE: Don't remove the command observers here.  This object gets destroyed
132  // after the Browser (which owns the CommandUpdater), so the CommandUpdater is
133  // already gone.
134}
135
136void ToolbarView::Init(Profile* profile) {
137  back_menu_model_.reset(new BackForwardMenuModel(
138      browser_, BackForwardMenuModel::BACKWARD_MENU));
139  forward_menu_model_.reset(new BackForwardMenuModel(
140      browser_, BackForwardMenuModel::FORWARD_MENU));
141  wrench_menu_model_.reset(new WrenchMenuModel(this, browser_));
142#if defined(OS_CHROMEOS)
143  if (chromeos::MenuUI::IsEnabled()) {
144      wrench_menu_2_.reset(
145          chromeos::WrenchMenuUI::CreateMenu2(wrench_menu_model_.get()));
146  }
147#endif
148  back_ = new views::ButtonDropDown(this, back_menu_model_.get());
149  back_->set_triggerable_event_flags(views::Event::EF_LEFT_BUTTON_DOWN |
150                                     views::Event::EF_MIDDLE_BUTTON_DOWN);
151  back_->set_tag(IDC_BACK);
152  back_->SetImageAlignment(views::ImageButton::ALIGN_RIGHT,
153                           views::ImageButton::ALIGN_TOP);
154  back_->SetTooltipText(
155      UTF16ToWide(l10n_util::GetStringUTF16(IDS_TOOLTIP_BACK)));
156  back_->SetAccessibleName(
157      UTF16ToWide(l10n_util::GetStringUTF16(IDS_ACCNAME_BACK)));
158  back_->SetID(VIEW_ID_BACK_BUTTON);
159
160  forward_ = new views::ButtonDropDown(this, forward_menu_model_.get());
161  forward_->set_triggerable_event_flags(views::Event::EF_LEFT_BUTTON_DOWN |
162                                        views::Event::EF_MIDDLE_BUTTON_DOWN);
163  forward_->set_tag(IDC_FORWARD);
164  forward_->SetTooltipText(
165      UTF16ToWide(l10n_util::GetStringUTF16(IDS_TOOLTIP_FORWARD)));
166  forward_->SetAccessibleName(
167      UTF16ToWide(l10n_util::GetStringUTF16(IDS_ACCNAME_FORWARD)));
168  forward_->SetID(VIEW_ID_FORWARD_BUTTON);
169
170  // Have to create this before |reload_| as |reload_|'s constructor needs it.
171  location_bar_ = new LocationBarView(profile, browser_->command_updater(),
172      model_, this, (display_mode_ == DISPLAYMODE_LOCATION) ?
173          LocationBarView::POPUP : LocationBarView::NORMAL);
174
175  reload_ = new ReloadButton(location_bar_, browser_);
176  reload_->set_triggerable_event_flags(views::Event::EF_LEFT_BUTTON_DOWN |
177                                       views::Event::EF_MIDDLE_BUTTON_DOWN);
178  reload_->set_tag(IDC_RELOAD);
179  reload_->SetTooltipText(
180      UTF16ToWide(l10n_util::GetStringUTF16(IDS_TOOLTIP_RELOAD)));
181  reload_->SetAccessibleName(
182      UTF16ToWide(l10n_util::GetStringUTF16(IDS_ACCNAME_RELOAD)));
183  reload_->SetID(VIEW_ID_RELOAD_BUTTON);
184
185#if defined(OS_CHROMEOS)
186  feedback_ = new views::ImageButton(this);
187  feedback_->set_tag(IDC_FEEDBACK);
188  feedback_->set_triggerable_event_flags(views::Event::EF_LEFT_BUTTON_DOWN |
189                                         views::Event::EF_MIDDLE_BUTTON_DOWN);
190  feedback_->set_tag(IDC_FEEDBACK);
191  feedback_->SetTooltipText(
192      UTF16ToWide(l10n_util::GetStringUTF16(IDS_TOOLTIP_FEEDBACK)));
193  feedback_->SetID(VIEW_ID_FEEDBACK_BUTTON);
194#endif
195
196  home_ = new views::ImageButton(this);
197  home_->set_triggerable_event_flags(views::Event::EF_LEFT_BUTTON_DOWN |
198                                     views::Event::EF_MIDDLE_BUTTON_DOWN);
199  home_->set_tag(IDC_HOME);
200  home_->SetTooltipText(
201      UTF16ToWide(l10n_util::GetStringUTF16(IDS_TOOLTIP_HOME)));
202  home_->SetAccessibleName(
203      UTF16ToWide(l10n_util::GetStringUTF16(IDS_ACCNAME_HOME)));
204  home_->SetID(VIEW_ID_HOME_BUTTON);
205
206  browser_actions_ = new BrowserActionsContainer(browser_, this);
207
208  app_menu_ = new views::MenuButton(NULL, std::wstring(), this, false);
209  app_menu_->set_border(NULL);
210  app_menu_->EnableCanvasFlippingForRTLUI(true);
211  app_menu_->SetAccessibleName(
212      UTF16ToWide(l10n_util::GetStringUTF16(IDS_ACCNAME_APP)));
213  app_menu_->SetTooltipText(UTF16ToWide(l10n_util::GetStringFUTF16(
214      IDS_APPMENU_TOOLTIP,
215      l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))));
216  app_menu_->SetID(VIEW_ID_APP_MENU);
217
218  // Add any necessary badges to the menu item based on the system state.
219  if (IsUpgradeRecommended() || ShouldShowIncompatibilityWarning() ||
220      ShouldShowBackgroundPageBadge()) {
221    UpdateAppMenuBadge();
222  }
223  LoadImages();
224
225  // Always add children in order from left to right, for accessibility.
226  AddChildView(back_);
227  AddChildView(forward_);
228  AddChildView(reload_);
229  AddChildView(home_);
230  AddChildView(location_bar_);
231  AddChildView(browser_actions_);
232#if defined(OS_CHROMEOS)
233  AddChildView(feedback_);
234#endif
235  AddChildView(app_menu_);
236
237  location_bar_->Init();
238  show_home_button_.Init(prefs::kShowHomeButton, profile->GetPrefs(), this);
239  browser_actions_->Init();
240
241  SetProfile(profile);
242
243  // Accessibility specific tooltip text.
244  if (BrowserAccessibilityState::GetInstance()->IsAccessibleBrowser()) {
245    back_->SetTooltipText(
246        UTF16ToWide(l10n_util::GetStringUTF16(IDS_ACCNAME_TOOLTIP_BACK)));
247    forward_->SetTooltipText(
248        UTF16ToWide(l10n_util::GetStringUTF16(IDS_ACCNAME_TOOLTIP_FORWARD)));
249  }
250}
251
252void ToolbarView::SetProfile(Profile* profile) {
253  if (profile != profile_) {
254    profile_ = profile;
255    location_bar_->SetProfile(profile);
256  }
257}
258
259void ToolbarView::Update(TabContents* tab, bool should_restore_state) {
260  if (location_bar_)
261    location_bar_->Update(should_restore_state ? tab : NULL);
262
263  if (browser_actions_)
264    browser_actions_->RefreshBrowserActionViews();
265}
266
267void ToolbarView::SetPaneFocusAndFocusLocationBar(int view_storage_id) {
268  SetPaneFocus(view_storage_id, location_bar_);
269}
270
271void ToolbarView::SetPaneFocusAndFocusAppMenu(int view_storage_id) {
272  SetPaneFocus(view_storage_id, app_menu_);
273}
274
275bool ToolbarView::IsAppMenuFocused() {
276  return app_menu_->HasFocus();
277}
278
279void ToolbarView::AddMenuListener(views::MenuListener* listener) {
280#if defined(OS_CHROMEOS)
281  if (chromeos::MenuUI::IsEnabled()) {
282    DCHECK(wrench_menu_2_.get());
283    wrench_menu_2_->AddMenuListener(listener);
284    return;
285  }
286#endif
287  menu_listeners_.push_back(listener);
288}
289
290void ToolbarView::RemoveMenuListener(views::MenuListener* listener) {
291#if defined(OS_CHROMEOS)
292  if (chromeos::MenuUI::IsEnabled()) {
293    DCHECK(wrench_menu_2_.get());
294    wrench_menu_2_->RemoveMenuListener(listener);
295    return;
296  }
297#endif
298  for (std::vector<views::MenuListener*>::iterator i(menu_listeners_.begin());
299       i != menu_listeners_.end(); ++i) {
300    if (*i == listener) {
301      menu_listeners_.erase(i);
302      return;
303    }
304  }
305}
306
307////////////////////////////////////////////////////////////////////////////////
308// ToolbarView, AccessiblePaneView overrides:
309
310bool ToolbarView::SetPaneFocus(
311    int view_storage_id, views::View* initial_focus) {
312  if (!AccessiblePaneView::SetPaneFocus(view_storage_id, initial_focus))
313    return false;
314
315  location_bar_->SetShowFocusRect(true);
316  return true;
317}
318
319AccessibilityTypes::Role ToolbarView::GetAccessibleRole() {
320  return AccessibilityTypes::ROLE_TOOLBAR;
321}
322
323////////////////////////////////////////////////////////////////////////////////
324// ToolbarView, Menu::BaseControllerDelegate overrides:
325
326bool ToolbarView::GetAcceleratorInfo(int id, menus::Accelerator* accel) {
327  return GetWidget()->GetAccelerator(id, accel);
328}
329
330////////////////////////////////////////////////////////////////////////////////
331// ToolbarView, views::MenuDelegate implementation:
332
333void ToolbarView::RunMenu(views::View* source, const gfx::Point& /* pt */) {
334  DCHECK_EQ(VIEW_ID_APP_MENU, source->GetID());
335
336  bool destroyed_flag = false;
337  destroyed_flag_ = &destroyed_flag;
338#if defined(OS_CHROMEOS)
339  if (chromeos::MenuUI::IsEnabled()) {
340    gfx::Point screen_loc;
341    views::View::ConvertPointToScreen(app_menu_, &screen_loc);
342    gfx::Rect bounds(screen_loc, app_menu_->size());
343    if (base::i18n::IsRTL())
344      bounds.set_x(bounds.x() - app_menu_->size().width());
345    wrench_menu_2_->RunMenuAt(gfx::Point(bounds.right(), bounds.bottom()),
346                              views::Menu2::ALIGN_TOPRIGHT);
347    // TODO(oshima): nuke this once we made decision about go or no go
348    // for domui menu.
349    goto cleanup;
350  }
351#endif
352  wrench_menu_ = new WrenchMenu(browser_);
353  wrench_menu_->Init(wrench_menu_model_.get());
354
355  for (size_t i = 0; i < menu_listeners_.size(); ++i)
356    menu_listeners_[i]->OnMenuOpened();
357
358  wrench_menu_->RunMenu(app_menu_);
359
360#if defined(OS_CHROMEOS)
361cleanup:
362#endif
363  if (destroyed_flag)
364    return;
365  destroyed_flag_ = NULL;
366
367  // Stop showing the background app badge also.
368  BackgroundPageTracker::GetInstance()->AcknowledgeBackgroundPages();
369}
370
371////////////////////////////////////////////////////////////////////////////////
372// ToolbarView, LocationBarView::Delegate implementation:
373
374TabContentsWrapper* ToolbarView::GetTabContentsWrapper() {
375  return browser_->GetSelectedTabContentsWrapper();
376}
377
378InstantController* ToolbarView::GetInstant() {
379  return browser_->instant();
380}
381
382void ToolbarView::OnInputInProgress(bool in_progress) {
383  // The edit should make sure we're only notified when something changes.
384  DCHECK(model_->input_in_progress() != in_progress);
385
386  model_->set_input_in_progress(in_progress);
387  location_bar_->Update(NULL);
388}
389
390////////////////////////////////////////////////////////////////////////////////
391// ToolbarView, CommandUpdater::CommandObserver implementation:
392
393void ToolbarView::EnabledStateChangedForCommand(int id, bool enabled) {
394  views::Button* button = NULL;
395  switch (id) {
396    case IDC_BACK:
397      button = back_;
398      break;
399    case IDC_FORWARD:
400      button = forward_;
401      break;
402    case IDC_HOME:
403      button = home_;
404      break;
405  }
406  if (button)
407    button->SetEnabled(enabled);
408}
409
410////////////////////////////////////////////////////////////////////////////////
411// ToolbarView, views::Button::ButtonListener implementation:
412
413void ToolbarView::ButtonPressed(views::Button* sender,
414                                const views::Event& event) {
415  int command = sender->tag();
416  WindowOpenDisposition disposition =
417      event_utils::DispositionFromEventFlags(sender->mouse_event_flags());
418  if ((disposition == CURRENT_TAB) &&
419      ((command == IDC_BACK) || (command == IDC_FORWARD))) {
420    // Forcibly reset the location bar, since otherwise it won't discard any
421    // ongoing user edits, since it doesn't realize this is a user-initiated
422    // action.
423    location_bar_->Revert();
424  }
425  browser_->ExecuteCommandWithDisposition(command, disposition);
426}
427
428////////////////////////////////////////////////////////////////////////////////
429// ToolbarView, NotificationObserver implementation:
430
431void ToolbarView::Observe(NotificationType type,
432                          const NotificationSource& source,
433                          const NotificationDetails& details) {
434  if (type == NotificationType::PREF_CHANGED) {
435    std::string* pref_name = Details<std::string>(details).ptr();
436    if (*pref_name == prefs::kShowHomeButton) {
437      Layout();
438      SchedulePaint();
439    }
440  } else if (type == NotificationType::UPGRADE_RECOMMENDED) {
441    UpdateAppMenuBadge();
442  } else if (type == NotificationType::MODULE_INCOMPATIBILITY_DETECTED) {
443    bool confirmed_bad = *Details<bool>(details).ptr();
444    if (confirmed_bad)
445      UpdateAppMenuBadge();
446  } else if (type ==
447      NotificationType::BACKGROUND_PAGE_TRACKER_CHANGED) {
448    // Force a repaint to add/remove the badge.
449    UpdateAppMenuBadge();
450  }
451}
452
453////////////////////////////////////////////////////////////////////////////////
454// ToolbarView, menus::AcceleratorProvider implementation:
455
456bool ToolbarView::GetAcceleratorForCommandId(int command_id,
457    menus::Accelerator* accelerator) {
458  // The standard Ctrl-X, Ctrl-V and Ctrl-C are not defined as accelerators
459  // anywhere so we need to check for them explicitly here.
460  // TODO(cpu) Bug 1109102. Query WebKit land for the actual bindings.
461  switch (command_id) {
462    case IDC_CUT:
463      *accelerator = views::Accelerator(app::VKEY_X, false, true, false);
464      return true;
465    case IDC_COPY:
466      *accelerator = views::Accelerator(app::VKEY_C, false, true, false);
467      return true;
468    case IDC_PASTE:
469      *accelerator = views::Accelerator(app::VKEY_V, false, true, false);
470      return true;
471  }
472  // Else, we retrieve the accelerator information from the frame.
473  return GetWidget()->GetAccelerator(command_id, accelerator);
474}
475
476////////////////////////////////////////////////////////////////////////////////
477// ToolbarView, views::View overrides:
478
479gfx::Size ToolbarView::GetPreferredSize() {
480  if (IsDisplayModeNormal()) {
481    int min_width = kEdgeSpacing +
482        back_->GetPreferredSize().width() + kButtonSpacing +
483        forward_->GetPreferredSize().width() + kButtonSpacing +
484        reload_->GetPreferredSize().width() + kStandardSpacing +
485        (show_home_button_.GetValue() ?
486            (home_->GetPreferredSize().width() + kButtonSpacing) : 0) +
487        browser_actions_->GetPreferredSize().width() +
488#if defined(OS_CHROMEOS)
489        feedback_->GetPreferredSize().width() + kButtonSpacing +
490#endif
491        app_menu_->GetPreferredSize().width() + kEdgeSpacing;
492
493    static SkBitmap normal_background;
494    if (normal_background.isNull()) {
495      ResourceBundle& rb = ResourceBundle::GetSharedInstance();
496      normal_background = *rb.GetBitmapNamed(IDR_CONTENT_TOP_CENTER);
497    }
498
499    return gfx::Size(min_width, normal_background.height());
500  }
501
502  int vertical_spacing = PopupTopSpacing() +
503      (GetWindow()->GetNonClientView()->UseNativeFrame() ?
504          kPopupBottomSpacingGlass : kPopupBottomSpacingNonGlass);
505  return gfx::Size(0, location_bar_->GetPreferredSize().height() +
506      vertical_spacing);
507}
508
509void ToolbarView::Layout() {
510  // If we have not been initialized yet just do nothing.
511  if (back_ == NULL)
512    return;
513
514  if (!IsDisplayModeNormal()) {
515    int edge_width = (browser_->window() && browser_->window()->IsMaximized()) ?
516        0 : kPopupBackgroundEdge->width();  // See Paint().
517    location_bar_->SetBounds(edge_width, PopupTopSpacing(),
518        width() - (edge_width * 2), location_bar_->GetPreferredSize().height());
519    return;
520  }
521
522  int child_y = std::min(kVertSpacing, height());
523  // We assume all child elements are the same height.
524  int child_height =
525      std::min(back_->GetPreferredSize().height(), height() - child_y);
526
527  // If the window is maximized, we extend the back button to the left so that
528  // clicking on the left-most pixel will activate the back button.
529  // TODO(abarth):  If the window becomes maximized but is not resized,
530  //                then Layout() might not be called and the back button
531  //                will be slightly the wrong size.  We should force a
532  //                Layout() in this case.
533  //                http://crbug.com/5540
534  int back_width = back_->GetPreferredSize().width();
535  if (browser_->window() && browser_->window()->IsMaximized())
536    back_->SetBounds(0, child_y, back_width + kEdgeSpacing, child_height);
537  else
538    back_->SetBounds(kEdgeSpacing, child_y, back_width, child_height);
539
540  forward_->SetBounds(back_->x() + back_->width() + kButtonSpacing,
541      child_y, forward_->GetPreferredSize().width(), child_height);
542
543  reload_->SetBounds(forward_->x() + forward_->width() + kButtonSpacing,
544      child_y, reload_->GetPreferredSize().width(), child_height);
545
546  if (show_home_button_.GetValue()) {
547    home_->SetVisible(true);
548    home_->SetBounds(reload_->x() + reload_->width() + kButtonSpacing, child_y,
549                     home_->GetPreferredSize().width(), child_height);
550  } else {
551    home_->SetVisible(false);
552    home_->SetBounds(reload_->x() + reload_->width(), child_y, 0, child_height);
553  }
554
555  int browser_actions_width = browser_actions_->GetPreferredSize().width();
556#if defined(OS_CHROMEOS)
557  int feedback_menu_width = feedback_->GetPreferredSize().width() +
558                            kButtonSpacing;
559#endif
560  int app_menu_width = app_menu_->GetPreferredSize().width();
561  int location_x = home_->x() + home_->width() + kStandardSpacing;
562  int available_width = width() - kEdgeSpacing - app_menu_width -
563#if defined(OS_CHROMEOS)
564      feedback_menu_width -
565#endif
566      browser_actions_width - location_x;
567
568  location_bar_->SetBounds(location_x, child_y, std::max(available_width, 0),
569                           child_height);
570
571  browser_actions_->SetBounds(location_bar_->x() + location_bar_->width(), 0,
572                              browser_actions_width, height());
573  // The browser actions need to do a layout explicitly, because when an
574  // extension is loaded/unloaded/changed, BrowserActionContainer removes and
575  // re-adds everything, regardless of whether it has a page action. For a
576  // page action, browser action bounds do not change, as a result of which
577  // SetBounds does not do a layout at all.
578  // TODO(sidchat): Rework the above behavior so that explicit layout is not
579  //                required.
580  browser_actions_->Layout();
581
582#if defined(OS_CHROMEOS)
583  feedback_->SetBounds(browser_actions_->x() + browser_actions_width, child_y,
584                       feedback_->GetPreferredSize().width(), child_height);
585  app_menu_->SetBounds(feedback_->x() + feedback_->width() + kButtonSpacing,
586                       child_y, app_menu_width, child_height);
587#else
588  app_menu_->SetBounds(browser_actions_->x() + browser_actions_width, child_y,
589                       app_menu_width, child_height);
590#endif
591}
592
593void ToolbarView::Paint(gfx::Canvas* canvas) {
594  View::Paint(canvas);
595
596  if (IsDisplayModeNormal())
597    return;
598
599  // In maximized mode, we don't draw the endcaps on the location bar, because
600  // when they're flush against the edge of the screen they just look glitchy.
601  if (!browser_->window() || !browser_->window()->IsMaximized()) {
602    int top_spacing = PopupTopSpacing();
603    canvas->DrawBitmapInt(*kPopupBackgroundEdge, 0, top_spacing);
604    canvas->DrawBitmapInt(*kPopupBackgroundEdge,
605                          width() - kPopupBackgroundEdge->width(), top_spacing);
606  }
607
608  // For glass, we need to draw a black line below the location bar to separate
609  // it from the content area.  For non-glass, the NonClientView draws the
610  // toolbar background below the location bar for us.
611  if (GetWindow()->GetNonClientView()->UseNativeFrame())
612    canvas->FillRectInt(SK_ColorBLACK, 0, height() - 1, width(), 1);
613}
614
615void ToolbarView::OnThemeChanged() {
616  LoadImages();
617}
618
619////////////////////////////////////////////////////////////////////////////////
620// ToolbarView, protected:
621
622// Override this so that when the user presses F6 to rotate toolbar panes,
623// the location bar gets focus, not the first control in the toolbar.
624views::View* ToolbarView::GetDefaultFocusableChild() {
625  return location_bar_;
626}
627
628void ToolbarView::RemovePaneFocus() {
629  AccessiblePaneView::RemovePaneFocus();
630  location_bar_->SetShowFocusRect(false);
631}
632
633////////////////////////////////////////////////////////////////////////////////
634// ToolbarView, private:
635
636bool ToolbarView::IsUpgradeRecommended() {
637#if defined(OS_CHROMEOS)
638  return (chromeos::CrosLibrary::Get()->GetUpdateLibrary()->status().status ==
639          chromeos::UPDATE_STATUS_UPDATED_NEED_REBOOT);
640#else
641  return (UpgradeDetector::GetInstance()->notify_upgrade());
642#endif
643}
644
645bool ToolbarView::ShouldShowBackgroundPageBadge() {
646  return BackgroundPageTracker::GetInstance()->
647      GetUnacknowledgedBackgroundPageCount() > 0;
648}
649
650bool ToolbarView::ShouldShowIncompatibilityWarning() {
651#if defined(OS_WIN)
652  EnumerateModulesModel* loaded_modules = EnumerateModulesModel::GetInstance();
653  return loaded_modules->confirmed_bad_modules_detected() > 0;
654#else
655  return false;
656#endif
657}
658
659int ToolbarView::PopupTopSpacing() const {
660  return GetWindow()->GetNonClientView()->UseNativeFrame() ?
661      0 : kPopupTopSpacingNonGlass;
662}
663
664void ToolbarView::LoadImages() {
665  ThemeProvider* tp = GetThemeProvider();
666
667  back_->SetImage(views::CustomButton::BS_NORMAL, tp->GetBitmapNamed(IDR_BACK));
668  back_->SetImage(views::CustomButton::BS_HOT, tp->GetBitmapNamed(IDR_BACK_H));
669  back_->SetImage(views::CustomButton::BS_PUSHED,
670      tp->GetBitmapNamed(IDR_BACK_P));
671  back_->SetImage(views::CustomButton::BS_DISABLED,
672      tp->GetBitmapNamed(IDR_BACK_D));
673
674  forward_->SetImage(views::CustomButton::BS_NORMAL,
675      tp->GetBitmapNamed(IDR_FORWARD));
676  forward_->SetImage(views::CustomButton::BS_HOT,
677      tp->GetBitmapNamed(IDR_FORWARD_H));
678  forward_->SetImage(views::CustomButton::BS_PUSHED,
679      tp->GetBitmapNamed(IDR_FORWARD_P));
680  forward_->SetImage(views::CustomButton::BS_DISABLED,
681      tp->GetBitmapNamed(IDR_FORWARD_D));
682
683  reload_->SetImage(views::CustomButton::BS_NORMAL,
684      tp->GetBitmapNamed(IDR_RELOAD));
685  reload_->SetImage(views::CustomButton::BS_HOT,
686      tp->GetBitmapNamed(IDR_RELOAD_H));
687  reload_->SetImage(views::CustomButton::BS_PUSHED,
688      tp->GetBitmapNamed(IDR_RELOAD_P));
689  reload_->SetToggledImage(views::CustomButton::BS_NORMAL,
690      tp->GetBitmapNamed(IDR_STOP));
691  reload_->SetToggledImage(views::CustomButton::BS_HOT,
692      tp->GetBitmapNamed(IDR_STOP_H));
693  reload_->SetToggledImage(views::CustomButton::BS_PUSHED,
694      tp->GetBitmapNamed(IDR_STOP_P));
695  reload_->SetToggledImage(views::CustomButton::BS_DISABLED,
696      tp->GetBitmapNamed(IDR_STOP_D));
697
698#if defined(OS_CHROMEOS)
699  feedback_->SetImage(views::CustomButton::BS_NORMAL,
700      tp->GetBitmapNamed(IDR_FEEDBACK));
701  feedback_->SetImage(views::CustomButton::BS_HOT,
702      tp->GetBitmapNamed(IDR_FEEDBACK_H));
703  feedback_->SetImage(views::CustomButton::BS_PUSHED,
704      tp->GetBitmapNamed(IDR_FEEDBACK_P));
705#endif
706
707  home_->SetImage(views::CustomButton::BS_NORMAL, tp->GetBitmapNamed(IDR_HOME));
708  home_->SetImage(views::CustomButton::BS_HOT, tp->GetBitmapNamed(IDR_HOME_H));
709  home_->SetImage(views::CustomButton::BS_PUSHED,
710      tp->GetBitmapNamed(IDR_HOME_P));
711
712  app_menu_->SetIcon(GetAppMenuIcon(views::CustomButton::BS_NORMAL));
713  app_menu_->SetHoverIcon(GetAppMenuIcon(views::CustomButton::BS_HOT));
714  app_menu_->SetPushedIcon(GetAppMenuIcon(views::CustomButton::BS_PUSHED));
715}
716
717void ToolbarView::UpdateAppMenuBadge() {
718  app_menu_->SetIcon(GetAppMenuIcon(views::CustomButton::BS_NORMAL));
719  app_menu_->SetHoverIcon(GetAppMenuIcon(views::CustomButton::BS_HOT));
720  app_menu_->SetPushedIcon(GetAppMenuIcon(views::CustomButton::BS_PUSHED));
721  SchedulePaint();
722}
723
724SkBitmap ToolbarView::GetAppMenuIcon(views::CustomButton::ButtonState state) {
725  ThemeProvider* tp = GetThemeProvider();
726
727  int id = 0;
728  switch (state) {
729    case views::CustomButton::BS_NORMAL: id = IDR_TOOLS;   break;
730    case views::CustomButton::BS_HOT:    id = IDR_TOOLS_H; break;
731    case views::CustomButton::BS_PUSHED: id = IDR_TOOLS_P; break;
732    default:                             NOTREACHED();     break;
733  }
734  SkBitmap icon = *tp->GetBitmapNamed(id);
735
736#if defined(OS_WIN)
737  // Keep track of whether we were showing the badge before, so we don't send
738  // multiple UMA events for example when multiple Chrome windows are open.
739  static bool incompatibility_badge_showing = false;
740  // Save the old value before resetting it.
741  bool was_showing = incompatibility_badge_showing;
742  incompatibility_badge_showing = false;
743#endif
744
745  bool add_badge = IsUpgradeRecommended() ||
746                   ShouldShowIncompatibilityWarning() ||
747                   ShouldShowBackgroundPageBadge();
748  if (!add_badge)
749    return icon;
750
751  // Draw the chrome app menu icon onto the canvas.
752  scoped_ptr<gfx::CanvasSkia> canvas(
753      new gfx::CanvasSkia(icon.width(), icon.height(), false));
754  canvas->DrawBitmapInt(icon, 0, 0);
755
756  SkBitmap badge;
757  // Only one badge can be active at any given time. The Upgrade notification
758  // is deemed most important, then the temporary background page badge,
759  // then the DLL conflict badge.
760  if (IsUpgradeRecommended()) {
761    badge = *tp->GetBitmapNamed(IDR_UPDATE_BADGE);
762  } else if (ShouldShowBackgroundPageBadge()) {
763    badge = *tp->GetBitmapNamed(IDR_BACKGROUND_BADGE);
764  } else if (ShouldShowIncompatibilityWarning()) {
765#if defined(OS_WIN)
766    if (!was_showing)
767      UserMetrics::RecordAction(UserMetricsAction("ConflictBadge"), profile_);
768    badge = *tp->GetBitmapNamed(IDR_CONFLICT_BADGE);
769    incompatibility_badge_showing = true;
770#else
771    NOTREACHED();
772#endif
773  } else {
774    NOTREACHED();
775  }
776
777  canvas->DrawBitmapInt(badge, icon.width() - badge.width(), kBadgeTopMargin);
778
779  return canvas->ExtractBitmap();
780}
781