Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / confirm_bubble_cocoa.mm
1 // Copyright (c) 2012 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.
4
5 #import "chrome/browser/ui/cocoa/confirm_bubble_cocoa.h"
6
7 #include "base/strings/string16.h"
8 #include "chrome/browser/themes/theme_service.h"
9 #import "chrome/browser/ui/cocoa/confirm_bubble_controller.h"
10 #include "chrome/browser/ui/confirm_bubble.h"
11 #include "chrome/browser/ui/confirm_bubble_model.h"
12 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSBezierPath+RoundRect.h"
13 #include "ui/gfx/image/image.h"
14 #include "ui/gfx/point.h"
15
16 // The width for the message text. We break lines so the specified message fits
17 // into this width.
18 const int kMaxMessageWidth = 400;
19
20 // The corner redius of this bubble view.
21 const int kBubbleCornerRadius = 3;
22
23 // The color for the border of this bubble view.
24 const float kBubbleWindowEdge = 0.7f;
25
26 // Constants used for layouting controls. These variables are copied from
27 // "ui/views/layout/layout_constants.h".
28 // Vertical spacing between a label and some control.
29 const int kLabelToControlVerticalSpacing = 8;
30
31 // Horizontal spacing between controls that are logically related.
32 const int kRelatedControlHorizontalSpacing = 8;
33
34 // Vertical spacing between controls that are logically related.
35 const int kRelatedControlVerticalSpacing = 8;
36
37 // Vertical spacing between the edge of the window and the
38 // top or bottom of a button.
39 const int kButtonVEdgeMargin = 6;
40
41 // Horizontal spacing between the edge of the window and the
42 // left or right of a button.
43 const int kButtonHEdgeMargin = 7;
44
45 namespace chrome {
46
47 void ShowConfirmBubble(gfx::NativeWindow window,
48                        gfx::NativeView anchor_view,
49                        const gfx::Point& origin,
50                        ConfirmBubbleModel* model) {
51   // Create a custom NSViewController that manages a bubble view, and add it to
52   // a child to the specified |anchor_view|. This controller will be
53   // automatically deleted when it loses first-responder status.
54   ConfirmBubbleController* controller =
55       [[ConfirmBubbleController alloc] initWithParent:anchor_view
56                                                origin:origin.ToCGPoint()
57                                                 model:model];
58   [anchor_view addSubview:[controller view]
59                positioned:NSWindowAbove
60                relativeTo:nil];
61   [[anchor_view window] makeFirstResponder:[controller view]];
62 }
63
64 }  // namespace chrome
65
66 // An interface that is derived from NSTextView and does not accept
67 // first-responder status, i.e. a NSTextView-derived class that never becomes
68 // the first responder. When we click a NSTextView object, it becomes the first
69 // responder. Unfortunately, we delete the ConfirmBubbleCocoa object anytime
70 // when it loses first-responder status not to prevent disturbing other
71 // responders.
72 // To prevent text views in this ConfirmBubbleCocoa object from stealing the
73 // first-responder status, we use this view in the ConfirmBubbleCocoa object.
74 @interface ConfirmBubbleTextView : NSTextView
75 @end
76
77 @implementation ConfirmBubbleTextView
78
79 - (BOOL)acceptsFirstResponder {
80   return NO;
81 }
82
83 @end
84
85 // Private Methods
86 @interface ConfirmBubbleCocoa (Private)
87 - (void)performLayout;
88 - (void)closeBubble;
89 @end
90
91 @implementation ConfirmBubbleCocoa
92
93 - (id)initWithParent:(NSView*)parent
94           controller:(ConfirmBubbleController*)controller {
95   // Create a NSView and set its width. We will set its position and height
96   // after finish layouting controls in performLayout:.
97   NSRect bounds =
98       NSMakeRect(0, 0, kMaxMessageWidth + kButtonHEdgeMargin * 2, 0);
99   if (self = [super initWithFrame:bounds]) {
100     parent_ = parent;
101     controller_ = controller;
102     [self performLayout];
103   }
104   return self;
105 }
106
107 - (void)drawRect:(NSRect)dirtyRect {
108   // Fill the background rectangle in white and draw its edge.
109   NSRect bounds = [self bounds];
110   bounds = NSInsetRect(bounds, 0.5, 0.5);
111   NSBezierPath* border =
112       [NSBezierPath gtm_bezierPathWithRoundRect:bounds
113                             topLeftCornerRadius:kBubbleCornerRadius
114                            topRightCornerRadius:kBubbleCornerRadius
115                          bottomLeftCornerRadius:kBubbleCornerRadius
116                         bottomRightCornerRadius:kBubbleCornerRadius];
117   [[NSColor colorWithDeviceWhite:1.0f alpha:1.0f] set];
118   [border fill];
119   [[NSColor colorWithDeviceWhite:kBubbleWindowEdge alpha:1.0f] set];
120   [border stroke];
121 }
122
123 // An NSResponder method.
124 - (BOOL)resignFirstResponder {
125   // We do not only accept this request but also close this bubble when we are
126   // asked to resign the first responder. This bubble should be displayed only
127   // while it is the first responder.
128   [self closeBubble];
129   return YES;
130 }
131
132 // NSControl action handlers. These handlers are called when we click a cancel
133 // button, a close icon, and an OK button, respectively.
134 - (IBAction)cancel:(id)sender {
135   [controller_ cancel];
136   [self closeBubble];
137 }
138
139 - (IBAction)close:(id)sender {
140   [self closeBubble];
141 }
142
143 - (IBAction)ok:(id)sender {
144   [controller_ accept];
145   [self closeBubble];
146 }
147
148 // An NSTextViewDelegate method. This function is called when we click a link in
149 // this bubble.
150 - (BOOL)textView:(NSTextView*)textView
151    clickedOnLink:(id)link
152          atIndex:(NSUInteger)charIndex {
153   [controller_ linkClicked];
154   [self closeBubble];
155   return YES;
156 }
157
158 // Initializes controls specified by the ConfirmBubbleModel object and layouts
159 // them into this bubble. This function retrieves text and images from the
160 // ConfirmBubbleModel object (via the ConfirmBubbleController object) and
161 // layouts them programmatically. This function layouts controls in the botom-up
162 // order since NSView uses bottom-up coordinate.
163 - (void)performLayout {
164   NSRect frameRect = [self frame];
165
166   // Add the ok button and the cancel button to the first row if we have either
167   // of them.
168   CGFloat left = kButtonHEdgeMargin;
169   CGFloat right = NSWidth(frameRect) - kButtonHEdgeMargin;
170   CGFloat bottom = kButtonVEdgeMargin;
171   CGFloat height = 0;
172   if ([controller_ hasOkButton]) {
173     okButton_.reset([[NSButton alloc]
174         initWithFrame:NSMakeRect(0, bottom, 0, 0)]);
175     [okButton_.get() setBezelStyle:NSRoundedBezelStyle];
176     [okButton_.get() setTitle:[controller_ okButtonText]];
177     [okButton_.get() setTarget:self];
178     [okButton_.get() setAction:@selector(ok:)];
179     [okButton_.get() sizeToFit];
180     NSRect okButtonRect = [okButton_.get() frame];
181     right -= NSWidth(okButtonRect);
182     okButtonRect.origin.x = right;
183     [okButton_.get() setFrame:okButtonRect];
184     [self addSubview:okButton_.get()];
185     height = std::max(height, NSHeight(okButtonRect));
186   }
187   if ([controller_ hasCancelButton]) {
188     cancelButton_.reset([[NSButton alloc]
189         initWithFrame:NSMakeRect(0, bottom, 0, 0)]);
190     [cancelButton_.get() setBezelStyle:NSRoundedBezelStyle];
191     [cancelButton_.get() setTitle:[controller_ cancelButtonText]];
192     [cancelButton_.get() setTarget:self];
193     [cancelButton_.get() setAction:@selector(cancel:)];
194     [cancelButton_.get() sizeToFit];
195     NSRect cancelButtonRect = [cancelButton_.get() frame];
196     right -= NSWidth(cancelButtonRect) + kButtonHEdgeMargin;
197     cancelButtonRect.origin.x = right;
198     [cancelButton_.get() setFrame:cancelButtonRect];
199     [self addSubview:cancelButton_.get()];
200     height = std::max(height, NSHeight(cancelButtonRect));
201   }
202
203   // Add the message label (and the link label) to the second row.
204   left = kButtonHEdgeMargin;
205   right = NSWidth(frameRect);
206   bottom += height + kRelatedControlVerticalSpacing;
207   height = 0;
208   messageLabel_.reset([[ConfirmBubbleTextView alloc]
209       initWithFrame:NSMakeRect(left, bottom, kMaxMessageWidth, 0)]);
210   NSString* messageText = [controller_ messageText];
211   NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
212   base::scoped_nsobject<NSMutableAttributedString> attributedMessage(
213       [[NSMutableAttributedString alloc] initWithString:messageText
214                                              attributes:attributes]);
215   NSString* linkText = [controller_ linkText];
216   if (linkText) {
217     base::scoped_nsobject<NSAttributedString> whiteSpace(
218         [[NSAttributedString alloc] initWithString:@" "]);
219     [attributedMessage.get() appendAttributedString:whiteSpace.get()];
220     [attributes setObject:[NSString string]
221                    forKey:NSLinkAttributeName];
222     base::scoped_nsobject<NSAttributedString> attributedLink(
223         [[NSAttributedString alloc] initWithString:linkText
224                                         attributes:attributes]);
225     [attributedMessage.get() appendAttributedString:attributedLink.get()];
226   }
227   [[messageLabel_.get() textStorage] setAttributedString:attributedMessage];
228   [messageLabel_.get() setHorizontallyResizable:NO];
229   [messageLabel_.get() setVerticallyResizable:YES];
230   [messageLabel_.get() setEditable:NO];
231   [messageLabel_.get() setDrawsBackground:NO];
232   [messageLabel_.get() setDelegate:self];
233   [messageLabel_.get() sizeToFit];
234   height = NSHeight([messageLabel_.get() frame]);
235   [self addSubview:messageLabel_.get()];
236
237   // Add the icon and the title label to the third row.
238   left = kButtonHEdgeMargin;
239   right = NSWidth(frameRect);
240   bottom += height + kLabelToControlVerticalSpacing;
241   height = 0;
242   NSImage* iconImage = [controller_ icon];
243   if (iconImage) {
244     icon_.reset([[NSImageView alloc] initWithFrame:NSMakeRect(
245         left, bottom, [iconImage size].width, [iconImage size].height)]);
246     [icon_.get() setImage:iconImage];
247     [self addSubview:icon_.get()];
248     left += NSWidth([icon_.get() frame]) + kRelatedControlHorizontalSpacing;
249     height = std::max(height, NSHeight([icon_.get() frame]));
250   }
251   titleLabel_.reset([[NSTextView alloc]
252       initWithFrame:NSMakeRect(left, bottom, right - left, 0)]);
253   [titleLabel_.get() setString:[controller_ title]];
254   [titleLabel_.get() setHorizontallyResizable:NO];
255   [titleLabel_.get() setVerticallyResizable:YES];
256   [titleLabel_.get() setEditable:NO];
257   [titleLabel_.get() setSelectable:NO];
258   [titleLabel_.get() setDrawsBackground:NO];
259   [titleLabel_.get() sizeToFit];
260   [self addSubview:titleLabel_.get()];
261   height = std::max(height, NSHeight([titleLabel_.get() frame]));
262
263   // Adjust the frame rectangle of this bubble so we can show all controls.
264   NSRect parentRect = [parent_ frame];
265   frameRect.size.height = bottom + height + kButtonVEdgeMargin;
266   frameRect.origin.x = (NSWidth(parentRect) - NSWidth(frameRect)) / 2;
267   frameRect.origin.y = NSHeight(parentRect) - NSHeight(frameRect);
268   [self setFrame:frameRect];
269 }
270
271 // Closes this bubble and releases all resources. This function just puts the
272 // owner ConfirmBubbleController object to the current autorelease pool. (This
273 // view will be deleted when the owner object is deleted.)
274 - (void)closeBubble {
275   [self removeFromSuperview];
276   [controller_ autorelease];
277   parent_ = nil;
278   controller_ = nil;
279 }
280
281 @end
282
283 @implementation ConfirmBubbleCocoa (ExposedForUnitTesting)
284
285 - (void)clickOk {
286   [self ok:self];
287 }
288
289 - (void)clickCancel {
290   [self cancel:self];
291 }
292
293 - (void)clickLink {
294   [self textView:messageLabel_.get() clickedOnLink:nil atIndex:0];
295 }
296
297 @end