OLD | NEW |
| (Empty) |
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/gtk/infobar_gtk.h" | |
6 | |
7 #include <gtk/gtk.h> | |
8 | |
9 #include "base/utf_string_conversions.h" | |
10 #include "chrome/browser/gtk/browser_window_gtk.h" | |
11 #include "chrome/browser/gtk/custom_button.h" | |
12 #include "chrome/browser/gtk/gtk_chrome_link_button.h" | |
13 #include "chrome/browser/gtk/gtk_chrome_shrinkable_hbox.h" | |
14 #include "chrome/browser/gtk/gtk_theme_provider.h" | |
15 #include "chrome/browser/gtk/gtk_util.h" | |
16 #include "chrome/browser/gtk/infobar_container_gtk.h" | |
17 #include "chrome/browser/platform_util.h" | |
18 #include "chrome/common/notification_service.h" | |
19 #include "gfx/gtk_util.h" | |
20 | |
21 extern const int InfoBar::kInfoBarHeight = 37; | |
22 | |
23 namespace { | |
24 | |
25 // Spacing after message (and before buttons). | |
26 const int kEndOfLabelSpacing = 6; | |
27 // Spacing between buttons. | |
28 const int kButtonButtonSpacing = 3; | |
29 | |
30 // Pixels between infobar elements. | |
31 const int kElementPadding = 5; | |
32 | |
33 // Extra padding on either end of info bar. | |
34 const int kLeftPadding = 5; | |
35 const int kRightPadding = 5; | |
36 | |
37 } // namespace | |
38 | |
39 InfoBar::InfoBar(InfoBarDelegate* delegate) | |
40 : container_(NULL), | |
41 delegate_(delegate), | |
42 theme_provider_(NULL), | |
43 arrow_model_(this) { | |
44 // Create |hbox_| and pad the sides. | |
45 hbox_ = gtk_hbox_new(FALSE, kElementPadding); | |
46 | |
47 // Make the whole infor bar horizontally shrinkable. | |
48 gtk_widget_set_size_request(hbox_, 0, -1); | |
49 | |
50 GtkWidget* padding = gtk_alignment_new(0, 0, 1, 1); | |
51 gtk_alignment_set_padding(GTK_ALIGNMENT(padding), | |
52 0, 0, kLeftPadding, kRightPadding); | |
53 | |
54 bg_box_ = gtk_event_box_new(); | |
55 gtk_widget_set_app_paintable(bg_box_, TRUE); | |
56 g_signal_connect(bg_box_, "expose-event", | |
57 G_CALLBACK(OnBackgroundExposeThunk), this); | |
58 gtk_container_add(GTK_CONTAINER(padding), hbox_); | |
59 gtk_container_add(GTK_CONTAINER(bg_box_), padding); | |
60 gtk_widget_set_size_request(bg_box_, -1, kInfoBarHeight); | |
61 | |
62 // Add the icon on the left, if any. | |
63 SkBitmap* icon = delegate->GetIcon(); | |
64 if (icon) { | |
65 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(icon); | |
66 GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf); | |
67 g_object_unref(pixbuf); | |
68 gtk_box_pack_start(GTK_BOX(hbox_), image, FALSE, FALSE, 0); | |
69 } | |
70 | |
71 close_button_.reset(CustomDrawButton::CloseButton(NULL)); | |
72 gtk_util::CenterWidgetInHBox(hbox_, close_button_->widget(), true, 0); | |
73 g_signal_connect(close_button_->widget(), "clicked", | |
74 G_CALLBACK(OnCloseButtonThunk), this); | |
75 | |
76 slide_widget_.reset(new SlideAnimatorGtk(bg_box_, | |
77 SlideAnimatorGtk::DOWN, | |
78 0, true, true, this)); | |
79 // We store a pointer back to |this| so we can refer to it from the infobar | |
80 // container. | |
81 g_object_set_data(G_OBJECT(slide_widget_->widget()), "info-bar", this); | |
82 } | |
83 | |
84 InfoBar::~InfoBar() { | |
85 } | |
86 | |
87 GtkWidget* InfoBar::widget() { | |
88 return slide_widget_->widget(); | |
89 } | |
90 | |
91 void InfoBar::AnimateOpen() { | |
92 slide_widget_->Open(); | |
93 | |
94 gtk_widget_show_all(bg_box_); | |
95 if (bg_box_->window) | |
96 gdk_window_lower(bg_box_->window); | |
97 } | |
98 | |
99 void InfoBar::Open() { | |
100 slide_widget_->OpenWithoutAnimation(); | |
101 | |
102 gtk_widget_show_all(bg_box_); | |
103 if (bg_box_->window) | |
104 gdk_window_lower(bg_box_->window); | |
105 } | |
106 | |
107 void InfoBar::AnimateClose() { | |
108 slide_widget_->Close(); | |
109 } | |
110 | |
111 void InfoBar::Close() { | |
112 if (delegate_) { | |
113 delegate_->InfoBarClosed(); | |
114 delegate_ = NULL; | |
115 } | |
116 delete this; | |
117 } | |
118 | |
119 bool InfoBar::IsAnimating() { | |
120 return slide_widget_->IsAnimating(); | |
121 } | |
122 | |
123 bool InfoBar::IsClosing() { | |
124 return slide_widget_->IsClosing(); | |
125 } | |
126 | |
127 void InfoBar::ShowArrowFor(InfoBar* other, bool animate) { | |
128 arrow_model_.ShowArrowFor(other, animate); | |
129 } | |
130 | |
131 void InfoBar::PaintStateChanged() { | |
132 gtk_widget_queue_draw(widget()); | |
133 } | |
134 | |
135 void InfoBar::RemoveInfoBar() const { | |
136 container_->RemoveDelegate(delegate_); | |
137 } | |
138 | |
139 void InfoBar::Closed() { | |
140 Close(); | |
141 } | |
142 | |
143 void InfoBar::SetThemeProvider(GtkThemeProvider* theme_provider) { | |
144 if (theme_provider_) { | |
145 NOTREACHED(); | |
146 return; | |
147 } | |
148 | |
149 theme_provider_ = theme_provider; | |
150 registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, | |
151 NotificationService::AllSources()); | |
152 UpdateBorderColor(); | |
153 } | |
154 | |
155 void InfoBar::Observe(NotificationType type, | |
156 const NotificationSource& source, | |
157 const NotificationDetails& details) { | |
158 UpdateBorderColor(); | |
159 } | |
160 | |
161 void InfoBar::AddLabelWithInlineLink(const string16& display_text, | |
162 const string16& link_text, | |
163 size_t link_offset, | |
164 GCallback callback) { | |
165 GtkWidget* link_button = gtk_chrome_link_button_new( | |
166 UTF16ToUTF8(link_text).c_str()); | |
167 gtk_chrome_link_button_set_use_gtk_theme( | |
168 GTK_CHROME_LINK_BUTTON(link_button), FALSE); | |
169 gtk_util::ForceFontSizePixels( | |
170 GTK_CHROME_LINK_BUTTON(link_button)->label, 13.4); | |
171 DCHECK(callback); | |
172 g_signal_connect(link_button, "clicked", callback, this); | |
173 gtk_util::SetButtonTriggersNavigation(link_button); | |
174 | |
175 GtkWidget* hbox = gtk_hbox_new(FALSE, 0); | |
176 // We want the link to be horizontally shrinkable, so that the Chrome | |
177 // window can be resized freely even with a very long link. | |
178 gtk_widget_set_size_request(hbox, 0, -1); | |
179 gtk_box_pack_start(GTK_BOX(hbox_), hbox, TRUE, TRUE, 0); | |
180 | |
181 // Need to insert the link inside the display text. | |
182 GtkWidget* initial_label = gtk_label_new( | |
183 UTF16ToUTF8(display_text.substr(0, link_offset)).c_str()); | |
184 GtkWidget* trailing_label = gtk_label_new( | |
185 UTF16ToUTF8(display_text.substr(link_offset)).c_str()); | |
186 | |
187 gtk_util::ForceFontSizePixels(initial_label, 13.4); | |
188 gtk_util::ForceFontSizePixels(trailing_label, 13.4); | |
189 | |
190 // TODO(joth): Unlike the AddLabelAndLink below, none of the label widgets | |
191 // are set as shrinkable here, meaning the text will run under the close | |
192 // button etc. when the width is restricted, rather than eliding. | |
193 gtk_widget_modify_fg(initial_label, GTK_STATE_NORMAL, >k_util::kGdkBlack); | |
194 gtk_widget_modify_fg(trailing_label, GTK_STATE_NORMAL, >k_util::kGdkBlack); | |
195 | |
196 // We don't want any spacing between the elements, so we pack them into | |
197 // this hbox that doesn't use kElementPadding. | |
198 gtk_box_pack_start(GTK_BOX(hbox), initial_label, FALSE, FALSE, 0); | |
199 gtk_util::CenterWidgetInHBox(hbox, link_button, false, 0); | |
200 gtk_box_pack_start(GTK_BOX(hbox), trailing_label, FALSE, FALSE, 0); | |
201 } | |
202 | |
203 // TODO(joth): This method factors out some common functionality between the | |
204 // various derived infobar classes, however the class hierarchy itself could | |
205 // use refactoring to reduce this duplication. http://crbug.com/38924 | |
206 void InfoBar::AddLabelAndLink(const string16& display_text, | |
207 const string16& link_text, | |
208 GCallback callback) { | |
209 GtkWidget* link_button = NULL; | |
210 if (!link_text.empty()) { | |
211 // If we have some link text, create the link button. | |
212 link_button = gtk_chrome_link_button_new(UTF16ToUTF8(link_text).c_str()); | |
213 gtk_chrome_link_button_set_use_gtk_theme( | |
214 GTK_CHROME_LINK_BUTTON(link_button), FALSE); | |
215 DCHECK(callback); | |
216 g_signal_connect(link_button, "clicked", callback, this); | |
217 gtk_util::SetButtonTriggersNavigation(link_button); | |
218 } | |
219 | |
220 GtkWidget* hbox = gtk_hbox_new(FALSE, 0); | |
221 // We want the link to be horizontally shrinkable, so that the Chrome | |
222 // window can be resized freely even with a very long link. | |
223 gtk_widget_set_size_request(hbox, 0, -1); | |
224 gtk_box_pack_start(GTK_BOX(hbox_), hbox, TRUE, TRUE, 0); | |
225 | |
226 if (link_button) | |
227 gtk_box_pack_end(GTK_BOX(hbox), link_button, FALSE, FALSE, 0); | |
228 GtkWidget* label = gtk_label_new(UTF16ToUTF8(display_text).c_str()); | |
229 gtk_util::ForceFontSizePixels(label, 13.4); | |
230 // In order to avoid the link_button and the label overlapping with each | |
231 // other, we make the label shrinkable. | |
232 gtk_widget_set_size_request(label, 0, -1); | |
233 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END); | |
234 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); | |
235 gtk_widget_modify_fg(label, GTK_STATE_NORMAL, >k_util::kGdkBlack); | |
236 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); | |
237 } | |
238 | |
239 void InfoBar::GetTopColor(InfoBarDelegate::Type type, | |
240 double* r, double* g, double *b) { | |
241 // These constants are copied from corresponding skia constants from | |
242 // browser/ui/views/infobars/infobars.cc, and then changed into 0-1 ranged | |
243 // values for cairo. | |
244 switch (type) { | |
245 case InfoBarDelegate::WARNING_TYPE: | |
246 *r = 255.0 / 255.0; | |
247 *g = 242.0 / 255.0; | |
248 *b = 183.0 / 255.0; | |
249 break; | |
250 case InfoBarDelegate::PAGE_ACTION_TYPE: | |
251 *r = 218.0 / 255.0; | |
252 *g = 231.0 / 255.0; | |
253 *b = 249.0 / 255.0; | |
254 break; | |
255 } | |
256 } | |
257 | |
258 void InfoBar::GetBottomColor(InfoBarDelegate::Type type, | |
259 double* r, double* g, double *b) { | |
260 switch (type) { | |
261 case InfoBarDelegate::WARNING_TYPE: | |
262 *r = 250.0 / 255.0; | |
263 *g = 230.0 / 255.0; | |
264 *b = 145.0 / 255.0; | |
265 break; | |
266 case InfoBarDelegate::PAGE_ACTION_TYPE: | |
267 *r = 179.0 / 255.0; | |
268 *g = 202.0 / 255.0; | |
269 *b = 231.0 / 255.0; | |
270 break; | |
271 } | |
272 } | |
273 | |
274 void InfoBar::UpdateBorderColor() { | |
275 gtk_widget_queue_draw(widget()); | |
276 } | |
277 | |
278 void InfoBar::OnCloseButton(GtkWidget* button) { | |
279 if (delegate_) | |
280 delegate_->InfoBarDismissed(); | |
281 RemoveInfoBar(); | |
282 } | |
283 | |
284 gboolean InfoBar::OnBackgroundExpose(GtkWidget* sender, | |
285 GdkEventExpose* event) { | |
286 const int height = sender->allocation.height; | |
287 | |
288 cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(sender->window)); | |
289 gdk_cairo_rectangle(cr, &event->area); | |
290 cairo_clip(cr); | |
291 | |
292 cairo_pattern_t* pattern = cairo_pattern_create_linear(0, 0, 0, height); | |
293 | |
294 double top_r, top_g, top_b; | |
295 GetTopColor(delegate_->GetInfoBarType(), &top_r, &top_g, &top_b); | |
296 cairo_pattern_add_color_stop_rgb(pattern, 0.0, top_r, top_g, top_b); | |
297 | |
298 double bottom_r, bottom_g, bottom_b; | |
299 GetBottomColor(delegate_->GetInfoBarType(), &bottom_r, &bottom_g, &bottom_b); | |
300 cairo_pattern_add_color_stop_rgb( | |
301 pattern, 1.0, bottom_r, bottom_g, bottom_b); | |
302 cairo_set_source(cr, pattern); | |
303 cairo_paint(cr); | |
304 cairo_pattern_destroy(pattern); | |
305 | |
306 // Draw the bottom border. | |
307 GdkColor border_color = theme_provider_->GetBorderColor(); | |
308 cairo_set_source_rgb(cr, border_color.red / 65535.0, | |
309 border_color.green / 65535.0, | |
310 border_color.blue / 65535.0); | |
311 cairo_set_line_width(cr, 1.0); | |
312 int y = sender->allocation.height; | |
313 cairo_move_to(cr, 0, y - 0.5); | |
314 cairo_rel_line_to(cr, sender->allocation.width, 0); | |
315 cairo_stroke(cr); | |
316 | |
317 cairo_destroy(cr); | |
318 | |
319 if (!arrow_model_.NeedToDrawInfoBarArrow()) | |
320 return FALSE; | |
321 | |
322 GtkWindow* parent = platform_util::GetTopLevel(widget()); | |
323 BrowserWindowGtk* browser_window = | |
324 BrowserWindowGtk::GetBrowserWindowForNativeWindow(parent); | |
325 int x = browser_window ? | |
326 browser_window->GetXPositionOfLocationIcon(sender) : 0; | |
327 | |
328 arrow_model_.Paint(sender, event, gfx::Point(x, y), border_color); | |
329 | |
330 return FALSE; | |
331 } | |
332 | |
333 // AlertInfoBar ---------------------------------------------------------------- | |
334 | |
335 class AlertInfoBar : public InfoBar { | |
336 public: | |
337 explicit AlertInfoBar(AlertInfoBarDelegate* delegate) | |
338 : InfoBar(delegate) { | |
339 AddLabelAndLink(delegate->GetMessageText(), string16(), NULL); | |
340 } | |
341 }; | |
342 | |
343 // LinkInfoBar ----------------------------------------------------------------- | |
344 | |
345 class LinkInfoBar : public InfoBar { | |
346 public: | |
347 explicit LinkInfoBar(LinkInfoBarDelegate* delegate) | |
348 : InfoBar(delegate) { | |
349 size_t link_offset; | |
350 string16 display_text = delegate->GetMessageTextWithOffset(&link_offset); | |
351 string16 link_text = delegate->GetLinkText(); | |
352 AddLabelWithInlineLink(display_text, link_text, link_offset, | |
353 G_CALLBACK(OnLinkClick)); | |
354 } | |
355 | |
356 private: | |
357 static void OnLinkClick(GtkWidget* button, LinkInfoBar* link_info_bar) { | |
358 if (link_info_bar->delegate_->AsLinkInfoBarDelegate()-> | |
359 LinkClicked(gtk_util::DispositionForCurrentButtonPressEvent())) { | |
360 link_info_bar->RemoveInfoBar(); | |
361 } | |
362 } | |
363 }; | |
364 | |
365 // ConfirmInfoBar -------------------------------------------------------------- | |
366 | |
367 class ConfirmInfoBar : public InfoBar { | |
368 public: | |
369 explicit ConfirmInfoBar(ConfirmInfoBarDelegate* delegate); | |
370 | |
371 private: | |
372 // Adds a button to the info bar by type. It will do nothing if the delegate | |
373 // doesn't specify a button of the given type. | |
374 void AddButton(ConfirmInfoBarDelegate::InfoBarButton type); | |
375 | |
376 CHROMEGTK_CALLBACK_0(ConfirmInfoBar, void, OnOkButton); | |
377 CHROMEGTK_CALLBACK_0(ConfirmInfoBar, void, OnCancelButton); | |
378 CHROMEGTK_CALLBACK_0(ConfirmInfoBar, void, OnLinkClicked); | |
379 | |
380 GtkWidget* confirm_hbox_; | |
381 | |
382 DISALLOW_COPY_AND_ASSIGN(ConfirmInfoBar); | |
383 }; | |
384 | |
385 ConfirmInfoBar::ConfirmInfoBar(ConfirmInfoBarDelegate* delegate) | |
386 : InfoBar(delegate) { | |
387 confirm_hbox_ = gtk_chrome_shrinkable_hbox_new(FALSE, FALSE, 0); | |
388 gtk_box_pack_start(GTK_BOX(hbox_), confirm_hbox_, TRUE, TRUE, 0); | |
389 gtk_widget_set_size_request(confirm_hbox_, 0, -1); | |
390 | |
391 std::string label_text = UTF16ToUTF8(delegate->GetMessageText()); | |
392 GtkWidget* label = gtk_label_new(label_text.c_str()); | |
393 gtk_util::ForceFontSizePixels(label, 13.4); | |
394 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); | |
395 gtk_util::CenterWidgetInHBox(confirm_hbox_, label, false, kEndOfLabelSpacing); | |
396 gtk_widget_modify_fg(label, GTK_STATE_NORMAL, >k_util::kGdkBlack); | |
397 g_signal_connect(label, "map", | |
398 G_CALLBACK(gtk_util::InitLabelSizeRequestAndEllipsizeMode), | |
399 NULL); | |
400 | |
401 AddButton(ConfirmInfoBarDelegate::BUTTON_CANCEL); | |
402 AddButton(ConfirmInfoBarDelegate::BUTTON_OK); | |
403 | |
404 std::string link_text = UTF16ToUTF8(delegate->GetLinkText()); | |
405 GtkWidget* link = gtk_chrome_link_button_new(link_text.c_str()); | |
406 gtk_misc_set_alignment(GTK_MISC(GTK_CHROME_LINK_BUTTON(link)->label), 0, 0.5); | |
407 g_signal_connect(link, "clicked", G_CALLBACK(OnLinkClickedThunk), this); | |
408 gtk_util::SetButtonTriggersNavigation(link); | |
409 // Until we switch to vector graphics, force the font size. | |
410 // 13.4px == 10pt @ 96dpi | |
411 gtk_util::ForceFontSizePixels(GTK_CHROME_LINK_BUTTON(link)->label, 13.4); | |
412 gtk_util::CenterWidgetInHBox(hbox_, link, true, kEndOfLabelSpacing); | |
413 } | |
414 | |
415 void ConfirmInfoBar::AddButton(ConfirmInfoBarDelegate::InfoBarButton type) { | |
416 if (delegate_->AsConfirmInfoBarDelegate()->GetButtons() & type) { | |
417 GtkWidget* button = gtk_button_new_with_label(UTF16ToUTF8( | |
418 delegate_->AsConfirmInfoBarDelegate()->GetButtonLabel(type)).c_str()); | |
419 gtk_util::CenterWidgetInHBox(confirm_hbox_, button, false, | |
420 kButtonButtonSpacing); | |
421 g_signal_connect(button, "clicked", | |
422 G_CALLBACK(type == ConfirmInfoBarDelegate::BUTTON_OK ? | |
423 OnOkButtonThunk : OnCancelButtonThunk), | |
424 this); | |
425 } | |
426 } | |
427 | |
428 void ConfirmInfoBar::OnCancelButton(GtkWidget* widget) { | |
429 if (delegate_->AsConfirmInfoBarDelegate()->Cancel()) | |
430 RemoveInfoBar(); | |
431 } | |
432 | |
433 void ConfirmInfoBar::OnOkButton(GtkWidget* widget) { | |
434 if (delegate_->AsConfirmInfoBarDelegate()->Accept()) | |
435 RemoveInfoBar(); | |
436 } | |
437 | |
438 void ConfirmInfoBar::OnLinkClicked(GtkWidget* widget) { | |
439 if (delegate_->AsConfirmInfoBarDelegate()->LinkClicked( | |
440 gtk_util::DispositionForCurrentButtonPressEvent())) { | |
441 RemoveInfoBar(); | |
442 } | |
443 } | |
444 | |
445 InfoBar* AlertInfoBarDelegate::CreateInfoBar() { | |
446 return new AlertInfoBar(this); | |
447 } | |
448 InfoBar* LinkInfoBarDelegate::CreateInfoBar() { | |
449 return new LinkInfoBar(this); | |
450 } | |
451 InfoBar* ConfirmInfoBarDelegate::CreateInfoBar() { | |
452 return new ConfirmInfoBar(this); | |
453 } | |
OLD | NEW |