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, >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 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