Home | History | Annotate | Download | only in infobars
      1 // Copyright (c) 2011 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 <gtk/gtk.h>
      8 
      9 #include "base/utf_string_conversions.h"
     10 #include "chrome/browser/platform_util.h"
     11 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
     12 #include "chrome/browser/ui/gtk/custom_button.h"
     13 #include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
     14 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     15 #include "chrome/browser/ui/gtk/gtk_util.h"
     16 #include "chrome/browser/ui/gtk/infobars/infobar_container_gtk.h"
     17 #include "content/common/notification_service.h"
     18 #include "ui/gfx/gtk_util.h"
     19 
     20 extern const int InfoBar::kInfoBarHeight = 37;
     21 
     22 namespace {
     23 
     24 // Pixels between infobar elements.
     25 const int kElementPadding = 5;
     26 
     27 // Extra padding on either end of info bar.
     28 const int kLeftPadding = 5;
     29 const int kRightPadding = 5;
     30 
     31 }  // namespace
     32 
     33 // static
     34 const int InfoBar::kEndOfLabelSpacing = 6;
     35 const int InfoBar::kButtonButtonSpacing = 3;
     36 
     37 InfoBar::InfoBar(InfoBarDelegate* delegate)
     38     : container_(NULL),
     39       delegate_(delegate),
     40       theme_service_(NULL),
     41       arrow_model_(this) {
     42   // Create |hbox_| and pad the sides.
     43   hbox_ = gtk_hbox_new(FALSE, kElementPadding);
     44 
     45   // Make the whole infor bar horizontally shrinkable.
     46   gtk_widget_set_size_request(hbox_, 0, -1);
     47 
     48   GtkWidget* padding = gtk_alignment_new(0, 0, 1, 1);
     49   gtk_alignment_set_padding(GTK_ALIGNMENT(padding),
     50       0, 0, kLeftPadding, kRightPadding);
     51 
     52   bg_box_ = gtk_event_box_new();
     53   gtk_widget_set_app_paintable(bg_box_, TRUE);
     54   g_signal_connect(bg_box_, "expose-event",
     55                    G_CALLBACK(OnBackgroundExposeThunk), this);
     56   gtk_container_add(GTK_CONTAINER(padding), hbox_);
     57   gtk_container_add(GTK_CONTAINER(bg_box_), padding);
     58   gtk_widget_set_size_request(bg_box_, -1, kInfoBarHeight);
     59 
     60   // Add the icon on the left, if any.
     61   SkBitmap* icon = delegate->GetIcon();
     62   if (icon) {
     63     GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(icon);
     64     GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf);
     65     g_object_unref(pixbuf);
     66 
     67     gtk_misc_set_alignment(GTK_MISC(image), 0.5, 0.5);
     68 
     69     gtk_box_pack_start(GTK_BOX(hbox_), image, FALSE, FALSE, 0);
     70   }
     71 
     72   close_button_.reset(CustomDrawButton::CloseButton(NULL));
     73   gtk_util::CenterWidgetInHBox(hbox_, close_button_->widget(), true, 0);
     74   g_signal_connect(close_button_->widget(), "clicked",
     75                    G_CALLBACK(OnCloseButtonThunk), this);
     76 
     77   slide_widget_.reset(new SlideAnimatorGtk(bg_box_,
     78                                            SlideAnimatorGtk::DOWN,
     79                                            0, true, true, this));
     80   // We store a pointer back to |this| so we can refer to it from the infobar
     81   // container.
     82   g_object_set_data(G_OBJECT(slide_widget_->widget()), "info-bar", this);
     83 }
     84 
     85 InfoBar::~InfoBar() {
     86 }
     87 
     88 GtkWidget* InfoBar::widget() {
     89   return slide_widget_->widget();
     90 }
     91 
     92 void InfoBar::AnimateOpen() {
     93   slide_widget_->Open();
     94 
     95   gtk_widget_show_all(bg_box_);
     96   if (bg_box_->window)
     97     gdk_window_lower(bg_box_->window);
     98 }
     99 
    100 void InfoBar::Open() {
    101   slide_widget_->OpenWithoutAnimation();
    102 
    103   gtk_widget_show_all(bg_box_);
    104   if (bg_box_->window)
    105     gdk_window_lower(bg_box_->window);
    106 }
    107 
    108 void InfoBar::AnimateClose() {
    109   slide_widget_->Close();
    110 }
    111 
    112 void InfoBar::Close() {
    113   if (delegate_) {
    114     delegate_->InfoBarClosed();
    115     delegate_ = NULL;
    116   }
    117   delete this;
    118 }
    119 
    120 bool InfoBar::IsAnimating() {
    121   return slide_widget_->IsAnimating();
    122 }
    123 
    124 bool InfoBar::IsClosing() {
    125   return slide_widget_->IsClosing();
    126 }
    127 
    128 void InfoBar::ShowArrowFor(InfoBar* other, bool animate) {
    129   arrow_model_.ShowArrowFor(other, animate);
    130 }
    131 
    132 void InfoBar::PaintStateChanged() {
    133   gtk_widget_queue_draw(widget());
    134 }
    135 
    136 void InfoBar::RemoveInfoBar() const {
    137   container_->RemoveDelegate(delegate_);
    138 }
    139 
    140 void InfoBar::Closed() {
    141   Close();
    142 }
    143 
    144 void InfoBar::SetThemeProvider(GtkThemeService* theme_service) {
    145   if (theme_service_) {
    146     NOTREACHED();
    147     return;
    148   }
    149 
    150   theme_service_ = theme_service;
    151   registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
    152                  NotificationService::AllSources());
    153   UpdateBorderColor();
    154 }
    155 
    156 void InfoBar::Observe(NotificationType type,
    157                       const NotificationSource& source,
    158                       const NotificationDetails& details) {
    159   UpdateBorderColor();
    160 }
    161 
    162 void InfoBar::AddLabelWithInlineLink(const string16& display_text,
    163                                      const string16& link_text,
    164                                      size_t link_offset,
    165                                      GCallback callback) {
    166   GtkWidget* link_button = gtk_chrome_link_button_new(
    167       UTF16ToUTF8(link_text).c_str());
    168   gtk_chrome_link_button_set_use_gtk_theme(
    169       GTK_CHROME_LINK_BUTTON(link_button), FALSE);
    170   gtk_util::ForceFontSizePixels(
    171       GTK_CHROME_LINK_BUTTON(link_button)->label, 13.4);
    172   DCHECK(callback);
    173   g_signal_connect(link_button, "clicked", callback, this);
    174   gtk_util::SetButtonTriggersNavigation(link_button);
    175 
    176   GtkWidget* hbox = gtk_hbox_new(FALSE, 0);
    177   // We want the link to be horizontally shrinkable, so that the Chrome
    178   // window can be resized freely even with a very long link.
    179   gtk_widget_set_size_request(hbox, 0, -1);
    180   gtk_box_pack_start(GTK_BOX(hbox_), hbox, TRUE, TRUE, 0);
    181 
    182   // Need to insert the link inside the display text.
    183   GtkWidget* initial_label = gtk_label_new(
    184       UTF16ToUTF8(display_text.substr(0, link_offset)).c_str());
    185   GtkWidget* trailing_label = gtk_label_new(
    186       UTF16ToUTF8(display_text.substr(link_offset)).c_str());
    187 
    188   gtk_util::ForceFontSizePixels(initial_label, 13.4);
    189   gtk_util::ForceFontSizePixels(trailing_label, 13.4);
    190 
    191   // TODO(joth): None of the label widgets are set as shrinkable here, meaning
    192   // the text will run under the close button etc. when the width is restricted,
    193   // rather than eliding.
    194   gtk_widget_modify_fg(initial_label, GTK_STATE_NORMAL, &gtk_util::kGdkBlack);
    195   gtk_widget_modify_fg(trailing_label, GTK_STATE_NORMAL, &gtk_util::kGdkBlack);
    196 
    197   // We don't want any spacing between the elements, so we pack them into
    198   // this hbox that doesn't use kElementPadding.
    199   gtk_box_pack_start(GTK_BOX(hbox), initial_label, FALSE, FALSE, 0);
    200   gtk_util::CenterWidgetInHBox(hbox, link_button, false, 0);
    201   gtk_box_pack_start(GTK_BOX(hbox), trailing_label, FALSE, FALSE, 0);
    202 }
    203 
    204 void InfoBar::GetTopColor(InfoBarDelegate::Type type,
    205                           double* r, double* g, double *b) {
    206   // These constants are copied from corresponding skia constants from
    207   // browser/ui/views/infobars/infobars.cc, and then changed into 0-1 ranged
    208   // values for cairo.
    209   switch (type) {
    210     case InfoBarDelegate::WARNING_TYPE:
    211       *r = 255.0 / 255.0;
    212       *g = 242.0 / 255.0;
    213       *b = 183.0 / 255.0;
    214       break;
    215     case InfoBarDelegate::PAGE_ACTION_TYPE:
    216       *r = 218.0 / 255.0;
    217       *g = 231.0 / 255.0;
    218       *b = 249.0 / 255.0;
    219       break;
    220   }
    221 }
    222 
    223 void InfoBar::GetBottomColor(InfoBarDelegate::Type type,
    224                              double* r, double* g, double *b) {
    225   switch (type) {
    226     case InfoBarDelegate::WARNING_TYPE:
    227       *r = 250.0 / 255.0;
    228       *g = 230.0 / 255.0;
    229       *b = 145.0 / 255.0;
    230       break;
    231     case InfoBarDelegate::PAGE_ACTION_TYPE:
    232       *r = 179.0 / 255.0;
    233       *g = 202.0 / 255.0;
    234       *b = 231.0 / 255.0;
    235       break;
    236   }
    237 }
    238 
    239 void InfoBar::UpdateBorderColor() {
    240   gtk_widget_queue_draw(widget());
    241 }
    242 
    243 void InfoBar::OnCloseButton(GtkWidget* button) {
    244   if (delegate_)
    245     delegate_->InfoBarDismissed();
    246   RemoveInfoBar();
    247 }
    248 
    249 gboolean InfoBar::OnBackgroundExpose(GtkWidget* sender,
    250                                      GdkEventExpose* event) {
    251   const int height = sender->allocation.height;
    252 
    253   cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(sender->window));
    254   gdk_cairo_rectangle(cr, &event->area);
    255   cairo_clip(cr);
    256 
    257   cairo_pattern_t* pattern = cairo_pattern_create_linear(0, 0, 0, height);
    258 
    259   double top_r, top_g, top_b;
    260   GetTopColor(delegate_->GetInfoBarType(), &top_r, &top_g, &top_b);
    261   cairo_pattern_add_color_stop_rgb(pattern, 0.0, top_r, top_g, top_b);
    262 
    263   double bottom_r, bottom_g, bottom_b;
    264   GetBottomColor(delegate_->GetInfoBarType(), &bottom_r, &bottom_g, &bottom_b);
    265   cairo_pattern_add_color_stop_rgb(
    266       pattern, 1.0, bottom_r, bottom_g, bottom_b);
    267   cairo_set_source(cr, pattern);
    268   cairo_paint(cr);
    269   cairo_pattern_destroy(pattern);
    270 
    271   // Draw the bottom border.
    272   GdkColor border_color = theme_service_->GetBorderColor();
    273   cairo_set_source_rgb(cr, border_color.red / 65535.0,
    274                            border_color.green / 65535.0,
    275                            border_color.blue / 65535.0);
    276   cairo_set_line_width(cr, 1.0);
    277   int y = sender->allocation.height;
    278   cairo_move_to(cr, 0, y - 0.5);
    279   cairo_rel_line_to(cr, sender->allocation.width, 0);
    280   cairo_stroke(cr);
    281 
    282   cairo_destroy(cr);
    283 
    284   if (!arrow_model_.NeedToDrawInfoBarArrow())
    285     return FALSE;
    286 
    287   GtkWindow* parent = platform_util::GetTopLevel(widget());
    288   BrowserWindowGtk* browser_window =
    289       BrowserWindowGtk::GetBrowserWindowForNativeWindow(parent);
    290   int x = browser_window ?
    291       browser_window->GetXPositionOfLocationIcon(sender) : 0;
    292 
    293   size_t size = InfoBarArrowModel::kDefaultArrowSize;
    294   gfx::Rect arrow_bounds(x - size, y - size, 2 * size, size);
    295   arrow_model_.Paint(sender, event, arrow_bounds, border_color);
    296 
    297   return FALSE;
    298 }
    299