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