| 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 #import "chrome/browser/ui/cocoa/chooser_content_view_cocoa.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/macros.h" | |
| 10 #include "base/strings/sys_string_conversions.h" | |
| 11 #include "chrome/browser/chooser_controller/chooser_controller.h" | |
| 12 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h" | |
| 13 #include "chrome/browser/ui/cocoa/spinner_view.h" | |
| 14 #include "chrome/grit/generated_resources.h" | |
| 15 #include "skia/ext/skia_utils_mac.h" | |
| 16 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTw
eaker.h" | |
| 17 #import "ui/base/cocoa/controls/hyperlink_button_cell.h" | |
| 18 #include "ui/base/l10n/l10n_util_mac.h" | |
| 19 #include "ui/base/resource/resource_bundle.h" | |
| 20 #include "ui/gfx/color_palette.h" | |
| 21 #include "ui/gfx/image/image_skia_util_mac.h" | |
| 22 #include "ui/gfx/paint_vector_icon.h" | |
| 23 #include "ui/gfx/vector_icons_public.h" | |
| 24 #include "ui/resources/grit/ui_resources.h" | |
| 25 | |
| 26 namespace { | |
| 27 | |
| 28 // Chooser width. | |
| 29 const CGFloat kChooserWidth = 390.0f; | |
| 30 | |
| 31 // Chooser height. | |
| 32 const CGFloat kChooserHeight = 330.0f; | |
| 33 | |
| 34 // Row view image size. | |
| 35 const CGFloat kRowViewImageSize = 20.0f; | |
| 36 | |
| 37 // Table row view height. | |
| 38 const CGFloat kTableRowViewOneLineHeight = 23.0f; | |
| 39 const CGFloat kTableRowViewTwoLinesHeight = 39.0f; | |
| 40 | |
| 41 // Spinner size. | |
| 42 const CGFloat kSpinnerSize = 24.0f; | |
| 43 | |
| 44 // Distance between the chooser border and the view that is closest to the | |
| 45 // border. | |
| 46 const CGFloat kMarginX = 20.0f; | |
| 47 const CGFloat kMarginY = 20.0f; | |
| 48 | |
| 49 // Distance between two views inside the chooser. | |
| 50 const CGFloat kHorizontalPadding = 10.0f; | |
| 51 const CGFloat kVerticalPadding = 10.0f; | |
| 52 | |
| 53 // Separator alpha value. | |
| 54 const CGFloat kSeparatorAlphaValue = 0.6f; | |
| 55 | |
| 56 // Separator height. | |
| 57 const CGFloat kSeparatorHeight = 1.0f; | |
| 58 | |
| 59 // Distance between two views inside the table row view. | |
| 60 const CGFloat kTableRowViewHorizontalPadding = 5.0f; | |
| 61 const CGFloat kTableRowViewVerticalPadding = 1.0f; | |
| 62 | |
| 63 // Distance between the adapter off help link and the scroll view boundaries. | |
| 64 const CGFloat kAdapterOffHelpLinkPadding = 5.0f; | |
| 65 | |
| 66 // The lookup table for signal strength level image. | |
| 67 const int kSignalStrengthLevelImageIds[5] = {IDR_SIGNAL_0_BAR, IDR_SIGNAL_1_BAR, | |
| 68 IDR_SIGNAL_2_BAR, IDR_SIGNAL_3_BAR, | |
| 69 IDR_SIGNAL_4_BAR}; | |
| 70 const int kSignalStrengthLevelImageSelectedIds[5] = { | |
| 71 IDR_SIGNAL_0_BAR_SELECTED, IDR_SIGNAL_1_BAR_SELECTED, | |
| 72 IDR_SIGNAL_2_BAR_SELECTED, IDR_SIGNAL_3_BAR_SELECTED, | |
| 73 IDR_SIGNAL_4_BAR_SELECTED}; | |
| 74 | |
| 75 // Creates a label with |text|. | |
| 76 base::scoped_nsobject<NSTextField> CreateLabel(NSString* text) { | |
| 77 base::scoped_nsobject<NSTextField> label( | |
| 78 [[NSTextField alloc] initWithFrame:NSZeroRect]); | |
| 79 [label setDrawsBackground:NO]; | |
| 80 [label setBezeled:NO]; | |
| 81 [label setEditable:NO]; | |
| 82 [label setSelectable:NO]; | |
| 83 [label setStringValue:text]; | |
| 84 [label setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]]; | |
| 85 [label setTextColor:[NSColor blackColor]]; | |
| 86 [label sizeToFit]; | |
| 87 return label; | |
| 88 } | |
| 89 | |
| 90 } // namespace | |
| 91 | |
| 92 // A table row view that contains one line of text, and optionally contains an | |
| 93 // image in front of the text. | |
| 94 @interface ChooserContentTableRowView : NSView { | |
| 95 @private | |
| 96 base::scoped_nsobject<NSImageView> image_; | |
| 97 base::scoped_nsobject<NSTextField> text_; | |
| 98 base::scoped_nsobject<NSTextField> pairedStatus_; | |
| 99 } | |
| 100 | |
| 101 // Designated initializer. | |
| 102 // This initializer is used when the chooser needs to show the no-devices-found | |
| 103 // message. | |
| 104 - (instancetype)initWithText:(NSString*)text; | |
| 105 | |
| 106 // Designated initializer. | |
| 107 - (instancetype)initWithText:(NSString*)text | |
| 108 signalStrengthLevel:(NSInteger)level | |
| 109 isConnected:(bool)isConnected | |
| 110 isPaired:(bool)isPaired | |
| 111 rowHeight:(CGFloat)rowHeight | |
| 112 textNeedIndentation:(bool)textNeedIndentation; | |
| 113 | |
| 114 // Gets the image in front of the text. | |
| 115 - (NSImageView*)image; | |
| 116 | |
| 117 // Gets the text. | |
| 118 - (NSTextField*)text; | |
| 119 | |
| 120 // Gets the paired status. | |
| 121 - (NSTextField*)pairedStatus; | |
| 122 | |
| 123 @end | |
| 124 | |
| 125 @implementation ChooserContentTableRowView | |
| 126 | |
| 127 - (instancetype)initWithText:(NSString*)text { | |
| 128 if ((self = [super initWithFrame:NSZeroRect])) { | |
| 129 text_ = CreateLabel(text); | |
| 130 CGFloat textHeight = NSHeight([text_ frame]); | |
| 131 CGFloat textOriginX = kTableRowViewHorizontalPadding; | |
| 132 CGFloat textOriginY = (kTableRowViewOneLineHeight - textHeight) / 2; | |
| 133 [text_ setFrameOrigin:NSMakePoint(textOriginX, textOriginY)]; | |
| 134 [self addSubview:text_]; | |
| 135 } | |
| 136 | |
| 137 return self; | |
| 138 } | |
| 139 | |
| 140 - (instancetype)initWithText:(NSString*)text | |
| 141 signalStrengthLevel:(NSInteger)level | |
| 142 isConnected:(bool)isConnected | |
| 143 isPaired:(bool)isPaired | |
| 144 rowHeight:(CGFloat)rowHeight | |
| 145 textNeedIndentation:(bool)textNeedIndentation { | |
| 146 if ((self = [super initWithFrame:NSZeroRect])) { | |
| 147 // Create the views. | |
| 148 // Image. | |
| 149 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
| 150 NSImage* image = nullptr; | |
| 151 if (isConnected) { | |
| 152 image = gfx::NSImageFromImageSkia(gfx::CreateVectorIcon( | |
| 153 gfx::VectorIconId::BLUETOOTH_CONNECTED, gfx::kChromeIconGrey)); | |
| 154 } else if (level != -1) { | |
| 155 DCHECK_GE(level, 0); | |
| 156 DCHECK_LT(level, base::checked_cast<NSInteger>( | |
| 157 arraysize(kSignalStrengthLevelImageIds))); | |
| 158 image = rb.GetNativeImageNamed(kSignalStrengthLevelImageIds[level]) | |
| 159 .ToNSImage(); | |
| 160 } | |
| 161 | |
| 162 CGFloat imageOriginX = kTableRowViewHorizontalPadding; | |
| 163 CGFloat imageOriginY = (rowHeight - kRowViewImageSize) / 2; | |
| 164 if (image) { | |
| 165 image_.reset([[NSImageView alloc] | |
| 166 initWithFrame:NSMakeRect(imageOriginX, imageOriginY, | |
| 167 kRowViewImageSize, kRowViewImageSize)]); | |
| 168 [image_ setImage:image]; | |
| 169 [self addSubview:image_]; | |
| 170 } | |
| 171 | |
| 172 // Text. | |
| 173 text_ = CreateLabel(text); | |
| 174 CGFloat textHeight = NSHeight([text_ frame]); | |
| 175 | |
| 176 // Paired status. | |
| 177 CGFloat pairedStatusHeight = 0.0f; | |
| 178 if (isPaired) { | |
| 179 pairedStatus_ = CreateLabel( | |
| 180 l10n_util::GetNSString(IDS_DEVICE_CHOOSER_PAIRED_STATUS_TEXT)); | |
| 181 [pairedStatus_ | |
| 182 setTextColor:skia::SkColorToCalibratedNSColor(gfx::kGoogleGreen700)]; | |
| 183 pairedStatusHeight = NSHeight([pairedStatus_ frame]); | |
| 184 } | |
| 185 | |
| 186 // Lay out the views. | |
| 187 // Text. | |
| 188 CGFloat textOriginX = kTableRowViewHorizontalPadding; | |
| 189 if (textNeedIndentation) | |
| 190 textOriginX += imageOriginX + kRowViewImageSize; | |
| 191 CGFloat textOriginY; | |
| 192 if (isPaired) { | |
| 193 textOriginY = pairedStatusHeight + | |
| 194 (rowHeight - textHeight - pairedStatusHeight - | |
| 195 kTableRowViewVerticalPadding) / | |
| 196 2 + | |
| 197 kTableRowViewVerticalPadding; | |
| 198 } else { | |
| 199 textOriginY = (rowHeight - textHeight) / 2; | |
| 200 } | |
| 201 | |
| 202 [text_ setFrameOrigin:NSMakePoint(textOriginX, textOriginY)]; | |
| 203 [self addSubview:text_]; | |
| 204 | |
| 205 // Paired status. | |
| 206 if (isPaired) { | |
| 207 CGFloat pairedStatusOriginX = textOriginX; | |
| 208 CGFloat pairedStatusOriginY = | |
| 209 (rowHeight - textHeight - pairedStatusHeight - | |
| 210 kTableRowViewVerticalPadding) / | |
| 211 2; | |
| 212 [pairedStatus_ | |
| 213 setFrameOrigin:NSMakePoint(pairedStatusOriginX, pairedStatusOriginY)]; | |
| 214 [self addSubview:pairedStatus_]; | |
| 215 } | |
| 216 } | |
| 217 | |
| 218 return self; | |
| 219 } | |
| 220 | |
| 221 - (NSImageView*)image { | |
| 222 return image_.get(); | |
| 223 } | |
| 224 | |
| 225 - (NSTextField*)text { | |
| 226 return text_.get(); | |
| 227 } | |
| 228 | |
| 229 - (NSTextField*)pairedStatus { | |
| 230 return pairedStatus_.get(); | |
| 231 } | |
| 232 | |
| 233 @end | |
| 234 | |
| 235 class ChooserContentViewController : public ChooserController::View { | |
| 236 public: | |
| 237 ChooserContentViewController(ChooserController* chooser_controller, | |
| 238 NSButton* adapter_off_help_button, | |
| 239 NSTextField* adapter_off_message, | |
| 240 NSTableView* table_view, | |
| 241 SpinnerView* spinner, | |
| 242 NSTextField* scanning_message, | |
| 243 NSTextField* word_connector, | |
| 244 NSButton* rescan_button); | |
| 245 ~ChooserContentViewController() override; | |
| 246 | |
| 247 // ChooserController::View: | |
| 248 void OnOptionsInitialized() override; | |
| 249 void OnOptionAdded(size_t index) override; | |
| 250 void OnOptionRemoved(size_t index) override; | |
| 251 void OnOptionUpdated(size_t index) override; | |
| 252 void OnAdapterEnabledChanged(bool enabled) override; | |
| 253 void OnRefreshStateChanged(bool refreshing) override; | |
| 254 | |
| 255 void UpdateTableView(); | |
| 256 | |
| 257 private: | |
| 258 ChooserController* chooser_controller_; | |
| 259 NSButton* adapter_off_help_button_; | |
| 260 NSTextField* adapter_off_message_; | |
| 261 NSTableView* table_view_; | |
| 262 SpinnerView* spinner_; | |
| 263 NSTextField* scanning_message_; | |
| 264 NSTextField* word_connector_; | |
| 265 NSButton* rescan_button_; | |
| 266 | |
| 267 DISALLOW_COPY_AND_ASSIGN(ChooserContentViewController); | |
| 268 }; | |
| 269 | |
| 270 ChooserContentViewController::ChooserContentViewController( | |
| 271 ChooserController* chooser_controller, | |
| 272 NSButton* adapter_off_help_button, | |
| 273 NSTextField* adapter_off_message, | |
| 274 NSTableView* table_view, | |
| 275 SpinnerView* spinner, | |
| 276 NSTextField* scanning_message, | |
| 277 NSTextField* word_connector, | |
| 278 NSButton* rescan_button) | |
| 279 : chooser_controller_(chooser_controller), | |
| 280 adapter_off_help_button_(adapter_off_help_button), | |
| 281 adapter_off_message_(adapter_off_message), | |
| 282 table_view_(table_view), | |
| 283 spinner_(spinner), | |
| 284 scanning_message_(scanning_message), | |
| 285 word_connector_(word_connector), | |
| 286 rescan_button_(rescan_button) { | |
| 287 DCHECK(chooser_controller_); | |
| 288 DCHECK(adapter_off_help_button_); | |
| 289 DCHECK(adapter_off_message_); | |
| 290 DCHECK(table_view_); | |
| 291 DCHECK(spinner_); | |
| 292 DCHECK(scanning_message_); | |
| 293 DCHECK(word_connector_); | |
| 294 DCHECK(rescan_button_); | |
| 295 chooser_controller_->set_view(this); | |
| 296 } | |
| 297 | |
| 298 ChooserContentViewController::~ChooserContentViewController() { | |
| 299 chooser_controller_->set_view(nullptr); | |
| 300 } | |
| 301 | |
| 302 void ChooserContentViewController::OnOptionsInitialized() { | |
| 303 UpdateTableView(); | |
| 304 } | |
| 305 | |
| 306 void ChooserContentViewController::OnOptionAdded(size_t index) { | |
| 307 UpdateTableView(); | |
| 308 [table_view_ setHidden:NO]; | |
| 309 [spinner_ setHidden:YES]; | |
| 310 } | |
| 311 | |
| 312 void ChooserContentViewController::OnOptionRemoved(size_t index) { | |
| 313 // |table_view_| will automatically select the removed item's next item. | |
| 314 // So here it tracks if the removed item is the item that was currently | |
| 315 // selected, if so, deselect it. Also if the removed item is before the | |
| 316 // currently selected item, the currently selected item's index needs to | |
| 317 // be adjusted by one. | |
| 318 NSIndexSet* selected_rows = [table_view_ selectedRowIndexes]; | |
| 319 NSMutableIndexSet* updated_selected_rows = [NSMutableIndexSet indexSet]; | |
| 320 NSUInteger row = [selected_rows firstIndex]; | |
| 321 while (row != NSNotFound) { | |
| 322 if (row < index) | |
| 323 [updated_selected_rows addIndex:row]; | |
| 324 else if (row > index) | |
| 325 [updated_selected_rows addIndex:row - 1]; | |
| 326 row = [selected_rows indexGreaterThanIndex:row]; | |
| 327 } | |
| 328 | |
| 329 [table_view_ selectRowIndexes:updated_selected_rows byExtendingSelection:NO]; | |
| 330 | |
| 331 UpdateTableView(); | |
| 332 } | |
| 333 | |
| 334 void ChooserContentViewController::OnOptionUpdated(size_t index) { | |
| 335 UpdateTableView(); | |
| 336 } | |
| 337 | |
| 338 void ChooserContentViewController::OnAdapterEnabledChanged(bool enabled) { | |
| 339 // No row is selected since the adapter status has changed. | |
| 340 // This will also disable the OK button if it was enabled because | |
| 341 // of a previously selected row. | |
| 342 [table_view_ deselectAll:nil]; | |
| 343 UpdateTableView(); | |
| 344 [table_view_ setHidden:enabled ? NO : YES]; | |
| 345 [adapter_off_help_button_ setHidden:enabled ? YES : NO]; | |
| 346 [adapter_off_message_ setHidden:enabled ? YES : NO]; | |
| 347 | |
| 348 [spinner_ setHidden:YES]; | |
| 349 | |
| 350 [scanning_message_ setHidden:YES]; | |
| 351 // When adapter is enabled, show |word_connector_| and |rescan_button_|; | |
| 352 // otherwise hide them. | |
| 353 [word_connector_ setHidden:enabled ? NO : YES]; | |
| 354 [rescan_button_ setHidden:enabled ? NO : YES]; | |
| 355 } | |
| 356 | |
| 357 void ChooserContentViewController::OnRefreshStateChanged(bool refreshing) { | |
| 358 if (refreshing) { | |
| 359 // No row is selected since the chooser is refreshing. | |
| 360 // This will also disable the OK button if it was enabled because | |
| 361 // of a previously selected row. | |
| 362 [table_view_ deselectAll:nil]; | |
| 363 UpdateTableView(); | |
| 364 } | |
| 365 | |
| 366 // When refreshing and no option available yet, hide |table_view_| and show | |
| 367 // |spinner_|. Otherwise show |table_view_| and hide |spinner_|. | |
| 368 bool table_view_hidden = | |
| 369 refreshing && (chooser_controller_->NumOptions() == 0); | |
| 370 [table_view_ setHidden:table_view_hidden ? YES : NO]; | |
| 371 [spinner_ setHidden:table_view_hidden ? NO : YES]; | |
| 372 | |
| 373 // When refreshing, show |scanning_message_| and hide |word_connector_| and | |
| 374 // |rescan_button_|. | |
| 375 // When complete, show |word_connector_| and |rescan_button_| and hide | |
| 376 // |scanning_message_|. | |
| 377 [scanning_message_ setHidden:refreshing ? NO : YES]; | |
| 378 [word_connector_ setHidden:refreshing ? YES : NO]; | |
| 379 [rescan_button_ setHidden:refreshing ? YES : NO]; | |
| 380 } | |
| 381 | |
| 382 void ChooserContentViewController::UpdateTableView() { | |
| 383 [table_view_ setEnabled:chooser_controller_->NumOptions() > 0]; | |
| 384 // For NSView-based table views, calling reloadData will deselect the | |
| 385 // currently selected row, so |selected_rows| stores the currently selected | |
| 386 // rows in order to select them again. | |
| 387 NSIndexSet* selected_rows = [table_view_ selectedRowIndexes]; | |
| 388 [table_view_ reloadData]; | |
| 389 [table_view_ selectRowIndexes:selected_rows byExtendingSelection:NO]; | |
| 390 } | |
| 391 | |
| 392 @implementation ChooserContentViewCocoa | |
| 393 | |
| 394 // TODO(juncai): restructure this function to be some smaller methods to | |
| 395 // create the pieces for the view. By doing so, the methods that calculate | |
| 396 // the frame and origins can be moved into those methods, rather than as | |
| 397 // helper functions. | |
| 398 - (instancetype)initWithChooserTitle:(NSString*)chooserTitle | |
| 399 chooserController: | |
| 400 (std::unique_ptr<ChooserController>)chooserController { | |
| 401 // ------------------------------------ | |
| 402 // | Chooser title | | |
| 403 // | -------------------------------- | | |
| 404 // | | option 0 | | | |
| 405 // | | option 1 | | | |
| 406 // | | option 2 | | | |
| 407 // | | | | | |
| 408 // | | | | | |
| 409 // | | | | | |
| 410 // | -------------------------------- | | |
| 411 // | [ Connect ] [ Cancel ] | | |
| 412 // |----------------------------------| | |
| 413 // | Get help | | |
| 414 // ------------------------------------ | |
| 415 | |
| 416 // Determine the dimensions of the chooser. | |
| 417 // Once the height and width are set, the buttons and permission menus can | |
| 418 // be laid out correctly. | |
| 419 NSRect chooserFrame = NSMakeRect(0, 0, kChooserWidth, kChooserHeight); | |
| 420 | |
| 421 if ((self = [super initWithFrame:chooserFrame])) { | |
| 422 chooserController_ = std::move(chooserController); | |
| 423 | |
| 424 // Create the views. | |
| 425 // Title. | |
| 426 titleView_ = [self createChooserTitle:chooserTitle]; | |
| 427 CGFloat titleHeight = NSHeight([titleView_ frame]); | |
| 428 | |
| 429 // Adapter turned off help button. | |
| 430 adapterOffHelpButton_ = [self | |
| 431 createHyperlinkButtonWithText: | |
| 432 l10n_util::GetNSString( | |
| 433 IDS_BLUETOOTH_DEVICE_CHOOSER_TURN_ON_BLUETOOTH_LINK_TEXT)]; | |
| 434 CGFloat adapterOffHelpButtonWidth = NSWidth([adapterOffHelpButton_ frame]); | |
| 435 CGFloat adapterOffHelpButtonHeight = | |
| 436 NSHeight([adapterOffHelpButton_ frame]); | |
| 437 | |
| 438 // Adapter turned off message. | |
| 439 adapterOffMessage_ = CreateLabel(l10n_util::GetNSStringF( | |
| 440 IDS_BLUETOOTH_DEVICE_CHOOSER_TURN_ADAPTER_OFF, base::string16())); | |
| 441 | |
| 442 // Connect button. | |
| 443 connectButton_ = [self createConnectButton]; | |
| 444 CGFloat connectButtonWidth = NSWidth([connectButton_ frame]); | |
| 445 CGFloat connectButtonHeight = NSHeight([connectButton_ frame]); | |
| 446 | |
| 447 // Cancel button. | |
| 448 cancelButton_ = [self createCancelButton]; | |
| 449 CGFloat cancelButtonWidth = NSWidth([cancelButton_ frame]); | |
| 450 | |
| 451 // Separator. | |
| 452 separator_ = [self createSeparator]; | |
| 453 | |
| 454 // Help button. | |
| 455 CGFloat helpButtonWidth = 0.0f; | |
| 456 CGFloat helpButtonHeight = 0.0f; | |
| 457 if (chooserController_->ShouldShowFootnoteView()) { | |
| 458 helpButton_ = | |
| 459 [self createHyperlinkButtonWithText: | |
| 460 l10n_util::GetNSStringF( | |
| 461 IDS_DEVICE_CHOOSER_GET_HELP_LINK_WITH_SCANNING_STATUS, | |
| 462 base::string16())]; | |
| 463 helpButtonWidth = NSWidth([helpButton_ frame]); | |
| 464 helpButtonHeight = NSHeight([helpButton_ frame]); | |
| 465 } | |
| 466 | |
| 467 // Scanning message. | |
| 468 scanningMessage_ = CreateLabel( | |
| 469 l10n_util::GetNSString(IDS_BLUETOOTH_DEVICE_CHOOSER_SCANNING)); | |
| 470 | |
| 471 // Word connector. | |
| 472 wordConnector_ = CreateLabel(l10n_util::GetNSStringF( | |
| 473 IDS_DEVICE_CHOOSER_GET_HELP_LINK_WITH_RE_SCAN_LINK, base::string16(), | |
| 474 base::string16())); | |
| 475 CGFloat wordConnectorWidth = NSWidth([wordConnector_ frame]); | |
| 476 | |
| 477 // Re-scan button. | |
| 478 rescanButton_ = | |
| 479 [self createHyperlinkButtonWithText: | |
| 480 l10n_util::GetNSString(IDS_BLUETOOTH_DEVICE_CHOOSER_RE_SCAN)]; | |
| 481 | |
| 482 // ScollView embedding with TableView. | |
| 483 CGFloat scrollViewWidth = kChooserWidth - 2 * kMarginX; | |
| 484 CGFloat scrollViewHeight = | |
| 485 kChooserHeight - 2 * kMarginY - | |
| 486 (chooserController_->ShouldShowFootnoteView() ? 4 * kVerticalPadding | |
| 487 : 2 * kVerticalPadding) - | |
| 488 titleHeight - connectButtonHeight - helpButtonHeight; | |
| 489 CGFloat scrollViewOriginX = kMarginX; | |
| 490 CGFloat scrollViewOriginY = | |
| 491 kMarginY + helpButtonHeight + | |
| 492 (chooserController_->ShouldShowFootnoteView() ? 3 * kVerticalPadding | |
| 493 : kVerticalPadding) + | |
| 494 connectButtonHeight; | |
| 495 NSRect scrollFrame = NSMakeRect(scrollViewOriginX, scrollViewOriginY, | |
| 496 scrollViewWidth, scrollViewHeight); | |
| 497 scrollView_.reset([[NSScrollView alloc] initWithFrame:scrollFrame]); | |
| 498 [scrollView_ setBorderType:NSBezelBorder]; | |
| 499 [scrollView_ setHasVerticalScroller:YES]; | |
| 500 [scrollView_ setHasHorizontalScroller:YES]; | |
| 501 [scrollView_ setAutohidesScrollers:YES]; | |
| 502 [scrollView_ setDrawsBackground:NO]; | |
| 503 | |
| 504 // TableView. | |
| 505 tableView_.reset([[NSTableView alloc] initWithFrame:NSZeroRect]); | |
| 506 tableColumn_.reset([[NSTableColumn alloc] initWithIdentifier:@""]); | |
| 507 [tableColumn_ setWidth:(scrollViewWidth - kMarginX)]; | |
| 508 [tableView_ addTableColumn:tableColumn_]; | |
| 509 // Make the column title invisible. | |
| 510 [tableView_ setHeaderView:nil]; | |
| 511 [tableView_ setFocusRingType:NSFocusRingTypeNone]; | |
| 512 [tableView_ | |
| 513 setAllowsMultipleSelection:chooserController_->AllowMultipleSelection() | |
| 514 ? YES | |
| 515 : NO]; | |
| 516 | |
| 517 // Spinner. | |
| 518 // Set the spinner in the center of the scroll view. | |
| 519 CGFloat spinnerOriginX = | |
| 520 scrollViewOriginX + (scrollViewWidth - kSpinnerSize) / 2; | |
| 521 CGFloat spinnerOriginY = | |
| 522 scrollViewOriginY + (scrollViewHeight - kSpinnerSize) / 2; | |
| 523 spinner_.reset([[SpinnerView alloc] | |
| 524 initWithFrame:NSMakeRect(spinnerOriginX, spinnerOriginY, kSpinnerSize, | |
| 525 kSpinnerSize)]); | |
| 526 | |
| 527 // Lay out the views. | |
| 528 // Title. | |
| 529 CGFloat titleOriginX = kMarginX; | |
| 530 CGFloat titleOriginY = kChooserHeight - kMarginY - titleHeight; | |
| 531 [titleView_ setFrameOrigin:NSMakePoint(titleOriginX, titleOriginY)]; | |
| 532 [self addSubview:titleView_]; | |
| 533 | |
| 534 // Adapter turned off help button. | |
| 535 CGFloat adapterOffHelpButtonOriginX = kMarginX + kAdapterOffHelpLinkPadding; | |
| 536 CGFloat adapterOffHelpButtonOriginY = titleOriginY - kVerticalPadding - | |
| 537 adapterOffHelpButtonHeight - | |
| 538 kAdapterOffHelpLinkPadding; | |
| 539 [adapterOffHelpButton_ | |
| 540 setFrameOrigin:NSMakePoint(adapterOffHelpButtonOriginX, | |
| 541 adapterOffHelpButtonOriginY)]; | |
| 542 [adapterOffHelpButton_ setTarget:self]; | |
| 543 [adapterOffHelpButton_ setAction:@selector(onAdapterOffHelp:)]; | |
| 544 [adapterOffHelpButton_ setHidden:YES]; | |
| 545 [self addSubview:adapterOffHelpButton_]; | |
| 546 | |
| 547 // Adapter turned off message. | |
| 548 CGFloat adapterOffMessageOriginX = adapterOffHelpButtonOriginX + | |
| 549 adapterOffHelpButtonWidth - | |
| 550 kHorizontalPadding / 2; | |
| 551 CGFloat adapterOffMessageOriginY = adapterOffHelpButtonOriginY; | |
| 552 [adapterOffMessage_ setFrameOrigin:NSMakePoint(adapterOffMessageOriginX, | |
| 553 adapterOffMessageOriginY)]; | |
| 554 [adapterOffMessage_ setHidden:YES]; | |
| 555 [self addSubview:adapterOffMessage_]; | |
| 556 | |
| 557 // ScollView and Spinner. Only one of them is shown. | |
| 558 [scrollView_ setDocumentView:tableView_]; | |
| 559 [self addSubview:scrollView_]; | |
| 560 [spinner_ setHidden:YES]; | |
| 561 [self addSubview:spinner_]; | |
| 562 | |
| 563 // Connect button. | |
| 564 CGFloat connectButtonOriginX = kChooserWidth - kMarginX - | |
| 565 kHorizontalPadding - connectButtonWidth - | |
| 566 cancelButtonWidth; | |
| 567 CGFloat connectButtonOriginY = | |
| 568 kMarginY + helpButtonHeight + | |
| 569 (chooserController_->ShouldShowFootnoteView() ? 2 * kVerticalPadding | |
| 570 : 0.0f); | |
| 571 [connectButton_ | |
| 572 setFrameOrigin:NSMakePoint(connectButtonOriginX, connectButtonOriginY)]; | |
| 573 [connectButton_ setEnabled:NO]; | |
| 574 [self addSubview:connectButton_]; | |
| 575 | |
| 576 // Cancel button. | |
| 577 CGFloat cancelButtonOriginX = kChooserWidth - kMarginX - cancelButtonWidth; | |
| 578 CGFloat cancelButtonOriginY = connectButtonOriginY; | |
| 579 [cancelButton_ | |
| 580 setFrameOrigin:NSMakePoint(cancelButtonOriginX, cancelButtonOriginY)]; | |
| 581 [self addSubview:cancelButton_]; | |
| 582 | |
| 583 if (chooserController_->ShouldShowFootnoteView()) { | |
| 584 // Separator. | |
| 585 CGFloat separatorOriginX = 0.0f; | |
| 586 separatorOriginY_ = kMarginY + helpButtonHeight + kVerticalPadding; | |
| 587 [separator_ | |
| 588 setFrameOrigin:NSMakePoint(separatorOriginX, separatorOriginY_)]; | |
| 589 [self addSubview:separator_]; | |
| 590 | |
| 591 // Help button. | |
| 592 CGFloat helpButtonOriginX = kMarginX; | |
| 593 CGFloat helpButtonOriginY = (kVerticalPadding + kMarginY) / 2; | |
| 594 [helpButton_ | |
| 595 setFrameOrigin:NSMakePoint(helpButtonOriginX, helpButtonOriginY)]; | |
| 596 [helpButton_ setTarget:self]; | |
| 597 [helpButton_ setAction:@selector(onHelpPressed:)]; | |
| 598 [self addSubview:helpButton_]; | |
| 599 | |
| 600 // Scanning message. | |
| 601 CGFloat scanningMessageOriginX = | |
| 602 kMarginX + helpButtonWidth - kHorizontalPadding / 2; | |
| 603 CGFloat scanningMessageOriginY = helpButtonOriginY; | |
| 604 [scanningMessage_ setFrameOrigin:NSMakePoint(scanningMessageOriginX, | |
| 605 scanningMessageOriginY)]; | |
| 606 [self addSubview:scanningMessage_]; | |
| 607 [scanningMessage_ setHidden:YES]; | |
| 608 | |
| 609 // Word connector. | |
| 610 CGFloat wordConnectorOriginX = | |
| 611 kMarginX + helpButtonWidth - kHorizontalPadding / 2; | |
| 612 CGFloat wordConnectorOriginY = helpButtonOriginY; | |
| 613 [wordConnector_ setFrameOrigin:NSMakePoint(wordConnectorOriginX, | |
| 614 wordConnectorOriginY)]; | |
| 615 [self addSubview:wordConnector_]; | |
| 616 [wordConnector_ setHidden:YES]; | |
| 617 | |
| 618 // Re-scan button. | |
| 619 CGFloat reScanButtonOriginX = | |
| 620 kMarginX + helpButtonWidth + wordConnectorWidth - kHorizontalPadding; | |
| 621 CGFloat reScanButtonOriginY = helpButtonOriginY; | |
| 622 [rescanButton_ | |
| 623 setFrameOrigin:NSMakePoint(reScanButtonOriginX, reScanButtonOriginY)]; | |
| 624 [rescanButton_ setTarget:self]; | |
| 625 [rescanButton_ setAction:@selector(onRescan:)]; | |
| 626 [self addSubview:rescanButton_]; | |
| 627 [rescanButton_ setHidden:YES]; | |
| 628 } | |
| 629 | |
| 630 chooserContentViewController_.reset(new ChooserContentViewController( | |
| 631 chooserController_.get(), adapterOffHelpButton_.get(), | |
| 632 adapterOffMessage_.get(), tableView_.get(), spinner_.get(), | |
| 633 scanningMessage_.get(), wordConnector_.get(), rescanButton_.get())); | |
| 634 } | |
| 635 | |
| 636 return self; | |
| 637 } | |
| 638 | |
| 639 - (base::scoped_nsobject<NSTextField>)createChooserTitle:(NSString*)title { | |
| 640 base::scoped_nsobject<NSTextField> titleView( | |
| 641 [[NSTextField alloc] initWithFrame:NSZeroRect]); | |
| 642 [titleView setDrawsBackground:NO]; | |
| 643 [titleView setBezeled:NO]; | |
| 644 [titleView setEditable:NO]; | |
| 645 [titleView setSelectable:NO]; | |
| 646 [titleView setStringValue:title]; | |
| 647 [titleView setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]]; | |
| 648 // The height is arbitrary as it will be adjusted later. | |
| 649 [titleView setFrameSize:NSMakeSize(kChooserWidth - 2 * kMarginX, 0.0f)]; | |
| 650 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:titleView]; | |
| 651 return titleView; | |
| 652 } | |
| 653 | |
| 654 - (base::scoped_nsobject<NSView>)createTableRowView:(NSInteger)row { | |
| 655 NSInteger level = -1; | |
| 656 bool isConnected = false; | |
| 657 bool isPaired = false; | |
| 658 size_t numOptions = chooserController_->NumOptions(); | |
| 659 base::scoped_nsobject<NSView> tableRowView; | |
| 660 if (numOptions == 0) { | |
| 661 DCHECK_EQ(0, row); | |
| 662 tableRowView.reset([[ChooserContentTableRowView alloc] | |
| 663 initWithText:[self optionAtIndex:row]]); | |
| 664 } else { | |
| 665 DCHECK_GE(row, 0); | |
| 666 DCHECK_LT(row, base::checked_cast<NSInteger>(numOptions)); | |
| 667 size_t rowIndex = base::checked_cast<size_t>(row); | |
| 668 if (chooserController_->ShouldShowIconBeforeText()) { | |
| 669 level = base::checked_cast<NSInteger>( | |
| 670 chooserController_->GetSignalStrengthLevel(rowIndex)); | |
| 671 } | |
| 672 isConnected = chooserController_->IsConnected(rowIndex); | |
| 673 isPaired = chooserController_->IsPaired(rowIndex); | |
| 674 bool textNeedIndentation = false; | |
| 675 for (size_t i = 0; i < numOptions; ++i) { | |
| 676 if (chooserController_->GetSignalStrengthLevel(i) != -1 || | |
| 677 chooserController_->IsConnected(i)) { | |
| 678 textNeedIndentation = true; | |
| 679 break; | |
| 680 } | |
| 681 } | |
| 682 tableRowView.reset([[ChooserContentTableRowView alloc] | |
| 683 initWithText:[self optionAtIndex:row] | |
| 684 signalStrengthLevel:level | |
| 685 isConnected:isConnected | |
| 686 isPaired:isPaired | |
| 687 rowHeight:[self tableRowViewHeight:row] | |
| 688 textNeedIndentation:textNeedIndentation]); | |
| 689 } | |
| 690 | |
| 691 return tableRowView; | |
| 692 } | |
| 693 | |
| 694 - (CGFloat)tableRowViewHeight:(NSInteger)row { | |
| 695 size_t numOptions = chooserController_->NumOptions(); | |
| 696 if (numOptions == 0) { | |
| 697 DCHECK_EQ(0, row); | |
| 698 return kTableRowViewOneLineHeight; | |
| 699 } | |
| 700 | |
| 701 DCHECK_GE(row, 0); | |
| 702 DCHECK_LT(row, base::checked_cast<NSInteger>(numOptions)); | |
| 703 size_t rowIndex = base::checked_cast<size_t>(row); | |
| 704 return chooserController_->IsPaired(rowIndex) ? kTableRowViewTwoLinesHeight | |
| 705 : kTableRowViewOneLineHeight; | |
| 706 } | |
| 707 | |
| 708 - (base::scoped_nsobject<NSButton>)createButtonWithTitle:(NSString*)title { | |
| 709 base::scoped_nsobject<NSButton> button( | |
| 710 [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]); | |
| 711 [button setButtonType:NSMomentaryPushInButton]; | |
| 712 [button setTitle:title]; | |
| 713 [button sizeToFit]; | |
| 714 return button; | |
| 715 } | |
| 716 | |
| 717 - (base::scoped_nsobject<NSButton>)createConnectButton { | |
| 718 NSString* connectTitle = | |
| 719 base::SysUTF16ToNSString(chooserController_->GetOkButtonLabel()); | |
| 720 return [self createButtonWithTitle:connectTitle]; | |
| 721 } | |
| 722 | |
| 723 - (base::scoped_nsobject<NSButton>)createCancelButton { | |
| 724 NSString* cancelTitle = | |
| 725 l10n_util::GetNSString(IDS_DEVICE_CHOOSER_CANCEL_BUTTON_TEXT); | |
| 726 return [self createButtonWithTitle:cancelTitle]; | |
| 727 } | |
| 728 | |
| 729 - (base::scoped_nsobject<NSBox>)createSeparator { | |
| 730 base::scoped_nsobject<NSBox> spacer([[NSBox alloc] initWithFrame:NSZeroRect]); | |
| 731 [spacer setBoxType:NSBoxSeparator]; | |
| 732 [spacer setBorderType:NSLineBorder]; | |
| 733 [spacer setAlphaValue:kSeparatorAlphaValue]; | |
| 734 [spacer setFrameSize:NSMakeSize(kChooserWidth, kSeparatorHeight)]; | |
| 735 return spacer; | |
| 736 } | |
| 737 | |
| 738 - (base::scoped_nsobject<NSButton>)createHyperlinkButtonWithText: | |
| 739 (NSString*)text { | |
| 740 base::scoped_nsobject<NSButton> button( | |
| 741 [[NSButton alloc] initWithFrame:NSZeroRect]); | |
| 742 base::scoped_nsobject<HyperlinkButtonCell> cell( | |
| 743 [[HyperlinkButtonCell alloc] initTextCell:text]); | |
| 744 [button setCell:cell.get()]; | |
| 745 [button sizeToFit]; | |
| 746 return button; | |
| 747 } | |
| 748 | |
| 749 - (NSButton*)adapterOffHelpButton { | |
| 750 return adapterOffHelpButton_.get(); | |
| 751 } | |
| 752 | |
| 753 - (NSTableView*)tableView { | |
| 754 return tableView_.get(); | |
| 755 } | |
| 756 | |
| 757 - (SpinnerView*)spinner { | |
| 758 return spinner_.get(); | |
| 759 } | |
| 760 | |
| 761 - (NSButton*)connectButton { | |
| 762 return connectButton_.get(); | |
| 763 } | |
| 764 | |
| 765 - (NSButton*)cancelButton { | |
| 766 return cancelButton_.get(); | |
| 767 } | |
| 768 | |
| 769 - (NSButton*)helpButton { | |
| 770 return helpButton_.get(); | |
| 771 } | |
| 772 | |
| 773 - (NSTextField*)scanningMessage { | |
| 774 return scanningMessage_.get(); | |
| 775 } | |
| 776 | |
| 777 - (NSTextField*)wordConnector { | |
| 778 return wordConnector_.get(); | |
| 779 } | |
| 780 | |
| 781 - (NSButton*)rescanButton { | |
| 782 return rescanButton_.get(); | |
| 783 } | |
| 784 | |
| 785 - (NSInteger)numberOfOptions { | |
| 786 // When there are no devices, the table contains a message saying there are | |
| 787 // no devices, so the number of rows is always at least 1. | |
| 788 return std::max(static_cast<NSInteger>(chooserController_->NumOptions()), | |
| 789 static_cast<NSInteger>(1)); | |
| 790 } | |
| 791 | |
| 792 // When this function is called with numOptions == 0, it is to show the | |
| 793 // message saying there are no devices. | |
| 794 - (NSString*)optionAtIndex:(NSInteger)index { | |
| 795 NSInteger numOptions = | |
| 796 static_cast<NSInteger>(chooserController_->NumOptions()); | |
| 797 if (numOptions == 0) { | |
| 798 DCHECK_EQ(0, index); | |
| 799 return base::SysUTF16ToNSString(chooserController_->GetNoOptionsText()); | |
| 800 } | |
| 801 | |
| 802 DCHECK_GE(index, 0); | |
| 803 DCHECK_LT(index, numOptions); | |
| 804 | |
| 805 return base::SysUTF16ToNSString( | |
| 806 chooserController_->GetOption(static_cast<size_t>(index))); | |
| 807 } | |
| 808 | |
| 809 - (void)updateTableView { | |
| 810 chooserContentViewController_->UpdateTableView(); | |
| 811 } | |
| 812 | |
| 813 - (void)accept { | |
| 814 NSIndexSet* selectedRows = [tableView_ selectedRowIndexes]; | |
| 815 NSUInteger index = [selectedRows firstIndex]; | |
| 816 std::vector<size_t> indices; | |
| 817 while (index != NSNotFound) { | |
| 818 indices.push_back(index); | |
| 819 index = [selectedRows indexGreaterThanIndex:index]; | |
| 820 } | |
| 821 chooserController_->Select(indices); | |
| 822 } | |
| 823 | |
| 824 - (void)cancel { | |
| 825 chooserController_->Cancel(); | |
| 826 } | |
| 827 | |
| 828 - (void)close { | |
| 829 chooserController_->Close(); | |
| 830 } | |
| 831 | |
| 832 - (void)onAdapterOffHelp:(id)sender { | |
| 833 chooserController_->OpenAdapterOffHelpUrl(); | |
| 834 } | |
| 835 | |
| 836 - (void)onRescan:(id)sender { | |
| 837 chooserController_->RefreshOptions(); | |
| 838 } | |
| 839 | |
| 840 - (void)onHelpPressed:(id)sender { | |
| 841 chooserController_->OpenHelpCenterUrl(); | |
| 842 } | |
| 843 | |
| 844 - (void)updateContentRowColor { | |
| 845 NSIndexSet* selectedRows = [tableView_ selectedRowIndexes]; | |
| 846 NSInteger numOptions = | |
| 847 base::checked_cast<NSInteger>(chooserController_->NumOptions()); | |
| 848 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
| 849 for (NSInteger rowIndex = 0; rowIndex < numOptions; ++rowIndex) { | |
| 850 BOOL isSelected = [selectedRows containsIndex:rowIndex]; | |
| 851 // Update the color of the text. | |
| 852 [[self tableRowViewText:rowIndex] | |
| 853 setTextColor:(isSelected ? [NSColor whiteColor] | |
| 854 : [NSColor blackColor])]; | |
| 855 | |
| 856 // Update the color of the image. | |
| 857 if (chooserController_->ShouldShowIconBeforeText()) { | |
| 858 if (chooserController_->IsConnected(rowIndex)) { | |
| 859 [[self tableRowViewImage:rowIndex] | |
| 860 setImage:gfx::NSImageFromImageSkia(gfx::CreateVectorIcon( | |
| 861 gfx::VectorIconId::BLUETOOTH_CONNECTED, | |
| 862 isSelected ? SK_ColorWHITE : gfx::kChromeIconGrey))]; | |
| 863 } else { | |
| 864 int signalStrengthLevel = | |
| 865 chooserController_->GetSignalStrengthLevel(rowIndex); | |
| 866 if (signalStrengthLevel != -1) { | |
| 867 int imageId = | |
| 868 isSelected | |
| 869 ? kSignalStrengthLevelImageSelectedIds[signalStrengthLevel] | |
| 870 : kSignalStrengthLevelImageIds[signalStrengthLevel]; | |
| 871 [[self tableRowViewImage:rowIndex] | |
| 872 setImage:rb.GetNativeImageNamed(imageId).ToNSImage()]; | |
| 873 } | |
| 874 } | |
| 875 } | |
| 876 | |
| 877 // Update the color of paired status. | |
| 878 NSTextField* pairedStatusText = [self tableRowViewPairedStatus:rowIndex]; | |
| 879 if (pairedStatusText) { | |
| 880 [pairedStatusText setTextColor:(skia::SkColorToCalibratedNSColor( | |
| 881 isSelected ? gfx::kGoogleGreen300 | |
| 882 : gfx::kGoogleGreen700))]; | |
| 883 } | |
| 884 } | |
| 885 } | |
| 886 | |
| 887 - (NSImageView*)tableRowViewImage:(NSInteger)row { | |
| 888 ChooserContentTableRowView* tableRowView = | |
| 889 [tableView_ viewAtColumn:0 row:row makeIfNecessary:YES]; | |
| 890 return [tableRowView image]; | |
| 891 } | |
| 892 | |
| 893 - (NSTextField*)tableRowViewText:(NSInteger)row { | |
| 894 ChooserContentTableRowView* tableRowView = | |
| 895 [tableView_ viewAtColumn:0 row:row makeIfNecessary:YES]; | |
| 896 return [tableRowView text]; | |
| 897 } | |
| 898 | |
| 899 - (NSTextField*)tableRowViewPairedStatus:(NSInteger)row { | |
| 900 ChooserContentTableRowView* tableRowView = | |
| 901 [tableView_ viewAtColumn:0 row:row makeIfNecessary:YES]; | |
| 902 return [tableRowView pairedStatus]; | |
| 903 } | |
| 904 | |
| 905 - (void)drawRect:(NSRect)rect { | |
| 906 [[NSColor colorWithCalibratedWhite:245.0f / 255.0f alpha:1.0f] setFill]; | |
| 907 NSRect footnoteFrame = | |
| 908 NSMakeRect(0.0f, 0.0f, kChooserWidth, separatorOriginY_); | |
| 909 NSRectFill(footnoteFrame); | |
| 910 [super drawRect:footnoteFrame]; | |
| 911 } | |
| 912 | |
| 913 @end | |
| OLD | NEW |