[email protected] | c4d6f87 | 2010-04-07 21:00:33 | [diff] [blame] | 1 | // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 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 "chrome/browser/gtk/extension_installed_bubble_gtk.h" |
| 6 | |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 7 | #include "app/l10n_util.h" |
| 8 | #include "app/resource_bundle.h" |
[email protected] | 7cf1b6ce | 2010-03-20 06:37:01 | [diff] [blame] | 9 | #include "base/i18n/rtl.h" |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 10 | #include "base/message_loop.h" |
[email protected] | ce7f62e3 | 2010-08-10 23:43:59 | [diff] [blame] | 11 | #include "base/utf_string_conversions.h" |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 12 | #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] | 16d51df | 2010-03-02 09:16:44 | [diff] [blame] | 17 | #include "chrome/browser/gtk/gtk_util.h" |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 18 | #include "chrome/browser/gtk/location_bar_view_gtk.h" |
| 19 | #include "chrome/common/extensions/extension.h" |
[email protected] | 942690b13 | 2010-05-11 06:42:14 | [diff] [blame] | 20 | #include "chrome/common/extensions/extension_action.h" |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 21 | #include "chrome/common/notification_service.h" |
| 22 | #include "chrome/common/notification_type.h" |
[email protected] | 5c7293a | 2010-03-17 06:40:57 | [diff] [blame] | 23 | #include "gfx/gtk_util.h" |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 24 | #include "grit/generated_resources.h" |
| 25 | #include "grit/theme_resources.h" |
| 26 | |
| 27 | namespace { |
| 28 | |
| 29 | const int kHorizontalColumnSpacing = 10; |
| 30 | const int kIconPadding = 3; |
| 31 | const int kIconSize = 43; |
| 32 | const int kTextColumnVerticalSpacing = 7; |
| 33 | const int kTextColumnWidth = 350; |
| 34 | |
[email protected] | 357f170 | 2010-08-28 00:28:35 | [diff] [blame] | 35 | // 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. |
| 38 | const int kAnimationWaitRetries = 10; |
| 39 | const int kAnimationWaitMS = 50; |
| 40 | |
[email protected] | 6194ed1 | 2010-02-26 21:34:11 | [diff] [blame] | 41 | // Padding between content and edge of info bubble. |
| 42 | const int kContentBorder = 7; |
| 43 | |
| 44 | } // namespace |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 45 | |
[email protected] | c4d6f87 | 2010-04-07 21:00:33 | [diff] [blame] | 46 | void ExtensionInstalledBubbleGtk::Show(Extension* extension, Browser* browser, |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 47 | SkBitmap icon) { |
| 48 | new ExtensionInstalledBubbleGtk(extension, browser, icon); |
| 49 | } |
| 50 | |
| 51 | ExtensionInstalledBubbleGtk::ExtensionInstalledBubbleGtk(Extension *extension, |
| 52 | Browser *browser, |
| 53 | SkBitmap icon) |
| 54 | : extension_(extension), |
| 55 | browser_(browser), |
[email protected] | 357f170 | 2010-08-28 00:28:35 | [diff] [blame] | 56 | icon_(icon), |
| 57 | animation_wait_retries_(kAnimationWaitRetries) { |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 58 | 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] | 8e38341 | 2010-10-19 16:57:03 | [diff] [blame^] | 78 | ExtensionInstalledBubbleGtk::~ExtensionInstalledBubbleGtk() {} |
| 79 | |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 80 | void 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 | |
| 95 | void 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] | 357f170 | 2010-08-28 00:28:35 | [diff] [blame] | 103 | 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] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 115 | // 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] | 1d880243 | 2010-03-16 21:18:04 | [diff] [blame] | 119 | // If the widget is not visible then browser_window could be incognito |
[email protected] | 357f170 | 2010-08-28 00:28:35 | [diff] [blame] | 120 | // 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] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 126 | } 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] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 143 | 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] | 6194ed1 | 2010-02-26 21:34:11 | [diff] [blame] | 148 | gtk_container_set_border_width(GTK_CONTAINER(bubble_content), kContentBorder); |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 149 | |
| 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] | 576d789 | 2010-03-23 02:31:58 | [diff] [blame] | 175 | g_object_unref(pixbuf); |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 176 | 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] | 9d9a0cc3 | 2010-08-08 21:11:12 | [diff] [blame] | 184 | std::string heading_text = l10n_util::GetStringFUTF8( |
| 185 | IDS_EXTENSION_INSTALLED_HEADING, UTF8ToUTF16(extension_->name())); |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 186 | 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] | 9d9a0cc3 | 2010-08-08 21:11:12 | [diff] [blame] | 197 | GtkWidget* info_label = gtk_label_new(l10n_util::GetStringUTF8( |
| 198 | IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO).c_str()); |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 199 | 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] | 0376e36 | 2010-10-04 22:05:04 | [diff] [blame] | 206 | l10n_util::GetStringUTF8(IDS_EXTENSION_INSTALLED_MANAGE_INFO).c_str()); |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 207 | 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] | 7cf1b6ce | 2010-03-20 06:37:01 | [diff] [blame] | 226 | !base::i18n::IsRTL() ? |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 227 | InfoBubbleGtk::ARROW_LOCATION_TOP_RIGHT : |
| 228 | InfoBubbleGtk::ARROW_LOCATION_TOP_LEFT; |
[email protected] | c4d6f87 | 2010-04-07 21:00:33 | [diff] [blame] | 229 | info_bubble_ = InfoBubbleGtk::Show(reference_widget, |
| 230 | NULL, |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 231 | bubble_content, |
| 232 | arrow_location, |
[email protected] | 5ff5ee9 | 2010-03-23 22:20:47 | [diff] [blame] | 233 | true, // match_system_theme |
| 234 | true, // grab_input |
[email protected] | 9bfc63e | 2010-01-12 02:17:02 | [diff] [blame] | 235 | theme_provider, |
| 236 | this); |
| 237 | } |
| 238 | |
| 239 | // static |
| 240 | void 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 |
| 249 | void 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 | |
| 269 | void ExtensionInstalledBubbleGtk::Close() { |
| 270 | Release(); // Balanced in ctor. |
| 271 | info_bubble_ = NULL; |
| 272 | } |