1 // Copyright (c) 2013 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.
5 #import "ui/message_center/cocoa/notification_controller.h"
7 #include "base/mac/foundation_util.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/sys_string_conversions.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "grit/ui_resources.h"
12 #include "grit/ui_strings.h"
13 #include "skia/ext/skia_utils_mac.h"
14 #import "ui/base/cocoa/hover_image_button.h"
15 #include "ui/base/l10n/l10n_util_mac.h"
16 #include "ui/base/resource/resource_bundle.h"
17 #include "ui/gfx/font_list.h"
18 #include "ui/gfx/text_elider.h"
19 #include "ui/gfx/text_utils.h"
20 #include "ui/message_center/message_center.h"
21 #include "ui/message_center/message_center_style.h"
22 #include "ui/message_center/notification.h"
25 @interface MCNotificationProgressBar : NSProgressIndicator
28 @implementation MCNotificationProgressBar
29 - (void)drawRect:(NSRect)dirtyRect {
30 NSRect sliceRect, remainderRect;
31 double progressFraction = ([self doubleValue] - [self minValue]) /
32 ([self maxValue] - [self minValue]);
33 NSDivideRect(dirtyRect, &sliceRect, &remainderRect,
34 NSWidth(dirtyRect) * progressFraction, NSMinXEdge);
36 NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:dirtyRect
37 xRadius:message_center::kProgressBarCornerRadius
38 yRadius:message_center::kProgressBarCornerRadius];
39 [gfx::SkColorToCalibratedNSColor(message_center::kProgressBarBackgroundColor)
43 if (progressFraction == 0.0)
46 path = [NSBezierPath bezierPathWithRoundedRect:sliceRect
47 xRadius:message_center::kProgressBarCornerRadius
48 yRadius:message_center::kProgressBarCornerRadius];
49 [gfx::SkColorToCalibratedNSColor(message_center::kProgressBarSliceColor) set];
54 ////////////////////////////////////////////////////////////////////////////////
56 @interface MCNotificationButtonCell : NSButtonCell {
61 @implementation MCNotificationButtonCell
62 - (void)drawBezelWithFrame:(NSRect)frame inView:(NSView*)controlView {
63 // Else mouseEntered: and mouseExited: won't be called and hovered_ won't be
65 DCHECK([self showsBorderOnlyWhileMouseInside]);
69 [gfx::SkColorToCalibratedNSColor(
70 message_center::kHoveredButtonBackgroundColor) set];
74 - (void)drawImage:(NSImage*)image
75 withFrame:(NSRect)frame
76 inView:(NSView*)controlView {
79 NSRect rect = NSMakeRect(message_center::kButtonHorizontalPadding,
80 message_center::kButtonIconTopPadding,
81 message_center::kNotificationButtonIconSize,
82 message_center::kNotificationButtonIconSize);
83 [image drawInRect:rect
85 operation:NSCompositeSourceOver
91 - (NSRect)drawTitle:(NSAttributedString*)title
92 withFrame:(NSRect)frame
93 inView:(NSView*)controlView {
94 CGFloat offsetX = message_center::kButtonHorizontalPadding;
95 if ([base::mac::ObjCCastStrict<NSButton>(controlView) image]) {
96 offsetX += message_center::kNotificationButtonIconSize +
97 message_center::kButtonIconToTitlePadding;
99 frame.origin.x = offsetX;
100 frame.size.width -= offsetX;
102 NSDictionary* attributes = @{
103 NSFontAttributeName :
104 [title attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL],
105 NSForegroundColorAttributeName :
106 gfx::SkColorToCalibratedNSColor(message_center::kRegularTextColor),
108 [[title string] drawWithRect:frame
109 options:(NSStringDrawingUsesLineFragmentOrigin |
110 NSStringDrawingTruncatesLastVisibleLine)
111 attributes:attributes];
115 - (void)mouseEntered:(NSEvent*)event {
118 // Else the cell won't be repainted on hover.
119 [super mouseEntered:event];
122 - (void)mouseExited:(NSEvent*)event {
124 [super mouseExited:event];
128 ////////////////////////////////////////////////////////////////////////////////
130 @interface MCNotificationView : NSBox {
132 MCNotificationController* controller_;
135 - (id)initWithController:(MCNotificationController*)controller
139 @implementation MCNotificationView
140 - (id)initWithController:(MCNotificationController*)controller
141 frame:(NSRect)frame {
142 if ((self = [super initWithFrame:frame]))
143 controller_ = controller;
147 - (void)mouseDown:(NSEvent*)event {
148 if ([event type] != NSLeftMouseDown) {
149 [super mouseDown:event];
152 [controller_ notificationClicked];
155 - (NSView*)hitTest:(NSPoint)point {
156 // Route the mouse click events on NSTextView to the container view.
157 NSView* hitView = [super hitTest:point];
159 return [hitView isKindOfClass:[NSTextView class]] ? self : hitView;
163 - (BOOL)accessibilityIsIgnored {
167 - (NSArray*)accessibilityActionNames {
168 return @[ NSAccessibilityPressAction ];
171 - (void)accessibilityPerformAction:(NSString*)action {
172 if ([action isEqualToString:NSAccessibilityPressAction]) {
173 [controller_ notificationClicked];
176 [super accessibilityPerformAction:action];
180 ////////////////////////////////////////////////////////////////////////////////
182 @interface AccessibilityIgnoredBox : NSBox
185 @implementation AccessibilityIgnoredBox
186 - (BOOL)accessibilityIsIgnored {
191 ////////////////////////////////////////////////////////////////////////////////
193 @interface MCNotificationController (Private)
194 // Configures a NSBox to be borderless, titleless, and otherwise appearance-
196 - (void)configureCustomBox:(NSBox*)box;
198 // Initializes the icon_ ivar and returns the view to insert into the hierarchy.
199 - (NSView*)createIconView;
201 // Creates a box that shows a border when the icon is not big enough to fill the
203 - (NSBox*)createImageBox;
205 // Initializes the closeButton_ ivar with the configured button.
206 - (void)configureCloseButtonInFrame:(NSRect)rootFrame;
208 // Initializes title_ in the given frame.
209 - (void)configureTitleInFrame:(NSRect)rootFrame;
211 // Initializes message_ in the given frame.
212 - (void)configureBodyInFrame:(NSRect)rootFrame;
214 // Initializes contextMessage_ in the given frame.
215 - (void)configureContextMessageInFrame:(NSRect)rootFrame;
217 // Creates a NSTextView that the caller owns configured as a label in a
219 - (NSTextView*)newLabelWithFrame:(NSRect)frame;
221 // Gets the rectangle in which notification content should be placed. This
222 // rectangle is to the right of the icon and left of the control buttons.
223 // This depends on the icon_ and closeButton_ being initialized.
224 - (NSRect)currentContentRect;
226 // Returns the wrapped text that could fit within the content rect with not
227 // more than the given number of lines. The wrapped text would be painted using
228 // the given font. The Ellipsis could be added at the end of the last line if
230 - (base::string16)wrapText:(const base::string16&)text
231 forFont:(NSFont*)font
232 maxNumberOfLines:(size_t)lines;
235 ////////////////////////////////////////////////////////////////////////////////
237 @implementation MCNotificationController
239 - (id)initWithNotification:(const message_center::Notification*)notification
240 messageCenter:(message_center::MessageCenter*)messageCenter {
241 if ((self = [super initWithNibName:nil bundle:nil])) {
242 notification_ = notification;
243 notificationID_ = notification_->id();
244 messageCenter_ = messageCenter;
250 // Create the root view of the notification.
251 NSRect rootFrame = NSMakeRect(0, 0,
252 message_center::kNotificationPreferredImageWidth,
253 message_center::kNotificationIconSize);
254 base::scoped_nsobject<MCNotificationView> rootView(
255 [[MCNotificationView alloc] initWithController:self frame:rootFrame]);
256 [self configureCustomBox:rootView];
257 [rootView setFillColor:gfx::SkColorToCalibratedNSColor(
258 message_center::kNotificationBackgroundColor)];
259 [self setView:rootView];
261 [rootView addSubview:[self createIconView]];
263 // Create the close button.
264 [self configureCloseButtonInFrame:rootFrame];
265 [rootView addSubview:closeButton_];
268 [self configureTitleInFrame:rootFrame];
269 [rootView addSubview:title_];
271 // Create the message body.
272 [self configureBodyInFrame:rootFrame];
273 [rootView addSubview:message_];
275 // Create the context message body.
276 [self configureContextMessageInFrame:rootFrame];
277 [rootView addSubview:contextMessage_];
279 // Populate the data.
280 [self updateNotification:notification_];
283 - (NSRect)updateNotification:(const message_center::Notification*)notification {
284 DCHECK_EQ(notification->id(), notificationID_);
285 notification_ = notification;
287 NSRect rootFrame = NSMakeRect(0, 0,
288 message_center::kNotificationPreferredImageWidth,
289 message_center::kNotificationIconSize);
292 [icon_ setImage:notification_->icon().AsNSImage()];
294 // The message_center:: constants are relative to capHeight at the top and
295 // relative to the baseline at the bottom, but NSTextField uses the full line
296 // height for its height.
297 CGFloat titleTopGap =
298 roundf([[title_ font] ascender] - [[title_ font] capHeight]);
299 CGFloat titleBottomGap = roundf(fabs([[title_ font] descender]));
300 CGFloat titlePadding = message_center::kTextTopPadding - titleTopGap;
302 CGFloat messageTopGap =
303 roundf([[message_ font] ascender] - [[message_ font] capHeight]);
304 CGFloat messageBottomGap = roundf(fabs([[message_ font] descender]));
305 CGFloat messagePadding =
306 message_center::kTextTopPadding - titleBottomGap - messageTopGap;
308 CGFloat contextMessageTopGap = roundf(
309 [[contextMessage_ font] ascender] - [[contextMessage_ font] capHeight]);
310 CGFloat contextMessagePadding =
311 message_center::kTextTopPadding - messageBottomGap - contextMessageTopGap;
313 // Set the title and recalculate the frame.
314 [title_ setString:base::SysUTF16ToNSString(
315 [self wrapText:notification_->title()
316 forFont:[title_ font]
317 maxNumberOfLines:message_center::kTitleLineLimit])];
319 NSRect titleFrame = [title_ frame];
320 titleFrame.origin.y = NSMaxY(rootFrame) - titlePadding - NSHeight(titleFrame);
322 // Set the message and recalculate the frame.
323 [message_ setString:base::SysUTF16ToNSString(
324 [self wrapText:notification_->message()
325 forFont:[message_ font]
326 maxNumberOfLines:message_center::kMessageExpandedLineLimit])];
327 [message_ sizeToFit];
328 NSRect messageFrame = [message_ frame];
330 // If there are list items, then the message_ view should not be displayed.
331 const std::vector<message_center::NotificationItem>& items =
332 notification->items();
333 if (items.size() > 0) {
334 [message_ setHidden:YES];
335 messageFrame.origin.y = titleFrame.origin.y;
336 messageFrame.size.height = 0;
338 [message_ setHidden:NO];
339 messageFrame.origin.y =
340 NSMinY(titleFrame) - messagePadding - NSHeight(messageFrame);
341 messageFrame.size.height = NSHeight([message_ frame]);
344 // Set the context message and recalculate the frame.
345 [contextMessage_ setString:base::SysUTF16ToNSString(
346 [self wrapText:notification_->context_message()
347 forFont:[contextMessage_ font]
348 maxNumberOfLines:message_center::kContextMessageLineLimit])];
349 [contextMessage_ sizeToFit];
350 NSRect contextMessageFrame = [contextMessage_ frame];
352 if (notification_->context_message().empty()) {
353 [contextMessage_ setHidden:YES];
354 contextMessageFrame.origin.y = messageFrame.origin.y;
355 contextMessageFrame.size.height = 0;
357 [contextMessage_ setHidden:NO];
358 contextMessageFrame.origin.y =
359 NSMinY(messageFrame) -
360 contextMessagePadding -
361 NSHeight(contextMessageFrame);
362 contextMessageFrame.size.height = NSHeight([contextMessage_ frame]);
365 // Create the list item views (up to a maximum).
366 [listView_ removeFromSuperview];
367 NSRect listFrame = NSZeroRect;
368 if (items.size() > 0) {
369 listFrame = [self currentContentRect];
370 listFrame.origin.y = 0;
371 listFrame.size.height = 0;
372 listView_.reset([[NSView alloc] initWithFrame:listFrame]);
373 [listView_ accessibilitySetOverrideValue:NSAccessibilityListRole
374 forAttribute:NSAccessibilityRoleAttribute];
376 accessibilitySetOverrideValue:NSAccessibilityContentListSubrole
377 forAttribute:NSAccessibilitySubroleAttribute];
380 NSFont* font = [NSFont systemFontOfSize:message_center::kMessageFontSize];
381 CGFloat lineHeight = roundf(NSHeight([font boundingRectForFont]));
383 const int kNumNotifications =
384 std::min(items.size(), message_center::kNotificationMaximumItems);
385 for (int i = kNumNotifications - 1; i >= 0; --i) {
386 NSTextView* itemView = [self newLabelWithFrame:
387 NSMakeRect(0, y, NSWidth(listFrame), lineHeight)];
388 [itemView setFont:font];
390 // Disable the word-wrap in order to show the text in single line.
391 [[itemView textContainer] setContainerSize:NSMakeSize(FLT_MAX, FLT_MAX)];
392 [[itemView textContainer] setWidthTracksTextView:NO];
394 // Construct the text from the title and message.
395 base::string16 text =
396 items[i].title + base::UTF8ToUTF16(" ") + items[i].message;
397 base::string16 ellidedText =
398 [self wrapText:text forFont:font maxNumberOfLines:1];
399 [itemView setString:base::SysUTF16ToNSString(ellidedText)];
401 // Use dim color for the title part.
402 NSColor* titleColor =
403 gfx::SkColorToCalibratedNSColor(message_center::kRegularTextColor);
404 NSRange titleRange = NSMakeRange(
406 std::min(ellidedText.size(), items[i].title.size()));
407 [itemView setTextColor:titleColor range:titleRange];
409 // Use dim color for the message part if it has not been truncated.
410 if (ellidedText.size() > items[i].title.size() + 1) {
411 NSColor* messageColor =
412 gfx::SkColorToCalibratedNSColor(message_center::kDimTextColor);
413 NSRange messageRange = NSMakeRange(
414 items[i].title.size() + 1,
415 ellidedText.size() - items[i].title.size() - 1);
416 [itemView setTextColor:messageColor range:messageRange];
419 [listView_ addSubview:itemView];
422 // TODO(thakis): The spacing is not completely right.
423 CGFloat listTopPadding =
424 message_center::kTextTopPadding - contextMessageTopGap;
425 listFrame.size.height = y;
427 NSMinY(contextMessageFrame) - listTopPadding - NSHeight(listFrame);
428 [listView_ setFrame:listFrame];
429 [[self view] addSubview:listView_];
432 // Create the progress bar view if needed.
433 [progressBarView_ removeFromSuperview];
434 NSRect progressBarFrame = NSZeroRect;
435 if (notification->type() == message_center::NOTIFICATION_TYPE_PROGRESS) {
436 progressBarFrame = [self currentContentRect];
437 progressBarFrame.origin.y = NSMinY(contextMessageFrame) -
438 message_center::kProgressBarTopPadding -
439 message_center::kProgressBarThickness;
440 progressBarFrame.size.height = message_center::kProgressBarThickness;
441 progressBarView_.reset(
442 [[MCNotificationProgressBar alloc] initWithFrame:progressBarFrame]);
443 // Setting indeterminate to NO does not work with custom drawRect.
444 [progressBarView_ setIndeterminate:YES];
445 [progressBarView_ setStyle:NSProgressIndicatorBarStyle];
446 [progressBarView_ setDoubleValue:notification->progress()];
447 [[self view] addSubview:progressBarView_];
450 // If the bottom-most element so far is out of the rootView's bounds, resize
452 CGFloat minY = NSMinY(contextMessageFrame);
453 if (listView_ && NSMinY(listFrame) < minY)
454 minY = NSMinY(listFrame);
455 if (progressBarView_ && NSMinY(progressBarFrame) < minY)
456 minY = NSMinY(progressBarFrame);
457 if (minY < messagePadding) {
458 CGFloat delta = messagePadding - minY;
459 rootFrame.size.height += delta;
460 titleFrame.origin.y += delta;
461 messageFrame.origin.y += delta;
462 contextMessageFrame.origin.y += delta;
463 listFrame.origin.y += delta;
464 progressBarFrame.origin.y += delta;
467 // Add the bottom container view.
468 NSRect frame = rootFrame;
469 frame.size.height = 0;
470 [bottomView_ removeFromSuperview];
471 bottomView_.reset([[NSView alloc] initWithFrame:frame]);
474 // Create action buttons if appropriate, bottom-up.
475 std::vector<message_center::ButtonInfo> buttons = notification->buttons();
476 for (int i = buttons.size() - 1; i >= 0; --i) {
477 message_center::ButtonInfo buttonInfo = buttons[i];
478 NSRect buttonFrame = frame;
479 buttonFrame.origin = NSMakePoint(0, y);
480 buttonFrame.size.height = message_center::kButtonHeight;
481 base::scoped_nsobject<NSButton> button(
482 [[NSButton alloc] initWithFrame:buttonFrame]);
483 base::scoped_nsobject<MCNotificationButtonCell> cell(
484 [[MCNotificationButtonCell alloc]
485 initTextCell:base::SysUTF16ToNSString(buttonInfo.title)]);
486 [cell setShowsBorderOnlyWhileMouseInside:YES];
487 [button setCell:cell];
488 [button setImage:buttonInfo.icon.AsNSImage()];
489 [button setBezelStyle:NSSmallSquareBezelStyle];
490 [button setImagePosition:NSImageLeft];
492 [button setTarget:self];
493 [button setAction:@selector(buttonClicked:)];
494 y += NSHeight(buttonFrame);
495 frame.size.height += NSHeight(buttonFrame);
496 [bottomView_ addSubview:button];
498 NSRect separatorFrame = frame;
499 separatorFrame.origin = NSMakePoint(0, y);
500 separatorFrame.size.height = 1;
501 base::scoped_nsobject<NSBox> separator(
502 [[AccessibilityIgnoredBox alloc] initWithFrame:separatorFrame]);
503 [self configureCustomBox:separator];
504 [separator setFillColor:gfx::SkColorToCalibratedNSColor(
505 message_center::kButtonSeparatorColor)];
506 y += NSHeight(separatorFrame);
507 frame.size.height += NSHeight(separatorFrame);
508 [bottomView_ addSubview:separator];
511 // Create the image view if appropriate.
512 gfx::Image notificationImage = notification->image();
513 if (!notificationImage.IsEmpty()) {
514 NSBox* imageBox = [self createImageBox:notificationImage];
515 NSRect outerFrame = frame;
516 outerFrame.origin = NSMakePoint(0, y);
517 outerFrame.size = [imageBox frame].size;
518 [imageBox setFrame:outerFrame];
520 y += NSHeight(outerFrame);
521 frame.size.height += NSHeight(outerFrame);
523 [bottomView_ addSubview:imageBox];
526 [bottomView_ setFrame:frame];
527 [[self view] addSubview:bottomView_];
529 rootFrame.size.height += NSHeight(frame);
530 titleFrame.origin.y += NSHeight(frame);
531 messageFrame.origin.y += NSHeight(frame);
532 contextMessageFrame.origin.y += NSHeight(frame);
533 listFrame.origin.y += NSHeight(frame);
534 progressBarFrame.origin.y += NSHeight(frame);
536 // Make sure that there is a minimum amount of spacing below the icon and
537 // the edge of the frame.
538 CGFloat bottomDelta = NSHeight(rootFrame) - NSHeight([icon_ frame]);
539 if (bottomDelta > 0 && bottomDelta < message_center::kIconBottomPadding) {
540 CGFloat bottomAdjust = message_center::kIconBottomPadding - bottomDelta;
541 rootFrame.size.height += bottomAdjust;
542 titleFrame.origin.y += bottomAdjust;
543 messageFrame.origin.y += bottomAdjust;
544 contextMessageFrame.origin.y += bottomAdjust;
545 listFrame.origin.y += bottomAdjust;
546 progressBarFrame.origin.y += bottomAdjust;
549 [[self view] setFrame:rootFrame];
550 [title_ setFrame:titleFrame];
551 [message_ setFrame:messageFrame];
552 [contextMessage_ setFrame:contextMessageFrame];
553 [listView_ setFrame:listFrame];
554 [progressBarView_ setFrame:progressBarFrame];
559 - (void)close:(id)sender {
560 [closeButton_ setTarget:nil];
561 messageCenter_->RemoveNotification([self notificationID], /*by_user=*/true);
564 - (void)buttonClicked:(id)button {
565 messageCenter_->ClickOnNotificationButton([self notificationID],
569 - (const message_center::Notification*)notification {
570 return notification_;
573 - (const std::string&)notificationID {
574 return notificationID_;
577 - (void)notificationClicked {
578 messageCenter_->ClickOnNotification([self notificationID]);
581 // Private /////////////////////////////////////////////////////////////////////
583 - (void)configureCustomBox:(NSBox*)box {
584 [box setBoxType:NSBoxCustom];
585 [box setBorderType:NSNoBorder];
586 [box setTitlePosition:NSNoTitle];
587 [box setContentViewMargins:NSZeroSize];
590 - (NSView*)createIconView {
591 // Create another box that shows a background color when the icon is not
592 // big enough to fill the space.
593 NSRect imageFrame = NSMakeRect(0, 0,
594 message_center::kNotificationIconSize,
595 message_center::kNotificationIconSize);
596 base::scoped_nsobject<NSBox> imageBox(
597 [[AccessibilityIgnoredBox alloc] initWithFrame:imageFrame]);
598 [self configureCustomBox:imageBox];
599 [imageBox setFillColor:gfx::SkColorToCalibratedNSColor(
600 message_center::kIconBackgroundColor)];
601 [imageBox setAutoresizingMask:NSViewMinYMargin];
603 // Inside the image box put the actual icon view.
604 icon_.reset([[NSImageView alloc] initWithFrame:imageFrame]);
605 [imageBox setContentView:icon_];
607 return imageBox.autorelease();
610 - (NSBox*)createImageBox:(gfx::Image)notificationImage {
611 using message_center::kNotificationImageBorderSize;
612 using message_center::kNotificationPreferredImageWidth;
613 using message_center::kNotificationPreferredImageHeight;
615 NSRect imageFrame = NSMakeRect(0, 0,
616 kNotificationPreferredImageWidth,
617 kNotificationPreferredImageHeight);
618 base::scoped_nsobject<NSBox> imageBox(
619 [[AccessibilityIgnoredBox alloc] initWithFrame:imageFrame]);
620 [self configureCustomBox:imageBox];
621 [imageBox setFillColor:gfx::SkColorToCalibratedNSColor(
622 message_center::kImageBackgroundColor)];
624 // Images with non-preferred aspect ratios get a border on all sides.
625 gfx::Size idealSize = gfx::Size(
626 kNotificationPreferredImageWidth, kNotificationPreferredImageHeight);
627 gfx::Size scaledSize = message_center::GetImageSizeForContainerSize(
628 idealSize, notificationImage.Size());
629 if (scaledSize != idealSize) {
631 NSMakeSize(kNotificationImageBorderSize, kNotificationImageBorderSize);
632 [imageBox setContentViewMargins:borderSize];
635 NSImage* image = notificationImage.AsNSImage();
636 base::scoped_nsobject<NSImageView> imageView(
637 [[NSImageView alloc] initWithFrame:imageFrame]);
638 [imageView setImage:image];
639 [imageView setImageScaling:NSImageScaleProportionallyUpOrDown];
640 [imageBox setContentView:imageView];
642 return imageBox.autorelease();
645 - (void)configureCloseButtonInFrame:(NSRect)rootFrame {
646 closeButton_.reset([[HoverImageButton alloc] initWithFrame:NSMakeRect(
647 NSMaxX(rootFrame) - message_center::kControlButtonSize,
648 NSMaxY(rootFrame) - message_center::kControlButtonSize,
649 message_center::kControlButtonSize,
650 message_center::kControlButtonSize)]);
651 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
652 [closeButton_ setDefaultImage:
653 rb.GetNativeImageNamed(IDR_NOTIFICATION_CLOSE).ToNSImage()];
654 [closeButton_ setHoverImage:
655 rb.GetNativeImageNamed(IDR_NOTIFICATION_CLOSE_HOVER).ToNSImage()];
656 [closeButton_ setPressedImage:
657 rb.GetNativeImageNamed(IDR_NOTIFICATION_CLOSE_PRESSED).ToNSImage()];
658 [[closeButton_ cell] setHighlightsBy:NSOnState];
659 [closeButton_ setTrackingEnabled:YES];
660 [closeButton_ setBordered:NO];
661 [closeButton_ setAutoresizingMask:NSViewMinYMargin];
662 [closeButton_ setTarget:self];
663 [closeButton_ setAction:@selector(close:)];
665 accessibilitySetOverrideValue:NSAccessibilityCloseButtonSubrole
666 forAttribute:NSAccessibilitySubroleAttribute];
668 accessibilitySetOverrideValue:
669 l10n_util::GetNSString(IDS_APP_ACCNAME_CLOSE)
670 forAttribute:NSAccessibilityTitleAttribute];
673 - (void)configureTitleInFrame:(NSRect)rootFrame {
674 NSRect frame = [self currentContentRect];
675 frame.size.height = 0;
676 title_.reset([self newLabelWithFrame:frame]);
677 [title_ setAutoresizingMask:NSViewMinYMargin];
678 [title_ setTextColor:gfx::SkColorToCalibratedNSColor(
679 message_center::kRegularTextColor)];
680 [title_ setFont:[NSFont messageFontOfSize:message_center::kTitleFontSize]];
683 - (void)configureBodyInFrame:(NSRect)rootFrame {
684 NSRect frame = [self currentContentRect];
685 frame.size.height = 0;
686 message_.reset([self newLabelWithFrame:frame]);
687 [message_ setAutoresizingMask:NSViewMinYMargin];
688 [message_ setTextColor:gfx::SkColorToCalibratedNSColor(
689 message_center::kRegularTextColor)];
691 [NSFont messageFontOfSize:message_center::kMessageFontSize]];
694 - (void)configureContextMessageInFrame:(NSRect)rootFrame {
695 NSRect frame = [self currentContentRect];
696 frame.size.height = 0;
697 contextMessage_.reset([self newLabelWithFrame:frame]);
698 [contextMessage_ setAutoresizingMask:NSViewMinYMargin];
699 [contextMessage_ setTextColor:gfx::SkColorToCalibratedNSColor(
700 message_center::kDimTextColor)];
701 [contextMessage_ setFont:
702 [NSFont messageFontOfSize:message_center::kMessageFontSize]];
705 - (NSTextView*)newLabelWithFrame:(NSRect)frame {
706 NSTextView* label = [[NSTextView alloc] initWithFrame:frame];
707 [label setDrawsBackground:NO];
708 [label setEditable:NO];
709 [label setSelectable:NO];
710 [label setTextContainerInset:NSMakeSize(0.0f, 0.0f)];
711 [[label textContainer] setLineFragmentPadding:0.0f];
715 - (NSRect)currentContentRect {
717 DCHECK(closeButton_);
719 NSRect iconFrame, contentFrame;
720 NSDivideRect([[self view] bounds], &iconFrame, &contentFrame,
721 NSWidth([icon_ frame]) + message_center::kIconToTextPadding,
723 contentFrame.size.width -= NSWidth([closeButton_ frame]);
727 - (base::string16)wrapText:(const base::string16&)text
728 forFont:(NSFont*)nsfont
729 maxNumberOfLines:(size_t)lines {
732 gfx::FontList font_list((gfx::Font(nsfont)));
733 int width = NSWidth([self currentContentRect]);
734 int height = (lines + 1) * font_list.GetHeight();
736 std::vector<base::string16> wrapped;
737 gfx::ElideRectangleText(text, font_list, width, height,
738 gfx::WRAP_LONG_WORDS, &wrapped);
740 // This could be possible when the input text contains only spaces.
742 return base::string16();
744 if (wrapped.size() > lines) {
745 // Add an ellipsis to the last line. If this ellipsis makes the last line
746 // too wide, that line will be further elided by the gfx::ElideText below.
747 base::string16 last =
748 wrapped[lines - 1] + base::UTF8ToUTF16(gfx::kEllipsis);
749 if (gfx::GetStringWidth(last, font_list) > width)
750 last = gfx::ElideText(last, font_list, width, gfx::ELIDE_AT_END);
751 wrapped.resize(lines - 1);
752 wrapped.push_back(last);
755 return lines == 1 ? wrapped[0] : JoinString(wrapped, '\n');