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
InfoBar(InfoBarDelegate * delegate)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
~InfoBar()85 InfoBar::~InfoBar() {
86 }
87
widget()88 GtkWidget* InfoBar::widget() {
89 return slide_widget_->widget();
90 }
91
AnimateOpen()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
Open()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
AnimateClose()108 void InfoBar::AnimateClose() {
109 slide_widget_->Close();
110 }
111
Close()112 void InfoBar::Close() {
113 if (delegate_) {
114 delegate_->InfoBarClosed();
115 delegate_ = NULL;
116 }
117 delete this;
118 }
119
IsAnimating()120 bool InfoBar::IsAnimating() {
121 return slide_widget_->IsAnimating();
122 }
123
IsClosing()124 bool InfoBar::IsClosing() {
125 return slide_widget_->IsClosing();
126 }
127
ShowArrowFor(InfoBar * other,bool animate)128 void InfoBar::ShowArrowFor(InfoBar* other, bool animate) {
129 arrow_model_.ShowArrowFor(other, animate);
130 }
131
PaintStateChanged()132 void InfoBar::PaintStateChanged() {
133 gtk_widget_queue_draw(widget());
134 }
135
RemoveInfoBar() const136 void InfoBar::RemoveInfoBar() const {
137 container_->RemoveDelegate(delegate_);
138 }
139
Closed()140 void InfoBar::Closed() {
141 Close();
142 }
143
SetThemeProvider(GtkThemeService * theme_service)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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)156 void InfoBar::Observe(NotificationType type,
157 const NotificationSource& source,
158 const NotificationDetails& details) {
159 UpdateBorderColor();
160 }
161
AddLabelWithInlineLink(const string16 & display_text,const string16 & link_text,size_t link_offset,GCallback callback)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, >k_util::kGdkBlack);
195 gtk_widget_modify_fg(trailing_label, GTK_STATE_NORMAL, >k_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
GetTopColor(InfoBarDelegate::Type type,double * r,double * g,double * b)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
GetBottomColor(InfoBarDelegate::Type type,double * r,double * g,double * b)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
UpdateBorderColor()239 void InfoBar::UpdateBorderColor() {
240 gtk_widget_queue_draw(widget());
241 }
242
OnCloseButton(GtkWidget * button)243 void InfoBar::OnCloseButton(GtkWidget* button) {
244 if (delegate_)
245 delegate_->InfoBarDismissed();
246 RemoveInfoBar();
247 }
248
OnBackgroundExpose(GtkWidget * sender,GdkEventExpose * event)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