| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 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/chooser_content_view.h" | |
| 6 | |
| 7 #include "base/memory/ptr_util.h" | |
| 8 #include "base/numerics/safe_conversions.h" | |
| 9 #include "chrome/grit/generated_resources.h" | |
| 10 #include "ui/base/l10n/l10n_util.h" | |
| 11 #include "ui/base/resource/resource_bundle.h" | |
| 12 #include "ui/gfx/color_palette.h" | |
| 13 #include "ui/gfx/geometry/point.h" | |
| 14 #include "ui/gfx/geometry/rect.h" | |
| 15 #include "ui/gfx/image/image_skia.h" | |
| 16 #include "ui/gfx/paint_vector_icon.h" | |
| 17 #include "ui/gfx/vector_icons_public.h" | |
| 18 #include "ui/resources/grit/ui_resources.h" | |
| 19 #include "ui/views/controls/styled_label.h" | |
| 20 #include "ui/views/controls/table/table_view.h" | |
| 21 #include "ui/views/controls/throbber.h" | |
| 22 #include "ui/views/widget/widget.h" | |
| 23 | |
| 24 namespace { | |
| 25 | |
| 26 const int kChooserWidth = 370; | |
| 27 | |
| 28 const int kChooserHeight = 260; | |
| 29 | |
| 30 const int kThrobberDiameter = 24; | |
| 31 | |
| 32 const int kAdapterOffHelpLinkPadding = 5; | |
| 33 | |
| 34 // The lookup table for signal strength level image. | |
| 35 const int kSignalStrengthLevelImageIds[5] = {IDR_SIGNAL_0_BAR, IDR_SIGNAL_1_BAR, | |
| 36 IDR_SIGNAL_2_BAR, IDR_SIGNAL_3_BAR, | |
| 37 IDR_SIGNAL_4_BAR}; | |
| 38 | |
| 39 } // namespace | |
| 40 | |
| 41 ChooserContentView::ChooserContentView( | |
| 42 views::TableViewObserver* table_view_observer, | |
| 43 std::unique_ptr<ChooserController> chooser_controller) | |
| 44 : chooser_controller_(std::move(chooser_controller)), | |
| 45 help_text_(l10n_util::GetStringFUTF16( | |
| 46 IDS_DEVICE_CHOOSER_GET_HELP_LINK_WITH_SCANNING_STATUS, | |
| 47 base::string16())), | |
| 48 help_and_scanning_text_(l10n_util::GetStringFUTF16( | |
| 49 IDS_DEVICE_CHOOSER_GET_HELP_LINK_WITH_SCANNING_STATUS, | |
| 50 l10n_util::GetStringUTF16(IDS_BLUETOOTH_DEVICE_CHOOSER_SCANNING))) { | |
| 51 base::string16 re_scan_text = | |
| 52 l10n_util::GetStringUTF16(IDS_BLUETOOTH_DEVICE_CHOOSER_RE_SCAN); | |
| 53 std::vector<size_t> offsets; | |
| 54 help_and_re_scan_text_ = l10n_util::GetStringFUTF16( | |
| 55 IDS_DEVICE_CHOOSER_GET_HELP_LINK_WITH_RE_SCAN_LINK, help_text_, | |
| 56 re_scan_text, &offsets); | |
| 57 help_text_range_ = gfx::Range(offsets[0], offsets[0] + help_text_.size()); | |
| 58 re_scan_text_range_ = | |
| 59 gfx::Range(offsets[1], offsets[1] + re_scan_text.size()); | |
| 60 chooser_controller_->set_view(this); | |
| 61 std::vector<ui::TableColumn> table_columns; | |
| 62 table_columns.push_back(ui::TableColumn()); | |
| 63 table_view_ = new views::TableView( | |
| 64 this, table_columns, | |
| 65 chooser_controller_->ShouldShowIconBeforeText() ? views::ICON_AND_TEXT | |
| 66 : views::TEXT_ONLY, | |
| 67 !chooser_controller_->AllowMultipleSelection() /* single_selection */); | |
| 68 table_view_->set_select_on_remove(false); | |
| 69 table_view_->set_observer(table_view_observer); | |
| 70 table_view_->SetEnabled(chooser_controller_->NumOptions() > 0); | |
| 71 | |
| 72 table_parent_ = table_view_->CreateParentIfNecessary(); | |
| 73 AddChildView(table_parent_); | |
| 74 | |
| 75 throbber_ = new views::Throbber(); | |
| 76 throbber_->SetVisible(false); | |
| 77 AddChildView(throbber_); | |
| 78 | |
| 79 base::string16 link_text = l10n_util::GetStringUTF16( | |
| 80 IDS_BLUETOOTH_DEVICE_CHOOSER_TURN_ON_BLUETOOTH_LINK_TEXT); | |
| 81 size_t offset = 0; | |
| 82 base::string16 text = l10n_util::GetStringFUTF16( | |
| 83 IDS_BLUETOOTH_DEVICE_CHOOSER_TURN_ADAPTER_OFF, link_text, &offset); | |
| 84 turn_adapter_off_help_ = new views::StyledLabel(text, this); | |
| 85 turn_adapter_off_help_->AddStyleRange( | |
| 86 gfx::Range(0, link_text.size()), | |
| 87 views::StyledLabel::RangeStyleInfo::CreateForLink()); | |
| 88 turn_adapter_off_help_->SetVisible(false); | |
| 89 AddChildView(turn_adapter_off_help_); | |
| 90 } | |
| 91 | |
| 92 ChooserContentView::~ChooserContentView() { | |
| 93 chooser_controller_->set_view(nullptr); | |
| 94 table_view_->set_observer(nullptr); | |
| 95 table_view_->SetModel(nullptr); | |
| 96 } | |
| 97 | |
| 98 gfx::Size ChooserContentView::GetPreferredSize() const { | |
| 99 return gfx::Size(kChooserWidth, kChooserHeight); | |
| 100 } | |
| 101 | |
| 102 void ChooserContentView::Layout() { | |
| 103 gfx::Rect rect(GetContentsBounds()); | |
| 104 table_parent_->SetBoundsRect(rect); | |
| 105 // Set the throbber in the center of the chooser. | |
| 106 throbber_->SetBounds((rect.width() - kThrobberDiameter) / 2, | |
| 107 (rect.height() - kThrobberDiameter) / 2, | |
| 108 kThrobberDiameter, kThrobberDiameter); | |
| 109 turn_adapter_off_help_->SetPosition( | |
| 110 gfx::Point(kAdapterOffHelpLinkPadding, kAdapterOffHelpLinkPadding)); | |
| 111 turn_adapter_off_help_->SizeToFit(rect.width() - | |
| 112 2 * kAdapterOffHelpLinkPadding); | |
| 113 views::View::Layout(); | |
| 114 } | |
| 115 | |
| 116 int ChooserContentView::RowCount() { | |
| 117 // When there are no devices, the table contains a message saying there | |
| 118 // are no devices, so the number of rows is always at least 1. | |
| 119 return std::max(base::checked_cast<int>(chooser_controller_->NumOptions()), | |
| 120 1); | |
| 121 } | |
| 122 | |
| 123 base::string16 ChooserContentView::GetText(int row, int column_id) { | |
| 124 int num_options = base::checked_cast<int>(chooser_controller_->NumOptions()); | |
| 125 if (num_options == 0) { | |
| 126 DCHECK_EQ(0, row); | |
| 127 return chooser_controller_->GetNoOptionsText(); | |
| 128 } | |
| 129 | |
| 130 DCHECK_GE(row, 0); | |
| 131 DCHECK_LT(row, num_options); | |
| 132 base::string16 text = | |
| 133 chooser_controller_->GetOption(static_cast<size_t>(row)); | |
| 134 return chooser_controller_->IsPaired(row) | |
| 135 ? l10n_util::GetStringFUTF16( | |
| 136 IDS_DEVICE_CHOOSER_DEVICE_NAME_AND_PAIRED_STATUS_TEXT, text) | |
| 137 : text; | |
| 138 } | |
| 139 | |
| 140 void ChooserContentView::SetObserver(ui::TableModelObserver* observer) {} | |
| 141 | |
| 142 gfx::ImageSkia ChooserContentView::GetIcon(int row) { | |
| 143 DCHECK(chooser_controller_->ShouldShowIconBeforeText()); | |
| 144 | |
| 145 size_t num_options = chooser_controller_->NumOptions(); | |
| 146 if (num_options == 0) { | |
| 147 DCHECK_EQ(0, row); | |
| 148 return gfx::ImageSkia(); | |
| 149 } | |
| 150 | |
| 151 DCHECK_GE(row, 0); | |
| 152 DCHECK_LT(row, base::checked_cast<int>(num_options)); | |
| 153 | |
| 154 if (chooser_controller_->IsConnected(row)) { | |
| 155 return gfx::CreateVectorIcon(gfx::VectorIconId::BLUETOOTH_CONNECTED, | |
| 156 gfx::kChromeIconGrey); | |
| 157 } | |
| 158 | |
| 159 int level = chooser_controller_->GetSignalStrengthLevel(row); | |
| 160 | |
| 161 if (level == -1) | |
| 162 return gfx::ImageSkia(); | |
| 163 | |
| 164 DCHECK_GE(level, 0); | |
| 165 DCHECK_LT(level, static_cast<int>(arraysize(kSignalStrengthLevelImageIds))); | |
| 166 | |
| 167 return *ResourceBundle::GetSharedInstance().GetImageSkiaNamed( | |
| 168 kSignalStrengthLevelImageIds[level]); | |
| 169 } | |
| 170 | |
| 171 void ChooserContentView::OnOptionsInitialized() { | |
| 172 table_view_->OnModelChanged(); | |
| 173 UpdateTableView(); | |
| 174 } | |
| 175 | |
| 176 void ChooserContentView::OnOptionAdded(size_t index) { | |
| 177 table_view_->OnItemsAdded(base::checked_cast<int>(index), 1); | |
| 178 UpdateTableView(); | |
| 179 table_view_->SetVisible(true); | |
| 180 throbber_->SetVisible(false); | |
| 181 throbber_->Stop(); | |
| 182 } | |
| 183 | |
| 184 void ChooserContentView::OnOptionRemoved(size_t index) { | |
| 185 table_view_->OnItemsRemoved(base::checked_cast<int>(index), 1); | |
| 186 UpdateTableView(); | |
| 187 } | |
| 188 | |
| 189 void ChooserContentView::OnOptionUpdated(size_t index) { | |
| 190 table_view_->OnItemsChanged(base::checked_cast<int>(index), 1); | |
| 191 UpdateTableView(); | |
| 192 } | |
| 193 | |
| 194 void ChooserContentView::OnAdapterEnabledChanged(bool enabled) { | |
| 195 // No row is selected since the adapter status has changed. | |
| 196 // This will also disable the OK button if it was enabled because | |
| 197 // of a previously selected row. | |
| 198 table_view_->Select(-1); | |
| 199 UpdateTableView(); | |
| 200 table_view_->SetVisible(enabled); | |
| 201 turn_adapter_off_help_->SetVisible(!enabled); | |
| 202 | |
| 203 throbber_->Stop(); | |
| 204 throbber_->SetVisible(false); | |
| 205 | |
| 206 if (enabled) { | |
| 207 SetGetHelpAndReScanLink(); | |
| 208 } else { | |
| 209 DCHECK(footnote_link_); | |
| 210 footnote_link_->SetText(help_text_); | |
| 211 footnote_link_->AddStyleRange( | |
| 212 help_text_range_, views::StyledLabel::RangeStyleInfo::CreateForLink()); | |
| 213 } | |
| 214 | |
| 215 if (GetWidget() && GetWidget()->GetRootView()) | |
| 216 GetWidget()->GetRootView()->Layout(); | |
| 217 } | |
| 218 | |
| 219 void ChooserContentView::OnRefreshStateChanged(bool refreshing) { | |
| 220 if (refreshing) { | |
| 221 // No row is selected since the chooser is refreshing. This will also | |
| 222 // disable the OK button if it was enabled because of a previously | |
| 223 // selected row. | |
| 224 table_view_->Select(-1); | |
| 225 UpdateTableView(); | |
| 226 } | |
| 227 | |
| 228 // When refreshing and no option available yet, hide |table_view_| and show | |
| 229 // |throbber_|. Otherwise show |table_view_| and hide |throbber_|. | |
| 230 bool throbber_visible = | |
| 231 refreshing && (chooser_controller_->NumOptions() == 0); | |
| 232 table_view_->SetVisible(!throbber_visible); | |
| 233 throbber_->SetVisible(throbber_visible); | |
| 234 if (throbber_visible) | |
| 235 throbber_->Start(); | |
| 236 else | |
| 237 throbber_->Stop(); | |
| 238 | |
| 239 if (refreshing) { | |
| 240 DCHECK(footnote_link_); | |
| 241 footnote_link_->SetText(help_and_scanning_text_); | |
| 242 footnote_link_->AddStyleRange( | |
| 243 help_text_range_, views::StyledLabel::RangeStyleInfo::CreateForLink()); | |
| 244 } else { | |
| 245 SetGetHelpAndReScanLink(); | |
| 246 } | |
| 247 | |
| 248 if (GetWidget() && GetWidget()->GetRootView()) | |
| 249 GetWidget()->GetRootView()->Layout(); | |
| 250 } | |
| 251 | |
| 252 void ChooserContentView::StyledLabelLinkClicked(views::StyledLabel* label, | |
| 253 const gfx::Range& range, | |
| 254 int event_flags) { | |
| 255 if (label == turn_adapter_off_help_) { | |
| 256 chooser_controller_->OpenAdapterOffHelpUrl(); | |
| 257 } else if (label == footnote_link_.get()) { | |
| 258 if (range == help_text_range_) | |
| 259 chooser_controller_->OpenHelpCenterUrl(); | |
| 260 else if (range == re_scan_text_range_) | |
| 261 chooser_controller_->RefreshOptions(); | |
| 262 else | |
| 263 NOTREACHED(); | |
| 264 } else { | |
| 265 NOTREACHED(); | |
| 266 } | |
| 267 } | |
| 268 | |
| 269 base::string16 ChooserContentView::GetWindowTitle() const { | |
| 270 return chooser_controller_->GetTitle(); | |
| 271 } | |
| 272 | |
| 273 base::string16 ChooserContentView::GetDialogButtonLabel( | |
| 274 ui::DialogButton button) const { | |
| 275 return button == ui::DIALOG_BUTTON_OK | |
| 276 ? chooser_controller_->GetOkButtonLabel() | |
| 277 : l10n_util::GetStringUTF16(IDS_DEVICE_CHOOSER_CANCEL_BUTTON_TEXT); | |
| 278 } | |
| 279 | |
| 280 bool ChooserContentView::IsDialogButtonEnabled(ui::DialogButton button) const { | |
| 281 return button != ui::DIALOG_BUTTON_OK || | |
| 282 !table_view_->selection_model().empty(); | |
| 283 } | |
| 284 | |
| 285 views::StyledLabel* ChooserContentView::footnote_link() { | |
| 286 if (chooser_controller_->ShouldShowFootnoteView()) { | |
| 287 footnote_link_ = base::MakeUnique<views::StyledLabel>(help_text_, this); | |
| 288 footnote_link_->set_owned_by_client(); | |
| 289 footnote_link_->AddStyleRange( | |
| 290 help_text_range_, views::StyledLabel::RangeStyleInfo::CreateForLink()); | |
| 291 } | |
| 292 | |
| 293 return footnote_link_.get(); | |
| 294 } | |
| 295 | |
| 296 void ChooserContentView::Accept() { | |
| 297 std::vector<size_t> indices( | |
| 298 table_view_->selection_model().selected_indices().begin(), | |
| 299 table_view_->selection_model().selected_indices().end()); | |
| 300 chooser_controller_->Select(indices); | |
| 301 } | |
| 302 | |
| 303 void ChooserContentView::Cancel() { | |
| 304 chooser_controller_->Cancel(); | |
| 305 } | |
| 306 | |
| 307 void ChooserContentView::Close() { | |
| 308 chooser_controller_->Close(); | |
| 309 } | |
| 310 | |
| 311 void ChooserContentView::UpdateTableView() { | |
| 312 if (chooser_controller_->NumOptions() == 0) { | |
| 313 table_view_->OnModelChanged(); | |
| 314 table_view_->SetEnabled(false); | |
| 315 } else { | |
| 316 table_view_->SetEnabled(true); | |
| 317 } | |
| 318 } | |
| 319 | |
| 320 void ChooserContentView::SetGetHelpAndReScanLink() { | |
| 321 DCHECK(footnote_link_); | |
| 322 footnote_link_->SetText(help_and_re_scan_text_); | |
| 323 footnote_link_->AddStyleRange( | |
| 324 help_text_range_, views::StyledLabel::RangeStyleInfo::CreateForLink()); | |
| 325 footnote_link_->AddStyleRange( | |
| 326 re_scan_text_range_, views::StyledLabel::RangeStyleInfo::CreateForLink()); | |
| 327 } | |
| OLD | NEW |