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