1// Copyright (c) 2013 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/outdated_upgrade_bubble_view.h" 6 7#include "base/metrics/histogram.h" 8#include "chrome/browser/upgrade_detector.h" 9#include "content/public/browser/page_navigator.h" 10#include "content/public/browser/user_metrics.h" 11#include "grit/chromium_strings.h" 12#include "grit/generated_resources.h" 13#include "grit/theme_resources.h" 14#include "ui/base/l10n/l10n_util.h" 15#include "ui/base/resource/resource_bundle.h" 16#include "ui/views/controls/button/label_button.h" 17#include "ui/views/controls/image_view.h" 18#include "ui/views/controls/label.h" 19#include "ui/views/layout/grid_layout.h" 20#include "ui/views/layout/layout_constants.h" 21#include "ui/views/widget/widget.h" 22#include "url/gurl.h" 23 24using views::GridLayout; 25 26namespace { 27 28// Fixed width of the column holding the description label of the bubble. 29// TODO(mad): Make sure there is enough room for all languages. 30const int kWidthOfDescriptionText = 330; 31 32// We subtract 2 to account for the natural button padding, and 33// to bring the separation visually in line with the row separation 34// height. 35const int kButtonPadding = views::kRelatedButtonHSpacing - 2; 36 37// The URL to be used to re-install Chrome when auto-update failed for too long. 38const char kDownloadChromeUrl[] = "https://www.google.com/chrome/?&brand=CHWL" 39 "&utm_campaign=en&utm_source=en-et-na-us-chrome-bubble&utm_medium=et"; 40 41// The maximum number of ignored bubble we track in the NumLaterPerReinstall 42// histogram. 43const int kMaxIgnored = 50; 44// The number of buckets we want the NumLaterPerReinstall histogram to use. 45const int kNumIgnoredBuckets = 5; 46 47} // namespace 48 49// OutdatedUpgradeBubbleView --------------------------------------------------- 50 51OutdatedUpgradeBubbleView* OutdatedUpgradeBubbleView::upgrade_bubble_ = NULL; 52int OutdatedUpgradeBubbleView::num_ignored_bubbles_ = 0; 53 54// static 55void OutdatedUpgradeBubbleView::ShowBubble(views::View* anchor_view, 56 content::PageNavigator* navigator) { 57 if (IsShowing()) 58 return; 59 upgrade_bubble_ = new OutdatedUpgradeBubbleView(anchor_view, navigator); 60 views::BubbleDelegateView::CreateBubble(upgrade_bubble_); 61 upgrade_bubble_->StartFade(true); 62 content::RecordAction( 63 content::UserMetricsAction("OutdatedUpgradeBubble.Show")); 64} 65 66bool OutdatedUpgradeBubbleView::IsAvailable() { 67// This should only work on non-Chrome OS desktop platforms. 68#if defined(OS_WIN) || defined(OS_MACOSX) || \ 69 (defined(OS_LINUX) && !defined(OS_CHROMEOS)) 70 return true; 71#else 72 return false; 73#endif 74} 75 76OutdatedUpgradeBubbleView::~OutdatedUpgradeBubbleView() { 77 if (!chose_to_reinstall_ && num_ignored_bubbles_ < kMaxIgnored) 78 ++num_ignored_bubbles_; 79} 80 81views::View* OutdatedUpgradeBubbleView::GetInitiallyFocusedView() { 82 return reinstall_button_; 83} 84 85void OutdatedUpgradeBubbleView::WindowClosing() { 86 // Reset |upgrade_bubble_| here, not in destructor, because destruction is 87 // asynchronous and ShowBubble may be called before full destruction and 88 // would attempt to show a bubble that is closing. 89 DCHECK_EQ(upgrade_bubble_, this); 90 upgrade_bubble_ = NULL; 91} 92 93void OutdatedUpgradeBubbleView::Init() { 94 base::string16 product_name( 95 l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)); 96 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 97 reinstall_button_ = new views::LabelButton( 98 this, l10n_util::GetStringFUTF16(IDS_REINSTALL_APP, product_name)); 99 reinstall_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 100 reinstall_button_->SetIsDefault(true); 101 reinstall_button_->SetFont(rb.GetFont(ui::ResourceBundle::BoldFont)); 102 103 later_button_ = new views::LabelButton( 104 this, l10n_util::GetStringUTF16(IDS_LATER)); 105 later_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 106 107 views::Label* title_label = new views::Label( 108 l10n_util::GetStringFUTF16(IDS_UPGRADE_BUBBLE_TITLE, product_name)); 109 title_label->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont)); 110 title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 111 112 views::Label* text_label = new views::Label( 113 l10n_util::GetStringFUTF16(IDS_UPGRADE_BUBBLE_TEXT, product_name)); 114 text_label->SetMultiLine(true); 115 text_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 116 117 views::ImageView* image_view = new views::ImageView(); 118 image_view->SetImage(rb.GetImageSkiaNamed(IDR_UPDATE_MENU_SEVERITY_HIGH)); 119 120 GridLayout* layout = new GridLayout(this); 121 SetLayoutManager(layout); 122 123 const int kIconTitleColumnSetId = 0; 124 views::ColumnSet* cs = layout->AddColumnSet(kIconTitleColumnSetId); 125 126 // Top (icon-title) row. 127 cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, 128 GridLayout::USE_PREF, 0, 0); 129 cs->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing); 130 cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, 131 GridLayout::USE_PREF, 0, 0); 132 cs->AddPaddingColumn(1, views::kUnrelatedControlHorizontalSpacing); 133 134 // Middle (text) row. 135 const int kTextColumnSetId = 1; 136 cs = layout->AddColumnSet(kTextColumnSetId); 137 cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, 138 GridLayout::FIXED, kWidthOfDescriptionText, 0); 139 140 // Bottom (buttons) row. 141 const int kButtonsColumnSetId = 2; 142 cs = layout->AddColumnSet(kButtonsColumnSetId); 143 cs->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing); 144 cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0, 145 GridLayout::USE_PREF, 0, 0); 146 cs->AddPaddingColumn(0, kButtonPadding); 147 cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0, 148 GridLayout::USE_PREF, 0, 0); 149 150 layout->StartRow(0, kIconTitleColumnSetId); 151 layout->AddView(image_view); 152 layout->AddView(title_label); 153 154 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); 155 layout->StartRow(0, kTextColumnSetId); 156 layout->AddView(text_label); 157 158 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing); 159 160 layout->StartRow(0, kButtonsColumnSetId); 161 layout->AddView(reinstall_button_); 162 layout->AddView(later_button_); 163 164 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)); 165} 166 167OutdatedUpgradeBubbleView::OutdatedUpgradeBubbleView( 168 views::View* anchor_view, content::PageNavigator* navigator) 169 : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT), 170 chose_to_reinstall_(false), 171 reinstall_button_(NULL), 172 later_button_(NULL), 173 navigator_(navigator) { 174 // Compensate for built-in vertical padding in the anchor view's image. 175 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0)); 176} 177 178void OutdatedUpgradeBubbleView::ButtonPressed( 179 views::Button* sender, const ui::Event& event) { 180 if (event.IsMouseEvent() && 181 !(static_cast<const ui::MouseEvent*>(&event))->IsOnlyLeftMouseButton()) { 182 return; 183 } 184 HandleButtonPressed(sender); 185} 186 187void OutdatedUpgradeBubbleView::HandleButtonPressed(views::Button* sender) { 188 if (sender == reinstall_button_) { 189 DCHECK(UpgradeDetector::GetInstance()->is_outdated_install()); 190 chose_to_reinstall_ = true; 191 UMA_HISTOGRAM_CUSTOM_COUNTS( 192 "OutdatedUpgradeBubble.NumLaterPerReinstall", num_ignored_bubbles_, 193 0, kMaxIgnored, kNumIgnoredBuckets); 194 content::RecordAction( 195 content::UserMetricsAction("OutdatedUpgradeBubble.Reinstall")); 196 navigator_->OpenURL(content::OpenURLParams(GURL(kDownloadChromeUrl), 197 content::Referrer(), 198 NEW_FOREGROUND_TAB, 199 content::PAGE_TRANSITION_LINK, 200 false)); 201 } else { 202 DCHECK_EQ(later_button_, sender); 203 content::RecordAction( 204 content::UserMetricsAction("OutdatedUpgradeBubble.Later")); 205 } 206 StartFade(false); 207} 208