1// Copyright (c) 2011 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/ui/cocoa/notifications/balloon_controller.h" 6 7#include "app/mac/nsimage_cache.h" 8#import "base/mac/cocoa_protocols.h" 9#include "base/mac/mac_util.h" 10#import "base/memory/scoped_nsobject.h" 11#include "base/utf_string_conversions.h" 12#include "chrome/browser/notifications/balloon.h" 13#include "chrome/browser/notifications/desktop_notification_service.h" 14#include "chrome/browser/notifications/desktop_notification_service_factory.h" 15#include "chrome/browser/notifications/notification.h" 16#include "chrome/browser/notifications/notification_options_menu_model.h" 17#include "chrome/browser/profiles/profile.h" 18#import "chrome/browser/ui/cocoa/hover_image_button.h" 19#import "chrome/browser/ui/cocoa/menu_controller.h" 20#import "chrome/browser/ui/cocoa/notifications/balloon_view.h" 21#include "chrome/browser/ui/cocoa/notifications/balloon_view_host_mac.h" 22#include "content/browser/renderer_host/render_view_host.h" 23#include "grit/generated_resources.h" 24#include "grit/theme_resources.h" 25#include "ui/base/l10n/l10n_util.h" 26#include "ui/base/resource/resource_bundle.h" 27 28namespace { 29 30// Margin, in pixels, between the notification frame and the contents 31// of the notification. 32const int kTopMargin = 1; 33const int kBottomMargin = 2; 34const int kLeftMargin = 2; 35const int kRightMargin = 2; 36 37} // namespace 38 39@interface BalloonController (Private) 40- (void)updateTrackingRect; 41@end 42 43@implementation BalloonController 44 45- (id)initWithBalloon:(Balloon*)balloon { 46 NSString* nibpath = 47 [base::mac::MainAppBundle() pathForResource:@"Notification" 48 ofType:@"nib"]; 49 if ((self = [super initWithWindowNibPath:nibpath owner:self])) { 50 balloon_ = balloon; 51 [self initializeHost]; 52 menuModel_.reset(new NotificationOptionsMenuModel(balloon)); 53 menuController_.reset([[MenuController alloc] initWithModel:menuModel_.get() 54 useWithPopUpButtonCell:NO]); 55 } 56 return self; 57} 58 59- (void)awakeFromNib { 60 DCHECK([self window]); 61 DCHECK_EQ(self, [[self window] delegate]); 62 63 NSImage* image = app::mac::GetCachedImageWithName(@"balloon_wrench.pdf"); 64 [optionsButton_ setDefaultImage:image]; 65 [optionsButton_ setDefaultOpacity:0.6]; 66 [optionsButton_ setHoverImage:image]; 67 [optionsButton_ setHoverOpacity:0.9]; 68 [optionsButton_ setPressedImage:image]; 69 [optionsButton_ setPressedOpacity:1.0]; 70 [[optionsButton_ cell] setHighlightsBy:NSNoCellMask]; 71 72 NSString* sourceLabelText = l10n_util::GetNSStringF( 73 IDS_NOTIFICATION_BALLOON_SOURCE_LABEL, 74 balloon_->notification().display_source()); 75 [originLabel_ setStringValue:sourceLabelText]; 76 77 // This condition is false in unit tests which have no RVH. 78 if (htmlContents_.get()) { 79 gfx::NativeView contents = htmlContents_->native_view(); 80 [contents setFrame:NSMakeRect(kLeftMargin, kTopMargin, 0, 0)]; 81 [[htmlContainer_ superview] addSubview:contents 82 positioned:NSWindowBelow 83 relativeTo:nil]; 84 } 85 86 // Use the standard close button for a utility window. 87 closeButton_ = [NSWindow standardWindowButton:NSWindowCloseButton 88 forStyleMask:NSUtilityWindowMask]; 89 NSRect frame = [closeButton_ frame]; 90 [closeButton_ setFrame:NSMakeRect(6, 1, frame.size.width, frame.size.height)]; 91 [closeButton_ setTarget:self]; 92 [closeButton_ setAction:@selector(closeButtonPressed:)]; 93 [shelf_ addSubview:closeButton_]; 94 [self updateTrackingRect]; 95 96 // Set the initial position without animating (the balloon should not 97 // yet be visible). 98 DCHECK(![[self window] isVisible]); 99 NSRect balloon_frame = NSMakeRect(balloon_->GetPosition().x(), 100 balloon_->GetPosition().y(), 101 [self desiredTotalWidth], 102 [self desiredTotalHeight]); 103 [[self window] setFrame:balloon_frame 104 display:NO]; 105} 106 107- (void)updateTrackingRect { 108 if (closeButtonTrackingTag_) 109 [shelf_ removeTrackingRect:closeButtonTrackingTag_]; 110 111 closeButtonTrackingTag_ = [shelf_ addTrackingRect:[closeButton_ frame] 112 owner:self 113 userData:nil 114 assumeInside:NO]; 115} 116 117- (void) mouseEntered:(NSEvent*)event { 118 [[closeButton_ cell] setHighlighted:YES]; 119} 120 121- (void) mouseExited:(NSEvent*)event { 122 [[closeButton_ cell] setHighlighted:NO]; 123} 124 125- (void)closeBalloonNow:(bool)byUser { 126 if (!balloon_) 127 return; 128 [self close]; 129 if (htmlContents_.get()) 130 htmlContents_->Shutdown(); 131 if (balloon_) 132 balloon_->OnClose(byUser); 133 balloon_ = NULL; 134} 135 136- (IBAction)optionsButtonPressed:(id)sender { 137 optionMenuIsActive_ = YES; 138 [NSMenu popUpContextMenu:[menuController_ menu] 139 withEvent:[NSApp currentEvent] 140 forView:optionsButton_]; 141 optionMenuIsActive_ = NO; 142 if (delayedClose_) 143 [self closeBalloonNow: false]; // always by script. 144} 145 146- (IBAction)permissionRevoked:(id)sender { 147 DesktopNotificationService* service = 148 DesktopNotificationServiceFactory::GetForProfile(balloon_->profile()); 149 service->DenyPermission(balloon_->notification().origin_url()); 150} 151 152- (IBAction)closeButtonPressed:(id)sender { 153 [self closeBalloon:YES]; 154 [self close]; 155} 156 157- (void)close { 158 if (closeButtonTrackingTag_) 159 [shelf_ removeTrackingRect:closeButtonTrackingTag_]; 160 161 [super close]; 162} 163 164- (void)closeBalloon:(bool)byUser { 165 // Keep alive while user is interacting with popup menu. 166 // Otherwise the script can close the notification and underlying balloon 167 // will be destroyed while user select a menu command. 168 if (!byUser && optionMenuIsActive_) { 169 delayedClose_ = YES; 170 return; 171 } 172 [self closeBalloonNow: byUser]; 173} 174 175- (void)updateContents { 176 DCHECK(htmlContents_.get()) << "BalloonView::Update called before Show"; 177 if (htmlContents_->render_view_host()) 178 htmlContents_->render_view_host()->NavigateToURL( 179 balloon_->notification().content_url()); 180} 181 182- (void)repositionToBalloon { 183 DCHECK(balloon_); 184 int x = balloon_->GetPosition().x(); 185 int y = balloon_->GetPosition().y(); 186 int w = [self desiredTotalWidth]; 187 int h = [self desiredTotalHeight]; 188 189 if (htmlContents_.get()) 190 htmlContents_->UpdateActualSize(balloon_->content_size()); 191 192 [[[self window] animator] setFrame:NSMakeRect(x, y, w, h) 193 display:YES]; 194} 195 196// Returns the total width the view should be to accommodate the balloon. 197- (int)desiredTotalWidth { 198 return (balloon_ ? balloon_->content_size().width() : 0) + 199 kLeftMargin + kRightMargin; 200} 201 202// Returns the total height the view should be to accommodate the balloon. 203- (int)desiredTotalHeight { 204 return (balloon_ ? balloon_->content_size().height() : 0) + 205 kTopMargin + kBottomMargin + [shelf_ frame].size.height; 206} 207 208// Returns the BalloonHost { 209- (BalloonViewHost*) getHost { 210 return htmlContents_.get(); 211} 212 213// Initializes the renderer host showing the HTML contents. 214- (void)initializeHost { 215 htmlContents_.reset(new BalloonViewHost(balloon_)); 216 htmlContents_->Init(); 217} 218 219// NSWindowDelegate notification. 220- (void)windowWillClose:(NSNotification*)notif { 221 [self autorelease]; 222} 223 224@end 225