Home | History | Annotate | Download | only in infobars
      1 // Copyright (c) 2012 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/ui/gtk/infobars/infobar_gtk.h"
      6 
      7 #include "base/debug/trace_event.h"
      8 #include "base/strings/utf_string_conversions.h"
      9 #include "chrome/browser/chrome_notification_types.h"
     10 #include "chrome/browser/profiles/profile.h"
     11 #include "chrome/browser/themes/theme_properties.h"
     12 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
     13 #include "chrome/browser/ui/gtk/custom_button.h"
     14 #include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
     15 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     16 #include "chrome/browser/ui/gtk/gtk_util.h"
     17 #include "chrome/browser/ui/gtk/infobars/infobar_container_gtk.h"
     18 #include "content/public/browser/notification_source.h"
     19 #include "content/public/browser/web_contents.h"
     20 #include "ui/base/gtk/gtk_expanded_container.h"
     21 #include "ui/base/gtk/gtk_hig_constants.h"
     22 #include "ui/base/gtk/gtk_signal_registrar.h"
     23 #include "ui/base/models/menu_model.h"
     24 #include "ui/gfx/gtk_util.h"
     25 #include "ui/gfx/image/image.h"
     26 
     27 namespace {
     28 
     29 // Pixels between infobar elements.
     30 const int kElementPadding = 5;
     31 
     32 // Extra padding on either end of info bar.
     33 const int kLeftPadding = 5;
     34 const int kRightPadding = 5;
     35 
     36 }  // namespace
     37 
     38 
     39 // InfoBar --------------------------------------------------------------------
     40 
     41 // static
     42 const int InfoBar::kSeparatorLineHeight = 1;
     43 const int InfoBar::kDefaultArrowTargetHeight = 9;
     44 const int InfoBar::kMaximumArrowTargetHeight = 24;
     45 const int InfoBar::kDefaultArrowTargetHalfWidth = kDefaultArrowTargetHeight;
     46 const int InfoBar::kMaximumArrowTargetHalfWidth = 14;
     47 const int InfoBar::kDefaultBarTargetHeight = 36;
     48 
     49 
     50 // InfoBarGtk -----------------------------------------------------------------
     51 
     52 // static
     53 const int InfoBarGtk::kEndOfLabelSpacing = 6;
     54 
     55 InfoBarGtk::InfoBarGtk(scoped_ptr<InfoBarDelegate> delegate)
     56     : InfoBar(delegate.Pass()),
     57       bg_box_(NULL),
     58       hbox_(NULL),
     59       theme_service_(NULL),
     60       signals_(new ui::GtkSignalRegistrar) {
     61 }
     62 
     63 InfoBarGtk::~InfoBarGtk() {
     64 }
     65 
     66 GdkColor InfoBarGtk::GetBorderColor() const {
     67   DCHECK(theme_service_);
     68   return theme_service_->GetBorderColor();
     69 }
     70 
     71 int InfoBarGtk::AnimatingHeight() const {
     72   return animation().is_animating() ? bar_target_height() : 0;
     73 }
     74 
     75 SkColor InfoBarGtk::ConvertGetColor(ColorGetter getter) {
     76   double r, g, b;
     77   (this->*getter)(delegate()->GetInfoBarType(), &r, &g, &b);
     78   return SkColorSetARGB(255, 255 * r, 255 * g, 255 * b);
     79 }
     80 
     81 void InfoBarGtk::GetTopColor(InfoBarDelegate::Type type,
     82                              double* r, double* g, double* b) {
     83   GetBackgroundColor(InfoBar::GetTopColor(type), r, g, b);
     84 }
     85 
     86 void InfoBarGtk::GetBottomColor(InfoBarDelegate::Type type,
     87                                 double* r, double* g, double* b) {
     88   GetBackgroundColor(InfoBar::GetBottomColor(type), r, g, b);
     89 }
     90 
     91 void InfoBarGtk::PlatformSpecificSetOwner() {
     92   DCHECK(owner());
     93   DCHECK(!theme_service_);
     94   theme_service_ = GtkThemeService::GetFrom(Profile::FromBrowserContext(
     95       owner()->web_contents()->GetBrowserContext()));
     96 
     97   // Create |hbox_| and pad the sides.
     98   hbox_ = gtk_hbox_new(FALSE, kElementPadding);
     99 
    100   // Make the whole infor bar horizontally shrinkable.
    101   gtk_widget_set_size_request(hbox_, 0, -1);
    102 
    103   GtkWidget* padding = gtk_alignment_new(0, 0, 1, 1);
    104   gtk_alignment_set_padding(GTK_ALIGNMENT(padding),
    105                             0, 0, kLeftPadding, kRightPadding);
    106 
    107   bg_box_ = gtk_event_box_new();
    108   gtk_widget_set_app_paintable(bg_box_, TRUE);
    109   g_signal_connect(bg_box_, "expose-event",
    110                    G_CALLBACK(OnBackgroundExposeThunk), this);
    111   gtk_container_add(GTK_CONTAINER(padding), hbox_);
    112   gtk_container_add(GTK_CONTAINER(bg_box_), padding);
    113 
    114   // Add the icon on the left, if any.
    115   gfx::Image icon = delegate()->GetIcon();
    116   if (!icon.IsEmpty()) {
    117     GtkWidget* image = gtk_image_new_from_pixbuf(icon.ToGdkPixbuf());
    118 
    119     gtk_misc_set_alignment(GTK_MISC(image), 0.5, 0.5);
    120 
    121     gtk_box_pack_start(GTK_BOX(hbox_), image, FALSE, FALSE, 0);
    122   }
    123 
    124   close_button_.reset(CustomDrawButton::CloseButtonBar(theme_service_));
    125   gtk_util::CenterWidgetInHBox(hbox_, close_button_->widget(), true, 0);
    126   signals_->Connect(close_button_->widget(), "clicked",
    127                     G_CALLBACK(OnCloseButtonThunk), this);
    128 
    129   widget_.Own(gtk_expanded_container_new());
    130   gtk_container_add(GTK_CONTAINER(widget_.get()), bg_box_);
    131   gtk_widget_set_size_request(widget_.get(), -1, 0);
    132 
    133   g_signal_connect(widget_.get(), "child-size-request",
    134                    G_CALLBACK(OnChildSizeRequestThunk),
    135                    this);
    136 
    137   registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
    138                  content::Source<ThemeService>(theme_service_));
    139   UpdateBorderColor();
    140 }
    141 
    142 void InfoBarGtk::PlatformSpecificShow(bool animate) {
    143   DCHECK(bg_box_);
    144 
    145   DCHECK(widget());
    146   gtk_widget_show_all(widget());
    147   gtk_widget_set_size_request(widget(), -1, bar_height());
    148 
    149   GdkWindow* gdk_window = gtk_widget_get_window(bg_box_);
    150   if (gdk_window)
    151     gdk_window_lower(gdk_window);
    152 }
    153 
    154 void InfoBarGtk::PlatformSpecificOnCloseSoon() {
    155   // We must close all menus and prevent any signals from being emitted while
    156   // we are animating the info bar closed.
    157   menu_.reset();
    158   signals_.reset();
    159 }
    160 
    161 void InfoBarGtk::PlatformSpecificOnHeightsRecalculated() {
    162   DCHECK(bg_box_);
    163   DCHECK(widget());
    164   gtk_widget_set_size_request(bg_box_, -1, bar_target_height());
    165   gtk_expanded_container_move(GTK_EXPANDED_CONTAINER(widget()),
    166                               bg_box_, 0,
    167                               bar_height() - bar_target_height());
    168 
    169   gtk_widget_set_size_request(widget(), -1, bar_height());
    170   gtk_widget_queue_draw(widget());
    171 }
    172 
    173 void InfoBarGtk::Observe(int type,
    174                          const content::NotificationSource& source,
    175                          const content::NotificationDetails& details) {
    176   DCHECK(widget());
    177   UpdateBorderColor();
    178 }
    179 
    180 void InfoBarGtk::ForceCloseButtonToUseChromeTheme() {
    181   close_button_->ForceChromeTheme();
    182 }
    183 
    184 GtkWidget* InfoBarGtk::CreateLabel(const std::string& text) {
    185   DCHECK(theme_service_);
    186   return theme_service_->BuildLabel(text, ui::kGdkBlack);
    187 }
    188 
    189 GtkWidget* InfoBarGtk::CreateLinkButton(const std::string& text) {
    190   DCHECK(theme_service_);
    191   return theme_service_->BuildChromeLinkButton(text);
    192 }
    193 
    194 // static
    195 GtkWidget* InfoBarGtk::CreateMenuButton(const std::string& text) {
    196   GtkWidget* button = gtk_button_new();
    197   GtkWidget* former_child = gtk_bin_get_child(GTK_BIN(button));
    198   if (former_child)
    199     gtk_container_remove(GTK_CONTAINER(button), former_child);
    200 
    201   GtkWidget* hbox = gtk_hbox_new(FALSE, 0);
    202 
    203   GtkWidget* label = gtk_label_new(text.c_str());
    204   gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
    205 
    206   GtkWidget* arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
    207   gtk_box_pack_start(GTK_BOX(hbox), arrow, FALSE, FALSE, 0);
    208 
    209   gtk_container_add(GTK_CONTAINER(button), hbox);
    210 
    211   return button;
    212 }
    213 
    214 void InfoBarGtk::AddLabelWithInlineLink(const base::string16& display_text,
    215                                         const base::string16& link_text,
    216                                         size_t link_offset,
    217                                         GCallback callback) {
    218   DCHECK(hbox_);
    219   GtkWidget* link_button = CreateLinkButton(UTF16ToUTF8(link_text));
    220   gtk_util::ForceFontSizePixels(
    221       GTK_CHROME_LINK_BUTTON(link_button)->label, 13.4);
    222   DCHECK(callback);
    223   signals_->Connect(link_button, "clicked", callback, this);
    224   gtk_util::SetButtonTriggersNavigation(link_button);
    225 
    226   GtkWidget* hbox = gtk_hbox_new(FALSE, 0);
    227   // We want the link to be horizontally shrinkable, so that the Chrome
    228   // window can be resized freely even with a very long link.
    229   gtk_widget_set_size_request(hbox, 0, -1);
    230   gtk_box_pack_start(GTK_BOX(hbox_), hbox, TRUE, TRUE, 0);
    231 
    232   // Need to insert the link inside the display text.
    233   GtkWidget* initial_label = CreateLabel(
    234       UTF16ToUTF8(display_text.substr(0, link_offset)));
    235   GtkWidget* trailing_label = CreateLabel(
    236       UTF16ToUTF8(display_text.substr(link_offset)));
    237 
    238   gtk_util::ForceFontSizePixels(initial_label, 13.4);
    239   gtk_util::ForceFontSizePixels(trailing_label, 13.4);
    240 
    241   // TODO(joth): None of the label widgets are set as shrinkable here, meaning
    242   // the text will run under the close button etc. when the width is restricted,
    243   // rather than eliding.
    244 
    245   // We don't want any spacing between the elements, so we pack them into
    246   // this hbox that doesn't use kElementPadding.
    247   gtk_box_pack_start(GTK_BOX(hbox), initial_label, FALSE, FALSE, 0);
    248   gtk_util::CenterWidgetInHBox(hbox, link_button, false, 0);
    249   gtk_box_pack_start(GTK_BOX(hbox), trailing_label, FALSE, FALSE, 0);
    250 }
    251 
    252 void InfoBarGtk::ShowMenuWithModel(GtkWidget* sender,
    253                                    MenuGtk::Delegate* delegate,
    254                                    ui::MenuModel* model) {
    255   menu_.reset(new MenuGtk(delegate, model));
    256   menu_->PopupForWidget(sender, 1, gtk_get_current_event_time());
    257 }
    258 
    259 void InfoBarGtk::GetBackgroundColor(SkColor color,
    260                                     double* r, double* g, double* b) {
    261   DCHECK(theme_service_);
    262   if (theme_service_->UsingNativeTheme())
    263     color = theme_service_->GetColor(ThemeProperties::COLOR_TOOLBAR);
    264   *r = SkColorGetR(color) / 255.0;
    265   *g = SkColorGetG(color) / 255.0;
    266   *b = SkColorGetB(color) / 255.0;
    267 }
    268 
    269 void InfoBarGtk::UpdateBorderColor() {
    270   DCHECK(widget());
    271   gtk_widget_queue_draw(widget());
    272 }
    273 
    274 void InfoBarGtk::OnCloseButton(GtkWidget* button) {
    275   // If we're not owned, we're already closing, so don't call
    276   // InfoBarDismissed(), since this can lead to us double-recording dismissals.
    277   if (owner())
    278     delegate()->InfoBarDismissed();
    279   RemoveSelf();
    280 }
    281 
    282 gboolean InfoBarGtk::OnBackgroundExpose(GtkWidget* sender,
    283                                         GdkEventExpose* event) {
    284   TRACE_EVENT0("ui::gtk", "InfoBarGtk::OnBackgroundExpose");
    285   DCHECK(theme_service_);
    286 
    287   GtkAllocation allocation;
    288   gtk_widget_get_allocation(sender, &allocation);
    289   const int height = allocation.height;
    290 
    291   cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(sender));
    292   gdk_cairo_rectangle(cr, &event->area);
    293   cairo_clip(cr);
    294 
    295   cairo_pattern_t* pattern = cairo_pattern_create_linear(0, 0, 0, height);
    296 
    297   double top_r, top_g, top_b;
    298   GetTopColor(delegate()->GetInfoBarType(), &top_r, &top_g, &top_b);
    299   cairo_pattern_add_color_stop_rgb(pattern, 0.0, top_r, top_g, top_b);
    300 
    301   double bottom_r, bottom_g, bottom_b;
    302   GetBottomColor(delegate()->GetInfoBarType(), &bottom_r, &bottom_g, &bottom_b);
    303   cairo_pattern_add_color_stop_rgb(
    304       pattern, 1.0, bottom_r, bottom_g, bottom_b);
    305   cairo_set_source(cr, pattern);
    306   cairo_paint(cr);
    307   cairo_pattern_destroy(pattern);
    308 
    309   // Draw the bottom border.
    310   GdkColor border_color = GetBorderColor();
    311   cairo_set_source_rgb(cr, border_color.red / 65535.0,
    312                        border_color.green / 65535.0,
    313                        border_color.blue / 65535.0);
    314   cairo_set_line_width(cr, 1.0);
    315   cairo_move_to(cr, 0, allocation.height - 0.5);
    316   cairo_rel_line_to(cr, allocation.width, 0);
    317   cairo_stroke(cr);
    318 
    319   cairo_destroy(cr);
    320 
    321   if (container()) {
    322     static_cast<InfoBarContainerGtk*>(container())->
    323         PaintInfobarBitsOn(sender, event, this);
    324   }
    325 
    326   return FALSE;
    327 }
    328 
    329 void InfoBarGtk::OnChildSizeRequest(GtkWidget* expanded,
    330                                     GtkWidget* child,
    331                                     GtkRequisition* requisition) {
    332   requisition->height = -1;
    333 }
    334