Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(288)

Side by Side Diff: chrome/browser/ui/cocoa/infobar_controller.mm

Issue 6377001: [Mac] Organize all infobar files into chrome/browser/ui/cocoa/infobars/.... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2010 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 <Cocoa/Cocoa.h>
6
7 #include "base/logging.h" // for NOTREACHED()
8 #include "base/mac/mac_util.h"
9 #include "base/sys_string_conversions.h"
10 #include "chrome/browser/tab_contents/infobar_delegate.h"
11 #import "chrome/browser/ui/cocoa/animatable_view.h"
12 #include "chrome/browser/ui/cocoa/event_utils.h"
13 #include "chrome/browser/ui/cocoa/infobar.h"
14 #import "chrome/browser/ui/cocoa/infobar_container_controller.h"
15 #import "chrome/browser/ui/cocoa/infobar_controller.h"
16 #include "skia/ext/skia_utils_mac.h"
17 #include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
18 #include "webkit/glue/window_open_disposition.h"
19
20 namespace {
21 // Durations set to match the default SlideAnimation duration.
22 const float kAnimateOpenDuration = 0.12;
23 const float kAnimateCloseDuration = 0.12;
24 }
25
26 // This simple subclass of |NSTextView| just doesn't show the (text) cursor
27 // (|NSTextView| displays the cursor with full keyboard accessibility enabled).
28 @interface InfoBarTextView : NSTextView
29 - (void)fixupCursor;
30 @end
31
32 @implementation InfoBarTextView
33
34 // Never draw the insertion point (otherwise, it shows up without any user
35 // action if full keyboard accessibility is enabled).
36 - (BOOL)shouldDrawInsertionPoint {
37 return NO;
38 }
39
40 - (NSRange)selectionRangeForProposedRange:(NSRange)proposedSelRange
41 granularity:(NSSelectionGranularity)granularity {
42 // Do not allow selections.
43 return NSMakeRange(0, 0);
44 }
45
46 // Convince NSTextView to not show an I-Beam cursor when the cursor is over the
47 // text view but not over actual text.
48 //
49 // http://www.mail-archive.com/cocoa-dev@lists.apple.com/msg10791.html
50 // "NSTextView sets the cursor over itself dynamically, based on considerations
51 // including the text under the cursor. It does so in -mouseEntered:,
52 // -mouseMoved:, and -cursorUpdate:, so those would be points to consider
53 // overriding."
54 - (void)mouseMoved:(NSEvent*)e {
55 [super mouseMoved:e];
56 [self fixupCursor];
57 }
58
59 - (void)mouseEntered:(NSEvent*)e {
60 [super mouseEntered:e];
61 [self fixupCursor];
62 }
63
64 - (void)cursorUpdate:(NSEvent*)e {
65 [super cursorUpdate:e];
66 [self fixupCursor];
67 }
68
69 - (void)fixupCursor {
70 if ([[NSCursor currentCursor] isEqual:[NSCursor IBeamCursor]])
71 [[NSCursor arrowCursor] set];
72 }
73
74 @end
75
76 @interface InfoBarController (PrivateMethods)
77 // Sets |label_| based on |labelPlaceholder_|, sets |labelPlaceholder_| to nil.
78 - (void)initializeLabel;
79
80 // Asks the container controller to remove the infobar for this delegate. This
81 // call will trigger a notification that starts the infobar animating closed.
82 - (void)removeInfoBar;
83
84 // Performs final cleanup after an animation is finished or stopped, including
85 // notifying the InfoBarDelegate that the infobar was closed and removing the
86 // infobar from its container, if necessary.
87 - (void)cleanUpAfterAnimation:(BOOL)finished;
88
89 // Sets the info bar message to the specified |message|, with a hypertext
90 // style link. |link| will be inserted into message at |linkOffset|.
91 - (void)setLabelToMessage:(NSString*)message
92 withLink:(NSString*)link
93 atOffset:(NSUInteger)linkOffset;
94 @end
95
96 @implementation InfoBarController
97
98 @synthesize containerController = containerController_;
99 @synthesize delegate = delegate_;
100
101 - (id)initWithDelegate:(InfoBarDelegate*)delegate {
102 DCHECK(delegate);
103 if ((self = [super initWithNibName:@"InfoBar"
104 bundle:base::mac::MainAppBundle()])) {
105 delegate_ = delegate;
106 }
107 return self;
108 }
109
110 // All infobars have an icon, so we set up the icon in the base class
111 // awakeFromNib.
112 - (void)awakeFromNib {
113 DCHECK(delegate_);
114 if (delegate_->GetIcon()) {
115 [image_ setImage:gfx::SkBitmapToNSImage(*(delegate_->GetIcon()))];
116 } else {
117 // No icon, remove it from the view and grow the textfield to include the
118 // space.
119 NSRect imageFrame = [image_ frame];
120 NSRect labelFrame = [labelPlaceholder_ frame];
121 labelFrame.size.width += NSMinX(imageFrame) - NSMinX(labelFrame);
122 labelFrame.origin.x = imageFrame.origin.x;
123 [image_ removeFromSuperview];
124 [labelPlaceholder_ setFrame:labelFrame];
125 }
126 [self initializeLabel];
127
128 [self addAdditionalControls];
129 }
130
131 // Called when someone clicks on the embedded link.
132 - (BOOL) textView:(NSTextView*)textView
133 clickedOnLink:(id)link
134 atIndex:(NSUInteger)charIndex {
135 if ([self respondsToSelector:@selector(linkClicked)])
136 [self performSelector:@selector(linkClicked)];
137 return YES;
138 }
139
140 // Called when someone clicks on the ok button.
141 - (void)ok:(id)sender {
142 // Subclasses must override this method if they do not hide the ok button.
143 NOTREACHED();
144 }
145
146 // Called when someone clicks on the cancel button.
147 - (void)cancel:(id)sender {
148 // Subclasses must override this method if they do not hide the cancel button.
149 NOTREACHED();
150 }
151
152 // Called when someone clicks on the close button.
153 - (void)dismiss:(id)sender {
154 [self removeInfoBar];
155 }
156
157 - (AnimatableView*)animatableView {
158 return static_cast<AnimatableView*>([self view]);
159 }
160
161 - (void)open {
162 // Simply reset the frame size to its opened size, forcing a relayout.
163 CGFloat finalHeight = [[self view] frame].size.height;
164 [[self animatableView] setHeight:finalHeight];
165 }
166
167 - (void)animateOpen {
168 // Force the frame size to be 0 and then start an animation.
169 NSRect frame = [[self view] frame];
170 CGFloat finalHeight = frame.size.height;
171 frame.size.height = 0;
172 [[self view] setFrame:frame];
173 [[self animatableView] animateToNewHeight:finalHeight
174 duration:kAnimateOpenDuration];
175 }
176
177 - (void)close {
178 // Stop any running animations.
179 [[self animatableView] stopAnimation];
180 infoBarClosing_ = YES;
181 [self cleanUpAfterAnimation:YES];
182 }
183
184 - (void)animateClosed {
185 // Start animating closed. We will receive a notification when the animation
186 // is done, at which point we can remove our view from the hierarchy and
187 // notify the delegate that the infobar was closed.
188 [[self animatableView] animateToNewHeight:0 duration:kAnimateCloseDuration];
189
190 // The above call may trigger an animationDidStop: notification for any
191 // currently-running animations, so do not set |infoBarClosing_| until after
192 // starting the animation.
193 infoBarClosing_ = YES;
194 }
195
196 - (void)addAdditionalControls {
197 // Default implementation does nothing.
198 }
199
200 - (void)infobarWillClose {
201 // Default implementation does nothing.
202 }
203
204 - (void)setLabelToMessage:(NSString*)message {
205 NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
206 NSFont* font = [NSFont labelFontOfSize:
207 [NSFont systemFontSizeForControlSize:NSRegularControlSize]];
208 [attributes setObject:font
209 forKey:NSFontAttributeName];
210 [attributes setObject:[NSCursor arrowCursor]
211 forKey:NSCursorAttributeName];
212 scoped_nsobject<NSAttributedString> attributedString(
213 [[NSAttributedString alloc] initWithString:message
214 attributes:attributes]);
215 [[label_.get() textStorage] setAttributedString:attributedString];
216 }
217
218 - (void)removeButtons {
219 // Extend the label all the way across.
220 NSRect labelFrame = [label_.get() frame];
221 labelFrame.size.width = NSMaxX([cancelButton_ frame]) - NSMinX(labelFrame);
222 [okButton_ removeFromSuperview];
223 [cancelButton_ removeFromSuperview];
224 [label_.get() setFrame:labelFrame];
225 }
226
227 @end
228
229 @implementation InfoBarController (PrivateMethods)
230
231 - (void)initializeLabel {
232 // Replace the label placeholder NSTextField with the real label NSTextView.
233 // The former doesn't show links in a nice way, but the latter can't be added
234 // in IB without a containing scroll view, so create the NSTextView
235 // programmatically.
236 label_.reset([[InfoBarTextView alloc]
237 initWithFrame:[labelPlaceholder_ frame]]);
238 [label_.get() setAutoresizingMask:[labelPlaceholder_ autoresizingMask]];
239 [[labelPlaceholder_ superview]
240 replaceSubview:labelPlaceholder_ with:label_.get()];
241 labelPlaceholder_ = nil; // Now released.
242 [label_.get() setDelegate:self];
243 [label_.get() setEditable:NO];
244 [label_.get() setDrawsBackground:NO];
245 [label_.get() setHorizontallyResizable:NO];
246 [label_.get() setVerticallyResizable:NO];
247 }
248
249 - (void)removeInfoBar {
250 // TODO(rohitrao): This method can be called even if the infobar has already
251 // been removed and |delegate_| is NULL. Is there a way to rewrite the code
252 // so that inner event loops don't cause us to try and remove the infobar
253 // twice? http://crbug.com/54253
254 [containerController_ removeDelegate:delegate_];
255 }
256
257 - (void)cleanUpAfterAnimation:(BOOL)finished {
258 // Don't need to do any cleanup if the bar was animating open.
259 if (!infoBarClosing_)
260 return;
261
262 // Notify the delegate that the infobar was closed. The delegate may delete
263 // itself as a result of InfoBarClosed(), so we null out its pointer.
264 if (delegate_) {
265 delegate_->InfoBarClosed();
266 delegate_ = NULL;
267 }
268
269 // If the animation ran to completion, then we need to remove ourselves from
270 // the container. If the animation was interrupted, then the container will
271 // take care of removing us.
272 // TODO(rohitrao): UGH! This works for now, but should be cleaner.
273 if (finished)
274 [containerController_ removeController:self];
275 }
276
277 - (void)animationDidStop:(NSAnimation*)animation {
278 [self cleanUpAfterAnimation:NO];
279 }
280
281 - (void)animationDidEnd:(NSAnimation*)animation {
282 [self cleanUpAfterAnimation:YES];
283 }
284
285 // TODO(joth): This method factors out some common functionality between the
286 // various derived infobar classes, however the class hierarchy itself could
287 // use refactoring to reduce this duplication. http://crbug.com/38924
288 - (void)setLabelToMessage:(NSString*)message
289 withLink:(NSString*)link
290 atOffset:(NSUInteger)linkOffset {
291 if (linkOffset == std::wstring::npos) {
292 // linkOffset == std::wstring::npos means the link should be right-aligned,
293 // which is not supported on Mac (http://crbug.com/47728).
294 NOTIMPLEMENTED();
295 linkOffset = [message length];
296 }
297 // Create an attributes dictionary for the entire message. We have
298 // to expicitly set the font the control's font. We also override
299 // the cursor to give us the normal cursor rather than the text
300 // insertion cursor.
301 NSMutableDictionary* linkAttributes = [NSMutableDictionary dictionary];
302 [linkAttributes setObject:[NSCursor arrowCursor]
303 forKey:NSCursorAttributeName];
304 NSFont* font = [NSFont labelFontOfSize:
305 [NSFont systemFontSizeForControlSize:NSRegularControlSize]];
306 [linkAttributes setObject:font
307 forKey:NSFontAttributeName];
308
309 // Create the attributed string for the main message text.
310 scoped_nsobject<NSMutableAttributedString> infoText(
311 [[NSMutableAttributedString alloc] initWithString:message]);
312 [infoText.get() addAttributes:linkAttributes
313 range:NSMakeRange(0, [infoText.get() length])];
314 // Add additional attributes to style the link text appropriately as
315 // well as linkify it.
316 [linkAttributes setObject:[NSColor blueColor]
317 forKey:NSForegroundColorAttributeName];
318 [linkAttributes setObject:[NSNumber numberWithBool:YES]
319 forKey:NSUnderlineStyleAttributeName];
320 [linkAttributes setObject:[NSCursor pointingHandCursor]
321 forKey:NSCursorAttributeName];
322 [linkAttributes setObject:[NSNumber numberWithInt:NSSingleUnderlineStyle]
323 forKey:NSUnderlineStyleAttributeName];
324 [linkAttributes setObject:[NSString string] // dummy value
325 forKey:NSLinkAttributeName];
326
327 // Insert the link text into the string at the appropriate offset.
328 scoped_nsobject<NSAttributedString> attributedString(
329 [[NSAttributedString alloc] initWithString:link
330 attributes:linkAttributes]);
331 [infoText.get() insertAttributedString:attributedString.get()
332 atIndex:linkOffset];
333 // Update the label view with the new text.
334 [[label_.get() textStorage] setAttributedString:infoText];
335 }
336
337 @end
338
339
340 /////////////////////////////////////////////////////////////////////////
341 // AlertInfoBarController implementation
342
343 @implementation AlertInfoBarController
344
345 // Alert infobars have a text message.
346 - (void)addAdditionalControls {
347 // No buttons.
348 [self removeButtons];
349
350 // Insert the text.
351 AlertInfoBarDelegate* delegate = delegate_->AsAlertInfoBarDelegate();
352 DCHECK(delegate);
353 [self setLabelToMessage:base::SysUTF16ToNSString(delegate->GetMessageText())];
354 }
355
356 @end
357
358
359 /////////////////////////////////////////////////////////////////////////
360 // LinkInfoBarController implementation
361
362 @implementation LinkInfoBarController
363
364 // Link infobars have a text message, of which part is linkified. We
365 // use an NSAttributedString to display styled text, and we set a
366 // NSLink attribute on the hyperlink portion of the message. Infobars
367 // use a custom NSTextField subclass, which allows us to override
368 // textView:clickedOnLink:atIndex: and intercept clicks.
369 //
370 - (void)addAdditionalControls {
371 // No buttons.
372 [self removeButtons];
373
374 LinkInfoBarDelegate* delegate = delegate_->AsLinkInfoBarDelegate();
375 DCHECK(delegate);
376 size_t offset = std::wstring::npos;
377 string16 message = delegate->GetMessageTextWithOffset(&offset);
378 [self setLabelToMessage:base::SysUTF16ToNSString(message)
379 withLink:base::SysUTF16ToNSString(delegate->GetLinkText())
380 atOffset:offset];
381 }
382
383 // Called when someone clicks on the link in the infobar. This method
384 // is called by the InfobarTextField on its delegate (the
385 // LinkInfoBarController).
386 - (void)linkClicked {
387 WindowOpenDisposition disposition =
388 event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
389 if (delegate_ && delegate_->AsLinkInfoBarDelegate()->LinkClicked(disposition))
390 [self removeInfoBar];
391 }
392
393 @end
394
395
396 /////////////////////////////////////////////////////////////////////////
397 // ConfirmInfoBarController implementation
398
399 @implementation ConfirmInfoBarController
400
401 // Called when someone clicks on the "OK" button.
402 - (IBAction)ok:(id)sender {
403 if (delegate_ && delegate_->AsConfirmInfoBarDelegate()->Accept())
404 [self removeInfoBar];
405 }
406
407 // Called when someone clicks on the "Cancel" button.
408 - (IBAction)cancel:(id)sender {
409 if (delegate_ && delegate_->AsConfirmInfoBarDelegate()->Cancel())
410 [self removeInfoBar];
411 }
412
413 // Confirm infobars can have OK and/or cancel buttons, depending on
414 // the return value of GetButtons(). We create each button if
415 // required and position them to the left of the close button.
416 - (void)addAdditionalControls {
417 ConfirmInfoBarDelegate* delegate = delegate_->AsConfirmInfoBarDelegate();
418 DCHECK(delegate);
419 int visibleButtons = delegate->GetButtons();
420
421 NSRect okButtonFrame = [okButton_ frame];
422 NSRect cancelButtonFrame = [cancelButton_ frame];
423
424 DCHECK(NSMaxX(okButtonFrame) < NSMinX(cancelButtonFrame))
425 << "Cancel button expected to be on the right of the Ok button in nib";
426
427 CGFloat rightEdge = NSMaxX(cancelButtonFrame);
428 CGFloat spaceBetweenButtons =
429 NSMinX(cancelButtonFrame) - NSMaxX(okButtonFrame);
430 CGFloat spaceBeforeButtons =
431 NSMinX(okButtonFrame) - NSMaxX([label_.get() frame]);
432
433 // Update and position the Cancel button if needed. Otherwise, hide it.
434 if (visibleButtons & ConfirmInfoBarDelegate::BUTTON_CANCEL) {
435 [cancelButton_ setTitle:base::SysUTF16ToNSString(
436 delegate->GetButtonLabel(ConfirmInfoBarDelegate::BUTTON_CANCEL))];
437 [GTMUILocalizerAndLayoutTweaker sizeToFitView:cancelButton_];
438 cancelButtonFrame = [cancelButton_ frame];
439
440 // Position the cancel button to the left of the Close button.
441 cancelButtonFrame.origin.x = rightEdge - cancelButtonFrame.size.width;
442 [cancelButton_ setFrame:cancelButtonFrame];
443
444 // Update the rightEdge
445 rightEdge = NSMinX(cancelButtonFrame);
446 } else {
447 [cancelButton_ removeFromSuperview];
448 }
449
450 // Update and position the OK button if needed. Otherwise, hide it.
451 if (visibleButtons & ConfirmInfoBarDelegate::BUTTON_OK) {
452 [okButton_ setTitle:base::SysUTF16ToNSString(
453 delegate->GetButtonLabel(ConfirmInfoBarDelegate::BUTTON_OK))];
454 [GTMUILocalizerAndLayoutTweaker sizeToFitView:okButton_];
455 okButtonFrame = [okButton_ frame];
456
457 // If we had a Cancel button, leave space between the buttons.
458 if (visibleButtons & ConfirmInfoBarDelegate::BUTTON_CANCEL) {
459 rightEdge -= spaceBetweenButtons;
460 }
461
462 // Position the OK button on our current right edge.
463 okButtonFrame.origin.x = rightEdge - okButtonFrame.size.width;
464 [okButton_ setFrame:okButtonFrame];
465
466
467 // Update the rightEdge
468 rightEdge = NSMinX(okButtonFrame);
469 } else {
470 [okButton_ removeFromSuperview];
471 }
472
473 // If we had either button, leave space before the edge of the textfield.
474 if ((visibleButtons & ConfirmInfoBarDelegate::BUTTON_CANCEL) ||
475 (visibleButtons & ConfirmInfoBarDelegate::BUTTON_OK)) {
476 rightEdge -= spaceBeforeButtons;
477 }
478
479 NSRect frame = [label_.get() frame];
480 DCHECK(rightEdge > NSMinX(frame))
481 << "Need to make the xib larger to handle buttons with text this long";
482 frame.size.width = rightEdge - NSMinX(frame);
483 [label_.get() setFrame:frame];
484
485 // Set the text and link.
486 NSString* message = base::SysUTF16ToNSString(delegate->GetMessageText());
487 string16 link = delegate->GetLinkText();
488 if (link.empty()) {
489 // Simple case: no link, so just set the message directly.
490 [self setLabelToMessage:message];
491 } else {
492 // Inserting the link unintentionally causes the text to have a slightly
493 // different result to the simple case above: text is truncated on word
494 // boundaries (if needed) rather than elided with ellipses.
495
496 // Add spacing between the label and the link.
497 message = [message stringByAppendingString:@" "];
498 [self setLabelToMessage:message
499 withLink:base::SysUTF16ToNSString(link)
500 atOffset:[message length]];
501 }
502 }
503
504 // Called when someone clicks on the link in the infobar. This method
505 // is called by the InfobarTextField on its delegate (the
506 // LinkInfoBarController).
507 - (void)linkClicked {
508 WindowOpenDisposition disposition =
509 event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
510 if (delegate_ &&
511 delegate_->AsConfirmInfoBarDelegate()->LinkClicked(disposition))
512 [self removeInfoBar];
513 }
514
515 @end
516
517
518 //////////////////////////////////////////////////////////////////////////
519 // CreateInfoBar() implementations
520
521 InfoBar* AlertInfoBarDelegate::CreateInfoBar() {
522 AlertInfoBarController* controller =
523 [[AlertInfoBarController alloc] initWithDelegate:this];
524 return new InfoBar(controller);
525 }
526
527 InfoBar* LinkInfoBarDelegate::CreateInfoBar() {
528 LinkInfoBarController* controller =
529 [[LinkInfoBarController alloc] initWithDelegate:this];
530 return new InfoBar(controller);
531 }
532
533 InfoBar* ConfirmInfoBarDelegate::CreateInfoBar() {
534 ConfirmInfoBarController* controller =
535 [[ConfirmInfoBarController alloc] initWithDelegate:this];
536 return new InfoBar(controller);
537 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698