| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 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. | |
| 4 | |
| 5 #include "chrome/browser/gtk/extension_installed_bubble_gtk.h" | |
| 6 | |
| 7 #include <string> | |
| 8 | |
| 9 #include "app/l10n_util.h" | |
| 10 #include "app/resource_bundle.h" | |
| 11 #include "base/i18n/rtl.h" | |
| 12 #include "base/message_loop.h" | |
| 13 #include "base/utf_string_conversions.h" | |
| 14 #include "chrome/browser/gtk/browser_actions_toolbar_gtk.h" | |
| 15 #include "chrome/browser/gtk/browser_toolbar_gtk.h" | |
| 16 #include "chrome/browser/gtk/browser_window_gtk.h" | |
| 17 #include "chrome/browser/gtk/gtk_theme_provider.h" | |
| 18 #include "chrome/browser/gtk/gtk_util.h" | |
| 19 #include "chrome/browser/gtk/location_bar_view_gtk.h" | |
| 20 #include "chrome/browser/ui/browser.h" | |
| 21 #include "chrome/common/extensions/extension.h" | |
| 22 #include "chrome/common/extensions/extension_action.h" | |
| 23 #include "chrome/common/notification_details.h" | |
| 24 #include "chrome/common/notification_source.h" | |
| 25 #include "chrome/common/notification_type.h" | |
| 26 #include "gfx/gtk_util.h" | |
| 27 #include "grit/generated_resources.h" | |
| 28 #include "grit/theme_resources.h" | |
| 29 | |
| 30 namespace { | |
| 31 | |
| 32 const int kHorizontalColumnSpacing = 10; | |
| 33 const int kIconPadding = 3; | |
| 34 const int kIconSize = 43; | |
| 35 const int kTextColumnVerticalSpacing = 7; | |
| 36 const int kTextColumnWidth = 350; | |
| 37 | |
| 38 // When showing the bubble for a new browser action, we may have to wait for | |
| 39 // the toolbar to finish animating to know where the item's final position | |
| 40 // will be. | |
| 41 const int kAnimationWaitRetries = 10; | |
| 42 const int kAnimationWaitMS = 50; | |
| 43 | |
| 44 // Padding between content and edge of info bubble. | |
| 45 const int kContentBorder = 7; | |
| 46 | |
| 47 } // namespace | |
| 48 | |
| 49 void ExtensionInstalledBubbleGtk::Show(const Extension* extension, | |
| 50 Browser* browser, | |
| 51 SkBitmap icon) { | |
| 52 new ExtensionInstalledBubbleGtk(extension, browser, icon); | |
| 53 } | |
| 54 | |
| 55 ExtensionInstalledBubbleGtk::ExtensionInstalledBubbleGtk( | |
| 56 const Extension* extension, Browser *browser, SkBitmap icon) | |
| 57 : extension_(extension), | |
| 58 browser_(browser), | |
| 59 icon_(icon), | |
| 60 animation_wait_retries_(kAnimationWaitRetries) { | |
| 61 AddRef(); // Balanced in Close(). | |
| 62 | |
| 63 if (!extension_->omnibox_keyword().empty()) { | |
| 64 type_ = OMNIBOX_KEYWORD; | |
| 65 } else if (extension_->browser_action()) { | |
| 66 type_ = BROWSER_ACTION; | |
| 67 } else if (extension->page_action() && | |
| 68 !extension->page_action()->default_icon_path().empty()) { | |
| 69 type_ = PAGE_ACTION; | |
| 70 } else { | |
| 71 type_ = GENERIC; | |
| 72 } | |
| 73 | |
| 74 // |extension| has been initialized but not loaded at this point. We need | |
| 75 // to wait on showing the Bubble until not only the EXTENSION_LOADED gets | |
| 76 // fired, but all of the EXTENSION_LOADED Observers have run. Only then can we | |
| 77 // be sure that a browser action or page action has had views created which we | |
| 78 // can inspect for the purpose of pointing to them. | |
| 79 registrar_.Add(this, NotificationType::EXTENSION_LOADED, | |
| 80 Source<Profile>(browser->profile())); | |
| 81 registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, | |
| 82 Source<Profile>(browser->profile())); | |
| 83 } | |
| 84 | |
| 85 ExtensionInstalledBubbleGtk::~ExtensionInstalledBubbleGtk() {} | |
| 86 | |
| 87 void ExtensionInstalledBubbleGtk::Observe(NotificationType type, | |
| 88 const NotificationSource& source, | |
| 89 const NotificationDetails& details) { | |
| 90 if (type == NotificationType::EXTENSION_LOADED) { | |
| 91 const Extension* extension = Details<const Extension>(details).ptr(); | |
| 92 if (extension == extension_) { | |
| 93 // PostTask to ourself to allow all EXTENSION_LOADED Observers to run. | |
| 94 MessageLoopForUI::current()->PostTask(FROM_HERE, NewRunnableMethod(this, | |
| 95 &ExtensionInstalledBubbleGtk::ShowInternal)); | |
| 96 } | |
| 97 } else if (type == NotificationType::EXTENSION_UNLOADED) { | |
| 98 const Extension* extension = | |
| 99 Details<UnloadedExtensionInfo>(details)->extension; | |
| 100 if (extension == extension_) | |
| 101 extension_ = NULL; | |
| 102 } else { | |
| 103 NOTREACHED() << L"Received unexpected notification"; | |
| 104 } | |
| 105 } | |
| 106 | |
| 107 void ExtensionInstalledBubbleGtk::ShowInternal() { | |
| 108 BrowserWindowGtk* browser_window = | |
| 109 BrowserWindowGtk::GetBrowserWindowForNativeWindow( | |
| 110 browser_->window()->GetNativeHandle()); | |
| 111 | |
| 112 GtkWidget* reference_widget = NULL; | |
| 113 | |
| 114 if (type_ == BROWSER_ACTION) { | |
| 115 BrowserActionsToolbarGtk* toolbar = | |
| 116 browser_window->GetToolbar()->GetBrowserActionsToolbar(); | |
| 117 | |
| 118 if (toolbar->animating() && animation_wait_retries_-- > 0) { | |
| 119 MessageLoopForUI::current()->PostDelayedTask( | |
| 120 FROM_HERE, | |
| 121 NewRunnableMethod(this, &ExtensionInstalledBubbleGtk::ShowInternal), | |
| 122 kAnimationWaitMS); | |
| 123 return; | |
| 124 } | |
| 125 | |
| 126 reference_widget = toolbar->GetBrowserActionWidget(extension_); | |
| 127 // glib delays recalculating layout, but we need reference_widget to know | |
| 128 // its coordinates, so we force a check_resize here. | |
| 129 gtk_container_check_resize(GTK_CONTAINER( | |
| 130 browser_window->GetToolbar()->widget())); | |
| 131 // If the widget is not visible then browser_window could be incognito | |
| 132 // with this extension disabled. Try showing it on the chevron. | |
| 133 // If that fails, fall back to default position. | |
| 134 if (reference_widget && !GTK_WIDGET_VISIBLE(reference_widget)) { | |
| 135 reference_widget = GTK_WIDGET_VISIBLE(toolbar->chevron()) ? | |
| 136 toolbar->chevron() : NULL; | |
| 137 } | |
| 138 } else if (type_ == PAGE_ACTION) { | |
| 139 LocationBarViewGtk* location_bar_view = | |
| 140 browser_window->GetToolbar()->GetLocationBarView(); | |
| 141 location_bar_view->SetPreviewEnabledPageAction(extension_->page_action(), | |
| 142 true); // preview_enabled | |
| 143 reference_widget = location_bar_view->GetPageActionWidget( | |
| 144 extension_->page_action()); | |
| 145 // glib delays recalculating layout, but we need reference_widget to know | |
| 146 // it's coordinates, so we force a check_resize here. | |
| 147 gtk_container_check_resize(GTK_CONTAINER( | |
| 148 browser_window->GetToolbar()->widget())); | |
| 149 DCHECK(reference_widget); | |
| 150 } else if (type_ == OMNIBOX_KEYWORD) { | |
| 151 LocationBarViewGtk* location_bar_view = | |
| 152 browser_window->GetToolbar()->GetLocationBarView(); | |
| 153 reference_widget = location_bar_view->location_entry_widget(); | |
| 154 DCHECK(reference_widget); | |
| 155 } | |
| 156 | |
| 157 // Default case. | |
| 158 if (reference_widget == NULL) | |
| 159 reference_widget = browser_window->GetToolbar()->GetAppMenuButton(); | |
| 160 | |
| 161 GtkThemeProvider* theme_provider = GtkThemeProvider::GetFrom( | |
| 162 browser_->profile()); | |
| 163 | |
| 164 // Setup the InfoBubble content. | |
| 165 GtkWidget* bubble_content = gtk_hbox_new(FALSE, kHorizontalColumnSpacing); | |
| 166 gtk_container_set_border_width(GTK_CONTAINER(bubble_content), kContentBorder); | |
| 167 | |
| 168 if (!icon_.isNull()) { | |
| 169 // Scale icon down to 43x43, but allow smaller icons (don't scale up). | |
| 170 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&icon_); | |
| 171 gfx::Size size(icon_.width(), icon_.height()); | |
| 172 if (size.width() > kIconSize || size.height() > kIconSize) { | |
| 173 if (size.width() > size.height()) { | |
| 174 size.set_height(size.height() * kIconSize / size.width()); | |
| 175 size.set_width(kIconSize); | |
| 176 } else { | |
| 177 size.set_width(size.width() * kIconSize / size.height()); | |
| 178 size.set_height(kIconSize); | |
| 179 } | |
| 180 | |
| 181 GdkPixbuf* old = pixbuf; | |
| 182 pixbuf = gdk_pixbuf_scale_simple(pixbuf, size.width(), size.height(), | |
| 183 GDK_INTERP_BILINEAR); | |
| 184 g_object_unref(old); | |
| 185 } | |
| 186 | |
| 187 // Put Icon in top of the left column. | |
| 188 GtkWidget* icon_column = gtk_vbox_new(FALSE, 0); | |
| 189 // Use 3 pixel padding to get visual balance with InfoBubble border on the | |
| 190 // left. | |
| 191 gtk_box_pack_start(GTK_BOX(bubble_content), icon_column, FALSE, FALSE, | |
| 192 kIconPadding); | |
| 193 GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf); | |
| 194 g_object_unref(pixbuf); | |
| 195 gtk_box_pack_start(GTK_BOX(icon_column), image, FALSE, FALSE, 0); | |
| 196 } | |
| 197 | |
| 198 // Center text column. | |
| 199 GtkWidget* text_column = gtk_vbox_new(FALSE, kTextColumnVerticalSpacing); | |
| 200 gtk_box_pack_start(GTK_BOX(bubble_content), text_column, FALSE, FALSE, 0); | |
| 201 | |
| 202 // Heading label | |
| 203 GtkWidget* heading_label = gtk_label_new(NULL); | |
| 204 string16 extension_name = UTF8ToUTF16(extension_->name()); | |
| 205 base::i18n::AdjustStringForLocaleDirection(&extension_name); | |
| 206 std::string heading_text = l10n_util::GetStringFUTF8( | |
| 207 IDS_EXTENSION_INSTALLED_HEADING, extension_name); | |
| 208 char* markup = g_markup_printf_escaped("<span size=\"larger\">%s</span>", | |
| 209 heading_text.c_str()); | |
| 210 gtk_label_set_markup(GTK_LABEL(heading_label), markup); | |
| 211 g_free(markup); | |
| 212 | |
| 213 gtk_label_set_line_wrap(GTK_LABEL(heading_label), TRUE); | |
| 214 gtk_widget_set_size_request(heading_label, kTextColumnWidth, -1); | |
| 215 gtk_box_pack_start(GTK_BOX(text_column), heading_label, FALSE, FALSE, 0); | |
| 216 | |
| 217 // Page action label | |
| 218 if (type_ == PAGE_ACTION) { | |
| 219 GtkWidget* info_label = gtk_label_new(l10n_util::GetStringUTF8( | |
| 220 IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO).c_str()); | |
| 221 gtk_label_set_line_wrap(GTK_LABEL(info_label), TRUE); | |
| 222 gtk_widget_set_size_request(info_label, kTextColumnWidth, -1); | |
| 223 gtk_box_pack_start(GTK_BOX(text_column), info_label, FALSE, FALSE, 0); | |
| 224 } | |
| 225 | |
| 226 // Omnibox keyword label | |
| 227 if (type_ == OMNIBOX_KEYWORD) { | |
| 228 GtkWidget* info_label = gtk_label_new(l10n_util::GetStringFUTF8( | |
| 229 IDS_EXTENSION_INSTALLED_OMNIBOX_KEYWORD_INFO, | |
| 230 UTF8ToUTF16(extension_->omnibox_keyword())).c_str()); | |
| 231 gtk_label_set_line_wrap(GTK_LABEL(info_label), TRUE); | |
| 232 gtk_widget_set_size_request(info_label, kTextColumnWidth, -1); | |
| 233 gtk_box_pack_start(GTK_BOX(text_column), info_label, FALSE, FALSE, 0); | |
| 234 } | |
| 235 | |
| 236 // Manage label | |
| 237 GtkWidget* manage_label = gtk_label_new( | |
| 238 l10n_util::GetStringUTF8(IDS_EXTENSION_INSTALLED_MANAGE_INFO).c_str()); | |
| 239 gtk_label_set_line_wrap(GTK_LABEL(manage_label), TRUE); | |
| 240 gtk_widget_set_size_request(manage_label, kTextColumnWidth, -1); | |
| 241 gtk_box_pack_start(GTK_BOX(text_column), manage_label, FALSE, FALSE, 0); | |
| 242 | |
| 243 // Create and pack the close button. | |
| 244 GtkWidget* close_column = gtk_vbox_new(FALSE, 0); | |
| 245 gtk_box_pack_start(GTK_BOX(bubble_content), close_column, FALSE, FALSE, 0); | |
| 246 close_button_.reset(CustomDrawButton::CloseButton(theme_provider)); | |
| 247 g_signal_connect(close_button_->widget(), "clicked", | |
| 248 G_CALLBACK(OnButtonClick), this); | |
| 249 gtk_box_pack_start(GTK_BOX(close_column), close_button_->widget(), | |
| 250 FALSE, FALSE, 0); | |
| 251 | |
| 252 InfoBubbleGtk::ArrowLocationGtk arrow_location = | |
| 253 !base::i18n::IsRTL() ? | |
| 254 InfoBubbleGtk::ARROW_LOCATION_TOP_RIGHT : | |
| 255 InfoBubbleGtk::ARROW_LOCATION_TOP_LEFT; | |
| 256 | |
| 257 gfx::Rect bounds = gtk_util::WidgetBounds(reference_widget); | |
| 258 if (type_ == OMNIBOX_KEYWORD) { | |
| 259 // Reverse the arrow for omnibox keywords, since the bubble will be on the | |
| 260 // other side of the window. We also clear the width to avoid centering | |
| 261 // the popup on the URL bar. | |
| 262 arrow_location = | |
| 263 !base::i18n::IsRTL() ? | |
| 264 InfoBubbleGtk::ARROW_LOCATION_TOP_LEFT : | |
| 265 InfoBubbleGtk::ARROW_LOCATION_TOP_RIGHT; | |
| 266 if (base::i18n::IsRTL()) | |
| 267 bounds.Offset(bounds.width(), 0); | |
| 268 bounds.set_width(0); | |
| 269 } | |
| 270 | |
| 271 info_bubble_ = InfoBubbleGtk::Show(reference_widget, | |
| 272 &bounds, | |
| 273 bubble_content, | |
| 274 arrow_location, | |
| 275 true, // match_system_theme | |
| 276 true, // grab_input | |
| 277 theme_provider, | |
| 278 this); | |
| 279 } | |
| 280 | |
| 281 // static | |
| 282 void ExtensionInstalledBubbleGtk::OnButtonClick(GtkWidget* button, | |
| 283 ExtensionInstalledBubbleGtk* bubble) { | |
| 284 if (button == bubble->close_button_->widget()) { | |
| 285 bubble->info_bubble_->Close(); | |
| 286 } else { | |
| 287 NOTREACHED(); | |
| 288 } | |
| 289 } | |
| 290 // InfoBubbleDelegate | |
| 291 void ExtensionInstalledBubbleGtk::InfoBubbleClosing(InfoBubbleGtk* info_bubble, | |
| 292 bool closed_by_escape) { | |
| 293 if (extension_ && type_ == PAGE_ACTION) { | |
| 294 // Turn the page action preview off. | |
| 295 BrowserWindowGtk* browser_window = | |
| 296 BrowserWindowGtk::GetBrowserWindowForNativeWindow( | |
| 297 browser_->window()->GetNativeHandle()); | |
| 298 LocationBarViewGtk* location_bar_view = | |
| 299 browser_window->GetToolbar()->GetLocationBarView(); | |
| 300 location_bar_view->SetPreviewEnabledPageAction(extension_->page_action(), | |
| 301 false); // preview_enabled | |
| 302 } | |
| 303 | |
| 304 // We need to allow the info bubble to close and remove the widgets from | |
| 305 // the window before we call Release() because close_button_ depends | |
| 306 // on all references being cleared before it is destroyed. | |
| 307 MessageLoopForUI::current()->PostTask(FROM_HERE, NewRunnableMethod(this, | |
| 308 &ExtensionInstalledBubbleGtk::Close)); | |
| 309 } | |
| 310 | |
| 311 void ExtensionInstalledBubbleGtk::Close() { | |
| 312 Release(); // Balanced in ctor. | |
| 313 info_bubble_ = NULL; | |
| 314 } | |
| OLD | NEW |