Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / ui / message_center / cocoa / notification_controller.mm
index 4095a96..5f642eb 100644 (file)
@@ -4,6 +4,8 @@
 
 #import "ui/message_center/cocoa/notification_controller.h"
 
+#include <algorithm>
+
 #include "base/mac/foundation_util.h"
 #include "base/strings/string_util.h"
 #include "base/strings/sys_string_conversions.h"
@@ -14,7 +16,9 @@
 #import "ui/base/cocoa/hover_image_button.h"
 #include "ui/base/l10n/l10n_util_mac.h"
 #include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/font_list.h"
 #include "ui/gfx/text_elider.h"
+#include "ui/gfx/text_utils.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/message_center_style.h"
 #include "ui/message_center/notification.h"
 @end
 
 ////////////////////////////////////////////////////////////////////////////////
+@interface MCNotificationButton : NSButton
+@end
+
+@implementation MCNotificationButton
+// drawRect: needs to fill the button with a background, otherwise we don't get
+// subpixel antialiasing.
+- (void)drawRect:(NSRect)dirtyRect {
+  NSColor* color = gfx::SkColorToCalibratedNSColor(
+      message_center::kNotificationBackgroundColor);
+  [color set];
+  NSRectFill(dirtyRect);
+  [super drawRect:dirtyRect];
+}
+@end
 
 @interface MCNotificationButtonCell : NSButtonCell {
   BOOL hovered_;
 }
 @end
 
+////////////////////////////////////////////////////////////////////////////////
 @implementation MCNotificationButtonCell
+- (BOOL)isOpaque {
+  return YES;
+}
+
 - (void)drawBezelWithFrame:(NSRect)frame inView:(NSView*)controlView {
   // Else mouseEntered: and mouseExited: won't be called and hovered_ won't be
   // valid.
 - (void)configureCustomBox:(NSBox*)box;
 
 // Initializes the icon_ ivar and returns the view to insert into the hierarchy.
-- (NSView*)createImageView;
+- (NSView*)createIconView;
+
+// Creates a box that shows a border when the icon is not big enough to fill the
+// space.
+- (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;
 
 // more than the given number of lines. The wrapped text would be painted using
 // the given font. The Ellipsis could be added at the end of the last line if
 // it is too long.
-- (string16)wrapText:(const string16&)text
-             forFont:(NSFont*)font
-    maxNumberOfLines:(size_t)lines;
+- (base::string16)wrapText:(const base::string16&)text
+                   forFont:(NSFont*)font
+          maxNumberOfLines:(size_t)lines;
 @end
 
 ////////////////////////////////////////////////////////////////////////////////
 - (void)loadView {
   // Create the root view of the notification.
   NSRect rootFrame = NSMakeRect(0, 0,
-      message_center::kNotificationPreferredImageSize,
+      message_center::kNotificationPreferredImageWidth,
       message_center::kNotificationIconSize);
   base::scoped_nsobject<MCNotificationView> rootView(
       [[MCNotificationView alloc] initWithController:self frame:rootFrame]);
       message_center::kNotificationBackgroundColor)];
   [self setView:rootView];
 
-  [rootView addSubview:[self createImageView]];
+  [rootView addSubview:[self createIconView]];
 
   // Create the close button.
   [self configureCloseButtonInFrame:rootFrame];
   [rootView addSubview:closeButton_];
 
+  // Create the small image.
+  [self configureSmallImageInFrame:rootFrame];
+  [[self view] addSubview:smallImage_];
+
+  NSRect contentFrame = [self currentContentRect];
+
   // Create the title.
-  [self configureTitleInFrame:rootFrame];
+  [self configureTitleInFrame:contentFrame];
   [rootView addSubview:title_];
 
   // Create the message body.
-  [self configureBodyInFrame:rootFrame];
+  [self configureBodyInFrame:contentFrame];
   [rootView addSubview:message_];
 
   // Create the context message body.
-  [self configureContextMessageInFrame:rootFrame];
+  [self configureContextMessageInFrame:contentFrame];
   [rootView addSubview:contextMessage_];
 
   // Populate the data.
   notification_ = notification;
 
   NSRect rootFrame = NSMakeRect(0, 0,
-      message_center::kNotificationPreferredImageSize,
+      message_center::kNotificationPreferredImageWidth,
       message_center::kNotificationIconSize);
 
+  [smallImage_ setImage:notification_->small_image().AsNSImage()];
+
   // Update the icon.
   [icon_ setImage:notification_->icon().AsNSImage()];
 
       [[itemView textContainer] setWidthTracksTextView:NO];
 
       // Construct the text from the title and message.
-      string16 text =
+      base::string16 text =
           items[i].title + base::UTF8ToUTF16(" ") + items[i].message;
-      string16 ellidedText =
+      base::string16 ellidedText =
           [self wrapText:text forFont:font maxNumberOfLines:1];
       [itemView setString:base::SysUTF16ToNSString(ellidedText)];
 
     NSRect buttonFrame = frame;
     buttonFrame.origin = NSMakePoint(0, y);
     buttonFrame.size.height = message_center::kButtonHeight;
-    base::scoped_nsobject<NSButton> button(
-        [[NSButton alloc] initWithFrame:buttonFrame]);
+    base::scoped_nsobject<MCNotificationButton> button(
+        [[MCNotificationButton alloc] initWithFrame:buttonFrame]);
     base::scoped_nsobject<MCNotificationButtonCell> cell(
         [[MCNotificationButtonCell alloc]
             initTextCell:base::SysUTF16ToNSString(buttonInfo.title)]);
   }
 
   // Create the image view if appropriate.
-  if (!notification->image().IsEmpty()) {
-    NSImage* image = notification->image().AsNSImage();
-    NSRect imageFrame = frame;
-    imageFrame.origin = NSMakePoint(0, y);
-    imageFrame.size = NSSizeFromCGSize(message_center::GetImageSizeForWidth(
-        NSWidth(frame), notification->image().Size()).ToCGSize());
-    base::scoped_nsobject<NSImageView> imageView(
-        [[NSImageView alloc] initWithFrame:imageFrame]);
-    [imageView setImage:image];
-    [imageView setImageScaling:NSImageScaleProportionallyUpOrDown];
-    y += NSHeight(imageFrame);
-    frame.size.height += NSHeight(imageFrame);
-    [bottomView_ addSubview:imageView];
+  gfx::Image notificationImage = notification->image();
+  if (!notificationImage.IsEmpty()) {
+    NSBox* imageBox = [self createImageBox:notificationImage];
+    NSRect outerFrame = frame;
+    outerFrame.origin = NSMakePoint(0, y);
+    outerFrame.size = [imageBox frame].size;
+    [imageBox setFrame:outerFrame];
+
+    y += NSHeight(outerFrame);
+    frame.size.height += NSHeight(outerFrame);
+
+    [bottomView_ addSubview:imageBox];
   }
 
   [bottomView_ setFrame:frame];
   [box setContentViewMargins:NSZeroSize];
 }
 
-- (NSView*)createImageView {
+- (NSView*)createIconView {
   // Create another box that shows a background color when the icon is not
   // big enough to fill the space.
   NSRect imageFrame = NSMakeRect(0, 0,
   return imageBox.autorelease();
 }
 
+- (NSBox*)createImageBox:(const gfx::Image&)notificationImage {
+  using message_center::kNotificationImageBorderSize;
+  using message_center::kNotificationPreferredImageWidth;
+  using message_center::kNotificationPreferredImageHeight;
+
+  NSRect imageFrame = NSMakeRect(0, 0,
+       kNotificationPreferredImageWidth,
+       kNotificationPreferredImageHeight);
+  base::scoped_nsobject<NSBox> imageBox(
+      [[AccessibilityIgnoredBox alloc] initWithFrame:imageFrame]);
+  [self configureCustomBox:imageBox];
+  [imageBox setFillColor:gfx::SkColorToCalibratedNSColor(
+      message_center::kImageBackgroundColor)];
+
+  // Images with non-preferred aspect ratios get a border on all sides.
+  gfx::Size idealSize = gfx::Size(
+      kNotificationPreferredImageWidth, kNotificationPreferredImageHeight);
+  gfx::Size scaledSize = message_center::GetImageSizeForContainerSize(
+      idealSize, notificationImage.Size());
+  if (scaledSize != idealSize) {
+    NSSize borderSize =
+        NSMakeSize(kNotificationImageBorderSize, kNotificationImageBorderSize);
+    [imageBox setContentViewMargins:borderSize];
+  }
+
+  NSImage* image = notificationImage.AsNSImage();
+  base::scoped_nsobject<NSImageView> imageView(
+      [[NSImageView alloc] initWithFrame:imageFrame]);
+  [imageView setImage:image];
+  [imageView setImageScaling:NSImageScaleProportionallyUpOrDown];
+  [imageBox setContentView:imageView];
+
+  return imageBox.autorelease();
+}
+
 - (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()];
                        forAttribute:NSAccessibilityTitleAttribute];
 }
 
-- (void)configureTitleInFrame:(NSRect)rootFrame {
-  NSRect frame = [self currentContentRect];
-  frame.size.height = 0;
-  title_.reset([self newLabelWithFrame:frame]);
+- (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)contentFrame {
+  contentFrame.size.height = 0;
+  title_.reset([self newLabelWithFrame:contentFrame]);
   [title_ setAutoresizingMask:NSViewMinYMargin];
   [title_ setTextColor:gfx::SkColorToCalibratedNSColor(
       message_center::kRegularTextColor)];
   [title_ setFont:[NSFont messageFontOfSize:message_center::kTitleFontSize]];
 }
 
-- (void)configureBodyInFrame:(NSRect)rootFrame {
-  NSRect frame = [self currentContentRect];
-  frame.size.height = 0;
-  message_.reset([self newLabelWithFrame:frame]);
+- (void)configureBodyInFrame:(NSRect)contentFrame {
+  contentFrame.size.height = 0;
+  message_.reset([self newLabelWithFrame:contentFrame]);
   [message_ setAutoresizingMask:NSViewMinYMargin];
   [message_ setTextColor:gfx::SkColorToCalibratedNSColor(
       message_center::kRegularTextColor)];
       [NSFont messageFontOfSize:message_center::kMessageFontSize]];
 }
 
-- (void)configureContextMessageInFrame:(NSRect)rootFrame {
-  NSRect frame = [self currentContentRect];
-  frame.size.height = 0;
-  contextMessage_.reset([self newLabelWithFrame:frame]);
+- (void)configureContextMessageInFrame:(NSRect)contentFrame {
+  contentFrame.size.height = 0;
+  contextMessage_.reset([self newLabelWithFrame:contentFrame]);
   [contextMessage_ setAutoresizingMask:NSViewMinYMargin];
   [contextMessage_ setTextColor:gfx::SkColorToCalibratedNSColor(
       message_center::kDimTextColor)];
 
 - (NSTextView*)newLabelWithFrame:(NSRect)frame {
   NSTextView* label = [[NSTextView alloc] initWithFrame:frame];
-  [label setDrawsBackground:NO];
+
+  // The labels MUST draw their background so that subpixel antialiasing can
+  // happen on the text.
+  [label setDrawsBackground:YES];
+  [label setBackgroundColor:gfx::SkColorToCalibratedNSColor(
+      message_center::kNotificationBackgroundColor)];
+
   [label setEditable:NO];
   [label setSelectable:NO];
   [label setTextContainerInset:NSMakeSize(0.0f, 0.0f)];
 - (NSRect)currentContentRect {
   DCHECK(icon_);
   DCHECK(closeButton_);
+  DCHECK(smallImage_);
 
   NSRect iconFrame, contentFrame;
   NSDivideRect([[self view] bounds], &iconFrame, &contentFrame,
       NSWidth([icon_ frame]) + message_center::kIconToTextPadding,
       NSMinXEdge);
-  contentFrame.size.width -= NSWidth([closeButton_ frame]);
+  // The content area is between the icon on the left and the control area
+  // on the right.
+  int controlAreaWidth =
+      std::max(NSWidth([closeButton_ frame]), NSWidth([smallImage_ frame]));
+  contentFrame.size.width -=
+      2 * message_center::kSmallImagePadding + controlAreaWidth;
   return contentFrame;
 }
 
-- (string16)wrapText:(const string16&)text
-             forFont:(NSFont*)nsfont
+- (base::string16)wrapText:(const base::string16&)text
+                   forFont:(NSFont*)nsfont
     maxNumberOfLines:(size_t)lines {
   if (text.empty())
     return text;
-  gfx::Font font(nsfont);
+  gfx::FontList font_list((gfx::Font(nsfont)));
   int width = NSWidth([self currentContentRect]);
-  int height = (lines + 1) * font.GetHeight();
+  int height = (lines + 1) * font_list.GetHeight();
 
-  std::vector<string16> wrapped;
-  gfx::ElideRectangleText(text, font, width, height,
+  std::vector<base::string16> wrapped;
+  gfx::ElideRectangleText(text, font_list, width, height,
                           gfx::WRAP_LONG_WORDS, &wrapped);
 
   // This could be possible when the input text contains only spaces.
   if (wrapped.empty())
-    return string16();
+    return base::string16();
 
   if (wrapped.size() > lines) {
     // Add an ellipsis to the last line. If this ellipsis makes the last line
     // too wide, that line will be further elided by the gfx::ElideText below.
-    string16 last = wrapped[lines - 1] + UTF8ToUTF16(gfx::kEllipsis);
-    if (font.GetStringWidth(last) > width)
-      last = gfx::ElideText(last, font, width, gfx::ELIDE_AT_END);
+    base::string16 last =
+        wrapped[lines - 1] + base::UTF8ToUTF16(gfx::kEllipsis);
+    if (gfx::GetStringWidth(last, font_list) > width)
+      last = gfx::ElideText(last, font_list, width, gfx::ELIDE_AT_END);
     wrapped.resize(lines - 1);
     wrapped.push_back(last);
   }