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.
5 #include "chrome/browser/ui/gtk/extensions/extension_installed_bubble_gtk.h"
9 #include "base/i18n/rtl.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/extensions/api/commands/command_service.h"
13 #include "chrome/browser/extensions/extension_action.h"
14 #include "chrome/browser/extensions/extension_action_manager.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/browser_dialogs.h"
17 #include "chrome/browser/ui/gtk/browser_actions_toolbar_gtk.h"
18 #include "chrome/browser/ui/gtk/browser_toolbar_gtk.h"
19 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
20 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
21 #include "chrome/browser/ui/gtk/gtk_util.h"
22 #include "chrome/browser/ui/gtk/location_bar_view_gtk.h"
23 #include "chrome/browser/ui/singleton_tabs.h"
24 #include "chrome/common/extensions/api/omnibox/omnibox_handler.h"
25 #include "chrome/common/url_constants.h"
26 #include "content/public/browser/notification_details.h"
27 #include "content/public/browser/notification_source.h"
28 #include "extensions/common/extension.h"
29 #include "grit/chromium_strings.h"
30 #include "grit/generated_resources.h"
31 #include "grit/theme_resources.h"
32 #include "ui/base/gtk/gtk_hig_constants.h"
33 #include "ui/base/l10n/l10n_util.h"
34 #include "ui/base/resource/resource_bundle.h"
35 #include "ui/gfx/gtk_util.h"
37 using extensions::Extension
;
38 using extensions::ExtensionActionManager
;
42 const int kHorizontalColumnSpacing
= 10;
43 const int kIconPadding
= 3;
44 const int kIconSize
= 43;
45 const int kTextColumnVerticalSpacing
= 7;
46 const int kTextColumnWidth
= 350;
52 void ShowExtensionInstalledBubble(const Extension
* extension
,
54 const SkBitmap
& icon
) {
55 ExtensionInstalledBubbleGtk::Show(extension
, browser
, icon
);
60 void ExtensionInstalledBubbleGtk::Show(const Extension
* extension
,
62 const SkBitmap
& icon
) {
63 new ExtensionInstalledBubbleGtk(extension
, browser
, icon
);
66 ExtensionInstalledBubbleGtk::ExtensionInstalledBubbleGtk(
67 const Extension
* extension
, Browser
*browser
, const SkBitmap
& icon
)
68 : bubble_(this, extension
, browser
, icon
) {
71 ExtensionInstalledBubbleGtk::~ExtensionInstalledBubbleGtk() {}
73 void ExtensionInstalledBubbleGtk::OnDestroy(GtkWidget
* widget
) {
78 bool ExtensionInstalledBubbleGtk::MaybeShowNow() {
79 BrowserWindowGtk
* browser_window
=
80 BrowserWindowGtk::GetBrowserWindowForNativeWindow(
81 bubble_
.browser()->window()->GetNativeWindow());
83 GtkWidget
* reference_widget
= NULL
;
85 if (bubble_
.type() == bubble_
.BROWSER_ACTION
) {
86 BrowserActionsToolbarGtk
* toolbar
=
87 browser_window
->GetToolbar()->GetBrowserActionsToolbar();
88 if (toolbar
->animating())
91 reference_widget
= toolbar
->GetBrowserActionWidget(bubble_
.extension());
92 // glib delays recalculating layout, but we need reference_widget to know
93 // its coordinates, so we force a check_resize here.
94 gtk_container_check_resize(GTK_CONTAINER(
95 browser_window
->GetToolbar()->widget()));
96 // If the widget is not visible then browser_window could be incognito
97 // with this extension disabled. Try showing it on the chevron.
98 // If that fails, fall back to default position.
99 if (reference_widget
&& !gtk_widget_get_visible(reference_widget
)) {
100 reference_widget
= gtk_widget_get_visible(toolbar
->chevron()) ?
101 toolbar
->chevron() : NULL
;
103 } else if (bubble_
.type() == bubble_
.PAGE_ACTION
) {
104 LocationBarViewGtk
* location_bar_view
=
105 browser_window
->GetToolbar()->GetLocationBarView();
106 ExtensionAction
* page_action
=
107 ExtensionActionManager::Get(bubble_
.browser()->profile())->
108 GetPageAction(*bubble_
.extension());
109 location_bar_view
->SetPreviewEnabledPageAction(page_action
,
110 true); // preview_enabled
111 reference_widget
= location_bar_view
->GetPageActionWidget(page_action
);
112 // glib delays recalculating layout, but we need reference_widget to know
113 // its coordinates, so we force a check_resize here.
114 gtk_container_check_resize(GTK_CONTAINER(
115 browser_window
->GetToolbar()->widget()));
116 DCHECK(reference_widget
);
117 } else if (bubble_
.type() == bubble_
.OMNIBOX_KEYWORD
) {
118 LocationBarViewGtk
* location_bar_view
=
119 browser_window
->GetToolbar()->GetLocationBarView();
120 reference_widget
= location_bar_view
->location_entry_widget();
121 DCHECK(reference_widget
);
125 if (reference_widget
== NULL
)
126 reference_widget
= browser_window
->GetToolbar()->GetAppMenuButton();
128 GtkThemeService
* theme_provider
= GtkThemeService::GetFrom(
129 bubble_
.browser()->profile());
131 // Setup the BubbleGtk content.
132 GtkWidget
* bubble_content
= gtk_hbox_new(FALSE
, kHorizontalColumnSpacing
);
133 gtk_container_set_border_width(GTK_CONTAINER(bubble_content
),
134 ui::kContentAreaBorder
);
136 if (!bubble_
.icon().isNull()) {
137 // Scale icon down to 43x43, but allow smaller icons (don't scale up).
138 GdkPixbuf
* pixbuf
= gfx::GdkPixbufFromSkBitmap(bubble_
.icon());
139 gfx::Size
size(bubble_
.icon().width(), bubble_
.icon().height());
140 if (size
.width() > kIconSize
|| size
.height() > kIconSize
) {
141 if (size
.width() > size
.height()) {
142 size
.set_height(size
.height() * kIconSize
/ size
.width());
143 size
.set_width(kIconSize
);
145 size
.set_width(size
.width() * kIconSize
/ size
.height());
146 size
.set_height(kIconSize
);
149 GdkPixbuf
* old
= pixbuf
;
150 pixbuf
= gdk_pixbuf_scale_simple(pixbuf
, size
.width(), size
.height(),
151 GDK_INTERP_BILINEAR
);
155 // Put Icon in top of the left column.
156 GtkWidget
* icon_column
= gtk_vbox_new(FALSE
, 0);
157 // Use 3 pixel padding to get visual balance with BubbleGtk border on the
159 gtk_box_pack_start(GTK_BOX(bubble_content
), icon_column
, FALSE
, FALSE
,
161 GtkWidget
* image
= gtk_image_new_from_pixbuf(pixbuf
);
162 g_object_unref(pixbuf
);
163 gtk_box_pack_start(GTK_BOX(icon_column
), image
, FALSE
, FALSE
, 0);
166 // Center text column.
167 GtkWidget
* text_column
= gtk_vbox_new(FALSE
, kTextColumnVerticalSpacing
);
168 gtk_box_pack_start(GTK_BOX(bubble_content
), text_column
, FALSE
, FALSE
, 0);
171 GtkWidget
* heading_label
= gtk_label_new(NULL
);
172 base::string16 extension_name
=
173 base::UTF8ToUTF16(bubble_
.extension()->name());
174 base::i18n::AdjustStringForLocaleDirection(&extension_name
);
175 std::string heading_text
= l10n_util::GetStringFUTF8(
176 IDS_EXTENSION_INSTALLED_HEADING
, extension_name
);
177 char* markup
= g_markup_printf_escaped("<span size=\"larger\">%s</span>",
178 heading_text
.c_str());
179 gtk_label_set_markup(GTK_LABEL(heading_label
), markup
);
182 gtk_util::SetLabelWidth(heading_label
, kTextColumnWidth
);
183 gtk_box_pack_start(GTK_BOX(text_column
), heading_label
, FALSE
, FALSE
, 0);
185 bool has_keybinding
= false;
187 // Browser action label.
188 if (bubble_
.type() == bubble_
.BROWSER_ACTION
) {
189 extensions::CommandService
* command_service
=
190 extensions::CommandService::Get(bubble_
.browser()->profile());
191 extensions::Command browser_action_command
;
192 GtkWidget
* info_label
;
193 if (!command_service
->GetBrowserActionCommand(
194 bubble_
.extension()->id(),
195 extensions::CommandService::ACTIVE_ONLY
,
196 &browser_action_command
,
198 info_label
= gtk_label_new(l10n_util::GetStringUTF8(
199 IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO
).c_str());
201 info_label
= gtk_label_new(l10n_util::GetStringFUTF8(
202 IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO_WITH_SHORTCUT
,
203 browser_action_command
.accelerator().GetShortcutText()).c_str());
204 has_keybinding
= true;
206 gtk_util::SetLabelWidth(info_label
, kTextColumnWidth
);
207 gtk_box_pack_start(GTK_BOX(text_column
), info_label
, FALSE
, FALSE
, 0);
210 // Page action label.
211 if (bubble_
.type() == bubble_
.PAGE_ACTION
) {
212 extensions::CommandService
* command_service
=
213 extensions::CommandService::Get(bubble_
.browser()->profile());
214 extensions::Command page_action_command
;
215 GtkWidget
* info_label
;
216 if (!command_service
->GetPageActionCommand(
217 bubble_
.extension()->id(),
218 extensions::CommandService::ACTIVE_ONLY
,
219 &page_action_command
,
221 info_label
= gtk_label_new(l10n_util::GetStringUTF8(
222 IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO
).c_str());
224 info_label
= gtk_label_new(l10n_util::GetStringFUTF8(
225 IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO_WITH_SHORTCUT
,
226 page_action_command
.accelerator().GetShortcutText()).c_str());
227 has_keybinding
= true;
229 gtk_util::SetLabelWidth(info_label
, kTextColumnWidth
);
230 gtk_box_pack_start(GTK_BOX(text_column
), info_label
, FALSE
, FALSE
, 0);
233 // Omnibox keyword label.
234 if (bubble_
.type() == bubble_
.OMNIBOX_KEYWORD
) {
235 GtkWidget
* info_label
= gtk_label_new(l10n_util::GetStringFUTF8(
236 IDS_EXTENSION_INSTALLED_OMNIBOX_KEYWORD_INFO
,
237 base::UTF8ToUTF16(extensions::OmniboxInfo::GetKeyword(
238 bubble_
.extension()))).c_str());
239 gtk_util::SetLabelWidth(info_label
, kTextColumnWidth
);
240 gtk_box_pack_start(GTK_BOX(text_column
), info_label
, FALSE
, FALSE
, 0);
243 if (has_keybinding
) {
244 GtkWidget
* manage_link
= theme_provider
->BuildChromeLinkButton(
245 l10n_util::GetStringUTF8(IDS_EXTENSION_INSTALLED_MANAGE_SHORTCUTS
));
246 GtkWidget
* link_hbox
= gtk_hbox_new(FALSE
, 0);
247 // Stick it in an hbox so it doesn't expand to the whole width.
248 gtk_box_pack_end(GTK_BOX(link_hbox
), manage_link
, FALSE
, FALSE
, 0);
249 gtk_box_pack_start(GTK_BOX(text_column
), link_hbox
, FALSE
, FALSE
, 0);
250 g_signal_connect(manage_link
, "clicked",
251 G_CALLBACK(OnLinkClickedThunk
), this);
254 GtkWidget
* manage_label
= gtk_label_new(
255 l10n_util::GetStringUTF8(IDS_EXTENSION_INSTALLED_MANAGE_INFO
).c_str());
256 gtk_util::SetLabelWidth(manage_label
, kTextColumnWidth
);
257 gtk_box_pack_start(GTK_BOX(text_column
), manage_label
, FALSE
, FALSE
, 0);
260 // Create and pack the close button.
261 GtkWidget
* close_column
= gtk_vbox_new(FALSE
, 0);
262 gtk_box_pack_start(GTK_BOX(bubble_content
), close_column
, FALSE
, FALSE
, 0);
263 close_button_
.reset(CustomDrawButton::CloseButtonBubble(theme_provider
));
264 g_signal_connect(close_button_
->widget(), "clicked",
265 G_CALLBACK(OnButtonClick
), this);
266 gtk_box_pack_start(GTK_BOX(close_column
), close_button_
->widget(),
269 BubbleGtk::FrameStyle frame_style
= BubbleGtk::ANCHOR_TOP_RIGHT
;
271 gfx::Rect bounds
= gtk_util::WidgetBounds(reference_widget
);
272 if (bubble_
.type() == bubble_
.OMNIBOX_KEYWORD
) {
273 // Reverse the arrow for omnibox keywords, since the bubble will be on the
274 // other side of the window. We also clear the width to avoid centering
275 // the popup on the URL bar.
276 frame_style
= BubbleGtk::ANCHOR_TOP_LEFT
;
277 if (base::i18n::IsRTL())
278 bounds
.Offset(bounds
.width(), 0);
282 gtk_bubble_
= BubbleGtk::Show(reference_widget
,
286 BubbleGtk::MATCH_SYSTEM_THEME
|
287 BubbleGtk::POPUP_WINDOW
|
288 BubbleGtk::GRAB_INPUT
,
291 g_signal_connect(bubble_content
, "destroy",
292 G_CALLBACK(&OnDestroyThunk
), this);
294 // gtk_bubble_ is now the owner of |this| and deletes it when the bubble
296 bubble_
.IgnoreBrowserClosing();
301 void ExtensionInstalledBubbleGtk::OnButtonClick(GtkWidget
* button
,
302 ExtensionInstalledBubbleGtk
* bubble
) {
303 if (button
== bubble
->close_button_
->widget()) {
304 bubble
->gtk_bubble_
->Close();
310 void ExtensionInstalledBubbleGtk::OnLinkClicked(GtkWidget
* widget
) {
311 gtk_bubble_
->Close();
313 std::string configure_url
= chrome::kChromeUIExtensionsURL
;
314 configure_url
+= chrome::kExtensionConfigureCommandsSubPage
;
315 chrome::NavigateParams
params(
316 chrome::GetSingletonTabNavigateParams(
317 bubble_
.browser(), GURL(configure_url
.c_str())));
318 chrome::Navigate(¶ms
);
321 void ExtensionInstalledBubbleGtk::BubbleClosing(BubbleGtk
* bubble
,
322 bool closed_by_escape
) {
323 if (bubble_
.extension() && bubble_
.type() == bubble_
.PAGE_ACTION
) {
324 // Turn the page action preview off.
325 BrowserWindowGtk
* browser_window
=
326 BrowserWindowGtk::GetBrowserWindowForNativeWindow(
327 bubble_
.browser()->window()->GetNativeWindow());
328 LocationBarViewGtk
* location_bar_view
=
329 browser_window
->GetToolbar()->GetLocationBarView();
330 location_bar_view
->SetPreviewEnabledPageAction(
331 ExtensionActionManager::Get(bubble_
.browser()->profile())->
332 GetPageAction(*bubble_
.extension()),
333 false); // preview_enabled