// Copyright (c) 2010 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/cocoa/notifications/balloon_controller.h" #include "app/l10n_util.h" #include "app/resource_bundle.h" #import "base/cocoa_protocols_mac.h" #include "base/mac_util.h" #include "base/nsimage_cache_mac.h" #import "base/scoped_nsobject.h" #include "base/utf_string_conversions.h" #import "chrome/browser/cocoa/hover_image_button.h" #import "chrome/browser/cocoa/menu_controller.h" #import "chrome/browser/cocoa/notifications/balloon_view.h" #include "chrome/browser/cocoa/notifications/balloon_view_host_mac.h" #include "chrome/browser/notifications/balloon.h" #include "chrome/browser/notifications/desktop_notification_service.h" #include "chrome/browser/notifications/notification_options_menu_model.h" #include "chrome/browser/profile.h" #include "chrome/browser/renderer_host/render_view_host.h" #include "grit/generated_resources.h" #include "grit/theme_resources.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 = [mac_util::MainAppBundle() 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]); NSImage* image = nsimage_cache::ImageNamed(@"balloon_wrench.pdf"); [optionsButton_ setDefaultImage:image]; [optionsButton_ setDefaultOpacity:0.6]; [optionsButton_ setHoverImage:image]; [optionsButton_ setHoverOpacity:0.9]; [optionsButton_ setPressedImage:image]; [optionsButton_ setPressedOpacity:1.0]; [[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]; } - (BOOL)handleEvent:(NSEvent*)event { BOOL eventHandled = NO; if ([event type] == NSLeftMouseDown) { NSPoint mouse = [shelf_ convertPoint:[event locationInWindow] fromView:nil]; if (NSPointInRect(mouse, [closeButton_ frame])) { [closeButton_ mouseDown:event]; // Bring back the front process that is deactivated when we click the // close button. if (frontProcessNum_.highLongOfPSN || frontProcessNum_.lowLongOfPSN) { SetFrontProcessWithOptions(&frontProcessNum_, kSetFrontProcessFrontWindowOnly); frontProcessNum_.highLongOfPSN = 0; frontProcessNum_.lowLongOfPSN = 0; } eventHandled = YES; } else if (NSPointInRect(mouse, [optionsButton_ frame])) { [optionsButton_ mouseDown:event]; eventHandled = YES; } } return eventHandled; } - (void) mouseEntered:(NSEvent*)event { [[closeButton_ cell] setHighlighted:YES]; // Remember the current front process so that we can bring it back later. if (!frontProcessNum_.highLongOfPSN && !frontProcessNum_.lowLongOfPSN) GetFrontProcess(&frontProcessNum_); } - (void) mouseExited:(NSEvent*)event { [[closeButton_ cell] setHighlighted:NO]; frontProcessNum_.highLongOfPSN = 0; frontProcessNum_.lowLongOfPSN = 0; } - (IBAction)optionsButtonPressed:(id)sender { [NSMenu popUpContextMenu:[menuController_ menu] withEvent:[NSApp currentEvent] forView:optionsButton_]; } - (IBAction)permissionRevoked:(id)sender { DesktopNotificationService* service = balloon_->profile()->GetDesktopNotificationService(); 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 { if (!balloon_) return; [self close]; if (htmlContents_.get()) htmlContents_->Shutdown(); if (balloon_) balloon_->OnClose(byUser); balloon_ = NULL; } - (void)updateContents { DCHECK(htmlContents_.get()) << "BalloonView::Update called before Show"; if (htmlContents_->render_view_host()) htmlContents_->render_view_host()->NavigateToURL( balloon_->notification().content_url()); } - (void)repositionToBalloon { DCHECK(balloon_); int x = balloon_->GetPosition().x(); int y = balloon_->GetPosition().y(); int w = [self desiredTotalWidth]; int h = [self desiredTotalHeight]; if (htmlContents_.get()) htmlContents_->UpdateActualSize(balloon_->content_size()); [[[self window] animator] setFrame:NSMakeRect(x, y, w, h) display:YES]; } // 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