1 // Copyright (c) 2012 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 "base/debug/trace_event.h" 8 #include "base/strings/utf_string_conversions.h" 9 #include "chrome/browser/chrome_notification_types.h" 10 #include "chrome/browser/profiles/profile.h" 11 #include "chrome/browser/themes/theme_properties.h" 12 #include "chrome/browser/ui/gtk/browser_window_gtk.h" 13 #include "chrome/browser/ui/gtk/custom_button.h" 14 #include "chrome/browser/ui/gtk/gtk_chrome_link_button.h" 15 #include "chrome/browser/ui/gtk/gtk_theme_service.h" 16 #include "chrome/browser/ui/gtk/gtk_util.h" 17 #include "chrome/browser/ui/gtk/infobars/infobar_container_gtk.h" 18 #include "content/public/browser/notification_source.h" 19 #include "content/public/browser/web_contents.h" 20 #include "ui/base/gtk/gtk_expanded_container.h" 21 #include "ui/base/gtk/gtk_hig_constants.h" 22 #include "ui/base/gtk/gtk_signal_registrar.h" 23 #include "ui/base/models/menu_model.h" 24 #include "ui/gfx/gtk_util.h" 25 #include "ui/gfx/image/image.h" 26 27 namespace { 28 29 // Pixels between infobar elements. 30 const int kElementPadding = 5; 31 32 // Extra padding on either end of info bar. 33 const int kLeftPadding = 5; 34 const int kRightPadding = 5; 35 36 } // namespace 37 38 39 // InfoBar -------------------------------------------------------------------- 40 41 // static 42 const int InfoBar::kSeparatorLineHeight = 1; 43 const int InfoBar::kDefaultArrowTargetHeight = 9; 44 const int InfoBar::kMaximumArrowTargetHeight = 24; 45 const int InfoBar::kDefaultArrowTargetHalfWidth = kDefaultArrowTargetHeight; 46 const int InfoBar::kMaximumArrowTargetHalfWidth = 14; 47 const int InfoBar::kDefaultBarTargetHeight = 36; 48 49 50 // InfoBarGtk ----------------------------------------------------------------- 51 52 // static 53 const int InfoBarGtk::kEndOfLabelSpacing = 6; 54 55 InfoBarGtk::InfoBarGtk(scoped_ptr<InfoBarDelegate> delegate) 56 : InfoBar(delegate.Pass()), 57 bg_box_(NULL), 58 hbox_(NULL), 59 theme_service_(NULL), 60 signals_(new ui::GtkSignalRegistrar) { 61 } 62 63 InfoBarGtk::~InfoBarGtk() { 64 } 65 66 GdkColor InfoBarGtk::GetBorderColor() const { 67 DCHECK(theme_service_); 68 return theme_service_->GetBorderColor(); 69 } 70 71 int InfoBarGtk::AnimatingHeight() const { 72 return animation().is_animating() ? bar_target_height() : 0; 73 } 74 75 SkColor InfoBarGtk::ConvertGetColor(ColorGetter getter) { 76 double r, g, b; 77 (this->*getter)(delegate()->GetInfoBarType(), &r, &g, &b); 78 return SkColorSetARGB(255, 255 * r, 255 * g, 255 * b); 79 } 80 81 void InfoBarGtk::GetTopColor(InfoBarDelegate::Type type, 82 double* r, double* g, double* b) { 83 GetBackgroundColor(InfoBar::GetTopColor(type), r, g, b); 84 } 85 86 void InfoBarGtk::GetBottomColor(InfoBarDelegate::Type type, 87 double* r, double* g, double* b) { 88 GetBackgroundColor(InfoBar::GetBottomColor(type), r, g, b); 89 } 90 91 void InfoBarGtk::PlatformSpecificSetOwner() { 92 DCHECK(owner()); 93 DCHECK(!theme_service_); 94 theme_service_ = GtkThemeService::GetFrom(Profile::FromBrowserContext( 95 owner()->web_contents()->GetBrowserContext())); 96 97 // Create |hbox_| and pad the sides. 98 hbox_ = gtk_hbox_new(FALSE, kElementPadding); 99 100 // Make the whole infor bar horizontally shrinkable. 101 gtk_widget_set_size_request(hbox_, 0, -1); 102 103 GtkWidget* padding = gtk_alignment_new(0, 0, 1, 1); 104 gtk_alignment_set_padding(GTK_ALIGNMENT(padding), 105 0, 0, kLeftPadding, kRightPadding); 106 107 bg_box_ = gtk_event_box_new(); 108 gtk_widget_set_app_paintable(bg_box_, TRUE); 109 g_signal_connect(bg_box_, "expose-event", 110 G_CALLBACK(OnBackgroundExposeThunk), this); 111 gtk_container_add(GTK_CONTAINER(padding), hbox_); 112 gtk_container_add(GTK_CONTAINER(bg_box_), padding); 113 114 // Add the icon on the left, if any. 115 gfx::Image icon = delegate()->GetIcon(); 116 if (!icon.IsEmpty()) { 117 GtkWidget* image = gtk_image_new_from_pixbuf(icon.ToGdkPixbuf()); 118 119 gtk_misc_set_alignment(GTK_MISC(image), 0.5, 0.5); 120 121 gtk_box_pack_start(GTK_BOX(hbox_), image, FALSE, FALSE, 0); 122 } 123 124 close_button_.reset(CustomDrawButton::CloseButtonBar(theme_service_)); 125 gtk_util::CenterWidgetInHBox(hbox_, close_button_->widget(), true, 0); 126 signals_->Connect(close_button_->widget(), "clicked", 127 G_CALLBACK(OnCloseButtonThunk), this); 128 129 widget_.Own(gtk_expanded_container_new()); 130 gtk_container_add(GTK_CONTAINER(widget_.get()), bg_box_); 131 gtk_widget_set_size_request(widget_.get(), -1, 0); 132 133 g_signal_connect(widget_.get(), "child-size-request", 134 G_CALLBACK(OnChildSizeRequestThunk), 135 this); 136 137 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED, 138 content::Source<ThemeService>(theme_service_)); 139 UpdateBorderColor(); 140 } 141 142 void InfoBarGtk::PlatformSpecificShow(bool animate) { 143 DCHECK(bg_box_); 144 145 DCHECK(widget()); 146 gtk_widget_show_all(widget()); 147 gtk_widget_set_size_request(widget(), -1, bar_height()); 148 149 GdkWindow* gdk_window = gtk_widget_get_window(bg_box_); 150 if (gdk_window) 151 gdk_window_lower(gdk_window); 152 } 153 154 void InfoBarGtk::PlatformSpecificOnCloseSoon() { 155 // We must close all menus and prevent any signals from being emitted while 156 // we are animating the info bar closed. 157 menu_.reset(); 158 signals_.reset(); 159 } 160 161 void InfoBarGtk::PlatformSpecificOnHeightsRecalculated() { 162 DCHECK(bg_box_); 163 DCHECK(widget()); 164 gtk_widget_set_size_request(bg_box_, -1, bar_target_height()); 165 gtk_expanded_container_move(GTK_EXPANDED_CONTAINER(widget()), 166 bg_box_, 0, 167 bar_height() - bar_target_height()); 168 169 gtk_widget_set_size_request(widget(), -1, bar_height()); 170 gtk_widget_queue_draw(widget()); 171 } 172 173 void InfoBarGtk::Observe(int type, 174 const content::NotificationSource& source, 175 const content::NotificationDetails& details) { 176 DCHECK(widget()); 177 UpdateBorderColor(); 178 } 179 180 void InfoBarGtk::ForceCloseButtonToUseChromeTheme() { 181 close_button_->ForceChromeTheme(); 182 } 183 184 GtkWidget* InfoBarGtk::CreateLabel(const std::string& text) { 185 DCHECK(theme_service_); 186 return theme_service_->BuildLabel(text, ui::kGdkBlack); 187 } 188 189 GtkWidget* InfoBarGtk::CreateLinkButton(const std::string& text) { 190 DCHECK(theme_service_); 191 return theme_service_->BuildChromeLinkButton(text); 192 } 193 194 // static 195 GtkWidget* InfoBarGtk::CreateMenuButton(const std::string& text) { 196 GtkWidget* button = gtk_button_new(); 197 GtkWidget* former_child = gtk_bin_get_child(GTK_BIN(button)); 198 if (former_child) 199 gtk_container_remove(GTK_CONTAINER(button), former_child); 200 201 GtkWidget* hbox = gtk_hbox_new(FALSE, 0); 202 203 GtkWidget* label = gtk_label_new(text.c_str()); 204 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); 205 206 GtkWidget* arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE); 207 gtk_box_pack_start(GTK_BOX(hbox), arrow, FALSE, FALSE, 0); 208 209 gtk_container_add(GTK_CONTAINER(button), hbox); 210 211 return button; 212 } 213 214 void InfoBarGtk::AddLabelWithInlineLink(const base::string16& display_text, 215 const base::string16& link_text, 216 size_t link_offset, 217 GCallback callback) { 218 DCHECK(hbox_); 219 GtkWidget* link_button = CreateLinkButton(UTF16ToUTF8(link_text)); 220 gtk_util::ForceFontSizePixels( 221 GTK_CHROME_LINK_BUTTON(link_button)->label, 13.4); 222 DCHECK(callback); 223 signals_->Connect(link_button, "clicked", callback, this); 224 gtk_util::SetButtonTriggersNavigation(link_button); 225 226 GtkWidget* hbox = gtk_hbox_new(FALSE, 0); 227 // We want the link to be horizontally shrinkable, so that the Chrome 228 // window can be resized freely even with a very long link. 229 gtk_widget_set_size_request(hbox, 0, -1); 230 gtk_box_pack_start(GTK_BOX(hbox_), hbox, TRUE, TRUE, 0); 231 232 // Need to insert the link inside the display text. 233 GtkWidget* initial_label = CreateLabel( 234 UTF16ToUTF8(display_text.substr(0, link_offset))); 235 GtkWidget* trailing_label = CreateLabel( 236 UTF16ToUTF8(display_text.substr(link_offset))); 237 238 gtk_util::ForceFontSizePixels(initial_label, 13.4); 239 gtk_util::ForceFontSizePixels(trailing_label, 13.4); 240 241 // TODO(joth): None of the label widgets are set as shrinkable here, meaning 242 // the text will run under the close button etc. when the width is restricted, 243 // rather than eliding. 244 245 // We don't want any spacing between the elements, so we pack them into 246 // this hbox that doesn't use kElementPadding. 247 gtk_box_pack_start(GTK_BOX(hbox), initial_label, FALSE, FALSE, 0); 248 gtk_util::CenterWidgetInHBox(hbox, link_button, false, 0); 249 gtk_box_pack_start(GTK_BOX(hbox), trailing_label, FALSE, FALSE, 0); 250 } 251 252 void InfoBarGtk::ShowMenuWithModel(GtkWidget* sender, 253 MenuGtk::Delegate* delegate, 254 ui::MenuModel* model) { 255 menu_.reset(new MenuGtk(delegate, model)); 256 menu_->PopupForWidget(sender, 1, gtk_get_current_event_time()); 257 } 258 259 void InfoBarGtk::GetBackgroundColor(SkColor color, 260 double* r, double* g, double* b) { 261 DCHECK(theme_service_); 262 if (theme_service_->UsingNativeTheme()) 263 color = theme_service_->GetColor(ThemeProperties::COLOR_TOOLBAR); 264 *r = SkColorGetR(color) / 255.0; 265 *g = SkColorGetG(color) / 255.0; 266 *b = SkColorGetB(color) / 255.0; 267 } 268 269 void InfoBarGtk::UpdateBorderColor() { 270 DCHECK(widget()); 271 gtk_widget_queue_draw(widget()); 272 } 273 274 void InfoBarGtk::OnCloseButton(GtkWidget* button) { 275 // If we're not owned, we're already closing, so don't call 276 // InfoBarDismissed(), since this can lead to us double-recording dismissals. 277 if (owner()) 278 delegate()->InfoBarDismissed(); 279 RemoveSelf(); 280 } 281 282 gboolean InfoBarGtk::OnBackgroundExpose(GtkWidget* sender, 283 GdkEventExpose* event) { 284 TRACE_EVENT0("ui::gtk", "InfoBarGtk::OnBackgroundExpose"); 285 DCHECK(theme_service_); 286 287 GtkAllocation allocation; 288 gtk_widget_get_allocation(sender, &allocation); 289 const int height = allocation.height; 290 291 cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(sender)); 292 gdk_cairo_rectangle(cr, &event->area); 293 cairo_clip(cr); 294 295 cairo_pattern_t* pattern = cairo_pattern_create_linear(0, 0, 0, height); 296 297 double top_r, top_g, top_b; 298 GetTopColor(delegate()->GetInfoBarType(), &top_r, &top_g, &top_b); 299 cairo_pattern_add_color_stop_rgb(pattern, 0.0, top_r, top_g, top_b); 300 301 double bottom_r, bottom_g, bottom_b; 302 GetBottomColor(delegate()->GetInfoBarType(), &bottom_r, &bottom_g, &bottom_b); 303 cairo_pattern_add_color_stop_rgb( 304 pattern, 1.0, bottom_r, bottom_g, bottom_b); 305 cairo_set_source(cr, pattern); 306 cairo_paint(cr); 307 cairo_pattern_destroy(pattern); 308 309 // Draw the bottom border. 310 GdkColor border_color = GetBorderColor(); 311 cairo_set_source_rgb(cr, border_color.red / 65535.0, 312 border_color.green / 65535.0, 313 border_color.blue / 65535.0); 314 cairo_set_line_width(cr, 1.0); 315 cairo_move_to(cr, 0, allocation.height - 0.5); 316 cairo_rel_line_to(cr, allocation.width, 0); 317 cairo_stroke(cr); 318 319 cairo_destroy(cr); 320 321 if (container()) { 322 static_cast<InfoBarContainerGtk*>(container())-> 323 PaintInfobarBitsOn(sender, event, this); 324 } 325 326 return FALSE; 327 } 328 329 void InfoBarGtk::OnChildSizeRequest(GtkWidget* expanded, 330 GtkWidget* child, 331 GtkRequisition* requisition) { 332 requisition->height = -1; 333 } 334