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