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