summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa/extension_installed_bubble_controller.mm
diff options
context:
space:
mode:
authormirandac@chromium.org <mirandac@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-31 21:45:17 +0000
committermirandac@chromium.org <mirandac@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-31 21:45:17 +0000
commit54bc0925f6430d3fd94fbfc8cabff0a41128701a (patch)
tree0596552395a069054716171e111bb5c971367290 /chrome/browser/cocoa/extension_installed_bubble_controller.mm
parent007cb731cd1d0af76a0688faa5d1e125c6f57db3 (diff)
downloadchromium_src-54bc0925f6430d3fd94fbfc8cabff0a41128701a.zip
chromium_src-54bc0925f6430d3fd94fbfc8cabff0a41128701a.tar.gz
chromium_src-54bc0925f6430d3fd94fbfc8cabff0a41128701a.tar.bz2
ExtensionInstalledBubble for Mac.
Adds ExtensionInstalledBubble.xib, which contains the framework for the bubble itself (icon view, close button, and three message fields). BUG= 26974 TEST= Install an extension. Bubble should show same information as windows bubble. Review URL: http://codereview.chromium.org/527012 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@37671 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/cocoa/extension_installed_bubble_controller.mm')
-rw-r--r--chrome/browser/cocoa/extension_installed_bubble_controller.mm343
1 files changed, 343 insertions, 0 deletions
diff --git a/chrome/browser/cocoa/extension_installed_bubble_controller.mm b/chrome/browser/cocoa/extension_installed_bubble_controller.mm
new file mode 100644
index 0000000..eba39e4
--- /dev/null
+++ b/chrome/browser/cocoa/extension_installed_bubble_controller.mm
@@ -0,0 +1,343 @@
+// 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.
+
+#import "extension_installed_bubble_controller.h"
+
+#include "app/l10n_util.h"
+#include "base/mac_util.h"
+#include "base/sys_string_conversions.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/browser_window.h"
+#include "chrome/browser/cocoa/autocomplete_text_field_cell.h"
+#include "chrome/browser/cocoa/browser_window_cocoa.h"
+#include "chrome/browser/cocoa/browser_window_controller.h"
+#include "chrome/browser/cocoa/extensions/browser_actions_controller.h"
+#include "chrome/browser/cocoa/hover_close_button.h"
+#include "chrome/browser/cocoa/info_bubble_view.h"
+#include "chrome/browser/cocoa/location_bar_view_mac.h"
+#include "chrome/browser/cocoa/toolbar_controller.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/notification_service.h"
+#include "grit/generated_resources.h"
+#import "skia/ext/skia_utils_mac.h"
+#import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
+
+
+
+// C++ class that receives EXTENSION_LOADED notifications and proxies them back
+// to |controller|.
+class ExtensionLoadedNotificationObserver : public NotificationObserver {
+ public:
+ ExtensionLoadedNotificationObserver(
+ ExtensionInstalledBubbleController* controller)
+ : controller_(controller) {
+ // Create a registrar and add ourselves to it.
+ registrar_.Add(this, NotificationType::EXTENSION_LOADED,
+ NotificationService::AllSources());
+ }
+
+ private:
+ // NotificationObserver implementation. Tells the controller to start showing
+ // its window on the main thread when the extension has finished loading.
+ void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ if (type == NotificationType::EXTENSION_LOADED) {
+ Extension* extension = Details<Extension>(details).ptr();
+ if (extension == [controller_ extension]) {
+ [controller_ performSelectorOnMainThread:@selector(showWindow:)
+ withObject:controller_
+ waitUntilDone:NO];
+ }
+ } else {
+ NOTREACHED() << "Received unexpected notification.";
+ }
+ }
+
+ NotificationRegistrar registrar_;
+ ExtensionInstalledBubbleController* controller_; // weak, owns us
+};
+
+@implementation ExtensionInstalledBubbleController
+
+@synthesize extension = extension_;
+
+- (id)initWithParentWindow:(NSWindow*)parentWindow
+ extension:(Extension*)extension
+ browser:(Browser*)browser
+ icon:(SkBitmap)icon {
+ NSString* nibPath =
+ [mac_util::MainAppBundle() pathForResource:@"ExtensionInstalledBubble"
+ ofType:@"nib"];
+ if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
+ parentWindow_ = parentWindow;
+ extension_ = extension;
+ browser_ = browser;
+ icon_.reset([gfx::SkBitmapToNSImage(icon) retain]);
+
+ if (extension->browser_action()) {
+ type_ = extension_installed_bubble::kBrowserAction;
+ } else if (extension->page_action() &&
+ !extension->page_action()->default_icon_path().empty()) {
+ type_ = extension_installed_bubble::kPageAction;
+ } else {
+ NOTREACHED(); // kGeneric installs handled in the extension_install_ui.
+ }
+
+ // Start showing window only after extension has fully loaded.
+ extensionObserver_.reset(new ExtensionLoadedNotificationObserver(self));
+
+ // Watch to see if the parent window closes, and close this one if so.
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
+ [center addObserver:self
+ selector:@selector(parentWindowWillClose:)
+ name:NSWindowWillCloseNotification
+ object:parentWindow_];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+
+- (void)close {
+ [parentWindow_ removeChildWindow:[self window]];
+ [super close];
+}
+
+- (void)parentWindowWillClose:(NSNotification*)notification {
+ [self close];
+}
+
+- (void)windowWillClose:(NSNotification*)notification {
+ // Turn off page action icon preview when the window closes.
+ if (extension_->page_action()) {
+ [self removePageActionPreview];
+ }
+ // We caught a close so we don't need to watch for the parent closing.
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [self autorelease];
+}
+
+// The controller is the delegate of the window, so it receives "did resign
+// key" notifications. When key is resigned, close the window.
+- (void)windowDidResignKey:(NSNotification*)notification {
+ NSWindow* window = [self window];
+ DCHECK_EQ([notification object], window);
+ DCHECK([window isVisible]);
+ [self close];
+}
+
+- (IBAction)closeWindow:(id)sender {
+ DCHECK([[self window] isVisible]);
+ [self close];
+}
+
+// Extracted to a function here so that it can be overwritten for unit
+// testing.
+- (void)removePageActionPreview {
+ BrowserWindowCocoa* window = static_cast<BrowserWindowCocoa*>(
+ browser_->window());
+ LocationBarViewMac* locationBarView = static_cast<LocationBarViewMac*>(
+ [[window->cocoa_controller() toolbarController] locationBarBridge]);
+
+ locationBarView->SetPreviewEnabledPageAction(extension_->page_action(),
+ false); // disables preview.
+ [locationBarView->GetAutocompleteTextField() setNeedsDisplay:YES];
+}
+
+// The extension installed bubble points at the browser action icon or the
+// page action icon (shown as a preview), depending on the extension type.
+// We need to calculate the location of these icons and the size of the
+// message itself (which varies with the title of the extension) in order
+// to figure out the origin point for the extension installed bubble.
+// TODO(mirandac): add framework to easily test extension UI components!
+- (NSPoint)calculateArrowPoint {
+ BrowserWindowCocoa* window =
+ static_cast<BrowserWindowCocoa*>(browser_->window());
+ NSPoint arrowPoint;
+
+ switch(type_) {
+ case extension_installed_bubble::kBrowserAction: {
+ // Find the center of the bottom of the browser action icon.
+ NSView* button = [[[window->cocoa_controller() toolbarController]
+ browserActionsController] browserActionViewForExtension:extension_];
+ DCHECK(button);
+ NSRect boundsRect = [[[button window] contentView]
+ convertRect:[button frame]
+ fromView:[button superview]];
+ arrowPoint =
+ NSMakePoint(NSMinX(boundsRect) + NSWidth([button frame]) / 2,
+ NSMinY(boundsRect));
+ break;
+ }
+ case extension_installed_bubble::kPageAction: {
+ LocationBarViewMac* locationBarView =
+ static_cast<LocationBarViewMac*>(
+ [[window->cocoa_controller() toolbarController]
+ locationBarBridge]);
+ // Tell the location bar to show a preview of the page action icon, which
+ // would ordinarily only be displayed on a page of the appropriate type.
+ // We remove this preview when the extension installed bubble closes.
+ locationBarView->SetPreviewEnabledPageAction(extension_->page_action(),
+ true);
+
+ // Find the center of the bottom of the page action icon.
+ AutocompleteTextField* field =
+ locationBarView->GetAutocompleteTextField();
+ size_t index =
+ locationBarView->GetPageActionIndex(extension_->page_action());
+ NSView* browserContentWindow = [window->GetNativeHandle() contentView];
+ NSRect iconRect = [[field autocompleteTextFieldCell]
+ pageActionFrameForIndex:index inFrame:[field frame]];
+ NSRect boundsrect = [browserContentWindow convertRect:iconRect
+ fromView:[field superview]];
+ arrowPoint =
+ NSMakePoint(NSMinX(boundsrect) - NSWidth(boundsrect) / 2 - 1,
+ NSMinY(boundsrect));
+ break;
+ }
+ default: {
+ NOTREACHED() << "Generic extension type not allowed in install bubble.";
+ }
+ }
+ return arrowPoint;
+}
+
+// We want this to be a child of a browser window. addChildWindow:
+// (called from this function) will bring the window on-screen;
+// unfortunately, [NSWindowController showWindow:] will also bring it
+// on-screen (but will cause unexpected changes to the window's
+// position). We cannot have an addChildWindow: and a subsequent
+// showWindow:. Thus, we have our own version.
+- (void)showWindow:(id)sender {
+ // Generic extensions get an infobar rather than a bubble.
+ DCHECK(type_ != extension_installed_bubble::kGeneric);
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+
+ // Load nib and calculate height based on messages to be shown.
+ NSWindow* window = [self initializeWindow];
+ int newWindowHeight = [self calculateWindowHeight];
+ NSSize windowDelta = NSMakeSize(
+ 0, newWindowHeight - NSHeight([[window contentView] bounds]));
+ [infoBubbleView_ setFrameSize:NSMakeSize(
+ NSWidth([[window contentView] bounds]), newWindowHeight)];
+ NSRect newFrame = [window frame];
+ newFrame.size.height += windowDelta.height;
+ [window setFrame:newFrame display:NO];
+
+ // Now that we have resized the window, adjust y pos of the messages.
+ [self setMessageFrames:newWindowHeight];
+
+ // Find window origin, taking into account bubble size and arrow location.
+ NSPoint origin =
+ [parentWindow_ convertBaseToScreen:[self calculateArrowPoint]];
+ origin.x -= NSWidth([window frame]) - kBubbleArrowXOffset -
+ kBubbleArrowWidth / 2;
+ origin.y -= NSHeight([window frame]);
+ [window setFrameOrigin:origin];
+
+ [parentWindow_ addChildWindow:window
+ ordered:NSWindowAbove];
+ [window makeKeyAndOrderFront:self];
+}
+
+// Finish nib loading, set arrow location and load icon into window. This
+// function is exposed for unit testing.
+- (NSWindow*)initializeWindow {
+ NSWindow* window = [self window]; // completes nib load
+ [infoBubbleView_ setArrowLocation:kTopRight];
+
+ // Set appropriate icon, resizing if necessary.
+ if ([icon_ size].width > extension_installed_bubble::kIconSize) {
+ [icon_ setSize:NSMakeSize(extension_installed_bubble::kIconSize,
+ extension_installed_bubble::kIconSize)];
+ }
+ [iconImage_ setImage:icon_];
+ [iconImage_ setNeedsDisplay:YES];
+ return window;
+ }
+
+// Calculate the height of each install message, resizing messages in their
+// frames to fit window width. Return the new window height, based on the
+// total of all message heights.
+- (int)calculateWindowHeight {
+ // Adjust the window height to reflect the sum height of all messages
+ // and vertical padding.
+ int newWindowHeight = 2 * extension_installed_bubble::kOuterVerticalMargin;
+
+ // First part of extension installed message.
+ [extensionInstalledMsg_ setStringValue:l10n_util::GetNSStringF(
+ IDS_EXTENSION_INSTALLED_HEADING, UTF8ToUTF16(extension_->name()))];
+ [GTMUILocalizerAndLayoutTweaker
+ sizeToFitFixedWidthTextField:extensionInstalledMsg_];
+ newWindowHeight += [extensionInstalledMsg_ frame].size.height +
+ extension_installed_bubble::kInnerVerticalMargin;
+
+ // If type is page action, include a special message about page actions.
+ if (type_ == extension_installed_bubble::kPageAction) {
+ [pageActionInfoMsg_ setHidden:NO];
+ [[pageActionInfoMsg_ cell]
+ setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+ [GTMUILocalizerAndLayoutTweaker
+ sizeToFitFixedWidthTextField:pageActionInfoMsg_];
+ newWindowHeight += [pageActionInfoMsg_ frame].size.height +
+ extension_installed_bubble::kInnerVerticalMargin;
+ }
+
+ // Second part of extension installed message.
+ [[extensionInstalledInfoMsg_ cell]
+ setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+ [GTMUILocalizerAndLayoutTweaker
+ sizeToFitFixedWidthTextField:extensionInstalledInfoMsg_];
+ newWindowHeight += [extensionInstalledInfoMsg_ frame].size.height;
+
+ return newWindowHeight;
+}
+
+// Adjust y-position of messages to sit properly in new window height.
+- (void)setMessageFrames:(int)newWindowHeight {
+ // The extension messages will always be shown.
+ NSRect extensionMessageFrame1 = [extensionInstalledMsg_ frame];
+ NSRect extensionMessageFrame2 = [extensionInstalledInfoMsg_ frame];
+
+ extensionMessageFrame1.origin.y = newWindowHeight - (
+ extensionMessageFrame1.size.height +
+ extension_installed_bubble::kOuterVerticalMargin);
+ [extensionInstalledMsg_ setFrame:extensionMessageFrame1];
+ if (type_ == extension_installed_bubble::kPageAction) {
+ // The page action message is only shown when appropriate.
+ NSRect pageActionMessageFrame = [pageActionInfoMsg_ frame];
+ pageActionMessageFrame.origin.y = extensionMessageFrame1.origin.y - (
+ pageActionMessageFrame.size.height +
+ extension_installed_bubble::kInnerVerticalMargin);
+ [pageActionInfoMsg_ setFrame:pageActionMessageFrame];
+ extensionMessageFrame2.origin.y = pageActionMessageFrame.origin.y - (
+ extensionMessageFrame2.size.height +
+ extension_installed_bubble::kInnerVerticalMargin);
+ } else {
+ extensionMessageFrame2.origin.y = extensionMessageFrame1.origin.y - (
+ extensionMessageFrame2.size.height +
+ extension_installed_bubble::kInnerVerticalMargin);
+ }
+ [extensionInstalledInfoMsg_ setFrame:extensionMessageFrame2];
+}
+
+// Exposed for unit testing.
+- (NSRect)getExtensionInstalledMsgFrame {
+ return [extensionInstalledMsg_ frame];
+}
+
+- (NSRect)getPageActionInfoMsgFrame {
+ return [pageActionInfoMsg_ frame];
+}
+
+- (NSRect)getExtensionInstalledInfoMsgFrame {
+ return [extensionInstalledInfoMsg_ frame];
+}
+
+@end