| Index: ui/app_list/cocoa/app_list_view_controller.mm
|
| diff --git a/ui/app_list/cocoa/app_list_view_controller.mm b/ui/app_list/cocoa/app_list_view_controller.mm
|
| index 6862aec488c98e153e7c1ec7d93c1e28281073e8..568acc72a08b53408e9fa48fc1839271607baa86 100644
|
| --- a/ui/app_list/cocoa/app_list_view_controller.mm
|
| +++ b/ui/app_list/cocoa/app_list_view_controller.mm
|
| @@ -11,6 +11,7 @@
|
| #include "base/macros.h"
|
| #include "base/strings/string_util.h"
|
| #include "base/strings/sys_string_conversions.h"
|
| +#include "base/strings/utf_string_conversions.h"
|
| #include "skia/ext/skia_utils_mac.h"
|
| #include "ui/app_list/app_list_constants.h"
|
| #include "ui/app_list/app_list_model.h"
|
| @@ -20,6 +21,7 @@
|
| #import "ui/app_list/cocoa/apps_grid_controller.h"
|
| #include "ui/app_list/search_box_model.h"
|
| #import "ui/base/cocoa/flipped_view.h"
|
| +#import "ui/gfx/image/image_skia_util_mac.h"
|
| #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
|
|
|
| namespace {
|
| @@ -45,6 +47,15 @@ const CGFloat kMaxSegmentWidth = 80;
|
| // Duration of the animation for sliding in and out search results.
|
| const NSTimeInterval kResultsAnimationDuration = 0.2;
|
|
|
| +// Properties of the message rectangle, if it is shown.
|
| +const NSRect kMessageRect = {{12, 12}, {370, 91}};
|
| +const CGFloat kMessageCornerRadius = 2;
|
| +const CGFloat kSpacingBelowMessageTitle = 6;
|
| +const SkColor kMessageBackgroundColor = SkColorSetRGB(0xFF, 0xFD, 0xE7);
|
| +const SkColor kMessageStrokeColor = SkColorSetARGB(0x3d, 0x00, 0x00, 0x00);
|
| +// The inset should be 16px, but NSTextView has its own inset of 3.
|
| +const CGFloat kMessageTextInset = 13;
|
| +
|
| } // namespace
|
|
|
| @interface BackgroundView : FlippedView;
|
| @@ -74,8 +85,33 @@ const NSTimeInterval kResultsAnimationDuration = 0.2;
|
|
|
| @end
|
|
|
| +@interface MessageBackgroundView : FlippedView
|
| +@end
|
| +
|
| +@implementation MessageBackgroundView
|
| +
|
| +- (void)drawRect:(NSRect)dirtyRect {
|
| + NSRect boundsRect = [self bounds];
|
| + gfx::ScopedNSGraphicsContextSaveGState context;
|
| + [[NSBezierPath bezierPathWithRoundedRect:boundsRect
|
| + xRadius:kMessageCornerRadius
|
| + yRadius:kMessageCornerRadius] addClip];
|
| +
|
| + [skia::SkColorToSRGBNSColor(kMessageStrokeColor) set];
|
| + NSRectFill(boundsRect);
|
| +
|
| + [[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(boundsRect, 1, 1)
|
| + xRadius:kMessageCornerRadius
|
| + yRadius:kMessageCornerRadius] addClip];
|
| + [skia::SkColorToSRGBNSColor(kMessageBackgroundColor) set];
|
| + NSRectFill(boundsRect);
|
| +}
|
| +
|
| +@end
|
| +
|
| @interface AppListViewController ()
|
|
|
| +- (void)updateMessage;
|
| - (void)loadAndSetView;
|
| - (void)revealSearchResults:(BOOL)show;
|
|
|
| @@ -176,6 +212,7 @@ void AppListModelObserverBridge::OnShutdown() {
|
| [appsSearchResultsController_ setDelegate:nil];
|
| [appsSearchBoxController_ setDelegate:nil];
|
| [appsGridController_ setDelegate:nil];
|
| + [messageText_ setDelegate:nil];
|
| }
|
| delegate_ = newDelegate;
|
| if (delegate_) {
|
| @@ -191,17 +228,179 @@ void AppListModelObserverBridge::OnShutdown() {
|
| app_list_model_observer_bridge_.reset(
|
| new app_list::AppListModelObserverBridge(self));
|
| [self onProfilesChanged];
|
| + [self updateMessage];
|
| }
|
|
|
| --(void)loadAndSetView {
|
| +- (void)updateMessage {
|
| + if (![AppsGridController hasFewerRows])
|
| + return;
|
| +
|
| + ResourceBundle& rb = ResourceBundle::GetSharedInstance();
|
| + NSFont* messageFont = rb.GetFontWithDelta(0).GetNativeFont();
|
| + NSFont* titleFont = rb.GetFontWithDelta(2).GetNativeFont();
|
| +
|
| + base::string16 title = delegate_->GetMessageTitle();
|
| + size_t messageBreak;
|
| + base::string16 messageFull = delegate_->GetMessageText(&messageBreak);
|
| + base::string16 shortcutName = delegate_->GetAppsShortcutName();
|
| + base::string16 learnMore = delegate_->GetLearnMoreText();
|
| + base::string16 learnMoreUrl = delegate_->GetLearnMoreLink();
|
| +
|
| + base::string16 messagePre = messageFull.substr(0, messageBreak);
|
| + base::string16 messagePost = messageFull.substr(messageBreak);
|
| +
|
| + NSURL* linkURL = [NSURL URLWithString:base::SysUTF16ToNSString(learnMoreUrl)];
|
| + gfx::ImageSkia* icon = delegate_->GetAppsIcon();
|
| +
|
| + // Shift the baseline up so that the graphics align centered. 4 looks nice. It
|
| + // happens to be the image size minus the font size, but that's a coincidence.
|
| + const CGFloat kBaselineShift = 4;
|
| + base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
|
| + [[NSMutableParagraphStyle alloc] init]);
|
| + [paragraphStyle setLineSpacing:kSpacingBelowMessageTitle + kBaselineShift];
|
| +
|
| + NSNumber* baselineOffset = [NSNumber numberWithFloat:kBaselineShift];
|
| + base::scoped_nsobject<NSMutableAttributedString> text(
|
| + [[NSMutableAttributedString alloc]
|
| + initWithString:base::SysUTF16ToNSString(title)
|
| + attributes:@{
|
| + NSParagraphStyleAttributeName : paragraphStyle,
|
| + NSFontAttributeName : titleFont
|
| + }]);
|
| +
|
| + NSDictionary* defaultAttributes = @{
|
| + NSFontAttributeName : messageFont,
|
| + NSBaselineOffsetAttributeName : baselineOffset
|
| + };
|
| +
|
| + base::scoped_nsobject<NSAttributedString> lineBreak(
|
| + [[NSAttributedString alloc] initWithString:@"\n"
|
| + attributes:defaultAttributes]);
|
| + base::scoped_nsobject<NSAttributedString> space([[NSAttributedString alloc]
|
| + initWithString:@" "
|
| + attributes:defaultAttributes]);
|
| + base::scoped_nsobject<NSAttributedString> messagePreString(
|
| + [[NSAttributedString alloc]
|
| + initWithString:base::SysUTF16ToNSString(messagePre)
|
| + attributes:defaultAttributes]);
|
| + base::scoped_nsobject<NSAttributedString> messagePostString(
|
| + [[NSAttributedString alloc]
|
| + initWithString:base::SysUTF16ToNSString(messagePost)
|
| + attributes:defaultAttributes]);
|
| +
|
| + // NSNoUnderlineStyle is broken.
|
| + base::scoped_nsobject<NSAttributedString> learnMoreString(
|
| + [[NSAttributedString alloc]
|
| + initWithString:base::SysUTF16ToNSString(learnMore)
|
| + attributes:@{
|
| + NSParagraphStyleAttributeName : paragraphStyle,
|
| + NSFontAttributeName : messageFont,
|
| + NSLinkAttributeName : linkURL,
|
| + NSBaselineOffsetAttributeName : baselineOffset,
|
| + NSUnderlineStyleAttributeName :
|
| + [NSNumber numberWithInt:NSNoUnderlineStyle]
|
| + }]);
|
| + base::scoped_nsobject<NSAttributedString> shortcutStringText(
|
| + [[NSAttributedString alloc]
|
| + initWithString:base::SysUTF16ToNSString(shortcutName)
|
| + attributes:defaultAttributes]);
|
| + base::scoped_nsobject<NSMutableAttributedString> shortcutString(
|
| + [[NSMutableAttributedString alloc] init]);
|
| + if (icon) {
|
| + NSImage* image = gfx::NSImageFromImageSkia(*icon);
|
| + // The image has a bunch of representations. Ensure the smallest is used.
|
| + // (Going smaller would make pixels all manky, so don't do that).
|
| + [image setSize:NSMakeSize(16, 16)];
|
| +
|
| + base::scoped_nsobject<NSTextAttachmentCell> attachmentCell(
|
| + [[NSTextAttachmentCell alloc] initImageCell:image]);
|
| + base::scoped_nsobject<NSTextAttachment> attachment(
|
| + [[NSTextAttachment alloc] init]);
|
| + [attachment setAttachmentCell:attachmentCell];
|
| + [shortcutString
|
| + appendAttributedString:[NSAttributedString
|
| + attributedStringWithAttachment:attachment]];
|
| + [shortcutString appendAttributedString:space];
|
| + }
|
| + [shortcutString appendAttributedString:shortcutStringText];
|
| +
|
| + [text appendAttributedString:lineBreak];
|
| + [text appendAttributedString:messagePreString];
|
| + [text appendAttributedString:shortcutString];
|
| + [text appendAttributedString:messagePostString];
|
| + [text appendAttributedString:space];
|
| + [text appendAttributedString:learnMoreString];
|
| +
|
| + [[messageText_ textStorage] setAttributedString:text];
|
| + [messageText_ sizeToFit];
|
| +
|
| + // If the user scroller preference is to always show scrollbars, and the
|
| + // translated message is long, the scroll track may be present. This means
|
| + // text will be under the scroller. We only want vertical scrolling, but
|
| + // reducing the width puts the scroll track in a weird spot. So, increase the
|
| + // width of the scroll view to move the track into the padding towards the
|
| + // message background border, then reduce the width of the text view. The
|
| + // non-overlay scroller still looks kinda weird but hopefully not many will
|
| + // actually see it.
|
| + CGFloat overlap =
|
| + NSWidth([messageText_ bounds]) - [messageScrollView_ contentSize].width;
|
| + if (overlap > 0) {
|
| + NSRect rect = [messageScrollView_ frame];
|
| + rect.size.width += kMessageTextInset - 2;
|
| + [messageScrollView_ setFrame:rect];
|
| + overlap -= kMessageTextInset - 2;
|
| + DCHECK_GT(overlap, 0);
|
| + rect = [messageText_ frame];
|
| + rect.size.width -= overlap;
|
| + [messageText_ setFrame:rect];
|
| + [messageText_ sizeToFit];
|
| +
|
| + // And after doing all that for some reason Cocoa scrolls to the bottom. So
|
| + // fix that.
|
| + [[messageScrollView_ documentView] scrollPoint:NSMakePoint(0, 0)];
|
| + }
|
| +
|
| + [messageText_ setDelegate:self];
|
| +}
|
| +
|
| +- (void)loadAndSetView {
|
| pagerControl_.reset([[AppListPagerView alloc] init]);
|
| [pagerControl_ setTarget:appsGridController_];
|
| [pagerControl_ setAction:@selector(onPagerClicked:)];
|
|
|
| NSRect gridFrame = [[appsGridController_ view] frame];
|
| - NSRect contentsRect = NSMakeRect(0, kSearchInputHeight + kTopSeparatorSize,
|
| - NSWidth(gridFrame), NSHeight(gridFrame) + kPagerPreferredHeight -
|
| - [AppsGridController scrollerPadding]);
|
| +
|
| + base::scoped_nsobject<NSView> messageTextBackground;
|
| + if ([AppsGridController hasFewerRows]) {
|
| + messageTextBackground.reset(
|
| + [[MessageBackgroundView alloc] initWithFrame:kMessageRect]);
|
| + NSRect frameRect =
|
| + NSInsetRect(kMessageRect, kMessageTextInset, kMessageTextInset);
|
| + messageText_.reset([[NSTextView alloc] initWithFrame:frameRect]);
|
| + // Provide a solid background here (as well as the background) so that
|
| + // subpixel AA works.
|
| + [messageText_
|
| + setBackgroundColor:skia::SkColorToSRGBNSColor(kMessageBackgroundColor)];
|
| + [messageText_ setDrawsBackground:YES];
|
| + [messageText_ setEditable:NO];
|
| + // Ideally setSelectable:NO would also be set here, but that disables mouse
|
| + // events completely, breaking the "Learn more" link. Instead, selection is
|
| + // "disabled" via a delegate method which Apple's documentation suggests. In
|
| + // reality, selection still happens, it just disappears once the mouse is
|
| + // released. To avoid the selection appearing, also set selected text to
|
| + // have no special attributes. Sadly, the mouse cursor still displays an
|
| + // I-beam, but hacking cursor rectangles on the view so that the "Learn
|
| + // More" link is still correctly handled is too hard.
|
| + [messageText_ setSelectedTextAttributes:@{}];
|
| + gridFrame.origin.y += NSMaxY([messageTextBackground frame]);
|
| + }
|
| +
|
| + [[appsGridController_ view] setFrame:gridFrame];
|
| +
|
| + NSRect contentsRect =
|
| + NSMakeRect(0, kSearchInputHeight + kTopSeparatorSize, NSWidth(gridFrame),
|
| + NSMaxY(gridFrame) + kPagerPreferredHeight -
|
| + [AppsGridController scrollerPadding]);
|
|
|
| contentsView_.reset([[FlippedView alloc] initWithFrame:contentsRect]);
|
|
|
| @@ -236,6 +435,28 @@ void AppListModelObserverBridge::OnShutdown() {
|
| [loadingIndicator_ setDisplayedWhenStopped:NO];
|
| [loadingIndicator_ startAnimation:self];
|
|
|
| + if (messageText_) {
|
| + [contentsView_ addSubview:messageTextBackground];
|
| +
|
| + // Add a scroll view in case the translation is long and doesn't fit. Mac
|
| + // likes to hide scrollbars, so add to the height so the user can see part
|
| + // of the next line of text: just extend out into the padding towards the
|
| + // text background's border. Subtract at least 2: one for the border stroke
|
| + // and one for a bit of padding.
|
| + NSRect frameRect = [messageText_ frame];
|
| + frameRect.size.height += kMessageTextInset - 2;
|
| + messageScrollView_.reset([[NSScrollView alloc] initWithFrame:frameRect]);
|
| + [messageScrollView_ setHasVerticalScroller:YES];
|
| + [messageScrollView_ setAutohidesScrollers:YES];
|
| +
|
| + // Now the message is going into an NSScrollView, origin should be 0, 0.
|
| + frameRect = [messageText_ frame];
|
| + frameRect.origin = NSMakePoint(0, 0);
|
| + [messageText_ setFrame:frameRect];
|
| +
|
| + [messageScrollView_ setDocumentView:messageText_];
|
| + [contentsView_ addSubview:messageScrollView_];
|
| + }
|
| [contentsView_ addSubview:[appsGridController_ view]];
|
| [contentsView_ addSubview:pagerControl_];
|
| [contentsView_ addSubview:loadingIndicator_];
|
| @@ -368,4 +589,20 @@ void AppListModelObserverBridge::OnShutdown() {
|
| [appsSearchBoxController_ rebuildMenu];
|
| }
|
|
|
| +// NSTextViewDelegate implementation.
|
| +
|
| +- (BOOL)textView:(NSTextView*)textView
|
| + clickedOnLink:(id)link
|
| + atIndex:(NSUInteger)charIndex {
|
| + DCHECK(delegate_);
|
| + delegate_->OpenLearnMoreLink();
|
| + return YES;
|
| +}
|
| +
|
| +- (NSArray*)textView:(NSTextView*)aTextView
|
| + willChangeSelectionFromCharacterRanges:(NSArray*)oldSelectedCharRanges
|
| + toCharacterRanges:(NSArray*)newSelectedCharRanges {
|
| + return oldSelectedCharRanges;
|
| +}
|
| +
|
| @end
|
|
|