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 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 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 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 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. 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 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 147 ItemView::~ItemView() { 148 } 149 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 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 204 NotificationProgressBar::NotificationProgressBar() { 205 } 206 207 NotificationProgressBar::~NotificationProgressBar() { 208 } 209 210 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 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 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 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 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 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 360 NotificationView::~NotificationView() { 361 } 362 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)); 369 } 370 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); 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 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 433 void NotificationView::OnFocus() { 434 MessageView::OnFocus(); 435 ScrollRectToVisible(GetLocalBounds()); 436 } 437 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 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 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 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 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 503 void NotificationView::ClickOnNotification(const std::string& notification_id) { 504 controller_->ClickOnNotification(notification_id); 505 } 506 507 void NotificationView::RemoveNotification(const std::string& notification_id, 508 bool by_user) { 509 controller_->RemoveNotification(notification_id, by_user); 510 } 511 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 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 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 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 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 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 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 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 746 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 782 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