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
20extern const int InfoBar::kInfoBarHeight = 37;
21
22namespace {
23
24// Pixels between infobar elements.
25const int kElementPadding = 5;
26
27// Extra padding on either end of info bar.
28const int kLeftPadding = 5;
29const int kRightPadding = 5;
30
31}  // namespace
32
33// static
34const int InfoBar::kEndOfLabelSpacing = 6;
35const int InfoBar::kButtonButtonSpacing = 3;
36
37InfoBar::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
85InfoBar::~InfoBar() {
86}
87
88GtkWidget* InfoBar::widget() {
89  return slide_widget_->widget();
90}
91
92void 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
100void 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
108void InfoBar::AnimateClose() {
109  slide_widget_->Close();
110}
111
112void InfoBar::Close() {
113  if (delegate_) {
114    delegate_->InfoBarClosed();
115    delegate_ = NULL;
116  }
117  delete this;
118}
119
120bool InfoBar::IsAnimating() {
121  return slide_widget_->IsAnimating();
122}
123
124bool InfoBar::IsClosing() {
125  return slide_widget_->IsClosing();
126}
127
128void InfoBar::ShowArrowFor(InfoBar* other, bool animate) {
129  arrow_model_.ShowArrowFor(other, animate);
130}
131
132void InfoBar::PaintStateChanged() {
133  gtk_widget_queue_draw(widget());
134}
135
136void InfoBar::RemoveInfoBar() const {
137  container_->RemoveDelegate(delegate_);
138}
139
140void InfoBar::Closed() {
141  Close();
142}
143
144void 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
156void InfoBar::Observe(NotificationType type,
157                      const NotificationSource& source,
158                      const NotificationDetails& details) {
159  UpdateBorderColor();
160}
161
162void 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
204void 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
223void 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
239void InfoBar::UpdateBorderColor() {
240  gtk_widget_queue_draw(widget());
241}
242
243void InfoBar::OnCloseButton(GtkWidget* button) {
244  if (delegate_)
245    delegate_->InfoBarDismissed();
246  RemoveInfoBar();
247}
248
249gboolean 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