| OLD | NEW |
| (Empty) |
| 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/notifications/balloon_view_gtk.h" | |
| 6 | |
| 7 #include <gtk/gtk.h> | |
| 8 | |
| 9 #include <string> | |
| 10 #include <vector> | |
| 11 | |
| 12 #include "base/bind.h" | |
| 13 #include "base/debug/trace_event.h" | |
| 14 #include "base/message_loop/message_loop.h" | |
| 15 #include "base/strings/string_util.h" | |
| 16 #include "chrome/browser/chrome_notification_types.h" | |
| 17 #include "chrome/browser/notifications/balloon.h" | |
| 18 #include "chrome/browser/notifications/desktop_notification_service.h" | |
| 19 #include "chrome/browser/notifications/notification.h" | |
| 20 #include "chrome/browser/notifications/notification_options_menu_model.h" | |
| 21 #include "chrome/browser/profiles/profile.h" | |
| 22 #include "chrome/browser/themes/theme_service.h" | |
| 23 #include "chrome/browser/ui/browser_list.h" | |
| 24 #include "chrome/browser/ui/browser_window.h" | |
| 25 #include "chrome/browser/ui/gtk/custom_button.h" | |
| 26 #include "chrome/browser/ui/gtk/gtk_theme_service.h" | |
| 27 #include "chrome/browser/ui/gtk/gtk_util.h" | |
| 28 #include "chrome/browser/ui/gtk/menu_gtk.h" | |
| 29 #include "chrome/browser/ui/gtk/notifications/balloon_view_host_gtk.h" | |
| 30 #include "chrome/browser/ui/gtk/rounded_window.h" | |
| 31 #include "content/public/browser/notification_source.h" | |
| 32 #include "content/public/browser/render_view_host.h" | |
| 33 #include "content/public/browser/render_widget_host_view.h" | |
| 34 #include "content/public/browser/web_contents.h" | |
| 35 #include "extensions/browser/extension_host.h" | |
| 36 #include "extensions/browser/process_manager.h" | |
| 37 #include "extensions/common/extension.h" | |
| 38 #include "grit/generated_resources.h" | |
| 39 #include "grit/theme_resources.h" | |
| 40 #include "ui/base/gtk/gtk_hig_constants.h" | |
| 41 #include "ui/base/l10n/l10n_util.h" | |
| 42 #include "ui/base/resource/resource_bundle.h" | |
| 43 #include "ui/gfx/animation/slide_animation.h" | |
| 44 #include "ui/gfx/canvas.h" | |
| 45 #include "ui/gfx/insets.h" | |
| 46 #include "ui/gfx/native_widget_types.h" | |
| 47 | |
| 48 namespace { | |
| 49 | |
| 50 // Margin, in pixels, between the notification frame and the contents | |
| 51 // of the notification. | |
| 52 const int kTopMargin = 0; | |
| 53 const int kBottomMargin = 1; | |
| 54 const int kLeftMargin = 1; | |
| 55 const int kRightMargin = 1; | |
| 56 | |
| 57 // Properties of the origin label. | |
| 58 const int kLeftLabelMargin = 8; | |
| 59 | |
| 60 // TODO(johnnyg): Add a shadow for the frame. | |
| 61 const int kLeftShadowWidth = 0; | |
| 62 const int kRightShadowWidth = 0; | |
| 63 const int kTopShadowWidth = 0; | |
| 64 const int kBottomShadowWidth = 0; | |
| 65 | |
| 66 // Space in pixels between text and icon on the buttons. | |
| 67 const int kButtonSpacing = 3; | |
| 68 | |
| 69 // Number of characters to show in the origin label before ellipsis. | |
| 70 const int kOriginLabelCharacters = 18; | |
| 71 | |
| 72 // The shelf height for the system default font size. It is scaled | |
| 73 // with changes in the default font size. | |
| 74 const int kDefaultShelfHeight = 25; | |
| 75 | |
| 76 // The amount that the bubble collections class offsets from the side of the | |
| 77 // screen. | |
| 78 const int kScreenBorder = 5; | |
| 79 | |
| 80 // Colors specified in various ways for different parts of the UI. | |
| 81 // These match the windows colors in balloon_view.cc | |
| 82 const char* kLabelColor = "#7D7D7D"; | |
| 83 const double kShelfBackgroundColorR = 245.0 / 255.0; | |
| 84 const double kShelfBackgroundColorG = 245.0 / 255.0; | |
| 85 const double kShelfBackgroundColorB = 245.0 / 255.0; | |
| 86 const double kDividerLineColorR = 180.0 / 255.0; | |
| 87 const double kDividerLineColorG = 180.0 / 255.0; | |
| 88 const double kDividerLineColorB = 180.0 / 255.0; | |
| 89 | |
| 90 // Makes the website label relatively smaller to the base text size. | |
| 91 const char* kLabelMarkup = "<span size=\"small\" color=\"%s\">%s</span>"; | |
| 92 | |
| 93 } // namespace | |
| 94 | |
| 95 BalloonViewImpl::BalloonViewImpl(BalloonCollection* collection) | |
| 96 : balloon_(NULL), | |
| 97 theme_service_(NULL), | |
| 98 frame_container_(NULL), | |
| 99 shelf_(NULL), | |
| 100 hbox_(NULL), | |
| 101 html_container_(NULL), | |
| 102 menu_showing_(false), | |
| 103 pending_close_(false), | |
| 104 weak_factory_(this) {} | |
| 105 | |
| 106 BalloonViewImpl::~BalloonViewImpl() { | |
| 107 if (frame_container_) { | |
| 108 GtkWidget* widget = frame_container_; | |
| 109 frame_container_ = NULL; | |
| 110 gtk_widget_hide(widget); | |
| 111 } | |
| 112 } | |
| 113 | |
| 114 void BalloonViewImpl::Close(bool by_user) { | |
| 115 // Delay a system-initiated close if the menu is showing. | |
| 116 if (!by_user && menu_showing_) { | |
| 117 pending_close_ = true; | |
| 118 } else { | |
| 119 base::MessageLoop::current()->PostTask( | |
| 120 FROM_HERE, | |
| 121 base::Bind(&BalloonViewImpl::DelayedClose, | |
| 122 weak_factory_.GetWeakPtr(), | |
| 123 by_user)); | |
| 124 } | |
| 125 } | |
| 126 | |
| 127 gfx::Size BalloonViewImpl::GetSize() const { | |
| 128 // BalloonView has no size if it hasn't been shown yet (which is when | |
| 129 // balloon_ is set). | |
| 130 if (!balloon_) | |
| 131 return gfx::Size(); | |
| 132 | |
| 133 // Although this may not be the instantaneous size of the balloon if | |
| 134 // called in the middle of an animation, it is the effective size that | |
| 135 // will result from the animation. | |
| 136 return gfx::Size(GetDesiredTotalWidth(), GetDesiredTotalHeight()); | |
| 137 } | |
| 138 | |
| 139 BalloonHost* BalloonViewImpl::GetHost() const { | |
| 140 return html_contents_.get(); | |
| 141 } | |
| 142 | |
| 143 void BalloonViewImpl::DelayedClose(bool by_user) { | |
| 144 html_contents_->Shutdown(); | |
| 145 if (frame_container_) { | |
| 146 // It's possible that |frame_container_| was destroyed before the | |
| 147 // BalloonViewImpl if our related browser window was closed first. | |
| 148 gtk_widget_hide(frame_container_); | |
| 149 } | |
| 150 balloon_->OnClose(by_user); | |
| 151 } | |
| 152 | |
| 153 void BalloonViewImpl::RepositionToBalloon() { | |
| 154 if (!frame_container_) { | |
| 155 // No need to create a slide animation when this balloon is fading out. | |
| 156 return; | |
| 157 } | |
| 158 | |
| 159 DCHECK(balloon_); | |
| 160 | |
| 161 // Create an amination from the current position to the desired one. | |
| 162 int start_x; | |
| 163 int start_y; | |
| 164 int start_w; | |
| 165 int start_h; | |
| 166 gtk_window_get_position(GTK_WINDOW(frame_container_), &start_x, &start_y); | |
| 167 gtk_window_get_size(GTK_WINDOW(frame_container_), &start_w, &start_h); | |
| 168 | |
| 169 int end_x = balloon_->GetPosition().x(); | |
| 170 int end_y = balloon_->GetPosition().y(); | |
| 171 int end_w = GetDesiredTotalWidth(); | |
| 172 int end_h = GetDesiredTotalHeight(); | |
| 173 | |
| 174 anim_frame_start_ = gfx::Rect(start_x, start_y, start_w, start_h); | |
| 175 anim_frame_end_ = gfx::Rect(end_x, end_y, end_w, end_h); | |
| 176 animation_.reset(new gfx::SlideAnimation(this)); | |
| 177 animation_->Show(); | |
| 178 } | |
| 179 | |
| 180 void BalloonViewImpl::AnimationProgressed(const gfx::Animation* animation) { | |
| 181 DCHECK_EQ(animation, animation_.get()); | |
| 182 | |
| 183 // Linear interpolation from start to end position. | |
| 184 double end = animation->GetCurrentValue(); | |
| 185 double start = 1.0 - end; | |
| 186 | |
| 187 gfx::Rect frame_position( | |
| 188 static_cast<int>(start * anim_frame_start_.x() + | |
| 189 end * anim_frame_end_.x()), | |
| 190 static_cast<int>(start * anim_frame_start_.y() + | |
| 191 end * anim_frame_end_.y()), | |
| 192 static_cast<int>(start * anim_frame_start_.width() + | |
| 193 end * anim_frame_end_.width()), | |
| 194 static_cast<int>(start * anim_frame_start_.height() + | |
| 195 end * anim_frame_end_.height())); | |
| 196 gtk_window_resize(GTK_WINDOW(frame_container_), | |
| 197 frame_position.width(), frame_position.height()); | |
| 198 gtk_window_move(GTK_WINDOW(frame_container_), | |
| 199 frame_position.x(), frame_position.y()); | |
| 200 | |
| 201 gfx::Rect contents_rect = GetContentsRectangle(); | |
| 202 html_contents_->UpdateActualSize(contents_rect.size()); | |
| 203 } | |
| 204 | |
| 205 void BalloonViewImpl::Show(Balloon* balloon) { | |
| 206 theme_service_ = GtkThemeService::GetFrom(balloon->profile()); | |
| 207 | |
| 208 const std::string source_label_text = l10n_util::GetStringFUTF8( | |
| 209 IDS_NOTIFICATION_BALLOON_SOURCE_LABEL, | |
| 210 balloon->notification().display_source()); | |
| 211 const std::string options_text = | |
| 212 l10n_util::GetStringUTF8(IDS_NOTIFICATION_OPTIONS_MENU_LABEL); | |
| 213 const std::string dismiss_text = | |
| 214 l10n_util::GetStringUTF8(IDS_NOTIFICATION_BALLOON_DISMISS_LABEL); | |
| 215 | |
| 216 balloon_ = balloon; | |
| 217 frame_container_ = gtk_window_new(GTK_WINDOW_POPUP); | |
| 218 | |
| 219 g_signal_connect(frame_container_, "expose-event", | |
| 220 G_CALLBACK(OnExposeThunk), this); | |
| 221 g_signal_connect(frame_container_, "destroy", | |
| 222 G_CALLBACK(OnDestroyThunk), this); | |
| 223 | |
| 224 // Construct the options menu. | |
| 225 options_menu_model_.reset(new NotificationOptionsMenuModel(balloon_)); | |
| 226 options_menu_.reset(new MenuGtk(this, options_menu_model_.get())); | |
| 227 | |
| 228 // Create a BalloonViewHost to host the HTML contents of this balloon. | |
| 229 html_contents_.reset(new BalloonViewHost(balloon)); | |
| 230 html_contents_->Init(); | |
| 231 gfx::NativeView contents = html_contents_->native_view(); | |
| 232 g_signal_connect_after(contents, "expose-event", | |
| 233 G_CALLBACK(OnContentsExposeThunk), this); | |
| 234 | |
| 235 // Divide the frame vertically into the shelf and the content area. | |
| 236 GtkWidget* vbox = gtk_vbox_new(0, 0); | |
| 237 gtk_container_add(GTK_CONTAINER(frame_container_), vbox); | |
| 238 | |
| 239 // Create the toolbar. | |
| 240 shelf_ = gtk_hbox_new(FALSE, 0); | |
| 241 gtk_widget_set_size_request(GTK_WIDGET(shelf_), -1, GetShelfHeight()); | |
| 242 gtk_container_add(GTK_CONTAINER(vbox), shelf_); | |
| 243 | |
| 244 // Create a label for the source of the notification and add it to the | |
| 245 // toolbar. | |
| 246 GtkWidget* source_label_ = gtk_label_new(NULL); | |
| 247 char* markup = g_markup_printf_escaped(kLabelMarkup, | |
| 248 kLabelColor, | |
| 249 source_label_text.c_str()); | |
| 250 gtk_label_set_markup(GTK_LABEL(source_label_), markup); | |
| 251 g_free(markup); | |
| 252 gtk_label_set_max_width_chars(GTK_LABEL(source_label_), | |
| 253 kOriginLabelCharacters); | |
| 254 gtk_label_set_ellipsize(GTK_LABEL(source_label_), PANGO_ELLIPSIZE_END); | |
| 255 GtkWidget* label_alignment = gtk_alignment_new(0, 0.5, 0, 0); | |
| 256 gtk_alignment_set_padding(GTK_ALIGNMENT(label_alignment), | |
| 257 0, 0, kLeftLabelMargin, 0); | |
| 258 gtk_container_add(GTK_CONTAINER(label_alignment), source_label_); | |
| 259 gtk_box_pack_start(GTK_BOX(shelf_), label_alignment, FALSE, FALSE, 0); | |
| 260 | |
| 261 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
| 262 | |
| 263 // Create a button to dismiss the balloon and add it to the toolbar. | |
| 264 close_button_.reset(CustomDrawButton::CloseButtonBar(theme_service_)); | |
| 265 close_button_->SetBackground( | |
| 266 SK_ColorBLACK, | |
| 267 rb.GetImageNamed(IDR_CLOSE_1).AsBitmap(), | |
| 268 rb.GetImageNamed(IDR_CLOSE_1_MASK).AsBitmap()); | |
| 269 gtk_widget_set_tooltip_text(close_button_->widget(), dismiss_text.c_str()); | |
| 270 g_signal_connect(close_button_->widget(), "clicked", | |
| 271 G_CALLBACK(OnCloseButtonThunk), this); | |
| 272 gtk_widget_set_can_focus(close_button_->widget(), FALSE); | |
| 273 GtkWidget* close_alignment = gtk_alignment_new(0.0, 0.5, 0, 0); | |
| 274 gtk_container_add(GTK_CONTAINER(close_alignment), close_button_->widget()); | |
| 275 gtk_box_pack_end(GTK_BOX(shelf_), close_alignment, FALSE, FALSE, | |
| 276 kButtonSpacing); | |
| 277 | |
| 278 // Create a button for showing the options menu, and add it to the toolbar. | |
| 279 options_menu_button_.reset(new CustomDrawButton(IDR_BALLOON_WRENCH, | |
| 280 IDR_BALLOON_WRENCH_P, | |
| 281 IDR_BALLOON_WRENCH_H, | |
| 282 0)); | |
| 283 gtk_widget_set_tooltip_text(options_menu_button_->widget(), | |
| 284 options_text.c_str()); | |
| 285 g_signal_connect(options_menu_button_->widget(), "button-press-event", | |
| 286 G_CALLBACK(OnOptionsMenuButtonThunk), this); | |
| 287 gtk_widget_set_can_focus(options_menu_button_->widget(), FALSE); | |
| 288 GtkWidget* options_alignment = gtk_alignment_new(0.0, 0.5, 0, 0); | |
| 289 gtk_container_add(GTK_CONTAINER(options_alignment), | |
| 290 options_menu_button_->widget()); | |
| 291 gtk_box_pack_end(GTK_BOX(shelf_), options_alignment, FALSE, FALSE, 0); | |
| 292 | |
| 293 // Add main contents to bubble. | |
| 294 GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); | |
| 295 gtk_alignment_set_padding( | |
| 296 GTK_ALIGNMENT(alignment), | |
| 297 kTopMargin, kBottomMargin, kLeftMargin, kRightMargin); | |
| 298 gtk_widget_show_all(alignment); | |
| 299 gtk_container_add(GTK_CONTAINER(alignment), contents); | |
| 300 gtk_container_add(GTK_CONTAINER(vbox), alignment); | |
| 301 gtk_widget_show_all(vbox); | |
| 302 | |
| 303 notification_registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED, | |
| 304 content::Source<ThemeService>(theme_service_)); | |
| 305 | |
| 306 // We don't do InitThemesFor() because it just forces a redraw. | |
| 307 gtk_util::ActAsRoundedWindow(frame_container_, ui::kGdkBlack, 3, | |
| 308 gtk_util::ROUNDED_ALL, | |
| 309 gtk_util::BORDER_ALL); | |
| 310 | |
| 311 // Realize the frame container so we can do size calculations. | |
| 312 gtk_widget_realize(frame_container_); | |
| 313 | |
| 314 // Update to make sure we have everything sized properly and then move our | |
| 315 // window offscreen for its initial animation. | |
| 316 html_contents_->UpdateActualSize(balloon_->content_size()); | |
| 317 int window_width; | |
| 318 gtk_window_get_size(GTK_WINDOW(frame_container_), &window_width, NULL); | |
| 319 | |
| 320 int pos_x = gdk_screen_width() - window_width - kScreenBorder; | |
| 321 int pos_y = gdk_screen_height(); | |
| 322 gtk_window_move(GTK_WINDOW(frame_container_), pos_x, pos_y); | |
| 323 balloon_->SetPosition(gfx::Point(pos_x, pos_y), false); | |
| 324 gtk_widget_show_all(frame_container_); | |
| 325 | |
| 326 notification_registrar_.Add(this, | |
| 327 chrome::NOTIFICATION_NOTIFY_BALLOON_DISCONNECTED, | |
| 328 content::Source<Balloon>(balloon)); | |
| 329 } | |
| 330 | |
| 331 void BalloonViewImpl::Update() { | |
| 332 DCHECK(html_contents_.get()) << "BalloonView::Update called before Show"; | |
| 333 if (!html_contents_->web_contents()) | |
| 334 return; | |
| 335 html_contents_->web_contents()->GetController().LoadURL( | |
| 336 balloon_->notification().content_url(), content::Referrer(), | |
| 337 content::PAGE_TRANSITION_LINK, std::string()); | |
| 338 } | |
| 339 | |
| 340 gfx::Point BalloonViewImpl::GetContentsOffset() const { | |
| 341 return gfx::Point(kLeftShadowWidth + kLeftMargin, | |
| 342 GetShelfHeight() + kTopShadowWidth + kTopMargin); | |
| 343 } | |
| 344 | |
| 345 int BalloonViewImpl::GetShelfHeight() const { | |
| 346 // TODO(johnnyg): add scaling here. | |
| 347 return kDefaultShelfHeight; | |
| 348 } | |
| 349 | |
| 350 int BalloonViewImpl::GetDesiredTotalWidth() const { | |
| 351 return balloon_->content_size().width() + | |
| 352 kLeftMargin + kRightMargin + kLeftShadowWidth + kRightShadowWidth; | |
| 353 } | |
| 354 | |
| 355 int BalloonViewImpl::GetDesiredTotalHeight() const { | |
| 356 return balloon_->content_size().height() + | |
| 357 kTopMargin + kBottomMargin + kTopShadowWidth + kBottomShadowWidth + | |
| 358 GetShelfHeight(); | |
| 359 } | |
| 360 | |
| 361 gfx::Rect BalloonViewImpl::GetContentsRectangle() const { | |
| 362 if (!frame_container_) | |
| 363 return gfx::Rect(); | |
| 364 | |
| 365 gfx::Size content_size = balloon_->content_size(); | |
| 366 gfx::Point offset = GetContentsOffset(); | |
| 367 int x = 0, y = 0; | |
| 368 gtk_window_get_position(GTK_WINDOW(frame_container_), &x, &y); | |
| 369 return gfx::Rect(x + offset.x(), y + offset.y(), | |
| 370 content_size.width(), content_size.height()); | |
| 371 } | |
| 372 | |
| 373 void BalloonViewImpl::Observe(int type, | |
| 374 const content::NotificationSource& source, | |
| 375 const content::NotificationDetails& details) { | |
| 376 if (type == chrome::NOTIFICATION_NOTIFY_BALLOON_DISCONNECTED) { | |
| 377 // If the renderer process attached to this balloon is disconnected | |
| 378 // (e.g., because of a crash), we want to close the balloon. | |
| 379 notification_registrar_.Remove(this, | |
| 380 chrome::NOTIFICATION_NOTIFY_BALLOON_DISCONNECTED, | |
| 381 content::Source<Balloon>(balloon_)); | |
| 382 Close(false); | |
| 383 } else if (type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED) { | |
| 384 // Since all the buttons change their own properties, and our expose does | |
| 385 // all the real differences, we'll need a redraw. | |
| 386 gtk_widget_queue_draw(frame_container_); | |
| 387 } else { | |
| 388 NOTREACHED(); | |
| 389 } | |
| 390 } | |
| 391 | |
| 392 void BalloonViewImpl::OnCloseButton(GtkWidget* widget) { | |
| 393 Close(true); | |
| 394 } | |
| 395 | |
| 396 // We draw black dots on the bottom left and right corners to fill in the | |
| 397 // border. Otherwise, the border has a gap because the sharp corners of the | |
| 398 // HTML view cut off the roundedness of the notification window. | |
| 399 gboolean BalloonViewImpl::OnContentsExpose(GtkWidget* sender, | |
| 400 GdkEventExpose* event) { | |
| 401 TRACE_EVENT0("ui::gtk", "BalloonViewImpl::OnContentsExpose"); | |
| 402 cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(sender)); | |
| 403 gdk_cairo_rectangle(cr, &event->area); | |
| 404 cairo_clip(cr); | |
| 405 | |
| 406 GtkAllocation allocation; | |
| 407 gtk_widget_get_allocation(sender, &allocation); | |
| 408 | |
| 409 // According to a discussion on a mailing list I found, these degenerate | |
| 410 // paths are the officially supported way to draw points in Cairo. | |
| 411 cairo_set_source_rgb(cr, 0, 0, 0); | |
| 412 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); | |
| 413 cairo_set_line_width(cr, 1.0); | |
| 414 cairo_move_to(cr, 0.5, allocation.height - 0.5); | |
| 415 cairo_close_path(cr); | |
| 416 cairo_move_to(cr, allocation.width - 0.5, allocation.height - 0.5); | |
| 417 cairo_close_path(cr); | |
| 418 cairo_stroke(cr); | |
| 419 cairo_destroy(cr); | |
| 420 | |
| 421 return FALSE; | |
| 422 } | |
| 423 | |
| 424 gboolean BalloonViewImpl::OnExpose(GtkWidget* sender, GdkEventExpose* event) { | |
| 425 TRACE_EVENT0("ui::gtk", "BalloonViewImpl::OnExpose"); | |
| 426 cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(sender)); | |
| 427 gdk_cairo_rectangle(cr, &event->area); | |
| 428 cairo_clip(cr); | |
| 429 | |
| 430 gfx::Size content_size = balloon_->content_size(); | |
| 431 gfx::Point offset = GetContentsOffset(); | |
| 432 | |
| 433 // Draw a background color behind the shelf. | |
| 434 cairo_set_source_rgb(cr, kShelfBackgroundColorR, | |
| 435 kShelfBackgroundColorG, kShelfBackgroundColorB); | |
| 436 cairo_rectangle(cr, kLeftMargin, kTopMargin + 0.5, | |
| 437 content_size.width() - 0.5, GetShelfHeight()); | |
| 438 cairo_fill(cr); | |
| 439 | |
| 440 // Now draw a one pixel line between content and shelf. | |
| 441 cairo_move_to(cr, offset.x(), offset.y() - 1); | |
| 442 cairo_line_to(cr, offset.x() + content_size.width(), offset.y() - 1); | |
| 443 cairo_set_line_width(cr, 0.5); | |
| 444 cairo_set_source_rgb(cr, kDividerLineColorR, | |
| 445 kDividerLineColorG, kDividerLineColorB); | |
| 446 cairo_stroke(cr); | |
| 447 | |
| 448 cairo_destroy(cr); | |
| 449 | |
| 450 return FALSE; | |
| 451 } | |
| 452 | |
| 453 void BalloonViewImpl::OnOptionsMenuButton(GtkWidget* widget, | |
| 454 GdkEventButton* event) { | |
| 455 menu_showing_ = true; | |
| 456 options_menu_->PopupForWidget(widget, event->button, event->time); | |
| 457 } | |
| 458 | |
| 459 // Called when the menu stops showing. | |
| 460 void BalloonViewImpl::StoppedShowing() { | |
| 461 menu_showing_ = false; | |
| 462 if (pending_close_) { | |
| 463 base::MessageLoop::current()->PostTask( | |
| 464 FROM_HERE, | |
| 465 base::Bind( | |
| 466 &BalloonViewImpl::DelayedClose, weak_factory_.GetWeakPtr(), false)); | |
| 467 } | |
| 468 } | |
| 469 | |
| 470 gboolean BalloonViewImpl::OnDestroy(GtkWidget* widget) { | |
| 471 frame_container_ = NULL; | |
| 472 Close(false); | |
| 473 return FALSE; // Propagate. | |
| 474 } | |
| OLD | NEW |