blob: c3ad8d6edf29e11488e59e2a1c66758bc5aa04bc [file] [log] [blame]
[email protected]c4d6f872010-04-07 21:00:331// Copyright (c) 2010 The Chromium Authors. All rights reserved.
[email protected]9bfc63e2010-01-12 02:17:022// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/gtk/extension_installed_bubble_gtk.h"
6
[email protected]9bfc63e2010-01-12 02:17:027#include "app/l10n_util.h"
8#include "app/resource_bundle.h"
[email protected]7cf1b6ce2010-03-20 06:37:019#include "base/i18n/rtl.h"
[email protected]9bfc63e2010-01-12 02:17:0210#include "base/message_loop.h"
[email protected]ce7f62e32010-08-10 23:43:5911#include "base/utf_string_conversions.h"
[email protected]9bfc63e2010-01-12 02:17:0212#include "chrome/browser/browser.h"
13#include "chrome/browser/gtk/browser_actions_toolbar_gtk.h"
14#include "chrome/browser/gtk/browser_toolbar_gtk.h"
15#include "chrome/browser/gtk/browser_window_gtk.h"
16#include "chrome/browser/gtk/gtk_theme_provider.h"
[email protected]16d51df2010-03-02 09:16:4417#include "chrome/browser/gtk/gtk_util.h"
[email protected]9bfc63e2010-01-12 02:17:0218#include "chrome/browser/gtk/location_bar_view_gtk.h"
19#include "chrome/common/extensions/extension.h"
[email protected]942690b132010-05-11 06:42:1420#include "chrome/common/extensions/extension_action.h"
[email protected]9bfc63e2010-01-12 02:17:0221#include "chrome/common/notification_service.h"
22#include "chrome/common/notification_type.h"
[email protected]5c7293a2010-03-17 06:40:5723#include "gfx/gtk_util.h"
[email protected]9bfc63e2010-01-12 02:17:0224#include "grit/generated_resources.h"
25#include "grit/theme_resources.h"
26
27namespace {
28
29const int kHorizontalColumnSpacing = 10;
30const int kIconPadding = 3;
31const int kIconSize = 43;
32const int kTextColumnVerticalSpacing = 7;
33const int kTextColumnWidth = 350;
34
[email protected]357f1702010-08-28 00:28:3535// When showing the bubble for a new browser action, we may have to wait for
36// the toolbar to finish animating to know where the item's final position
37// will be.
38const int kAnimationWaitRetries = 10;
39const int kAnimationWaitMS = 50;
40
[email protected]6194ed12010-02-26 21:34:1141// Padding between content and edge of info bubble.
42const int kContentBorder = 7;
43
44} // namespace
[email protected]9bfc63e2010-01-12 02:17:0245
[email protected]c4d6f872010-04-07 21:00:3346void ExtensionInstalledBubbleGtk::Show(Extension* extension, Browser* browser,
[email protected]9bfc63e2010-01-12 02:17:0247 SkBitmap icon) {
48 new ExtensionInstalledBubbleGtk(extension, browser, icon);
49}
50
51ExtensionInstalledBubbleGtk::ExtensionInstalledBubbleGtk(Extension *extension,
52 Browser *browser,
53 SkBitmap icon)
54 : extension_(extension),
55 browser_(browser),
[email protected]357f1702010-08-28 00:28:3556 icon_(icon),
57 animation_wait_retries_(kAnimationWaitRetries) {
[email protected]9bfc63e2010-01-12 02:17:0258 AddRef(); // Balanced in Close().
59
60 if (extension_->browser_action()) {
61 type_ = BROWSER_ACTION;
62 } else if (extension->page_action() &&
63 !extension->page_action()->default_icon_path().empty()) {
64 type_ = PAGE_ACTION;
65 } else {
66 type_ = GENERIC;
67 }
68
69 // |extension| has been initialized but not loaded at this point. We need
70 // to wait on showing the Bubble until not only the EXTENSION_LOADED gets
71 // fired, but all of the EXTENSION_LOADED Observers have run. Only then can we
72 // be sure that a browser action or page action has had views created which we
73 // can inspect for the purpose of pointing to them.
74 registrar_.Add(this, NotificationType::EXTENSION_LOADED,
75 NotificationService::AllSources());
76}
77
[email protected]8e383412010-10-19 16:57:0378ExtensionInstalledBubbleGtk::~ExtensionInstalledBubbleGtk() {}
79
[email protected]9bfc63e2010-01-12 02:17:0280void ExtensionInstalledBubbleGtk::Observe(NotificationType type,
81 const NotificationSource& source,
82 const NotificationDetails& details) {
83 if (type == NotificationType::EXTENSION_LOADED) {
84 Extension* extension = Details<Extension>(details).ptr();
85 if (extension == extension_) {
86 // PostTask to ourself to allow all EXTENSION_LOADED Observers to run.
87 MessageLoopForUI::current()->PostTask(FROM_HERE, NewRunnableMethod(this,
88 &ExtensionInstalledBubbleGtk::ShowInternal));
89 }
90 } else {
91 NOTREACHED() << L"Received unexpected notification";
92 }
93}
94
95void ExtensionInstalledBubbleGtk::ShowInternal() {
96 BrowserWindowGtk* browser_window =
97 BrowserWindowGtk::GetBrowserWindowForNativeWindow(
98 browser_->window()->GetNativeHandle());
99
100 GtkWidget* reference_widget = NULL;
101
102 if (type_ == BROWSER_ACTION) {
[email protected]357f1702010-08-28 00:28:35103 BrowserActionsToolbarGtk* toolbar =
104 browser_window->GetToolbar()->GetBrowserActionsToolbar();
105
106 if (toolbar->animating() && animation_wait_retries_-- > 0) {
107 MessageLoopForUI::current()->PostDelayedTask(
108 FROM_HERE,
109 NewRunnableMethod(this, &ExtensionInstalledBubbleGtk::ShowInternal),
110 kAnimationWaitMS);
111 return;
112 }
113
114 reference_widget = toolbar->GetBrowserActionWidget(extension_);
[email protected]9bfc63e2010-01-12 02:17:02115 // glib delays recalculating layout, but we need reference_widget to know
116 // its coordinates, so we force a check_resize here.
117 gtk_container_check_resize(GTK_CONTAINER(
118 browser_window->GetToolbar()->widget()));
[email protected]1d8802432010-03-16 21:18:04119 // If the widget is not visible then browser_window could be incognito
[email protected]357f1702010-08-28 00:28:35120 // with this extension disabled. Try showing it on the chevron.
121 // If that fails, fall back to default position.
122 if (reference_widget && !GTK_WIDGET_VISIBLE(reference_widget)) {
123 reference_widget = GTK_WIDGET_VISIBLE(toolbar->chevron()) ?
124 toolbar->chevron() : NULL;
125 }
[email protected]9bfc63e2010-01-12 02:17:02126 } else if (type_ == PAGE_ACTION) {
127 LocationBarViewGtk* location_bar_view =
128 browser_window->GetToolbar()->GetLocationBarView();
129 location_bar_view->SetPreviewEnabledPageAction(extension_->page_action(),
130 true); // preview_enabled
131 reference_widget = location_bar_view->GetPageActionWidget(
132 extension_->page_action());
133 // glib delays recalculating layout, but we need reference_widget to know
134 // it's coordinates, so we force a check_resize here.
135 gtk_container_check_resize(GTK_CONTAINER(
136 browser_window->GetToolbar()->widget()));
137 DCHECK(reference_widget);
138 }
139 // Default case.
140 if (reference_widget == NULL)
141 reference_widget = browser_window->GetToolbar()->GetAppMenuButton();
142
[email protected]9bfc63e2010-01-12 02:17:02143 GtkThemeProvider* theme_provider = GtkThemeProvider::GetFrom(
144 browser_->profile());
145
146 // Setup the InfoBubble content.
147 GtkWidget* bubble_content = gtk_hbox_new(FALSE, kHorizontalColumnSpacing);
[email protected]6194ed12010-02-26 21:34:11148 gtk_container_set_border_width(GTK_CONTAINER(bubble_content), kContentBorder);
[email protected]9bfc63e2010-01-12 02:17:02149
150 // Scale icon down to 43x43, but allow smaller icons (don't scale up).
151 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&icon_);
152 gfx::Size size(icon_.width(), icon_.height());
153 if (size.width() > kIconSize || size.height() > kIconSize) {
154 if (size.width() > size.height()) {
155 size.set_height(size.height() * kIconSize / size.width());
156 size.set_width(kIconSize);
157 } else {
158 size.set_width(size.width() * kIconSize / size.height());
159 size.set_height(kIconSize);
160 }
161
162 GdkPixbuf* old = pixbuf;
163 pixbuf = gdk_pixbuf_scale_simple(pixbuf, size.width(), size.height(),
164 GDK_INTERP_BILINEAR);
165 g_object_unref(old);
166 }
167
168 // Put Icon in top of the left column.
169 GtkWidget* icon_column = gtk_vbox_new(FALSE, 0);
170 // Use 3 pixel padding to get visual balance with InfoBubble border on the
171 // left.
172 gtk_box_pack_start(GTK_BOX(bubble_content), icon_column, FALSE, FALSE,
173 kIconPadding);
174 GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf);
[email protected]576d7892010-03-23 02:31:58175 g_object_unref(pixbuf);
[email protected]9bfc63e2010-01-12 02:17:02176 gtk_box_pack_start(GTK_BOX(icon_column), image, FALSE, FALSE, 0);
177
178 // Center text column.
179 GtkWidget* text_column = gtk_vbox_new(FALSE, kTextColumnVerticalSpacing);
180 gtk_box_pack_start(GTK_BOX(bubble_content), text_column, FALSE, FALSE, 0);
181
182 // Heading label
183 GtkWidget* heading_label = gtk_label_new(NULL);
[email protected]9d9a0cc32010-08-08 21:11:12184 std::string heading_text = l10n_util::GetStringFUTF8(
185 IDS_EXTENSION_INSTALLED_HEADING, UTF8ToUTF16(extension_->name()));
[email protected]9bfc63e2010-01-12 02:17:02186 char* markup = g_markup_printf_escaped("<span size=\"larger\">%s</span>",
187 heading_text.c_str());
188 gtk_label_set_markup(GTK_LABEL(heading_label), markup);
189 g_free(markup);
190
191 gtk_label_set_line_wrap(GTK_LABEL(heading_label), TRUE);
192 gtk_widget_set_size_request(heading_label, kTextColumnWidth, -1);
193 gtk_box_pack_start(GTK_BOX(text_column), heading_label, FALSE, FALSE, 0);
194
195 // Page action label
196 if (type_ == ExtensionInstalledBubbleGtk::PAGE_ACTION) {
[email protected]9d9a0cc32010-08-08 21:11:12197 GtkWidget* info_label = gtk_label_new(l10n_util::GetStringUTF8(
198 IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO).c_str());
[email protected]9bfc63e2010-01-12 02:17:02199 gtk_label_set_line_wrap(GTK_LABEL(info_label), TRUE);
200 gtk_widget_set_size_request(info_label, kTextColumnWidth, -1);
201 gtk_box_pack_start(GTK_BOX(text_column), info_label, FALSE, FALSE, 0);
202 }
203
204 // Manage label
205 GtkWidget* manage_label = gtk_label_new(
[email protected]0376e362010-10-04 22:05:04206 l10n_util::GetStringUTF8(IDS_EXTENSION_INSTALLED_MANAGE_INFO).c_str());
[email protected]9bfc63e2010-01-12 02:17:02207 gtk_label_set_line_wrap(GTK_LABEL(manage_label), TRUE);
208 gtk_widget_set_size_request(manage_label, kTextColumnWidth, -1);
209 gtk_box_pack_start(GTK_BOX(text_column), manage_label, FALSE, FALSE, 0);
210
211 // Create and pack the close button.
212 GtkWidget* close_column = gtk_vbox_new(FALSE, 0);
213 gtk_box_pack_start(GTK_BOX(bubble_content), close_column, FALSE, FALSE, 0);
214 close_button_.reset(CustomDrawButton::CloseButton(theme_provider));
215 g_signal_connect(close_button_->widget(), "clicked",
216 G_CALLBACK(OnButtonClick), this);
217 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
218 close_button_->SetBackground(
219 theme_provider->GetColor(BrowserThemeProvider::COLOR_TAB_TEXT),
220 rb.GetBitmapNamed(IDR_CLOSE_BAR),
221 rb.GetBitmapNamed(IDR_CLOSE_BAR_MASK));
222 gtk_box_pack_start(GTK_BOX(close_column), close_button_->widget(),
223 FALSE, FALSE, 0);
224
225 InfoBubbleGtk::ArrowLocationGtk arrow_location =
[email protected]7cf1b6ce2010-03-20 06:37:01226 !base::i18n::IsRTL() ?
[email protected]9bfc63e2010-01-12 02:17:02227 InfoBubbleGtk::ARROW_LOCATION_TOP_RIGHT :
228 InfoBubbleGtk::ARROW_LOCATION_TOP_LEFT;
[email protected]c4d6f872010-04-07 21:00:33229 info_bubble_ = InfoBubbleGtk::Show(reference_widget,
230 NULL,
[email protected]9bfc63e2010-01-12 02:17:02231 bubble_content,
232 arrow_location,
[email protected]5ff5ee92010-03-23 22:20:47233 true, // match_system_theme
234 true, // grab_input
[email protected]9bfc63e2010-01-12 02:17:02235 theme_provider,
236 this);
237}
238
239// static
240void ExtensionInstalledBubbleGtk::OnButtonClick(GtkWidget* button,
241 ExtensionInstalledBubbleGtk* bubble) {
242 if (button == bubble->close_button_->widget()) {
243 bubble->info_bubble_->Close();
244 } else {
245 NOTREACHED();
246 }
247}
248// InfoBubbleDelegate
249void ExtensionInstalledBubbleGtk::InfoBubbleClosing(InfoBubbleGtk* info_bubble,
250 bool closed_by_escape) {
251 if (extension_->page_action()) {
252 // Turn the page action preview off.
253 BrowserWindowGtk* browser_window =
254 BrowserWindowGtk::GetBrowserWindowForNativeWindow(
255 browser_->window()->GetNativeHandle());
256 LocationBarViewGtk* location_bar_view =
257 browser_window->GetToolbar()->GetLocationBarView();
258 location_bar_view->SetPreviewEnabledPageAction(extension_->page_action(),
259 false); // preview_enabled
260 }
261
262 // We need to allow the info bubble to close and remove the widgets from
263 // the window before we call Release() because close_button_ depends
264 // on all references being cleared before it is destroyed.
265 MessageLoopForUI::current()->PostTask(FROM_HERE, NewRunnableMethod(this,
266 &ExtensionInstalledBubbleGtk::Close));
267}
268
269void ExtensionInstalledBubbleGtk::Close() {
270 Release(); // Balanced in ctor.
271 info_bubble_ = NULL;
272}