// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/cocoa/validation_message_bubble_cocoa.h" #include "base/mac/foundation_util.h" #include "base/memory/scoped_ptr.h" #include "base/strings/sys_string_conversions.h" #import "chrome/browser/ui/cocoa/info_bubble_view.h" #import "chrome/browser/ui/cocoa/info_bubble_window.h" #import "chrome/browser/ui/cocoa/validation_message_bubble_controller.h" #include "chrome/browser/ui/validation_message_bubble.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" #include "grit/theme_resources.h" #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h" #import "ui/base/cocoa/base_view.h" #include "ui/base/cocoa/cocoa_base_utils.h" #import "ui/base/cocoa/flipped_view.h" #include "ui/base/resource/resource_bundle.h" const CGFloat kWindowInitialWidth = 200; const CGFloat kWindowInitialHeight = 100; const CGFloat kWindowMinWidth = 64; const CGFloat kWindowMaxWidth = 256; const CGFloat kWindowPadding = 8; const CGFloat kIconTextMargin = 4; const CGFloat kTextVerticalMargin = 4; @implementation ValidationMessageBubbleController - (id)init:(NSWindow*)parentWindow anchoredAt:(NSPoint)anchorPoint mainText:(const base::string16&)mainText subText:(const base::string16&)subText { base::scoped_nsobject window( [[InfoBubbleWindow alloc] initWithContentRect: NSMakeRect(0, 0, kWindowInitialWidth, kWindowInitialHeight) styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]); if ((self = [super initWithWindow:window.get() parentWindow:parentWindow anchoredAt:anchorPoint])) { [[self bubble] setArrowLocation:info_bubble::kTopLeft]; self.shouldOpenAsKeyWindow = NO; NSView* contentView = [ValidationMessageBubbleController constructContentView:mainText subText:subText]; [[window contentView] addSubview:contentView]; NSRect contentFrame = [contentView frame]; NSRect windowFrame = [window frame]; windowFrame.size.width = NSWidth(contentFrame) + kWindowPadding * 2; windowFrame.size.height = NSHeight(contentFrame) + kWindowPadding * 2 + info_bubble::kBubbleArrowHeight; [window setFrame:windowFrame display:NO]; [self showWindow:nil]; } return self; } + (NSView*)constructContentView:(const base::string16&)mainText subText:(const base::string16&)subText { NSRect contentFrame = NSMakeRect(kWindowPadding, kWindowPadding, 0, 0); FlippedView* contentView = [[FlippedView alloc] initWithFrame:contentFrame]; NSImage* image = ResourceBundle::GetSharedInstance() .GetNativeImageNamed(IDR_INPUT_ALERT).ToNSImage(); base::scoped_nsobject imageView([[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, image.size.width, image.size.height)]); [imageView setImageFrameStyle:NSImageFrameNone]; [imageView setImage:image]; [contentView addSubview:imageView]; contentFrame.size.height = image.size.height; const CGFloat textX = NSWidth([imageView frame]) + kIconTextMargin; NSRect textFrame = NSMakeRect(textX, 0, NSWidth(contentFrame) - textX, 0); base::scoped_nsobject text( [[NSTextField alloc] initWithFrame:textFrame]); [text setStringValue:base::SysUTF16ToNSString(mainText)]; [text setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]]; [text setEditable:NO]; [text setBezeled:NO]; const CGFloat minTextWidth = kWindowMinWidth - kWindowPadding * 2 - textX; const CGFloat maxTextWidth = kWindowMaxWidth - kWindowPadding * 2 - textX; [text sizeToFit]; [contentView addSubview:text]; textFrame = [text frame]; if (NSWidth(textFrame) < minTextWidth) { textFrame.size.width = minTextWidth; [text setFrame:textFrame]; } else if (NSWidth(textFrame) > maxTextWidth) { textFrame.size.width = maxTextWidth; textFrame.size.height = 0; [text setFrame:textFrame]; [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:text]; textFrame = [text frame]; } contentFrame.size.width = NSMaxX(textFrame); contentFrame.size.height = std::max(NSHeight(contentFrame), NSHeight(textFrame)); if (!subText.empty()) { NSRect subTextFrame = NSMakeRect( textX, NSMaxY(textFrame) + kTextVerticalMargin, NSWidth(textFrame), 0); base::scoped_nsobject text2( [[NSTextField alloc] initWithFrame:subTextFrame]); [text2 setStringValue:base::SysUTF16ToNSString(subText)]; [text2 setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; [text2 setEditable:NO]; [text2 setBezeled:NO]; [text2 sizeToFit]; subTextFrame = [text2 frame]; if (NSWidth(subTextFrame) > maxTextWidth) { subTextFrame.size.width = maxTextWidth; [text2 setFrame:subTextFrame]; [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:text2]; subTextFrame = [text2 frame]; } [contentView addSubview:text2]; contentFrame.size.width = std::max(NSWidth(contentFrame), NSMaxX(subTextFrame)); contentFrame.size.height = std::max(NSHeight(contentFrame), NSMaxY(subTextFrame)); } [contentView setFrame:contentFrame]; return contentView; } @end // implementation ValidationMessageBubbleCocoa // ---------------------------------------------------------------- // Converts |anchor_in_root_view| in rwhv coordinates to cocoa screen // coordinates, and returns an NSPoint at the center of the bottom side of the // converted rectangle. NSPoint GetAnchorPoint(content::RenderWidgetHost* widget_host, const gfx::Rect& anchor_in_root_view) { BaseView* view = base::mac::ObjCCastStrict( widget_host->GetView()->GetNativeView()); NSRect cocoaRect = [view flipRectToNSRect:anchor_in_root_view]; NSRect windowRect = [view convertRect:cocoaRect toView:nil]; NSPoint point = NSMakePoint(NSMidX(windowRect), NSMinY(windowRect)); return ui::ConvertPointFromWindowToScreen([view window], point); } ValidationMessageBubbleCocoa::ValidationMessageBubbleCocoa( content::WebContents* web_contents, const gfx::Rect& anchor_in_root_view, const base::string16& main_text, const base::string16& sub_text) { content::RenderWidgetHost* widget_host = web_contents->GetRenderViewHost()->GetWidget(); controller_.reset([[[ValidationMessageBubbleController alloc] init:[widget_host->GetView()->GetNativeView() window] anchoredAt:GetAnchorPoint(widget_host, anchor_in_root_view) mainText:main_text subText:sub_text] retain]); } ValidationMessageBubbleCocoa::~ValidationMessageBubbleCocoa() { [controller_ close]; } void ValidationMessageBubbleCocoa::SetPositionRelativeToAnchor( content::RenderWidgetHost* widget_host, const gfx::Rect& anchor_in_root_view) { [controller_ setAnchorPoint:GetAnchorPoint(widget_host, anchor_in_root_view)]; }