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.
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/string_util.h"
12 #include "base/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/common/chrome_switches.h"
17 #include "chrome/common/extensions/extension.h"
18 #include "chrome/installer/util/browser_distribution.h"
19 #include "content/public/browser/page_navigator.h"
20 #include "content/public/browser/web_contents.h"
21 #include "grit/chromium_strings.h"
22 #include "grit/generated_resources.h"
23 #include "grit/google_chrome_strings.h"
24 #include "grit/theme_resources.h"
25 #include "ui/base/animation/animation_delegate.h"
26 #include "ui/base/animation/slide_animation.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "ui/base/resource/resource_bundle.h"
29 #include "ui/gfx/transform.h"
30 #include "ui/views/border.h"
31 #include "ui/views/controls/button/checkbox.h"
32 #include "ui/views/controls/image_view.h"
33 #include "ui/views/controls/label.h"
34 #include "ui/views/controls/link.h"
35 #include "ui/views/controls/link_listener.h"
36 #include "ui/views/controls/separator.h"
37 #include "ui/views/layout/box_layout.h"
38 #include "ui/views/layout/grid_layout.h"
39 #include "ui/views/layout/layout_constants.h"
40 #include "ui/views/view.h"
41 #include "ui/views/widget/widget.h"
42 #include "ui/views/window/dialog_delegate.h"
45 #include "chrome/browser/extensions/app_host_installer_win.h"
46 #include "chrome/installer/launcher_support/chrome_launcher_support.h"
49 using content::OpenURLParams
;
50 using content::Referrer
;
51 using extensions::BundleInstaller
;
55 // Size of extension icon in top left of dialog.
56 const int kIconSize
= 69;
58 // Width of the left column of the dialog when the extension requests
60 const int kPermissionsLeftColumnWidth
= 250;
62 // Width of the left column of the dialog when the extension requests no
64 const int kNoPermissionsLeftColumnWidth
= 200;
66 // Width of the left column for bundle install prompts. There's only one column
67 // in this case, so make it wider than normal.
68 const int kBundleLeftColumnWidth
= 300;
70 // Width of the left column for external install prompts. The text is long in
71 // this case, so make it wider than normal.
72 const int kExternalInstallLeftColumnWidth
= 350;
74 // Heading font size correction.
75 const int kHeadingFontSizeDelta
= 1;
77 const int kRatingFontSizeDelta
= -1;
79 void AddResourceIcon(const gfx::ImageSkia
* skia_image
, void* data
) {
80 views::View
* parent
= static_cast<views::View
*>(data
);
81 views::ImageView
* image_view
= new views::ImageView();
82 image_view
->SetImage(*skia_image
);
83 parent
->AddChildView(image_view
);
86 // Creates a string for displaying |message| to the user. If it has to look
87 // like a entry in a bullet point list, one is added.
88 string16
PrepareForDisplay(const string16
& message
, bool bullet_point
) {
89 return bullet_point
? l10n_util::GetStringFUTF16(
90 IDS_EXTENSION_PERMISSION_LINE
,
94 // Implements the extension installation dialog for TOOLKIT_VIEWS.
95 class ExtensionInstallDialogView
: public views::DialogDelegateView
,
96 public views::LinkListener
{
98 ExtensionInstallDialogView(content::PageNavigator
* navigator
,
99 ExtensionInstallPrompt::Delegate
* delegate
,
100 const ExtensionInstallPrompt::Prompt
& prompt
,
101 bool show_launcher_opt_in
);
102 virtual ~ExtensionInstallDialogView();
104 // Changes the size of the containing widget to match the preferred size
106 void SizeToContents();
109 // views::DialogDelegateView:
110 virtual string16
GetDialogButtonLabel(ui::DialogButton button
) const OVERRIDE
;
111 virtual int GetDefaultDialogButton() const OVERRIDE
;
112 virtual bool Cancel() OVERRIDE
;
113 virtual bool Accept() OVERRIDE
;
115 // views::WidgetDelegate:
116 virtual ui::ModalType
GetModalType() const OVERRIDE
;
117 virtual string16
GetWindowTitle() const OVERRIDE
;
119 // views::LinkListener:
120 virtual void LinkClicked(views::Link
* source
, int event_flags
) OVERRIDE
;
122 bool is_inline_install() const {
123 return prompt_
.type() == ExtensionInstallPrompt::INLINE_INSTALL_PROMPT
;
126 bool is_bundle_install() const {
127 return prompt_
.type() == ExtensionInstallPrompt::BUNDLE_INSTALL_PROMPT
;
130 bool is_external_install() const {
131 return prompt_
.type() == ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT
;
134 content::PageNavigator
* navigator_
;
135 ExtensionInstallPrompt::Delegate
* delegate_
;
136 ExtensionInstallPrompt::Prompt prompt_
;
137 bool show_launcher_opt_in_
;
139 // The lifetime of |app_launcher_opt_in_checkbox_| is managed by the views
141 views::Checkbox
* app_launcher_opt_in_checkbox_
;
143 DISALLOW_COPY_AND_ASSIGN(ExtensionInstallDialogView
);
146 // A view to display a single IssueAdviceInfoEntry.
147 class IssueAdviceView
: public views::View
,
148 public ui::AnimationDelegate
{
150 IssueAdviceView(ExtensionInstallDialogView
* owner
,
151 const IssueAdviceInfoEntry
& issue_advice
,
152 int horizontal_space
);
153 virtual ~IssueAdviceView() {}
155 // Implementation of views::View:
156 virtual bool OnMousePressed(const ui::MouseEvent
& event
) OVERRIDE
;
157 virtual void OnMouseReleased(const ui::MouseEvent
& event
) OVERRIDE
;
158 virtual void ChildPreferredSizeChanged(views::View
* child
) OVERRIDE
;
160 // Implementation of ui::AnimationDelegate:
161 virtual void AnimationProgressed(const ui::Animation
* animation
) OVERRIDE
;
164 // A view which displays all the details of an IssueAdviceInfoEntry.
165 class DetailsView
: public views::View
{
167 explicit DetailsView(int horizontal_space
);
168 virtual ~DetailsView() {}
170 // Implementation of views::View:
171 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
173 void AddDetail(const string16
& detail
);
175 // Animates this to be a height proportional to |state|.
176 void AnimateToState(double state
);
179 views::GridLayout
* layout_
;
182 DISALLOW_COPY_AND_ASSIGN(DetailsView
);
185 // The dialog that owns |this|. It's also an ancestor in the View hierarchy.
186 ExtensionInstallDialogView
* owner_
;
188 // A view for showing |issue_advice.details|.
189 DetailsView
* details_view_
;
191 // The '>' zippy control.
192 views::ImageView
* arrow_view_
;
194 ui::SlideAnimation slide_animation_
;
196 DISALLOW_COPY_AND_ASSIGN(IssueAdviceView
);
199 void DoShowDialog(const ExtensionInstallPrompt::ShowParams
& show_params
,
200 ExtensionInstallPrompt::Delegate
* delegate
,
201 const ExtensionInstallPrompt::Prompt
& prompt
,
202 bool show_launcher_opt_in
) {
203 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
204 views::Widget::CreateWindowWithParent(
205 new ExtensionInstallDialogView(show_params
.navigator
, delegate
, prompt
,
206 show_launcher_opt_in
),
207 show_params
.parent_window
)->Show();
210 // Runs on the FILE thread. Check if the launcher is present and then show
211 // the install dialog with an appropriate |show_launcher_opt_in|.
212 void CheckLauncherAndShowDialog(
213 const ExtensionInstallPrompt::ShowParams
& show_params
,
214 ExtensionInstallPrompt::Delegate
* delegate
,
215 const ExtensionInstallPrompt::Prompt
& prompt
) {
216 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
218 bool present
= chrome_launcher_support::IsAppLauncherPresent();
221 bool present
= false;
223 content::BrowserThread::PostTask(
224 content::BrowserThread::UI
,
226 base::Bind(&DoShowDialog
, show_params
, delegate
, prompt
, !present
));
229 void ShowExtensionInstallDialogImpl(
230 const ExtensionInstallPrompt::ShowParams
& show_params
,
231 ExtensionInstallPrompt::Delegate
* delegate
,
232 const ExtensionInstallPrompt::Prompt
& prompt
) {
233 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
235 CommandLine
* command_line
= CommandLine::ForCurrentProcess();
236 if (command_line
->HasSwitch(switches::kEnableAppListOptIn
) &&
237 BrowserDistribution::GetDistribution()->AppHostIsSupported() &&
238 prompt
.extension()->is_platform_app()) {
239 content::BrowserThread::PostTask(
240 content::BrowserThread::FILE,
242 base::Bind(&CheckLauncherAndShowDialog
, show_params
, delegate
, prompt
));
246 DoShowDialog(show_params
, delegate
, prompt
, false);
251 ExtensionInstallDialogView::ExtensionInstallDialogView(
252 content::PageNavigator
* navigator
,
253 ExtensionInstallPrompt::Delegate
* delegate
,
254 const ExtensionInstallPrompt::Prompt
& prompt
,
255 bool show_launcher_opt_in
)
256 : navigator_(navigator
),
259 show_launcher_opt_in_(show_launcher_opt_in
),
260 app_launcher_opt_in_checkbox_(NULL
) {
261 // Possible grid layouts:
263 // w/ permissions no permissions
264 // +--------------------+------+ +--------------+------+
265 // | heading | icon | | heading | icon |
266 // +--------------------| | +--------------| |
267 // | rating | | | rating | |
268 // +--------------------| | +--------------+ |
269 // | user_count | | | user_count | |
270 // +--------------------| | +--------------| |
271 // | store_link | | | store_link | |
272 // +--------------------+------+ +--------------+------+
274 // +--------------------+------+
275 // | permissions_header | |
276 // +--------------------+------+
278 // +--------------------+------+
280 // +--------------------+------+
283 // w/ permissions XOR oauth issues no permissions
284 // +--------------------+------+ +--------------+------+
285 // | heading | icon | | heading | icon |
286 // +--------------------| | +--------------+------+
287 // | permissions_header | |
288 // +--------------------| |
290 // +--------------------| |
292 // +--------------------+------+
294 // w/ permissions AND oauth issues
295 // +--------------------+------+
296 // | heading | icon |
297 // +--------------------| |
298 // | permissions_header | |
299 // +--------------------| |
301 // +--------------------| |
303 // +--------------------+------+
305 // +---------------------------+
307 // +---------------------------+
309 // +---------------------------+
311 // w/ launcher opt in
312 // +--------------------+------+
313 // | heading | icon |
314 // +--------------------| |
315 // | permissions_header | |
317 // | ......................... |
319 // +--------------------+------+
320 // | launcher opt in |
321 // +---------------------------+
323 views::GridLayout
* layout
= views::GridLayout::CreatePanel(this);
324 SetLayoutManager(layout
);
326 int column_set_id
= 0;
327 views::ColumnSet
* column_set
= layout
->AddColumnSet(column_set_id
);
328 int left_column_width
=
329 prompt
.GetPermissionCount() + prompt
.GetOAuthIssueCount() > 0 ?
330 kPermissionsLeftColumnWidth
: kNoPermissionsLeftColumnWidth
;
331 if (is_bundle_install())
332 left_column_width
= kBundleLeftColumnWidth
;
333 if (is_external_install())
334 left_column_width
= kExternalInstallLeftColumnWidth
;
336 column_set
->AddColumn(views::GridLayout::LEADING
,
337 views::GridLayout::FILL
,
339 views::GridLayout::USE_PREF
,
342 if (!is_bundle_install()) {
343 column_set
->AddPaddingColumn(0, views::kPanelHorizMargin
);
344 column_set
->AddColumn(views::GridLayout::LEADING
,
345 views::GridLayout::LEADING
,
347 views::GridLayout::USE_PREF
,
352 layout
->StartRow(0, column_set_id
);
354 views::Label
* heading
= new views::Label(prompt
.GetHeading());
355 heading
->SetFont(heading
->font().DeriveFont(kHeadingFontSizeDelta
,
357 heading
->SetMultiLine(true);
358 heading
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
359 heading
->SizeToFit(left_column_width
);
360 layout
->AddView(heading
);
362 if (!is_bundle_install()) {
363 // Scale down to icon size, but allow smaller icons (don't scale up).
364 const gfx::ImageSkia
* image
= prompt
.icon().ToImageSkia();
365 gfx::Size
size(image
->width(), image
->height());
366 if (size
.width() > kIconSize
|| size
.height() > kIconSize
)
367 size
= gfx::Size(kIconSize
, kIconSize
);
368 views::ImageView
* icon
= new views::ImageView();
369 icon
->SetImageSize(size
);
370 icon
->SetImage(*image
);
371 icon
->SetHorizontalAlignment(views::ImageView::CENTER
);
372 icon
->SetVerticalAlignment(views::ImageView::CENTER
);
373 int icon_row_span
= 1;
374 if (is_inline_install()) {
375 // Also span the rating, user_count and store_link rows.
377 } else if (prompt
.GetPermissionCount()) {
378 // Also span the permission header and each of the permission rows (all
379 // have a padding row above it).
380 icon_row_span
= 3 + prompt
.GetPermissionCount() * 2;
381 } else if (prompt
.GetOAuthIssueCount()) {
382 // Also span the permission header and each of the permission rows (all
383 // have a padding row above it).
384 icon_row_span
= 3 + prompt
.GetOAuthIssueCount() * 2;
386 layout
->AddView(icon
, 1, icon_row_span
);
389 if (is_inline_install()) {
390 layout
->StartRow(0, column_set_id
);
391 views::View
* rating
= new views::View();
392 rating
->SetLayoutManager(new views::BoxLayout(
393 views::BoxLayout::kHorizontal
, 0, 0, 0));
394 layout
->AddView(rating
);
395 prompt
.AppendRatingStars(AddResourceIcon
, rating
);
397 views::Label
* rating_count
= new views::Label(prompt
.GetRatingCount());
398 rating_count
->SetFont(
399 rating_count
->font().DeriveFont(kRatingFontSizeDelta
));
400 // Add some space between the stars and the rating count.
401 rating_count
->set_border(views::Border::CreateEmptyBorder(0, 2, 0, 0));
402 rating
->AddChildView(rating_count
);
404 layout
->StartRow(0, column_set_id
);
405 views::Label
* user_count
= new views::Label(prompt
.GetUserCount());
406 user_count
->SetAutoColorReadabilityEnabled(false);
407 user_count
->SetEnabledColor(SK_ColorGRAY
);
408 user_count
->SetFont(user_count
->font().DeriveFont(kRatingFontSizeDelta
));
409 layout
->AddView(user_count
);
411 layout
->StartRow(0, column_set_id
);
412 views::Link
* store_link
= new views::Link(
413 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_STORE_LINK
));
414 store_link
->SetFont(store_link
->font().DeriveFont(kRatingFontSizeDelta
));
415 store_link
->set_listener(this);
416 layout
->AddView(store_link
);
419 if (is_bundle_install()) {
420 BundleInstaller::ItemList items
= prompt_
.bundle()->GetItemsWithState(
421 BundleInstaller::Item::STATE_PENDING
);
422 for (size_t i
= 0; i
< items
.size(); ++i
) {
423 string16 extension_name
= UTF8ToUTF16(items
[i
].localized_name
);
424 base::i18n::AdjustStringForLocaleDirection(&extension_name
);
425 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
426 layout
->StartRow(0, column_set_id
);
427 views::Label
* extension_label
= new views::Label(
428 PrepareForDisplay(extension_name
, true));
429 extension_label
->SetMultiLine(true);
430 extension_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
431 extension_label
->SizeToFit(left_column_width
);
432 layout
->AddView(extension_label
);
436 if (prompt
.GetPermissionCount()) {
437 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
439 if (is_inline_install()) {
440 layout
->StartRow(0, column_set_id
);
441 layout
->AddView(new views::Separator(), 3, 1, views::GridLayout::FILL
,
442 views::GridLayout::FILL
);
443 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
446 layout
->StartRow(0, column_set_id
);
447 views::Label
* permissions_header
= NULL
;
448 if (is_bundle_install()) {
449 // We need to make the font bold like this, rather than using SetFont,
450 // because otherwise SizeToFit mis-judges the width of the line.
451 gfx::Font bold_font
= ui::ResourceBundle::GetSharedInstance().GetFont(
452 ui::ResourceBundle::BaseFont
).DeriveFont(
453 kHeadingFontSizeDelta
, gfx::Font::BOLD
);
454 permissions_header
= new views::Label(
455 prompt
.GetPermissionsHeading(), bold_font
);
457 permissions_header
= new views::Label(prompt
.GetPermissionsHeading());
459 permissions_header
->SetMultiLine(true);
460 permissions_header
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
461 permissions_header
->SizeToFit(left_column_width
);
462 layout
->AddView(permissions_header
);
464 for (size_t i
= 0; i
< prompt
.GetPermissionCount(); ++i
) {
465 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
466 layout
->StartRow(0, column_set_id
);
467 views::Label
* permission_label
= new views::Label(PrepareForDisplay(
468 prompt
.GetPermission(i
), true));
469 permission_label
->SetMultiLine(true);
470 permission_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
471 permission_label
->SizeToFit(left_column_width
);
472 layout
->AddView(permission_label
);
476 if (prompt
.GetOAuthIssueCount()) {
477 // Slide in under the permissions, if there are any. If there are
478 // permissions, the OAuth prompt stretches all the way to the right of the
479 // dialog. If there are no permissions, the OAuth prompt just takes up the
481 int space_for_oauth
= left_column_width
;
482 if (prompt
.GetPermissionCount()) {
483 space_for_oauth
+= kIconSize
;
484 column_set
= layout
->AddColumnSet(++column_set_id
);
485 column_set
->AddColumn(views::GridLayout::FILL
,
486 views::GridLayout::FILL
,
488 views::GridLayout::USE_PREF
,
493 layout
->StartRowWithPadding(0, column_set_id
,
494 0, views::kRelatedControlVerticalSpacing
);
495 views::Label
* oauth_header
= new views::Label(prompt
.GetOAuthHeading());
496 oauth_header
->SetMultiLine(true);
497 oauth_header
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
498 oauth_header
->SizeToFit(left_column_width
);
499 layout
->AddView(oauth_header
);
501 for (size_t i
= 0; i
< prompt
.GetOAuthIssueCount(); ++i
) {
502 layout
->StartRowWithPadding(
504 0, views::kRelatedControlVerticalSpacing
);
506 IssueAdviceView
* issue_advice_view
=
507 new IssueAdviceView(this, prompt
.GetOAuthIssue(i
), space_for_oauth
);
508 layout
->AddView(issue_advice_view
);
512 if (show_launcher_opt_in
) {
513 // Put the launcher opt-in prompt at the bottom. It should always stretch
514 // to take up the whole width of the dialog.
515 column_set
= layout
->AddColumnSet(++column_set_id
);
516 column_set
->AddColumn(views::GridLayout::FILL
,
517 views::GridLayout::FILL
,
519 views::GridLayout::USE_PREF
,
521 left_column_width
+ kIconSize
);
522 layout
->StartRowWithPadding(0, column_set_id
,
523 0, views::kRelatedControlVerticalSpacing
);
524 app_launcher_opt_in_checkbox_
= new views::Checkbox(
525 l10n_util::GetStringUTF16(IDS_APP_LIST_OPT_IN_TEXT
));
526 app_launcher_opt_in_checkbox_
->SetFont(
527 app_launcher_opt_in_checkbox_
->font().DeriveFont(0, gfx::Font::BOLD
));
528 layout
->AddView(app_launcher_opt_in_checkbox_
);
532 ExtensionInstallDialogView::~ExtensionInstallDialogView() {}
534 void ExtensionInstallDialogView::SizeToContents() {
535 GetWidget()->SetSize(GetWidget()->non_client_view()->GetPreferredSize());
538 string16
ExtensionInstallDialogView::GetDialogButtonLabel(
539 ui::DialogButton button
) const {
541 case ui::DIALOG_BUTTON_OK
:
542 return prompt_
.GetAcceptButtonLabel();
543 case ui::DIALOG_BUTTON_CANCEL
:
544 return prompt_
.HasAbortButtonLabel() ?
545 prompt_
.GetAbortButtonLabel() :
546 l10n_util::GetStringUTF16(IDS_CANCEL
);
553 int ExtensionInstallDialogView::GetDefaultDialogButton() const {
554 return ui::DIALOG_BUTTON_CANCEL
;
557 bool ExtensionInstallDialogView::Cancel() {
558 delegate_
->InstallUIAbort(true);
562 bool ExtensionInstallDialogView::Accept() {
564 extensions::AppHostInstaller::SetInstallWithLauncher(
565 app_launcher_opt_in_checkbox_
&&
566 app_launcher_opt_in_checkbox_
->checked());
568 delegate_
->InstallUIProceed();
572 ui::ModalType
ExtensionInstallDialogView::GetModalType() const {
573 return ui::MODAL_TYPE_WINDOW
;
576 string16
ExtensionInstallDialogView::GetWindowTitle() const {
577 return prompt_
.GetDialogTitle();
580 void ExtensionInstallDialogView::LinkClicked(views::Link
* source
,
582 GURL
store_url(extension_urls::GetWebstoreItemDetailURLPrefix() +
583 prompt_
.extension()->id());
584 OpenURLParams
params(
585 store_url
, Referrer(), NEW_FOREGROUND_TAB
, content::PAGE_TRANSITION_LINK
,
587 navigator_
->OpenURL(params
);
588 GetWidget()->Close();
592 ExtensionInstallPrompt::ShowDialogCallback
593 ExtensionInstallPrompt::GetDefaultShowDialogCallback() {
594 return base::Bind(&ShowExtensionInstallDialogImpl
);
597 // IssueAdviceView::DetailsView ------------------------------------------------
599 IssueAdviceView::DetailsView::DetailsView(int horizontal_space
)
600 : layout_(new views::GridLayout(this)),
602 SetLayoutManager(layout_
);
603 views::ColumnSet
* column_set
= layout_
->AddColumnSet(0);
604 column_set
->AddColumn(views::GridLayout::LEADING
,
605 views::GridLayout::LEADING
,
607 views::GridLayout::FIXED
,
612 void IssueAdviceView::DetailsView::AddDetail(const string16
& detail
) {
613 layout_
->StartRowWithPadding(0, 0,
614 0, views::kRelatedControlSmallVerticalSpacing
);
615 views::Label
* detail_label
=
616 new views::Label(PrepareForDisplay(detail
, true));
617 detail_label
->SetMultiLine(true);
618 detail_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
619 layout_
->AddView(detail_label
);
622 gfx::Size
IssueAdviceView::DetailsView::GetPreferredSize() {
623 gfx::Size size
= views::View::GetPreferredSize();
624 return gfx::Size(size
.width(), size
.height() * state_
);
627 void IssueAdviceView::DetailsView::AnimateToState(double state
) {
629 PreferredSizeChanged();
633 // IssueAdviceView -------------------------------------------------------------
635 IssueAdviceView::IssueAdviceView(ExtensionInstallDialogView
* owner
,
636 const IssueAdviceInfoEntry
& issue_advice
,
637 int horizontal_space
)
641 slide_animation_(this) {
642 // TODO(estade): replace this with a more appropriate image.
643 const gfx::ImageSkia
& image
= *ui::ResourceBundle::GetSharedInstance().
644 GetImageSkiaNamed(IDR_OMNIBOX_TTS
);
646 views::GridLayout
* layout
= new views::GridLayout(this);
647 SetLayoutManager(layout
);
648 int column_set_id
= 0;
649 views::ColumnSet
* column_set
= layout
->AddColumnSet(column_set_id
);
650 if (!issue_advice
.details
.empty()) {
651 column_set
->AddColumn(views::GridLayout::LEADING
,
652 views::GridLayout::LEADING
,
654 views::GridLayout::FIXED
,
657 horizontal_space
-= image
.width();
658 details_view_
= new DetailsView(horizontal_space
);
660 column_set
->AddColumn(views::GridLayout::LEADING
,
661 views::GridLayout::FILL
,
663 views::GridLayout::FIXED
,
666 layout
->StartRow(0, column_set_id
);
669 arrow_view_
= new views::ImageView();
670 arrow_view_
->SetImage(image
);
671 arrow_view_
->SetVerticalAlignment(views::ImageView::CENTER
);
672 layout
->AddView(arrow_view_
);
675 views::Label
* description_label
=
676 new views::Label(PrepareForDisplay(issue_advice
.description
,
678 description_label
->SetMultiLine(true);
679 description_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
680 description_label
->SizeToFit(horizontal_space
);
681 layout
->AddView(description_label
);
686 layout
->StartRow(0, column_set_id
);
687 layout
->SkipColumns(1);
688 layout
->AddView(details_view_
);
690 for (size_t i
= 0; i
< issue_advice
.details
.size(); ++i
)
691 details_view_
->AddDetail(issue_advice
.details
[i
]);
694 bool IssueAdviceView::OnMousePressed(const ui::MouseEvent
& event
) {
695 return details_view_
&& event
.IsLeftMouseButton();
698 void IssueAdviceView::OnMouseReleased(const ui::MouseEvent
& event
) {
699 if (slide_animation_
.IsShowing())
700 slide_animation_
.Hide();
702 slide_animation_
.Show();
705 void IssueAdviceView::AnimationProgressed(const ui::Animation
* animation
) {
706 DCHECK_EQ(animation
, &slide_animation_
);
709 details_view_
->AnimateToState(animation
->GetCurrentValue());
712 gfx::Transform rotate
;
713 if (animation
->GetCurrentValue() != 0.0) {
714 rotate
.Translate(arrow_view_
->width() / 2.0,
715 arrow_view_
->height() / 2.0);
716 // TODO(estade): for some reason there are rendering errors at 90 degrees.
718 rotate
.Rotate(animation
->GetCurrentValue() * 89);
719 rotate
.Translate(-arrow_view_
->width() / 2.0,
720 -arrow_view_
->height() / 2.0);
722 arrow_view_
->SetTransform(rotate
);
726 void IssueAdviceView::ChildPreferredSizeChanged(views::View
* child
) {
727 owner_
->SizeToContents();