| 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/views/notifications/balloon_view_views.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <vector> | |
| 9 | |
| 10 #include "base/bind.h" | |
| 11 #include "base/message_loop/message_loop.h" | |
| 12 #include "base/strings/utf_string_conversions.h" | |
| 13 #include "chrome/browser/chrome_notification_types.h" | |
| 14 #include "chrome/browser/notifications/balloon_collection.h" | |
| 15 #include "chrome/browser/notifications/desktop_notification_service.h" | |
| 16 #include "chrome/browser/notifications/notification.h" | |
| 17 #include "chrome/browser/notifications/notification_options_menu_model.h" | |
| 18 #include "chrome/browser/ui/views/notifications/balloon_view_host.h" | |
| 19 #include "content/public/browser/notification_details.h" | |
| 20 #include "content/public/browser/notification_source.h" | |
| 21 #include "content/public/browser/notification_types.h" | |
| 22 #include "content/public/browser/render_view_host.h" | |
| 23 #include "content/public/browser/render_widget_host_view.h" | |
| 24 #include "content/public/browser/web_contents.h" | |
| 25 #include "grit/generated_resources.h" | |
| 26 #include "grit/theme_resources.h" | |
| 27 #include "ui/base/l10n/l10n_util.h" | |
| 28 #include "ui/base/resource/resource_bundle.h" | |
| 29 #include "ui/gfx/animation/slide_animation.h" | |
| 30 #include "ui/gfx/canvas.h" | |
| 31 #include "ui/gfx/native_widget_types.h" | |
| 32 #include "ui/gfx/path.h" | |
| 33 #include "ui/views/bubble/bubble_border.h" | |
| 34 #include "ui/views/controls/button/image_button.h" | |
| 35 #include "ui/views/controls/button/menu_button.h" | |
| 36 #include "ui/views/controls/button/text_button.h" | |
| 37 #include "ui/views/controls/label.h" | |
| 38 #include "ui/views/controls/menu/menu_item_view.h" | |
| 39 #include "ui/views/controls/menu/menu_runner.h" | |
| 40 #include "ui/views/controls/native/native_view_host.h" | |
| 41 #include "ui/views/widget/widget.h" | |
| 42 | |
| 43 namespace { | |
| 44 | |
| 45 const int kTopMargin = 2; | |
| 46 const int kBottomMargin = 0; | |
| 47 const int kLeftMargin = 4; | |
| 48 const int kRightMargin = 4; | |
| 49 | |
| 50 // Margin between various shelf buttons/label and the shelf border. | |
| 51 const int kShelfMargin = 2; | |
| 52 | |
| 53 // Spacing between the options and close buttons. | |
| 54 const int kOptionsDismissSpacing = 4; | |
| 55 | |
| 56 // Spacing between the options button and label text. | |
| 57 const int kLabelOptionsSpacing = 4; | |
| 58 | |
| 59 // Margin between shelf border and title label. | |
| 60 const int kLabelLeftMargin = 6; | |
| 61 | |
| 62 // Size of the drop shadow. The shadow is provided by BubbleBorder, | |
| 63 // not this class. | |
| 64 const int kLeftShadowWidth = 0; | |
| 65 const int kRightShadowWidth = 0; | |
| 66 const int kTopShadowWidth = 0; | |
| 67 const int kBottomShadowWidth = 6; | |
| 68 | |
| 69 // Optional animation. | |
| 70 const bool kAnimateEnabled = true; | |
| 71 | |
| 72 // Colors | |
| 73 const SkColor kControlBarBackgroundColor = SkColorSetRGB(245, 245, 245); | |
| 74 const SkColor kControlBarTextColor = SkColorSetRGB(125, 125, 125); | |
| 75 const SkColor kControlBarSeparatorLineColor = SkColorSetRGB(180, 180, 180); | |
| 76 | |
| 77 } // namespace | |
| 78 | |
| 79 // static | |
| 80 int BalloonView::GetHorizontalMargin() { | |
| 81 return kLeftMargin + kRightMargin + kLeftShadowWidth + kRightShadowWidth; | |
| 82 } | |
| 83 | |
| 84 BalloonViewImpl::BalloonViewImpl(BalloonCollection* collection) | |
| 85 : balloon_(NULL), | |
| 86 collection_(collection), | |
| 87 frame_container_(NULL), | |
| 88 html_container_(NULL), | |
| 89 close_button_(NULL), | |
| 90 options_menu_button_(NULL), | |
| 91 enable_web_ui_(false), | |
| 92 closed_by_user_(false), | |
| 93 closed_(false) { | |
| 94 // We're owned by Balloon and don't want to be deleted by our parent View. | |
| 95 set_owned_by_client(); | |
| 96 | |
| 97 SetBorder(scoped_ptr<views::Border>( | |
| 98 new views::BubbleBorder(views::BubbleBorder::FLOAT, | |
| 99 views::BubbleBorder::NO_SHADOW, | |
| 100 SK_ColorWHITE))); | |
| 101 } | |
| 102 | |
| 103 BalloonViewImpl::~BalloonViewImpl() { | |
| 104 } | |
| 105 | |
| 106 void BalloonViewImpl::Close(bool by_user) { | |
| 107 if (closed_) | |
| 108 return; | |
| 109 | |
| 110 closed_ = true; | |
| 111 animation_->Stop(); | |
| 112 html_contents_->Shutdown(); | |
| 113 // Detach contents from the widget before they close. | |
| 114 // This is necessary because a widget may be deleted | |
| 115 // after this when chrome is shutting down. | |
| 116 html_container_->GetRootView()->RemoveAllChildViews(true); | |
| 117 html_container_->Close(); | |
| 118 frame_container_->GetRootView()->RemoveAllChildViews(true); | |
| 119 frame_container_->Close(); | |
| 120 closed_by_user_ = by_user; | |
| 121 // |frame_container_->::Close()| is async. When processed it'll call back to | |
| 122 // DeleteDelegate() and we'll cleanup. | |
| 123 } | |
| 124 | |
| 125 gfx::Size BalloonViewImpl::GetSize() const { | |
| 126 // BalloonView has no size if it hasn't been shown yet (which is when | |
| 127 // balloon_ is set). | |
| 128 if (!balloon_) | |
| 129 return gfx::Size(0, 0); | |
| 130 | |
| 131 return gfx::Size(GetTotalWidth(), GetTotalHeight()); | |
| 132 } | |
| 133 | |
| 134 BalloonHost* BalloonViewImpl::GetHost() const { | |
| 135 return html_contents_.get(); | |
| 136 } | |
| 137 | |
| 138 void BalloonViewImpl::OnMenuButtonClicked(views::View* source, | |
| 139 const gfx::Point& point) { | |
| 140 CreateOptionsMenu(); | |
| 141 | |
| 142 menu_runner_.reset(new views::MenuRunner(options_menu_model_.get())); | |
| 143 | |
| 144 gfx::Point screen_location; | |
| 145 views::View::ConvertPointToScreen(options_menu_button_, &screen_location); | |
| 146 if (menu_runner_->RunMenuAt( | |
| 147 source->GetWidget()->GetTopLevelWidget(), | |
| 148 options_menu_button_, | |
| 149 gfx::Rect(screen_location, options_menu_button_->size()), | |
| 150 views::MenuItemView::TOPRIGHT, | |
| 151 ui::MENU_SOURCE_NONE, | |
| 152 views::MenuRunner::HAS_MNEMONICS) == views::MenuRunner::MENU_DELETED) | |
| 153 return; | |
| 154 } | |
| 155 | |
| 156 void BalloonViewImpl::OnDisplayChanged() { | |
| 157 collection_->DisplayChanged(); | |
| 158 } | |
| 159 | |
| 160 void BalloonViewImpl::OnWorkAreaChanged() { | |
| 161 collection_->DisplayChanged(); | |
| 162 } | |
| 163 | |
| 164 void BalloonViewImpl::DeleteDelegate() { | |
| 165 balloon_->OnClose(closed_by_user_); | |
| 166 } | |
| 167 | |
| 168 void BalloonViewImpl::ButtonPressed(views::Button* sender, const ui::Event&) { | |
| 169 // The only button currently is the close button. | |
| 170 DCHECK_EQ(close_button_, sender); | |
| 171 Close(true); | |
| 172 } | |
| 173 | |
| 174 gfx::Size BalloonViewImpl::GetPreferredSize() { | |
| 175 return gfx::Size(1000, 1000); | |
| 176 } | |
| 177 | |
| 178 void BalloonViewImpl::SizeContentsWindow() { | |
| 179 if (!html_container_ || !frame_container_) | |
| 180 return; | |
| 181 | |
| 182 gfx::Rect contents_rect = GetContentsRectangle(); | |
| 183 html_container_->SetBounds(contents_rect); | |
| 184 html_container_->StackAboveWidget(frame_container_); | |
| 185 | |
| 186 gfx::Path path; | |
| 187 GetContentsMask(contents_rect, &path); | |
| 188 html_container_->SetShape(path.CreateNativeRegion()); | |
| 189 | |
| 190 close_button_->SetBoundsRect(GetCloseButtonBounds()); | |
| 191 options_menu_button_->SetBoundsRect(GetOptionsButtonBounds()); | |
| 192 source_label_->SetBoundsRect(GetLabelBounds()); | |
| 193 } | |
| 194 | |
| 195 void BalloonViewImpl::RepositionToBalloon() { | |
| 196 if (closed_) | |
| 197 return; | |
| 198 | |
| 199 DCHECK(frame_container_); | |
| 200 DCHECK(html_container_); | |
| 201 DCHECK(balloon_); | |
| 202 | |
| 203 if (!kAnimateEnabled) { | |
| 204 frame_container_->SetBounds(GetBoundsForFrameContainer()); | |
| 205 gfx::Rect contents_rect = GetContentsRectangle(); | |
| 206 html_container_->SetBounds(contents_rect); | |
| 207 html_contents_->SetPreferredSize(contents_rect.size()); | |
| 208 content::RenderWidgetHostView* view = | |
| 209 html_contents_->web_contents()->GetRenderWidgetHostView(); | |
| 210 if (view) | |
| 211 view->SetSize(contents_rect.size()); | |
| 212 return; | |
| 213 } | |
| 214 | |
| 215 anim_frame_end_ = GetBoundsForFrameContainer(); | |
| 216 anim_frame_start_ = frame_container_->GetClientAreaBoundsInScreen(); | |
| 217 animation_.reset(new gfx::SlideAnimation(this)); | |
| 218 animation_->Show(); | |
| 219 } | |
| 220 | |
| 221 void BalloonViewImpl::Update() { | |
| 222 if (closed_) | |
| 223 return; | |
| 224 | |
| 225 // Tls might get called before html_contents_ is set in Show() if more than | |
| 226 // one update with the same replace_id occurs, or if an update occurs after | |
| 227 // the ballon has been closed (e.g. during shutdown) but before this has been | |
| 228 // destroyed. | |
| 229 if (!html_contents_.get() || !html_contents_->web_contents()) | |
| 230 return; | |
| 231 html_contents_->web_contents()->GetController().LoadURL( | |
| 232 balloon_->notification().content_url(), content::Referrer(), | |
| 233 content::PAGE_TRANSITION_LINK, std::string()); | |
| 234 } | |
| 235 | |
| 236 void BalloonViewImpl::AnimationProgressed(const gfx::Animation* animation) { | |
| 237 DCHECK_EQ(animation_.get(), animation); | |
| 238 | |
| 239 // Linear interpolation from start to end position. | |
| 240 gfx::Rect frame_position(animation_->CurrentValueBetween( | |
| 241 anim_frame_start_, anim_frame_end_)); | |
| 242 frame_container_->SetBounds(frame_position); | |
| 243 | |
| 244 gfx::Path path; | |
| 245 gfx::Rect contents_rect = GetContentsRectangle(); | |
| 246 html_container_->SetBounds(contents_rect); | |
| 247 GetContentsMask(contents_rect, &path); | |
| 248 html_container_->SetShape(path.CreateNativeRegion()); | |
| 249 | |
| 250 html_contents_->SetPreferredSize(contents_rect.size()); | |
| 251 content::RenderWidgetHostView* view = | |
| 252 html_contents_->web_contents()->GetRenderWidgetHostView(); | |
| 253 if (view) | |
| 254 view->SetSize(contents_rect.size()); | |
| 255 } | |
| 256 | |
| 257 gfx::Rect BalloonViewImpl::GetCloseButtonBounds() const { | |
| 258 gfx::Rect bounds(GetContentsBounds()); | |
| 259 bounds.set_height(GetShelfHeight()); | |
| 260 const gfx::Size& pref_size(close_button_->GetPreferredSize()); | |
| 261 bounds.Inset(bounds.width() - kShelfMargin - pref_size.width(), 0, | |
| 262 kShelfMargin, 0); | |
| 263 bounds.ClampToCenteredSize(pref_size); | |
| 264 return bounds; | |
| 265 } | |
| 266 | |
| 267 gfx::Rect BalloonViewImpl::GetOptionsButtonBounds() const { | |
| 268 gfx::Rect bounds(GetContentsBounds()); | |
| 269 bounds.set_height(GetShelfHeight()); | |
| 270 const gfx::Size& pref_size(options_menu_button_->GetPreferredSize()); | |
| 271 bounds.set_x(GetCloseButtonBounds().x() - kOptionsDismissSpacing - | |
| 272 pref_size.width()); | |
| 273 bounds.set_width(pref_size.width()); | |
| 274 bounds.ClampToCenteredSize(pref_size); | |
| 275 return bounds; | |
| 276 } | |
| 277 | |
| 278 gfx::Rect BalloonViewImpl::GetLabelBounds() const { | |
| 279 gfx::Rect bounds(GetContentsBounds()); | |
| 280 bounds.set_height(GetShelfHeight()); | |
| 281 gfx::Size pref_size(source_label_->GetPreferredSize()); | |
| 282 bounds.Inset(kLabelLeftMargin, 0, bounds.width() - | |
| 283 GetOptionsButtonBounds().x() + kLabelOptionsSpacing, 0); | |
| 284 pref_size.set_width(bounds.width()); | |
| 285 bounds.ClampToCenteredSize(pref_size); | |
| 286 return bounds; | |
| 287 } | |
| 288 | |
| 289 void BalloonViewImpl::Show(Balloon* balloon) { | |
| 290 if (closed_) | |
| 291 return; | |
| 292 | |
| 293 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
| 294 | |
| 295 balloon_ = balloon; | |
| 296 | |
| 297 const base::string16 source_label_text = l10n_util::GetStringFUTF16( | |
| 298 IDS_NOTIFICATION_BALLOON_SOURCE_LABEL, | |
| 299 balloon->notification().display_source()); | |
| 300 | |
| 301 source_label_ = new views::Label(source_label_text); | |
| 302 AddChildView(source_label_); | |
| 303 options_menu_button_ = | |
| 304 new views::MenuButton(NULL, base::string16(), this, false); | |
| 305 AddChildView(options_menu_button_); | |
| 306 close_button_ = new views::ImageButton(this); | |
| 307 close_button_->SetTooltipText(l10n_util::GetStringUTF16( | |
| 308 IDS_NOTIFICATION_BALLOON_DISMISS_LABEL)); | |
| 309 AddChildView(close_button_); | |
| 310 | |
| 311 // We have to create two windows: one for the contents and one for the | |
| 312 // frame. Why? | |
| 313 // * The contents is an html window which cannot be a | |
| 314 // layered window (because it may have child windows for instance). | |
| 315 // * The frame is a layered window so that we can have nicely rounded | |
| 316 // corners using alpha blending (and we may do other alpha blending | |
| 317 // effects). | |
| 318 // Unfortunately, layered windows cannot have child windows. (Well, they can | |
| 319 // but the child windows don't render). | |
| 320 // | |
| 321 // We carefully keep these two windows in sync to present the illusion of | |
| 322 // one window to the user. | |
| 323 // | |
| 324 // We don't let the OS manage the RTL layout of these widgets, because | |
| 325 // this code is already taking care of correctly reversing the layout. | |
| 326 html_contents_.reset(new BalloonViewHost(balloon)); | |
| 327 html_contents_->SetPreferredSize(gfx::Size(10000, 10000)); | |
| 328 if (enable_web_ui_) | |
| 329 html_contents_->EnableWebUI(); | |
| 330 | |
| 331 html_container_ = new views::Widget; | |
| 332 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); | |
| 333 html_container_->Init(params); | |
| 334 html_container_->SetContentsView(html_contents_->view()); | |
| 335 | |
| 336 frame_container_ = new views::Widget; | |
| 337 params.delegate = this; | |
| 338 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; | |
| 339 params.bounds = GetBoundsForFrameContainer(); | |
| 340 frame_container_->Init(params); | |
| 341 frame_container_->SetContentsView(this); | |
| 342 frame_container_->StackAboveWidget(html_container_); | |
| 343 | |
| 344 // GetContentsRectangle() is calculated relative to |frame_container_|. Make | |
| 345 // sure |frame_container_| has bounds before we ask for | |
| 346 // GetContentsRectangle(). | |
| 347 html_container_->SetBounds(GetContentsRectangle()); | |
| 348 | |
| 349 // SetAlwaysOnTop should be called after StackAboveWidget because otherwise | |
| 350 // the top-most flag will be removed. | |
| 351 html_container_->SetAlwaysOnTop(true); | |
| 352 frame_container_->SetAlwaysOnTop(true); | |
| 353 | |
| 354 close_button_->SetImage(views::CustomButton::STATE_NORMAL, | |
| 355 rb.GetImageSkiaNamed(IDR_CLOSE_1)); | |
| 356 close_button_->SetImage(views::CustomButton::STATE_HOVERED, | |
| 357 rb.GetImageSkiaNamed(IDR_CLOSE_1_H)); | |
| 358 close_button_->SetImage(views::CustomButton::STATE_PRESSED, | |
| 359 rb.GetImageSkiaNamed(IDR_CLOSE_1_P)); | |
| 360 close_button_->SetBoundsRect(GetCloseButtonBounds()); | |
| 361 close_button_->SetBackground(SK_ColorBLACK, | |
| 362 rb.GetImageSkiaNamed(IDR_CLOSE_1), | |
| 363 rb.GetImageSkiaNamed(IDR_CLOSE_1_MASK)); | |
| 364 | |
| 365 options_menu_button_->SetIcon(*rb.GetImageSkiaNamed(IDR_BALLOON_WRENCH)); | |
| 366 options_menu_button_->SetHoverIcon( | |
| 367 *rb.GetImageSkiaNamed(IDR_BALLOON_WRENCH_H)); | |
| 368 options_menu_button_->SetPushedIcon(*rb.GetImageSkiaNamed( | |
| 369 IDR_BALLOON_WRENCH_P)); | |
| 370 options_menu_button_->set_alignment(views::TextButton::ALIGN_CENTER); | |
| 371 options_menu_button_->SetBorder(views::Border::NullBorder()); | |
| 372 options_menu_button_->SetBoundsRect(GetOptionsButtonBounds()); | |
| 373 | |
| 374 source_label_->SetFontList(rb.GetFontList(ui::ResourceBundle::SmallFont)); | |
| 375 source_label_->SetBackgroundColor(kControlBarBackgroundColor); | |
| 376 source_label_->SetEnabledColor(kControlBarTextColor); | |
| 377 source_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
| 378 source_label_->SetBoundsRect(GetLabelBounds()); | |
| 379 | |
| 380 SizeContentsWindow(); | |
| 381 html_container_->Show(); | |
| 382 frame_container_->Show(); | |
| 383 | |
| 384 notification_registrar_.Add( | |
| 385 this, chrome::NOTIFICATION_NOTIFY_BALLOON_DISCONNECTED, | |
| 386 content::Source<Balloon>(balloon)); | |
| 387 } | |
| 388 | |
| 389 void BalloonViewImpl::CreateOptionsMenu() { | |
| 390 if (options_menu_model_.get()) | |
| 391 return; | |
| 392 options_menu_model_.reset(new NotificationOptionsMenuModel(balloon_)); | |
| 393 } | |
| 394 | |
| 395 void BalloonViewImpl::GetContentsMask(const gfx::Rect& rect, | |
| 396 gfx::Path* path) const { | |
| 397 // This rounds the corners, and we also cut out a circle for the close | |
| 398 // button, since we can't guarantee the ordering of two top-most windows. | |
| 399 SkScalar radius = SkIntToScalar(views::BubbleBorder::GetCornerRadius()); | |
| 400 SkScalar spline_radius = radius - | |
| 401 SkScalarMul(radius, (SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3); | |
| 402 SkScalar left = SkIntToScalar(0); | |
| 403 SkScalar top = SkIntToScalar(0); | |
| 404 SkScalar right = SkIntToScalar(rect.width()); | |
| 405 SkScalar bottom = SkIntToScalar(rect.height()); | |
| 406 | |
| 407 path->moveTo(left, top); | |
| 408 path->lineTo(right, top); | |
| 409 path->lineTo(right, bottom - radius); | |
| 410 path->cubicTo(right, bottom - spline_radius, | |
| 411 right - spline_radius, bottom, | |
| 412 right - radius, bottom); | |
| 413 path->lineTo(left + radius, bottom); | |
| 414 path->cubicTo(left + spline_radius, bottom, | |
| 415 left, bottom - spline_radius, | |
| 416 left, bottom - radius); | |
| 417 path->lineTo(left, top); | |
| 418 path->close(); | |
| 419 } | |
| 420 | |
| 421 void BalloonViewImpl::GetFrameMask(const gfx::Rect& rect, | |
| 422 gfx::Path* path) const { | |
| 423 SkScalar radius = SkIntToScalar(views::BubbleBorder::GetCornerRadius()); | |
| 424 SkScalar spline_radius = radius - | |
| 425 SkScalarMul(radius, (SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3); | |
| 426 SkScalar left = SkIntToScalar(rect.x()); | |
| 427 SkScalar top = SkIntToScalar(rect.y()); | |
| 428 SkScalar right = SkIntToScalar(rect.right()); | |
| 429 SkScalar bottom = SkIntToScalar(rect.bottom()); | |
| 430 | |
| 431 path->moveTo(left, bottom); | |
| 432 path->lineTo(left, top + radius); | |
| 433 path->cubicTo(left, top + spline_radius, | |
| 434 left + spline_radius, top, | |
| 435 left + radius, top); | |
| 436 path->lineTo(right - radius, top); | |
| 437 path->cubicTo(right - spline_radius, top, | |
| 438 right, top + spline_radius, | |
| 439 right, top + radius); | |
| 440 path->lineTo(right, bottom); | |
| 441 path->lineTo(left, bottom); | |
| 442 path->close(); | |
| 443 } | |
| 444 | |
| 445 gfx::Point BalloonViewImpl::GetContentsOffset() const { | |
| 446 return gfx::Point(kLeftShadowWidth + kLeftMargin, | |
| 447 kTopShadowWidth + kTopMargin); | |
| 448 } | |
| 449 | |
| 450 gfx::Rect BalloonViewImpl::GetBoundsForFrameContainer() const { | |
| 451 return gfx::Rect(balloon_->GetPosition().x(), balloon_->GetPosition().y(), | |
| 452 GetTotalWidth(), GetTotalHeight()); | |
| 453 } | |
| 454 | |
| 455 int BalloonViewImpl::GetShelfHeight() const { | |
| 456 // TODO(johnnyg): add scaling here. | |
| 457 int max_button_height = std::max(std::max( | |
| 458 close_button_->GetPreferredSize().height(), | |
| 459 options_menu_button_->GetPreferredSize().height()), | |
| 460 source_label_->GetPreferredSize().height()); | |
| 461 return max_button_height + kShelfMargin * 2; | |
| 462 } | |
| 463 | |
| 464 int BalloonViewImpl::GetBalloonFrameHeight() const { | |
| 465 return GetTotalHeight() - GetShelfHeight(); | |
| 466 } | |
| 467 | |
| 468 int BalloonViewImpl::GetTotalWidth() const { | |
| 469 return balloon_->content_size().width() + | |
| 470 kLeftMargin + kRightMargin + kLeftShadowWidth + kRightShadowWidth; | |
| 471 } | |
| 472 | |
| 473 int BalloonViewImpl::GetTotalHeight() const { | |
| 474 return balloon_->content_size().height() + | |
| 475 kTopMargin + kBottomMargin + kTopShadowWidth + kBottomShadowWidth + | |
| 476 GetShelfHeight(); | |
| 477 } | |
| 478 | |
| 479 gfx::Rect BalloonViewImpl::GetContentsRectangle() const { | |
| 480 if (!frame_container_) | |
| 481 return gfx::Rect(); | |
| 482 | |
| 483 gfx::Size content_size = balloon_->content_size(); | |
| 484 gfx::Point offset = GetContentsOffset(); | |
| 485 gfx::Rect frame_rect = frame_container_->GetWindowBoundsInScreen(); | |
| 486 return gfx::Rect(frame_rect.x() + offset.x(), | |
| 487 frame_rect.y() + GetShelfHeight() + offset.y(), | |
| 488 content_size.width(), | |
| 489 content_size.height()); | |
| 490 } | |
| 491 | |
| 492 void BalloonViewImpl::OnPaint(gfx::Canvas* canvas) { | |
| 493 DCHECK(canvas); | |
| 494 // Paint the menu bar area white, with proper rounded corners. | |
| 495 gfx::Path path; | |
| 496 gfx::Rect rect = GetContentsBounds(); | |
| 497 rect.set_height(GetShelfHeight()); | |
| 498 GetFrameMask(rect, &path); | |
| 499 | |
| 500 SkPaint paint; | |
| 501 paint.setAntiAlias(true); | |
| 502 paint.setColor(kControlBarBackgroundColor); | |
| 503 canvas->DrawPath(path, paint); | |
| 504 | |
| 505 // Draw a 1-pixel gray line between the content and the menu bar. | |
| 506 int line_width = GetTotalWidth() - kLeftMargin - kRightMargin; | |
| 507 canvas->FillRect(gfx::Rect(kLeftMargin, rect.bottom(), line_width, 1), | |
| 508 kControlBarSeparatorLineColor); | |
| 509 View::OnPaint(canvas); | |
| 510 OnPaintBorder(canvas); | |
| 511 } | |
| 512 | |
| 513 void BalloonViewImpl::OnBoundsChanged(const gfx::Rect& previous_bounds) { | |
| 514 SizeContentsWindow(); | |
| 515 } | |
| 516 | |
| 517 void BalloonViewImpl::Observe(int type, | |
| 518 const content::NotificationSource& source, | |
| 519 const content::NotificationDetails& details) { | |
| 520 if (type != chrome::NOTIFICATION_NOTIFY_BALLOON_DISCONNECTED) { | |
| 521 NOTREACHED(); | |
| 522 return; | |
| 523 } | |
| 524 | |
| 525 // If the renderer process attached to this balloon is disconnected | |
| 526 // (e.g., because of a crash), we want to close the balloon. | |
| 527 notification_registrar_.Remove( | |
| 528 this, chrome::NOTIFICATION_NOTIFY_BALLOON_DISCONNECTED, | |
| 529 content::Source<Balloon>(balloon_)); | |
| 530 Close(false); | |
| 531 } | |
| OLD | NEW |