blob: fb481cd56c46c6c6f368b9015d839f3b500c4ded [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/cocoa/notifications/balloon_controller.h"
#include "base/mac/bundle_locations.h"
#include "base/mac/mac_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/notifications/balloon.h"
#include "chrome/browser/notifications/desktop_notification_service.h"
#include "chrome/browser/notifications/desktop_notification_service_factory.h"
#include "chrome/browser/notifications/notification.h"
#include "chrome/browser/notifications/notification_options_menu_model.h"
#include "chrome/browser/profiles/profile.h"
#import "chrome/browser/ui/cocoa/notifications/balloon_view.h"
#include "chrome/browser/ui/cocoa/notifications/balloon_view_host_mac.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#import "ui/base/cocoa/hover_image_button.h"
#import "ui/base/cocoa/menu_controller.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
namespace {
// Margin, in pixels, between the notification frame and the contents
// of the notification.
const int kTopMargin = 1;
const int kBottomMargin = 2;
const int kLeftMargin = 2;
const int kRightMargin = 2;
} // namespace
@interface BalloonController (Private)
- (void)updateTrackingRect;
@end
@implementation BalloonController
- (id)initWithBalloon:(Balloon*)balloon {
NSString* nibpath =
[base::mac::FrameworkBundle() pathForResource:@"Notification"
ofType:@"nib"];
if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
balloon_ = balloon;
[self initializeHost];
menuModel_.reset(new NotificationOptionsMenuModel(balloon));
menuController_.reset([[MenuController alloc] initWithModel:menuModel_.get()
useWithPopUpButtonCell:NO]);
}
return self;
}
- (void)awakeFromNib {
DCHECK([self window]);
DCHECK_EQ(self, [[self window] delegate]);
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
[optionsButton_ setDefaultImage:
rb.GetNativeImageNamed(IDR_BALLOON_WRENCH).ToNSImage()];
[optionsButton_ setHoverImage:
rb.GetNativeImageNamed(IDR_BALLOON_WRENCH_H).ToNSImage()];
[optionsButton_ setPressedImage:
rb.GetNativeImageNamed(IDR_BALLOON_WRENCH_P).ToNSImage()];
[[optionsButton_ cell] setHighlightsBy:NSNoCellMask];
NSString* sourceLabelText = l10n_util::GetNSStringF(
IDS_NOTIFICATION_BALLOON_SOURCE_LABEL,
balloon_->notification().display_source());
[originLabel_ setStringValue:sourceLabelText];
// This condition is false in unit tests which have no RVH.
if (htmlContents_.get()) {
gfx::NativeView contents = htmlContents_->native_view();
[contents setFrame:NSMakeRect(kLeftMargin, kTopMargin, 0, 0)];
[[htmlContainer_ superview] addSubview:contents
positioned:NSWindowBelow
relativeTo:nil];
}
// Use the standard close button for a utility window.
closeButton_ = [NSWindow standardWindowButton:NSWindowCloseButton
forStyleMask:NSUtilityWindowMask];
NSRect frame = [closeButton_ frame];
[closeButton_ setFrame:NSMakeRect(6, 1, frame.size.width, frame.size.height)];
[closeButton_ setTarget:self];
[closeButton_ setAction:@selector(closeButtonPressed:)];
[shelf_ addSubview:closeButton_];
[self updateTrackingRect];
// Set the initial position without animating (the balloon should not
// yet be visible).
DCHECK(![[self window] isVisible]);
NSRect balloon_frame = NSMakeRect(balloon_->GetPosition().x(),
balloon_->GetPosition().y(),
[self desiredTotalWidth],
[self desiredTotalHeight]);
[[self window] setFrame:balloon_frame
display:NO];
}
- (void)updateTrackingRect {
if (closeButtonTrackingTag_)
[shelf_ removeTrackingRect:closeButtonTrackingTag_];
closeButtonTrackingTag_ = [shelf_ addTrackingRect:[closeButton_ frame]
owner:self
userData:nil
assumeInside:NO];
}
- (void) mouseEntered:(NSEvent*)event {
[[closeButton_ cell] setHighlighted:YES];
}
- (void) mouseExited:(NSEvent*)event {
[[closeButton_ cell] setHighlighted:NO];
}
- (void)closeBalloonNow:(bool)byUser {
if (!balloon_)
return;
[self close];
if (htmlContents_.get())
htmlContents_->Shutdown();
if (balloon_)
balloon_->OnClose(byUser);
balloon_ = NULL;
}
- (IBAction)optionsButtonPressed:(id)sender {
optionMenuIsActive_ = YES;
[NSMenu popUpContextMenu:[menuController_ menu]
withEvent:[NSApp currentEvent]
forView:optionsButton_];
optionMenuIsActive_ = NO;
if (delayedClose_)
[self closeBalloonNow: false]; // always by script.
}
- (IBAction)permissionRevoked:(id)sender {
DesktopNotificationService* service =
DesktopNotificationServiceFactory::GetForProfile(balloon_->profile());
service->DenyPermission(balloon_->notification().origin_url());
}
- (IBAction)closeButtonPressed:(id)sender {
[self closeBalloon:YES];
[self close];
}
- (void)close {
if (closeButtonTrackingTag_)
[shelf_ removeTrackingRect:closeButtonTrackingTag_];
[super close];
}
- (void)closeBalloon:(bool)byUser {
// Keep alive while user is interacting with popup menu.
// Otherwise the script can close the notification and underlying balloon
// will be destroyed while user select a menu command.
if (!byUser && optionMenuIsActive_) {
delayedClose_ = YES;
return;
}
[self closeBalloonNow: byUser];
}
- (void)updateContents {
DCHECK(htmlContents_.get()) << "BalloonView::Update called before Show";
if (htmlContents_->web_contents()) {
htmlContents_->web_contents()->GetController().LoadURL(
balloon_->notification().content_url(), content::Referrer(),
content::PAGE_TRANSITION_LINK, std::string());
}
}
- (void)repositionToBalloon {
DCHECK(balloon_);
int x = balloon_->GetPosition().x();
int y = balloon_->GetPosition().y();
int w = [self desiredTotalWidth];
int h = [self desiredTotalHeight];
[[self window] setFrame:NSMakeRect(x, y, w, h)
display:YES];
if (htmlContents_.get())
htmlContents_->UpdateActualSize(balloon_->content_size());
}
// Returns the total width the view should be to accommodate the balloon.
- (int)desiredTotalWidth {
return (balloon_ ? balloon_->content_size().width() : 0) +
kLeftMargin + kRightMargin;
}
// Returns the total height the view should be to accommodate the balloon.
- (int)desiredTotalHeight {
return (balloon_ ? balloon_->content_size().height() : 0) +
kTopMargin + kBottomMargin + [shelf_ frame].size.height;
}
// Returns the BalloonHost {
- (BalloonViewHost*) getHost {
return htmlContents_.get();
}
// Initializes the renderer host showing the HTML contents.
- (void)initializeHost {
htmlContents_.reset(new BalloonViewHost(balloon_));
htmlContents_->Init();
}
// NSWindowDelegate notification.
- (void)windowWillClose:(NSNotification*)notif {
[self autorelease];
}
@end