blob: a286c4d3428c15bb5af3b92493267e1a5defbff2 [file] [log] [blame]
[email protected]def8e9a2009-03-17 19:15:221// 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
3// found in the LICENSE file.
4
5#include "chrome/browser/cocoa/status_bubble_mac.h"
6
[email protected]7e9d83722009-10-12 22:27:567#include <limits>
8
[email protected]614519812010-03-19 08:20:569#include "app/text_elider.h"
[email protected]7e9d83722009-10-12 22:27:5610#include "base/compiler_specific.h"
11#include "base/message_loop.h"
[email protected]def8e9a2009-03-17 19:15:2212#include "base/string_util.h"
13#include "base/sys_string_conversions.h"
[email protected]1a3e0962009-09-08 21:09:2914#import "chrome/browser/cocoa/bubble_view.h"
[email protected]e0fc2f12010-03-14 23:30:5915#include "gfx/point.h"
[email protected]def8e9a2009-03-17 19:15:2216#include "googleurl/src/gurl.h"
[email protected]44fc30f2009-11-10 19:59:2617#import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h"
[email protected]def8e9a2009-03-17 19:15:2218#import "third_party/GTM/AppKit/GTMNSBezierPath+RoundRect.h"
[email protected]203aa8f2009-08-31 15:17:1119#import "third_party/GTM/AppKit/GTMNSColor+Luminance.h"
[email protected]def8e9a2009-03-17 19:15:2220
21namespace {
22
23const int kWindowHeight = 18;
[email protected]7e9d83722009-10-12 22:27:5624
[email protected]def8e9a2009-03-17 19:15:2225// The width of the bubble in relation to the width of the parent window.
[email protected]7e9d83722009-10-12 22:27:5626const double kWindowWidthPercent = 1.0 / 3.0;
[email protected]def8e9a2009-03-17 19:15:2227
[email protected]2789a422009-03-19 17:40:1828// How close the mouse can get to the infobubble before it starts sliding
29// off-screen.
30const int kMousePadding = 20;
31
[email protected]def8e9a2009-03-17 19:15:2232const int kTextPadding = 3;
[email protected]def8e9a2009-03-17 19:15:2233
[email protected]7e9d83722009-10-12 22:27:5634// The animation key used for fade-in and fade-out transitions.
[email protected]a866cc22010-06-15 17:34:5935NSString* const kFadeAnimationKey = @"alphaValue";
[email protected]def8e9a2009-03-17 19:15:2236
[email protected]7e9d83722009-10-12 22:27:5637// The status bubble's maximum opacity, when fully faded in.
38const CGFloat kBubbleOpacity = 1.0;
39
40// Delay before showing or hiding the bubble after a SetStatus or SetURL call.
41const int64 kShowDelayMilliseconds = 80;
42const int64 kHideDelayMilliseconds = 250;
43
44// How long each fade should last.
45const NSTimeInterval kShowFadeInDurationSeconds = 0.120;
46const NSTimeInterval kHideFadeOutDurationSeconds = 0.200;
47
48// The minimum representable time interval. This can be used as the value
49// passed to +[NSAnimationContext setDuration:] to stop an in-progress
50// animation as quickly as possible.
51const NSTimeInterval kMinimumTimeInterval =
52 std::numeric_limits<NSTimeInterval>::min();
53
54} // namespace
55
56@interface StatusBubbleAnimationDelegate : NSObject {
57 @private
58 StatusBubbleMac* statusBubble_; // weak; owns us indirectly
[email protected]def8e9a2009-03-17 19:15:2259}
60
[email protected]7e9d83722009-10-12 22:27:5661- (id)initWithStatusBubble:(StatusBubbleMac*)statusBubble;
62
63// Invalidates this object so that no further calls will be made to
64// statusBubble_. This should be called when statusBubble_ is released, to
65// prevent attempts to call into the released object.
66- (void)invalidate;
67
68// CAAnimation delegate method
69- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished;
70@end
71
72@implementation StatusBubbleAnimationDelegate
73
74- (id)initWithStatusBubble:(StatusBubbleMac*)statusBubble {
75 if ((self = [super init])) {
76 statusBubble_ = statusBubble;
77 }
78
79 return self;
80}
81
82- (void)invalidate {
83 statusBubble_ = NULL;
84}
85
86- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished {
87 if (statusBubble_)
88 statusBubble_->AnimationDidStop(animation, finished ? true : false);
89}
90
91@end
[email protected]def8e9a2009-03-17 19:15:2292
[email protected]9b032bf2009-07-21 17:34:2393StatusBubbleMac::StatusBubbleMac(NSWindow* parent, id delegate)
[email protected]7e9d83722009-10-12 22:27:5694 : ALLOW_THIS_IN_INITIALIZER_LIST(timer_factory_(this)),
95 parent_(parent),
[email protected]9b032bf2009-07-21 17:34:2396 delegate_(delegate),
[email protected]def8e9a2009-03-17 19:15:2297 window_(nil),
98 status_text_(nil),
[email protected]7e9d83722009-10-12 22:27:5699 url_text_(nil),
100 state_(kBubbleHidden),
101 immediate_(false) {
[email protected]def8e9a2009-03-17 19:15:22102}
103
104StatusBubbleMac::~StatusBubbleMac() {
105 Hide();
[email protected]7e9d83722009-10-12 22:27:56106
107 if (window_) {
108 [[[window_ animationForKey:kFadeAnimationKey] delegate] invalidate];
[email protected]6ee7d1162009-11-30 15:38:32109 Detach();
[email protected]7e9d83722009-10-12 22:27:56110 [window_ release];
111 window_ = nil;
112 }
[email protected]def8e9a2009-03-17 19:15:22113}
114
115void StatusBubbleMac::SetStatus(const std::wstring& status) {
116 Create();
117
[email protected]7e9d83722009-10-12 22:27:56118 SetText(status, false);
[email protected]def8e9a2009-03-17 19:15:22119}
120
121void StatusBubbleMac::SetURL(const GURL& url, const std::wstring& languages) {
122 Create();
123
124 NSRect frame = [window_ frame];
125 int text_width = static_cast<int>(frame.size.width -
[email protected]1a3e0962009-09-08 21:09:29126 kBubbleViewTextPositionX -
[email protected]def8e9a2009-03-17 19:15:22127 kTextPadding);
128 NSFont* font = [[window_ contentView] font];
[email protected]7322c4402009-05-15 02:16:10129 gfx::Font font_chr =
130 gfx::Font::CreateFont(base::SysNSStringToWide([font fontName]),
131 [font pointSize]);
[email protected]def8e9a2009-03-17 19:15:22132
133 std::wstring status = gfx::ElideUrl(url, font_chr, text_width, languages);
[email protected]def8e9a2009-03-17 19:15:22134
[email protected]7e9d83722009-10-12 22:27:56135 SetText(status, true);
[email protected]def8e9a2009-03-17 19:15:22136}
137
[email protected]7e9d83722009-10-12 22:27:56138void StatusBubbleMac::SetText(const std::wstring& text, bool is_url) {
139 // The status bubble allows the status and URL strings to be set
140 // independently. Whichever was set non-empty most recently will be the
141 // value displayed. When both are empty, the status bubble hides.
142
143 NSString* text_ns = base::SysWideToNSString(text);
144
[email protected]def8e9a2009-03-17 19:15:22145 NSString** main;
146 NSString** backup;
147
148 if (is_url) {
149 main = &url_text_;
150 backup = &status_text_;
151 } else {
152 main = &status_text_;
153 backup = &url_text_;
154 }
155
[email protected]7e9d83722009-10-12 22:27:56156 // Don't return from this function early. It's important to make sure that
157 // all calls to StartShowing and StartHiding are made, so that all delays
158 // are observed properly. Specifically, if the state is currently
159 // kBubbleShowingTimer, the timer will need to be restarted even if
160 // [text_ns isEqualToString:*main] is true.
[email protected]def8e9a2009-03-17 19:15:22161
[email protected]7e9d83722009-10-12 22:27:56162 [*main autorelease];
163 *main = [text_ns retain];
164
165 bool show = true;
166 if ([*main length] > 0)
[email protected]def8e9a2009-03-17 19:15:22167 [[window_ contentView] setContent:*main];
[email protected]7e9d83722009-10-12 22:27:56168 else if ([*backup length] > 0)
[email protected]def8e9a2009-03-17 19:15:22169 [[window_ contentView] setContent:*backup];
[email protected]7e9d83722009-10-12 22:27:56170 else
171 show = false;
[email protected]def8e9a2009-03-17 19:15:22172
[email protected]7e9d83722009-10-12 22:27:56173 if (show)
174 StartShowing();
175 else
176 StartHiding();
[email protected]def8e9a2009-03-17 19:15:22177}
178
179void StatusBubbleMac::Hide() {
[email protected]7e9d83722009-10-12 22:27:56180 CancelTimer();
[email protected]def8e9a2009-03-17 19:15:22181
[email protected]7e9d83722009-10-12 22:27:56182 bool fade_out = false;
183 if (state_ == kBubbleHidingFadeOut || state_ == kBubbleShowingFadeIn) {
184 SetState(kBubbleHidingFadeOut);
185
186 if (!immediate_) {
187 // An animation is in progress. Cancel it by starting a new animation.
188 // Use kMinimumTimeInterval to set the opacity as rapidly as possible.
189 fade_out = true;
190 [NSAnimationContext beginGrouping];
191 [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval];
192 [[window_ animator] setAlphaValue:0.0];
193 [NSAnimationContext endGrouping];
194 }
195 }
196
197 if (!fade_out) {
198 // No animation is in progress, so the opacity can be set directly.
199 [window_ setAlphaValue:0.0];
200 SetState(kBubbleHidden);
[email protected]def8e9a2009-03-17 19:15:22201 }
202
203 [status_text_ release];
204 status_text_ = nil;
205 [url_text_ release];
206 url_text_ = nil;
207}
208
[email protected]b8595e92009-11-14 01:18:00209void StatusBubbleMac::MouseMoved(
210 const gfx::Point& location, bool left_content) {
211 if (left_content)
212 return;
213
[email protected]2789a422009-03-19 17:40:18214 if (!window_)
215 return;
216
[email protected]b8595e92009-11-14 01:18:00217 // TODO(thakis): Use 'location' here instead of NSEvent.
[email protected]2789a422009-03-19 17:40:18218 NSPoint cursor_location = [NSEvent mouseLocation];
219 --cursor_location.y; // docs say the y coord starts at 1 not 0; don't ask why
220
221 // Get the normal position of the frame.
222 NSRect window_frame = [window_ frame];
223 window_frame.origin = [parent_ frame].origin;
224
[email protected]09a62d72009-11-09 16:29:27225 bool isShelfVisible = false;
226
[email protected]10eaf87f2009-09-16 00:59:04227 // Adjust the position to sit on top of download and extension shelves.
[email protected]d225f59c2009-09-15 19:25:19228 // |delegate_| can be nil during unit tests.
[email protected]09a62d72009-11-09 16:29:27229 if ([delegate_ respondsToSelector:@selector(verticalOffsetForStatusBubble)]) {
[email protected]10eaf87f2009-09-16 00:59:04230 window_frame.origin.y += [delegate_ verticalOffsetForStatusBubble];
[email protected]09a62d72009-11-09 16:29:27231 isShelfVisible = [delegate_ verticalOffsetForStatusBubble] > 0;
232 }
[email protected]d225f59c2009-09-15 19:25:19233
[email protected]2789a422009-03-19 17:40:18234 // Get the cursor position relative to the popup.
235 cursor_location.x -= NSMaxX(window_frame);
236 cursor_location.y -= NSMaxY(window_frame);
237
[email protected]269ab282009-11-09 16:08:49238
[email protected]2789a422009-03-19 17:40:18239 // If the mouse is in a position where we think it would move the
240 // status bubble, figure out where and how the bubble should be moved.
241 if (cursor_location.y < kMousePadding &&
242 cursor_location.x < kMousePadding) {
243 int offset = kMousePadding - cursor_location.y;
244
245 // Make the movement non-linear.
246 offset = offset * offset / kMousePadding;
247
248 // When the mouse is entering from the right, we want the offset to be
249 // scaled by how horizontally far away the cursor is from the bubble.
250 if (cursor_location.x > 0) {
251 offset = offset * ((kMousePadding - cursor_location.x) / kMousePadding);
252 }
253
[email protected]269ab282009-11-09 16:08:49254 bool isOnScreen = true;
255 NSScreen* screen = [window_ screen];
256 if (screen &&
257 NSMinY([screen visibleFrame]) > NSMinY(window_frame) - offset) {
258 isOnScreen = false;
[email protected]2789a422009-03-19 17:40:18259 }
260
[email protected]269ab282009-11-09 16:08:49261 if (isOnScreen && !isShelfVisible) {
262 // Cap the offset and change the visual presentation of the bubble
263 // depending on where it ends up (so that rounded corners square off
264 // and mate to the edges of the tab content).
265 if (offset >= NSHeight(window_frame)) {
266 offset = NSHeight(window_frame);
267 [[window_ contentView] setCornerFlags:
268 kRoundedBottomLeftCorner | kRoundedBottomRightCorner];
269 } else if (offset > 0) {
270 [[window_ contentView] setCornerFlags:
271 kRoundedTopRightCorner | kRoundedBottomLeftCorner |
272 kRoundedBottomRightCorner];
273 } else {
274 [[window_ contentView] setCornerFlags:kRoundedTopRightCorner];
275 }
276 window_frame.origin.y -= offset;
277 } else {
278 // The bubble will obscure the download shelf. Move the bubble to the
279 // right and reset Y offset_ to zero.
280 [[window_ contentView] setCornerFlags:kRoundedTopLeftCorner];
281
282 // Subtract border width + bubble width.
283 window_frame.origin.x += NSWidth([parent_ frame]) - NSWidth(window_frame);
284 }
[email protected]2789a422009-03-19 17:40:18285 } else {
[email protected]1a3e0962009-09-08 21:09:29286 [[window_ contentView] setCornerFlags:kRoundedTopRightCorner];
[email protected]2789a422009-03-19 17:40:18287 }
[email protected]9b032bf2009-07-21 17:34:23288
[email protected]9b032bf2009-07-21 17:34:23289 [window_ setFrame:window_frame display:YES];
[email protected]def8e9a2009-03-17 19:15:22290}
291
[email protected]3a6a3b62009-05-27 21:36:20292void StatusBubbleMac::UpdateDownloadShelfVisibility(bool visible) {
[email protected]3a6a3b62009-05-27 21:36:20293}
294
[email protected]def8e9a2009-03-17 19:15:22295void StatusBubbleMac::Create() {
296 if (window_)
297 return;
298
[email protected]def8e9a2009-03-17 19:15:22299 // TODO(avi):fix this for RTL
[email protected]35b69972010-03-16 19:31:32300 NSRect window_rect = CalculateWindowFrame();
301 // initWithContentRect has origin in screen coords and size in scaled window
302 // coordinates.
303 window_rect.size =
304 [[parent_ contentView] convertSize:window_rect.size fromView:nil];
305 window_ = [[NSWindow alloc] initWithContentRect:window_rect
[email protected]def8e9a2009-03-17 19:15:22306 styleMask:NSBorderlessWindowMask
307 backing:NSBackingStoreBuffered
308 defer:YES];
309 [window_ setMovableByWindowBackground:NO];
310 [window_ setBackgroundColor:[NSColor clearColor]];
311 [window_ setLevel:NSNormalWindowLevel];
312 [window_ setOpaque:NO];
313 [window_ setHasShadow:NO];
314
[email protected]1a3e0962009-09-08 21:09:29315 // We do not need to worry about the bubble outliving |parent_| because our
316 // teardown sequence in BWC guarantees that |parent_| outlives the status
317 // bubble and that the StatusBubble is torn down completely prior to the
318 // window going away.
319 scoped_nsobject<BubbleView> view(
320 [[BubbleView alloc] initWithFrame:NSZeroRect themeProvider:parent_]);
[email protected]def8e9a2009-03-17 19:15:22321 [window_ setContentView:view];
322
[email protected]7e9d83722009-10-12 22:27:56323 [window_ setAlphaValue:0.0];
[email protected]def8e9a2009-03-17 19:15:22324
[email protected]7e9d83722009-10-12 22:27:56325 // Set a delegate for the fade-in and fade-out transitions to be notified
326 // when fades are complete. The ownership model is for window_ to own
327 // animation_dictionary, which owns animation, which owns
328 // animation_delegate.
329 CAAnimation* animation = [[window_ animationForKey:kFadeAnimationKey] copy];
330 [animation autorelease];
331 StatusBubbleAnimationDelegate* animation_delegate =
332 [[StatusBubbleAnimationDelegate alloc] initWithStatusBubble:this];
333 [animation_delegate autorelease];
334 [animation setDelegate:animation_delegate];
335 NSMutableDictionary* animation_dictionary =
336 [NSMutableDictionary dictionaryWithDictionary:[window_ animations]];
337 [animation_dictionary setObject:animation forKey:kFadeAnimationKey];
338 [window_ setAnimations:animation_dictionary];
[email protected]2789a422009-03-19 17:40:18339
[email protected]6ee7d1162009-11-30 15:38:32340 // Don't |Attach()| since we don't know the appropriate state; let the
341 // |SetState()| call do that.
[email protected]7e9d83722009-10-12 22:27:56342
[email protected]1a3e0962009-09-08 21:09:29343 [view setCornerFlags:kRoundedTopRightCorner];
[email protected]b8595e92009-11-14 01:18:00344 MouseMoved(gfx::Point(), false);
[email protected]def8e9a2009-03-17 19:15:22345}
346
[email protected]7e9d83722009-10-12 22:27:56347void StatusBubbleMac::Attach() {
[email protected]6ee7d1162009-11-30 15:38:32348 // This method may be called several times during the process of creating or
349 // showing a status bubble to attach the bubble to its parent window.
350 if (!is_attached())
[email protected]7e9d83722009-10-12 22:27:56351 [parent_ addChildWindow:window_ ordered:NSWindowAbove];
352}
353
[email protected]6ee7d1162009-11-30 15:38:32354void StatusBubbleMac::Detach() {
355 // This method may be called several times in the process of hiding or
356 // destroying a status bubble.
[email protected]97ef052b2009-12-07 06:18:53357 if (is_attached()) {
358 [parent_ removeChildWindow:window_]; // See crbug.com/28107 ...
359 [window_ orderOut:nil]; // ... and crbug.com/29054.
360 }
[email protected]6ee7d1162009-11-30 15:38:32361}
362
[email protected]7e9d83722009-10-12 22:27:56363void StatusBubbleMac::AnimationDidStop(CAAnimation* animation, bool finished) {
[email protected]52163ca2009-11-11 20:08:18364 DCHECK([NSThread isMainThread]);
[email protected]7e9d83722009-10-12 22:27:56365 DCHECK(state_ == kBubbleShowingFadeIn || state_ == kBubbleHidingFadeOut);
[email protected]6ee7d1162009-11-30 15:38:32366 DCHECK(is_attached());
[email protected]7e9d83722009-10-12 22:27:56367
368 if (finished) {
369 // Because of the mechanism used to interrupt animations, this is never
370 // actually called with finished set to false. If animations ever become
371 // directly interruptible, the check will ensure that state_ remains
372 // properly synchronized.
373 if (state_ == kBubbleShowingFadeIn) {
374 DCHECK_EQ([[window_ animator] alphaValue], kBubbleOpacity);
[email protected]52163ca2009-11-11 20:08:18375 SetState(kBubbleShown);
[email protected]7e9d83722009-10-12 22:27:56376 } else {
377 DCHECK_EQ([[window_ animator] alphaValue], 0.0);
[email protected]52163ca2009-11-11 20:08:18378 SetState(kBubbleHidden);
[email protected]7e9d83722009-10-12 22:27:56379 }
380 }
381}
382
383void StatusBubbleMac::SetState(StatusBubbleState state) {
[email protected]6ee7d1162009-11-30 15:38:32384 // We must be hidden or attached, but not both.
385 DCHECK((state_ == kBubbleHidden) ^ is_attached());
386
[email protected]7e9d83722009-10-12 22:27:56387 if (state == state_)
388 return;
389
[email protected]6ee7d1162009-11-30 15:38:32390 if (state == kBubbleHidden)
391 Detach();
392 else
393 Attach();
394
[email protected]7e9d83722009-10-12 22:27:56395 if ([delegate_ respondsToSelector:@selector(statusBubbleWillEnterState:)])
396 [delegate_ statusBubbleWillEnterState:state];
397
398 state_ = state;
399}
400
401void StatusBubbleMac::Fade(bool show) {
[email protected]52163ca2009-11-11 20:08:18402 DCHECK([NSThread isMainThread]);
403
[email protected]7e9d83722009-10-12 22:27:56404 StatusBubbleState fade_state = kBubbleShowingFadeIn;
405 StatusBubbleState target_state = kBubbleShown;
406 NSTimeInterval full_duration = kShowFadeInDurationSeconds;
407 CGFloat opacity = kBubbleOpacity;
408
409 if (!show) {
410 fade_state = kBubbleHidingFadeOut;
411 target_state = kBubbleHidden;
412 full_duration = kHideFadeOutDurationSeconds;
413 opacity = 0.0;
414 }
415
416 DCHECK(state_ == fade_state || state_ == target_state);
417
418 if (state_ == target_state)
419 return;
420
[email protected]7e9d83722009-10-12 22:27:56421 if (immediate_) {
422 [window_ setAlphaValue:opacity];
423 SetState(target_state);
424 return;
425 }
426
427 // If an incomplete transition has left the opacity somewhere between 0 and
428 // kBubbleOpacity, the fade rate is kept constant by shortening the duration.
429 NSTimeInterval duration =
430 full_duration *
431 fabs(opacity - [[window_ animator] alphaValue]) / kBubbleOpacity;
432
433 // 0.0 will not cancel an in-progress animation.
434 if (duration == 0.0)
435 duration = kMinimumTimeInterval;
436
437 // This will cancel an in-progress transition and replace it with this fade.
[email protected]def8e9a2009-03-17 19:15:22438 [NSAnimationContext beginGrouping];
[email protected]231744282010-04-14 19:35:37439 // Don't use the GTM additon for the "Steve" slowdown because this can happen
440 // async from user actions and the effects could be a surprise.
441 [[NSAnimationContext currentContext] setDuration:duration];
[email protected]7e9d83722009-10-12 22:27:56442 [[window_ animator] setAlphaValue:opacity];
[email protected]def8e9a2009-03-17 19:15:22443 [NSAnimationContext endGrouping];
444}
445
[email protected]7e9d83722009-10-12 22:27:56446void StatusBubbleMac::StartTimer(int64 delay_ms) {
[email protected]52163ca2009-11-11 20:08:18447 DCHECK([NSThread isMainThread]);
[email protected]7e9d83722009-10-12 22:27:56448 DCHECK(state_ == kBubbleShowingTimer || state_ == kBubbleHidingTimer);
449
450 if (immediate_) {
451 TimerFired();
452 return;
453 }
454
455 // There can only be one running timer.
456 CancelTimer();
457
458 MessageLoop::current()->PostDelayedTask(
459 FROM_HERE,
460 timer_factory_.NewRunnableMethod(&StatusBubbleMac::TimerFired),
461 delay_ms);
462}
463
464void StatusBubbleMac::CancelTimer() {
[email protected]52163ca2009-11-11 20:08:18465 DCHECK([NSThread isMainThread]);
466
[email protected]7e9d83722009-10-12 22:27:56467 if (!timer_factory_.empty())
468 timer_factory_.RevokeAll();
469}
470
471void StatusBubbleMac::TimerFired() {
472 DCHECK(state_ == kBubbleShowingTimer || state_ == kBubbleHidingTimer);
[email protected]52163ca2009-11-11 20:08:18473 DCHECK([NSThread isMainThread]);
[email protected]7e9d83722009-10-12 22:27:56474
475 if (state_ == kBubbleShowingTimer) {
476 SetState(kBubbleShowingFadeIn);
477 Fade(true);
478 } else {
479 SetState(kBubbleHidingFadeOut);
480 Fade(false);
481 }
482}
483
484void StatusBubbleMac::StartShowing() {
[email protected]6ee7d1162009-11-30 15:38:32485 // Note that |SetState()| will |Attach()| or |Detach()| as required.
[email protected]7e9d83722009-10-12 22:27:56486
487 if (state_ == kBubbleHidden) {
488 // Arrange to begin fading in after a delay.
489 SetState(kBubbleShowingTimer);
490 StartTimer(kShowDelayMilliseconds);
491 } else if (state_ == kBubbleHidingFadeOut) {
492 // Cancel the fade-out in progress and replace it with a fade in.
493 SetState(kBubbleShowingFadeIn);
494 Fade(true);
495 } else if (state_ == kBubbleHidingTimer) {
496 // The bubble was already shown but was waiting to begin fading out. It's
497 // given a stay of execution.
498 SetState(kBubbleShown);
499 CancelTimer();
500 } else if (state_ == kBubbleShowingTimer) {
501 // The timer was already running but nothing was showing yet. Reaching
502 // this point means that there is a new request to show something. Start
503 // over again by resetting the timer, effectively invalidating the earlier
504 // request.
505 StartTimer(kShowDelayMilliseconds);
506 }
507
508 // If the state is kBubbleShown or kBubbleShowingFadeIn, leave everything
509 // alone.
510}
511
512void StatusBubbleMac::StartHiding() {
513 if (state_ == kBubbleShown) {
514 // Arrange to begin fading out after a delay.
515 SetState(kBubbleHidingTimer);
516 StartTimer(kHideDelayMilliseconds);
517 } else if (state_ == kBubbleShowingFadeIn) {
518 // Cancel the fade-in in progress and replace it with a fade out.
519 SetState(kBubbleHidingFadeOut);
520 Fade(false);
521 } else if (state_ == kBubbleShowingTimer) {
522 // The bubble was already hidden but was waiting to begin fading in. Too
523 // bad, it won't get the opportunity now.
524 SetState(kBubbleHidden);
525 CancelTimer();
526 }
527
528 // If the state is kBubbleHidden, kBubbleHidingFadeOut, or
529 // kBubbleHidingTimer, leave everything alone. The timer is not reset as
530 // with kBubbleShowingTimer in StartShowing() because a subsequent request
531 // to hide something while one is already in flight does not invalidate the
532 // earlier request.
[email protected]def8e9a2009-03-17 19:15:22533}
534
[email protected]34f8fee22009-10-08 21:02:36535void StatusBubbleMac::UpdateSizeAndPosition() {
536 if (!window_)
537 return;
538
539 [window_ setFrame:CalculateWindowFrame() display:YES];
540}
541
[email protected]a96146e12010-02-17 17:13:06542void StatusBubbleMac::SwitchParentWindow(NSWindow* parent) {
543 DCHECK(parent);
544
[email protected]a716ef332010-02-19 19:55:30545 // If not attached, just update our member variable and position.
[email protected]a96146e12010-02-17 17:13:06546 if (!is_attached()) {
547 parent_ = parent;
[email protected]1821bdcf2010-03-05 20:40:50548 [[window_ contentView] setThemeProvider:parent];
[email protected]a716ef332010-02-19 19:55:30549 UpdateSizeAndPosition();
[email protected]a96146e12010-02-17 17:13:06550 return;
551 }
552
553 Detach();
554 parent_ = parent;
[email protected]1821bdcf2010-03-05 20:40:50555 [[window_ contentView] setThemeProvider:parent];
[email protected]a96146e12010-02-17 17:13:06556 Attach();
557 UpdateSizeAndPosition();
558}
559
[email protected]34f8fee22009-10-08 21:02:36560NSRect StatusBubbleMac::CalculateWindowFrame() {
561 DCHECK(parent_);
562
[email protected]35b69972010-03-16 19:31:32563 NSSize size = NSMakeSize(0, kWindowHeight);
564 size = [[parent_ contentView] convertSize:size toView:nil];
565
[email protected]34f8fee22009-10-08 21:02:36566 NSRect rect = [parent_ frame];
[email protected]35b69972010-03-16 19:31:32567 rect.size.height = size.height;
[email protected]34f8fee22009-10-08 21:02:36568 rect.size.width = static_cast<int>(kWindowWidthPercent * rect.size.width);
569 return rect;
570}