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