| // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/gtk/extension_installed_bubble_gtk.h" |
| |
| #include "app/gfx/gtk_util.h" |
| #include "app/l10n_util.h" |
| #include "app/resource_bundle.h" |
| #include "base/message_loop.h" |
| #include "chrome/browser/browser.h" |
| #include "chrome/browser/gtk/browser_actions_toolbar_gtk.h" |
| #include "chrome/browser/gtk/browser_toolbar_gtk.h" |
| #include "chrome/browser/gtk/browser_window_gtk.h" |
| #include "chrome/browser/gtk/gtk_theme_provider.h" |
| #include "chrome/browser/gtk/location_bar_view_gtk.h" |
| #include "chrome/common/extensions/extension.h" |
| #include "chrome/common/gtk_util.h" |
| #include "chrome/common/notification_service.h" |
| #include "chrome/common/notification_type.h" |
| #include "grit/generated_resources.h" |
| #include "grit/theme_resources.h" |
| |
| namespace { |
| |
| const int kHorizontalColumnSpacing = 10; |
| const int kIconPadding = 3; |
| const int kIconSize = 43; |
| const int kTextColumnVerticalSpacing = 7; |
| const int kTextColumnWidth = 350; |
| |
| } |
| |
| void ExtensionInstalledBubbleGtk::Show(Extension *extension, Browser *browser, |
| SkBitmap icon) { |
| new ExtensionInstalledBubbleGtk(extension, browser, icon); |
| } |
| |
| ExtensionInstalledBubbleGtk::ExtensionInstalledBubbleGtk(Extension *extension, |
| Browser *browser, |
| SkBitmap icon) |
| : extension_(extension), |
| browser_(browser), |
| icon_(icon) { |
| AddRef(); // Balanced in Close(). |
| |
| if (extension_->browser_action()) { |
| type_ = BROWSER_ACTION; |
| } else if (extension->page_action() && |
| !extension->page_action()->default_icon_path().empty()) { |
| type_ = PAGE_ACTION; |
| } else { |
| type_ = GENERIC; |
| } |
| |
| // |extension| has been initialized but not loaded at this point. We need |
| // to wait on showing the Bubble until not only the EXTENSION_LOADED gets |
| // fired, but all of the EXTENSION_LOADED Observers have run. Only then can we |
| // be sure that a browser action or page action has had views created which we |
| // can inspect for the purpose of pointing to them. |
| registrar_.Add(this, NotificationType::EXTENSION_LOADED, |
| NotificationService::AllSources()); |
| } |
| |
| void ExtensionInstalledBubbleGtk::Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| if (type == NotificationType::EXTENSION_LOADED) { |
| Extension* extension = Details<Extension>(details).ptr(); |
| if (extension == extension_) { |
| // PostTask to ourself to allow all EXTENSION_LOADED Observers to run. |
| MessageLoopForUI::current()->PostTask(FROM_HERE, NewRunnableMethod(this, |
| &ExtensionInstalledBubbleGtk::ShowInternal)); |
| } |
| } else { |
| NOTREACHED() << L"Received unexpected notification"; |
| } |
| } |
| |
| void ExtensionInstalledBubbleGtk::ShowInternal() { |
| BrowserWindowGtk* browser_window = |
| BrowserWindowGtk::GetBrowserWindowForNativeWindow( |
| browser_->window()->GetNativeHandle()); |
| |
| GtkWidget* reference_widget = NULL; |
| |
| if (type_ == BROWSER_ACTION) { |
| reference_widget = browser_window->GetToolbar()->GetBrowserActionsToolbar() |
| ->GetBrowserActionWidget(extension_); |
| // glib delays recalculating layout, but we need reference_widget to know |
| // its coordinates, so we force a check_resize here. |
| gtk_container_check_resize(GTK_CONTAINER( |
| browser_window->GetToolbar()->widget())); |
| DCHECK(reference_widget); |
| } else if (type_ == PAGE_ACTION) { |
| LocationBarViewGtk* location_bar_view = |
| browser_window->GetToolbar()->GetLocationBarView(); |
| location_bar_view->SetPreviewEnabledPageAction(extension_->page_action(), |
| true); // preview_enabled |
| reference_widget = location_bar_view->GetPageActionWidget( |
| extension_->page_action()); |
| // glib delays recalculating layout, but we need reference_widget to know |
| // it's coordinates, so we force a check_resize here. |
| gtk_container_check_resize(GTK_CONTAINER( |
| browser_window->GetToolbar()->widget())); |
| DCHECK(reference_widget); |
| } |
| // Default case. |
| if (reference_widget == NULL) |
| reference_widget = browser_window->GetToolbar()->GetAppMenuButton(); |
| |
| gfx::Rect bounds = gtk_util::GetWidgetRectRelativeToToplevel( |
| reference_widget); |
| GtkThemeProvider* theme_provider = GtkThemeProvider::GetFrom( |
| browser_->profile()); |
| |
| // Setup the InfoBubble content. |
| GtkWidget* bubble_content = gtk_hbox_new(FALSE, kHorizontalColumnSpacing); |
| |
| // Scale icon down to 43x43, but allow smaller icons (don't scale up). |
| GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&icon_); |
| gfx::Size size(icon_.width(), icon_.height()); |
| if (size.width() > kIconSize || size.height() > kIconSize) { |
| if (size.width() > size.height()) { |
| size.set_height(size.height() * kIconSize / size.width()); |
| size.set_width(kIconSize); |
| } else { |
| size.set_width(size.width() * kIconSize / size.height()); |
| size.set_height(kIconSize); |
| } |
| |
| GdkPixbuf* old = pixbuf; |
| pixbuf = gdk_pixbuf_scale_simple(pixbuf, size.width(), size.height(), |
| GDK_INTERP_BILINEAR); |
| g_object_unref(old); |
| } |
| |
| // Put Icon in top of the left column. |
| GtkWidget* icon_column = gtk_vbox_new(FALSE, 0); |
| // Use 3 pixel padding to get visual balance with InfoBubble border on the |
| // left. |
| gtk_box_pack_start(GTK_BOX(bubble_content), icon_column, FALSE, FALSE, |
| kIconPadding); |
| GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf); |
| gtk_box_pack_start(GTK_BOX(icon_column), image, FALSE, FALSE, 0); |
| |
| // Center text column. |
| GtkWidget* text_column = gtk_vbox_new(FALSE, kTextColumnVerticalSpacing); |
| gtk_box_pack_start(GTK_BOX(bubble_content), text_column, FALSE, FALSE, 0); |
| |
| // Heading label |
| GtkWidget* heading_label = gtk_label_new(NULL); |
| std::string heading_text = WideToUTF8(l10n_util::GetStringF( |
| IDS_EXTENSION_INSTALLED_HEADING, |
| UTF8ToWide(extension_->name()))); |
| char* markup = g_markup_printf_escaped("<span size=\"larger\">%s</span>", |
| heading_text.c_str()); |
| gtk_label_set_markup(GTK_LABEL(heading_label), markup); |
| g_free(markup); |
| |
| gtk_label_set_line_wrap(GTK_LABEL(heading_label), TRUE); |
| gtk_widget_set_size_request(heading_label, kTextColumnWidth, -1); |
| gtk_box_pack_start(GTK_BOX(text_column), heading_label, FALSE, FALSE, 0); |
| |
| // Page action label |
| if (type_ == ExtensionInstalledBubbleGtk::PAGE_ACTION) { |
| GtkWidget* info_label = gtk_label_new( |
| WideToUTF8(l10n_util::GetString( |
| IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO)).c_str()); |
| gtk_label_set_line_wrap(GTK_LABEL(info_label), TRUE); |
| gtk_widget_set_size_request(info_label, kTextColumnWidth, -1); |
| gtk_box_pack_start(GTK_BOX(text_column), info_label, FALSE, FALSE, 0); |
| } |
| |
| // Manage label |
| GtkWidget* manage_label = gtk_label_new( |
| WideToUTF8(l10n_util::GetStringF(IDS_EXTENSION_INSTALLED_MANAGE_INFO, |
| UTF8ToWide(extension_->name()))).c_str()); |
| gtk_label_set_line_wrap(GTK_LABEL(manage_label), TRUE); |
| gtk_widget_set_size_request(manage_label, kTextColumnWidth, -1); |
| gtk_box_pack_start(GTK_BOX(text_column), manage_label, FALSE, FALSE, 0); |
| |
| // Create and pack the close button. |
| GtkWidget* close_column = gtk_vbox_new(FALSE, 0); |
| gtk_box_pack_start(GTK_BOX(bubble_content), close_column, FALSE, FALSE, 0); |
| close_button_.reset(CustomDrawButton::CloseButton(theme_provider)); |
| g_signal_connect(close_button_->widget(), "clicked", |
| G_CALLBACK(OnButtonClick), this); |
| ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
| close_button_->SetBackground( |
| theme_provider->GetColor(BrowserThemeProvider::COLOR_TAB_TEXT), |
| rb.GetBitmapNamed(IDR_CLOSE_BAR), |
| rb.GetBitmapNamed(IDR_CLOSE_BAR_MASK)); |
| gtk_box_pack_start(GTK_BOX(close_column), close_button_->widget(), |
| FALSE, FALSE, 0); |
| |
| InfoBubbleGtk::ArrowLocationGtk arrow_location = |
| (l10n_util::GetTextDirection() == l10n_util::LEFT_TO_RIGHT) ? |
| InfoBubbleGtk::ARROW_LOCATION_TOP_RIGHT : |
| InfoBubbleGtk::ARROW_LOCATION_TOP_LEFT; |
| info_bubble_ = InfoBubbleGtk::Show(browser_window->window(), |
| bounds, |
| bubble_content, |
| arrow_location, |
| true, |
| theme_provider, |
| this); |
| } |
| |
| // static |
| void ExtensionInstalledBubbleGtk::OnButtonClick(GtkWidget* button, |
| ExtensionInstalledBubbleGtk* bubble) { |
| if (button == bubble->close_button_->widget()) { |
| bubble->info_bubble_->Close(); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| // InfoBubbleDelegate |
| void ExtensionInstalledBubbleGtk::InfoBubbleClosing(InfoBubbleGtk* info_bubble, |
| bool closed_by_escape) { |
| if (extension_->page_action()) { |
| // Turn the page action preview off. |
| BrowserWindowGtk* browser_window = |
| BrowserWindowGtk::GetBrowserWindowForNativeWindow( |
| browser_->window()->GetNativeHandle()); |
| LocationBarViewGtk* location_bar_view = |
| browser_window->GetToolbar()->GetLocationBarView(); |
| location_bar_view->SetPreviewEnabledPageAction(extension_->page_action(), |
| false); // preview_enabled |
| } |
| |
| // We need to allow the info bubble to close and remove the widgets from |
| // the window before we call Release() because close_button_ depends |
| // on all references being cleared before it is destroyed. |
| MessageLoopForUI::current()->PostTask(FROM_HERE, NewRunnableMethod(this, |
| &ExtensionInstalledBubbleGtk::Close)); |
| } |
| |
| void ExtensionInstalledBubbleGtk::Close() { |
| Release(); // Balanced in ctor. |
| info_bubble_ = NULL; |
| } |