BookmarkManager: Fix 'new folder text field size changes on clicking it' issue.
[chromium-blink-merge.git] / ui / message_center / views / notification_view.cc
blob308bef5fbd81c70f65cb5cc7dc8cb716982497a1
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.
5 #include "ui/message_center/views/notification_view.h"
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 "components/url_formatter/elide_url.h"
12 #include "third_party/skia/include/core/SkPaint.h"
13 #include "third_party/skia/include/core/SkPath.h"
14 #include "ui/base/cursor/cursor.h"
15 #include "ui/base/layout.h"
16 #include "ui/gfx/canvas.h"
17 #include "ui/gfx/geometry/size.h"
18 #include "ui/gfx/skia_util.h"
19 #include "ui/gfx/text_elider.h"
20 #include "ui/message_center/message_center.h"
21 #include "ui/message_center/message_center_style.h"
22 #include "ui/message_center/notification.h"
23 #include "ui/message_center/notification_types.h"
24 #include "ui/message_center/views/bounded_label.h"
25 #include "ui/message_center/views/constants.h"
26 #include "ui/message_center/views/message_center_controller.h"
27 #include "ui/message_center/views/notification_button.h"
28 #include "ui/message_center/views/padded_button.h"
29 #include "ui/message_center/views/proportional_image_view.h"
30 #include "ui/native_theme/native_theme.h"
31 #include "ui/resources/grit/ui_resources.h"
32 #include "ui/strings/grit/ui_strings.h"
33 #include "ui/views/background.h"
34 #include "ui/views/border.h"
35 #include "ui/views/controls/button/image_button.h"
36 #include "ui/views/controls/image_view.h"
37 #include "ui/views/controls/label.h"
38 #include "ui/views/controls/progress_bar.h"
39 #include "ui/views/layout/box_layout.h"
40 #include "ui/views/layout/fill_layout.h"
41 #include "ui/views/native_cursor.h"
42 #include "ui/views/painter.h"
43 #include "ui/views/view_targeter.h"
44 #include "ui/views/widget/widget.h"
45 #include "url/gurl.h"
47 namespace {
49 // Dimensions.
50 const int kProgressBarWidth = message_center::kNotificationWidth -
51 message_center::kTextLeftPadding - message_center::kTextRightPadding;
52 const int kProgressBarBottomPadding = 0;
54 // static
55 scoped_ptr<views::Border> MakeEmptyBorder(int top,
56 int left,
57 int bottom,
58 int right) {
59 return views::Border::CreateEmptyBorder(top, left, bottom, right);
62 // static
63 scoped_ptr<views::Border> MakeTextBorder(int padding, int top, int bottom) {
64 // Split the padding between the top and the bottom, then add the extra space.
65 return MakeEmptyBorder(padding / 2 + top,
66 message_center::kTextLeftPadding,
67 (padding + 1) / 2 + bottom,
68 message_center::kTextRightPadding);
71 // static
72 scoped_ptr<views::Border> MakeProgressBarBorder(int top, int bottom) {
73 return MakeEmptyBorder(top,
74 message_center::kTextLeftPadding,
75 bottom,
76 message_center::kTextRightPadding);
79 // static
80 scoped_ptr<views::Border> MakeSeparatorBorder(int top,
81 int left,
82 SkColor color) {
83 return views::Border::CreateSolidSidedBorder(top, left, 0, 0, color);
86 // static
87 // Return true if and only if the image is null or has alpha.
88 bool HasAlpha(gfx::ImageSkia& image, views::Widget* widget) {
89 // Determine which bitmap to use.
90 float factor = 1.0f;
91 if (widget)
92 factor = ui::GetScaleFactorForNativeView(widget->GetNativeView());
94 // Extract that bitmap's alpha and look for a non-opaque pixel there.
95 SkBitmap bitmap = image.GetRepresentation(factor).sk_bitmap();
96 if (!bitmap.isNull()) {
97 SkBitmap alpha;
98 bitmap.extractAlpha(&alpha);
99 for (int y = 0; y < bitmap.height(); ++y) {
100 for (int x = 0; x < bitmap.width(); ++x) {
101 if (alpha.getColor(x, y) != SK_ColorBLACK) {
102 return true;
108 // If no opaque pixel was found, return false unless the bitmap is empty.
109 return bitmap.isNull();
112 // ItemView ////////////////////////////////////////////////////////////////////
114 // ItemViews are responsible for drawing each list notification item's title and
115 // message next to each other within a single column.
116 class ItemView : public views::View {
117 public:
118 ItemView(const message_center::NotificationItem& item);
119 ~ItemView() override;
121 // Overridden from views::View:
122 void SetVisible(bool visible) override;
124 private:
125 DISALLOW_COPY_AND_ASSIGN(ItemView);
128 ItemView::ItemView(const message_center::NotificationItem& item) {
129 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
130 0, 0, message_center::kItemTitleToMessagePadding));
132 views::Label* title = new views::Label(item.title);
133 title->set_collapse_when_hidden(true);
134 title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
135 title->SetEnabledColor(message_center::kRegularTextColor);
136 title->SetBackgroundColor(message_center::kRegularTextBackgroundColor);
137 AddChildView(title);
139 views::Label* message = new views::Label(item.message);
140 message->set_collapse_when_hidden(true);
141 message->SetHorizontalAlignment(gfx::ALIGN_LEFT);
142 message->SetEnabledColor(message_center::kDimTextColor);
143 message->SetBackgroundColor(message_center::kDimTextBackgroundColor);
144 AddChildView(message);
146 PreferredSizeChanged();
147 SchedulePaint();
150 ItemView::~ItemView() {
153 void ItemView::SetVisible(bool visible) {
154 views::View::SetVisible(visible);
155 for (int i = 0; i < child_count(); ++i)
156 child_at(i)->SetVisible(visible);
159 // NotificationProgressBar /////////////////////////////////////////////////////
161 class NotificationProgressBar : public views::ProgressBar {
162 public:
163 NotificationProgressBar();
164 ~NotificationProgressBar() override;
166 private:
167 // Overriden from View
168 gfx::Size GetPreferredSize() const override;
169 void OnPaint(gfx::Canvas* canvas) override;
171 DISALLOW_COPY_AND_ASSIGN(NotificationProgressBar);
174 NotificationProgressBar::NotificationProgressBar() {
177 NotificationProgressBar::~NotificationProgressBar() {
180 gfx::Size NotificationProgressBar::GetPreferredSize() const {
181 gfx::Size pref_size(kProgressBarWidth, message_center::kProgressBarThickness);
182 gfx::Insets insets = GetInsets();
183 pref_size.Enlarge(insets.width(), insets.height());
184 return pref_size;
187 void NotificationProgressBar::OnPaint(gfx::Canvas* canvas) {
188 gfx::Rect content_bounds = GetContentsBounds();
190 // Draw background.
191 SkPath background_path;
192 background_path.addRoundRect(gfx::RectToSkRect(content_bounds),
193 message_center::kProgressBarCornerRadius,
194 message_center::kProgressBarCornerRadius);
195 SkPaint background_paint;
196 background_paint.setStyle(SkPaint::kFill_Style);
197 background_paint.setFlags(SkPaint::kAntiAlias_Flag);
198 background_paint.setColor(message_center::kProgressBarBackgroundColor);
199 canvas->DrawPath(background_path, background_paint);
201 // Draw slice.
202 const int slice_width =
203 static_cast<int>(content_bounds.width() * GetNormalizedValue() + 0.5);
204 if (slice_width < 1)
205 return;
207 gfx::Rect slice_bounds = content_bounds;
208 slice_bounds.set_width(slice_width);
209 SkPath slice_path;
210 slice_path.addRoundRect(gfx::RectToSkRect(slice_bounds),
211 message_center::kProgressBarCornerRadius,
212 message_center::kProgressBarCornerRadius);
213 SkPaint slice_paint;
214 slice_paint.setStyle(SkPaint::kFill_Style);
215 slice_paint.setFlags(SkPaint::kAntiAlias_Flag);
216 slice_paint.setColor(message_center::kProgressBarSliceColor);
217 canvas->DrawPath(slice_path, slice_paint);
220 } // namespace
222 namespace message_center {
224 // NotificationView ////////////////////////////////////////////////////////////
226 // static
227 NotificationView* NotificationView::Create(MessageCenterController* controller,
228 const Notification& notification,
229 bool top_level) {
230 switch (notification.type()) {
231 case NOTIFICATION_TYPE_BASE_FORMAT:
232 case NOTIFICATION_TYPE_IMAGE:
233 case NOTIFICATION_TYPE_MULTIPLE:
234 case NOTIFICATION_TYPE_SIMPLE:
235 case NOTIFICATION_TYPE_PROGRESS:
236 break;
237 default:
238 // If the caller asks for an unrecognized kind of view (entirely possible
239 // if an application is running on an older version of this code that
240 // doesn't have the requested kind of notification template), we'll fall
241 // back to a notification instance that will provide at least basic
242 // functionality.
243 LOG(WARNING) << "Unable to fulfill request for unrecognized "
244 << "notification type " << notification.type() << ". "
245 << "Falling back to simple notification type.";
248 // Currently all roads lead to the generic NotificationView.
249 NotificationView* notification_view =
250 new NotificationView(controller, notification);
252 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
253 // Don't create shadows for notification toasts on linux wih aura.
254 if (top_level)
255 return notification_view;
256 #endif
258 notification_view->CreateShadowBorder();
259 return notification_view;
262 views::View* NotificationView::TargetForRect(views::View* root,
263 const gfx::Rect& rect) {
264 CHECK_EQ(root, this);
266 // TODO(tdanderson): Modify this function to support rect-based event
267 // targeting. Using the center point of |rect| preserves this function's
268 // expected behavior for the time being.
269 gfx::Point point = rect.CenterPoint();
271 // Want to return this for underlying views, otherwise GetCursor is not
272 // called. But buttons are exceptions, they'll have their own event handlings.
273 std::vector<views::View*> buttons(action_buttons_.begin(),
274 action_buttons_.end());
275 buttons.push_back(close_button());
277 for (size_t i = 0; i < buttons.size(); ++i) {
278 gfx::Point point_in_child = point;
279 ConvertPointToTarget(this, buttons[i], &point_in_child);
280 if (buttons[i]->HitTestPoint(point_in_child))
281 return buttons[i]->GetEventHandlerForPoint(point_in_child);
284 return root;
287 void NotificationView::CreateOrUpdateViews(const Notification& notification) {
288 CreateOrUpdateTitleView(notification);
289 CreateOrUpdateMessageView(notification);
290 CreateOrUpdateProgressBarView(notification);
291 CreateOrUpdateListItemViews(notification);
292 CreateOrUpdateIconView(notification);
293 CreateOrUpdateImageView(notification);
294 CreateOrUpdateContextMessageView(notification);
295 CreateOrUpdateActionButtonViews(notification);
298 void NotificationView::SetAccessibleName(const Notification& notification) {
299 std::vector<base::string16> accessible_lines;
300 accessible_lines.push_back(notification.title());
301 accessible_lines.push_back(notification.message());
302 accessible_lines.push_back(notification.context_message());
303 std::vector<NotificationItem> items = notification.items();
304 for (size_t i = 0; i < items.size() && i < kNotificationMaximumItems; ++i) {
305 accessible_lines.push_back(items[i].title + base::ASCIIToUTF16(" ") +
306 items[i].message);
308 set_accessible_name(
309 base::JoinString(accessible_lines, base::ASCIIToUTF16("\n")));
312 NotificationView::NotificationView(MessageCenterController* controller,
313 const Notification& notification)
314 : MessageView(this,
315 notification.id(),
316 notification.notifier_id(),
317 notification.small_image().AsImageSkia(),
318 notification.display_source()),
319 controller_(controller),
320 clickable_(notification.clickable()),
321 top_view_(NULL),
322 title_view_(NULL),
323 message_view_(NULL),
324 context_message_view_(NULL),
325 icon_view_(NULL),
326 bottom_view_(NULL),
327 image_container_(NULL),
328 image_view_(NULL),
329 progress_bar_view_(NULL) {
330 // Create the top_view_, which collects into a vertical box all content
331 // at the top of the notification (to the right of the icon) except for the
332 // close button.
333 top_view_ = new views::View();
334 top_view_->SetLayoutManager(
335 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
336 top_view_->SetBorder(
337 MakeEmptyBorder(kTextTopPadding - 8, 0, kTextBottomPadding - 5, 0));
338 AddChildView(top_view_);
339 // Create the bottom_view_, which collects into a vertical box all content
340 // below the notification icon.
341 bottom_view_ = new views::View();
342 bottom_view_->SetLayoutManager(
343 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
344 AddChildView(bottom_view_);
346 CreateOrUpdateViews(notification);
348 // Put together the different content and control views. Layering those allows
349 // for proper layout logic and it also allows the close button and small
350 // image to overlap the content as needed to provide large enough click and
351 // touch areas (<http://crbug.com/168822> and <http://crbug.com/168856>).
352 AddChildView(small_image());
353 AddChildView(close_button());
354 SetAccessibleName(notification);
356 SetEventTargeter(
357 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
360 NotificationView::~NotificationView() {
363 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));
371 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);
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);
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);
394 int content_height = std::max(top_height, kIconSize) + bottom_height;
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);
402 return content_height + GetInsets().height();
405 void NotificationView::Layout() {
406 MessageView::Layout();
407 gfx::Insets insets = GetInsets();
408 int content_width = width() - insets.width();
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);
416 if (message_view_)
417 message_view_->SetLineLimit(GetMessageLineLimit(title_lines, width()));
419 // Top views.
420 int top_height = top_view_->GetHeightForWidth(content_width);
421 top_view_->SetBounds(insets.left(), insets.top(), content_width, top_height);
423 // Icon.
424 icon_view_->SetBounds(insets.left(), insets.top(), kIconSize, kIconSize);
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);
433 void NotificationView::OnFocus() {
434 MessageView::OnFocus();
435 ScrollRectToVisible(GetLocalBounds());
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());
444 gfx::NativeCursor NotificationView::GetCursor(const ui::MouseEvent& event) {
445 if (!clickable_ || !controller_->HasClickedListener(notification_id()))
446 return views::View::GetCursor(event);
448 return views::GetNativeHandCursor();
451 void NotificationView::UpdateWithNotification(
452 const Notification& notification) {
453 MessageView::UpdateWithNotification(notification);
455 CreateOrUpdateViews(notification);
456 SetAccessibleName(notification);
457 Layout();
458 SchedulePaint();
461 void NotificationView::ButtonPressed(views::Button* sender,
462 const ui::Event& event) {
463 // Certain operations can cause |this| to be destructed, so copy the members
464 // we send to other parts of the code.
465 // TODO(dewittj): Remove this hack.
466 std::string id(notification_id());
467 // See if the button pressed was an action button.
468 for (size_t i = 0; i < action_buttons_.size(); ++i) {
469 if (sender == action_buttons_[i]) {
470 controller_->ClickOnNotificationButton(id, i);
471 return;
475 // Let the superclass handled anything other than action buttons.
476 // Warning: This may cause the NotificationView itself to be deleted,
477 // so don't do anything afterwards.
478 MessageView::ButtonPressed(sender, event);
481 void NotificationView::ClickOnNotification(const std::string& notification_id) {
482 controller_->ClickOnNotification(notification_id);
485 void NotificationView::RemoveNotification(const std::string& notification_id,
486 bool by_user) {
487 controller_->RemoveNotification(notification_id, by_user);
490 void NotificationView::CreateOrUpdateTitleView(
491 const Notification& notification) {
492 if (notification.title().empty()) {
493 if (title_view_) {
494 // Deletion will also remove |title_view_| from its parent.
495 delete title_view_;
496 title_view_ = NULL;
498 return;
501 DCHECK(top_view_ != NULL);
503 const gfx::FontList& font_list =
504 views::Label().font_list().DeriveWithSizeDelta(2);
506 int title_character_limit =
507 kNotificationWidth * kMaxTitleLines / kMinPixelsPerTitleCharacter;
509 base::string16 title = gfx::TruncateString(notification.title(),
510 title_character_limit,
511 gfx::WORD_BREAK);
512 if (!title_view_) {
513 int padding = kTitleLineHeight - font_list.GetHeight();
515 title_view_ = new BoundedLabel(title, font_list);
516 title_view_->SetLineHeight(kTitleLineHeight);
517 title_view_->SetLineLimit(kMaxTitleLines);
518 title_view_->SetColors(message_center::kRegularTextColor,
519 kRegularTextBackgroundColor);
520 title_view_->SetBorder(MakeTextBorder(padding, 3, 0));
521 top_view_->AddChildView(title_view_);
522 } else {
523 title_view_->SetText(title);
527 void NotificationView::CreateOrUpdateMessageView(
528 const Notification& notification) {
529 if (notification.message().empty()) {
530 if (message_view_) {
531 // Deletion will also remove |message_view_| from its parent.
532 delete message_view_;
533 message_view_ = NULL;
535 return;
538 DCHECK(top_view_ != NULL);
540 base::string16 text = gfx::TruncateString(notification.message(),
541 kMessageCharacterLimit,
542 gfx::WORD_BREAK);
543 if (!message_view_) {
544 int padding = kMessageLineHeight - views::Label().font_list().GetHeight();
545 message_view_ = new BoundedLabel(text);
546 message_view_->SetLineHeight(kMessageLineHeight);
547 message_view_->SetColors(message_center::kRegularTextColor,
548 kDimTextBackgroundColor);
549 message_view_->SetBorder(MakeTextBorder(padding, 4, 0));
550 top_view_->AddChildView(message_view_);
551 } else {
552 message_view_->SetText(text);
555 message_view_->SetVisible(!notification.items().size());
558 base::string16 NotificationView::FormatContextMessage(
559 const Notification& notification) const {
560 if (notification.UseOriginAsContextMessage()) {
561 const GURL url = notification.origin_url();
562 DCHECK(url.is_valid());
563 return url_formatter::ElideHost(url, views::Label().font_list(),
564 kContextMessageViewWidth);
567 return gfx::TruncateString(notification.context_message(),
568 kContextMessageCharacterLimit, gfx::WORD_BREAK);
571 void NotificationView::CreateOrUpdateContextMessageView(
572 const Notification& notification) {
573 if (notification.context_message().empty() &&
574 !notification.UseOriginAsContextMessage()) {
575 if (context_message_view_) {
576 // Deletion will also remove |context_message_view_| from its parent.
577 delete context_message_view_;
578 context_message_view_ = NULL;
580 return;
583 DCHECK(top_view_ != NULL);
585 base::string16 message = FormatContextMessage(notification);
587 if (!context_message_view_) {
588 int padding = kMessageLineHeight - views::Label().font_list().GetHeight();
589 context_message_view_ = new BoundedLabel(message);
590 context_message_view_->SetLineLimit(
591 message_center::kContextMessageLineLimit);
592 context_message_view_->SetLineHeight(kMessageLineHeight);
593 context_message_view_->SetColors(message_center::kDimTextColor,
594 kContextTextBackgroundColor);
595 context_message_view_->SetBorder(MakeTextBorder(padding, 4, 0));
596 top_view_->AddChildView(context_message_view_);
597 } else {
598 context_message_view_->SetText(message);
602 void NotificationView::CreateOrUpdateProgressBarView(
603 const Notification& notification) {
604 if (notification.type() != NOTIFICATION_TYPE_PROGRESS) {
605 if (progress_bar_view_) {
606 // Deletion will also remove |progress_bar_view_| from its parent.
607 delete progress_bar_view_;
608 progress_bar_view_ = NULL;
610 return;
613 DCHECK(top_view_ != NULL);
615 if (!progress_bar_view_) {
616 progress_bar_view_ = new NotificationProgressBar();
617 progress_bar_view_->SetBorder(MakeProgressBarBorder(
618 message_center::kProgressBarTopPadding, kProgressBarBottomPadding));
619 top_view_->AddChildView(progress_bar_view_);
622 progress_bar_view_->SetValue(notification.progress() / 100.0);
623 progress_bar_view_->SetVisible(!notification.items().size());
626 void NotificationView::CreateOrUpdateListItemViews(
627 const Notification& notification) {
628 for (size_t i = 0; i < item_views_.size(); ++i)
629 delete item_views_[i];
630 item_views_.clear();
632 int padding = kMessageLineHeight - views::Label().font_list().GetHeight();
633 std::vector<NotificationItem> items = notification.items();
635 if (items.size() == 0)
636 return;
638 DCHECK(top_view_);
639 for (size_t i = 0; i < items.size() && i < kNotificationMaximumItems; ++i) {
640 ItemView* item_view = new ItemView(items[i]);
641 item_view->SetBorder(MakeTextBorder(padding, i ? 0 : 4, 0));
642 item_views_.push_back(item_view);
643 top_view_->AddChildView(item_view);
647 void NotificationView::CreateOrUpdateIconView(
648 const Notification& notification) {
649 if (!icon_view_) {
650 icon_view_ = new ProportionalImageView(gfx::Size(kIconSize, kIconSize));
651 AddChildView(icon_view_);
654 gfx::ImageSkia icon = notification.icon().AsImageSkia();
655 if (notification.adjust_icon()) {
656 icon_view_->set_background(
657 views::Background::CreateSolidBackground(kIconBackgroundColor));
658 gfx::Size max_image_size =
659 notification.type() == NOTIFICATION_TYPE_SIMPLE &&
660 (icon.width() < kIconSize || icon.height() < kIconSize ||
661 HasAlpha(icon, GetWidget()))
662 ? gfx::Size(kLegacyIconSize, kLegacyIconSize)
663 : gfx::Size(kIconSize, kIconSize);
664 icon_view_->SetImage(icon, max_image_size);
665 } else {
666 icon_view_->SetImage(icon, icon.size());
667 icon_view_->set_background(nullptr);
671 void NotificationView::CreateOrUpdateImageView(
672 const Notification& notification) {
673 // |image_view_| is the view representing the area covered by the
674 // notification's image, including background and border. Its size can be
675 // specified in advance and images will be scaled to fit including a border if
676 // necessary.
677 if (notification.image().IsEmpty()) {
678 delete image_container_;
679 image_container_ = NULL;
680 image_view_ = NULL;
681 return;
684 gfx::Size ideal_size(kNotificationPreferredImageWidth,
685 kNotificationPreferredImageHeight);
687 if (!image_container_) {
688 DCHECK(!image_view_);
689 DCHECK(bottom_view_);
690 DCHECK_EQ(this, bottom_view_->parent());
692 image_container_ = new views::View();
693 image_container_->SetLayoutManager(new views::FillLayout());
694 image_container_->set_background(views::Background::CreateSolidBackground(
695 message_center::kImageBackgroundColor));
697 image_view_ = new message_center::ProportionalImageView(ideal_size);
698 image_container_->AddChildView(image_view_);
699 bottom_view_->AddChildViewAt(image_container_, 0);
702 DCHECK(image_view_);
703 image_view_->SetImage(notification.image().AsImageSkia(), ideal_size);
705 gfx::Size scaled_size = message_center::GetImageSizeForContainerSize(
706 ideal_size, notification.image().Size());
707 image_view_->SetBorder(ideal_size != scaled_size
708 ? views::Border::CreateSolidBorder(
709 message_center::kNotificationImageBorderSize,
710 SK_ColorTRANSPARENT)
711 : NULL);
714 void NotificationView::CreateOrUpdateActionButtonViews(
715 const Notification& notification) {
716 std::vector<ButtonInfo> buttons = notification.buttons();
717 bool new_buttons = action_buttons_.size() != buttons.size();
719 if (new_buttons || buttons.size() == 0) {
720 // STLDeleteElements also clears the container.
721 STLDeleteElements(&separators_);
722 STLDeleteElements(&action_buttons_);
725 DCHECK(bottom_view_);
726 DCHECK_EQ(this, bottom_view_->parent());
728 for (size_t i = 0; i < buttons.size(); ++i) {
729 ButtonInfo button_info = buttons[i];
730 if (new_buttons) {
731 views::View* separator = new views::ImageView();
732 separator->SetBorder(MakeSeparatorBorder(1, 0, kButtonSeparatorColor));
733 separators_.push_back(separator);
734 bottom_view_->AddChildView(separator);
735 NotificationButton* button = new NotificationButton(this);
736 button->SetTitle(button_info.title);
737 button->SetIcon(button_info.icon.AsImageSkia());
738 action_buttons_.push_back(button);
739 bottom_view_->AddChildView(button);
740 } else {
741 action_buttons_[i]->SetTitle(button_info.title);
742 action_buttons_[i]->SetIcon(button_info.icon.AsImageSkia());
743 action_buttons_[i]->SchedulePaint();
744 action_buttons_[i]->Layout();
748 if (new_buttons) {
749 Layout();
750 views::Widget* widget = GetWidget();
751 if (widget != NULL) {
752 widget->SetSize(widget->GetContentsView()->GetPreferredSize());
753 GetWidget()->SynthesizeMouseMoveEvent();
758 int NotificationView::GetMessageLineLimit(int title_lines, int width) const {
759 // Image notifications require that the image must be kept flush against
760 // their icons, but we can allow more text if no image.
761 int effective_title_lines = std::max(0, title_lines - 1);
762 int line_reduction_from_title = (image_view_ ? 1 : 2) * effective_title_lines;
763 if (!image_view_) {
764 // Title lines are counted as twice as big as message lines for the purpose
765 // of this calculation.
766 // The effect from the title reduction here should be:
767 // * 0 title lines: 5 max lines message.
768 // * 1 title line: 5 max lines message.
769 // * 2 title lines: 3 max lines message.
770 return std::max(
772 message_center::kMessageExpandedLineLimit - line_reduction_from_title);
775 int message_line_limit = message_center::kMessageCollapsedLineLimit;
777 // Subtract any lines taken by the context message.
778 if (context_message_view_) {
779 message_line_limit -= context_message_view_->GetLinesForWidthAndLimit(
780 width,
781 message_center::kContextMessageLineLimit);
784 // The effect from the title reduction here should be:
785 // * 0 title lines: 2 max lines message + context message.
786 // * 1 title line: 2 max lines message + context message.
787 // * 2 title lines: 1 max lines message + context message.
788 message_line_limit =
789 std::max(0, message_line_limit - line_reduction_from_title);
791 return message_line_limit;
794 int NotificationView::GetMessageHeight(int width, int limit) const {
795 return message_view_ ?
796 message_view_->GetSizeForWidthAndLines(width, limit).height() : 0;
799 } // namespace message_center