| OLD | NEW |
| 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> |
| 8 |
| 7 #include "app/gfx/text_elider.h" | 9 #include "app/gfx/text_elider.h" |
| 10 #include "base/compiler_specific.h" |
| 11 #include "base/message_loop.h" |
| 8 #include "base/string_util.h" | 12 #include "base/string_util.h" |
| 9 #include "base/sys_string_conversions.h" | 13 #include "base/sys_string_conversions.h" |
| 10 #import "chrome/browser/cocoa/bubble_view.h" | 14 #import "chrome/browser/cocoa/bubble_view.h" |
| 11 #include "googleurl/src/gurl.h" | 15 #include "googleurl/src/gurl.h" |
| 12 #import "third_party/GTM/AppKit/GTMNSBezierPath+RoundRect.h" | 16 #import "third_party/GTM/AppKit/GTMNSBezierPath+RoundRect.h" |
| 13 #import "third_party/GTM/AppKit/GTMNSColor+Luminance.h" | 17 #import "third_party/GTM/AppKit/GTMNSColor+Luminance.h" |
| 14 #import "third_party/GTM/AppKit/GTMTheme.h" | 18 #import "third_party/GTM/AppKit/GTMTheme.h" |
| 15 | 19 |
| 16 namespace { | 20 namespace { |
| 17 | 21 |
| 18 const int kWindowHeight = 18; | 22 const int kWindowHeight = 18; |
| 23 |
| 19 // The width of the bubble in relation to the width of the parent window. | 24 // The width of the bubble in relation to the width of the parent window. |
| 20 const float kWindowWidthPercent = 1.0f/3.0f; | 25 const double kWindowWidthPercent = 1.0 / 3.0; |
| 21 | 26 |
| 22 // How close the mouse can get to the infobubble before it starts sliding | 27 // How close the mouse can get to the infobubble before it starts sliding |
| 23 // off-screen. | 28 // off-screen. |
| 24 const int kMousePadding = 20; | 29 const int kMousePadding = 20; |
| 25 | 30 |
| 26 const int kTextPadding = 3; | 31 const int kTextPadding = 3; |
| 27 | 32 |
| 28 // How long each fade should last for. | 33 // The animation key used for fade-in and fade-out transitions. |
| 29 const int kShowFadeDuration = 0.120f; | 34 const NSString* kFadeAnimationKey = @"alphaValue"; |
| 30 const int kHideFadeDuration = 0.200f; | |
| 31 | 35 |
| 36 // The status bubble's maximum opacity, when fully faded in. |
| 37 const CGFloat kBubbleOpacity = 1.0; |
| 38 |
| 39 // Delay before showing or hiding the bubble after a SetStatus or SetURL call. |
| 40 const int64 kShowDelayMilliseconds = 80; |
| 41 const int64 kHideDelayMilliseconds = 250; |
| 42 |
| 43 // How long each fade should last. |
| 44 const NSTimeInterval kShowFadeInDurationSeconds = 0.120; |
| 45 const NSTimeInterval kHideFadeOutDurationSeconds = 0.200; |
| 46 |
| 47 // The minimum representable time interval. This can be used as the value |
| 48 // passed to +[NSAnimationContext setDuration:] to stop an in-progress |
| 49 // animation as quickly as possible. |
| 50 const NSTimeInterval kMinimumTimeInterval = |
| 51 std::numeric_limits<NSTimeInterval>::min(); |
| 52 |
| 53 } // namespace |
| 54 |
| 55 @interface StatusBubbleAnimationDelegate : NSObject { |
| 56 @private |
| 57 StatusBubbleMac* statusBubble_; // weak; owns us indirectly |
| 32 } | 58 } |
| 33 | 59 |
| 34 // TODO(avi): | 60 - (id)initWithStatusBubble:(StatusBubbleMac*)statusBubble; |
| 35 // - do display delay | 61 |
| 62 // Invalidates this object so that no further calls will be made to |
| 63 // statusBubble_. This should be called when statusBubble_ is released, to |
| 64 // prevent attempts to call into the released object. |
| 65 - (void)invalidate; |
| 66 |
| 67 // CAAnimation delegate method |
| 68 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished; |
| 69 @end |
| 70 |
| 71 @implementation StatusBubbleAnimationDelegate |
| 72 |
| 73 - (id)initWithStatusBubble:(StatusBubbleMac*)statusBubble { |
| 74 if ((self = [super init])) { |
| 75 statusBubble_ = statusBubble; |
| 76 } |
| 77 |
| 78 return self; |
| 79 } |
| 80 |
| 81 - (void)invalidate { |
| 82 statusBubble_ = NULL; |
| 83 } |
| 84 |
| 85 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished { |
| 86 if (statusBubble_) |
| 87 statusBubble_->AnimationDidStop(animation, finished ? true : false); |
| 88 } |
| 89 |
| 90 @end |
| 36 | 91 |
| 37 StatusBubbleMac::StatusBubbleMac(NSWindow* parent, id delegate) | 92 StatusBubbleMac::StatusBubbleMac(NSWindow* parent, id delegate) |
| 38 : parent_(parent), | 93 : ALLOW_THIS_IN_INITIALIZER_LIST(timer_factory_(this)), |
| 94 parent_(parent), |
| 39 delegate_(delegate), | 95 delegate_(delegate), |
| 40 window_(nil), | 96 window_(nil), |
| 41 status_text_(nil), | 97 status_text_(nil), |
| 42 url_text_(nil) { | 98 url_text_(nil), |
| 99 state_(kBubbleHidden), |
| 100 immediate_(false) { |
| 43 } | 101 } |
| 44 | 102 |
| 45 StatusBubbleMac::~StatusBubbleMac() { | 103 StatusBubbleMac::~StatusBubbleMac() { |
| 46 Hide(); | 104 Hide(); |
| 105 |
| 106 if (window_) { |
| 107 [[[window_ animationForKey:kFadeAnimationKey] delegate] invalidate]; |
| 108 [parent_ removeChildWindow:window_]; |
| 109 [window_ release]; |
| 110 window_ = nil; |
| 111 } |
| 47 } | 112 } |
| 48 | 113 |
| 49 void StatusBubbleMac::SetStatus(const std::wstring& status) { | 114 void StatusBubbleMac::SetStatus(const std::wstring& status) { |
| 50 Create(); | 115 Create(); |
| 51 | 116 |
| 52 NSString* status_ns = base::SysWideToNSString(status); | 117 SetText(status, false); |
| 53 | |
| 54 SetStatus(status_ns, false); | |
| 55 } | 118 } |
| 56 | 119 |
| 57 void StatusBubbleMac::SetURL(const GURL& url, const std::wstring& languages) { | 120 void StatusBubbleMac::SetURL(const GURL& url, const std::wstring& languages) { |
| 58 Create(); | 121 Create(); |
| 59 | 122 |
| 60 NSRect frame = [window_ frame]; | 123 NSRect frame = [window_ frame]; |
| 61 int text_width = static_cast<int>(frame.size.width - | 124 int text_width = static_cast<int>(frame.size.width - |
| 62 kBubbleViewTextPositionX - | 125 kBubbleViewTextPositionX - |
| 63 kTextPadding); | 126 kTextPadding); |
| 64 NSFont* font = [[window_ contentView] font]; | 127 NSFont* font = [[window_ contentView] font]; |
| 65 gfx::Font font_chr = | 128 gfx::Font font_chr = |
| 66 gfx::Font::CreateFont(base::SysNSStringToWide([font fontName]), | 129 gfx::Font::CreateFont(base::SysNSStringToWide([font fontName]), |
| 67 [font pointSize]); | 130 [font pointSize]); |
| 68 | 131 |
| 69 std::wstring status = gfx::ElideUrl(url, font_chr, text_width, languages); | 132 std::wstring status = gfx::ElideUrl(url, font_chr, text_width, languages); |
| 70 NSString* status_ns = base::SysWideToNSString(status); | |
| 71 | 133 |
| 72 SetStatus(status_ns, true); | 134 SetText(status, true); |
| 73 } | 135 } |
| 74 | 136 |
| 75 void StatusBubbleMac::SetStatus(NSString* status, bool is_url) { | 137 void StatusBubbleMac::SetText(const std::wstring& text, bool is_url) { |
| 138 // The status bubble allows the status and URL strings to be set |
| 139 // independently. Whichever was set non-empty most recently will be the |
| 140 // value displayed. When both are empty, the status bubble hides. |
| 141 |
| 142 NSString* text_ns = base::SysWideToNSString(text); |
| 143 |
| 76 NSString** main; | 144 NSString** main; |
| 77 NSString** backup; | 145 NSString** backup; |
| 78 | 146 |
| 79 if (is_url) { | 147 if (is_url) { |
| 80 main = &url_text_; | 148 main = &url_text_; |
| 81 backup = &status_text_; | 149 backup = &status_text_; |
| 82 } else { | 150 } else { |
| 83 main = &status_text_; | 151 main = &status_text_; |
| 84 backup = &url_text_; | 152 backup = &url_text_; |
| 85 } | 153 } |
| 86 | 154 |
| 87 if ([status isEqualToString:*main]) | 155 // Don't return from this function early. It's important to make sure that |
| 88 return; | 156 // all calls to StartShowing and StartHiding are made, so that all delays |
| 157 // are observed properly. Specifically, if the state is currently |
| 158 // kBubbleShowingTimer, the timer will need to be restarted even if |
| 159 // [text_ns isEqualToString:*main] is true. |
| 89 | 160 |
| 90 [*main release]; | 161 [*main autorelease]; |
| 91 *main = [status retain]; | 162 *main = [text_ns retain]; |
| 92 if ([*main length] > 0) { | 163 |
| 164 bool show = true; |
| 165 if ([*main length] > 0) |
| 93 [[window_ contentView] setContent:*main]; | 166 [[window_ contentView] setContent:*main]; |
| 94 } else if ([*backup length] > 0) { | 167 else if ([*backup length] > 0) |
| 95 [[window_ contentView] setContent:*backup]; | 168 [[window_ contentView] setContent:*backup]; |
| 96 } else { | 169 else |
| 97 Hide(); | 170 show = false; |
| 98 } | |
| 99 | 171 |
| 100 FadeIn(); | 172 if (show) |
| 173 StartShowing(); |
| 174 else |
| 175 StartHiding(); |
| 101 } | 176 } |
| 102 | 177 |
| 103 void StatusBubbleMac::Hide() { | 178 void StatusBubbleMac::Hide() { |
| 104 FadeOut(); | 179 CancelTimer(); |
| 105 | 180 |
| 106 if (window_) { | 181 bool fade_out = false; |
| 107 [parent_ removeChildWindow:window_]; | 182 if (state_ == kBubbleHidingFadeOut || state_ == kBubbleShowingFadeIn) { |
| 108 [window_ release]; | 183 SetState(kBubbleHidingFadeOut); |
| 109 window_ = nil; | 184 |
| 185 if (!immediate_) { |
| 186 // An animation is in progress. Cancel it by starting a new animation. |
| 187 // Use kMinimumTimeInterval to set the opacity as rapidly as possible. |
| 188 fade_out = true; |
| 189 [NSAnimationContext beginGrouping]; |
| 190 [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval]; |
| 191 [[window_ animator] setAlphaValue:0.0]; |
| 192 [NSAnimationContext endGrouping]; |
| 193 } |
| 194 } |
| 195 |
| 196 if (!fade_out) { |
| 197 // No animation is in progress, so the opacity can be set directly. |
| 198 [window_ setAlphaValue:0.0]; |
| 199 SetState(kBubbleHidden); |
| 110 } | 200 } |
| 111 | 201 |
| 112 [status_text_ release]; | 202 [status_text_ release]; |
| 113 status_text_ = nil; | 203 status_text_ = nil; |
| 114 [url_text_ release]; | 204 [url_text_ release]; |
| 115 url_text_ = nil; | 205 url_text_ = nil; |
| 116 } | 206 } |
| 117 | 207 |
| 118 void StatusBubbleMac::MouseMoved() { | 208 void StatusBubbleMac::MouseMoved() { |
| 119 if (!window_) | 209 if (!window_) |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 158 [[window_ contentView] setCornerFlags: | 248 [[window_ contentView] setCornerFlags: |
| 159 kRoundedBottomLeftCorner | kRoundedBottomRightCorner]; | 249 kRoundedBottomLeftCorner | kRoundedBottomRightCorner]; |
| 160 } else if (offset > 0) { | 250 } else if (offset > 0) { |
| 161 [[window_ contentView] setCornerFlags: | 251 [[window_ contentView] setCornerFlags: |
| 162 kRoundedTopRightCorner | kRoundedBottomLeftCorner | | 252 kRoundedTopRightCorner | kRoundedBottomLeftCorner | |
| 163 kRoundedBottomRightCorner]; | 253 kRoundedBottomRightCorner]; |
| 164 } else { | 254 } else { |
| 165 [[window_ contentView] setCornerFlags:kRoundedTopRightCorner]; | 255 [[window_ contentView] setCornerFlags:kRoundedTopRightCorner]; |
| 166 } | 256 } |
| 167 | 257 |
| 168 offset_ = offset; | |
| 169 window_frame.origin.y -= offset; | 258 window_frame.origin.y -= offset; |
| 170 } else { | 259 } else { |
| 171 offset_ = 0; | |
| 172 [[window_ contentView] setCornerFlags:kRoundedTopRightCorner]; | 260 [[window_ contentView] setCornerFlags:kRoundedTopRightCorner]; |
| 173 } | 261 } |
| 174 | 262 |
| 175 [window_ setFrame:window_frame display:YES]; | 263 [window_ setFrame:window_frame display:YES]; |
| 176 } | 264 } |
| 177 | 265 |
| 178 void StatusBubbleMac::UpdateDownloadShelfVisibility(bool visible) { | 266 void StatusBubbleMac::UpdateDownloadShelfVisibility(bool visible) { |
| 179 } | 267 } |
| 180 | 268 |
| 181 void StatusBubbleMac::Create() { | 269 void StatusBubbleMac::Create() { |
| (...skipping 12 matching lines...) Expand all Loading... |
| 194 [window_ setHasShadow:NO]; | 282 [window_ setHasShadow:NO]; |
| 195 | 283 |
| 196 // We do not need to worry about the bubble outliving |parent_| because our | 284 // We do not need to worry about the bubble outliving |parent_| because our |
| 197 // teardown sequence in BWC guarantees that |parent_| outlives the status | 285 // teardown sequence in BWC guarantees that |parent_| outlives the status |
| 198 // bubble and that the StatusBubble is torn down completely prior to the | 286 // bubble and that the StatusBubble is torn down completely prior to the |
| 199 // window going away. | 287 // window going away. |
| 200 scoped_nsobject<BubbleView> view( | 288 scoped_nsobject<BubbleView> view( |
| 201 [[BubbleView alloc] initWithFrame:NSZeroRect themeProvider:parent_]); | 289 [[BubbleView alloc] initWithFrame:NSZeroRect themeProvider:parent_]); |
| 202 [window_ setContentView:view]; | 290 [window_ setContentView:view]; |
| 203 | 291 |
| 204 [parent_ addChildWindow:window_ ordered:NSWindowAbove]; | 292 [window_ setAlphaValue:0.0]; |
| 205 | 293 |
| 206 [window_ setAlphaValue:0.0f]; | 294 // Set a delegate for the fade-in and fade-out transitions to be notified |
| 295 // when fades are complete. The ownership model is for window_ to own |
| 296 // animation_dictionary, which owns animation, which owns |
| 297 // animation_delegate. |
| 298 CAAnimation* animation = [[window_ animationForKey:kFadeAnimationKey] copy]; |
| 299 [animation autorelease]; |
| 300 StatusBubbleAnimationDelegate* animation_delegate = |
| 301 [[StatusBubbleAnimationDelegate alloc] initWithStatusBubble:this]; |
| 302 [animation_delegate autorelease]; |
| 303 [animation setDelegate:animation_delegate]; |
| 304 NSMutableDictionary* animation_dictionary = |
| 305 [NSMutableDictionary dictionaryWithDictionary:[window_ animations]]; |
| 306 [animation_dictionary setObject:animation forKey:kFadeAnimationKey]; |
| 307 [window_ setAnimations:animation_dictionary]; |
| 207 | 308 |
| 208 offset_ = 0; | 309 Attach(); |
| 310 |
| 209 [view setCornerFlags:kRoundedTopRightCorner]; | 311 [view setCornerFlags:kRoundedTopRightCorner]; |
| 210 MouseMoved(); | 312 MouseMoved(); |
| 211 } | 313 } |
| 212 | 314 |
| 213 void StatusBubbleMac::FadeIn() { | 315 void StatusBubbleMac::Attach() { |
| 316 // If the parent window is offscreen when the child is added, the child will |
| 317 // never be displayed, even when the parent moves on-screen. This method |
| 318 // may be called several times during the process of creating or showing a |
| 319 // status bubble to attach the bubble to its parent window. |
| 320 if (![window_ parentWindow] && [parent_ isVisible]) |
| 321 [parent_ addChildWindow:window_ ordered:NSWindowAbove]; |
| 322 } |
| 323 |
| 324 void StatusBubbleMac::AnimationDidStop(CAAnimation* animation, bool finished) { |
| 325 DCHECK(state_ == kBubbleShowingFadeIn || state_ == kBubbleHidingFadeOut); |
| 326 |
| 327 if (finished) { |
| 328 // Because of the mechanism used to interrupt animations, this is never |
| 329 // actually called with finished set to false. If animations ever become |
| 330 // directly interruptible, the check will ensure that state_ remains |
| 331 // properly synchronized. |
| 332 if (state_ == kBubbleShowingFadeIn) { |
| 333 DCHECK_EQ([[window_ animator] alphaValue], kBubbleOpacity); |
| 334 state_ = kBubbleShown; |
| 335 } else { |
| 336 DCHECK_EQ([[window_ animator] alphaValue], 0.0); |
| 337 state_ = kBubbleHidden; |
| 338 } |
| 339 } |
| 340 } |
| 341 |
| 342 void StatusBubbleMac::SetState(StatusBubbleState state) { |
| 343 if (state == state_) |
| 344 return; |
| 345 |
| 346 if ([delegate_ respondsToSelector:@selector(statusBubbleWillEnterState:)]) |
| 347 [delegate_ statusBubbleWillEnterState:state]; |
| 348 |
| 349 state_ = state; |
| 350 } |
| 351 |
| 352 void StatusBubbleMac::Fade(bool show) { |
| 353 StatusBubbleState fade_state = kBubbleShowingFadeIn; |
| 354 StatusBubbleState target_state = kBubbleShown; |
| 355 NSTimeInterval full_duration = kShowFadeInDurationSeconds; |
| 356 CGFloat opacity = kBubbleOpacity; |
| 357 |
| 358 if (!show) { |
| 359 fade_state = kBubbleHidingFadeOut; |
| 360 target_state = kBubbleHidden; |
| 361 full_duration = kHideFadeOutDurationSeconds; |
| 362 opacity = 0.0; |
| 363 } |
| 364 |
| 365 DCHECK(state_ == fade_state || state_ == target_state); |
| 366 |
| 367 if (state_ == target_state) |
| 368 return; |
| 369 |
| 370 Attach(); |
| 371 |
| 372 if (immediate_) { |
| 373 [window_ setAlphaValue:opacity]; |
| 374 SetState(target_state); |
| 375 return; |
| 376 } |
| 377 |
| 378 // If an incomplete transition has left the opacity somewhere between 0 and |
| 379 // kBubbleOpacity, the fade rate is kept constant by shortening the duration. |
| 380 NSTimeInterval duration = |
| 381 full_duration * |
| 382 fabs(opacity - [[window_ animator] alphaValue]) / kBubbleOpacity; |
| 383 |
| 384 // 0.0 will not cancel an in-progress animation. |
| 385 if (duration == 0.0) |
| 386 duration = kMinimumTimeInterval; |
| 387 |
| 388 // This will cancel an in-progress transition and replace it with this fade. |
| 214 [NSAnimationContext beginGrouping]; | 389 [NSAnimationContext beginGrouping]; |
| 215 [[NSAnimationContext currentContext] setDuration:kShowFadeDuration]; | 390 [[NSAnimationContext currentContext] setDuration:duration]; |
| 216 [[window_ animator] setAlphaValue:1.0f]; | 391 [[window_ animator] setAlphaValue:opacity]; |
| 217 [NSAnimationContext endGrouping]; | 392 [NSAnimationContext endGrouping]; |
| 218 } | 393 } |
| 219 | 394 |
| 220 void StatusBubbleMac::FadeOut() { | 395 void StatusBubbleMac::StartTimer(int64 delay_ms) { |
| 221 [NSAnimationContext beginGrouping]; | 396 DCHECK(state_ == kBubbleShowingTimer || state_ == kBubbleHidingTimer); |
| 222 [[NSAnimationContext currentContext] setDuration:kHideFadeDuration]; | 397 |
| 223 [[window_ animator] setAlphaValue:0.0f]; | 398 if (immediate_) { |
| 224 [NSAnimationContext endGrouping]; | 399 TimerFired(); |
| 400 return; |
| 401 } |
| 402 |
| 403 // There can only be one running timer. |
| 404 CancelTimer(); |
| 405 |
| 406 MessageLoop::current()->PostDelayedTask( |
| 407 FROM_HERE, |
| 408 timer_factory_.NewRunnableMethod(&StatusBubbleMac::TimerFired), |
| 409 delay_ms); |
| 410 } |
| 411 |
| 412 void StatusBubbleMac::CancelTimer() { |
| 413 if (!timer_factory_.empty()) |
| 414 timer_factory_.RevokeAll(); |
| 415 } |
| 416 |
| 417 void StatusBubbleMac::TimerFired() { |
| 418 DCHECK(state_ == kBubbleShowingTimer || state_ == kBubbleHidingTimer); |
| 419 |
| 420 if (state_ == kBubbleShowingTimer) { |
| 421 SetState(kBubbleShowingFadeIn); |
| 422 Fade(true); |
| 423 } else { |
| 424 SetState(kBubbleHidingFadeOut); |
| 425 Fade(false); |
| 426 } |
| 427 } |
| 428 |
| 429 void StatusBubbleMac::StartShowing() { |
| 430 Attach(); |
| 431 |
| 432 if (state_ == kBubbleHidden) { |
| 433 // Arrange to begin fading in after a delay. |
| 434 SetState(kBubbleShowingTimer); |
| 435 StartTimer(kShowDelayMilliseconds); |
| 436 } else if (state_ == kBubbleHidingFadeOut) { |
| 437 // Cancel the fade-out in progress and replace it with a fade in. |
| 438 SetState(kBubbleShowingFadeIn); |
| 439 Fade(true); |
| 440 } else if (state_ == kBubbleHidingTimer) { |
| 441 // The bubble was already shown but was waiting to begin fading out. It's |
| 442 // given a stay of execution. |
| 443 SetState(kBubbleShown); |
| 444 CancelTimer(); |
| 445 } else if (state_ == kBubbleShowingTimer) { |
| 446 // The timer was already running but nothing was showing yet. Reaching |
| 447 // this point means that there is a new request to show something. Start |
| 448 // over again by resetting the timer, effectively invalidating the earlier |
| 449 // request. |
| 450 StartTimer(kShowDelayMilliseconds); |
| 451 } |
| 452 |
| 453 // If the state is kBubbleShown or kBubbleShowingFadeIn, leave everything |
| 454 // alone. |
| 455 } |
| 456 |
| 457 void StatusBubbleMac::StartHiding() { |
| 458 if (state_ == kBubbleShown) { |
| 459 // Arrange to begin fading out after a delay. |
| 460 SetState(kBubbleHidingTimer); |
| 461 StartTimer(kHideDelayMilliseconds); |
| 462 } else if (state_ == kBubbleShowingFadeIn) { |
| 463 // Cancel the fade-in in progress and replace it with a fade out. |
| 464 SetState(kBubbleHidingFadeOut); |
| 465 Fade(false); |
| 466 } else if (state_ == kBubbleShowingTimer) { |
| 467 // The bubble was already hidden but was waiting to begin fading in. Too |
| 468 // bad, it won't get the opportunity now. |
| 469 SetState(kBubbleHidden); |
| 470 CancelTimer(); |
| 471 } |
| 472 |
| 473 // If the state is kBubbleHidden, kBubbleHidingFadeOut, or |
| 474 // kBubbleHidingTimer, leave everything alone. The timer is not reset as |
| 475 // with kBubbleShowingTimer in StartShowing() because a subsequent request |
| 476 // to hide something while one is already in flight does not invalidate the |
| 477 // earlier request. |
| 225 } | 478 } |
| 226 | 479 |
| 227 void StatusBubbleMac::UpdateSizeAndPosition() { | 480 void StatusBubbleMac::UpdateSizeAndPosition() { |
| 228 if (!window_) | 481 if (!window_) |
| 229 return; | 482 return; |
| 230 | 483 |
| 231 [window_ setFrame:CalculateWindowFrame() display:YES]; | 484 [window_ setFrame:CalculateWindowFrame() display:YES]; |
| 232 } | 485 } |
| 233 | 486 |
| 234 NSRect StatusBubbleMac::CalculateWindowFrame() { | 487 NSRect StatusBubbleMac::CalculateWindowFrame() { |
| 235 DCHECK(parent_); | 488 DCHECK(parent_); |
| 236 | 489 |
| 237 NSRect rect = [parent_ frame]; | 490 NSRect rect = [parent_ frame]; |
| 238 rect.size.height = kWindowHeight; | 491 rect.size.height = kWindowHeight; |
| 239 rect.size.width = static_cast<int>(kWindowWidthPercent * rect.size.width); | 492 rect.size.width = static_cast<int>(kWindowWidthPercent * rect.size.width); |
| 240 return rect; | 493 return rect; |
| 241 } | 494 } |
| OLD | NEW |