Commit 9f7f7451 authored by dewittj@chromium.org's avatar dewittj@chromium.org

Adds a small icon to notifications, and connects it to synced notifications.

The small icon is a 16x16 image in the bottom right of all notification
templates.  Currently it is not exposed to the JS API, but instead is used
only by internal API systems (starting with synced notifications.)

BUG=284592

Review URL: https://codereview.chromium.org/149433005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@247875 0039d316-1c4b-4281-b951-d872f2087c98
parent ccc08dc2
......@@ -125,6 +125,9 @@ class Notification : public message_center::Notification {
// The URL of a large image to be displayed for a a rich notification.
GURL image_url_;
// The URL of a small image to be displayed for a a rich notification.
GURL small_image_url_;
// The user-supplied replace ID for the notification.
base::string16 replace_id_;
......
......@@ -6,6 +6,7 @@
#include "base/basictypes.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
......@@ -15,9 +16,16 @@
#include "chrome/browser/notifications/sync_notifier/chrome_notifier_delegate.h"
#include "chrome/browser/notifications/sync_notifier/chrome_notifier_service.h"
#include "content/public/browser/browser_thread.h"
#include "skia/ext/image_operations.h"
#include "sync/protocol/sync.pb.h"
#include "sync/protocol/synced_notification_specifics.pb.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/size.h"
#include "ui/gfx/skbitmap_operations.h"
#include "ui/message_center/message_center_style.h"
#include "ui/message_center/message_center_util.h"
#include "ui/message_center/notification_types.h"
......@@ -304,6 +312,28 @@ void SyncedNotification::Show(NotificationUIManager* notification_manager,
if (!image_bitmap_.IsEmpty())
rich_notification_data.image = image_bitmap_;
if (!app_icon_bitmap_.IsEmpty()) {
// Since we can't control the size of images we download, resize using a
// high quality filter down to the appropriate icon size.
// TODO(dewittj): Remove this when correct resources are sent via the
// protobuf.
SkBitmap new_app_icon =
skia::ImageOperations::Resize(app_icon_bitmap_.AsBitmap(),
skia::ImageOperations::RESIZE_BEST,
message_center::kSmallImageSize,
message_center::kSmallImageSize);
// The app icon should be in grayscale.
// TODO(dewittj): Remove this when correct resources are sent via the
// protobuf.
color_utils::HSL shift = {-1, 0, 0.6};
SkBitmap grayscale =
SkBitmapOperations::CreateHSLShiftedBitmap(new_app_icon, shift);
gfx::Image small_image =
gfx::Image(gfx::ImageSkia(gfx::ImageSkiaRep(grayscale, 1.0f)));
rich_notification_data.small_image = small_image;
}
// Set the ContextMessage inside the rich notification data for the
// annotation.
rich_notification_data.context_message = annotation;
......@@ -326,7 +356,6 @@ void SyncedNotification::Show(NotificationUIManager* notification_manager,
replace_key,
rich_notification_data,
delegate.get());
// In case the notification is not supposed to be toasted, pretend that it
// has already been shown.
ui_notification.set_shown_as_popup(!toast_state_);
......
......@@ -330,6 +330,10 @@ TEST_F(SyncedNotificationTest, OnFetchCompleteTest) {
notification1_->OnFetchComplete(GURL(kButtonTwoIconUrl), &bitmap);
// Expect that the app icon has some data in it.
EXPECT_FALSE(notification1_->GetAppIcon().IsEmpty());
EXPECT_FALSE(notification_manager()->notification().small_image().IsEmpty());
// Since we check Show() thoroughly in its own test, we only check cursorily.
EXPECT_EQ(message_center::NOTIFICATION_TYPE_IMAGE,
notification_manager()->notification().type());
......
......@@ -37,6 +37,9 @@ MESSAGE_CENTER_EXPORT
// The button that invokes |-close:|, in the upper-right corner.
base::scoped_nsobject<HoverImageButton> closeButton_;
// The small icon associated with the notification, on the bottom right.
base::scoped_nsobject<NSImageView> smallImage_;
// The large icon associated with the notification, on the left side.
base::scoped_nsobject<NSImageView> icon_;
......@@ -84,6 +87,7 @@ MESSAGE_CENTER_EXPORT
@end
@interface MCNotificationController (TestingInterface)
- (NSImageView*)smallImageView;
- (NSImageView*)iconView;
@end
......
......@@ -200,11 +200,14 @@
// Creates a box that shows a border when the icon is not big enough to fill the
// space.
- (NSBox*)createImageBox;
- (NSBox*)createImageBox:(const gfx::Image&)notificationImage;
// Initializes the closeButton_ ivar with the configured button.
- (void)configureCloseButtonInFrame:(NSRect)rootFrame;
// Initializes the smallImage_ ivar with the appropriate frame.
- (void)configureSmallImageInFrame:(NSRect)rootFrame;
// Initializes title_ in the given frame.
- (void)configureTitleInFrame:(NSRect)rootFrame;
......@@ -229,7 +232,7 @@
// it is too long.
- (base::string16)wrapText:(const base::string16&)text
forFont:(NSFont*)font
maxNumberOfLines:(size_t)lines;
maxNumberOfLines:(size_t)lines;
@end
////////////////////////////////////////////////////////////////////////////////
......@@ -264,6 +267,9 @@
[self configureCloseButtonInFrame:rootFrame];
[rootView addSubview:closeButton_];
[self configureSmallImageInFrame:rootFrame];
[[self view] addSubview:smallImage_];
// Create the title.
[self configureTitleInFrame:rootFrame];
[rootView addSubview:title_];
......@@ -288,6 +294,8 @@
message_center::kNotificationPreferredImageWidth,
message_center::kNotificationIconSize);
[smallImage_ setImage:notification_->small_image().AsNSImage()];
// Update the icon.
[icon_ setImage:notification_->icon().AsNSImage()];
......@@ -607,7 +615,7 @@
return imageBox.autorelease();
}
- (NSBox*)createImageBox:(gfx::Image)notificationImage {
- (NSBox*)createImageBox:(const gfx::Image&)notificationImage {
using message_center::kNotificationImageBorderSize;
using message_center::kNotificationPreferredImageWidth;
using message_center::kNotificationPreferredImageHeight;
......@@ -643,11 +651,15 @@
}
- (void)configureCloseButtonInFrame:(NSRect)rootFrame {
closeButton_.reset([[HoverImageButton alloc] initWithFrame:NSMakeRect(
NSMaxX(rootFrame) - message_center::kControlButtonSize,
NSMaxY(rootFrame) - message_center::kControlButtonSize,
message_center::kControlButtonSize,
message_center::kControlButtonSize)]);
// The close button is configured to be the same size as the small image.
int closeButtonOriginOffset =
message_center::kSmallImageSize + message_center::kSmallImagePadding;
NSRect closeButtonFrame =
NSMakeRect(NSMaxX(rootFrame) - closeButtonOriginOffset,
NSMaxY(rootFrame) - closeButtonOriginOffset,
message_center::kSmallImageSize,
message_center::kSmallImageSize);
closeButton_.reset([[HoverImageButton alloc] initWithFrame:closeButtonFrame]);
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
[closeButton_ setDefaultImage:
rb.GetNativeImageNamed(IDR_NOTIFICATION_CLOSE).ToNSImage()];
......@@ -670,6 +682,19 @@
forAttribute:NSAccessibilityTitleAttribute];
}
- (void)configureSmallImageInFrame:(NSRect)rootFrame {
int smallImageXOffset =
message_center::kSmallImagePadding + message_center::kSmallImageSize;
NSRect smallImageFrame =
NSMakeRect(NSMaxX(rootFrame) - smallImageXOffset,
NSMinY(rootFrame) + message_center::kSmallImagePadding,
message_center::kSmallImageSize,
message_center::kSmallImageSize);
smallImage_.reset([[NSImageView alloc] initWithFrame:smallImageFrame]);
[smallImage_ setImageScaling:NSImageScaleProportionallyUpOrDown];
[smallImage_ setAutoresizingMask:NSViewMinYMargin];
}
- (void)configureTitleInFrame:(NSRect)rootFrame {
NSRect frame = [self currentContentRect];
frame.size.height = 0;
......
......@@ -65,6 +65,10 @@ class MockMessageCenter : public message_center::FakeMessageCenter {
return closeButton_.get();
}
- (NSImageView*)smallImageView {
return smallImage_.get();
}
- (NSButton*)secondButton {
// The buttons are in Cocoa-y-order, so the 2nd button is first.
NSView* view = [[bottomView_ subviews] objectAtIndex:0];
......@@ -122,7 +126,9 @@ TEST_F(NotificationControllerTest, BasicLayout) {
DummyNotifierId(),
message_center::RichNotificationData(),
NULL));
notification->set_icon(gfx::Image([TestIcon() retain]));
gfx::Image testIcon([TestIcon() retain]);
notification->set_icon(testIcon);
notification->set_small_image(testIcon);
base::scoped_nsobject<MCNotificationController> controller(
[[MCNotificationController alloc] initWithNotification:notification.get()
......@@ -130,6 +136,7 @@ TEST_F(NotificationControllerTest, BasicLayout) {
[controller view];
EXPECT_EQ(TestIcon(), [[controller iconView] image]);
EXPECT_EQ(TestIcon(), [[controller smallImageView] image]);
EXPECT_EQ(base::SysNSStringToUTF16([[controller titleView] string]),
notification->title());
EXPECT_EQ(base::SysNSStringToUTF16([[controller messageView] string]),
......@@ -209,11 +216,15 @@ TEST_F(NotificationControllerTest, Update) {
EXPECT_EQ(NSHeight([[controller view] frame]),
message_center::kNotificationIconSize);
EXPECT_FALSE([[controller iconView] image]);
EXPECT_FALSE([[controller smallImageView] image]);
// Update the icon.
notification->set_icon(gfx::Image([TestIcon() retain]));
gfx::Image testIcon([TestIcon() retain]);
notification->set_icon(testIcon);
notification->set_small_image(testIcon);
[controller updateNotification:notification.get()];
EXPECT_EQ(TestIcon(), [[controller iconView] image]);
EXPECT_EQ(TestIcon(), [[controller smallImageView] image]);
EXPECT_EQ(NSHeight([[controller view] frame]),
message_center::kNotificationIconSize);
}
......
......@@ -22,6 +22,8 @@ const int kNotificationImageBorderSize = 10;
const int kNotificationPreferredImageWidth = 360;
const int kNotificationPreferredImageHeight = 240;
const int kSettingsIconSize = 16;
const int kSmallImageSize = 16;
const int kSmallImagePadding = 4;
// Limits.
const size_t kMaxVisibleMessageCenterNotifications = 100;
......
......@@ -39,6 +39,7 @@ RichNotificationData::RichNotificationData(const RichNotificationData& other)
expanded_message(other.expanded_message),
context_message(other.context_message),
image(other.image),
small_image(other.small_image),
items(other.items),
progress(other.progress),
buttons(other.buttons),
......
......@@ -45,6 +45,7 @@ class MESSAGE_CENTER_EXPORT RichNotificationData {
base::string16 expanded_message;
base::string16 context_message;
gfx::Image image;
gfx::Image small_image;
std::vector<NotificationItem> items;
int progress;
std::vector<ButtonInfo> buttons;
......@@ -133,6 +134,11 @@ class MESSAGE_CENTER_EXPORT Notification {
const gfx::Image& image() const { return optional_fields_.image; }
void set_image(const gfx::Image& image) { optional_fields_.image = image; }
const gfx::Image& small_image() const { return optional_fields_.small_image; }
void set_small_image(const gfx::Image& image) {
optional_fields_.small_image = image;
}
// Buttons, with icons fetched asynchronously.
const std::vector<ButtonInfo>& buttons() const {
return optional_fields_.buttons;
......
......@@ -18,6 +18,7 @@
#include "ui/message_center/views/padded_button.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/painter.h"
......@@ -38,6 +39,7 @@ namespace message_center {
MessageView::MessageView(MessageViewController* controller,
const std::string& notification_id,
const NotifierId& notifier_id,
const gfx::ImageSkia& small_image,
const base::string16& display_source)
: controller_(controller),
notification_id_(notification_id),
......@@ -53,6 +55,14 @@ MessageView::MessageView(MessageViewController* controller,
views::Background::CreateSolidBackground(kNotificationBackgroundColor));
AddChildView(background_view_);
views::ImageView* small_image_view = new views::ImageView();
small_image_view->SetImage(small_image);
small_image_view->SetImageSize(gfx::Size(kSmallImageSize, kSmallImageSize));
// The small image view should be added to view hierarchy by the derived
// class. This ensures that it is on top of other views.
small_image_view->set_owned_by_client();
small_image_view_.reset(small_image_view);
PaddedButton *close = new PaddedButton(this);
close->SetPadding(-kCloseIconRightPadding, kCloseIconTopPadding);
close->SetNormalImage(IDR_NOTIFICATION_CLOSE);
......@@ -139,6 +149,8 @@ bool MessageView::OnKeyReleased(const ui::KeyEvent& event) {
}
void MessageView::OnPaint(gfx::Canvas* canvas) {
DCHECK_EQ(this, close_button_->parent());
DCHECK_EQ(this, small_image_view_->parent());
SlideOutView::OnPaint(canvas);
views::Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
}
......@@ -163,9 +175,19 @@ void MessageView::Layout() {
// Close button.
gfx::Size close_size(close_button_->GetPreferredSize());
close_button_->SetBounds(
content_bounds.right() - close_size.width(), content_bounds.y(),
close_size.width(), close_size.height());
gfx::Rect close_rect(content_bounds.right() - close_size.width(),
content_bounds.y(),
close_size.width(),
close_size.height());
close_button_->SetBoundsRect(close_rect);
gfx::Size small_image_size(small_image_view_->GetPreferredSize());
gfx::Rect small_image_rect(small_image_size);
small_image_rect.set_origin(gfx::Point(
content_bounds.right() - small_image_size.width() - kSmallImagePadding,
content_bounds.bottom() - small_image_size.height() -
kSmallImagePadding));
small_image_view_->SetBoundsRect(small_image_rect);
}
void MessageView::OnGestureEvent(ui::GestureEvent* event) {
......
......@@ -19,6 +19,7 @@ class MenuModel;
namespace views {
class ImageButton;
class ImageView;
class Painter;
class ScrollView;
}
......@@ -48,6 +49,7 @@ class MESSAGE_CENTER_EXPORT MessageView : public views::SlideOutView,
MessageView(MessageViewController* controller,
const std::string& notification_id,
const NotifierId& notifier_id,
const gfx::ImageSkia& small_image,
const base::string16& display_source);
virtual ~MessageView();
......@@ -90,6 +92,7 @@ class MESSAGE_CENTER_EXPORT MessageView : public views::SlideOutView,
// Overridden from views::SlideOutView:
virtual void OnSlideOut() OVERRIDE;
views::ImageView* small_image() { return small_image_view_.get(); }
views::ImageButton* close_button() { return close_button_.get(); }
views::ScrollView* scroller() { return scroller_; }
......@@ -99,6 +102,7 @@ class MESSAGE_CENTER_EXPORT MessageView : public views::SlideOutView,
NotifierId notifier_id_;
views::View* background_view_; // Owned by views hierarchy.
scoped_ptr<views::ImageButton> close_button_;
scoped_ptr<views::ImageView> small_image_view_;
views::ScrollView* scroller_;
base::string16 accessible_name_;
......
......@@ -305,6 +305,7 @@ NotificationView::NotificationView(MessageCenterController* controller,
: MessageView(this,
notification.id(),
notification.notifier_id(),
notification.small_image().AsImageSkia(),
notification.display_source()),
controller_(controller),
clickable_(notification.clickable()),
......@@ -465,6 +466,7 @@ NotificationView::NotificationView(MessageCenterController* controller,
AddChildView(top_view_);
AddChildView(icon_view_);
AddChildView(bottom_view_);
AddChildView(small_image());
AddChildView(close_button());
AddChildView(expand_button_);
set_accessible_name(JoinString(accessible_lines, '\n'));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment