| // Copyright (c) 2011 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/infobars/infobar_gtk.h" |
| |
| #include <gtk/gtk.h> |
| |
| #include "base/utf_string_conversions.h" |
| #include "chrome/browser/platform_util.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/infobars/infobar_container_gtk.h" |
| #include "content/common/notification_service.h" |
| #include "ui/gfx/gtk_util.h" |
| |
| extern const int InfoBar::kInfoBarHeight = 37; |
| |
| namespace { |
| |
| // Pixels between infobar elements. |
| const int kElementPadding = 5; |
| |
| // Extra padding on either end of info bar. |
| const int kLeftPadding = 5; |
| const int kRightPadding = 5; |
| |
| } // namespace |
| |
| // static |
| const int InfoBar::kEndOfLabelSpacing = 6; |
| const int InfoBar::kButtonButtonSpacing = 3; |
| |
| InfoBar::InfoBar(InfoBarDelegate* delegate) |
| : container_(NULL), |
| delegate_(delegate), |
| theme_service_(NULL), |
| arrow_model_(this) { |
| // Create |hbox_| and pad the sides. |
| hbox_ = gtk_hbox_new(FALSE, kElementPadding); |
| |
| // Make the whole infor bar horizontally shrinkable. |
| gtk_widget_set_size_request(hbox_, 0, -1); |
| |
| GtkWidget* padding = gtk_alignment_new(0, 0, 1, 1); |
| gtk_alignment_set_padding(GTK_ALIGNMENT(padding), |
| 0, 0, kLeftPadding, kRightPadding); |
| |
| bg_box_ = gtk_event_box_new(); |
| gtk_widget_set_app_paintable(bg_box_, TRUE); |
| g_signal_connect(bg_box_, "expose-event", |
| G_CALLBACK(OnBackgroundExposeThunk), this); |
| gtk_container_add(GTK_CONTAINER(padding), hbox_); |
| gtk_container_add(GTK_CONTAINER(bg_box_), padding); |
| gtk_widget_set_size_request(bg_box_, -1, kInfoBarHeight); |
| |
| // Add the icon on the left, if any. |
| SkBitmap* icon = delegate->GetIcon(); |
| if (icon) { |
| GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(icon); |
| GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf); |
| g_object_unref(pixbuf); |
| |
| gtk_misc_set_alignment(GTK_MISC(image), 0.5, 0.5); |
| |
| gtk_box_pack_start(GTK_BOX(hbox_), image, FALSE, FALSE, 0); |
| } |
| |
| close_button_.reset(CustomDrawButton::CloseButton(NULL)); |
| gtk_util::CenterWidgetInHBox(hbox_, close_button_->widget(), true, 0); |
| g_signal_connect(close_button_->widget(), "clicked", |
| G_CALLBACK(OnCloseButtonThunk), this); |
| |
| slide_widget_.reset(new SlideAnimatorGtk(bg_box_, |
| SlideAnimatorGtk::DOWN, |
| 0, true, true, this)); |
| // We store a pointer back to |this| so we can refer to it from the infobar |
| // container. |
| g_object_set_data(G_OBJECT(slide_widget_->widget()), "info-bar", this); |
| } |
| |
| InfoBar::~InfoBar() { |
| } |
| |
| GtkWidget* InfoBar::widget() { |
| return slide_widget_->widget(); |
| } |
| |
| void InfoBar::AnimateOpen() { |
| slide_widget_->Open(); |
| |
| gtk_widget_show_all(bg_box_); |
| if (bg_box_->window) |
| gdk_window_lower(bg_box_->window); |
| } |
| |
| void InfoBar::Open() { |
| slide_widget_->OpenWithoutAnimation(); |
| |
| gtk_widget_show_all(bg_box_); |
| if (bg_box_->window) |
| gdk_window_lower(bg_box_->window); |
| } |
| |
| void InfoBar::AnimateClose() { |
| slide_widget_->Close(); |
| } |
| |
| void InfoBar::Close() { |
| if (delegate_) { |
| delegate_->InfoBarClosed(); |
| delegate_ = NULL; |
| } |
| delete this; |
| } |
| |
| bool InfoBar::IsAnimating() { |
| return slide_widget_->IsAnimating(); |
| } |
| |
| bool InfoBar::IsClosing() { |
| return slide_widget_->IsClosing(); |
| } |
| |
| void InfoBar::ShowArrowFor(InfoBar* other, bool animate) { |
| arrow_model_.ShowArrowFor(other, animate); |
| } |
| |
| void InfoBar::PaintStateChanged() { |
| gtk_widget_queue_draw(widget()); |
| } |
| |
| void InfoBar::RemoveInfoBar() const { |
| container_->RemoveDelegate(delegate_); |
| } |
| |
| void InfoBar::Closed() { |
| Close(); |
| } |
| |
| void InfoBar::SetThemeProvider(GtkThemeService* theme_service) { |
| if (theme_service_) { |
| NOTREACHED(); |
| return; |
| } |
| |
| theme_service_ = theme_service; |
| registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, |
| NotificationService::AllSources()); |
| UpdateBorderColor(); |
| } |
| |
| void InfoBar::Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| UpdateBorderColor(); |
| } |
| |
| void InfoBar::AddLabelWithInlineLink(const string16& display_text, |
| const string16& link_text, |
| size_t link_offset, |
| GCallback callback) { |
| GtkWidget* link_button = gtk_chrome_link_button_new( |
| UTF16ToUTF8(link_text).c_str()); |
| gtk_chrome_link_button_set_use_gtk_theme( |
| GTK_CHROME_LINK_BUTTON(link_button), FALSE); |
| gtk_util::ForceFontSizePixels( |
| GTK_CHROME_LINK_BUTTON(link_button)->label, 13.4); |
| DCHECK(callback); |
| g_signal_connect(link_button, "clicked", callback, this); |
| gtk_util::SetButtonTriggersNavigation(link_button); |
| |
| GtkWidget* hbox = gtk_hbox_new(FALSE, 0); |
| // We want the link to be horizontally shrinkable, so that the Chrome |
| // window can be resized freely even with a very long link. |
| gtk_widget_set_size_request(hbox, 0, -1); |
| gtk_box_pack_start(GTK_BOX(hbox_), hbox, TRUE, TRUE, 0); |
| |
| // Need to insert the link inside the display text. |
| GtkWidget* initial_label = gtk_label_new( |
| UTF16ToUTF8(display_text.substr(0, link_offset)).c_str()); |
| GtkWidget* trailing_label = gtk_label_new( |
| UTF16ToUTF8(display_text.substr(link_offset)).c_str()); |
| |
| gtk_util::ForceFontSizePixels(initial_label, 13.4); |
| gtk_util::ForceFontSizePixels(trailing_label, 13.4); |
| |
| // TODO(joth): None of the label widgets are set as shrinkable here, meaning |
| // the text will run under the close button etc. when the width is restricted, |
| // rather than eliding. |
| gtk_widget_modify_fg(initial_label, GTK_STATE_NORMAL, >k_util::kGdkBlack); |
| gtk_widget_modify_fg(trailing_label, GTK_STATE_NORMAL, >k_util::kGdkBlack); |
| |
| // We don't want any spacing between the elements, so we pack them into |
| // this hbox that doesn't use kElementPadding. |
| gtk_box_pack_start(GTK_BOX(hbox), initial_label, FALSE, FALSE, 0); |
| gtk_util::CenterWidgetInHBox(hbox, link_button, false, 0); |
| gtk_box_pack_start(GTK_BOX(hbox), trailing_label, FALSE, FALSE, 0); |
| } |
| |
| void InfoBar::GetTopColor(InfoBarDelegate::Type type, |
| double* r, double* g, double *b) { |
| // These constants are copied from corresponding skia constants from |
| // browser/ui/views/infobars/infobars.cc, and then changed into 0-1 ranged |
| // values for cairo. |
| switch (type) { |
| case InfoBarDelegate::WARNING_TYPE: |
| *r = 255.0 / 255.0; |
| *g = 242.0 / 255.0; |
| *b = 183.0 / 255.0; |
| break; |
| case InfoBarDelegate::PAGE_ACTION_TYPE: |
| *r = 218.0 / 255.0; |
| *g = 231.0 / 255.0; |
| *b = 249.0 / 255.0; |
| break; |
| } |
| } |
| |
| void InfoBar::GetBottomColor(InfoBarDelegate::Type type, |
| double* r, double* g, double *b) { |
| switch (type) { |
| case InfoBarDelegate::WARNING_TYPE: |
| *r = 250.0 / 255.0; |
| *g = 230.0 / 255.0; |
| *b = 145.0 / 255.0; |
| break; |
| case InfoBarDelegate::PAGE_ACTION_TYPE: |
| *r = 179.0 / 255.0; |
| *g = 202.0 / 255.0; |
| *b = 231.0 / 255.0; |
| break; |
| } |
| } |
| |
| void InfoBar::UpdateBorderColor() { |
| gtk_widget_queue_draw(widget()); |
| } |
| |
| void InfoBar::OnCloseButton(GtkWidget* button) { |
| if (delegate_) |
| delegate_->InfoBarDismissed(); |
| RemoveInfoBar(); |
| } |
| |
| gboolean InfoBar::OnBackgroundExpose(GtkWidget* sender, |
| GdkEventExpose* event) { |
| const int height = sender->allocation.height; |
| |
| cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(sender->window)); |
| gdk_cairo_rectangle(cr, &event->area); |
| cairo_clip(cr); |
| |
| cairo_pattern_t* pattern = cairo_pattern_create_linear(0, 0, 0, height); |
| |
| double top_r, top_g, top_b; |
| GetTopColor(delegate_->GetInfoBarType(), &top_r, &top_g, &top_b); |
| cairo_pattern_add_color_stop_rgb(pattern, 0.0, top_r, top_g, top_b); |
| |
| double bottom_r, bottom_g, bottom_b; |
| GetBottomColor(delegate_->GetInfoBarType(), &bottom_r, &bottom_g, &bottom_b); |
| cairo_pattern_add_color_stop_rgb( |
| pattern, 1.0, bottom_r, bottom_g, bottom_b); |
| cairo_set_source(cr, pattern); |
| cairo_paint(cr); |
| cairo_pattern_destroy(pattern); |
| |
| // Draw the bottom border. |
| GdkColor border_color = theme_service_->GetBorderColor(); |
| cairo_set_source_rgb(cr, border_color.red / 65535.0, |
| border_color.green / 65535.0, |
| border_color.blue / 65535.0); |
| cairo_set_line_width(cr, 1.0); |
| int y = sender->allocation.height; |
| cairo_move_to(cr, 0, y - 0.5); |
| cairo_rel_line_to(cr, sender->allocation.width, 0); |
| cairo_stroke(cr); |
| |
| cairo_destroy(cr); |
| |
| if (!arrow_model_.NeedToDrawInfoBarArrow()) |
| return FALSE; |
| |
| GtkWindow* parent = platform_util::GetTopLevel(widget()); |
| BrowserWindowGtk* browser_window = |
| BrowserWindowGtk::GetBrowserWindowForNativeWindow(parent); |
| int x = browser_window ? |
| browser_window->GetXPositionOfLocationIcon(sender) : 0; |
| |
| size_t size = InfoBarArrowModel::kDefaultArrowSize; |
| gfx::Rect arrow_bounds(x - size, y - size, 2 * size, size); |
| arrow_model_.Paint(sender, event, arrow_bounds, border_color); |
| |
| return FALSE; |
| } |