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