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