blob: aa74d13fdc5fb8c2715dc405d383d5f5be429a01 [file] [log] [blame]
// Copyright (c) 2012 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/ui/gtk/web_intent_picker_gtk.h"
#include <algorithm>
#include "base/i18n/rtl.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/favicon/favicon_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tab_contents/tab_util.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/gtk/browser_toolbar_gtk.h"
#include "chrome/browser/ui/gtk/browser_window_gtk.h"
#include "chrome/browser/ui/gtk/custom_button.h"
#include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
#include "chrome/browser/ui/gtk/gtk_theme_service.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "chrome/browser/ui/gtk/location_bar_view_gtk.h"
#include "chrome/browser/ui/gtk/tab_contents_container_gtk.h"
#include "chrome/browser/ui/gtk/throbber_gtk.h"
#include "chrome/browser/ui/intents/web_intent_picker_controller.h"
#include "chrome/browser/ui/intents/web_intent_picker_delegate.h"
#include "chrome/browser/ui/intents/web_intent_picker_model.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/common/chrome_notification_types.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "googleurl/src/gurl.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/google_chrome_strings.h"
#include "grit/theme_resources.h"
#include "ui/base/gtk/gtk_hig_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/text/text_elider.h"
#include "ui/gfx/gtk_util.h"
#include "ui/gfx/image/image.h"
using content::WebContents;
namespace {
// The width in pixels of the area between the icon on the left and the close
// button on the right.
const int kMainContentWidth = 400;
// The pixel size of the header label when using a non-native theme.
const int kHeaderLabelPixelSize = 15;
// The pixel size of the font of the main content of the dialog.
const int kMainContentPixelSize = 13;
// The maximum width in pixels of a suggested extension's title link.
const int kTitleLinkMaxWidth = 130;
// Indices of the extension row widgets.
enum {
kIconIndex,
kTitleLinkIndex,
kStarsIndex,
kInstallButtonIndex,
};
GtkThemeService *GetThemeService(TabContentsWrapper* wrapper) {
return GtkThemeService::GetFrom(wrapper->profile());
}
// Set the image of |button| to |pixbuf|.
void SetServiceButtonImage(GtkWidget* button, GdkPixbuf* pixbuf) {
gtk_button_set_image(GTK_BUTTON(button), gtk_image_new_from_pixbuf(pixbuf));
gtk_button_set_image_position(GTK_BUTTON(button), GTK_POS_LEFT);
}
void SetWidgetFontSizeCallback(GtkWidget* widget, gpointer data) {
if (GTK_IS_LABEL(widget)) {
int* size = static_cast<int*>(data);
gtk_util::ForceFontSizePixels(widget, *size);
return;
}
if (GTK_IS_CONTAINER(widget))
gtk_container_forall(GTK_CONTAINER(widget), SetWidgetFontSizeCallback,
data);
}
void SetWidgetFontSize(GtkWidget* widget, int size) {
gtk_container_forall(GTK_CONTAINER(widget), SetWidgetFontSizeCallback, &size);
}
// Get the index of the row containing |widget|. Assume the widget is the child
// of an hbox, which is a child of a vbox. The hbox represents a row, and the
// vbox the full table.
size_t GetExtensionWidgetRow(GtkWidget* widget) {
GtkWidget* hbox = gtk_widget_get_parent(widget);
DCHECK(hbox);
GtkWidget* vbox = gtk_widget_get_parent(hbox);
DCHECK(vbox);
GList* hbox_list = gtk_container_get_children(GTK_CONTAINER(vbox));
gint index = g_list_index(hbox_list, hbox);
DCHECK(index != -1);
g_list_free(hbox_list);
return index;
}
// A gtk_container_foreach callback to enable/disable a widget.
void EnableWidgetCallback(GtkWidget* widget, gpointer data) {
gtk_widget_set_sensitive(widget, *static_cast<gboolean*>(data));
}
// Create a new widget displaying |rating| as |kNumStarsPerRating| star images.
// Rating should be in the range [0, kNumStarsPerRating].
GtkWidget* CreateStarsWidget(double rating) {
const int kNumStarsPerRating = 5; // Number of stars in a rating.
const int kStarSpacing = 1; // Spacing between stars in pixels.
GtkWidget* hbox = gtk_hbox_new(FALSE, kStarSpacing);
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
for (int i = 0; i < kNumStarsPerRating; ++i) {
GdkPixbuf* star = rb.GetNativeImageNamed(
WebIntentPicker::GetNthStarImageIdFromCWSRating(rating, i),
ui::ResourceBundle::RTL_ENABLED).ToGdkPixbuf();
gtk_box_pack_start(GTK_BOX(hbox), gtk_image_new_from_pixbuf(star),
FALSE, FALSE, 0);
}
return hbox;
}
} // namespace
// static
WebIntentPicker* WebIntentPicker::Create(TabContentsWrapper* wrapper,
WebIntentPickerDelegate* delegate,
WebIntentPickerModel* model) {
return new WebIntentPickerGtk(wrapper, delegate, model);
}
WebIntentPickerGtk::WebIntentPickerGtk(TabContentsWrapper* wrapper,
WebIntentPickerDelegate* delegate,
WebIntentPickerModel* model)
: wrapper_(wrapper),
delegate_(delegate),
model_(model),
contents_(NULL),
header_label_(NULL),
button_vbox_(NULL),
cws_label_(NULL),
extensions_vbox_(NULL),
window_(NULL) {
DCHECK(delegate_ != NULL);
model_->set_observer(this);
InitContents();
UpdateInstalledServices();
UpdateCWSLabel();
UpdateSuggestedExtensions();
GtkThemeService* theme_service = GetThemeService(wrapper);
registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
content::Source<ThemeService>(theme_service));
theme_service->InitThemesFor(this);
window_ = new ConstrainedWindowGtk(wrapper, this);
}
WebIntentPickerGtk::~WebIntentPickerGtk() {
}
void WebIntentPickerGtk::Close() {
window_->CloseConstrainedWindow();
if (inline_disposition_tab_contents_.get())
inline_disposition_tab_contents_->web_contents()->OnCloseStarted();
}
void WebIntentPickerGtk::SetActionString(const string16& action) {
gtk_label_set_text(GTK_LABEL(header_label_), UTF16ToUTF8(action).c_str());
}
void WebIntentPickerGtk::OnExtensionInstallSuccess(const std::string& id) {
RemoveThrobber();
}
void WebIntentPickerGtk::OnExtensionInstallFailure(const std::string& id) {
// The throbber has an alignment as its parent, so it must be used instead of
// the throbber to find the extension row.
size_t index =
GetExtensionWidgetRow(gtk_widget_get_parent(throbber_->widget()));
GList* vbox_list =
gtk_container_get_children(GTK_CONTAINER(extensions_vbox_));
GtkWidget* hbox = static_cast<GtkWidget*>(g_list_nth_data(vbox_list, index));
RemoveThrobber();
gtk_widget_show_all(hbox);
g_list_free(vbox_list);
SetWidgetsEnabled(true);
}
void WebIntentPickerGtk::OnModelChanged(WebIntentPickerModel* model) {
UpdateInstalledServices();
UpdateCWSLabel();
UpdateSuggestedExtensions();
}
void WebIntentPickerGtk::OnFaviconChanged(WebIntentPickerModel* model,
size_t index) {
UpdateInstalledServices();
}
void WebIntentPickerGtk::OnExtensionIconChanged(WebIntentPickerModel* model,
const string16& extension_id) {
UpdateSuggestedExtensions();
}
void WebIntentPickerGtk::OnInlineDisposition(WebIntentPickerModel* model,
const GURL& url) {
content::WebContents* web_contents = content::WebContents::Create(
wrapper_->profile(),
tab_util::GetSiteInstanceForNewTab(wrapper_->profile(), url),
MSG_ROUTING_NONE, NULL, NULL);
inline_disposition_tab_contents_.reset(new TabContentsWrapper(web_contents));
inline_disposition_delegate_.reset(
new WebIntentInlineDispositionDelegate(this, web_contents,
wrapper_->profile()));
// Must call this immediately after WebContents creation to avoid race
// with load.
delegate_->OnInlineDispositionWebContentsCreated(web_contents);
tab_contents_container_.reset(new TabContentsContainerGtk(NULL));
tab_contents_container_->SetTab(inline_disposition_tab_contents_.get());
inline_disposition_tab_contents_->web_contents()->GetController().LoadURL(
url, content::Referrer(), content::PAGE_TRANSITION_START_PAGE,
std::string());
// Replace the picker contents with the inline disposition.
gtk_util::RemoveAllChildren(contents_);
GtkWidget* service_hbox = gtk_hbox_new(FALSE, ui::kControlSpacing);
// TODO(gbillock): Eventually get the service icon button here.
// Maybe add a title or something too?
close_button_.reset(
CustomDrawButton::CloseButton(GetThemeService(wrapper_)));
g_signal_connect(close_button_->widget(),
"clicked",
G_CALLBACK(OnCloseButtonClickThunk),
this);
gtk_widget_set_can_focus(close_button_->widget(), FALSE);
GtkWidget* close_vbox = gtk_vbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(close_vbox), close_button_->widget(),
FALSE, FALSE, 0);
gtk_box_pack_end(GTK_BOX(service_hbox), close_vbox, FALSE, FALSE, 0);
GtkWidget* vbox = gtk_vbox_new(FALSE, ui::kContentAreaSpacing);
gtk_box_pack_start(GTK_BOX(vbox), service_hbox, TRUE, TRUE, 0);
// The separator between the icon/title/close and the inline renderer.
gtk_box_pack_start(GTK_BOX(vbox), gtk_hseparator_new(), FALSE, TRUE, 0);
gtk_box_pack_end(GTK_BOX(vbox), tab_contents_container_->widget(),
TRUE, TRUE, 0);
gtk_container_add(GTK_CONTAINER(contents_), vbox);
gfx::Size size = GetDefaultInlineDispositionSize(web_contents);
gtk_widget_set_size_request(tab_contents_container_->widget(),
size.width(), size.height());
gtk_widget_show_all(contents_);
}
GtkWidget* WebIntentPickerGtk::GetWidgetRoot() {
return contents_;
}
GtkWidget* WebIntentPickerGtk::GetFocusWidget() {
return contents_;
}
void WebIntentPickerGtk::DeleteDelegate() {
// The delegate is deleted when the contents widget is destroyed. See
// OnDestroy.
delegate_->OnClosing();
}
bool WebIntentPickerGtk::ShouldHaveBorderPadding() const {
return false;
}
void WebIntentPickerGtk::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK_EQ(type, chrome::NOTIFICATION_BROWSER_THEME_CHANGED);
GtkThemeService* theme_service = GetThemeService(wrapper_);
if (theme_service->UsingNativeTheme())
gtk_util::UndoForceFontSize(header_label_);
else
gtk_util::ForceFontSizePixels(header_label_, kHeaderLabelPixelSize);
UpdateInstalledServices();
UpdateSuggestedExtensions();
}
void WebIntentPickerGtk::OnDestroy(GtkWidget* button) {
// Destroy this object when the contents widget is destroyed. It can't be
// "delete this" because this function happens in a callback.
MessageLoop::current()->DeleteSoon(FROM_HERE, this);
model_->set_observer(NULL);
window_ = NULL;
}
void WebIntentPickerGtk::OnCloseButtonClick(GtkWidget* button) {
delegate_->OnPickerClosed();
}
void WebIntentPickerGtk::OnExtensionLinkClick(GtkWidget* link) {
size_t index = GetExtensionWidgetRow(link);
const WebIntentPickerModel::SuggestedExtension& extension =
model_->GetSuggestedExtensionAt(index);
delegate_->OnExtensionLinkClicked(UTF16ToUTF8(extension.id));
}
void WebIntentPickerGtk::OnExtensionInstallButtonClick(GtkWidget* button) {
size_t index = GetExtensionWidgetRow(button);
const WebIntentPickerModel::SuggestedExtension& extension =
model_->GetSuggestedExtensionAt(index);
delegate_->OnExtensionInstallRequested(UTF16ToUTF8(extension.id));
SetWidgetsEnabled(false);
// Re-enable the clicked extension row.
GList* vbox_list =
gtk_container_get_children(GTK_CONTAINER(extensions_vbox_));
GtkWidget* hbox = static_cast<GtkWidget*>(g_list_nth_data(vbox_list, index));
gtk_widget_set_sensitive(hbox, TRUE);
// Hide the install button.
GList* hbox_list = gtk_container_get_children(GTK_CONTAINER(hbox));
GtkWidget* install_button =
static_cast<GtkWidget*>(g_list_nth_data(hbox_list, kInstallButtonIndex));
GtkAllocation allocation;
gtk_widget_get_allocation(install_button, &allocation);
gtk_widget_hide(install_button);
g_list_free(hbox_list);
g_list_free(vbox_list);
// Show the throbber with the same size as the install button.
GtkWidget* throbber = AddThrobberToExtensionAt(index);
gtk_widget_set_size_request(throbber, allocation.width, allocation.height);
gtk_widget_show_all(throbber);
}
void WebIntentPickerGtk::OnMoreSuggestionsLinkClick(GtkWidget* link) {
// TODO(binji): This should link to a CWS search, based on the current
// action/type pair.
delegate_->OnSuggestionsLinkClicked();
}
void WebIntentPickerGtk::OnServiceButtonClick(GtkWidget* button) {
GList* button_list = gtk_container_get_children(GTK_CONTAINER(button_vbox_));
gint index = g_list_index(button_list, button);
DCHECK(index != -1);
g_list_free(button_list);
const WebIntentPickerModel::InstalledService& installed_service =
model_->GetInstalledServiceAt(index);
delegate_->OnServiceChosen(installed_service.url,
installed_service.disposition);
}
void WebIntentPickerGtk::InitContents() {
GtkThemeService* theme_service = GetThemeService(wrapper_);
// Main contents vbox.
contents_ = gtk_vbox_new(FALSE, 0);
gtk_widget_set_size_request(contents_, kMainContentWidth, -1);
// Hbox containing the close button.
GtkWidget* close_hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(contents_), close_hbox, TRUE, TRUE, 0);
close_button_.reset(
CustomDrawButton::CloseButton(GetThemeService(wrapper_)));
g_signal_connect(close_button_->widget(), "clicked",
G_CALLBACK(OnCloseButtonClickThunk), this);
gtk_widget_set_can_focus(close_button_->widget(), FALSE);
gtk_box_pack_end(GTK_BOX(close_hbox), close_button_->widget(),
FALSE, FALSE, 0);
GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
gtk_alignment_set_padding(
GTK_ALIGNMENT(alignment), 0, ui::kContentAreaBorder,
ui::kContentAreaBorder, ui::kContentAreaBorder);
gtk_box_pack_end(GTK_BOX(contents_), alignment, TRUE, TRUE, 0);
GtkWidget* sub_contents = gtk_vbox_new(FALSE, ui::kContentAreaSpacing);
gtk_container_add(GTK_CONTAINER(alignment), sub_contents);
// Hbox containing the header label.
GtkWidget* header_hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(sub_contents), header_hbox, TRUE, TRUE, 0);
// Label text will be set in the call to SetActionString().
header_label_ = theme_service->BuildLabel(std::string(), ui::kGdkBlack);
gtk_util::ForceFontSizePixels(header_label_, kHeaderLabelPixelSize);
gtk_box_pack_start(GTK_BOX(header_hbox), header_label_, TRUE, TRUE, 0);
gtk_misc_set_alignment(GTK_MISC(header_label_), 0, 0);
// Add separation between the installed services list and the app suggestions.
GtkWidget* button_alignment = gtk_alignment_new(0.5, 0, 0, 0);
gtk_alignment_set_padding(GTK_ALIGNMENT(button_alignment), 0,
kMainContentPixelSize * 2, 0, 0);
// Vbox containing all service buttons.
button_vbox_ = gtk_vbox_new(FALSE, ui::kControlSpacing);
gtk_container_add(GTK_CONTAINER(button_alignment), button_vbox_);
gtk_box_pack_start(GTK_BOX(sub_contents), button_alignment, TRUE, TRUE, 0);
// Chrome Web Store label.
cws_label_ = theme_service->BuildLabel(
l10n_util::GetStringUTF8(IDS_INTENT_PICKER_GET_MORE_SERVICES).c_str(),
ui::kGdkBlack);
gtk_box_pack_start(GTK_BOX(sub_contents), cws_label_, TRUE, TRUE, 0);
gtk_misc_set_alignment(GTK_MISC(cws_label_), 0, 0);
gtk_widget_set_no_show_all(cws_label_, TRUE);
// Set the label width to the size of |sub_contents|, which we don't have
// access to yet, by calculating the main content width minus borders.
gtk_util::SetLabelWidth(cws_label_,
kMainContentWidth - 2 * ui::kContentAreaBorder);
gtk_util::ForceFontSizePixels(cws_label_, kMainContentPixelSize);
// Suggested extensions vbox.
extensions_vbox_ = gtk_vbox_new(FALSE, ui::kControlSpacing);
GtkWidget* indent_extensions = gtk_alignment_new(0.0, 0.5, 1.0, 1.0);
gtk_alignment_set_padding(GTK_ALIGNMENT(indent_extensions), 0, 0,
ui::kGroupIndent, ui::kGroupIndent);
gtk_container_add(GTK_CONTAINER(indent_extensions), extensions_vbox_);
gtk_widget_set_no_show_all(indent_extensions, TRUE);
gtk_box_pack_start(GTK_BOX(sub_contents), indent_extensions, TRUE, TRUE, 0);
// CWS 'More Suggestions' link.
GtkWidget* link_alignment = gtk_alignment_new(0, 0.5f, 0, 0);
GtkWidget* more_suggestions_link = theme_service->BuildChromeLinkButton(
l10n_util::GetStringUTF8(IDS_INTENT_PICKER_MORE_SUGGESTIONS).c_str());
gtk_container_add(GTK_CONTAINER(link_alignment), more_suggestions_link);
gtk_chrome_link_button_set_use_gtk_theme(
GTK_CHROME_LINK_BUTTON(more_suggestions_link),
theme_service->UsingNativeTheme());
gtk_util::ForceFontSizePixels(
GTK_CHROME_LINK_BUTTON(more_suggestions_link)->label,
kMainContentPixelSize);
g_signal_connect(more_suggestions_link, "clicked",
G_CALLBACK(OnMoreSuggestionsLinkClickThunk), this);
GtkWidget* indent_link = gtk_util::IndentWidget(link_alignment);
gtk_box_pack_start(GTK_BOX(sub_contents), indent_link, TRUE, TRUE, 0);
// Throbber, which will be added to the hierarchy when necessary.
throbber_.reset(new ThrobberGtk(theme_service));
g_signal_connect(contents_, "destroy", G_CALLBACK(&OnDestroyThunk), this);
}
void WebIntentPickerGtk::UpdateInstalledServices() {
gtk_util::RemoveAllChildren(button_vbox_);
if (model_->GetInstalledServiceCount() == 0) {
gtk_widget_hide(gtk_widget_get_parent(button_vbox_));
return;
}
for (size_t i = 0; i < model_->GetInstalledServiceCount(); ++i) {
const WebIntentPickerModel::InstalledService& installed_service =
model_->GetInstalledServiceAt(i);
GtkWidget* button = gtk_button_new();
gtk_widget_set_tooltip_text(button, installed_service.url.spec().c_str());
gtk_button_set_label(GTK_BUTTON(button),
UTF16ToUTF8(installed_service.title).c_str());
gtk_button_set_alignment(GTK_BUTTON(button), 0, 0);
gtk_container_add(GTK_CONTAINER(button_vbox_), button);
g_signal_connect(button, "clicked", G_CALLBACK(OnServiceButtonClickThunk),
this);
SetServiceButtonImage(button, installed_service.favicon.ToGdkPixbuf());
// Must be called after SetServiceButtonImage as the internal label widget
// is replaced in that call.
SetWidgetFontSize(button, kMainContentPixelSize);
}
gtk_widget_show_all(button_vbox_);
gtk_widget_show(gtk_widget_get_parent(button_vbox_));
}
void WebIntentPickerGtk::UpdateCWSLabel() {
if (model_->GetInstalledServiceCount() == 0) {
gtk_widget_hide(gtk_widget_get_parent(button_vbox_));
gtk_label_set_text(GTK_LABEL(cws_label_), l10n_util::GetStringUTF8(
IDS_INTENT_PICKER_GET_MORE_SERVICES_NONE_INSTALLED).c_str());
} else {
gtk_label_set_text(GTK_LABEL(cws_label_), l10n_util::GetStringUTF8(
IDS_INTENT_PICKER_GET_MORE_SERVICES).c_str());
gtk_widget_show(gtk_widget_get_parent(button_vbox_));
}
if (model_->GetSuggestedExtensionCount() == 0)
gtk_widget_hide(cws_label_);
else
gtk_widget_show(cws_label_);
}
void WebIntentPickerGtk::UpdateSuggestedExtensions() {
GtkThemeService* theme_service = GetThemeService(wrapper_);
gtk_util::RemoveAllChildren(extensions_vbox_);
if (model_->GetSuggestedExtensionCount() == 0) {
gtk_widget_hide(gtk_widget_get_parent(extensions_vbox_));
return;
}
gtk_widget_show(gtk_widget_get_parent(extensions_vbox_));
for (size_t i = 0; i < model_->GetSuggestedExtensionCount(); ++i) {
const WebIntentPickerModel::SuggestedExtension& extension =
model_->GetSuggestedExtensionAt(i);
GtkWidget* hbox = gtk_hbox_new(FALSE, ui::kControlSpacing);
gtk_box_pack_start(GTK_BOX(extensions_vbox_), hbox, FALSE, FALSE, 0);
// Icon.
GtkWidget* icon = gtk_image_new_from_pixbuf(extension.icon.ToGdkPixbuf());
gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 0);
// Title link.
string16 elided_title = ui::ElideText(extension.title, gfx::Font(),
kTitleLinkMaxWidth, ui::ELIDE_AT_END);
GtkWidget* title_link = theme_service->BuildChromeLinkButton(
UTF16ToUTF8(elided_title).c_str());
gtk_chrome_link_button_set_use_gtk_theme(GTK_CHROME_LINK_BUTTON(title_link),
theme_service->UsingNativeTheme());
gtk_util::ForceFontSizePixels(GTK_CHROME_LINK_BUTTON(title_link)->label,
kMainContentPixelSize);
g_signal_connect(title_link, "clicked",
G_CALLBACK(OnExtensionLinkClickThunk), this);
gtk_box_pack_start(GTK_BOX(hbox), title_link, FALSE, FALSE, 0);
// Stars.
GtkWidget* stars = CreateStarsWidget(extension.average_rating);
gtk_box_pack_start(GTK_BOX(hbox), stars, FALSE, FALSE, 0);
// Install button.
GtkWidget* install_button = gtk_button_new();
gtk_button_set_label(
GTK_BUTTON(install_button),
l10n_util::GetStringUTF8(IDS_INTENT_PICKER_INSTALL_EXTENSION).c_str());
GtkWidget* label = gtk_bin_get_child(GTK_BIN(install_button));
gtk_util::ForceFontSizePixels(label, kMainContentPixelSize);
g_signal_connect(install_button, "clicked",
G_CALLBACK(OnExtensionInstallButtonClickThunk), this);
gtk_box_pack_end(GTK_BOX(hbox), install_button, FALSE, FALSE, 0);
}
gtk_widget_show_all(extensions_vbox_);
gtk_widget_show(gtk_widget_get_parent(extensions_vbox_));
}
void WebIntentPickerGtk::SetWidgetsEnabled(bool enabled) {
gboolean data = enabled;
gtk_container_foreach(GTK_CONTAINER(button_vbox_), EnableWidgetCallback,
&data);
gtk_container_foreach(GTK_CONTAINER(extensions_vbox_), EnableWidgetCallback,
&data);
}
GtkWidget* WebIntentPickerGtk::AddThrobberToExtensionAt(size_t index) {
// The throbber should be unparented.
DCHECK(!gtk_widget_get_parent(throbber_->widget()));
GList* vbox_list =
gtk_container_get_children(GTK_CONTAINER(extensions_vbox_));
GtkWidget* hbox = static_cast<GtkWidget*>(g_list_nth_data(vbox_list, index));
GtkWidget* alignment = gtk_alignment_new(0.5, 0.5, 0, 0);
gtk_container_add(GTK_CONTAINER(alignment), throbber_->widget());
gtk_box_pack_end(GTK_BOX(hbox), alignment, FALSE, FALSE, 0);
g_list_free(vbox_list);
throbber_->Start();
return alignment;
}
void WebIntentPickerGtk::RemoveThrobber() {
GtkWidget* alignment = gtk_widget_get_parent(throbber_->widget());
DCHECK(alignment);
gtk_container_remove(GTK_CONTAINER(alignment), throbber_->widget());
gtk_widget_destroy(alignment);
throbber_->Stop();
}