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