OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #import <Cocoa/Cocoa.h> | 5 #import <Cocoa/Cocoa.h> |
6 | 6 |
7 #include "base/logging.h" // for NOTREACHED() | 7 #include "base/logging.h" // for NOTREACHED() |
8 #include "base/mac/mac_util.h" | 8 #include "base/mac/mac_util.h" |
9 #include "base/sys_string_conversions.h" | 9 #include "base/sys_string_conversions.h" |
10 #include "chrome/browser/infobars/infobar_tab_helper.h" | 10 #include "chrome/browser/infobars/infobar_tab_helper.h" |
11 #include "chrome/browser/tab_contents/confirm_infobar_delegate.h" | 11 #include "chrome/browser/tab_contents/confirm_infobar_delegate.h" |
12 #include "chrome/browser/tab_contents/link_infobar_delegate.h" | 12 #include "chrome/browser/tab_contents/link_infobar_delegate.h" |
13 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" | |
14 #import "chrome/browser/ui/cocoa/animatable_view.h" | 13 #import "chrome/browser/ui/cocoa/animatable_view.h" |
15 #import "chrome/browser/ui/cocoa/browser_window_controller.h" | 14 #import "chrome/browser/ui/cocoa/browser_window_controller.h" |
16 #include "chrome/browser/ui/cocoa/event_utils.h" | 15 #include "chrome/browser/ui/cocoa/event_utils.h" |
| 16 #import "chrome/browser/ui/cocoa/hyperlink_text_view.h" |
17 #include "chrome/browser/ui/cocoa/infobars/infobar.h" | 17 #include "chrome/browser/ui/cocoa/infobars/infobar.h" |
18 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h" | 18 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h" |
19 #import "chrome/browser/ui/cocoa/infobars/infobar_controller.h" | 19 #import "chrome/browser/ui/cocoa/infobars/infobar_controller.h" |
20 #import "chrome/browser/ui/cocoa/infobars/infobar_gradient_view.h" | 20 #import "chrome/browser/ui/cocoa/infobars/infobar_gradient_view.h" |
21 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h" | 21 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h" |
| 22 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" |
22 #include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" | 23 #include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" |
23 #include "ui/gfx/image/image.h" | 24 #include "ui/gfx/image/image.h" |
24 #include "webkit/glue/window_open_disposition.h" | 25 #include "webkit/glue/window_open_disposition.h" |
25 | 26 |
26 namespace { | 27 namespace { |
27 // Durations set to match the default SlideAnimation duration. | 28 // Durations set to match the default SlideAnimation duration. |
28 const float kAnimateOpenDuration = 0.12; | 29 const float kAnimateOpenDuration = 0.12; |
29 const float kAnimateCloseDuration = 0.12; | 30 const float kAnimateCloseDuration = 0.12; |
30 | |
31 // The baseline shift for text in the NSTextView. | |
32 const float kTextBaselineShift = -1.0; | |
33 } | 31 } |
34 | 32 |
35 // This simple subclass of |NSTextView| just doesn't show the (text) cursor | |
36 // (|NSTextView| displays the cursor with full keyboard accessibility enabled). | |
37 @interface InfoBarTextView : NSTextView | |
38 - (void)fixupCursor; | |
39 @end | |
40 | |
41 @implementation InfoBarTextView | |
42 | |
43 // Never draw the insertion point (otherwise, it shows up without any user | |
44 // action if full keyboard accessibility is enabled). | |
45 - (BOOL)shouldDrawInsertionPoint { | |
46 return NO; | |
47 } | |
48 | |
49 - (NSRange)selectionRangeForProposedRange:(NSRange)proposedSelRange | |
50 granularity:(NSSelectionGranularity)granularity { | |
51 // Do not allow selections. | |
52 return NSMakeRange(0, 0); | |
53 } | |
54 | |
55 // Convince NSTextView to not show an I-Beam cursor when the cursor is over the | |
56 // text view but not over actual text. | |
57 // | |
58 // http://www.mail-archive.com/cocoa-dev@lists.apple.com/msg10791.html | |
59 // "NSTextView sets the cursor over itself dynamically, based on considerations | |
60 // including the text under the cursor. It does so in -mouseEntered:, | |
61 // -mouseMoved:, and -cursorUpdate:, so those would be points to consider | |
62 // overriding." | |
63 - (void)mouseMoved:(NSEvent*)e { | |
64 [super mouseMoved:e]; | |
65 [self fixupCursor]; | |
66 } | |
67 | |
68 - (void)mouseEntered:(NSEvent*)e { | |
69 [super mouseEntered:e]; | |
70 [self fixupCursor]; | |
71 } | |
72 | |
73 - (void)cursorUpdate:(NSEvent*)e { | |
74 [super cursorUpdate:e]; | |
75 [self fixupCursor]; | |
76 } | |
77 | |
78 - (void)fixupCursor { | |
79 if ([[NSCursor currentCursor] isEqual:[NSCursor IBeamCursor]]) | |
80 [[NSCursor arrowCursor] set]; | |
81 } | |
82 | |
83 @end | |
84 | |
85 @interface InfoBarController (PrivateMethods) | 33 @interface InfoBarController (PrivateMethods) |
86 // Sets |label_| based on |labelPlaceholder_|, sets |labelPlaceholder_| to nil. | 34 // Sets |label_| based on |labelPlaceholder_|, sets |labelPlaceholder_| to nil. |
87 - (void)initializeLabel; | 35 - (void)initializeLabel; |
88 | 36 |
89 // Asks the container controller to remove the infobar for this delegate. This | 37 // Asks the container controller to remove the infobar for this delegate. This |
90 // call will trigger a notification that starts the infobar animating closed. | 38 // call will trigger a notification that starts the infobar animating closed. |
91 - (void)removeSelf; | 39 - (void)removeSelf; |
92 | 40 |
93 // Performs final cleanup after an animation is finished or stopped, including | 41 // Performs final cleanup after an animation is finished or stopped, including |
94 // notifying the InfoBarDelegate that the infobar was closed and removing the | 42 // notifying the InfoBarDelegate that the infobar was closed and removing the |
95 // infobar from its container, if necessary. | 43 // infobar from its container, if necessary. |
96 - (void)cleanUpAfterAnimation:(BOOL)finished; | 44 - (void)cleanUpAfterAnimation:(BOOL)finished; |
97 | 45 |
98 // Sets the info bar message to the specified |message|, with a hypertext | |
99 // style link. |link| will be inserted into message at |linkOffset|. | |
100 - (void)setLabelToMessage:(NSString*)message | |
101 withLink:(NSString*)link | |
102 atOffset:(NSUInteger)linkOffset; | |
103 | |
104 // Returns the point, in gradient view coordinates, at which the apex of the | 46 // Returns the point, in gradient view coordinates, at which the apex of the |
105 // infobar tip should be drawn. | 47 // infobar tip should be drawn. |
106 - (NSPoint)pointForTipApex; | 48 - (NSPoint)pointForTipApex; |
107 @end | 49 @end |
108 | 50 |
109 @implementation InfoBarController | 51 @implementation InfoBarController |
110 | 52 |
111 @synthesize containerController = containerController_; | 53 @synthesize containerController = containerController_; |
112 @synthesize delegate = delegate_; | 54 @synthesize delegate = delegate_; |
113 | 55 |
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
222 } | 164 } |
223 | 165 |
224 - (void)addAdditionalControls { | 166 - (void)addAdditionalControls { |
225 // Default implementation does nothing. | 167 // Default implementation does nothing. |
226 } | 168 } |
227 | 169 |
228 - (void)infobarWillClose { | 170 - (void)infobarWillClose { |
229 // Default implementation does nothing. | 171 // Default implementation does nothing. |
230 } | 172 } |
231 | 173 |
232 - (void)setLabelToMessage:(NSString*)message { | |
233 NSMutableDictionary* attributes = [NSMutableDictionary dictionary]; | |
234 NSFont* font = [NSFont labelFontOfSize: | |
235 [NSFont systemFontSizeForControlSize:NSRegularControlSize]]; | |
236 [attributes setObject:font | |
237 forKey:NSFontAttributeName]; | |
238 [attributes setObject:[NSCursor arrowCursor] | |
239 forKey:NSCursorAttributeName]; | |
240 [attributes setObject:[NSNumber numberWithFloat:kTextBaselineShift] | |
241 forKey:NSBaselineOffsetAttributeName]; | |
242 scoped_nsobject<NSAttributedString> attributedString( | |
243 [[NSAttributedString alloc] initWithString:message | |
244 attributes:attributes]); | |
245 [[label_.get() textStorage] setAttributedString:attributedString]; | |
246 } | |
247 | |
248 - (void)removeButtons { | 174 - (void)removeButtons { |
249 // Extend the label all the way across. | 175 // Extend the label all the way across. |
250 NSRect labelFrame = [label_.get() frame]; | 176 NSRect labelFrame = [label_.get() frame]; |
251 labelFrame.size.width = NSMaxX([cancelButton_ frame]) - NSMinX(labelFrame); | 177 labelFrame.size.width = NSMaxX([cancelButton_ frame]) - NSMinX(labelFrame); |
252 [okButton_ removeFromSuperview]; | 178 [okButton_ removeFromSuperview]; |
253 [cancelButton_ removeFromSuperview]; | 179 [cancelButton_ removeFromSuperview]; |
254 [label_.get() setFrame:labelFrame]; | 180 [label_.get() setFrame:labelFrame]; |
255 } | 181 } |
256 | 182 |
257 @end | 183 @end |
258 | 184 |
259 @implementation InfoBarController (PrivateMethods) | 185 @implementation InfoBarController (PrivateMethods) |
260 | 186 |
261 - (void)initializeLabel { | 187 - (void)initializeLabel { |
262 // Replace the label placeholder NSTextField with the real label NSTextView. | 188 // Replace the label placeholder NSTextField with the real label NSTextView. |
263 // The former doesn't show links in a nice way, but the latter can't be added | 189 // The former doesn't show links in a nice way, but the latter can't be added |
264 // in IB without a containing scroll view, so create the NSTextView | 190 // in IB without a containing scroll view, so create the NSTextView |
265 // programmatically. | 191 // programmatically. |
266 label_.reset([[InfoBarTextView alloc] | 192 label_.reset([[HyperlinkTextView alloc] |
267 initWithFrame:[labelPlaceholder_ frame]]); | 193 initWithFrame:[labelPlaceholder_ frame]]); |
268 [label_.get() setAutoresizingMask:[labelPlaceholder_ autoresizingMask]]; | 194 [label_.get() setAutoresizingMask:[labelPlaceholder_ autoresizingMask]]; |
269 [[labelPlaceholder_ superview] | 195 [[labelPlaceholder_ superview] |
270 replaceSubview:labelPlaceholder_ with:label_.get()]; | 196 replaceSubview:labelPlaceholder_ with:label_.get()]; |
271 labelPlaceholder_ = nil; // Now released. | 197 labelPlaceholder_ = nil; // Now released. |
272 [label_.get() setDelegate:self]; | 198 [label_.get() setDelegate:self]; |
273 [label_.get() setEditable:NO]; | |
274 [label_.get() setDrawsBackground:NO]; | |
275 [label_.get() setHorizontallyResizable:NO]; | |
276 [label_.get() setVerticallyResizable:NO]; | |
277 } | 199 } |
278 | 200 |
279 - (void)removeSelf { | 201 - (void)removeSelf { |
280 // TODO(rohitrao): This method can be called even if the infobar has already | 202 // TODO(rohitrao): This method can be called even if the infobar has already |
281 // been removed and |delegate_| is NULL. Is there a way to rewrite the code | 203 // been removed and |delegate_| is NULL. Is there a way to rewrite the code |
282 // so that inner event loops don't cause us to try and remove the infobar | 204 // so that inner event loops don't cause us to try and remove the infobar |
283 // twice? http://crbug.com/54253 | 205 // twice? http://crbug.com/54253 |
284 if (owner_) | 206 if (owner_) |
285 owner_->infobar_tab_helper()->RemoveInfoBar(delegate_); | 207 owner_->infobar_tab_helper()->RemoveInfoBar(delegate_); |
286 owner_ = NULL; | 208 owner_ = NULL; |
(...skipping 20 matching lines...) Expand all Loading... |
307 } | 229 } |
308 | 230 |
309 - (void)animationDidStop:(NSAnimation*)animation { | 231 - (void)animationDidStop:(NSAnimation*)animation { |
310 [self cleanUpAfterAnimation:NO]; | 232 [self cleanUpAfterAnimation:NO]; |
311 } | 233 } |
312 | 234 |
313 - (void)animationDidEnd:(NSAnimation*)animation { | 235 - (void)animationDidEnd:(NSAnimation*)animation { |
314 [self cleanUpAfterAnimation:YES]; | 236 [self cleanUpAfterAnimation:YES]; |
315 } | 237 } |
316 | 238 |
317 // TODO(joth): This method factors out some common functionality between the | |
318 // various derived infobar classes, however the class hierarchy itself could | |
319 // use refactoring to reduce this duplication. http://crbug.com/38924 | |
320 - (void)setLabelToMessage:(NSString*)message | |
321 withLink:(NSString*)link | |
322 atOffset:(NSUInteger)linkOffset { | |
323 if (linkOffset == std::wstring::npos) { | |
324 // linkOffset == std::wstring::npos means the link should be right-aligned, | |
325 // which is not supported on Mac (http://crbug.com/47728). | |
326 NOTIMPLEMENTED(); | |
327 linkOffset = [message length]; | |
328 } | |
329 // Create an attributes dictionary for the entire message. We have | |
330 // to explicitly set the control's font. We also override the cursor to give | |
331 // us the normal cursor rather than the text insertion cursor. | |
332 NSMutableDictionary* linkAttributes = [NSMutableDictionary dictionary]; | |
333 [linkAttributes setObject:[NSCursor arrowCursor] | |
334 forKey:NSCursorAttributeName]; | |
335 NSFont* font = [NSFont labelFontOfSize: | |
336 [NSFont systemFontSizeForControlSize:NSRegularControlSize]]; | |
337 [linkAttributes setObject:font | |
338 forKey:NSFontAttributeName]; | |
339 | |
340 // Create the attributed string for the main message text. | |
341 scoped_nsobject<NSMutableAttributedString> infoText( | |
342 [[NSMutableAttributedString alloc] initWithString:message]); | |
343 [infoText.get() addAttributes:linkAttributes | |
344 range:NSMakeRange(0, [infoText.get() length])]; | |
345 // Add additional attributes to style the link text appropriately as | |
346 // well as linkify it. | |
347 [linkAttributes setObject:[NSColor blueColor] | |
348 forKey:NSForegroundColorAttributeName]; | |
349 [linkAttributes setObject:[NSNumber numberWithBool:YES] | |
350 forKey:NSUnderlineStyleAttributeName]; | |
351 [linkAttributes setObject:[NSCursor pointingHandCursor] | |
352 forKey:NSCursorAttributeName]; | |
353 [linkAttributes setObject:[NSNumber numberWithInt:NSSingleUnderlineStyle] | |
354 forKey:NSUnderlineStyleAttributeName]; | |
355 [linkAttributes setObject:[NSString string] // dummy value | |
356 forKey:NSLinkAttributeName]; | |
357 | |
358 // Insert the link text into the string at the appropriate offset. | |
359 scoped_nsobject<NSAttributedString> attributedString( | |
360 [[NSAttributedString alloc] initWithString:link | |
361 attributes:linkAttributes]); | |
362 [infoText.get() insertAttributedString:attributedString.get() | |
363 atIndex:linkOffset]; | |
364 // The entire text needs a baseline shift. | |
365 [infoText addAttribute:NSBaselineOffsetAttributeName | |
366 value:[NSNumber numberWithDouble:kTextBaselineShift] | |
367 range:NSMakeRange(0, [infoText length])]; | |
368 | |
369 // Update the label view with the new text. | |
370 [[label_.get() textStorage] setAttributedString:infoText]; | |
371 } | |
372 | |
373 - (NSPoint)pointForTipApex { | 239 - (NSPoint)pointForTipApex { |
374 BrowserWindowController* windowController = | 240 BrowserWindowController* windowController = |
375 [containerController_ browserWindowController]; | 241 [containerController_ browserWindowController]; |
376 if (!windowController) { | 242 if (!windowController) { |
377 // This should only happen in unit tests. | 243 // This should only happen in unit tests. |
378 return NSZeroPoint; | 244 return NSZeroPoint; |
379 } | 245 } |
380 | 246 |
381 LocationBarViewMac* locationBar = [windowController locationBarBridge]; | 247 LocationBarViewMac* locationBar = [windowController locationBarBridge]; |
382 NSPoint point = locationBar->GetPageInfoBubblePoint(); | 248 NSPoint point = locationBar->GetPageInfoBubblePoint(); |
(...skipping 16 matching lines...) Expand all Loading... |
399 // textView:clickedOnLink:atIndex: and intercept clicks. | 265 // textView:clickedOnLink:atIndex: and intercept clicks. |
400 // | 266 // |
401 - (void)addAdditionalControls { | 267 - (void)addAdditionalControls { |
402 // No buttons. | 268 // No buttons. |
403 [self removeButtons]; | 269 [self removeButtons]; |
404 | 270 |
405 LinkInfoBarDelegate* delegate = delegate_->AsLinkInfoBarDelegate(); | 271 LinkInfoBarDelegate* delegate = delegate_->AsLinkInfoBarDelegate(); |
406 DCHECK(delegate); | 272 DCHECK(delegate); |
407 size_t offset = std::wstring::npos; | 273 size_t offset = std::wstring::npos; |
408 string16 message = delegate->GetMessageTextWithOffset(&offset); | 274 string16 message = delegate->GetMessageTextWithOffset(&offset); |
409 [self setLabelToMessage:base::SysUTF16ToNSString(message) | 275 string16 link = delegate->GetLinkText(); |
410 withLink:base::SysUTF16ToNSString(delegate->GetLinkText()) | 276 NSFont* font = [NSFont labelFontOfSize: |
411 atOffset:offset]; | 277 [NSFont systemFontSizeForControlSize:NSRegularControlSize]]; |
| 278 HyperlinkTextView* view = (HyperlinkTextView*)label_.get(); |
| 279 [view setMessageAndLink:base::SysUTF16ToNSString(message) |
| 280 withLink:base::SysUTF16ToNSString(link) |
| 281 atOffset:offset |
| 282 font:font |
| 283 messageColor:[NSColor blackColor] |
| 284 linkColor:[NSColor blueColor]]; |
412 } | 285 } |
413 | 286 |
414 // Called when someone clicks on the link in the infobar. This method | 287 // Called when someone clicks on the link in the infobar. This method |
415 // is called by the InfobarTextField on its delegate (the | 288 // is called by the InfobarTextField on its delegate (the |
416 // LinkInfoBarController). | 289 // LinkInfoBarController). |
417 - (void)linkClicked { | 290 - (void)linkClicked { |
418 WindowOpenDisposition disposition = | 291 WindowOpenDisposition disposition = |
419 event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]); | 292 event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]); |
420 if (delegate_ && | 293 if (delegate_ && |
421 delegate_->AsLinkInfoBarDelegate()->LinkClicked(disposition)) { | 294 delegate_->AsLinkInfoBarDelegate()->LinkClicked(disposition)) { |
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
518 | 391 |
519 NSRect frame = [label_.get() frame]; | 392 NSRect frame = [label_.get() frame]; |
520 DCHECK(rightEdge > NSMinX(frame)) | 393 DCHECK(rightEdge > NSMinX(frame)) |
521 << "Need to make the xib larger to handle buttons with text this long"; | 394 << "Need to make the xib larger to handle buttons with text this long"; |
522 frame.size.width = rightEdge - NSMinX(frame); | 395 frame.size.width = rightEdge - NSMinX(frame); |
523 [label_.get() setFrame:frame]; | 396 [label_.get() setFrame:frame]; |
524 | 397 |
525 // Set the text and link. | 398 // Set the text and link. |
526 NSString* message = base::SysUTF16ToNSString(delegate->GetMessageText()); | 399 NSString* message = base::SysUTF16ToNSString(delegate->GetMessageText()); |
527 string16 link = delegate->GetLinkText(); | 400 string16 link = delegate->GetLinkText(); |
528 if (link.empty()) { | 401 if (!link.empty()) { |
529 // Simple case: no link, so just set the message directly. | |
530 [self setLabelToMessage:message]; | |
531 } else { | |
532 // Inserting the link unintentionally causes the text to have a slightly | |
533 // different result to the simple case above: text is truncated on word | |
534 // boundaries (if needed) rather than elided with ellipses. | |
535 | |
536 // Add spacing between the label and the link. | 402 // Add spacing between the label and the link. |
537 message = [message stringByAppendingString:@" "]; | 403 message = [message stringByAppendingString:@" "]; |
538 [self setLabelToMessage:message | |
539 withLink:base::SysUTF16ToNSString(link) | |
540 atOffset:[message length]]; | |
541 } | 404 } |
| 405 NSFont* font = [NSFont labelFontOfSize: |
| 406 [NSFont systemFontSizeForControlSize:NSRegularControlSize]]; |
| 407 HyperlinkTextView* view = (HyperlinkTextView*)label_.get(); |
| 408 [view setMessageAndLink:message |
| 409 withLink:base::SysUTF16ToNSString(link) |
| 410 atOffset:[message length] |
| 411 font:font |
| 412 messageColor:[NSColor blackColor] |
| 413 linkColor:[NSColor blueColor]]; |
542 } | 414 } |
543 | 415 |
544 // Called when someone clicks on the link in the infobar. This method | 416 // Called when someone clicks on the link in the infobar. This method |
545 // is called by the InfobarTextField on its delegate (the | 417 // is called by the InfobarTextField on its delegate (the |
546 // LinkInfoBarController). | 418 // LinkInfoBarController). |
547 - (void)linkClicked { | 419 - (void)linkClicked { |
548 WindowOpenDisposition disposition = | 420 WindowOpenDisposition disposition = |
549 event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]); | 421 event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]); |
550 if (delegate_ && | 422 if (delegate_ && |
551 delegate_->AsConfirmInfoBarDelegate()->LinkClicked(disposition)) | 423 delegate_->AsConfirmInfoBarDelegate()->LinkClicked(disposition)) |
(...skipping 10 matching lines...) Expand all Loading... |
562 LinkInfoBarController* controller = | 434 LinkInfoBarController* controller = |
563 [[LinkInfoBarController alloc] initWithDelegate:this owner:owner]; | 435 [[LinkInfoBarController alloc] initWithDelegate:this owner:owner]; |
564 return new InfoBar(controller, this); | 436 return new InfoBar(controller, this); |
565 } | 437 } |
566 | 438 |
567 InfoBar* ConfirmInfoBarDelegate::CreateInfoBar(TabContentsWrapper* owner) { | 439 InfoBar* ConfirmInfoBarDelegate::CreateInfoBar(TabContentsWrapper* owner) { |
568 ConfirmInfoBarController* controller = | 440 ConfirmInfoBarController* controller = |
569 [[ConfirmInfoBarController alloc] initWithDelegate:this owner:owner]; | 441 [[ConfirmInfoBarController alloc] initWithDelegate:this owner:owner]; |
570 return new InfoBar(controller, this); | 442 return new InfoBar(controller, this); |
571 } | 443 } |
OLD | NEW |