diff options
author | dewittj@chromium.org <dewittj@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-09 23:15:20 +0000 |
---|---|---|
committer | dewittj@chromium.org <dewittj@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-09 23:15:20 +0000 |
commit | a13607a5082b140d4566b62ed97645035ad7e79c (patch) | |
tree | 7e036e6b363e909b4caa5ae19a8c2601e52c0acb /ui/message_center | |
parent | 6c8d40b3c00c4f6514a4ab29d01a22d7f2e6cc7f (diff) | |
download | chromium_src-a13607a5082b140d4566b62ed97645035ad7e79c.zip chromium_src-a13607a5082b140d4566b62ed97645035ad7e79c.tar.gz chromium_src-a13607a5082b140d4566b62ed97645035ad7e79c.tar.bz2 |
Retain popup bubble mouse status even through updates.
Currently rapid updates completely block the user from
interacting with the view even for completely static
content such as the close button.
BUG=368025
Review URL: https://codereview.chromium.org/271773002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@269458 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui/message_center')
-rw-r--r-- | ui/message_center/views/bounded_label.cc | 12 | ||||
-rw-r--r-- | ui/message_center/views/bounded_label.h | 1 | ||||
-rw-r--r-- | ui/message_center/views/message_center_view.cc | 27 | ||||
-rw-r--r-- | ui/message_center/views/message_popup_collection.cc | 18 | ||||
-rw-r--r-- | ui/message_center/views/message_view.cc | 5 | ||||
-rw-r--r-- | ui/message_center/views/message_view.h | 3 | ||||
-rw-r--r-- | ui/message_center/views/notification_view.cc | 394 | ||||
-rw-r--r-- | ui/message_center/views/notification_view.h | 17 | ||||
-rw-r--r-- | ui/message_center/views/notification_view_unittest.cc | 209 | ||||
-rw-r--r-- | ui/message_center/views/toast_contents_view.cc | 9 | ||||
-rw-r--r-- | ui/message_center/views/toast_contents_view.h | 3 |
11 files changed, 490 insertions, 208 deletions
diff --git a/ui/message_center/views/bounded_label.cc b/ui/message_center/views/bounded_label.cc index 9a39fb5..8d43f41 100644 --- a/ui/message_center/views/bounded_label.cc +++ b/ui/message_center/views/bounded_label.cc @@ -43,6 +43,9 @@ class InnerBoundedLabel : public views::Label { gfx::Size GetSizeForWidthAndLines(int width, int lines); std::vector<base::string16> GetWrappedText(int width, int lines); + // Overridden from views::Label. + virtual void SetText(const base::string16& text) OVERRIDE; + protected: // Overridden from views::Label. virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE; @@ -190,6 +193,11 @@ void InnerBoundedLabel::OnPaint(gfx::Canvas* canvas) { } } +void InnerBoundedLabel::SetText(const base::string16& text) { + views::Label::SetText(text); + ClearCaches(); +} + int InnerBoundedLabel::GetTextFlags() { int flags = gfx::Canvas::MULTI_LINE | gfx::Canvas::CHARACTER_BREAK; @@ -293,6 +301,10 @@ void BoundedLabel::SetLineLimit(int lines) { line_limit_ = std::max(lines, -1); } +void BoundedLabel::SetText(const base::string16& text) { + label_->SetText(text); +} + int BoundedLabel::GetLineHeight() const { return label_->line_height(); } diff --git a/ui/message_center/views/bounded_label.h b/ui/message_center/views/bounded_label.h index 2396cd1..9f13949 100644 --- a/ui/message_center/views/bounded_label.h +++ b/ui/message_center/views/bounded_label.h @@ -40,6 +40,7 @@ class MESSAGE_CENTER_EXPORT BoundedLabel : public views::View { void SetColors(SkColor textColor, SkColor backgroundColor); void SetLineHeight(int height); // Pass in 0 for default height. void SetLineLimit(int lines); // Pass in -1 for no limit. + void SetText(const base::string16& text); // Additionally clears caches. int GetLineHeight() const; int GetLineLimit() const; diff --git a/ui/message_center/views/message_center_view.cc b/ui/message_center/views/message_center_view.cc index a290d7a..599b71d 100644 --- a/ui/message_center/views/message_center_view.cc +++ b/ui/message_center/views/message_center_view.cc @@ -113,7 +113,7 @@ class MessageListView : public views::View, void AddNotificationAt(MessageView* view, int i); void RemoveNotification(MessageView* view); - void UpdateNotification(MessageView* view, MessageView* new_view); + void UpdateNotification(MessageView* view, const Notification& notification); void SetRepositionTarget(const gfx::Rect& target_rect); void ResetRepositionSession(); void ClearAllNotifications(const gfx::Rect& visible_scroll_rect); @@ -261,23 +261,17 @@ void MessageListView::RemoveNotification(MessageView* view) { } void MessageListView::UpdateNotification(MessageView* view, - MessageView* new_view) { + const Notification& notification) { int index = GetIndexOf(view); DCHECK_LE(0, index); // GetIndexOf is negative if not a child. if (animator_.get()) animator_->StopAnimatingView(view); - gfx::Rect old_bounds = view->bounds(); if (deleting_views_.find(view) != deleting_views_.end()) deleting_views_.erase(view); if (deleted_when_done_.find(view) != deleted_when_done_.end()) deleted_when_done_.erase(view); - delete view; - AddChildViewAt(new_view, index); - new_view->SetBounds(old_bounds.x(), - old_bounds.y(), - old_bounds.width(), - new_view->GetHeightForWidth(old_bounds.width())); + view->UpdateWithNotification(notification); DoUpdateIfPossible(); } @@ -886,16 +880,11 @@ void MessageCenterView::OnNotificationUpdated(const std::string& id) { for (NotificationList::Notifications::const_iterator iter = notifications.begin(); iter != notifications.end(); ++iter) { if ((*iter)->id() == id) { - NotificationView* new_view = - NotificationView::Create(this, - *(*iter), - false); // Not creating a top-level - // notification. - new_view->set_context_menu_controller(context_menu_controller_.get()); - new_view->set_scroller(scroller_); - message_list_view_->UpdateNotification(view, new_view); - notification_views_[id] = new_view; - NotificationsChanged(); + int old_width = view->width(); + int old_height = view->GetHeightForWidth(old_width); + message_list_view_->UpdateNotification(view, **iter); + if (view->GetHeightForWidth(old_width) != old_height) + NotificationsChanged(); break; } } diff --git a/ui/message_center/views/message_popup_collection.cc b/ui/message_center/views/message_popup_collection.cc index fb4cef9..4dd70a4 100644 --- a/ui/message_center/views/message_popup_collection.cc +++ b/ui/message_center/views/message_popup_collection.cc @@ -450,20 +450,22 @@ void MessagePopupCollection::OnNotificationUpdated( for (NotificationList::PopupNotifications::iterator iter = notifications.begin(); iter != notifications.end(); ++iter) { - if ((*iter)->id() != notification_id) + Notification* notification = *iter; + DCHECK(notification); + ToastContentsView* toast_contents_view = *toast_iter; + DCHECK(toast_contents_view); + + if (notification->id() != notification_id) continue; const RichNotificationData& optional_fields = - (*iter)->rich_notification_data(); + notification->rich_notification_data(); bool a11y_feedback_for_updates = optional_fields.should_make_spoken_feedback_for_popup_updates; - NotificationView* view = - NotificationView::Create(*toast_iter, - *(*iter), - true); // Create top-level notification. - view->set_context_menu_controller(context_menu_controller_.get()); - (*toast_iter)->SetContents(view, a11y_feedback_for_updates); + toast_contents_view->UpdateContents(*notification, + a11y_feedback_for_updates); + updated = true; } diff --git a/ui/message_center/views/message_view.cc b/ui/message_center/views/message_view.cc index 1783533..11ff548 100644 --- a/ui/message_center/views/message_view.cc +++ b/ui/message_center/views/message_view.cc @@ -83,6 +83,11 @@ MessageView::MessageView(MessageViewController* controller, MessageView::~MessageView() { } +void MessageView::UpdateWithNotification(const Notification& notification) { + small_image_view_->SetImage(notification.small_image().AsImageSkia()); + display_source_ = notification.display_source(); +} + // static gfx::Insets MessageView::GetShadowInsets() { return gfx::Insets(kShadowBlur / 2 - kShadowOffset, diff --git a/ui/message_center/views/message_view.h b/ui/message_center/views/message_view.h index 967047a..04f4d97 100644 --- a/ui/message_center/views/message_view.h +++ b/ui/message_center/views/message_view.h @@ -53,6 +53,9 @@ class MESSAGE_CENTER_EXPORT MessageView : public views::SlideOutView, const base::string16& display_source); virtual ~MessageView(); + // Updates this view with the new data contained in the notification. + virtual void UpdateWithNotification(const Notification& notification); + // Returns the insets for the shadow it will have for rich notification. static gfx::Insets GetShadowInsets(); diff --git a/ui/message_center/views/notification_view.cc b/ui/message_center/views/notification_view.cc index 5109a19..0884205 100644 --- a/ui/message_center/views/notification_view.cc +++ b/ui/message_center/views/notification_view.cc @@ -291,6 +291,30 @@ NotificationView* NotificationView::Create(MessageCenterController* controller, return notification_view; } +void NotificationView::CreateOrUpdateViews(const Notification& notification) { + CreateOrUpdateTitleView(notification); + CreateOrUpdateMessageView(notification); + CreateOrUpdateContextMessageView(notification); + CreateOrUpdateProgressBarView(notification); + CreateOrUpdateListItemViews(notification); + CreateOrUpdateIconView(notification); + CreateOrUpdateImageView(notification); + CreateOrUpdateActionButtonViews(notification); +} + +void NotificationView::SetAccessibleName(const Notification& notification) { + std::vector<base::string16> accessible_lines; + accessible_lines.push_back(notification.title()); + accessible_lines.push_back(notification.message()); + accessible_lines.push_back(notification.context_message()); + std::vector<NotificationItem> items = notification.items(); + for (size_t i = 0; i < items.size() && i < kNotificationMaximumItems; ++i) { + accessible_lines.push_back(items[i].title + base::ASCIIToUTF16(" ") + + items[i].message); + } + set_accessible_name(JoinString(accessible_lines, '\n')); +} + NotificationView::NotificationView(MessageCenterController* controller, const Notification& notification) : MessageView(this, @@ -299,8 +323,15 @@ NotificationView::NotificationView(MessageCenterController* controller, notification.small_image().AsImageSkia(), notification.display_source()), controller_(controller), - clickable_(notification.clickable()) { - std::vector<base::string16> accessible_lines; + clickable_(notification.clickable()), + top_view_(NULL), + title_view_(NULL), + message_view_(NULL), + context_message_view_(NULL), + icon_view_(NULL), + bottom_view_(NULL), + image_view_(NULL), + progress_bar_view_(NULL) { // Create the top_view_, which collects into a vertical box all content // at the top of the notification (to the right of the icon) except for the // close button. @@ -309,150 +340,31 @@ NotificationView::NotificationView(MessageCenterController* controller, new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); top_view_->SetBorder( MakeEmptyBorder(kTextTopPadding - 8, 0, kTextBottomPadding - 5, 0)); - - const gfx::FontList default_label_font_list = views::Label().font_list(); - - // Create the title view if appropriate. - title_view_ = NULL; - if (!notification.title().empty()) { - const gfx::FontList& font_list = - default_label_font_list.DeriveWithSizeDelta(2); - int padding = kTitleLineHeight - font_list.GetHeight(); - int title_character_limit = - kNotificationWidth * kMaxTitleLines / kMinPixelsPerTitleCharacter; - title_view_ = new BoundedLabel( - gfx::TruncateString(notification.title(), title_character_limit), - font_list); - title_view_->SetLineHeight(kTitleLineHeight); - title_view_->SetLineLimit(kMaxTitleLines); - title_view_->SetColors(message_center::kRegularTextColor, - kRegularTextBackgroundColor); - title_view_->SetBorder(MakeTextBorder(padding, 3, 0)); - top_view_->AddChildView(title_view_); - accessible_lines.push_back(notification.title()); - } - - // Create the message view if appropriate. - message_view_ = NULL; - if (!notification.message().empty()) { - int padding = kMessageLineHeight - default_label_font_list.GetHeight(); - message_view_ = new BoundedLabel( - gfx::TruncateString(notification.message(), kMessageCharacterLimit)); - message_view_->SetLineHeight(kMessageLineHeight); - message_view_->SetVisible(!notification.items().size()); - message_view_->SetColors(message_center::kRegularTextColor, - kDimTextBackgroundColor); - message_view_->SetBorder(MakeTextBorder(padding, 4, 0)); - top_view_->AddChildView(message_view_); - accessible_lines.push_back(notification.message()); - } - - // Create the context message view if appropriate. - context_message_view_ = NULL; - if (!notification.context_message().empty()) { - int padding = kMessageLineHeight - default_label_font_list.GetHeight(); - context_message_view_ = - new BoundedLabel(gfx::TruncateString(notification.context_message(), - kContextMessageCharacterLimit), - default_label_font_list); - context_message_view_->SetLineLimit( - message_center::kContextMessageLineLimit); - context_message_view_->SetLineHeight(kMessageLineHeight); - context_message_view_->SetColors(message_center::kDimTextColor, - kContextTextBackgroundColor); - context_message_view_->SetBorder(MakeTextBorder(padding, 4, 0)); - top_view_->AddChildView(context_message_view_); - accessible_lines.push_back(notification.context_message()); - } - - // Create the progress bar view. - progress_bar_view_ = NULL; - if (notification.type() == NOTIFICATION_TYPE_PROGRESS) { - progress_bar_view_ = new NotificationProgressBar(); - progress_bar_view_->SetBorder(MakeProgressBarBorder( - message_center::kProgressBarTopPadding, kProgressBarBottomPadding)); - progress_bar_view_->SetValue(notification.progress() / 100.0); - top_view_->AddChildView(progress_bar_view_); - } - - // Create the list item views (up to a maximum). - int padding = kMessageLineHeight - default_label_font_list.GetHeight(); - std::vector<NotificationItem> items = notification.items(); - for (size_t i = 0; i < items.size() && i < kNotificationMaximumItems; ++i) { - ItemView* item_view = new ItemView(items[i]); - item_view->SetBorder(MakeTextBorder(padding, i ? 0 : 4, 0)); - item_views_.push_back(item_view); - top_view_->AddChildView(item_view); - accessible_lines.push_back( - items[i].title + base::ASCIIToUTF16(" ") + items[i].message); - } - - // Create the notification icon view. - gfx::ImageSkia icon = notification.icon().AsImageSkia(); - if (notification.type() == NOTIFICATION_TYPE_SIMPLE && - (icon.width() != kIconSize || - icon.height() != kIconSize || - HasAlpha(icon, GetWidget()))) { - views::ImageView* icon_view = new views::ImageView(); - icon_view->SetImage(icon); - icon_view->SetImageSize(gfx::Size(kLegacyIconSize, kLegacyIconSize)); - icon_view->SetHorizontalAlignment(views::ImageView::CENTER); - icon_view->SetVerticalAlignment(views::ImageView::CENTER); - icon_view_ = icon_view; - } else { - icon_view_ = - new ProportionalImageView(icon, gfx::Size(kIconSize, kIconSize)); - } - - icon_view_->set_background( - views::Background::CreateSolidBackground(kIconBackgroundColor)); - + AddChildView(top_view_); // Create the bottom_view_, which collects into a vertical box all content // below the notification icon. bottom_view_ = new views::View(); bottom_view_->SetLayoutManager( new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); + AddChildView(bottom_view_); - // Create the image view if appropriate. - image_view_ = NULL; - if (!notification.image().IsEmpty()) { - gfx::Size image_size( - kNotificationPreferredImageWidth, kNotificationPreferredImageHeight); - image_view_ = MakeNotificationImage(notification.image(), image_size); - bottom_view_->AddChildView(image_view_); - } - - // Create action buttons if appropriate. - std::vector<ButtonInfo> buttons = notification.buttons(); - for (size_t i = 0; i < buttons.size(); ++i) { - views::View* separator = new views::ImageView(); - separator->SetBorder(MakeSeparatorBorder(1, 0, kButtonSeparatorColor)); - bottom_view_->AddChildView(separator); - NotificationButton* button = new NotificationButton(this); - ButtonInfo button_info = buttons[i]; - button->SetTitle(button_info.title); - button->SetIcon(button_info.icon.AsImageSkia()); - action_buttons_.push_back(button); - bottom_view_->AddChildView(button); - } + CreateOrUpdateViews(notification); // Put together the different content and control views. Layering those allows // for proper layout logic and it also allows the close button and small // image to overlap the content as needed to provide large enough click and // touch areas (<http://crbug.com/168822> and <http://crbug.com/168856>). - AddChildView(top_view_); - AddChildView(icon_view_); - AddChildView(bottom_view_); AddChildView(small_image()); AddChildView(close_button()); - set_accessible_name(JoinString(accessible_lines, '\n')); + SetAccessibleName(notification); } NotificationView::~NotificationView() { } gfx::Size NotificationView::GetPreferredSize() { - int top_width = top_view_->GetPreferredSize().width(); + int top_width = top_view_->GetPreferredSize().width() + + icon_view_->GetPreferredSize().width(); int bottom_width = bottom_view_->GetPreferredSize().width(); int preferred_width = std::max(top_width, bottom_width) + GetInsets().width(); return gfx::Size(preferred_width, GetHeightForWidth(preferred_width)); @@ -559,6 +471,16 @@ gfx::NativeCursor NotificationView::GetCursor(const ui::MouseEvent& event) { return ui::kCursorHand; } +void NotificationView::UpdateWithNotification( + const Notification& notification) { + MessageView::UpdateWithNotification(notification); + + CreateOrUpdateViews(notification); + SetAccessibleName(notification); + Layout(); + SchedulePaint(); +} + void NotificationView::ButtonPressed(views::Button* sender, const ui::Event& event) { // Certain operations can cause |this| to be destructed, so copy the members @@ -588,6 +510,224 @@ void NotificationView::RemoveNotification(const std::string& notification_id, controller_->RemoveNotification(notification_id, by_user); } +void NotificationView::CreateOrUpdateTitleView( + const Notification& notification) { + if (notification.title().empty()) { + if (title_view_) { + // Deletion will also remove |title_view_| from its parent. + delete title_view_; + title_view_ = NULL; + } + return; + } + + DCHECK(top_view_ != NULL); + + const gfx::FontList& font_list = + views::Label().font_list().DeriveWithSizeDelta(2); + + int title_character_limit = + kNotificationWidth * kMaxTitleLines / kMinPixelsPerTitleCharacter; + + if (!title_view_) { + int padding = kTitleLineHeight - font_list.GetHeight(); + + title_view_ = new BoundedLabel( + gfx::TruncateString(notification.title(), title_character_limit), + font_list); + title_view_->SetLineHeight(kTitleLineHeight); + title_view_->SetLineLimit(kMaxTitleLines); + title_view_->SetColors(message_center::kRegularTextColor, + kRegularTextBackgroundColor); + title_view_->SetBorder(MakeTextBorder(padding, 3, 0)); + top_view_->AddChildView(title_view_); + } else { + title_view_->SetText( + gfx::TruncateString(notification.title(), title_character_limit)); + } +} + +void NotificationView::CreateOrUpdateMessageView( + const Notification& notification) { + if (notification.message().empty()) { + if (message_view_) { + // Deletion will also remove |message_view_| from its parent. + delete message_view_; + message_view_ = NULL; + } + return; + } + + DCHECK(top_view_ != NULL); + + if (!message_view_) { + int padding = kMessageLineHeight - views::Label().font_list().GetHeight(); + message_view_ = new BoundedLabel( + gfx::TruncateString(notification.message(), kMessageCharacterLimit)); + message_view_->SetLineHeight(kMessageLineHeight); + message_view_->SetColors(message_center::kRegularTextColor, + kDimTextBackgroundColor); + message_view_->SetBorder(MakeTextBorder(padding, 4, 0)); + top_view_->AddChildView(message_view_); + } else { + message_view_->SetText( + gfx::TruncateString(notification.message(), kMessageCharacterLimit)); + } + + message_view_->SetVisible(!notification.items().size()); +} + +void NotificationView::CreateOrUpdateContextMessageView( + const Notification& notification) { + if (notification.context_message().empty()) { + if (context_message_view_) { + // Deletion will also remove |context_message_view_| from its parent. + delete context_message_view_; + context_message_view_ = NULL; + } + return; + } + + DCHECK(top_view_ != NULL); + + if (!context_message_view_) { + int padding = kMessageLineHeight - views::Label().font_list().GetHeight(); + context_message_view_ = new BoundedLabel(gfx::TruncateString( + notification.context_message(), kContextMessageCharacterLimit)); + context_message_view_->SetLineLimit( + message_center::kContextMessageLineLimit); + context_message_view_->SetLineHeight(kMessageLineHeight); + context_message_view_->SetColors(message_center::kDimTextColor, + kContextTextBackgroundColor); + context_message_view_->SetBorder(MakeTextBorder(padding, 4, 0)); + top_view_->AddChildView(context_message_view_); + } else { + context_message_view_->SetText(gfx::TruncateString( + notification.context_message(), kContextMessageCharacterLimit)); + } +} + +void NotificationView::CreateOrUpdateProgressBarView( + const Notification& notification) { + if (notification.type() != NOTIFICATION_TYPE_PROGRESS) { + if (progress_bar_view_) { + // Deletion will also remove |progress_bar_view_| from its parent. + delete progress_bar_view_; + progress_bar_view_ = NULL; + } + return; + } + + DCHECK(top_view_ != NULL); + + if (!progress_bar_view_) { + progress_bar_view_ = new NotificationProgressBar(); + progress_bar_view_->SetBorder(MakeProgressBarBorder( + message_center::kProgressBarTopPadding, kProgressBarBottomPadding)); + top_view_->AddChildView(progress_bar_view_); + } + + progress_bar_view_->SetValue(notification.progress() / 100.0); + progress_bar_view_->SetVisible(!notification.items().size()); +} + +void NotificationView::CreateOrUpdateListItemViews( + const Notification& notification) { + for (size_t i = 0; i < item_views_.size(); ++i) + delete item_views_[i]; + item_views_.clear(); + + int padding = kMessageLineHeight - views::Label().font_list().GetHeight(); + std::vector<NotificationItem> items = notification.items(); + + if (items.size() == 0) + return; + + DCHECK(top_view_); + for (size_t i = 0; i < items.size() && i < kNotificationMaximumItems; ++i) { + ItemView* item_view = new ItemView(items[i]); + item_view->SetBorder(MakeTextBorder(padding, i ? 0 : 4, 0)); + item_views_.push_back(item_view); + top_view_->AddChildView(item_view); + } +} + +void NotificationView::CreateOrUpdateIconView( + const Notification& notification) { + if (icon_view_) { + delete icon_view_; + icon_view_ = NULL; + } + + // TODO(dewittj): Detect a compatible update and use the existing icon view. + gfx::ImageSkia icon = notification.icon().AsImageSkia(); + if (notification.type() == NOTIFICATION_TYPE_SIMPLE && + (icon.width() != kIconSize || icon.height() != kIconSize || + HasAlpha(icon, GetWidget()))) { + views::ImageView* icon_view = new views::ImageView(); + icon_view->SetImage(icon); + icon_view->SetImageSize(gfx::Size(kLegacyIconSize, kLegacyIconSize)); + icon_view->SetHorizontalAlignment(views::ImageView::CENTER); + icon_view->SetVerticalAlignment(views::ImageView::CENTER); + icon_view_ = icon_view; + } else { + icon_view_ = + new ProportionalImageView(icon, gfx::Size(kIconSize, kIconSize)); + } + + icon_view_->set_background( + views::Background::CreateSolidBackground(kIconBackgroundColor)); + + AddChildView(icon_view_); +} + +void NotificationView::CreateOrUpdateImageView( + const Notification& notification) { + if (image_view_) { + delete image_view_; + image_view_ = NULL; + } + + DCHECK(bottom_view_); + DCHECK_EQ(this, bottom_view_->parent()); + + // TODO(dewittj): Detect a compatible update and use the existing image view. + if (!notification.image().IsEmpty()) { + gfx::Size image_size(kNotificationPreferredImageWidth, + kNotificationPreferredImageHeight); + image_view_ = MakeNotificationImage(notification.image(), image_size); + bottom_view_->AddChildView(image_view_); + } +} + +void NotificationView::CreateOrUpdateActionButtonViews( + const Notification& notification) { + for (size_t i = 0; i < separators_.size(); ++i) + delete separators_[i]; + separators_.clear(); + + for (size_t i = 0; i < action_buttons_.size(); ++i) + delete action_buttons_[i]; + action_buttons_.clear(); + + DCHECK(bottom_view_); + DCHECK_EQ(this, bottom_view_->parent()); + + std::vector<ButtonInfo> buttons = notification.buttons(); + for (size_t i = 0; i < buttons.size(); ++i) { + views::View* separator = new views::ImageView(); + separator->SetBorder(MakeSeparatorBorder(1, 0, kButtonSeparatorColor)); + separators_.push_back(separator); + bottom_view_->AddChildView(separator); + NotificationButton* button = new NotificationButton(this); + ButtonInfo button_info = buttons[i]; + button->SetTitle(button_info.title); + button->SetIcon(button_info.icon.AsImageSkia()); + action_buttons_.push_back(button); + bottom_view_->AddChildView(button); + } +} + int NotificationView::GetMessageLineLimit(int title_lines, int width) { // Image notifications require that the image must be kept flush against // their icons, but we can allow more text if no image. diff --git a/ui/message_center/views/notification_view.h b/ui/message_center/views/notification_view.h index 3a35169..0b2f9c8 100644 --- a/ui/message_center/views/notification_view.h +++ b/ui/message_center/views/notification_view.h @@ -51,6 +51,8 @@ class MESSAGE_CENTER_EXPORT NotificationView : public MessageView, virtual gfx::NativeCursor GetCursor(const ui::MouseEvent& event) OVERRIDE; // Overridden from MessageView: + virtual void UpdateWithNotification( + const Notification& notification) OVERRIDE; virtual void ButtonPressed(views::Button* sender, const ui::Event& event) OVERRIDE; @@ -68,7 +70,21 @@ class MESSAGE_CENTER_EXPORT NotificationView : public MessageView, const Notification& notification); private: + FRIEND_TEST_ALL_PREFIXES(NotificationViewTest, CreateOrUpdateTest); FRIEND_TEST_ALL_PREFIXES(NotificationViewTest, TestLineLimits); + + void CreateOrUpdateViews(const Notification& notification); + void SetAccessibleName(const Notification& notification); + + void CreateOrUpdateTitleView(const Notification& notification); + void CreateOrUpdateMessageView(const Notification& notification); + void CreateOrUpdateContextMessageView(const Notification& notification); + void CreateOrUpdateProgressBarView(const Notification& notification); + void CreateOrUpdateListItemViews(const Notification& notification); + void CreateOrUpdateIconView(const Notification& notification); + void CreateOrUpdateImageView(const Notification& notification); + void CreateOrUpdateActionButtonViews(const Notification& notification); + int GetMessageLineLimit(int title_lines, int width); int GetMessageHeight(int width, int limit); @@ -88,6 +104,7 @@ class MESSAGE_CENTER_EXPORT NotificationView : public MessageView, views::View* image_view_; views::ProgressBar* progress_bar_view_; std::vector<views::View*> action_buttons_; + std::vector<views::View*> separators_; DISALLOW_COPY_AND_ASSIGN(NotificationView); }; diff --git a/ui/message_center/views/notification_view_unittest.cc b/ui/message_center/views/notification_view_unittest.cc index 8868628..0ca8e0a 100644 --- a/ui/message_center/views/notification_view_unittest.cc +++ b/ui/message_center/views/notification_view_unittest.cc @@ -10,68 +10,169 @@ #include "third_party/skia/include/core/SkBitmap.h" #include "ui/gfx/image/image.h" #include "ui/message_center/notification.h" +#include "ui/message_center/notification_list.h" +#include "ui/message_center/notification_types.h" +#include "ui/message_center/views/message_center_controller.h" namespace message_center { /* Test fixture ***************************************************************/ -typedef testing::Test NotificationViewTest; +class NotificationViewTest : public testing::Test, + public MessageCenterController { + public: + NotificationViewTest(); + virtual ~NotificationViewTest(); -TEST_F(NotificationViewTest, TestLineLimits) { - message_center::RichNotificationData data; - std::string id("id"); - NotifierId notifier_id(NotifierId::APPLICATION, "notifier"); - scoped_ptr<Notification> notification( - new Notification(NOTIFICATION_TYPE_BASE_FORMAT, - id, - base::UTF8ToUTF16("test title"), - base::UTF8ToUTF16("test message"), - gfx::Image(), - base::string16() /* display_source */, - notifier_id, - data, - NULL /* delegate */)); - scoped_ptr<NotificationView> view(new NotificationView(NULL, *notification)); - - EXPECT_EQ(5, view->GetMessageLineLimit(0, 360)); - EXPECT_EQ(5, view->GetMessageLineLimit(1, 360)); - EXPECT_EQ(3, view->GetMessageLineLimit(2, 360)); + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + NotificationView* notification_view() { return notification_view_.get(); } + Notification* notification() { return notification_.get(); } + RichNotificationData* data() { return data_.get(); } + + // Overridden from MessageCenterController: + virtual void ClickOnNotification(const std::string& notification_id) OVERRIDE; + virtual void RemoveNotification(const std::string& notification_id, + bool by_user) OVERRIDE; + virtual scoped_ptr<ui::MenuModel> CreateMenuModel( + const NotifierId& notifier_id, + const base::string16& display_source) OVERRIDE; + virtual bool HasClickedListener(const std::string& notification_id) OVERRIDE; + virtual void ClickOnNotificationButton(const std::string& notification_id, + int button_index) OVERRIDE; + + protected: + const gfx::Image CreateTestImage(int width, int height) { + return gfx::Image::CreateFrom1xBitmap(CreateBitmap(width, height)); + } + + const SkBitmap CreateBitmap(int width, int height) { + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); + bitmap.allocPixels(); + bitmap.eraseRGB(0, 255, 0); + return bitmap; + } + + private: + scoped_ptr<RichNotificationData> data_; + scoped_ptr<Notification> notification_; + scoped_ptr<NotificationView> notification_view_; + + DISALLOW_COPY_AND_ASSIGN(NotificationViewTest); +}; + +NotificationViewTest::NotificationViewTest() { +} +NotificationViewTest::~NotificationViewTest() { +} + +void NotificationViewTest::SetUp() { + // Create a dummy notification. SkBitmap bitmap; - bitmap.setConfig(SkBitmap::kARGB_8888_Config, 2, 2); - bitmap.allocPixels(); - bitmap.eraseColor(SK_ColorGREEN); - data.image = gfx::Image::CreateFrom1xBitmap(bitmap); - notification.reset(new Notification(NOTIFICATION_TYPE_BASE_FORMAT, - id, - base::UTF8ToUTF16("test title"), - base::UTF8ToUTF16("test message"), - gfx::Image(), - base::string16() /* display_source */, - notifier_id, - data, - NULL /* delegate */)); - view.reset(new NotificationView(NULL, *notification)); - - EXPECT_EQ(2, view->GetMessageLineLimit(0, 360)); - EXPECT_EQ(2, view->GetMessageLineLimit(1, 360)); - EXPECT_EQ(1, view->GetMessageLineLimit(2, 360)); - - data.context_message = base::UTF8ToUTF16("foo"); - notification.reset(new Notification(NOTIFICATION_TYPE_BASE_FORMAT, - id, - base::UTF8ToUTF16("test title"), - base::UTF8ToUTF16("test message"), - gfx::Image(), - base::string16() /* display_source */, - notifier_id, - data, - NULL /* delegate */)); - view.reset(new NotificationView(NULL, *notification)); - - EXPECT_EQ(1, view->GetMessageLineLimit(0, 360)); - EXPECT_EQ(1, view->GetMessageLineLimit(1, 360)); - EXPECT_EQ(0, view->GetMessageLineLimit(2, 360)); + data_.reset(new RichNotificationData()); + notification_.reset( + new Notification(NOTIFICATION_TYPE_BASE_FORMAT, + std::string("notification id"), + base::UTF8ToUTF16("title"), + base::UTF8ToUTF16("message"), + CreateTestImage(80, 80), + base::UTF8ToUTF16("display source"), + NotifierId(NotifierId::APPLICATION, "extension_id"), + *data_, + NULL)); + notification_->set_small_image(CreateTestImage(16, 16)); + notification_->set_image(CreateTestImage(320, 240)); + + // Then create a new NotificationView with that single notification. + notification_view_.reset( + NotificationView::Create(this, *notification_, true)); +} + +void NotificationViewTest::TearDown() { + notification_view_.reset(); +} + +void NotificationViewTest::ClickOnNotification( + const std::string& notification_id) { + // For this test, this method should not be invoked. + NOTREACHED(); +} + +void NotificationViewTest::RemoveNotification( + const std::string& notification_id, + bool by_user) { + // For this test, this method should not be invoked. + NOTREACHED(); +} + +scoped_ptr<ui::MenuModel> NotificationViewTest::CreateMenuModel( + const NotifierId& notifier_id, + const base::string16& display_source) { + // For this test, this method should not be invoked. + NOTREACHED(); + return scoped_ptr<ui::MenuModel>(); +} + +bool NotificationViewTest::HasClickedListener( + const std::string& notification_id) { + return true; +} + +void NotificationViewTest::ClickOnNotificationButton( + const std::string& notification_id, + int button_index) { + // For this test, this method should not be invoked. + NOTREACHED(); +} + +/* Unit tests *****************************************************************/ + +TEST_F(NotificationViewTest, CreateOrUpdateTest) { + EXPECT_TRUE(NULL != notification_view()->title_view_); + EXPECT_TRUE(NULL != notification_view()->message_view_); + EXPECT_TRUE(NULL != notification_view()->icon_view_); + EXPECT_TRUE(NULL != notification_view()->image_view_); + + notification()->set_image(gfx::Image()); + notification()->set_title(base::ASCIIToUTF16("")); + notification()->set_message(base::ASCIIToUTF16("")); + notification()->set_icon(gfx::Image()); + + notification_view()->CreateOrUpdateViews(*notification()); + EXPECT_TRUE(NULL == notification_view()->title_view_); + EXPECT_TRUE(NULL == notification_view()->message_view_); + EXPECT_TRUE(NULL == notification_view()->image_view_); + // We still expect an icon view for all layouts. + EXPECT_TRUE(NULL != notification_view()->icon_view_); +} + +TEST_F(NotificationViewTest, TestLineLimits) { + notification()->set_image(CreateTestImage(0, 0)); + notification()->set_context_message(base::ASCIIToUTF16("")); + notification_view()->CreateOrUpdateViews(*notification()); + + EXPECT_EQ(5, notification_view()->GetMessageLineLimit(0, 360)); + EXPECT_EQ(5, notification_view()->GetMessageLineLimit(1, 360)); + EXPECT_EQ(3, notification_view()->GetMessageLineLimit(2, 360)); + + notification()->set_image(CreateTestImage(2, 2)); + notification_view()->CreateOrUpdateViews(*notification()); + + EXPECT_EQ(2, notification_view()->GetMessageLineLimit(0, 360)); + EXPECT_EQ(2, notification_view()->GetMessageLineLimit(1, 360)); + EXPECT_EQ(1, notification_view()->GetMessageLineLimit(2, 360)); + + notification()->set_context_message(base::UTF8ToUTF16("foo")); + notification_view()->CreateOrUpdateViews(*notification()); + + EXPECT_TRUE(notification_view()->context_message_view_ != NULL); + + EXPECT_EQ(1, notification_view()->GetMessageLineLimit(0, 360)); + EXPECT_EQ(1, notification_view()->GetMessageLineLimit(1, 360)); + EXPECT_EQ(0, notification_view()->GetMessageLineLimit(2, 360)); } } // namespace message_center diff --git a/ui/message_center/views/toast_contents_view.cc b/ui/message_center/views/toast_contents_view.cc index 690e694..d004e57 100644 --- a/ui/message_center/views/toast_contents_view.cc +++ b/ui/message_center/views/toast_contents_view.cc @@ -87,6 +87,15 @@ void ToastContentsView::SetContents(MessageView* view, NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, false); } +void ToastContentsView::UpdateContents(const Notification& notification, + bool a11y_feedback_for_updates) { + DCHECK_GT(child_count(), 0); + MessageView* message_view = static_cast<MessageView*>(child_at(0)); + message_view->UpdateWithNotification(notification); + if (a11y_feedback_for_updates) + NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, false); +} + void ToastContentsView::RevealWithAnimation(gfx::Point origin) { // Place/move the toast widgets. Currently it stacks the widgets from the // right-bottom of the work area. diff --git a/ui/message_center/views/toast_contents_view.h b/ui/message_center/views/toast_contents_view.h index 456703c..8e80b73 100644 --- a/ui/message_center/views/toast_contents_view.h +++ b/ui/message_center/views/toast_contents_view.h @@ -50,6 +50,9 @@ class ToastContentsView : public views::WidgetDelegateView, // accessibility message should be read after this update. void SetContents(MessageView* view, bool a11y_feedback_for_updates); + void UpdateContents(const Notification& notification, + bool a11y_feedback_for_updates); + // Shows the new toast for the first time, animated. // |origin| is the right-bottom corner of the toast. void RevealWithAnimation(gfx::Point origin); |