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