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