1 // Copyright (c) 2012 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 <vector> 6 7 #include "base/basictypes.h" 8 #include "base/command_line.h" 9 #include "base/compiler_specific.h" 10 #include "base/i18n/rtl.h" 11 #include "base/strings/string_util.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "chrome/browser/extensions/bundle_installer.h" 14 #include "chrome/browser/extensions/extension_install_prompt.h" 15 #include "chrome/browser/profiles/profile.h" 16 #include "chrome/browser/ui/views/constrained_window_views.h" 17 #include "chrome/common/chrome_switches.h" 18 #include "chrome/common/extensions/extension.h" 19 #include "chrome/installer/util/browser_distribution.h" 20 #include "content/public/browser/page_navigator.h" 21 #include "content/public/browser/web_contents.h" 22 #include "grit/chromium_strings.h" 23 #include "grit/generated_resources.h" 24 #include "grit/google_chrome_strings.h" 25 #include "grit/theme_resources.h" 26 #include "ui/base/animation/animation_delegate.h" 27 #include "ui/base/animation/slide_animation.h" 28 #include "ui/base/l10n/l10n_util.h" 29 #include "ui/base/resource/resource_bundle.h" 30 #include "ui/gfx/transform.h" 31 #include "ui/views/border.h" 32 #include "ui/views/controls/button/image_button.h" 33 #include "ui/views/controls/image_view.h" 34 #include "ui/views/controls/label.h" 35 #include "ui/views/controls/link.h" 36 #include "ui/views/controls/link_listener.h" 37 #include "ui/views/controls/scroll_view.h" 38 #include "ui/views/controls/separator.h" 39 #include "ui/views/layout/box_layout.h" 40 #include "ui/views/layout/grid_layout.h" 41 #include "ui/views/layout/layout_constants.h" 42 #include "ui/views/view.h" 43 #include "ui/views/widget/widget.h" 44 #include "ui/views/window/dialog_delegate.h" 45 46 using content::OpenURLParams; 47 using content::Referrer; 48 using extensions::BundleInstaller; 49 50 namespace { 51 52 // Size of extension icon in top left of dialog. 53 const int kIconSize = 69; 54 55 // The dialog width. 56 const int kDialogWidth = 385; 57 58 // The dialog will resize based on its content, but this sets a maximum height 59 // before overflowing a scrollbar. 60 const int kDialogMaxHeight = 300; 61 62 // Width of the left column of the dialog when the extension requests 63 // permissions. 64 const int kPermissionsLeftColumnWidth = 250; 65 66 // Width of the left column of the dialog when the extension requests no 67 // permissions. 68 const int kNoPermissionsLeftColumnWidth = 200; 69 70 // Width of the left column for bundle install prompts. There's only one column 71 // in this case, so make it wider than normal. 72 const int kBundleLeftColumnWidth = 300; 73 74 // Width of the left column for external install prompts. The text is long in 75 // this case, so make it wider than normal. 76 const int kExternalInstallLeftColumnWidth = 350; 77 78 // Maximum height of the retained files view. 79 const int kMaxRetainedFilesHeight = 100; 80 81 typedef std::vector<string16> PermissionDetails; 82 83 void AddResourceIcon(const gfx::ImageSkia* skia_image, void* data) { 84 views::View* parent = static_cast<views::View*>(data); 85 views::ImageView* image_view = new views::ImageView(); 86 image_view->SetImage(*skia_image); 87 parent->AddChildView(image_view); 88 } 89 90 // Creates a string for displaying |message| to the user. If it has to look 91 // like a entry in a bullet point list, one is added. 92 string16 PrepareForDisplay(const string16& message, bool bullet_point) { 93 return bullet_point ? l10n_util::GetStringFUTF16( 94 IDS_EXTENSION_PERMISSION_LINE, 95 message) : message; 96 } 97 98 // Implements the extension installation dialog for TOOLKIT_VIEWS. 99 class ExtensionInstallDialogView : public views::DialogDelegateView, 100 public views::LinkListener { 101 public: 102 ExtensionInstallDialogView(content::PageNavigator* navigator, 103 ExtensionInstallPrompt::Delegate* delegate, 104 const ExtensionInstallPrompt::Prompt& prompt); 105 virtual ~ExtensionInstallDialogView(); 106 107 // Called when one of the child elements has expanded/collapsed. 108 void ContentsChanged(); 109 110 private: 111 // views::DialogDelegateView: 112 virtual int GetDialogButtons() const OVERRIDE; 113 virtual string16 GetDialogButtonLabel(ui::DialogButton button) const OVERRIDE; 114 virtual int GetDefaultDialogButton() const OVERRIDE; 115 virtual bool Cancel() OVERRIDE; 116 virtual bool Accept() OVERRIDE; 117 virtual ui::ModalType GetModalType() const OVERRIDE; 118 virtual string16 GetWindowTitle() const OVERRIDE; 119 virtual void Layout() OVERRIDE; 120 virtual gfx::Size GetPreferredSize() OVERRIDE; 121 122 // views::LinkListener: 123 virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE; 124 125 bool is_inline_install() const { 126 return prompt_.type() == ExtensionInstallPrompt::INLINE_INSTALL_PROMPT; 127 } 128 129 bool is_bundle_install() const { 130 return prompt_.type() == ExtensionInstallPrompt::BUNDLE_INSTALL_PROMPT; 131 } 132 133 bool is_external_install() const { 134 return prompt_.type() == ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT; 135 } 136 137 content::PageNavigator* navigator_; 138 ExtensionInstallPrompt::Delegate* delegate_; 139 ExtensionInstallPrompt::Prompt prompt_; 140 141 // The scroll view containing all the details for the dialog (including all 142 // collapsible/expandable sections). 143 views::ScrollView* scroll_view_; 144 145 // The container view for the scroll view. 146 views::View* scrollable_; 147 148 // The preferred size of the dialog. 149 gfx::Size dialog_size_; 150 151 DISALLOW_COPY_AND_ASSIGN(ExtensionInstallDialogView); 152 }; 153 154 // A view to display text with an expandable details section. 155 class ExpandableContainerView : public views::View, 156 public views::ButtonListener, 157 public views::LinkListener, 158 public ui::AnimationDelegate { 159 public: 160 ExpandableContainerView(ExtensionInstallDialogView* owner, 161 const string16& description, 162 const PermissionDetails& details, 163 int horizontal_space, 164 bool show_bullets); 165 virtual ~ExpandableContainerView(); 166 167 // views::View: 168 virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE; 169 170 // views::ButtonListener: 171 virtual void ButtonPressed(views::Button* sender, 172 const ui::Event& event) OVERRIDE; 173 174 // views::LinkListener: 175 virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE; 176 177 // ui::AnimationDelegate: 178 virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE; 179 virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE; 180 181 private: 182 // A view which displays all the details of an IssueAdviceInfoEntry. 183 class DetailsView : public views::View { 184 public: 185 explicit DetailsView(int horizontal_space, bool show_bullets); 186 virtual ~DetailsView() {} 187 188 // views::View: 189 virtual gfx::Size GetPreferredSize() OVERRIDE; 190 191 void AddDetail(const string16& detail); 192 193 // Animates this to be a height proportional to |state|. 194 void AnimateToState(double state); 195 196 private: 197 views::GridLayout* layout_; 198 double state_; 199 200 // Whether to show bullets in front of each item in the details. 201 bool show_bullets_; 202 203 DISALLOW_COPY_AND_ASSIGN(DetailsView); 204 }; 205 206 // Expand/Collapse the detail section for this ExpandableContainerView. 207 void ToggleDetailLevel(); 208 209 // The dialog that owns |this|. It's also an ancestor in the View hierarchy. 210 ExtensionInstallDialogView* owner_; 211 212 // A view for showing |issue_advice.details|. 213 DetailsView* details_view_; 214 215 // The '>' zippy control. 216 views::ImageView* arrow_view_; 217 218 ui::SlideAnimation slide_animation_; 219 220 // The 'more details' link shown under the heading (changes to 'hide details' 221 // when the details section is expanded). 222 views::Link* more_details_; 223 224 // The up/down arrow next to the 'more detail' link (points up/down depending 225 // on whether the details section is expanded). 226 views::ImageButton* arrow_toggle_; 227 228 // Whether the details section is expanded. 229 bool expanded_; 230 231 DISALLOW_COPY_AND_ASSIGN(ExpandableContainerView); 232 }; 233 234 void ShowExtensionInstallDialogImpl( 235 const ExtensionInstallPrompt::ShowParams& show_params, 236 ExtensionInstallPrompt::Delegate* delegate, 237 const ExtensionInstallPrompt::Prompt& prompt) { 238 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 239 CreateBrowserModalDialogViews( 240 new ExtensionInstallDialogView(show_params.navigator, delegate, prompt), 241 show_params.parent_window)->Show(); 242 } 243 244 // A ScrollView that imposes a maximum size on its viewport but sizes its 245 // contents to its preferred size. 246 class MaxSizeScrollView : public views::ScrollView { 247 public: 248 MaxSizeScrollView(int max_height, int max_width); 249 // Overridden from views::View: 250 virtual gfx::Size GetPreferredSize() OVERRIDE; 251 virtual void Layout() OVERRIDE; 252 253 private: 254 const int max_height_; 255 const int max_width_; 256 257 DISALLOW_COPY_AND_ASSIGN(MaxSizeScrollView); 258 }; 259 260 MaxSizeScrollView::MaxSizeScrollView(int max_height, int max_width) 261 : max_height_(max_height), 262 max_width_(max_width) {} 263 264 gfx::Size MaxSizeScrollView::GetPreferredSize() { 265 gfx::Size size = contents()->GetPreferredSize(); 266 size.SetToMin(gfx::Size(max_width_, max_height_)); 267 gfx::Insets insets = GetInsets(); 268 size.Enlarge(insets.width(), insets.height()); 269 size.Enlarge(GetScrollBarWidth(), GetScrollBarHeight()); 270 return size; 271 } 272 273 void MaxSizeScrollView::Layout() { 274 contents()->SizeToPreferredSize(); 275 views::ScrollView::Layout(); 276 } 277 278 } // namespace 279 280 ExtensionInstallDialogView::ExtensionInstallDialogView( 281 content::PageNavigator* navigator, 282 ExtensionInstallPrompt::Delegate* delegate, 283 const ExtensionInstallPrompt::Prompt& prompt) 284 : navigator_(navigator), 285 delegate_(delegate), 286 prompt_(prompt) { 287 // Possible grid layouts: 288 // Inline install 289 // w/ permissions no permissions 290 // +--------------------+------+ +--------------+------+ 291 // | heading | icon | | heading | icon | 292 // +--------------------| | +--------------| | 293 // | rating | | | rating | | 294 // +--------------------| | +--------------+ | 295 // | user_count | | | user_count | | 296 // +--------------------| | +--------------| | 297 // | store_link | | | store_link | | 298 // +--------------------+------+ +--------------+------+ 299 // | separator | 300 // +--------------------+------+ 301 // | permissions_header | | 302 // +--------------------+------+ 303 // | permission1 | | 304 // +--------------------+------+ 305 // | permission2 | | 306 // +--------------------+------+ 307 // 308 // Regular install 309 // w/ permissions XOR oauth issues no permissions 310 // +--------------------+------+ +--------------+------+ 311 // | heading | icon | | heading | icon | 312 // +--------------------| | +--------------+------+ 313 // | permissions_header | | 314 // +--------------------| | 315 // | permission1 | | 316 // +--------------------| | 317 // | permission2 | | 318 // +--------------------+------+ 319 // 320 // w/ permissions AND oauth issues 321 // +--------------------+------+ 322 // | heading | icon | 323 // +--------------------| | 324 // | permissions_header | | 325 // +--------------------| | 326 // | permission1 | | 327 // +--------------------| | 328 // | permission2 | | 329 // +--------------------+------+ 330 // | oauth header | 331 // +---------------------------+ 332 // | oauth issue 1 | 333 // +---------------------------+ 334 // | oauth issue 2 | 335 // +---------------------------+ 336 337 scroll_view_ = new views::ScrollView(); 338 AddChildView(scroll_view_); 339 scrollable_ = new views::View(); 340 scroll_view_->SetContents(scrollable_); 341 342 views::GridLayout* layout = views::GridLayout::CreatePanel(scrollable_); 343 scrollable_->SetLayoutManager(layout); 344 345 int column_set_id = 0; 346 views::ColumnSet* column_set = layout->AddColumnSet(column_set_id); 347 int left_column_width = 348 (prompt.ShouldShowPermissions() + prompt.GetOAuthIssueCount() + 349 prompt.GetRetainedFileCount()) > 0 ? 350 kPermissionsLeftColumnWidth : kNoPermissionsLeftColumnWidth; 351 if (is_bundle_install()) 352 left_column_width = kBundleLeftColumnWidth; 353 if (is_external_install()) 354 left_column_width = kExternalInstallLeftColumnWidth; 355 356 column_set->AddColumn(views::GridLayout::LEADING, 357 views::GridLayout::FILL, 358 0, // no resizing 359 views::GridLayout::USE_PREF, 360 0, // no fixed width 361 left_column_width); 362 if (!is_bundle_install()) { 363 column_set->AddPaddingColumn(0, views::kPanelHorizMargin); 364 column_set->AddColumn(views::GridLayout::LEADING, 365 views::GridLayout::LEADING, 366 0, // no resizing 367 views::GridLayout::USE_PREF, 368 0, // no fixed width 369 kIconSize); 370 } 371 372 layout->StartRow(0, column_set_id); 373 374 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 375 376 views::Label* heading = new views::Label(prompt.GetHeading()); 377 heading->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont)); 378 heading->SetMultiLine(true); 379 heading->SetHorizontalAlignment(gfx::ALIGN_LEFT); 380 heading->SizeToFit(left_column_width); 381 layout->AddView(heading); 382 383 if (!is_bundle_install()) { 384 // Scale down to icon size, but allow smaller icons (don't scale up). 385 const gfx::ImageSkia* image = prompt.icon().ToImageSkia(); 386 gfx::Size size(image->width(), image->height()); 387 if (size.width() > kIconSize || size.height() > kIconSize) 388 size = gfx::Size(kIconSize, kIconSize); 389 views::ImageView* icon = new views::ImageView(); 390 icon->SetImageSize(size); 391 icon->SetImage(*image); 392 icon->SetHorizontalAlignment(views::ImageView::CENTER); 393 icon->SetVerticalAlignment(views::ImageView::CENTER); 394 int icon_row_span = 1; 395 if (is_inline_install()) { 396 // Also span the rating, user_count and store_link rows. 397 icon_row_span = 4; 398 } else if (prompt.ShouldShowPermissions()) { 399 size_t permission_count = prompt.GetPermissionCount(); 400 if (permission_count > 0) { 401 // Also span the permission header and each of the permission rows (all 402 // have a padding row above it). 403 icon_row_span = 3 + permission_count * 2; 404 } else { 405 // This is the 'no special permissions' case, so span the line we add 406 // (without a header) saying the extension has no special privileges. 407 icon_row_span = 4; 408 } 409 } else if (prompt.GetOAuthIssueCount()) { 410 // Also span the permission header and each of the permission rows (all 411 // have a padding row above it). 412 icon_row_span = 3 + prompt.GetOAuthIssueCount() * 2; 413 } else if (prompt.GetRetainedFileCount()) { 414 // Also span the permission header and the retained files container. 415 icon_row_span = 4; 416 } 417 layout->AddView(icon, 1, icon_row_span); 418 } 419 420 if (is_inline_install()) { 421 layout->StartRow(0, column_set_id); 422 views::View* rating = new views::View(); 423 rating->SetLayoutManager(new views::BoxLayout( 424 views::BoxLayout::kHorizontal, 0, 0, 0)); 425 layout->AddView(rating); 426 prompt.AppendRatingStars(AddResourceIcon, rating); 427 428 views::Label* rating_count = new views::Label(prompt.GetRatingCount()); 429 rating_count->SetFont(rb.GetFont(ui::ResourceBundle::SmallFont)); 430 // Add some space between the stars and the rating count. 431 rating_count->set_border(views::Border::CreateEmptyBorder(0, 2, 0, 0)); 432 rating->AddChildView(rating_count); 433 434 layout->StartRow(0, column_set_id); 435 views::Label* user_count = new views::Label(prompt.GetUserCount()); 436 user_count->SetAutoColorReadabilityEnabled(false); 437 user_count->SetEnabledColor(SK_ColorGRAY); 438 user_count->SetFont(rb.GetFont(ui::ResourceBundle::SmallFont)); 439 layout->AddView(user_count); 440 441 layout->StartRow(0, column_set_id); 442 views::Link* store_link = new views::Link( 443 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_STORE_LINK)); 444 store_link->SetFont(rb.GetFont(ui::ResourceBundle::SmallFont)); 445 store_link->set_listener(this); 446 layout->AddView(store_link); 447 } 448 449 if (is_bundle_install()) { 450 BundleInstaller::ItemList items = prompt.bundle()->GetItemsWithState( 451 BundleInstaller::Item::STATE_PENDING); 452 for (size_t i = 0; i < items.size(); ++i) { 453 string16 extension_name = UTF8ToUTF16(items[i].localized_name); 454 base::i18n::AdjustStringForLocaleDirection(&extension_name); 455 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 456 layout->StartRow(0, column_set_id); 457 views::Label* extension_label = new views::Label( 458 PrepareForDisplay(extension_name, true)); 459 extension_label->SetMultiLine(true); 460 extension_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 461 extension_label->SizeToFit(left_column_width); 462 layout->AddView(extension_label); 463 } 464 } 465 466 if (prompt.ShouldShowPermissions()) { 467 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 468 469 if (prompt.GetPermissionCount() > 0) { 470 if (is_inline_install()) { 471 layout->StartRow(0, column_set_id); 472 layout->AddView(new views::Separator(views::Separator::HORIZONTAL), 473 3, 1, views::GridLayout::FILL, views::GridLayout::FILL); 474 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 475 } 476 477 layout->StartRow(0, column_set_id); 478 views::Label* permissions_header = NULL; 479 if (is_bundle_install()) { 480 // We need to pass the Font in the constructor, rather than calling 481 // SetFont later, because otherwise SizeToFit mis-judges the width 482 // of the line. 483 permissions_header = new views::Label( 484 prompt.GetPermissionsHeading(), 485 rb.GetFont(ui::ResourceBundle::MediumFont)); 486 } else { 487 permissions_header = new views::Label(prompt.GetPermissionsHeading()); 488 } 489 permissions_header->SetMultiLine(true); 490 permissions_header->SetHorizontalAlignment(gfx::ALIGN_LEFT); 491 permissions_header->SizeToFit(left_column_width); 492 layout->AddView(permissions_header); 493 494 for (size_t i = 0; i < prompt.GetPermissionCount(); ++i) { 495 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 496 layout->StartRow(0, column_set_id); 497 views::Label* permission_label = new views::Label(PrepareForDisplay( 498 prompt.GetPermission(i), true)); 499 permission_label->SetMultiLine(true); 500 permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 501 permission_label->SizeToFit(left_column_width); 502 layout->AddView(permission_label); 503 504 // If we have more details to provide, show them in collapsed form. 505 if (!prompt.GetPermissionsDetails(i).empty()) { 506 layout->StartRow(0, column_set_id); 507 PermissionDetails details; 508 details.push_back( 509 PrepareForDisplay(prompt.GetPermissionsDetails(i), false)); 510 ExpandableContainerView* details_container = 511 new ExpandableContainerView( 512 this, string16(), details, left_column_width, false); 513 layout->AddView(details_container); 514 } 515 } 516 } else { 517 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 518 layout->StartRow(0, column_set_id); 519 views::Label* permission_label = new views::Label( 520 l10n_util::GetStringUTF16(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS)); 521 permission_label->SetMultiLine(true); 522 permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 523 permission_label->SizeToFit(left_column_width); 524 layout->AddView(permission_label); 525 } 526 } 527 528 if (prompt.GetOAuthIssueCount()) { 529 // Slide in under the permissions, if there are any. If there are 530 // permissions, the OAuth prompt stretches all the way to the right of the 531 // dialog. If there are no permissions, the OAuth prompt just takes up the 532 // left column. 533 int space_for_oauth = left_column_width; 534 if (prompt.GetPermissionCount()) { 535 space_for_oauth += kIconSize; 536 column_set = layout->AddColumnSet(++column_set_id); 537 column_set->AddColumn(views::GridLayout::FILL, 538 views::GridLayout::FILL, 539 1, 540 views::GridLayout::USE_PREF, 541 0, // no fixed width 542 space_for_oauth); 543 } 544 545 layout->StartRowWithPadding(0, column_set_id, 546 0, views::kRelatedControlVerticalSpacing); 547 views::Label* oauth_header = new views::Label(prompt.GetOAuthHeading()); 548 oauth_header->SetMultiLine(true); 549 oauth_header->SetHorizontalAlignment(gfx::ALIGN_LEFT); 550 oauth_header->SizeToFit(left_column_width); 551 layout->AddView(oauth_header); 552 553 for (size_t i = 0; i < prompt.GetOAuthIssueCount(); ++i) { 554 layout->StartRowWithPadding( 555 0, column_set_id, 556 0, views::kRelatedControlVerticalSpacing); 557 558 PermissionDetails details; 559 const IssueAdviceInfoEntry& entry = prompt.GetOAuthIssue(i); 560 for (size_t x = 0; x < entry.details.size(); ++x) 561 details.push_back(entry.details[x]); 562 ExpandableContainerView* issue_advice_view = 563 new ExpandableContainerView( 564 this, entry.description, details, space_for_oauth, false); 565 layout->AddView(issue_advice_view); 566 } 567 } 568 if (prompt.GetRetainedFileCount()) { 569 // Slide in under the permissions or OAuth, if there are any. If there are 570 // either, the retained files prompt stretches all the way to the right of 571 // the dialog. If there are no permissions or OAuth, the retained files 572 // prompt just takes up the left column. 573 int space_for_files = left_column_width; 574 if (prompt.GetPermissionCount() || prompt.GetOAuthIssueCount()) { 575 space_for_files += kIconSize; 576 column_set = layout->AddColumnSet(++column_set_id); 577 column_set->AddColumn(views::GridLayout::FILL, 578 views::GridLayout::FILL, 579 1, 580 views::GridLayout::USE_PREF, 581 0, // no fixed width 582 space_for_files); 583 } 584 585 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 586 587 layout->StartRow(0, column_set_id); 588 views::Label* retained_files_header = NULL; 589 retained_files_header = 590 new views::Label(prompt.GetRetainedFilesHeadingWithCount()); 591 retained_files_header->SetMultiLine(true); 592 retained_files_header->SetHorizontalAlignment(gfx::ALIGN_LEFT); 593 retained_files_header->SizeToFit(space_for_files); 594 layout->AddView(retained_files_header); 595 596 layout->StartRow(0, column_set_id); 597 PermissionDetails details; 598 for (size_t i = 0; i < prompt.GetRetainedFileCount(); ++i) 599 details.push_back(prompt.GetRetainedFile(i)); 600 ExpandableContainerView* issue_advice_view = 601 new ExpandableContainerView( 602 this, string16(), details, space_for_files, false); 603 layout->AddView(issue_advice_view); 604 } 605 606 gfx::Size scrollable_size = scrollable_->GetPreferredSize(); 607 scrollable_->SetBoundsRect(gfx::Rect(scrollable_size)); 608 dialog_size_ = gfx::Size( 609 kDialogWidth, 610 std::min(scrollable_size.height(), kDialogMaxHeight)); 611 } 612 613 ExtensionInstallDialogView::~ExtensionInstallDialogView() {} 614 615 void ExtensionInstallDialogView::ContentsChanged() { 616 Layout(); 617 } 618 619 int ExtensionInstallDialogView::GetDialogButtons() const { 620 int buttons = prompt_.GetDialogButtons(); 621 // Simply having just an OK button is *not* supported. See comment on function 622 // GetDialogButtons in dialog_delegate.h for reasons. 623 DCHECK_GT(buttons & ui::DIALOG_BUTTON_CANCEL, 0); 624 return buttons; 625 } 626 627 string16 ExtensionInstallDialogView::GetDialogButtonLabel( 628 ui::DialogButton button) const { 629 switch (button) { 630 case ui::DIALOG_BUTTON_OK: 631 return prompt_.GetAcceptButtonLabel(); 632 case ui::DIALOG_BUTTON_CANCEL: 633 return prompt_.HasAbortButtonLabel() ? 634 prompt_.GetAbortButtonLabel() : 635 l10n_util::GetStringUTF16(IDS_CANCEL); 636 default: 637 NOTREACHED(); 638 return string16(); 639 } 640 } 641 642 int ExtensionInstallDialogView::GetDefaultDialogButton() const { 643 return ui::DIALOG_BUTTON_CANCEL; 644 } 645 646 bool ExtensionInstallDialogView::Cancel() { 647 delegate_->InstallUIAbort(true); 648 return true; 649 } 650 651 bool ExtensionInstallDialogView::Accept() { 652 delegate_->InstallUIProceed(); 653 return true; 654 } 655 656 ui::ModalType ExtensionInstallDialogView::GetModalType() const { 657 return ui::MODAL_TYPE_WINDOW; 658 } 659 660 string16 ExtensionInstallDialogView::GetWindowTitle() const { 661 return prompt_.GetDialogTitle(); 662 } 663 664 void ExtensionInstallDialogView::LinkClicked(views::Link* source, 665 int event_flags) { 666 GURL store_url(extension_urls::GetWebstoreItemDetailURLPrefix() + 667 prompt_.extension()->id()); 668 OpenURLParams params( 669 store_url, Referrer(), NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK, 670 false); 671 navigator_->OpenURL(params); 672 GetWidget()->Close(); 673 } 674 675 void ExtensionInstallDialogView::Layout() { 676 views::View* contents_view = scroll_view_->contents(); 677 int content_width = width(); 678 int content_height = contents_view->GetHeightForWidth(content_width); 679 if (content_height > height()) { 680 content_width -= scroll_view_->GetScrollBarWidth(); 681 content_height = contents_view->GetHeightForWidth(content_width); 682 } 683 contents_view->SetBounds(0, 0, content_width, content_height); 684 scroll_view_->SetBounds(0, 0, width(), height()); 685 686 DialogDelegateView::Layout(); 687 } 688 689 gfx::Size ExtensionInstallDialogView::GetPreferredSize() { 690 return dialog_size_; 691 } 692 693 // static 694 ExtensionInstallPrompt::ShowDialogCallback 695 ExtensionInstallPrompt::GetDefaultShowDialogCallback() { 696 return base::Bind(&ShowExtensionInstallDialogImpl); 697 } 698 699 // ExpandableContainerView::DetailsView ---------------------------------------- 700 701 ExpandableContainerView::DetailsView::DetailsView(int horizontal_space, 702 bool show_bullets) 703 : layout_(new views::GridLayout(this)), 704 state_(0), 705 show_bullets_(show_bullets) { 706 SetLayoutManager(layout_); 707 views::ColumnSet* column_set = layout_->AddColumnSet(0); 708 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing); 709 column_set->AddColumn(views::GridLayout::LEADING, 710 views::GridLayout::LEADING, 711 0, 712 views::GridLayout::FIXED, 713 horizontal_space, 714 0); 715 } 716 717 void ExpandableContainerView::DetailsView::AddDetail(const string16& detail) { 718 layout_->StartRowWithPadding(0, 0, 719 0, views::kRelatedControlSmallVerticalSpacing); 720 views::Label* detail_label = 721 new views::Label(PrepareForDisplay(detail, show_bullets_)); 722 detail_label->SetMultiLine(true); 723 detail_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 724 layout_->AddView(detail_label); 725 } 726 727 gfx::Size ExpandableContainerView::DetailsView::GetPreferredSize() { 728 gfx::Size size = views::View::GetPreferredSize(); 729 return gfx::Size(size.width(), size.height() * state_); 730 } 731 732 void ExpandableContainerView::DetailsView::AnimateToState(double state) { 733 state_ = state; 734 PreferredSizeChanged(); 735 SchedulePaint(); 736 } 737 738 // ExpandableContainerView ----------------------------------------------------- 739 740 ExpandableContainerView::ExpandableContainerView( 741 ExtensionInstallDialogView* owner, 742 const string16& description, 743 const PermissionDetails& details, 744 int horizontal_space, 745 bool show_bullets) 746 : owner_(owner), 747 details_view_(NULL), 748 arrow_view_(NULL), 749 slide_animation_(this), 750 expanded_(false) { 751 views::GridLayout* layout = new views::GridLayout(this); 752 SetLayoutManager(layout); 753 int column_set_id = 0; 754 views::ColumnSet* column_set = layout->AddColumnSet(column_set_id); 755 column_set->AddColumn(views::GridLayout::LEADING, 756 views::GridLayout::LEADING, 757 0, 758 views::GridLayout::USE_PREF, 759 0, 760 0); 761 if (!description.empty()) { 762 layout->StartRow(0, column_set_id); 763 764 views::Label* description_label = 765 new views::Label(PrepareForDisplay(description, true)); 766 description_label->SetMultiLine(true); 767 description_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 768 description_label->SizeToFit(horizontal_space); 769 layout->AddView(description_label); 770 } 771 772 if (details.empty()) 773 return; 774 775 details_view_ = new DetailsView(horizontal_space, show_bullets); 776 777 layout->StartRow(0, column_set_id); 778 layout->AddView(details_view_); 779 780 for (size_t i = 0; i < details.size(); ++i) 781 details_view_->AddDetail(details[i]); 782 783 // Prepare the columns for the More Details row. 784 column_set = layout->AddColumnSet(++column_set_id); 785 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing); 786 column_set->AddColumn(views::GridLayout::LEADING, 787 views::GridLayout::LEADING, 788 0, 789 views::GridLayout::USE_PREF, 790 0, 791 0); 792 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing); 793 column_set->AddColumn(views::GridLayout::LEADING, 794 views::GridLayout::LEADING, 795 0, 796 views::GridLayout::USE_PREF, 797 0, 798 0); 799 column_set->AddColumn(views::GridLayout::LEADING, 800 views::GridLayout::LEADING, 801 0, 802 views::GridLayout::USE_PREF, 803 0, 804 0); 805 806 // Add the More Details link. 807 layout->StartRow(0, column_set_id); 808 more_details_ = new views::Link( 809 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS)); 810 more_details_->set_listener(this); 811 more_details_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 812 layout->AddView(more_details_); 813 814 // Add the arrow after the More Details link. 815 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 816 arrow_toggle_ = new views::ImageButton(this); 817 arrow_toggle_->SetImage(views::Button::STATE_NORMAL, 818 rb.GetImageSkiaNamed(IDR_DOWN_ARROW)); 819 layout->AddView(arrow_toggle_); 820 } 821 822 ExpandableContainerView::~ExpandableContainerView() { 823 } 824 825 void ExpandableContainerView::ButtonPressed( 826 views::Button* sender, const ui::Event& event) { 827 ToggleDetailLevel(); 828 } 829 830 void ExpandableContainerView::LinkClicked( 831 views::Link* source, int event_flags) { 832 ToggleDetailLevel(); 833 } 834 835 void ExpandableContainerView::AnimationProgressed( 836 const ui::Animation* animation) { 837 DCHECK_EQ(&slide_animation_, animation); 838 if (details_view_) 839 details_view_->AnimateToState(animation->GetCurrentValue()); 840 } 841 842 void ExpandableContainerView::AnimationEnded(const ui::Animation* animation) { 843 if (animation->GetCurrentValue() != 0.0) { 844 arrow_toggle_->SetImage( 845 views::Button::STATE_NORMAL, 846 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 847 IDR_UP_ARROW)); 848 } else { 849 arrow_toggle_->SetImage( 850 views::Button::STATE_NORMAL, 851 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 852 IDR_DOWN_ARROW)); 853 } 854 855 more_details_->SetText(expanded_ ? 856 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS) : 857 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS)); 858 } 859 860 void ExpandableContainerView::ChildPreferredSizeChanged(views::View* child) { 861 owner_->ContentsChanged(); 862 } 863 864 void ExpandableContainerView::ToggleDetailLevel() { 865 expanded_ = !expanded_; 866 867 if (slide_animation_.IsShowing()) 868 slide_animation_.Hide(); 869 else 870 slide_animation_.Show(); 871 } 872