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 #include "chrome/browser/ui/cocoa/validation_message_bubble_cocoa.h"
7 #include "base/mac/foundation_util.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/strings/sys_string_conversions.h"
10 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
11 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
12 #import "chrome/browser/ui/cocoa/validation_message_bubble_controller.h"
13 #include "chrome/browser/ui/validation_message_bubble.h"
14 #include "content/public/browser/render_view_host.h"
15 #include "content/public/browser/render_widget_host_view.h"
16 #include "content/public/browser/web_contents.h"
17 #include "grit/theme_resources.h"
18 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
19 #import "ui/base/cocoa/base_view.h"
20 #import "ui/base/cocoa/flipped_view.h"
21 #include "ui/base/resource/resource_bundle.h"
23 const CGFloat kWindowInitialWidth = 200;
24 const CGFloat kWindowInitialHeight = 100;
25 const CGFloat kWindowMinWidth = 64;
26 const CGFloat kWindowMaxWidth = 256;
27 const CGFloat kWindowPadding = 8;
28 const CGFloat kIconTextMargin = 4;
29 const CGFloat kTextVerticalMargin = 4;
31 @implementation ValidationMessageBubbleController
33 - (id)init:(NSWindow*)parentWindow
34 anchoredAt:(NSPoint)anchorPoint
35 mainText:(const base::string16&)mainText
36 subText:(const base::string16&)subText {
38 base::scoped_nsobject<InfoBubbleWindow> window(
39 [[InfoBubbleWindow alloc] initWithContentRect:
40 NSMakeRect(0, 0, kWindowInitialWidth, kWindowInitialHeight)
41 styleMask:NSBorderlessWindowMask
42 backing:NSBackingStoreBuffered
44 if ((self = [super initWithWindow:window.get()
45 parentWindow:parentWindow
46 anchoredAt:anchorPoint])) {
47 [[self bubble] setArrowLocation:info_bubble::kTopLeft];
48 self.shouldOpenAsKeyWindow = NO;
50 NSView* contentView = [ValidationMessageBubbleController
51 constructContentView:mainText subText:subText];
52 [[window contentView] addSubview:contentView];
53 NSRect contentFrame = [contentView frame];
54 NSRect windowFrame = [window frame];
55 windowFrame.size.width = NSWidth(contentFrame) + kWindowPadding * 2;
56 windowFrame.size.height = NSHeight(contentFrame) + kWindowPadding * 2
57 + info_bubble::kBubbleArrowHeight;
58 [window setFrame:windowFrame display:NO];
60 [self showWindow:nil];
65 + (NSView*)constructContentView:(const base::string16&)mainText
66 subText:(const base::string16&)subText {
67 NSRect contentFrame = NSMakeRect(kWindowPadding, kWindowPadding, 0, 0);
68 FlippedView* contentView = [[FlippedView alloc] initWithFrame:contentFrame];
70 NSImage* image = ResourceBundle::GetSharedInstance()
71 .GetNativeImageNamed(IDR_INPUT_ALERT).ToNSImage();
72 base::scoped_nsobject<NSImageView> imageView([[NSImageView alloc]
73 initWithFrame:NSMakeRect(0, 0, image.size.width, image.size.height)]);
74 [imageView setImageFrameStyle:NSImageFrameNone];
75 [imageView setImage:image];
76 [contentView addSubview:imageView];
77 contentFrame.size.height = image.size.height;
79 const CGFloat textX = NSWidth([imageView frame]) + kIconTextMargin;
80 NSRect textFrame = NSMakeRect(textX, 0, NSWidth(contentFrame) - textX, 0);
81 base::scoped_nsobject<NSTextField> text(
82 [[NSTextField alloc] initWithFrame:textFrame]);
83 [text setStringValue:base::SysUTF16ToNSString(mainText)];
84 [text setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
85 [text setEditable:NO];
87 const CGFloat minTextWidth = kWindowMinWidth - kWindowPadding * 2 - textX;
88 const CGFloat maxTextWidth = kWindowMaxWidth - kWindowPadding * 2 - textX;
90 [contentView addSubview:text];
91 textFrame = [text frame];
92 if (NSWidth(textFrame) < minTextWidth) {
93 textFrame.size.width = minTextWidth;
94 [text setFrame:textFrame];
95 } else if (NSWidth(textFrame) > maxTextWidth) {
96 textFrame.size.width = maxTextWidth;
97 textFrame.size.height = 0;
98 [text setFrame:textFrame];
99 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:text];
100 textFrame = [text frame];
102 contentFrame.size.width = NSMaxX(textFrame);
103 contentFrame.size.height =
104 std::max(NSHeight(contentFrame), NSHeight(textFrame));
106 if (!subText.empty()) {
107 NSRect subTextFrame = NSMakeRect(
108 textX, NSMaxY(textFrame) + kTextVerticalMargin,
109 NSWidth(textFrame), 0);
110 base::scoped_nsobject<NSTextField> text2(
111 [[NSTextField alloc] initWithFrame:subTextFrame]);
112 [text2 setStringValue:base::SysUTF16ToNSString(subText)];
113 [text2 setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
114 [text2 setEditable:NO];
115 [text2 setBezeled:NO];
117 subTextFrame = [text2 frame];
118 if (NSWidth(subTextFrame) > maxTextWidth) {
119 subTextFrame.size.width = maxTextWidth;
120 [text2 setFrame:subTextFrame];
121 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:text2];
122 subTextFrame = [text2 frame];
124 [contentView addSubview:text2];
125 contentFrame.size.width =
126 std::max(NSWidth(contentFrame), NSMaxX(subTextFrame));
127 contentFrame.size.height =
128 std::max(NSHeight(contentFrame), NSMaxY(subTextFrame));
131 [contentView setFrame:contentFrame];
136 @end // implementation ValidationMessageBubbleCocoa
138 // ----------------------------------------------------------------
140 // Converts |anchor_in_root_view| in rwhv coordinates to cocoa screen
141 // coordinates, and returns an NSPoint at the center of the bottom side of the
142 // converted rectangle.
143 NSPoint GetAnchorPoint(content::RenderWidgetHost* widget_host,
144 const gfx::Rect& anchor_in_root_view) {
145 BaseView* view = base::mac::ObjCCastStrict<BaseView>(
146 widget_host->GetView()->GetNativeView());
147 NSRect cocoaRect = [view flipRectToNSRect:anchor_in_root_view];
148 NSRect windowRect = [view convertRect:cocoaRect toView:nil];
149 NSPoint point = NSMakePoint(NSMidX(windowRect), NSMinY(windowRect));
150 return [[view window] convertBaseToScreen:point];
153 ValidationMessageBubbleCocoa::ValidationMessageBubbleCocoa(
154 content::WebContents* web_contents,
155 const gfx::Rect& anchor_in_root_view,
156 const base::string16& main_text,
157 const base::string16& sub_text) {
158 content::RenderWidgetHost* widget_host = web_contents->GetRenderViewHost();
159 controller_.reset([[[ValidationMessageBubbleController alloc]
160 init:[widget_host->GetView()->GetNativeView() window]
161 anchoredAt:GetAnchorPoint(widget_host, anchor_in_root_view)
163 subText:sub_text] retain]);
166 ValidationMessageBubbleCocoa::~ValidationMessageBubbleCocoa() {
170 void ValidationMessageBubbleCocoa::SetPositionRelativeToAnchor(
171 content::RenderWidgetHost* widget_host,
172 const gfx::Rect& anchor_in_root_view) {
173 [controller_ setAnchorPoint:GetAnchorPoint(widget_host, anchor_in_root_view)];