Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(80)

Side by Side Diff: chrome/browser/notifications/balloon_view_win.cc

Issue 208068: Desktop Notifications UI (for windows) (Closed)
Patch Set: Created 11 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « chrome/browser/notifications/balloon_view_win.h ('k') | chrome/browser/notifications/balloon_win.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698