Home | History | Annotate | Download | only in notifications
      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