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/notifications/balloon_view_win.h" |
| 6 |
| 7 #include "base/message_loop.h" |
| 8 #include "base/string_util.h" |
| 9 #include "base/gfx/gdi_util.h" |
| 10 |
| 11 #include "app/gfx/canvas.h" |
| 12 #include "app/l10n_util.h" |
| 13 #include "chrome/browser/notifications/balloon_contents_win.h" |
| 14 #include "chrome/browser/notifications/balloons.h" |
| 15 #include "chrome/common/notification_details.h" |
| 16 #include "chrome/common/notification_source.h" |
| 17 #include "chrome/common/notification_type.h" |
| 18 #include "views/widget/widget_win.h" |
| 19 #include "views/painter.h" |
| 20 #include "views/controls/button/button.h" |
| 21 #include "views/controls/button/text_button.h" |
| 22 #include "views/controls/label.h" |
| 23 |
| 24 #include "grit/generated_resources.h" |
| 25 #include "grit/theme_resources.h" |
| 26 |
| 27 namespace { |
| 28 // How many pixels of overlap there is between the shelf top and the |
| 29 // balloon bottom. |
| 30 const int kTopMargin = 1; |
| 31 const int kBottomMargin = 1; |
| 32 const int kLeftMargin = 1; |
| 33 const int kRightMargin = 1; |
| 34 const int kShelfBoarderTopOverlap = 2; |
| 35 |
| 36 // Properties of the dismiss button. |
| 37 const int kDismissButtonWidth = 46; |
| 38 const int kDismissButtonHeight = 20; |
| 39 const wchar_t kDismissButtonText[] = L"Dismiss"; |
| 40 |
| 41 // Properties of the origin label. |
| 42 const int kLeftLabelMargin = 5; |
| 43 |
| 44 // TODO(levin): Add a shadow for the frame. |
| 45 const int kLeftShadowWidth = 0; |
| 46 const int kRightShadowWidth = 0; |
| 47 const int kTopShadowWidth = 0; |
| 48 const int kBottomShadowWidth = 0; |
| 49 |
| 50 // Optional animation. |
| 51 const bool kAnimateEnabled = true; |
| 52 |
| 53 // The shelf height for the system default font size. It is scaled |
| 54 // with changes in the default font size. |
| 55 const int kDefaultShelfHeight = 22; |
| 56 } // namespace |
| 57 |
| 58 class BalloonCloseButtonListener : public views::ButtonListener { |
| 59 public: |
| 60 BalloonCloseButtonListener(BalloonView* view) : view_(view) {} |
| 61 virtual ~BalloonCloseButtonListener() {} |
| 62 |
| 63 virtual void ButtonPressed(views::Button* sender, const views::Event&) { |
| 64 view_->Close(); |
| 65 } |
| 66 |
| 67 private: |
| 68 BalloonView* view_; |
| 69 }; |
| 70 |
| 71 BalloonView::BalloonView() |
| 72 : balloon_(NULL), |
| 73 frame_container_(NULL), |
| 74 html_container_(NULL), |
| 75 method_factory_(this) { |
| 76 int shelf_images[9]; |
| 77 shelf_images[views::ImagePainter::BORDER_TOP_LEFT] = |
| 78 IDR_BALLOON_SHELF_TOP_LEFT; |
| 79 shelf_images[views::ImagePainter::BORDER_TOP] = IDR_BALLOON_SHELF_TOP_CENTER; |
| 80 shelf_images[views::ImagePainter::BORDER_TOP_RIGHT] = |
| 81 IDR_BALLOON_SHELF_TOP_RIGHT; |
| 82 shelf_images[views::ImagePainter::BORDER_RIGHT] = IDR_BALLOON_SHELF_RIGHT; |
| 83 shelf_images[views::ImagePainter::BORDER_BOTTOM_RIGHT] = |
| 84 IDR_BALLOON_SHELF_BOTTOM_RIGHT; |
| 85 shelf_images[views::ImagePainter::BORDER_BOTTOM] = |
| 86 IDR_BALLOON_SHELF_BOTTOM_CENTER; |
| 87 shelf_images[views::ImagePainter::BORDER_BOTTOM_LEFT] = |
| 88 IDR_BALLOON_SHELF_BOTTOM_LEFT; |
| 89 shelf_images[views::ImagePainter::BORDER_LEFT] = IDR_BALLOON_SHELF_LEFT; |
| 90 shelf_images[views::ImagePainter::BORDER_CENTER] = IDR_BALLOON_SHELF_CENTER; |
| 91 shelf_background_.reset(new views::ImagePainter(shelf_images, true)); |
| 92 |
| 93 int balloon_images[8]; |
| 94 balloon_images[views::ImagePainter::BORDER_TOP_LEFT] = IDR_BALLOON_TOP_LEFT; |
| 95 balloon_images[views::ImagePainter::BORDER_TOP] = IDR_BALLOON_TOP_CENTER; |
| 96 balloon_images[views::ImagePainter::BORDER_TOP_RIGHT] = IDR_BALLOON_TOP_RIGHT; |
| 97 balloon_images[views::ImagePainter::BORDER_RIGHT] = IDR_BALLOON_RIGHT; |
| 98 balloon_images[views::ImagePainter::BORDER_BOTTOM_RIGHT] = |
| 99 IDR_BALLOON_BOTTOM_RIGHT; |
| 100 balloon_images[views::ImagePainter::BORDER_BOTTOM] = |
| 101 IDR_BALLOON_BOTTOM_CENTER; |
| 102 balloon_images[views::ImagePainter::BORDER_BOTTOM_LEFT] = |
| 103 IDR_BALLOON_BOTTOM_LEFT; |
| 104 balloon_images[views::ImagePainter::BORDER_LEFT] = IDR_BALLOON_LEFT; |
| 105 balloon_background_.reset(new views::ImagePainter(balloon_images, false)); |
| 106 } |
| 107 |
| 108 BalloonView::~BalloonView() { |
| 109 } |
| 110 |
| 111 void BalloonView::Close() { |
| 112 MessageLoop::current()->PostTask( |
| 113 FROM_HERE, |
| 114 method_factory_.NewRunnableMethod(&BalloonView::DelayedClose)); |
| 115 } |
| 116 |
| 117 void BalloonView::DelayedClose() { |
| 118 balloon_->Close(); |
| 119 html_contents_->Shutdown(); |
| 120 html_container_->CloseNow(); |
| 121 frame_container_->CloseNow(); |
| 122 } |
| 123 |
| 124 void BalloonView::DidChangeBounds(const gfx::Rect& previous, |
| 125 const gfx::Rect& current) { |
| 126 SizeContentsWindow(); |
| 127 } |
| 128 |
| 129 void BalloonView::SizeContentsWindow() { |
| 130 if (!html_container_ || !frame_container_) { |
| 131 return; |
| 132 } |
| 133 gfx::Rect contents_rect = contents_rectangle(); |
| 134 html_container_->SetWindowPos(frame_container_->GetNativeView(), |
| 135 contents_rect.x(), |
| 136 contents_rect.y(), |
| 137 contents_rect.width(), |
| 138 contents_rect.height(), |
| 139 SWP_NOACTIVATE); |
| 140 |
| 141 // Note: System will own the hrgn after we call SetWindowRgn, |
| 142 // so we don't need to call DeleteObject() for the mask. |
| 143 ::SetWindowRgn(html_container_->GetNativeView(), |
| 144 GetContentsMask(contents_rect), |
| 145 false); |
| 146 } |
| 147 |
| 148 void BalloonView::RepositionToBalloon() { |
| 149 DCHECK(frame_container_); |
| 150 DCHECK(html_container_); |
| 151 DCHECK(balloon_); |
| 152 |
| 153 if (!kAnimateEnabled) { |
| 154 frame_container_->MoveWindow(balloon_->position().x(), |
| 155 balloon_->position().y(), |
| 156 balloon_->size().width(), |
| 157 balloon_->size().height()); |
| 158 gfx::Rect contents_rect = contents_rectangle(); |
| 159 html_container_->MoveWindow(contents_rect.x(), |
| 160 contents_rect.y(), |
| 161 contents_rect.width(), |
| 162 contents_rect.height()); |
| 163 return; |
| 164 } |
| 165 |
| 166 anim_frame_end_ = gfx::Rect(balloon_->position().x(), |
| 167 balloon_->position().y(), |
| 168 balloon_->size().width(), |
| 169 balloon_->size().height()); |
| 170 frame_container_->GetBounds(&anim_frame_start_, false); |
| 171 |
| 172 animation_.reset(new SlideAnimation(this)); |
| 173 animation_->Show(); |
| 174 } |
| 175 |
| 176 void BalloonView::AnimationProgressed(const Animation* animation) { |
| 177 DCHECK(animation == animation_.get()); |
| 178 |
| 179 double e = animation->GetCurrentValue(); |
| 180 double s = (1.0 - e); |
| 181 |
| 182 gfx::Rect frame_position( |
| 183 (int) (s * anim_frame_start_.x() + e * anim_frame_end_.x()), |
| 184 (int) (s * anim_frame_start_.y() + e * anim_frame_end_.y()), |
| 185 (int) (s * anim_frame_start_.width() + e * anim_frame_end_.width()), |
| 186 (int) (s * anim_frame_start_.height() + e * anim_frame_end_.height())); |
| 187 |
| 188 frame_container_->MoveWindow(frame_position.x(), |
| 189 frame_position.y(), |
| 190 frame_position.width(), |
| 191 frame_position.height()); |
| 192 gfx::Rect contents_rect = contents_rectangle(); |
| 193 html_container_->MoveWindow(contents_rect.x(), |
| 194 contents_rect.y(), |
| 195 contents_rect.width(), |
| 196 contents_rect.height()); |
| 197 } |
| 198 |
| 199 void BalloonView::Show(Balloon* balloon) { |
| 200 balloon_ = balloon; |
| 201 close_button_listener_.reset(new BalloonCloseButtonListener(this)); |
| 202 |
| 203 SetBounds(balloon_->position().x(), |
| 204 balloon_->position().y(), |
| 205 balloon_->size().width(), |
| 206 balloon_->size().height()); |
| 207 |
| 208 // We have to create two windows: one for the contents and one for the |
| 209 // frame. Why? |
| 210 // * The contents is an html window which cannot be a |
| 211 // layered window (because it may have child windows for instance). |
| 212 // * The frame is a layered window so that we can have nicely rounded |
| 213 // corners using alpha blending (and we may do other alpha blending |
| 214 // effects). |
| 215 // Unfortunately, layered windows cannot have child windows. (Well, they can |
| 216 // but the child windows don't render). |
| 217 // |
| 218 // We carefully keep these two windows in sync to present the illusion of |
| 219 // one window to the user. |
| 220 |
| 221 html_container_ = new views::WidgetWin(); |
| 222 html_container_->set_window_style(WS_POPUP); |
| 223 html_container_->set_window_ex_style(WS_EX_TOPMOST | WS_EX_TOOLWINDOW); |
| 224 gfx::Rect contents_rect = contents_rectangle(); |
| 225 html_container_->Init(NULL, contents_rect); |
| 226 html_contents_ = new BalloonContents(balloon); |
| 227 html_contents_->SetPreferredSize(gfx::Size(10000, 10000)); |
| 228 html_container_->SetContentsView(html_contents_); |
| 229 |
| 230 const std::wstring dismiss_text = |
| 231 l10n_util::GetString(IDS_NOTIFICATION_BALLOON_DISMISS_LABEL); |
| 232 |
| 233 close_button_ = new views::TextButton(close_button_listener_.get(), |
| 234 dismiss_text); |
| 235 close_button_->set_alignment(views::TextButton::ALIGN_CENTER); |
| 236 close_button_->SetBounds(width() - kDismissButtonWidth |
| 237 - kRightMargin, |
| 238 height() - kDismissButtonHeight |
| 239 - kShelfBoarderTopOverlap |
| 240 - kBottomMargin, |
| 241 kDismissButtonWidth, |
| 242 kDismissButtonHeight); |
| 243 AddChildView(close_button_); |
| 244 |
| 245 const std::wstring source_label_text = l10n_util::GetStringF( |
| 246 IDS_NOTIFICATION_BALLOON_SOURCE_LABEL, |
| 247 ASCIIToWide(this->balloon_->notification().origin_url().GetOrigin().spec()
)); |
| 248 |
| 249 views::Label* source_label = new views::Label(source_label_text); |
| 250 source_label->SetHorizontalAlignment(views::Label::ALIGN_LEFT); |
| 251 source_label->SetBounds(kLeftLabelMargin, |
| 252 height() - kDismissButtonHeight |
| 253 - kShelfBoarderTopOverlap |
| 254 - kBottomMargin, |
| 255 width() - kDismissButtonWidth |
| 256 - kRightMargin, |
| 257 kDismissButtonHeight); |
| 258 AddChildView(source_label); |
| 259 |
| 260 frame_container_ = new views::WidgetWin(); |
| 261 frame_container_->set_window_style(WS_POPUP); |
| 262 frame_container_->set_window_ex_style( |
| 263 WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW); |
| 264 gfx::Rect balloon_rect(x(), y(), width(), height()); |
| 265 frame_container_->Init(NULL, balloon_rect); |
| 266 frame_container_->SetContentsView(this); |
| 267 |
| 268 SizeContentsWindow(); |
| 269 html_container_->ShowWindow(SW_SHOWNOACTIVATE); |
| 270 frame_container_->ShowWindow(SW_SHOWNOACTIVATE); |
| 271 |
| 272 notification_registrar_.Add(this, |
| 273 NotificationType::NOTIFY_BALLOON_DISCONNECTED, Source<Balloon>(balloon)); |
| 274 } |
| 275 |
| 276 |
| 277 HRGN BalloonView::GetContentsMask(gfx::Rect& contents_rect) const { |
| 278 // This needs to remove areas that look like the following from each corner: |
| 279 // |
| 280 // xx |
| 281 // x |
| 282 |
| 283 // Ideally, the frame would alpha blend these pixels on to the balloon |
| 284 // contents. However, that is a problem because the frame and contents are |
| 285 // separate windows, and the OS may swap in z order (so the contents goes on |
| 286 // top of the frame and covers the alpha blended pixels). |
| 287 |
| 288 std::vector<gfx::Rect> cutouts; |
| 289 // Upper left. |
| 290 cutouts.push_back(gfx::Rect(0, 0, 2, 1)); |
| 291 cutouts.push_back(gfx::Rect(0, 1, 1, 1)); |
| 292 |
| 293 // Upper right. |
| 294 cutouts.push_back(gfx::Rect(contents_rect.width() - 2, 0, 2, 1)); |
| 295 cutouts.push_back(gfx::Rect(contents_rect.width() - 1, 1, 1, 1)); |
| 296 |
| 297 // Lower left. |
| 298 cutouts.push_back(gfx::Rect(0, contents_rect.height() - 1, 2, 1)); |
| 299 cutouts.push_back(gfx::Rect(0, contents_rect.height() - 2, 1, 1)); |
| 300 |
| 301 // Lower right. |
| 302 cutouts.push_back(gfx::Rect(contents_rect.width() - 2, |
| 303 contents_rect.height() - 1, |
| 304 2, |
| 305 1)); |
| 306 cutouts.push_back(gfx::Rect(contents_rect.width() - 1, |
| 307 contents_rect.height() - 2, |
| 308 1, |
| 309 1)); |
| 310 |
| 311 HRGN hrgn = ::CreateRectRgn(0, |
| 312 0, |
| 313 contents_rect.width(), |
| 314 contents_rect.height()); |
| 315 gfx::SubtractRectanglesFromRegion(hrgn, cutouts); |
| 316 return hrgn; |
| 317 } |
| 318 |
| 319 gfx::Point BalloonView::contents_offset() const { |
| 320 return gfx::Point(kTopShadowWidth + kTopMargin, |
| 321 kLeftShadowWidth + kLeftMargin); |
| 322 } |
| 323 |
| 324 int BalloonView::shelf_height() const { |
| 325 // TODO(levin): add scaling here. |
| 326 return kDefaultShelfHeight; |
| 327 } |
| 328 |
| 329 int BalloonView::frame_width() const { |
| 330 return size().width() - kLeftShadowWidth - kRightShadowWidth; |
| 331 } |
| 332 |
| 333 int BalloonView::total_frame_height() const { |
| 334 return size().height() - kTopShadowWidth - kBottomShadowWidth; |
| 335 } |
| 336 |
| 337 int BalloonView::balloon_frame_height() const { |
| 338 return total_frame_height() - shelf_height(); |
| 339 } |
| 340 |
| 341 gfx::Rect BalloonView::contents_rectangle() const { |
| 342 if (!frame_container_) { |
| 343 return gfx::Rect(); |
| 344 } |
| 345 int contents_width = frame_width() - kLeftMargin - kRightMargin; |
| 346 int contents_height = balloon_frame_height() - kTopMargin - kBottomMargin; |
| 347 gfx::Point offset = contents_offset(); |
| 348 gfx::Rect frame_rect; |
| 349 frame_container_->GetBounds(&frame_rect, true); |
| 350 return gfx::Rect(frame_rect.x() + offset.x(), |
| 351 frame_rect.y() + offset.y(), |
| 352 contents_width, |
| 353 contents_height); |
| 354 } |
| 355 |
| 356 void BalloonView::Paint(gfx::Canvas* canvas) { |
| 357 DCHECK(canvas); |
| 358 |
| 359 int background_width = frame_width(); |
| 360 int background_height = balloon_frame_height(); |
| 361 |
| 362 balloon_background_->Paint(background_width, background_height, canvas); |
| 363 |
| 364 canvas->save(); |
| 365 SkScalar y_offset = |
| 366 static_cast<SkScalar>(background_height - kShelfBoarderTopOverlap); |
| 367 canvas->translate(0, y_offset); |
| 368 shelf_background_->Paint(background_width, shelf_height(), canvas); |
| 369 canvas->restore(); |
| 370 |
| 371 View::Paint(canvas); |
| 372 } |
| 373 |
| 374 void BalloonView::Observe(NotificationType type, |
| 375 const NotificationSource& source, |
| 376 const NotificationDetails& details) { |
| 377 if (type != NotificationType::NOTIFY_BALLOON_DISCONNECTED) { |
| 378 NOTREACHED(); |
| 379 return; |
| 380 } |
| 381 |
| 382 notification_registrar_.Remove(this, |
| 383 NotificationType::NOTIFY_BALLOON_DISCONNECTED, Source<Balloon>(balloon_)); |
| 384 |
| 385 Close(); |
| 386 } |
OLD | NEW |