• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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