blob: 7b592c6c8135e060c83836342f03bc5e5db78fae [file] [log] [blame]
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/cocoa/status_bubble_mac.h"
#include "app/gfx/text_elider.h"
#include "base/string_util.h"
#include "base/sys_string_conversions.h"
#import "chrome/browser/cocoa/bubble_view.h"
#include "googleurl/src/gurl.h"
#import "third_party/GTM/AppKit/GTMNSBezierPath+RoundRect.h"
#import "third_party/GTM/AppKit/GTMNSColor+Luminance.h"
#import "third_party/GTM/AppKit/GTMTheme.h"
namespace {
const int kWindowHeight = 18;
// The width of the bubble in relation to the width of the parent window.
const float kWindowWidthPercent = 1.0f/3.0f;
// How close the mouse can get to the infobubble before it starts sliding
// off-screen.
const int kMousePadding = 20;
const int kTextPadding = 3;
// How long each fade should last for.
const int kShowFadeDuration = 0.120f;
const int kHideFadeDuration = 0.200f;
}
// TODO(avi):
// - do display delay
StatusBubbleMac::StatusBubbleMac(NSWindow* parent, id delegate)
: parent_(parent),
delegate_(delegate),
window_(nil),
status_text_(nil),
url_text_(nil) {
}
StatusBubbleMac::~StatusBubbleMac() {
Hide();
}
void StatusBubbleMac::SetStatus(const std::wstring& status) {
Create();
NSString* status_ns = base::SysWideToNSString(status);
SetStatus(status_ns, false);
}
void StatusBubbleMac::SetURL(const GURL& url, const std::wstring& languages) {
Create();
NSRect frame = [window_ frame];
int text_width = static_cast<int>(frame.size.width -
kBubbleViewTextPositionX -
kTextPadding);
NSFont* font = [[window_ contentView] font];
gfx::Font font_chr =
gfx::Font::CreateFont(base::SysNSStringToWide([font fontName]),
[font pointSize]);
std::wstring status = gfx::ElideUrl(url, font_chr, text_width, languages);
NSString* status_ns = base::SysWideToNSString(status);
SetStatus(status_ns, true);
}
void StatusBubbleMac::SetStatus(NSString* status, bool is_url) {
NSString** main;
NSString** backup;
if (is_url) {
main = &url_text_;
backup = &status_text_;
} else {
main = &status_text_;
backup = &url_text_;
}
if ([status isEqualToString:*main])
return;
[*main release];
*main = [status retain];
if ([*main length] > 0) {
[[window_ contentView] setContent:*main];
} else if ([*backup length] > 0) {
[[window_ contentView] setContent:*backup];
} else {
Hide();
}
FadeIn();
}
void StatusBubbleMac::Hide() {
FadeOut();
if (window_) {
[parent_ removeChildWindow:window_];
[window_ release];
window_ = nil;
}
[status_text_ release];
status_text_ = nil;
[url_text_ release];
url_text_ = nil;
}
void StatusBubbleMac::MouseMoved() {
if (!window_)
return;
NSPoint cursor_location = [NSEvent mouseLocation];
--cursor_location.y; // docs say the y coord starts at 1 not 0; don't ask why
// Get the normal position of the frame.
NSRect window_frame = [window_ frame];
window_frame.origin = [parent_ frame].origin;
// Adjust the position to sit on top of download and extension shelves.
// |delegate_| can be nil during unit tests.
if ([delegate_ respondsToSelector:@selector(verticalOffsetForStatusBubble)])
window_frame.origin.y += [delegate_ verticalOffsetForStatusBubble];
// Get the cursor position relative to the popup.
cursor_location.x -= NSMaxX(window_frame);
cursor_location.y -= NSMaxY(window_frame);
// If the mouse is in a position where we think it would move the
// status bubble, figure out where and how the bubble should be moved.
if (cursor_location.y < kMousePadding &&
cursor_location.x < kMousePadding) {
int offset = kMousePadding - cursor_location.y;
// Make the movement non-linear.
offset = offset * offset / kMousePadding;
// When the mouse is entering from the right, we want the offset to be
// scaled by how horizontally far away the cursor is from the bubble.
if (cursor_location.x > 0) {
offset = offset * ((kMousePadding - cursor_location.x) / kMousePadding);
}
// Cap the offset and change the visual presentation of the bubble
// depending on where it ends up (so that rounded corners square off
// and mate to the edges of the tab content).
if (offset >= NSHeight(window_frame)) {
offset = NSHeight(window_frame);
[[window_ contentView] setCornerFlags:
kRoundedBottomLeftCorner | kRoundedBottomRightCorner];
} else if (offset > 0) {
[[window_ contentView] setCornerFlags:
kRoundedTopRightCorner | kRoundedBottomLeftCorner |
kRoundedBottomRightCorner];
} else {
[[window_ contentView] setCornerFlags:kRoundedTopRightCorner];
}
offset_ = offset;
window_frame.origin.y -= offset;
} else {
offset_ = 0;
[[window_ contentView] setCornerFlags:kRoundedTopRightCorner];
}
[window_ setFrame:window_frame display:YES];
}
void StatusBubbleMac::UpdateDownloadShelfVisibility(bool visible) {
}
void StatusBubbleMac::Create() {
if (window_)
return;
NSRect rect = [parent_ frame];
rect.size.height = kWindowHeight;
rect.size.width = static_cast<int>(kWindowWidthPercent * rect.size.width);
// TODO(avi):fix this for RTL
window_ = [[NSWindow alloc] initWithContentRect:rect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:YES];
[window_ setMovableByWindowBackground:NO];
[window_ setBackgroundColor:[NSColor clearColor]];
[window_ setLevel:NSNormalWindowLevel];
[window_ setOpaque:NO];
[window_ setHasShadow:NO];
// We do not need to worry about the bubble outliving |parent_| because our
// teardown sequence in BWC guarantees that |parent_| outlives the status
// bubble and that the StatusBubble is torn down completely prior to the
// window going away.
scoped_nsobject<BubbleView> view(
[[BubbleView alloc] initWithFrame:NSZeroRect themeProvider:parent_]);
[window_ setContentView:view];
[parent_ addChildWindow:window_ ordered:NSWindowAbove];
[window_ setAlphaValue:0.0f];
offset_ = 0;
[view setCornerFlags:kRoundedTopRightCorner];
MouseMoved();
}
void StatusBubbleMac::FadeIn() {
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:kShowFadeDuration];
[[window_ animator] setAlphaValue:1.0f];
[NSAnimationContext endGrouping];
}
void StatusBubbleMac::FadeOut() {
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:kHideFadeDuration];
[[window_ animator] setAlphaValue:0.0f];
[NSAnimationContext endGrouping];
}