#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"
#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);
}