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