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

Side by Side Diff: chrome/browser/cocoa/status_bubble_mac.mm

Issue 3437002: Make status bubble expand on Mac to fit URLs longer than the standard width.... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 10 years, 3 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
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2009 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 #include "chrome/browser/cocoa/status_bubble_mac.h" 5 #include "chrome/browser/cocoa/status_bubble_mac.h"
6 6
7 #include <limits> 7 #include <limits>
8 8
9 #include "app/text_elider.h" 9 #include "app/text_elider.h"
10 #include "base/compiler_specific.h" 10 #include "base/compiler_specific.h"
11 #include "base/message_loop.h" 11 #include "base/message_loop.h"
12 #include "base/string_util.h" 12 #include "base/string_util.h"
13 #include "base/sys_string_conversions.h" 13 #include "base/sys_string_conversions.h"
14 #include "base/utf_string_conversions.h" 14 #include "base/utf_string_conversions.h"
15 #import "chrome/browser/cocoa/bubble_view.h" 15 #import "chrome/browser/cocoa/bubble_view.h"
16 #include "gfx/point.h" 16 #include "gfx/point.h"
17 #include "googleurl/src/gurl.h" 17 #include "net/base/net_util.h"
18 #import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h" 18 #import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h"
19 #import "third_party/GTM/AppKit/GTMNSBezierPath+RoundRect.h" 19 #import "third_party/GTM/AppKit/GTMNSBezierPath+RoundRect.h"
20 #import "third_party/GTM/AppKit/GTMNSColor+Luminance.h" 20 #import "third_party/GTM/AppKit/GTMNSColor+Luminance.h"
21 21
22 namespace { 22 namespace {
23 23
24 const int kWindowHeight = 18; 24 const int kWindowHeight = 18;
25 25
26 // The width of the bubble in relation to the width of the parent window. 26 // The width of the bubble in relation to the width of the parent window.
27 const double kWindowWidthPercent = 1.0 / 3.0; 27 const double kWindowWidthPercent = 1.0 / 3.0;
(...skipping 17 matching lines...) Expand all
45 // How long each fade should last. 45 // How long each fade should last.
46 const NSTimeInterval kShowFadeInDurationSeconds = 0.120; 46 const NSTimeInterval kShowFadeInDurationSeconds = 0.120;
47 const NSTimeInterval kHideFadeOutDurationSeconds = 0.200; 47 const NSTimeInterval kHideFadeOutDurationSeconds = 0.200;
48 48
49 // The minimum representable time interval. This can be used as the value 49 // The minimum representable time interval. This can be used as the value
50 // passed to +[NSAnimationContext setDuration:] to stop an in-progress 50 // passed to +[NSAnimationContext setDuration:] to stop an in-progress
51 // animation as quickly as possible. 51 // animation as quickly as possible.
52 const NSTimeInterval kMinimumTimeInterval = 52 const NSTimeInterval kMinimumTimeInterval =
53 std::numeric_limits<NSTimeInterval>::min(); 53 std::numeric_limits<NSTimeInterval>::min();
54 54
55 // How quickly the status bubble should expand, in seconds.
56 const CGFloat kExpansionDuration = 0.125;
57
55 } // namespace 58 } // namespace
56 59
57 @interface StatusBubbleAnimationDelegate : NSObject { 60 @interface StatusBubbleAnimationDelegate : NSObject {
58 @private 61 @private
59 StatusBubbleMac* statusBubble_; // weak; owns us indirectly 62 StatusBubbleMac* statusBubble_; // weak; owns us indirectly
60 } 63 }
61 64
62 - (id)initWithStatusBubble:(StatusBubbleMac*)statusBubble; 65 - (id)initWithStatusBubble:(StatusBubbleMac*)statusBubble;
63 66
64 // Invalidates this object so that no further calls will be made to 67 // Invalidates this object so that no further calls will be made to
(...skipping 21 matching lines...) Expand all
86 89
87 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished { 90 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished {
88 if (statusBubble_) 91 if (statusBubble_)
89 statusBubble_->AnimationDidStop(animation, finished ? true : false); 92 statusBubble_->AnimationDidStop(animation, finished ? true : false);
90 } 93 }
91 94
92 @end 95 @end
93 96
94 StatusBubbleMac::StatusBubbleMac(NSWindow* parent, id delegate) 97 StatusBubbleMac::StatusBubbleMac(NSWindow* parent, id delegate)
95 : ALLOW_THIS_IN_INITIALIZER_LIST(timer_factory_(this)), 98 : ALLOW_THIS_IN_INITIALIZER_LIST(timer_factory_(this)),
99 ALLOW_THIS_IN_INITIALIZER_LIST(expand_timer_factory_(this)),
96 parent_(parent), 100 parent_(parent),
97 delegate_(delegate), 101 delegate_(delegate),
98 window_(nil), 102 window_(nil),
99 status_text_(nil), 103 status_text_(nil),
100 url_text_(nil), 104 url_text_(nil),
101 state_(kBubbleHidden), 105 state_(kBubbleHidden),
102 immediate_(false) { 106 immediate_(false),
107 is_expanded_(false) {
103 } 108 }
104 109
105 StatusBubbleMac::~StatusBubbleMac() { 110 StatusBubbleMac::~StatusBubbleMac() {
106 Hide(); 111 Hide();
107 112
108 if (window_) { 113 if (window_) {
109 [[[window_ animationForKey:kFadeAnimationKey] delegate] invalidate]; 114 [[[window_ animationForKey:kFadeAnimationKey] delegate] invalidate];
110 Detach(); 115 Detach();
111 [window_ release]; 116 [window_ release];
112 window_ = nil; 117 window_ = nil;
113 } 118 }
114 } 119 }
115 120
116 void StatusBubbleMac::SetStatus(const string16& status) { 121 void StatusBubbleMac::SetStatus(const string16& status) {
117 Create(); 122 Create();
118 123
119 SetText(status, false); 124 SetText(status, false);
120 } 125 }
121 126
122 void StatusBubbleMac::SetURL(const GURL& url, const string16& languages) { 127 void StatusBubbleMac::SetURL(const GURL& url, const string16& languages) {
128 url_ = url;
129 languages_ = languages;
130
123 Create(); 131 Create();
124 132
125 NSRect frame = [window_ frame]; 133 NSRect frame = [window_ frame];
126 int text_width = static_cast<int>(frame.size.width - 134
135 // Reset frame size when bubble is hidden.
136 if (state_ == kBubbleHidden) {
137 is_expanded_ = false;
138 frame.size.width = NSWidth(CalculateWindowFrame(/*expand=*/false));
139 [window_ setFrame:frame display:NO];
140 }
141
142 int text_width = static_cast<int>(NSWidth(frame) -
127 kBubbleViewTextPositionX - 143 kBubbleViewTextPositionX -
128 kTextPadding); 144 kTextPadding);
145
146 // Scale from view to window coordinates before eliding URL string.
147 NSSize scaled_width = NSMakeSize(text_width, 0);
148 scaled_width = [[parent_ contentView] convertSize:scaled_width fromView:nil];
149 text_width = static_cast<int>(scaled_width.width);
129 NSFont* font = [[window_ contentView] font]; 150 NSFont* font = [[window_ contentView] font];
130 gfx::Font font_chr(base::SysNSStringToWide([font fontName]), 151 gfx::Font font_chr(base::SysNSStringToWide([font fontName]),
131 [font pointSize]); 152 [font pointSize]);
132 153
154 string16 original_url_text = net::FormatUrl(url, UTF16ToUTF8(languages));
133 string16 status = WideToUTF16(gfx::ElideUrl(url, font_chr, text_width, 155 string16 status = WideToUTF16(gfx::ElideUrl(url, font_chr, text_width,
134 UTF16ToWideHack(languages))); 156 UTF16ToWideHack(languages)));
135 157
136 SetText(status, true); 158 SetText(status, true);
159
160 // In testing, don't use animation. When ExpandBubble is tested, it is
161 // called explicitly.
162 if (immediate_)
163 return;
164 else
165 CancelExpandTimer();
166
167 // If the bubble has been expanded, the user has already hovered over a link
168 // to trigger the expanded state. Don't wait to change the bubble in this
169 // case -- immediately expand or contract to fit the URL.
170 if (is_expanded_ && !url.is_empty()) {
171 ExpandBubble();
172 } else if (original_url_text.length() > status.length()) {
173 MessageLoop::current()->PostDelayedTask(FROM_HERE,
174 expand_timer_factory_.NewRunnableMethod(
175 &StatusBubbleMac::ExpandBubble), kExpandHoverDelay);
176 }
137 } 177 }
138 178
139 void StatusBubbleMac::SetText(const string16& text, bool is_url) { 179 void StatusBubbleMac::SetText(const string16& text, bool is_url) {
140 // The status bubble allows the status and URL strings to be set 180 // The status bubble allows the status and URL strings to be set
141 // independently. Whichever was set non-empty most recently will be the 181 // independently. Whichever was set non-empty most recently will be the
142 // value displayed. When both are empty, the status bubble hides. 182 // value displayed. When both are empty, the status bubble hides.
143 183
144 NSString* text_ns = base::SysUTF16ToNSString(text); 184 NSString* text_ns = base::SysUTF16ToNSString(text);
145 185
146 NSString** main; 186 NSString** main;
(...skipping 25 matching lines...) Expand all
172 show = false; 212 show = false;
173 213
174 if (show) 214 if (show)
175 StartShowing(); 215 StartShowing();
176 else 216 else
177 StartHiding(); 217 StartHiding();
178 } 218 }
179 219
180 void StatusBubbleMac::Hide() { 220 void StatusBubbleMac::Hide() {
181 CancelTimer(); 221 CancelTimer();
222 CancelExpandTimer();
223 is_expanded_ = false;
182 224
183 bool fade_out = false; 225 bool fade_out = false;
184 if (state_ == kBubbleHidingFadeOut || state_ == kBubbleShowingFadeIn) { 226 if (state_ == kBubbleHidingFadeOut || state_ == kBubbleShowingFadeIn) {
185 SetState(kBubbleHidingFadeOut); 227 SetState(kBubbleHidingFadeOut);
186 228
187 if (!immediate_) { 229 if (!immediate_) {
188 // An animation is in progress. Cancel it by starting a new animation. 230 // An animation is in progress. Cancel it by starting a new animation.
189 // Use kMinimumTimeInterval to set the opacity as rapidly as possible. 231 // Use kMinimumTimeInterval to set the opacity as rapidly as possible.
190 fade_out = true; 232 fade_out = true;
191 [NSAnimationContext beginGrouping]; 233 [NSAnimationContext beginGrouping];
192 [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval]; 234 [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval];
193 [[window_ animator] setAlphaValue:0.0]; 235 [[window_ animator] setAlphaValue:0.0];
194 [NSAnimationContext endGrouping]; 236 [NSAnimationContext endGrouping];
195 } 237 }
196 } 238 }
197 239
198 if (!fade_out) { 240 if (!fade_out) {
199 // No animation is in progress, so the opacity can be set directly. 241 // No animation is in progress, so the opacity can be set directly.
200 [window_ setAlphaValue:0.0]; 242 [window_ setAlphaValue:0.0];
201 SetState(kBubbleHidden); 243 SetState(kBubbleHidden);
202 } 244 }
203 245
246 // Stop any width animation and reset the bubble size.
247 if (!immediate_) {
248 [NSAnimationContext beginGrouping];
249 [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval];
250 [[window_ animator] setFrame:CalculateWindowFrame(/*expand=*/false)
251 display:NO];
252 [NSAnimationContext endGrouping];
253 } else {
254 [window_ setFrame:CalculateWindowFrame(/*expand=*/false) display:NO];
255 }
256
204 [status_text_ release]; 257 [status_text_ release];
205 status_text_ = nil; 258 status_text_ = nil;
206 [url_text_ release]; 259 [url_text_ release];
207 url_text_ = nil; 260 url_text_ = nil;
208 } 261 }
209 262
210 void StatusBubbleMac::MouseMoved( 263 void StatusBubbleMac::MouseMoved(
211 const gfx::Point& location, bool left_content) { 264 const gfx::Point& location, bool left_content) {
212 if (left_content) 265 if (left_content)
213 return; 266 return;
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
291 } 344 }
292 345
293 void StatusBubbleMac::UpdateDownloadShelfVisibility(bool visible) { 346 void StatusBubbleMac::UpdateDownloadShelfVisibility(bool visible) {
294 } 347 }
295 348
296 void StatusBubbleMac::Create() { 349 void StatusBubbleMac::Create() {
297 if (window_) 350 if (window_)
298 return; 351 return;
299 352
300 // TODO(avi):fix this for RTL 353 // TODO(avi):fix this for RTL
301 NSRect window_rect = CalculateWindowFrame(); 354 NSRect window_rect = CalculateWindowFrame(/*expand=*/false);
302 // initWithContentRect has origin in screen coords and size in scaled window 355 // initWithContentRect has origin in screen coords and size in scaled window
303 // coordinates. 356 // coordinates.
304 window_rect.size = 357 window_rect.size =
305 [[parent_ contentView] convertSize:window_rect.size fromView:nil]; 358 [[parent_ contentView] convertSize:window_rect.size fromView:nil];
306 window_ = [[NSWindow alloc] initWithContentRect:window_rect 359 window_ = [[NSWindow alloc] initWithContentRect:window_rect
307 styleMask:NSBorderlessWindowMask 360 styleMask:NSBorderlessWindowMask
308 backing:NSBackingStoreBuffered 361 backing:NSBackingStoreBuffered
309 defer:YES]; 362 defer:YES];
310 [window_ setMovableByWindowBackground:NO]; 363 [window_ setMovableByWindowBackground:NO];
311 [window_ setBackgroundColor:[NSColor clearColor]]; 364 [window_ setBackgroundColor:[NSColor clearColor]];
(...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after
528 CancelTimer(); 581 CancelTimer();
529 } 582 }
530 583
531 // If the state is kBubbleHidden, kBubbleHidingFadeOut, or 584 // If the state is kBubbleHidden, kBubbleHidingFadeOut, or
532 // kBubbleHidingTimer, leave everything alone. The timer is not reset as 585 // kBubbleHidingTimer, leave everything alone. The timer is not reset as
533 // with kBubbleShowingTimer in StartShowing() because a subsequent request 586 // with kBubbleShowingTimer in StartShowing() because a subsequent request
534 // to hide something while one is already in flight does not invalidate the 587 // to hide something while one is already in flight does not invalidate the
535 // earlier request. 588 // earlier request.
536 } 589 }
537 590
591 void StatusBubbleMac::CancelExpandTimer() {
592 DCHECK([NSThread isMainThread]);
593 expand_timer_factory_.RevokeAll();
594 }
595
596 void StatusBubbleMac::ExpandBubble() {
597 // Calculate the width available for expanded and standard bubbles.
598 NSRect window_frame = CalculateWindowFrame(/*expand=*/true);
599 CGFloat max_bubble_width = NSWidth(window_frame);
600 CGFloat standard_bubble_width =
601 NSWidth(CalculateWindowFrame(/*expand=*/false));
602
603 // Generate the URL string that fits in the expanded bubble.
604 NSFont* font = [[window_ contentView] font];
605 gfx::Font font_chr(base::SysNSStringToWide([font fontName]),
606 [font pointSize]);
607 string16 expanded_url = WideToUTF16(gfx::ElideUrl(url_, font_chr,
608 max_bubble_width, UTF16ToWide(languages_)));
609
610 // Scale width from gfx::Font in view coordinates to window coordinates.
611 int required_width_for_string =
612 font_chr.GetStringWidth(UTF16ToWide(expanded_url)) +
613 kTextPadding * 2 + kBubbleViewTextPositionX;
614 NSSize scaled_width = NSMakeSize(required_width_for_string, 0);
615 scaled_width = [[parent_ contentView] convertSize:scaled_width toView:nil];
616 required_width_for_string = scaled_width.width;
617
618 // The expanded width must be at least as wide as the standard width, but no
619 // wider than the maximum width for its parent frame.
620 int expanded_bubble_width =
621 std::max(standard_bubble_width,
622 std::min(max_bubble_width,
623 static_cast<CGFloat>(required_width_for_string)));
624
625 SetText(expanded_url, true);
626 is_expanded_ = true;
627 window_frame.size.width = expanded_bubble_width;
628
629 // In testing, don't do any animation.
630 if (immediate_) {
631 [window_ setFrame:window_frame display:YES];
632 return;
633 }
634
635 [NSAnimationContext beginGrouping];
636 [[NSAnimationContext currentContext] setDuration:kExpansionDuration];
637 [[window_ animator] setFrame:window_frame display:YES];
638 [NSAnimationContext endGrouping];
639 }
640
538 void StatusBubbleMac::UpdateSizeAndPosition() { 641 void StatusBubbleMac::UpdateSizeAndPosition() {
539 if (!window_) 642 if (!window_)
540 return; 643 return;
541 644
542 [window_ setFrame:CalculateWindowFrame() display:YES]; 645 [window_ setFrame:CalculateWindowFrame(/*expand=*/false) display:YES];
543 } 646 }
544 647
545 void StatusBubbleMac::SwitchParentWindow(NSWindow* parent) { 648 void StatusBubbleMac::SwitchParentWindow(NSWindow* parent) {
546 DCHECK(parent); 649 DCHECK(parent);
547 650
548 // If not attached, just update our member variable and position. 651 // If not attached, just update our member variable and position.
549 if (!is_attached()) { 652 if (!is_attached()) {
550 parent_ = parent; 653 parent_ = parent;
551 [[window_ contentView] setThemeProvider:parent]; 654 [[window_ contentView] setThemeProvider:parent];
552 UpdateSizeAndPosition(); 655 UpdateSizeAndPosition();
553 return; 656 return;
554 } 657 }
555 658
556 Detach(); 659 Detach();
557 parent_ = parent; 660 parent_ = parent;
558 [[window_ contentView] setThemeProvider:parent]; 661 [[window_ contentView] setThemeProvider:parent];
559 Attach(); 662 Attach();
560 UpdateSizeAndPosition(); 663 UpdateSizeAndPosition();
561 } 664 }
562 665
563 NSRect StatusBubbleMac::CalculateWindowFrame() { 666 NSRect StatusBubbleMac::CalculateWindowFrame(bool expanded_width) {
564 DCHECK(parent_); 667 DCHECK(parent_);
565 668
566 NSSize size = NSMakeSize(0, kWindowHeight); 669 NSSize size = NSMakeSize(0, kWindowHeight);
567 size = [[parent_ contentView] convertSize:size toView:nil]; 670 size = [[parent_ contentView] convertSize:size toView:nil];
568 671
569 NSRect rect = [parent_ frame]; 672 NSRect rect = [parent_ frame];
570 rect.size.height = size.height; 673 rect.size.height = size.height;
571 rect.size.width = static_cast<int>(kWindowWidthPercent * rect.size.width); 674
675 const int kScrollbarWidth = 16; // see BrowserWindowController.
676 if (expanded_width) {
677 rect.size.width = NSWidth(rect) - kScrollbarWidth;
678 } else {
679 rect.size.width = static_cast<int>(kWindowWidthPercent * NSWidth(rect));
680 }
681
572 return rect; 682 return rect;
573 } 683 }
OLDNEW
« no previous file with comments | « chrome/browser/cocoa/status_bubble_mac.h ('k') | chrome/browser/cocoa/status_bubble_mac_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698