1 // Copyright 2014 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.
5 #include "chrome/browser/ui/views/extensions/bookmark_app_bubble_view.h"
7 #include "base/numerics/safe_conversions.h"
8 #include "base/strings/string16.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/ui/host_desktop.h"
12 #include "chrome/grit/generated_resources.h"
13 #include "content/public/browser/web_contents.h"
14 #include "extensions/common/constants.h"
15 #include "ui/accessibility/ax_view_state.h"
16 #include "ui/base/l10n/l10n_util.h"
17 #include "ui/base/resource/resource_bundle.h"
18 #include "ui/events/keycodes/keyboard_codes.h"
19 #include "ui/gfx/image/image_skia.h"
20 #include "ui/gfx/image/image_skia_source.h"
21 #include "ui/views/controls/button/checkbox.h"
22 #include "ui/views/controls/button/label_button.h"
23 #include "ui/views/controls/image_view.h"
24 #include "ui/views/controls/label.h"
25 #include "ui/views/controls/textfield/textfield.h"
26 #include "ui/views/layout/grid_layout.h"
27 #include "ui/views/layout/layout_constants.h"
28 #include "ui/views/widget/widget.h"
30 using views::ColumnSet
;
31 using views::GridLayout
;
35 // Minimum width of the the bubble.
36 const int kMinBubbleWidth
= 300;
37 // Minimum width of the the textfield.
38 const int kMinTextfieldWidth
= 200;
40 const int kIconSize
= extension_misc::EXTENSION_ICON_MEDIUM
;
42 class WebAppInfoImageSource
: public gfx::ImageSkiaSource
{
44 WebAppInfoImageSource(int dip_size
, const WebApplicationInfo
& info
)
45 : dip_size_(dip_size
), info_(info
) {}
46 ~WebAppInfoImageSource() override
{}
49 gfx::ImageSkiaRep
GetImageForScale(float scale
) override
{
50 int size
= base::saturated_cast
<int>(dip_size_
* scale
);
51 for (const auto& icon_info
: info_
.icons
) {
52 if (icon_info
.width
== size
) {
53 return gfx::ImageSkiaRep(icon_info
.data
, scale
);
56 return gfx::ImageSkiaRep();
60 WebApplicationInfo info_
;
65 BookmarkAppBubbleView::~BookmarkAppBubbleView() {
69 void BookmarkAppBubbleView::ShowBubble(
70 views::View
* anchor_view
,
71 const WebApplicationInfo
& web_app_info
,
72 const BrowserWindow::ShowBookmarkAppBubbleCallback
& callback
) {
73 // |bookmark_app_bubble| becomes owned by the BubbleDelegateView through the
74 // views system, and is freed when the BubbleDelegateView is closed and
75 // subsequently destroyed.
76 BookmarkAppBubbleView
* bookmark_app_bubble
=
77 new BookmarkAppBubbleView(anchor_view
, web_app_info
, callback
);
78 views::BubbleDelegateView::CreateBubble(bookmark_app_bubble
)->Show();
79 // Select the entire title textfield contents when the bubble is first shown.
80 bookmark_app_bubble
->title_tf_
->SelectAll(true);
81 bookmark_app_bubble
->SetArrowPaintType(views::BubbleBorder::PAINT_NONE
);
84 BookmarkAppBubbleView::BookmarkAppBubbleView(
85 views::View
* anchor_view
,
86 const WebApplicationInfo
& web_app_info
,
87 const BrowserWindow::ShowBookmarkAppBubbleCallback
& callback
)
88 : BubbleDelegateView(anchor_view
, views::BubbleBorder::TOP_RIGHT
),
89 web_app_info_(web_app_info
),
90 user_accepted_(false),
94 open_as_window_checkbox_(NULL
),
96 const SkColor background_color
= GetNativeTheme()->GetSystemColor(
97 ui::NativeTheme::kColorId_DialogBackground
);
98 set_arrow(views::BubbleBorder::TOP_CENTER
);
99 set_color(background_color
);
100 set_background(views::Background::CreateSolidBackground(background_color
));
101 set_margins(gfx::Insets(views::kPanelVertMargin
, 0, 0, 0));
104 void BookmarkAppBubbleView::Init() {
105 views::Label
* title_label
= new views::Label(
106 l10n_util::GetStringUTF16(TitleStringId()));
107 ui::ResourceBundle
* rb
= &ui::ResourceBundle::GetSharedInstance();
108 title_label
->SetFontList(rb
->GetFontList(ui::ResourceBundle::MediumFont
));
109 title_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
112 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_ADD
));
113 add_button_
->SetStyle(views::Button::STYLE_BUTTON
);
114 add_button_
->SetIsDefault(true);
117 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_CANCEL
));
118 cancel_button_
->SetStyle(views::Button::STYLE_BUTTON
);
120 GridLayout
* layout
= new GridLayout(this);
121 SetLayoutManager(layout
);
123 // Column sets used in the layout of the bubble.
126 TITLE_TEXT_COLUMN_SET_ID
,
127 CONTENT_COLUMN_SET_ID
130 // The column layout used for the title and checkbox.
131 ColumnSet
* cs
= layout
->AddColumnSet(TITLE_COLUMN_SET_ID
);
132 cs
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
134 GridLayout::LEADING
, GridLayout::CENTER
, 0, GridLayout::USE_PREF
, 0, 0);
135 cs
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
137 // The column layout used for the icon and text box.
138 cs
= layout
->AddColumnSet(TITLE_TEXT_COLUMN_SET_ID
);
139 cs
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
140 cs
->AddColumn(GridLayout::LEADING
,
143 GridLayout::USE_PREF
,
146 cs
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
147 cs
->AddColumn(GridLayout::FILL
,
150 GridLayout::USE_PREF
,
153 cs
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
155 // The column layout used for the row with buttons.
156 cs
= layout
->AddColumnSet(CONTENT_COLUMN_SET_ID
);
157 cs
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
159 GridLayout::LEADING
, GridLayout::CENTER
, 0, GridLayout::USE_PREF
, 0, 0);
160 cs
->AddPaddingColumn(1, views::kUnrelatedControlHorizontalSpacing
);
162 GridLayout::LEADING
, GridLayout::CENTER
, 0, GridLayout::USE_PREF
, 0, 0);
163 cs
->AddPaddingColumn(0, views::kRelatedButtonHSpacing
);
165 GridLayout::LEADING
, GridLayout::CENTER
, 0, GridLayout::USE_PREF
, 0, 0);
166 cs
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
168 layout
->StartRow(0, TITLE_COLUMN_SET_ID
);
169 layout
->AddView(title_label
);
170 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
172 layout
->StartRow(0, TITLE_TEXT_COLUMN_SET_ID
);
173 icon_image_view_
= new views::ImageView();
175 gfx::Size
image_size(kIconSize
, kIconSize
);
176 gfx::ImageSkia
image(new WebAppInfoImageSource(kIconSize
, web_app_info_
),
178 icon_image_view_
->SetImageSize(image_size
);
179 icon_image_view_
->SetImage(image
);
180 layout
->AddView(icon_image_view_
);
182 title_tf_
= new views::Textfield();
183 title_tf_
->SetText(web_app_info_
.title
);
184 title_tf_
->SetAccessibleName(l10n_util::GetStringUTF16(
185 IDS_BOOKMARK_APP_AX_BUBBLE_NAME_LABEL
));
186 title_tf_
->set_controller(this);
187 layout
->AddView(title_tf_
);
188 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
190 layout
->StartRow(0, CONTENT_COLUMN_SET_ID
);
191 open_as_window_checkbox_
= new views::Checkbox(
192 l10n_util::GetStringUTF16(IDS_BOOKMARK_APP_BUBBLE_OPEN_AS_WINDOW
));
193 open_as_window_checkbox_
->SetChecked(web_app_info_
.open_as_window
);
194 layout
->AddView(open_as_window_checkbox_
);
195 layout
->AddView(add_button_
);
196 layout
->AddView(cancel_button_
);
197 layout
->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing
);
199 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN
, ui::EF_NONE
));
200 UpdateAddButtonState();
203 views::View
* BookmarkAppBubbleView::GetInitiallyFocusedView() {
207 void BookmarkAppBubbleView::WindowClosing() {
208 callback_
.Run(user_accepted_
, web_app_info_
);
211 bool BookmarkAppBubbleView::AcceleratorPressed(
212 const ui::Accelerator
& accelerator
) {
213 if (accelerator
.key_code() == ui::VKEY_RETURN
) {
214 HandleButtonPressed(add_button_
);
217 return BubbleDelegateView::AcceleratorPressed(accelerator
);
220 void BookmarkAppBubbleView::GetAccessibleState(ui::AXViewState
* state
) {
221 views::BubbleDelegateView::GetAccessibleState(state
);
222 state
->name
= l10n_util::GetStringUTF16(TitleStringId());
225 gfx::Size
BookmarkAppBubbleView::GetMinimumSize() const {
226 gfx::Size
size(views::BubbleDelegateView::GetPreferredSize());
227 size
.SetToMax(gfx::Size(kMinBubbleWidth
, 0));
231 void BookmarkAppBubbleView::ButtonPressed(views::Button
* sender
,
232 const ui::Event
& event
) {
233 HandleButtonPressed(sender
);
236 void BookmarkAppBubbleView::ContentsChanged(
237 views::Textfield
* sender
,
238 const base::string16
& new_contents
) {
239 DCHECK_EQ(title_tf_
, sender
);
240 UpdateAddButtonState();
243 void BookmarkAppBubbleView::HandleButtonPressed(views::Button
* sender
) {
244 if (sender
== add_button_
) {
245 user_accepted_
= true;
246 web_app_info_
.title
= GetTrimmedTitle();
247 web_app_info_
.open_as_window
= open_as_window_checkbox_
->checked();
250 GetWidget()->Close();
253 void BookmarkAppBubbleView::UpdateAddButtonState() {
254 add_button_
->SetEnabled(!GetTrimmedTitle().empty());
257 int BookmarkAppBubbleView::TitleStringId() {
259 int string_id
= IDS_ADD_TO_TASKBAR_BUBBLE_TITLE
;
261 int string_id
= IDS_ADD_TO_DESKTOP_BUBBLE_TITLE
;
264 if (chrome::GetHostDesktopTypeForNativeWindow(
265 anchor_widget()->GetNativeWindow()) ==
266 chrome::HOST_DESKTOP_TYPE_ASH
) {
267 string_id
= IDS_ADD_TO_SHELF_BUBBLE_TITLE
;
273 base::string16
BookmarkAppBubbleView::GetTrimmedTitle() {
274 base::string16
title(title_tf_
->text());
275 base::TrimWhitespace(title
, base::TRIM_ALL
, &title
);