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/text_elider.h"
18 #include "ui/message_center/message_center.h"
19 #include "ui/message_center/message_center_style.h"
20 #include "ui/message_center/notification.h"
23 @interface MCNotificationProgressBar : NSProgressIndicator
26 @implementation MCNotificationProgressBar
27 - (void)drawRect:(NSRect)dirtyRect {
28 NSRect sliceRect, remainderRect;
29 double progressFraction = ([self doubleValue] - [self minValue]) /
30 ([self maxValue] - [self minValue]);
31 NSDivideRect(dirtyRect, &sliceRect, &remainderRect,
32 NSWidth(dirtyRect) * progressFraction, NSMinXEdge);
34 NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:dirtyRect
35 xRadius:message_center::kProgressBarCornerRadius
36 yRadius:message_center::kProgressBarCornerRadius];
37 [gfx::SkColorToCalibratedNSColor(message_center::kProgressBarBackgroundColor)
41 if (progressFraction == 0.0)
44 path = [NSBezierPath bezierPathWithRoundedRect:sliceRect
45 xRadius:message_center::kProgressBarCornerRadius
46 yRadius:message_center::kProgressBarCornerRadius];
47 [gfx::SkColorToCalibratedNSColor(message_center::kProgressBarSliceColor) set];
52 ////////////////////////////////////////////////////////////////////////////////
54 @interface MCNotificationButtonCell : NSButtonCell {
59 @implementation MCNotificationButtonCell
60 - (void)drawBezelWithFrame:(NSRect)frame inView:(NSView*)controlView {
61 // Else mouseEntered: and mouseExited: won't be called and hovered_ won't be
63 DCHECK([self showsBorderOnlyWhileMouseInside]);
67 [gfx::SkColorToCalibratedNSColor(
68 message_center::kHoveredButtonBackgroundColor) set];
72 - (void)drawImage:(NSImage*)image
73 withFrame:(NSRect)frame
74 inView:(NSView*)controlView {
77 NSRect rect = NSMakeRect(message_center::kButtonHorizontalPadding,
78 message_center::kButtonIconTopPadding,
79 message_center::kNotificationButtonIconSize,
80 message_center::kNotificationButtonIconSize);
81 [image drawInRect:rect
83 operation:NSCompositeSourceOver
89 - (NSRect)drawTitle:(NSAttributedString*)title
90 withFrame:(NSRect)frame
91 inView:(NSView*)controlView {
92 CGFloat offsetX = message_center::kButtonHorizontalPadding;
93 if ([base::mac::ObjCCastStrict<NSButton>(controlView) image]) {
94 offsetX += message_center::kNotificationButtonIconSize +
95 message_center::kButtonIconToTitlePadding;
97 frame.origin.x = offsetX;
98 frame.size.width -= offsetX;
100 NSDictionary* attributes = @{
101 NSFontAttributeName :
102 [title attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL],
103 NSForegroundColorAttributeName :
104 gfx::SkColorToCalibratedNSColor(message_center::kRegularTextColor),
106 [[title string] drawWithRect:frame
107 options:(NSStringDrawingUsesLineFragmentOrigin |
108 NSStringDrawingTruncatesLastVisibleLine)
109 attributes:attributes];
113 - (void)mouseEntered:(NSEvent*)event {
116 // Else the cell won't be repainted on hover.
117 [super mouseEntered:event];
120 - (void)mouseExited:(NSEvent*)event {
122 [super mouseExited:event];
126 ////////////////////////////////////////////////////////////////////////////////
128 @interface MCNotificationView : NSBox {
130 MCNotificationController* controller_;
133 - (id)initWithController:(MCNotificationController*)controller
137 @implementation MCNotificationView
138 - (id)initWithController:(MCNotificationController*)controller
139 frame:(NSRect)frame {
140 if ((self = [super initWithFrame:frame]))
141 controller_ = controller;
145 - (void)mouseDown:(NSEvent*)event {
146 if ([event type] != NSLeftMouseDown) {
147 [super mouseDown:event];
150 [controller_ notificationClicked];
153 - (NSView*)hitTest:(NSPoint)point {
154 // Route the mouse click events on NSTextView to the container view.
155 NSView* hitView = [super hitTest:point];
157 return [hitView isKindOfClass:[NSTextView class]] ? self : hitView;
161 - (BOOL)accessibilityIsIgnored {
165 - (NSArray*)accessibilityActionNames {
166 return @[ NSAccessibilityPressAction ];
169 - (void)accessibilityPerformAction:(NSString*)action {
170 if ([action isEqualToString:NSAccessibilityPressAction]) {
171 [controller_ notificationClicked];
174 [super accessibilityPerformAction:action];
178 ////////////////////////////////////////////////////////////////////////////////
180 @interface AccessibilityIgnoredBox : NSBox
183 @implementation AccessibilityIgnoredBox
184 - (BOOL)accessibilityIsIgnored {
189 ////////////////////////////////////////////////////////////////////////////////
191 @interface MCNotificationController (Private)
192 // Configures a NSBox to be borderless, titleless, and otherwise appearance-
194 - (void)configureCustomBox:(NSBox*)box;
196 // Initializes the icon_ ivar and returns the view to insert into the hierarchy.
197 - (NSView*)createImageView;
199 // Initializes the closeButton_ ivar with the configured button.
200 - (void)configureCloseButtonInFrame:(NSRect)rootFrame;
202 // Initializes title_ in the given frame.
203 - (void)configureTitleInFrame:(NSRect)rootFrame;
205 // Initializes message_ in the given frame.
206 - (void)configureBodyInFrame:(NSRect)rootFrame;
208 // Initializes contextMessage_ in the given frame.
209 - (void)configureContextMessageInFrame:(NSRect)rootFrame;
211 // Creates a NSTextView that the caller owns configured as a label in a
213 - (NSTextView*)newLabelWithFrame:(NSRect)frame;
215 // Gets the rectangle in which notification content should be placed. This
216 // rectangle is to the right of the icon and left of the control buttons.
217 // This depends on the icon_ and closeButton_ being initialized.
218 - (NSRect)currentContentRect;
220 // Returns the wrapped text that could fit within the content rect with not
221 // more than the given number of lines. The wrapped text would be painted using
222 // the given font. The Ellipsis could be added at the end of the last line if
224 - (string16)wrapText:(const string16&)text
225 forFont:(NSFont*)font
226 maxNumberOfLines:(size_t)lines;
229 ////////////////////////////////////////////////////////////////////////////////
231 @implementation MCNotificationController
233 - (id)initWithNotification:(const message_center::Notification*)notification
234 messageCenter:(message_center::MessageCenter*)messageCenter {
235 if ((self = [super initWithNibName:nil bundle:nil])) {
236 notification_ = notification;
237 notificationID_ = notification_->id();
238 messageCenter_ = messageCenter;
244 // Create the root view of the notification.
245 NSRect rootFrame = NSMakeRect(0, 0,
246 message_center::kNotificationPreferredImageSize,
247 message_center::kNotificationIconSize);
248 base::scoped_nsobject<MCNotificationView> rootView(
249 [[MCNotificationView alloc] initWithController:self frame:rootFrame]);
250 [self configureCustomBox:rootView];
251 [rootView setFillColor:gfx::SkColorToCalibratedNSColor(
252 message_center::kNotificationBackgroundColor)];
253 [self setView:rootView];
255 [rootView addSubview:[self createImageView]];
257 // Create the close button.
258 [self configureCloseButtonInFrame:rootFrame];
259 [rootView addSubview:closeButton_];
262 [self configureTitleInFrame:rootFrame];
263 [rootView addSubview:title_];
265 // Create the message body.
266 [self configureBodyInFrame:rootFrame];
267 [rootView addSubview:message_];
269 // Create the context message body.
270 [self configureContextMessageInFrame:rootFrame];
271 [rootView addSubview:contextMessage_];
273 // Populate the data.
274 [self updateNotification:notification_];
277 - (NSRect)updateNotification:(const message_center::Notification*)notification {
278 DCHECK_EQ(notification->id(), notificationID_);
279 notification_ = notification;
281 NSRect rootFrame = NSMakeRect(0, 0,
282 message_center::kNotificationPreferredImageSize,
283 message_center::kNotificationIconSize);
286 [icon_ setImage:notification_->icon().AsNSImage()];
288 // The message_center:: constants are relative to capHeight at the top and
289 // relative to the baseline at the bottom, but NSTextField uses the full line
290 // height for its height.
291 CGFloat titleTopGap =
292 roundf([[title_ font] ascender] - [[title_ font] capHeight]);
293 CGFloat titleBottomGap = roundf(fabs([[title_ font] descender]));
294 CGFloat titlePadding = message_center::kTextTopPadding - titleTopGap;
296 CGFloat messageTopGap =
297 roundf([[message_ font] ascender] - [[message_ font] capHeight]);
298 CGFloat messageBottomGap = roundf(fabs([[message_ font] descender]));
299 CGFloat messagePadding =
300 message_center::kTextTopPadding - titleBottomGap - messageTopGap;
302 CGFloat contextMessageTopGap = roundf(
303 [[contextMessage_ font] ascender] - [[contextMessage_ font] capHeight]);
304 CGFloat contextMessagePadding =
305 message_center::kTextTopPadding - messageBottomGap - contextMessageTopGap;
307 // Set the title and recalculate the frame.
308 [title_ setString:base::SysUTF16ToNSString(
309 [self wrapText:notification_->title()
310 forFont:[title_ font]
311 maxNumberOfLines:message_center::kTitleLineLimit])];
313 NSRect titleFrame = [title_ frame];
314 titleFrame.origin.y = NSMaxY(rootFrame) - titlePadding - NSHeight(titleFrame);
316 // Set the message and recalculate the frame.
317 [message_ setString:base::SysUTF16ToNSString(
318 [self wrapText:notification_->message()
319 forFont:[message_ font]
320 maxNumberOfLines:message_center::kMessageExpandedLineLimit])];
321 [message_ sizeToFit];
322 NSRect messageFrame = [message_ frame];
324 // If there are list items, then the message_ view should not be displayed.
325 const std::vector<message_center::NotificationItem>& items =
326 notification->items();
327 if (items.size() > 0) {
328 [message_ setHidden:YES];
329 messageFrame.origin.y = titleFrame.origin.y;
330 messageFrame.size.height = 0;
332 [message_ setHidden:NO];
333 messageFrame.origin.y =
334 NSMinY(titleFrame) - messagePadding - NSHeight(messageFrame);
335 messageFrame.size.height = NSHeight([message_ frame]);
338 // Set the context message and recalculate the frame.
339 [contextMessage_ setString:base::SysUTF16ToNSString(
340 [self wrapText:notification_->context_message()
341 forFont:[contextMessage_ font]
342 maxNumberOfLines:message_center::kContextMessageLineLimit])];
343 [contextMessage_ sizeToFit];
344 NSRect contextMessageFrame = [contextMessage_ frame];
346 if (notification_->context_message().empty()) {
347 [contextMessage_ setHidden:YES];
348 contextMessageFrame.origin.y = messageFrame.origin.y;
349 contextMessageFrame.size.height = 0;
351 [contextMessage_ setHidden:NO];
352 contextMessageFrame.origin.y =
353 NSMinY(messageFrame) -
354 contextMessagePadding -
355 NSHeight(contextMessageFrame);
356 contextMessageFrame.size.height = NSHeight([contextMessage_ frame]);
359 // Create the list item views (up to a maximum).
360 [listView_ removeFromSuperview];
361 NSRect listFrame = NSZeroRect;
362 if (items.size() > 0) {
363 listFrame = [self currentContentRect];
364 listFrame.origin.y = 0;
365 listFrame.size.height = 0;
366 listView_.reset([[NSView alloc] initWithFrame:listFrame]);
367 [listView_ accessibilitySetOverrideValue:NSAccessibilityListRole
368 forAttribute:NSAccessibilityRoleAttribute];
370 accessibilitySetOverrideValue:NSAccessibilityContentListSubrole
371 forAttribute:NSAccessibilitySubroleAttribute];
374 NSFont* font = [NSFont systemFontOfSize:message_center::kMessageFontSize];
375 CGFloat lineHeight = roundf(NSHeight([font boundingRectForFont]));
377 const int kNumNotifications =
378 std::min(items.size(), message_center::kNotificationMaximumItems);
379 for (int i = kNumNotifications - 1; i >= 0; --i) {
380 NSTextView* itemView = [self newLabelWithFrame:
381 NSMakeRect(0, y, NSWidth(listFrame), lineHeight)];
382 [itemView setFont:font];
384 // Disable the word-wrap in order to show the text in single line.
385 [[itemView textContainer] setContainerSize:NSMakeSize(FLT_MAX, FLT_MAX)];
386 [[itemView textContainer] setWidthTracksTextView:NO];
388 // Construct the text from the title and message.
390 items[i].title + base::UTF8ToUTF16(" ") + items[i].message;
391 string16 ellidedText =
392 [self wrapText:text forFont:font maxNumberOfLines:1];
393 [itemView setString:base::SysUTF16ToNSString(ellidedText)];
395 // Use dim color for the title part.
396 NSColor* titleColor =
397 gfx::SkColorToCalibratedNSColor(message_center::kRegularTextColor);
398 NSRange titleRange = NSMakeRange(
400 std::min(ellidedText.size(), items[i].title.size()));
401 [itemView setTextColor:titleColor range:titleRange];
403 // Use dim color for the message part if it has not been truncated.
404 if (ellidedText.size() > items[i].title.size() + 1) {
405 NSColor* messageColor =
406 gfx::SkColorToCalibratedNSColor(message_center::kDimTextColor);
407 NSRange messageRange = NSMakeRange(
408 items[i].title.size() + 1,
409 ellidedText.size() - items[i].title.size() - 1);
410 [itemView setTextColor:messageColor range:messageRange];
413 [listView_ addSubview:itemView];
416 // TODO(thakis): The spacing is not completely right.
417 CGFloat listTopPadding =
418 message_center::kTextTopPadding - contextMessageTopGap;
419 listFrame.size.height = y;
421 NSMinY(contextMessageFrame) - listTopPadding - NSHeight(listFrame);
422 [listView_ setFrame:listFrame];
423 [[self view] addSubview:listView_];
426 // Create the progress bar view if needed.
427 [progressBarView_ removeFromSuperview];
428 NSRect progressBarFrame = NSZeroRect;
429 if (notification->type() == message_center::NOTIFICATION_TYPE_PROGRESS) {
430 progressBarFrame = [self currentContentRect];
431 progressBarFrame.origin.y = NSMinY(contextMessageFrame) -
432 message_center::kProgressBarTopPadding -
433 message_center::kProgressBarThickness;
434 progressBarFrame.size.height = message_center::kProgressBarThickness;
435 progressBarView_.reset(
436 [[MCNotificationProgressBar alloc] initWithFrame:progressBarFrame]);
437 // Setting indeterminate to NO does not work with custom drawRect.
438 [progressBarView_ setIndeterminate:YES];
439 [progressBarView_ setStyle:NSProgressIndicatorBarStyle];
440 [progressBarView_ setDoubleValue:notification->progress()];
441 [[self view] addSubview:progressBarView_];
444 // If the bottom-most element so far is out of the rootView's bounds, resize
446 CGFloat minY = NSMinY(contextMessageFrame);
447 if (listView_ && NSMinY(listFrame) < minY)
448 minY = NSMinY(listFrame);
449 if (progressBarView_ && NSMinY(progressBarFrame) < minY)
450 minY = NSMinY(progressBarFrame);
451 if (minY < messagePadding) {
452 CGFloat delta = messagePadding - minY;
453 rootFrame.size.height += delta;
454 titleFrame.origin.y += delta;
455 messageFrame.origin.y += delta;
456 contextMessageFrame.origin.y += delta;
457 listFrame.origin.y += delta;
458 progressBarFrame.origin.y += delta;
461 // Add the bottom container view.
462 NSRect frame = rootFrame;
463 frame.size.height = 0;
464 [bottomView_ removeFromSuperview];
465 bottomView_.reset([[NSView alloc] initWithFrame:frame]);
468 // Create action buttons if appropriate, bottom-up.
469 std::vector<message_center::ButtonInfo> buttons = notification->buttons();
470 for (int i = buttons.size() - 1; i >= 0; --i) {
471 message_center::ButtonInfo buttonInfo = buttons[i];
472 NSRect buttonFrame = frame;
473 buttonFrame.origin = NSMakePoint(0, y);
474 buttonFrame.size.height = message_center::kButtonHeight;
475 base::scoped_nsobject<NSButton> button(
476 [[NSButton alloc] initWithFrame:buttonFrame]);
477 base::scoped_nsobject<MCNotificationButtonCell> cell(
478 [[MCNotificationButtonCell alloc]
479 initTextCell:base::SysUTF16ToNSString(buttonInfo.title)]);
480 [cell setShowsBorderOnlyWhileMouseInside:YES];
481 [button setCell:cell];
482 [button setImage:buttonInfo.icon.AsNSImage()];
483 [button setBezelStyle:NSSmallSquareBezelStyle];
484 [button setImagePosition:NSImageLeft];
486 [button setTarget:self];
487 [button setAction:@selector(buttonClicked:)];
488 y += NSHeight(buttonFrame);
489 frame.size.height += NSHeight(buttonFrame);
490 [bottomView_ addSubview:button];
492 NSRect separatorFrame = frame;
493 separatorFrame.origin = NSMakePoint(0, y);
494 separatorFrame.size.height = 1;
495 base::scoped_nsobject<NSBox> separator(
496 [[AccessibilityIgnoredBox alloc] initWithFrame:separatorFrame]);
497 [self configureCustomBox:separator];
498 [separator setFillColor:gfx::SkColorToCalibratedNSColor(
499 message_center::kButtonSeparatorColor)];
500 y += NSHeight(separatorFrame);
501 frame.size.height += NSHeight(separatorFrame);
502 [bottomView_ addSubview:separator];
505 // Create the image view if appropriate.
506 if (!notification->image().IsEmpty()) {
507 NSImage* image = notification->image().AsNSImage();
508 NSRect imageFrame = frame;
509 imageFrame.origin = NSMakePoint(0, y);
510 imageFrame.size = NSSizeFromCGSize(message_center::GetImageSizeForWidth(
511 NSWidth(frame), notification->image().Size()).ToCGSize());
512 base::scoped_nsobject<NSImageView> imageView(
513 [[NSImageView alloc] initWithFrame:imageFrame]);
514 [imageView setImage:image];
515 [imageView setImageScaling:NSImageScaleProportionallyUpOrDown];
516 y += NSHeight(imageFrame);
517 frame.size.height += NSHeight(imageFrame);
518 [bottomView_ addSubview:imageView];
521 [bottomView_ setFrame:frame];
522 [[self view] addSubview:bottomView_];
524 rootFrame.size.height += NSHeight(frame);
525 titleFrame.origin.y += NSHeight(frame);
526 messageFrame.origin.y += NSHeight(frame);
527 contextMessageFrame.origin.y += NSHeight(frame);
528 listFrame.origin.y += NSHeight(frame);
529 progressBarFrame.origin.y += NSHeight(frame);
531 // Make sure that there is a minimum amount of spacing below the icon and
532 // the edge of the frame.
533 CGFloat bottomDelta = NSHeight(rootFrame) - NSHeight([icon_ frame]);
534 if (bottomDelta > 0 && bottomDelta < message_center::kIconBottomPadding) {
535 CGFloat bottomAdjust = message_center::kIconBottomPadding - bottomDelta;
536 rootFrame.size.height += bottomAdjust;
537 titleFrame.origin.y += bottomAdjust;
538 messageFrame.origin.y += bottomAdjust;
539 contextMessageFrame.origin.y += bottomAdjust;
540 listFrame.origin.y += bottomAdjust;
541 progressBarFrame.origin.y += bottomAdjust;
544 [[self view] setFrame:rootFrame];
545 [title_ setFrame:titleFrame];
546 [message_ setFrame:messageFrame];
547 [contextMessage_ setFrame:contextMessageFrame];
548 [listView_ setFrame:listFrame];
549 [progressBarView_ setFrame:progressBarFrame];
554 - (void)close:(id)sender {
555 [closeButton_ setTarget:nil];
556 messageCenter_->RemoveNotification([self notificationID], /*by_user=*/true);
559 - (void)buttonClicked:(id)button {
560 messageCenter_->ClickOnNotificationButton([self notificationID],
564 - (const message_center::Notification*)notification {
565 return notification_;
568 - (const std::string&)notificationID {
569 return notificationID_;
572 - (void)notificationClicked {
573 messageCenter_->ClickOnNotification([self notificationID]);
576 // Private /////////////////////////////////////////////////////////////////////
578 - (void)configureCustomBox:(NSBox*)box {
579 [box setBoxType:NSBoxCustom];
580 [box setBorderType:NSNoBorder];
581 [box setTitlePosition:NSNoTitle];
582 [box setContentViewMargins:NSZeroSize];
585 - (NSView*)createImageView {
586 // Create another box that shows a background color when the icon is not
587 // big enough to fill the space.
588 NSRect imageFrame = NSMakeRect(0, 0,
589 message_center::kNotificationIconSize,
590 message_center::kNotificationIconSize);
591 base::scoped_nsobject<NSBox> imageBox(
592 [[AccessibilityIgnoredBox alloc] initWithFrame:imageFrame]);
593 [self configureCustomBox:imageBox];
594 [imageBox setFillColor:gfx::SkColorToCalibratedNSColor(
595 message_center::kIconBackgroundColor)];
596 [imageBox setAutoresizingMask:NSViewMinYMargin];
598 // Inside the image box put the actual icon view.
599 icon_.reset([[NSImageView alloc] initWithFrame:imageFrame]);
600 [imageBox setContentView:icon_];
602 return imageBox.autorelease();
605 - (void)configureCloseButtonInFrame:(NSRect)rootFrame {
606 closeButton_.reset([[HoverImageButton alloc] initWithFrame:NSMakeRect(
607 NSMaxX(rootFrame) - message_center::kControlButtonSize,
608 NSMaxY(rootFrame) - message_center::kControlButtonSize,
609 message_center::kControlButtonSize,
610 message_center::kControlButtonSize)]);
611 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
612 [closeButton_ setDefaultImage:
613 rb.GetNativeImageNamed(IDR_NOTIFICATION_CLOSE).ToNSImage()];
614 [closeButton_ setHoverImage:
615 rb.GetNativeImageNamed(IDR_NOTIFICATION_CLOSE_HOVER).ToNSImage()];
616 [closeButton_ setPressedImage:
617 rb.GetNativeImageNamed(IDR_NOTIFICATION_CLOSE_PRESSED).ToNSImage()];
618 [[closeButton_ cell] setHighlightsBy:NSOnState];
619 [closeButton_ setTrackingEnabled:YES];
620 [closeButton_ setBordered:NO];
621 [closeButton_ setAutoresizingMask:NSViewMinYMargin];
622 [closeButton_ setTarget:self];
623 [closeButton_ setAction:@selector(close:)];
625 accessibilitySetOverrideValue:NSAccessibilityCloseButtonSubrole
626 forAttribute:NSAccessibilitySubroleAttribute];
628 accessibilitySetOverrideValue:
629 l10n_util::GetNSString(IDS_APP_ACCNAME_CLOSE)
630 forAttribute:NSAccessibilityTitleAttribute];
633 - (void)configureTitleInFrame:(NSRect)rootFrame {
634 NSRect frame = [self currentContentRect];
635 frame.size.height = 0;
636 title_.reset([self newLabelWithFrame:frame]);
637 [title_ setAutoresizingMask:NSViewMinYMargin];
638 [title_ setTextColor:gfx::SkColorToCalibratedNSColor(
639 message_center::kRegularTextColor)];
640 [title_ setFont:[NSFont messageFontOfSize:message_center::kTitleFontSize]];
643 - (void)configureBodyInFrame:(NSRect)rootFrame {
644 NSRect frame = [self currentContentRect];
645 frame.size.height = 0;
646 message_.reset([self newLabelWithFrame:frame]);
647 [message_ setAutoresizingMask:NSViewMinYMargin];
648 [message_ setTextColor:gfx::SkColorToCalibratedNSColor(
649 message_center::kRegularTextColor)];
651 [NSFont messageFontOfSize:message_center::kMessageFontSize]];
654 - (void)configureContextMessageInFrame:(NSRect)rootFrame {
655 NSRect frame = [self currentContentRect];
656 frame.size.height = 0;
657 contextMessage_.reset([self newLabelWithFrame:frame]);
658 [contextMessage_ setAutoresizingMask:NSViewMinYMargin];
659 [contextMessage_ setTextColor:gfx::SkColorToCalibratedNSColor(
660 message_center::kDimTextColor)];
661 [contextMessage_ setFont:
662 [NSFont messageFontOfSize:message_center::kMessageFontSize]];
665 - (NSTextView*)newLabelWithFrame:(NSRect)frame {
666 NSTextView* label = [[NSTextView alloc] initWithFrame:frame];
667 [label setDrawsBackground:NO];
668 [label setEditable:NO];
669 [label setSelectable:NO];
670 [label setTextContainerInset:NSMakeSize(0.0f, 0.0f)];
671 [[label textContainer] setLineFragmentPadding:0.0f];
675 - (NSRect)currentContentRect {
677 DCHECK(closeButton_);
679 NSRect iconFrame, contentFrame;
680 NSDivideRect([[self view] bounds], &iconFrame, &contentFrame,
681 NSWidth([icon_ frame]) + message_center::kIconToTextPadding,
683 contentFrame.size.width -= NSWidth([closeButton_ frame]);
687 - (string16)wrapText:(const string16&)text
688 forFont:(NSFont*)nsfont
689 maxNumberOfLines:(size_t)lines {
692 gfx::Font font(nsfont);
693 int width = NSWidth([self currentContentRect]);
694 int height = (lines + 1) * font.GetHeight();
696 std::vector<string16> wrapped;
697 gfx::ElideRectangleText(text, font, width, height,
698 gfx::WRAP_LONG_WORDS, &wrapped);
700 // This could be possible when the input text contains only spaces.
704 if (wrapped.size() > lines) {
705 // Add an ellipsis to the last line. If this ellipsis makes the last line
706 // too wide, that line will be further elided by the gfx::ElideText below.
707 string16 last = wrapped[lines - 1] + UTF8ToUTF16(gfx::kEllipsis);
708 if (font.GetStringWidth(last) > width)
709 last = gfx::ElideText(last, font, width, gfx::ELIDE_AT_END);
710 wrapped.resize(lines - 1);
711 wrapped.push_back(last);
714 return lines == 1 ? wrapped[0] : JoinString(wrapped, '\n');