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 28 namespace { 29 30 // Margin, in pixels, between the notification frame and the contents 31 // of the notification. 32 const int kTopMargin = 1; 33 const int kBottomMargin = 2; 34 const int kLeftMargin = 2; 35 const 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