// 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(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(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 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]; }