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