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 "ui/message_center/views/notification_view.h"
6
7 #include "base/command_line.h"
8 #include "base/stl_util.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "grit/ui_resources.h"
12 #include "grit/ui_strings.h"
13 #include "ui/base/cursor/cursor.h"
14 #include "ui/base/l10n/l10n_util.h"
15 #include "ui/base/layout.h"
16 #include "ui/base/resource/resource_bundle.h"
17 #include "ui/gfx/canvas.h"
18 #include "ui/gfx/size.h"
19 #include "ui/gfx/skia_util.h"
20 #include "ui/gfx/text_elider.h"
21 #include "ui/message_center/message_center.h"
22 #include "ui/message_center/message_center_style.h"
23 #include "ui/message_center/notification.h"
24 #include "ui/message_center/notification_types.h"
25 #include "ui/message_center/views/bounded_label.h"
26 #include "ui/message_center/views/constants.h"
27 #include "ui/message_center/views/message_center_controller.h"
28 #include "ui/message_center/views/notification_button.h"
29 #include "ui/message_center/views/padded_button.h"
30 #include "ui/message_center/views/proportional_image_view.h"
31 #include "ui/native_theme/native_theme.h"
32 #include "ui/views/background.h"
33 #include "ui/views/border.h"
34 #include "ui/views/controls/button/image_button.h"
35 #include "ui/views/controls/image_view.h"
36 #include "ui/views/controls/label.h"
37 #include "ui/views/controls/progress_bar.h"
38 #include "ui/views/layout/box_layout.h"
39 #include "ui/views/layout/fill_layout.h"
40 #include "ui/views/native_cursor.h"
41 #include "ui/views/painter.h"
42 #include "ui/views/widget/widget.h"
43
44 namespace {
45
46 // Dimensions.
47 const int kProgressBarWidth = message_center::kNotificationWidth -
48 message_center::kTextLeftPadding - message_center::kTextRightPadding;
49 const int kProgressBarBottomPadding = 0;
50
51 // static
MakeEmptyBorder(int top,int left,int bottom,int right)52 scoped_ptr<views::Border> MakeEmptyBorder(int top,
53 int left,
54 int bottom,
55 int right) {
56 return views::Border::CreateEmptyBorder(top, left, bottom, right);
57 }
58
59 // static
MakeTextBorder(int padding,int top,int bottom)60 scoped_ptr<views::Border> MakeTextBorder(int padding, int top, int bottom) {
61 // Split the padding between the top and the bottom, then add the extra space.
62 return MakeEmptyBorder(padding / 2 + top,
63 message_center::kTextLeftPadding,
64 (padding + 1) / 2 + bottom,
65 message_center::kTextRightPadding);
66 }
67
68 // static
MakeProgressBarBorder(int top,int bottom)69 scoped_ptr<views::Border> MakeProgressBarBorder(int top, int bottom) {
70 return MakeEmptyBorder(top,
71 message_center::kTextLeftPadding,
72 bottom,
73 message_center::kTextRightPadding);
74 }
75
76 // static
MakeSeparatorBorder(int top,int left,SkColor color)77 scoped_ptr<views::Border> MakeSeparatorBorder(int top,
78 int left,
79 SkColor color) {
80 return views::Border::CreateSolidSidedBorder(top, left, 0, 0, color);
81 }
82
83 // static
84 // Return true if and only if the image is null or has alpha.
HasAlpha(gfx::ImageSkia & image,views::Widget * widget)85 bool HasAlpha(gfx::ImageSkia& image, views::Widget* widget) {
86 // Determine which bitmap to use.
87 float factor = 1.0f;
88 if (widget)
89 factor = ui::GetScaleFactorForNativeView(widget->GetNativeView());
90
91 // Extract that bitmap's alpha and look for a non-opaque pixel there.
92 SkBitmap bitmap = image.GetRepresentation(factor).sk_bitmap();
93 if (!bitmap.isNull()) {
94 SkBitmap alpha;
95 bitmap.extractAlpha(&alpha);
96 for (int y = 0; y < bitmap.height(); ++y) {
97 for (int x = 0; x < bitmap.width(); ++x) {
98 if (alpha.getColor(x, y) != SK_ColorBLACK) {
99 return true;
100 }
101 }
102 }
103 }
104
105 // If no opaque pixel was found, return false unless the bitmap is empty.
106 return bitmap.isNull();
107 }
108
109 // ItemView ////////////////////////////////////////////////////////////////////
110
111 // ItemViews are responsible for drawing each list notification item's title and
112 // message next to each other within a single column.
113 class ItemView : public views::View {
114 public:
115 ItemView(const message_center::NotificationItem& item);
116 virtual ~ItemView();
117
118 // Overridden from views::View:
119 virtual void SetVisible(bool visible) OVERRIDE;
120
121 private:
122 DISALLOW_COPY_AND_ASSIGN(ItemView);
123 };
124
ItemView(const message_center::NotificationItem & item)125 ItemView::ItemView(const message_center::NotificationItem& item) {
126 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
127 0, 0, message_center::kItemTitleToMessagePadding));
128
129 views::Label* title = new views::Label(item.title);
130 title->set_collapse_when_hidden(true);
131 title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
132 title->SetEnabledColor(message_center::kRegularTextColor);
133 title->SetBackgroundColor(message_center::kRegularTextBackgroundColor);
134 AddChildView(title);
135
136 views::Label* message = new views::Label(item.message);
137 message->set_collapse_when_hidden(true);
138 message->SetHorizontalAlignment(gfx::ALIGN_LEFT);
139 message->SetEnabledColor(message_center::kDimTextColor);
140 message->SetBackgroundColor(message_center::kDimTextBackgroundColor);
141 AddChildView(message);
142
143 PreferredSizeChanged();
144 SchedulePaint();
145 }
146
~ItemView()147 ItemView::~ItemView() {
148 }
149
SetVisible(bool visible)150 void ItemView::SetVisible(bool visible) {
151 views::View::SetVisible(visible);
152 for (int i = 0; i < child_count(); ++i)
153 child_at(i)->SetVisible(visible);
154 }
155
156 // The NotificationImage is the view representing the area covered by the
157 // notification's image, including background and border. Its size can be
158 // specified in advance and images will be scaled to fit including a border if
159 // necessary.
160
161 // static
MakeNotificationImage(const gfx::Image & image,gfx::Size size)162 views::View* MakeNotificationImage(const gfx::Image& image, gfx::Size size) {
163 views::View* container = new views::View();
164 container->SetLayoutManager(new views::FillLayout());
165 container->set_background(views::Background::CreateSolidBackground(
166 message_center::kImageBackgroundColor));
167
168 gfx::Size ideal_size(
169 message_center::kNotificationPreferredImageWidth,
170 message_center::kNotificationPreferredImageHeight);
171 gfx::Size scaled_size =
172 message_center::GetImageSizeForContainerSize(ideal_size, image.Size());
173
174 views::View* proportional_image_view =
175 new message_center::ProportionalImageView(image.AsImageSkia(),
176 ideal_size);
177
178 // This calculation determines that the new image would have the correct
179 // height for width.
180 if (ideal_size != scaled_size) {
181 proportional_image_view->SetBorder(views::Border::CreateSolidBorder(
182 message_center::kNotificationImageBorderSize, SK_ColorTRANSPARENT));
183 }
184
185 container->AddChildView(proportional_image_view);
186 return container;
187 }
188
189 // NotificationProgressBar /////////////////////////////////////////////////////
190
191 class NotificationProgressBar : public views::ProgressBar {
192 public:
193 NotificationProgressBar();
194 virtual ~NotificationProgressBar();
195
196 private:
197 // Overriden from View
198 virtual gfx::Size GetPreferredSize() const OVERRIDE;
199 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
200
201 DISALLOW_COPY_AND_ASSIGN(NotificationProgressBar);
202 };
203
NotificationProgressBar()204 NotificationProgressBar::NotificationProgressBar() {
205 }
206
~NotificationProgressBar()207 NotificationProgressBar::~NotificationProgressBar() {
208 }
209
GetPreferredSize() const210 gfx::Size NotificationProgressBar::GetPreferredSize() const {
211 gfx::Size pref_size(kProgressBarWidth, message_center::kProgressBarThickness);
212 gfx::Insets insets = GetInsets();
213 pref_size.Enlarge(insets.width(), insets.height());
214 return pref_size;
215 }
216
OnPaint(gfx::Canvas * canvas)217 void NotificationProgressBar::OnPaint(gfx::Canvas* canvas) {
218 gfx::Rect content_bounds = GetContentsBounds();
219
220 // Draw background.
221 SkPath background_path;
222 background_path.addRoundRect(gfx::RectToSkRect(content_bounds),
223 message_center::kProgressBarCornerRadius,
224 message_center::kProgressBarCornerRadius);
225 SkPaint background_paint;
226 background_paint.setStyle(SkPaint::kFill_Style);
227 background_paint.setFlags(SkPaint::kAntiAlias_Flag);
228 background_paint.setColor(message_center::kProgressBarBackgroundColor);
229 canvas->DrawPath(background_path, background_paint);
230
231 // Draw slice.
232 const int slice_width =
233 static_cast<int>(content_bounds.width() * GetNormalizedValue() + 0.5);
234 if (slice_width < 1)
235 return;
236
237 gfx::Rect slice_bounds = content_bounds;
238 slice_bounds.set_width(slice_width);
239 SkPath slice_path;
240 slice_path.addRoundRect(gfx::RectToSkRect(slice_bounds),
241 message_center::kProgressBarCornerRadius,
242 message_center::kProgressBarCornerRadius);
243 SkPaint slice_paint;
244 slice_paint.setStyle(SkPaint::kFill_Style);
245 slice_paint.setFlags(SkPaint::kAntiAlias_Flag);
246 slice_paint.setColor(message_center::kProgressBarSliceColor);
247 canvas->DrawPath(slice_path, slice_paint);
248 }
249
250 } // namespace
251
252 namespace message_center {
253
254 // NotificationView ////////////////////////////////////////////////////////////
255
256 // static
Create(MessageCenterController * controller,const Notification & notification,bool top_level)257 NotificationView* NotificationView::Create(MessageCenterController* controller,
258 const Notification& notification,
259 bool top_level) {
260 switch (notification.type()) {
261 case NOTIFICATION_TYPE_BASE_FORMAT:
262 case NOTIFICATION_TYPE_IMAGE:
263 case NOTIFICATION_TYPE_MULTIPLE:
264 case NOTIFICATION_TYPE_SIMPLE:
265 case NOTIFICATION_TYPE_PROGRESS:
266 break;
267 default:
268 // If the caller asks for an unrecognized kind of view (entirely possible
269 // if an application is running on an older version of this code that
270 // doesn't have the requested kind of notification template), we'll fall
271 // back to a notification instance that will provide at least basic
272 // functionality.
273 LOG(WARNING) << "Unable to fulfill request for unrecognized "
274 << "notification type " << notification.type() << ". "
275 << "Falling back to simple notification type.";
276 }
277
278 // Currently all roads lead to the generic NotificationView.
279 NotificationView* notification_view =
280 new NotificationView(controller, notification);
281
282 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
283 // Don't create shadows for notification toasts on linux wih aura.
284 if (top_level)
285 return notification_view;
286 #endif
287
288 notification_view->CreateShadowBorder();
289 return notification_view;
290 }
291
CreateOrUpdateViews(const Notification & notification)292 void NotificationView::CreateOrUpdateViews(const Notification& notification) {
293 CreateOrUpdateTitleView(notification);
294 CreateOrUpdateMessageView(notification);
295 CreateOrUpdateContextMessageView(notification);
296 CreateOrUpdateProgressBarView(notification);
297 CreateOrUpdateListItemViews(notification);
298 CreateOrUpdateIconView(notification);
299 CreateOrUpdateImageView(notification);
300 CreateOrUpdateActionButtonViews(notification);
301 }
302
SetAccessibleName(const Notification & notification)303 void NotificationView::SetAccessibleName(const Notification& notification) {
304 std::vector<base::string16> accessible_lines;
305 accessible_lines.push_back(notification.title());
306 accessible_lines.push_back(notification.message());
307 accessible_lines.push_back(notification.context_message());
308 std::vector<NotificationItem> items = notification.items();
309 for (size_t i = 0; i < items.size() && i < kNotificationMaximumItems; ++i) {
310 accessible_lines.push_back(items[i].title + base::ASCIIToUTF16(" ") +
311 items[i].message);
312 }
313 set_accessible_name(JoinString(accessible_lines, '\n'));
314 }
315
NotificationView(MessageCenterController * controller,const Notification & notification)316 NotificationView::NotificationView(MessageCenterController* controller,
317 const Notification& notification)
318 : MessageView(this,
319 notification.id(),
320 notification.notifier_id(),
321 notification.small_image().AsImageSkia(),
322 notification.display_source()),
323 controller_(controller),
324 clickable_(notification.clickable()),
325 top_view_(NULL),
326 title_view_(NULL),
327 message_view_(NULL),
328 context_message_view_(NULL),
329 icon_view_(NULL),
330 bottom_view_(NULL),
331 image_view_(NULL),
332 progress_bar_view_(NULL) {
333 // Create the top_view_, which collects into a vertical box all content
334 // at the top of the notification (to the right of the icon) except for the
335 // close button.
336 top_view_ = new views::View();
337 top_view_->SetLayoutManager(
338 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
339 top_view_->SetBorder(
340 MakeEmptyBorder(kTextTopPadding - 8, 0, kTextBottomPadding - 5, 0));
341 AddChildView(top_view_);
342 // Create the bottom_view_, which collects into a vertical box all content
343 // below the notification icon.
344 bottom_view_ = new views::View();
345 bottom_view_->SetLayoutManager(
346 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
347 AddChildView(bottom_view_);
348
349 CreateOrUpdateViews(notification);
350
351 // Put together the different content and control views. Layering those allows
352 // for proper layout logic and it also allows the close button and small
353 // image to overlap the content as needed to provide large enough click and
354 // touch areas (<http://crbug.com/168822> and <http://crbug.com/168856>).
355 AddChildView(small_image());
356 AddChildView(close_button());
357 SetAccessibleName(notification);
358 }
359
~NotificationView()360 NotificationView::~NotificationView() {
361 }
362
GetPreferredSize() const363 gfx::Size NotificationView::GetPreferredSize() const {
364 int top_width = top_view_->GetPreferredSize().width() +
365 icon_view_->GetPreferredSize().width();
366 int bottom_width = bottom_view_->GetPreferredSize().width();
367 int preferred_width = std::max(top_width, bottom_width) + GetInsets().width();
368 return gfx::Size(preferred_width, GetHeightForWidth(preferred_width));
369 }
370
GetHeightForWidth(int width) const371 int NotificationView::GetHeightForWidth(int width) const {
372 // Get the height assuming no line limit changes.
373 int content_width = width - GetInsets().width();
374 int top_height = top_view_->GetHeightForWidth(content_width);
375 int bottom_height = bottom_view_->GetHeightForWidth(content_width);
376
377 // <http://crbug.com/230448> Fix: Adjust the height when the message_view's
378 // line limit would be different for the specified width than it currently is.
379 // TODO(dharcourt): Avoid BoxLayout and directly compute the correct height.
380 if (message_view_) {
381 int title_lines = 0;
382 if (title_view_) {
383 title_lines = title_view_->GetLinesForWidthAndLimit(width,
384 kMaxTitleLines);
385 }
386 int used_limit = message_view_->GetLineLimit();
387 int correct_limit = GetMessageLineLimit(title_lines, width);
388 if (used_limit != correct_limit) {
389 top_height -= GetMessageHeight(content_width, used_limit);
390 top_height += GetMessageHeight(content_width, correct_limit);
391 }
392 }
393
394 int content_height = std::max(top_height, kIconSize) + bottom_height;
395
396 // Adjust the height to make sure there is at least 16px of space below the
397 // icon if there is any space there (<http://crbug.com/232966>).
398 if (content_height > kIconSize)
399 content_height = std::max(content_height,
400 kIconSize + message_center::kIconBottomPadding);
401
402 return content_height + GetInsets().height();
403 }
404
Layout()405 void NotificationView::Layout() {
406 MessageView::Layout();
407 gfx::Insets insets = GetInsets();
408 int content_width = width() - insets.width();
409
410 // Before any resizing, set or adjust the number of message lines.
411 int title_lines = 0;
412 if (title_view_) {
413 title_lines =
414 title_view_->GetLinesForWidthAndLimit(width(), kMaxTitleLines);
415 }
416 if (message_view_)
417 message_view_->SetLineLimit(GetMessageLineLimit(title_lines, width()));
418
419 // Top views.
420 int top_height = top_view_->GetHeightForWidth(content_width);
421 top_view_->SetBounds(insets.left(), insets.top(), content_width, top_height);
422
423 // Icon.
424 icon_view_->SetBounds(insets.left(), insets.top(), kIconSize, kIconSize);
425
426 // Bottom views.
427 int bottom_y = insets.top() + std::max(top_height, kIconSize);
428 int bottom_height = bottom_view_->GetHeightForWidth(content_width);
429 bottom_view_->SetBounds(insets.left(), bottom_y,
430 content_width, bottom_height);
431 }
432
OnFocus()433 void NotificationView::OnFocus() {
434 MessageView::OnFocus();
435 ScrollRectToVisible(GetLocalBounds());
436 }
437
ScrollRectToVisible(const gfx::Rect & rect)438 void NotificationView::ScrollRectToVisible(const gfx::Rect& rect) {
439 // Notification want to show the whole notification when a part of it (like
440 // a button) gets focused.
441 views::View::ScrollRectToVisible(GetLocalBounds());
442 }
443
GetEventHandlerForRect(const gfx::Rect & rect)444 views::View* NotificationView::GetEventHandlerForRect(const gfx::Rect& rect) {
445 // TODO(tdanderson): Modify this function to support rect-based event
446 // targeting. Using the center point of |rect| preserves this function's
447 // expected behavior for the time being.
448 gfx::Point point = rect.CenterPoint();
449
450 // Want to return this for underlying views, otherwise GetCursor is not
451 // called. But buttons are exceptions, they'll have their own event handlings.
452 std::vector<views::View*> buttons(action_buttons_.begin(),
453 action_buttons_.end());
454 buttons.push_back(close_button());
455
456 for (size_t i = 0; i < buttons.size(); ++i) {
457 gfx::Point point_in_child = point;
458 ConvertPointToTarget(this, buttons[i], &point_in_child);
459 if (buttons[i]->HitTestPoint(point_in_child))
460 return buttons[i]->GetEventHandlerForPoint(point_in_child);
461 }
462
463 return this;
464 }
465
GetCursor(const ui::MouseEvent & event)466 gfx::NativeCursor NotificationView::GetCursor(const ui::MouseEvent& event) {
467 if (!clickable_ || !controller_->HasClickedListener(notification_id()))
468 return views::View::GetCursor(event);
469
470 return views::GetNativeHandCursor();
471 }
472
UpdateWithNotification(const Notification & notification)473 void NotificationView::UpdateWithNotification(
474 const Notification& notification) {
475 MessageView::UpdateWithNotification(notification);
476
477 CreateOrUpdateViews(notification);
478 SetAccessibleName(notification);
479 Layout();
480 SchedulePaint();
481 }
482
ButtonPressed(views::Button * sender,const ui::Event & event)483 void NotificationView::ButtonPressed(views::Button* sender,
484 const ui::Event& event) {
485 // Certain operations can cause |this| to be destructed, so copy the members
486 // we send to other parts of the code.
487 // TODO(dewittj): Remove this hack.
488 std::string id(notification_id());
489 // See if the button pressed was an action button.
490 for (size_t i = 0; i < action_buttons_.size(); ++i) {
491 if (sender == action_buttons_[i]) {
492 controller_->ClickOnNotificationButton(id, i);
493 return;
494 }
495 }
496
497 // Let the superclass handled anything other than action buttons.
498 // Warning: This may cause the NotificationView itself to be deleted,
499 // so don't do anything afterwards.
500 MessageView::ButtonPressed(sender, event);
501 }
502
ClickOnNotification(const std::string & notification_id)503 void NotificationView::ClickOnNotification(const std::string& notification_id) {
504 controller_->ClickOnNotification(notification_id);
505 }
506
RemoveNotification(const std::string & notification_id,bool by_user)507 void NotificationView::RemoveNotification(const std::string& notification_id,
508 bool by_user) {
509 controller_->RemoveNotification(notification_id, by_user);
510 }
511
CreateOrUpdateTitleView(const Notification & notification)512 void NotificationView::CreateOrUpdateTitleView(
513 const Notification& notification) {
514 if (notification.title().empty()) {
515 if (title_view_) {
516 // Deletion will also remove |title_view_| from its parent.
517 delete title_view_;
518 title_view_ = NULL;
519 }
520 return;
521 }
522
523 DCHECK(top_view_ != NULL);
524
525 const gfx::FontList& font_list =
526 views::Label().font_list().DeriveWithSizeDelta(2);
527
528 int title_character_limit =
529 kNotificationWidth * kMaxTitleLines / kMinPixelsPerTitleCharacter;
530
531 if (!title_view_) {
532 int padding = kTitleLineHeight - font_list.GetHeight();
533
534 title_view_ = new BoundedLabel(
535 gfx::TruncateString(notification.title(), title_character_limit),
536 font_list);
537 title_view_->SetLineHeight(kTitleLineHeight);
538 title_view_->SetLineLimit(kMaxTitleLines);
539 title_view_->SetColors(message_center::kRegularTextColor,
540 kRegularTextBackgroundColor);
541 title_view_->SetBorder(MakeTextBorder(padding, 3, 0));
542 top_view_->AddChildView(title_view_);
543 } else {
544 title_view_->SetText(
545 gfx::TruncateString(notification.title(), title_character_limit));
546 }
547 }
548
CreateOrUpdateMessageView(const Notification & notification)549 void NotificationView::CreateOrUpdateMessageView(
550 const Notification& notification) {
551 if (notification.message().empty()) {
552 if (message_view_) {
553 // Deletion will also remove |message_view_| from its parent.
554 delete message_view_;
555 message_view_ = NULL;
556 }
557 return;
558 }
559
560 DCHECK(top_view_ != NULL);
561
562 if (!message_view_) {
563 int padding = kMessageLineHeight - views::Label().font_list().GetHeight();
564 message_view_ = new BoundedLabel(
565 gfx::TruncateString(notification.message(), kMessageCharacterLimit));
566 message_view_->SetLineHeight(kMessageLineHeight);
567 message_view_->SetColors(message_center::kRegularTextColor,
568 kDimTextBackgroundColor);
569 message_view_->SetBorder(MakeTextBorder(padding, 4, 0));
570 top_view_->AddChildView(message_view_);
571 } else {
572 message_view_->SetText(
573 gfx::TruncateString(notification.message(), kMessageCharacterLimit));
574 }
575
576 message_view_->SetVisible(!notification.items().size());
577 }
578
CreateOrUpdateContextMessageView(const Notification & notification)579 void NotificationView::CreateOrUpdateContextMessageView(
580 const Notification& notification) {
581 if (notification.context_message().empty()) {
582 if (context_message_view_) {
583 // Deletion will also remove |context_message_view_| from its parent.
584 delete context_message_view_;
585 context_message_view_ = NULL;
586 }
587 return;
588 }
589
590 DCHECK(top_view_ != NULL);
591
592 if (!context_message_view_) {
593 int padding = kMessageLineHeight - views::Label().font_list().GetHeight();
594 context_message_view_ = new BoundedLabel(gfx::TruncateString(
595 notification.context_message(), kContextMessageCharacterLimit));
596 context_message_view_->SetLineLimit(
597 message_center::kContextMessageLineLimit);
598 context_message_view_->SetLineHeight(kMessageLineHeight);
599 context_message_view_->SetColors(message_center::kDimTextColor,
600 kContextTextBackgroundColor);
601 context_message_view_->SetBorder(MakeTextBorder(padding, 4, 0));
602 top_view_->AddChildView(context_message_view_);
603 } else {
604 context_message_view_->SetText(gfx::TruncateString(
605 notification.context_message(), kContextMessageCharacterLimit));
606 }
607 }
608
CreateOrUpdateProgressBarView(const Notification & notification)609 void NotificationView::CreateOrUpdateProgressBarView(
610 const Notification& notification) {
611 if (notification.type() != NOTIFICATION_TYPE_PROGRESS) {
612 if (progress_bar_view_) {
613 // Deletion will also remove |progress_bar_view_| from its parent.
614 delete progress_bar_view_;
615 progress_bar_view_ = NULL;
616 }
617 return;
618 }
619
620 DCHECK(top_view_ != NULL);
621
622 if (!progress_bar_view_) {
623 progress_bar_view_ = new NotificationProgressBar();
624 progress_bar_view_->SetBorder(MakeProgressBarBorder(
625 message_center::kProgressBarTopPadding, kProgressBarBottomPadding));
626 top_view_->AddChildView(progress_bar_view_);
627 }
628
629 progress_bar_view_->SetValue(notification.progress() / 100.0);
630 progress_bar_view_->SetVisible(!notification.items().size());
631 }
632
CreateOrUpdateListItemViews(const Notification & notification)633 void NotificationView::CreateOrUpdateListItemViews(
634 const Notification& notification) {
635 for (size_t i = 0; i < item_views_.size(); ++i)
636 delete item_views_[i];
637 item_views_.clear();
638
639 int padding = kMessageLineHeight - views::Label().font_list().GetHeight();
640 std::vector<NotificationItem> items = notification.items();
641
642 if (items.size() == 0)
643 return;
644
645 DCHECK(top_view_);
646 for (size_t i = 0; i < items.size() && i < kNotificationMaximumItems; ++i) {
647 ItemView* item_view = new ItemView(items[i]);
648 item_view->SetBorder(MakeTextBorder(padding, i ? 0 : 4, 0));
649 item_views_.push_back(item_view);
650 top_view_->AddChildView(item_view);
651 }
652 }
653
CreateOrUpdateIconView(const Notification & notification)654 void NotificationView::CreateOrUpdateIconView(
655 const Notification& notification) {
656 if (icon_view_) {
657 delete icon_view_;
658 icon_view_ = NULL;
659 }
660
661 // TODO(dewittj): Detect a compatible update and use the existing icon view.
662 gfx::ImageSkia icon = notification.icon().AsImageSkia();
663 if (notification.type() == NOTIFICATION_TYPE_SIMPLE &&
664 (icon.width() != kIconSize || icon.height() != kIconSize ||
665 HasAlpha(icon, GetWidget()))) {
666 views::ImageView* icon_view = new views::ImageView();
667 icon_view->SetImage(icon);
668 icon_view->SetImageSize(gfx::Size(kLegacyIconSize, kLegacyIconSize));
669 icon_view->SetHorizontalAlignment(views::ImageView::CENTER);
670 icon_view->SetVerticalAlignment(views::ImageView::CENTER);
671 icon_view_ = icon_view;
672 } else {
673 icon_view_ =
674 new ProportionalImageView(icon, gfx::Size(kIconSize, kIconSize));
675 }
676
677 icon_view_->set_background(
678 views::Background::CreateSolidBackground(kIconBackgroundColor));
679
680 AddChildView(icon_view_);
681 }
682
CreateOrUpdateImageView(const Notification & notification)683 void NotificationView::CreateOrUpdateImageView(
684 const Notification& notification) {
685 if (image_view_) {
686 delete image_view_;
687 image_view_ = NULL;
688 }
689
690 DCHECK(bottom_view_);
691 DCHECK_EQ(this, bottom_view_->parent());
692
693 // TODO(dewittj): Detect a compatible update and use the existing image view.
694 if (!notification.image().IsEmpty()) {
695 gfx::Size image_size(kNotificationPreferredImageWidth,
696 kNotificationPreferredImageHeight);
697 image_view_ = MakeNotificationImage(notification.image(), image_size);
698 bottom_view_->AddChildViewAt(image_view_, 0);
699 }
700 }
701
CreateOrUpdateActionButtonViews(const Notification & notification)702 void NotificationView::CreateOrUpdateActionButtonViews(
703 const Notification& notification) {
704 std::vector<ButtonInfo> buttons = notification.buttons();
705 bool new_buttons = action_buttons_.size() != buttons.size();
706
707 if (new_buttons || buttons.size() == 0) {
708 // STLDeleteElements also clears the container.
709 STLDeleteElements(&separators_);
710 STLDeleteElements(&action_buttons_);
711 }
712
713 DCHECK(bottom_view_);
714 DCHECK_EQ(this, bottom_view_->parent());
715
716 for (size_t i = 0; i < buttons.size(); ++i) {
717 ButtonInfo button_info = buttons[i];
718 if (new_buttons) {
719 views::View* separator = new views::ImageView();
720 separator->SetBorder(MakeSeparatorBorder(1, 0, kButtonSeparatorColor));
721 separators_.push_back(separator);
722 bottom_view_->AddChildView(separator);
723 NotificationButton* button = new NotificationButton(this);
724 button->SetTitle(button_info.title);
725 button->SetIcon(button_info.icon.AsImageSkia());
726 action_buttons_.push_back(button);
727 bottom_view_->AddChildView(button);
728 } else {
729 action_buttons_[i]->SetTitle(button_info.title);
730 action_buttons_[i]->SetIcon(button_info.icon.AsImageSkia());
731 action_buttons_[i]->SchedulePaint();
732 action_buttons_[i]->Layout();
733 }
734 }
735
736 if (new_buttons) {
737 Layout();
738 views::Widget* widget = GetWidget();
739 if (widget != NULL) {
740 widget->SetSize(widget->GetContentsView()->GetPreferredSize());
741 GetWidget()->SynthesizeMouseMoveEvent();
742 }
743 }
744 }
745
GetMessageLineLimit(int title_lines,int width) const746 int NotificationView::GetMessageLineLimit(int title_lines, int width) const {
747 // Image notifications require that the image must be kept flush against
748 // their icons, but we can allow more text if no image.
749 int effective_title_lines = std::max(0, title_lines - 1);
750 int line_reduction_from_title = (image_view_ ? 1 : 2) * effective_title_lines;
751 if (!image_view_) {
752 // Title lines are counted as twice as big as message lines for the purpose
753 // of this calculation.
754 // The effect from the title reduction here should be:
755 // * 0 title lines: 5 max lines message.
756 // * 1 title line: 5 max lines message.
757 // * 2 title lines: 3 max lines message.
758 return std::max(
759 0,
760 message_center::kMessageExpandedLineLimit - line_reduction_from_title);
761 }
762
763 int message_line_limit = message_center::kMessageCollapsedLineLimit;
764
765 // Subtract any lines taken by the context message.
766 if (context_message_view_) {
767 message_line_limit -= context_message_view_->GetLinesForWidthAndLimit(
768 width,
769 message_center::kContextMessageLineLimit);
770 }
771
772 // The effect from the title reduction here should be:
773 // * 0 title lines: 2 max lines message + context message.
774 // * 1 title line: 2 max lines message + context message.
775 // * 2 title lines: 1 max lines message + context message.
776 message_line_limit =
777 std::max(0, message_line_limit - line_reduction_from_title);
778
779 return message_line_limit;
780 }
781
GetMessageHeight(int width,int limit) const782 int NotificationView::GetMessageHeight(int width, int limit) const {
783 return message_view_ ?
784 message_view_->GetSizeForWidthAndLines(width, limit).height() : 0;
785 }
786
787 } // namespace message_center
788