OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2009 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/views/notifications/balloon_view.h" |
| 6 |
| 7 #include <vector> |
| 8 |
| 9 #include "app/gfx/canvas.h" |
| 10 #include "app/gfx/gdi_util.h" |
| 11 #include "app/gfx/insets.h" |
| 12 #include "app/gfx/native_widget_types.h" |
| 13 #include "app/l10n_util.h" |
| 14 #include "app/resource_bundle.h" |
| 15 #include "base/message_loop.h" |
| 16 #include "base/string_util.h" |
| 17 #include "chrome/browser/browser_theme_provider.h" |
| 18 #include "chrome/browser/notifications/balloon.h" |
| 19 #include "chrome/browser/views/notifications/balloon_view_host.h" |
| 20 #include "chrome/common/notification_details.h" |
| 21 #include "chrome/common/notification_source.h" |
| 22 #include "chrome/common/notification_type.h" |
| 23 #include "grit/generated_resources.h" |
| 24 #include "grit/theme_resources.h" |
| 25 #include "views/controls/button/button.h" |
| 26 #include "views/controls/button/text_button.h" |
| 27 #include "views/controls/label.h" |
| 28 #include "views/controls/native/native_view_host.h" |
| 29 #include "views/painter.h" |
| 30 #include "views/widget/widget_win.h" |
| 31 |
| 32 using views::Widget; |
| 33 |
| 34 namespace { |
| 35 |
| 36 // How many pixels of overlap there is between the shelf top and the |
| 37 // balloon bottom. |
| 38 const int kTopMargin = 1; |
| 39 const int kBottomMargin = 1; |
| 40 const int kLeftMargin = 1; |
| 41 const int kRightMargin = 1; |
| 42 const int kShelfBorderTopOverlap = 2; |
| 43 |
| 44 // Properties of the dismiss button. |
| 45 const int kDismissButtonWidth = 46; |
| 46 const int kDismissButtonHeight = 20; |
| 47 |
| 48 // Properties of the origin label. |
| 49 const int kLeftLabelMargin = 5; |
| 50 |
| 51 // TODO(johnnyg): Add a shadow for the frame. |
| 52 const int kLeftShadowWidth = 0; |
| 53 const int kRightShadowWidth = 0; |
| 54 const int kTopShadowWidth = 0; |
| 55 const int kBottomShadowWidth = 0; |
| 56 |
| 57 // Optional animation. |
| 58 const bool kAnimateEnabled = true; |
| 59 |
| 60 // The shelf height for the system default font size. It is scaled |
| 61 // with changes in the default font size. |
| 62 const int kDefaultShelfHeight = 22; |
| 63 |
| 64 } // namespace |
| 65 |
| 66 class BalloonCloseButtonListener : public views::ButtonListener { |
| 67 public: |
| 68 explicit BalloonCloseButtonListener(BalloonView* view) |
| 69 : view_(view) {} |
| 70 virtual ~BalloonCloseButtonListener() {} |
| 71 |
| 72 // The only button currently is the close button. |
| 73 virtual void ButtonPressed(views::Button* sender, const views::Event&) { |
| 74 view_->Close(); |
| 75 } |
| 76 |
| 77 private: |
| 78 // Non-owned pointer to the view which owns this object. |
| 79 BalloonView* view_; |
| 80 }; |
| 81 |
| 82 BalloonViewImpl::BalloonViewImpl() |
| 83 : balloon_(NULL), |
| 84 frame_container_(NULL), |
| 85 html_container_(NULL), |
| 86 html_contents_(NULL), |
| 87 shelf_background_(NULL), |
| 88 balloon_background_(NULL), |
| 89 close_button_(NULL), |
| 90 close_button_listener_(NULL), |
| 91 animation_(NULL), |
| 92 method_factory_(this) { |
| 93 // This object is not to be deleted by the views hierarchy, |
| 94 // as it is owned by the balloon. |
| 95 SetParentOwned(false); |
| 96 |
| 97 // Load the sprites for the frames. |
| 98 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
| 99 SkBitmap* shelf_bitmap = rb.GetBitmapNamed(IDR_BALLOON_SHELF); |
| 100 SkBitmap* border_bitmap = rb.GetBitmapNamed(IDR_BALLOON_BORDER); |
| 101 |
| 102 // Insets are such because the sprites have 3x3 corners. |
| 103 gfx::Insets insets(3, 3, 3, 3); |
| 104 shelf_background_.reset( |
| 105 views::Painter::CreateImagePainter(*shelf_bitmap, insets, true)); |
| 106 balloon_background_.reset( |
| 107 views::Painter::CreateImagePainter(*border_bitmap, insets, false)); |
| 108 } |
| 109 |
| 110 BalloonViewImpl::~BalloonViewImpl() { |
| 111 } |
| 112 |
| 113 void BalloonViewImpl::Close() { |
| 114 MessageLoop::current()->PostTask(FROM_HERE, |
| 115 method_factory_.NewRunnableMethod(&BalloonViewImpl::DelayedClose)); |
| 116 } |
| 117 |
| 118 void BalloonViewImpl::DelayedClose() { |
| 119 html_contents_->Shutdown(); |
| 120 html_container_->CloseNow(); |
| 121 frame_container_->CloseNow(); |
| 122 balloon_->Close(true); |
| 123 } |
| 124 |
| 125 void BalloonViewImpl::DidChangeBounds(const gfx::Rect& previous, |
| 126 const gfx::Rect& current) { |
| 127 SizeContentsWindow(); |
| 128 } |
| 129 |
| 130 void BalloonViewImpl::SizeContentsWindow() { |
| 131 if (!html_container_ || !frame_container_) |
| 132 return; |
| 133 |
| 134 gfx::Rect contents_rect = GetContentsRectangle(); |
| 135 html_container_->SetBounds(contents_rect); |
| 136 html_container_->MoveAbove(frame_container_); |
| 137 |
| 138 gfx::Path path; |
| 139 GetContentsMask(contents_rect, &path); |
| 140 html_container_->SetShape(path); |
| 141 } |
| 142 |
| 143 void BalloonViewImpl::RepositionToBalloon() { |
| 144 DCHECK(frame_container_); |
| 145 DCHECK(html_container_); |
| 146 DCHECK(balloon_); |
| 147 |
| 148 if (!kAnimateEnabled) { |
| 149 frame_container_->SetBounds( |
| 150 gfx::Rect(balloon_->position(), balloon_->size())); |
| 151 gfx::Rect contents_rect = GetContentsRectangle(); |
| 152 html_container_->SetBounds(contents_rect); |
| 153 return; |
| 154 } |
| 155 |
| 156 anim_frame_end_ = gfx::Rect(balloon_->position().x(), |
| 157 balloon_->position().y(), |
| 158 balloon_->size().width(), |
| 159 balloon_->size().height()); |
| 160 frame_container_->GetBounds(&anim_frame_start_, false); |
| 161 animation_.reset(new SlideAnimation(this)); |
| 162 animation_->Show(); |
| 163 } |
| 164 |
| 165 void BalloonViewImpl::AnimationProgressed(const Animation* animation) { |
| 166 DCHECK(animation == animation_.get()); |
| 167 |
| 168 // Linear interpolation from start to end position. |
| 169 double e = animation->GetCurrentValue(); |
| 170 double s = (1.0 - e); |
| 171 |
| 172 gfx::Rect frame_position( |
| 173 static_cast<int>(s * anim_frame_start_.x() + |
| 174 e * anim_frame_end_.x()), |
| 175 static_cast<int>(s * anim_frame_start_.y() + |
| 176 e * anim_frame_end_.y()), |
| 177 static_cast<int>(s * anim_frame_start_.width() + |
| 178 e * anim_frame_end_.width()), |
| 179 static_cast<int>(s * anim_frame_start_.height() + |
| 180 e * anim_frame_end_.height())); |
| 181 |
| 182 frame_container_->SetBounds(frame_position); |
| 183 html_container_->SetBounds(GetContentsRectangle()); |
| 184 } |
| 185 |
| 186 void BalloonViewImpl::Show(Balloon* balloon) { |
| 187 balloon_ = balloon; |
| 188 close_button_listener_.reset(new BalloonCloseButtonListener(this)); |
| 189 |
| 190 SetBounds(balloon_->position().x(), balloon_->position().y(), |
| 191 balloon_->size().width(), balloon_->size().height()); |
| 192 |
| 193 // We have to create two windows: one for the contents and one for the |
| 194 // frame. Why? |
| 195 // * The contents is an html window which cannot be a |
| 196 // layered window (because it may have child windows for instance). |
| 197 // * The frame is a layered window so that we can have nicely rounded |
| 198 // corners using alpha blending (and we may do other alpha blending |
| 199 // effects). |
| 200 // Unfortunately, layered windows cannot have child windows. (Well, they can |
| 201 // but the child windows don't render). |
| 202 // |
| 203 // We carefully keep these two windows in sync to present the illusion of |
| 204 // one window to the user. |
| 205 gfx::Rect contents_rect = GetContentsRectangle(); |
| 206 html_contents_ = new BalloonViewHost(balloon); |
| 207 html_contents_->SetPreferredSize(gfx::Size(10000, 10000)); |
| 208 |
| 209 html_container_ = Widget::CreatePopupWidget(Widget::NotTransparent, |
| 210 Widget::AcceptEvents, |
| 211 Widget::DeleteOnDestroy); |
| 212 html_container_->SetAlwaysOnTop(true); |
| 213 html_container_->Init(NULL, contents_rect); |
| 214 html_container_->SetContentsView(html_contents_); |
| 215 |
| 216 gfx::Rect balloon_rect(x(), y(), width(), height()); |
| 217 frame_container_ = Widget::CreatePopupWidget(Widget::Transparent, |
| 218 Widget::AcceptEvents, |
| 219 Widget::DeleteOnDestroy); |
| 220 frame_container_->SetAlwaysOnTop(true); |
| 221 frame_container_->Init(NULL, balloon_rect); |
| 222 frame_container_->SetContentsView(this); |
| 223 |
| 224 const std::wstring dismiss_text = |
| 225 l10n_util::GetString(IDS_NOTIFICATION_BALLOON_DISMISS_LABEL); |
| 226 close_button_ = new views::TextButton(close_button_listener_.get(), |
| 227 dismiss_text); |
| 228 close_button_->SetFont( |
| 229 ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::SmallFont)); |
| 230 close_button_->SetEnabledColor(BrowserThemeProvider::kDefaultColorTabText); |
| 231 close_button_->set_alignment(views::TextButton::ALIGN_CENTER); |
| 232 close_button_->SetBounds(width() - kDismissButtonWidth |
| 233 - kRightMargin, |
| 234 height() - kDismissButtonHeight |
| 235 - kShelfBorderTopOverlap |
| 236 - kBottomMargin, |
| 237 kDismissButtonWidth, |
| 238 kDismissButtonHeight); |
| 239 AddChildView(close_button_); |
| 240 |
| 241 const std::wstring source_label_text = l10n_util::GetStringF( |
| 242 IDS_NOTIFICATION_BALLOON_SOURCE_LABEL, |
| 243 ASCIIToWide(this->balloon_->notification().origin_url().spec())); |
| 244 |
| 245 views::Label* source_label = new views::Label(source_label_text); |
| 246 source_label->SetFont(ResourceBundle::GetSharedInstance().GetFont( |
| 247 ResourceBundle::SmallFont)); |
| 248 source_label->SetColor(BrowserThemeProvider::kDefaultColorTabText); |
| 249 source_label->SetHorizontalAlignment(views::Label::ALIGN_LEFT); |
| 250 source_label->SetBounds(kLeftLabelMargin, |
| 251 height() - kDismissButtonHeight |
| 252 - kShelfBorderTopOverlap |
| 253 - kBottomMargin, |
| 254 width() - kDismissButtonWidth |
| 255 - kRightMargin, |
| 256 kDismissButtonHeight); |
| 257 AddChildView(source_label); |
| 258 |
| 259 SizeContentsWindow(); |
| 260 html_container_->Show(); |
| 261 frame_container_->Show(); |
| 262 |
| 263 notification_registrar_.Add(this, |
| 264 NotificationType::NOTIFY_BALLOON_DISCONNECTED, Source<Balloon>(balloon)); |
| 265 } |
| 266 |
| 267 |
| 268 void BalloonViewImpl::GetContentsMask(const gfx::Rect& rect, |
| 269 gfx::Path* path) const { |
| 270 // This needs to remove areas that look like the following from each corner: |
| 271 // |
| 272 // xx |
| 273 // x |
| 274 path->moveTo(SkScalar(1), SkScalar(0)); |
| 275 // Upper right corner |
| 276 path->arcTo(rect.width() - SkScalar(2), SkScalar(0), |
| 277 rect.width() - SkScalar(1), SkScalar(2), |
| 278 SkScalar(1)); |
| 279 // Lower right corner |
| 280 path->arcTo(rect.width() - SkScalar(1), rect.height() - SkScalar(2), |
| 281 rect.width() - SkScalar(2), rect.height() - SkScalar(1), |
| 282 SkScalar(1)); |
| 283 // Lower left corner |
| 284 path->arcTo(SkScalar(1), rect.height() - SkScalar(1), |
| 285 0, rect.height() - SkScalar(2), |
| 286 SkScalar(1)); |
| 287 // Upper left corner |
| 288 path->arcTo(0, SkScalar(1), SkScalar(1), 0, SkScalar(1)); |
| 289 } |
| 290 |
| 291 gfx::Point BalloonViewImpl::GetContentsOffset() const { |
| 292 return gfx::Point(kTopShadowWidth + kTopMargin, |
| 293 kLeftShadowWidth + kLeftMargin); |
| 294 } |
| 295 |
| 296 int BalloonViewImpl::GetShelfHeight() const { |
| 297 // TODO(johnnyg): add scaling here. |
| 298 return kDefaultShelfHeight; |
| 299 } |
| 300 |
| 301 int BalloonViewImpl::GetFrameWidth() const { |
| 302 return size().width() - kLeftShadowWidth - kRightShadowWidth; |
| 303 } |
| 304 |
| 305 int BalloonViewImpl::GetTotalFrameHeight() const { |
| 306 return size().height() - kTopShadowWidth - kBottomShadowWidth; |
| 307 } |
| 308 |
| 309 int BalloonViewImpl::GetBalloonFrameHeight() const { |
| 310 return GetTotalFrameHeight() - GetShelfHeight(); |
| 311 } |
| 312 |
| 313 gfx::Rect BalloonViewImpl::GetContentsRectangle() const { |
| 314 if (!frame_container_) |
| 315 return gfx::Rect(); |
| 316 |
| 317 int contents_width = GetFrameWidth() - kLeftMargin - kRightMargin; |
| 318 int contents_height = GetBalloonFrameHeight() - kTopMargin - kBottomMargin; |
| 319 gfx::Point offset = GetContentsOffset(); |
| 320 gfx::Rect frame_rect; |
| 321 frame_container_->GetBounds(&frame_rect, true); |
| 322 return gfx::Rect(frame_rect.x() + offset.x(), frame_rect.y() + offset.y(), |
| 323 contents_width, contents_height); |
| 324 } |
| 325 |
| 326 void BalloonViewImpl::Paint(gfx::Canvas* canvas) { |
| 327 DCHECK(canvas); |
| 328 int background_width = GetFrameWidth(); |
| 329 int background_height = GetBalloonFrameHeight(); |
| 330 balloon_background_->Paint(background_width, background_height, canvas); |
| 331 canvas->save(); |
| 332 SkScalar y_offset = |
| 333 static_cast<SkScalar>(background_height - kShelfBorderTopOverlap); |
| 334 canvas->translate(0, y_offset); |
| 335 shelf_background_->Paint(background_width, GetShelfHeight(), canvas); |
| 336 canvas->restore(); |
| 337 |
| 338 View::Paint(canvas); |
| 339 } |
| 340 |
| 341 void BalloonViewImpl::Observe(NotificationType type, |
| 342 const NotificationSource& source, |
| 343 const NotificationDetails& details) { |
| 344 if (type != NotificationType::NOTIFY_BALLOON_DISCONNECTED) { |
| 345 NOTREACHED(); |
| 346 return; |
| 347 } |
| 348 |
| 349 // If the renderer process attached to this balloon is disconnected |
| 350 // (e.g., because of a crash), we want to close the balloon. |
| 351 notification_registrar_.Remove(this, |
| 352 NotificationType::NOTIFY_BALLOON_DISCONNECTED, Source<Balloon>(balloon_)); |
| 353 Close(); |
| 354 } |
OLD | NEW |