| Index: chrome/browser/cocoa/page_info_bubble_controller.mm
|
| diff --git a/chrome/browser/cocoa/page_info_bubble_controller.mm b/chrome/browser/cocoa/page_info_bubble_controller.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..710f11c469002d21ab90fe7e0313806cd7763a5a
|
| --- /dev/null
|
| +++ b/chrome/browser/cocoa/page_info_bubble_controller.mm
|
| @@ -0,0 +1,383 @@
|
| +// 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 "chrome/browser/cocoa/page_info_bubble_controller.h"
|
| +
|
| +#include "app/l10n_util_mac.h"
|
| +#include "app/resource_bundle.h"
|
| +#include "base/sys_string_conversions.h"
|
| +#include "chrome/browser/cert_store.h"
|
| +#include "chrome/browser/certificate_viewer.h"
|
| +#import "chrome/browser/cocoa/browser_window_controller.h"
|
| +#import "chrome/browser/cocoa/location_bar/location_bar_view_mac.h"
|
| +#import "chrome/browser/cocoa/info_bubble_view.h"
|
| +#import "chrome/browser/cocoa/info_bubble_window.h"
|
| +#include "chrome/browser/profile.h"
|
| +#include "grit/generated_resources.h"
|
| +#include "grit/theme_resources.h"
|
| +#include "net/base/cert_status_flags.h"
|
| +#include "net/base/x509_certificate.h"
|
| +#import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
|
| +
|
| +@interface PageInfoBubbleController (Private)
|
| +- (PageInfoModel*)model;
|
| +- (NSButton*)certificateButtonWithFrame:(NSRect)frame;
|
| +- (NSImage*)statusIconForState:(PageInfoModel::SectionInfoState)state;
|
| +- (void)configureTextFieldAsLabel:(NSTextField*)textField;
|
| +- (CGFloat)addTitleViewForInfo:(const PageInfoModel::SectionInfo&)info
|
| + toSubviews:(NSMutableArray*)subviews
|
| + atPoint:(NSPoint)point;
|
| +- (CGFloat)addDescriptionViewForInfo:(const PageInfoModel::SectionInfo&)info
|
| + toSubviews:(NSMutableArray*)subviews
|
| + atPoint:(NSPoint)point;
|
| +- (CGFloat)addCertificateButtonToSubviews:(NSMutableArray*)subviews
|
| + atOffset:(CGFloat)offset;
|
| +- (void)addImageViewForInfo:(const PageInfoModel::SectionInfo&)info
|
| + toSubviews:(NSMutableArray*)subviews
|
| + atOffset:(CGFloat)offset;
|
| +@end
|
| +
|
| +namespace {
|
| +
|
| +// The width of the window, in view coordinates. The height will be determined
|
| +// by the content.
|
| +const NSInteger kWindowWidth = 380;
|
| +
|
| +// Spacing in between sections.
|
| +const NSInteger kVerticalSpacing = 10;
|
| +
|
| +// Padding along on the X-axis between the window frame and content.
|
| +const NSInteger kFramePadding = 20;
|
| +
|
| +// Spacing between the title and description text views.
|
| +const NSInteger kTitleSpacing = 2;
|
| +
|
| +// Spacing between the image and the text.
|
| +const NSInteger kImageSpacing = 10;
|
| +
|
| +// Square size of the image.
|
| +const CGFloat kImageSize = 30;
|
| +
|
| +// The X position of the text fields. Variants for with and without an image.
|
| +const CGFloat kTextXPositionNoImage = kFramePadding;
|
| +const CGFloat kTextXPosition = kTextXPositionNoImage + kImageSize +
|
| + kImageSpacing;
|
| +
|
| +// Width of the text fields.
|
| +const CGFloat kTextWidth = kWindowWidth - (kImageSize + kImageSpacing +
|
| + kFramePadding * 2);
|
| +
|
| +// Takes in the bubble's height and the parent window, which should be a
|
| +// BrowserWindow, and gets the proper anchor point for the bubble. The point is
|
| +// in screen coordinates.
|
| +NSPoint AnchorPointFromParentWindow(NSWindow* parent, CGFloat bubbleHeight) {
|
| + BrowserWindowController* controller = [parent windowController];
|
| + NSPoint origin = NSZeroPoint;
|
| + if ([controller isKindOfClass:[BrowserWindowController class]]) {
|
| + LocationBarViewMac* location_bar = [controller locationBarBridge];
|
| + if (location_bar) {
|
| + NSPoint arrowTip = location_bar->GetPageInfoBubblePoint();
|
| + origin = [parent convertBaseToScreen:arrowTip];
|
| + origin.y -= bubbleHeight;
|
| + }
|
| + }
|
| + return origin;
|
| +}
|
| +
|
| +// Bridge that listens for change notifications from the model.
|
| +class PageInfoModelBubbleBridge : public PageInfoModel::PageInfoModelObserver {
|
| + public:
|
| + PageInfoModelBubbleBridge() : controller_(nil) {}
|
| +
|
| + // PageInfoModelObserver implementation.
|
| + virtual void ModelChanged() {
|
| + [controller_ performLayout];
|
| + }
|
| +
|
| + // Sets the controller.
|
| + void set_controller(PageInfoBubbleController* controller) {
|
| + controller_ = controller;
|
| + }
|
| +
|
| + private:
|
| + PageInfoBubbleController* controller_; // weak
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +namespace browser {
|
| +
|
| +void ShowPageInfoBubble(gfx::NativeWindow parent,
|
| + Profile* profile,
|
| + const GURL& url,
|
| + const NavigationEntry::SSLStatus& ssl,
|
| + bool show_history) {
|
| + PageInfoModelBubbleBridge* bridge = new PageInfoModelBubbleBridge();
|
| + PageInfoModel* model =
|
| + new PageInfoModel(profile, url, ssl, show_history, bridge);
|
| + PageInfoBubbleController* controller =
|
| + [[PageInfoBubbleController alloc] initWithPageInfoModel:model
|
| + modelObserver:bridge
|
| + parentWindow:parent];
|
| + bridge->set_controller(controller);
|
| + [controller setCertID:ssl.cert_id()];
|
| + [controller showWindow:nil];
|
| +}
|
| +
|
| +} // namespace browser
|
| +
|
| +@implementation PageInfoBubbleController
|
| +
|
| +@synthesize certID = certID_;
|
| +
|
| +- (id)initWithPageInfoModel:(PageInfoModel*)model
|
| + modelObserver:(PageInfoModel::PageInfoModelObserver*)bridge
|
| + parentWindow:(NSWindow*)parentWindow {
|
| + // Use an arbitrary height because it will be changed by the bridge.
|
| + NSRect contentRect = NSMakeRect(0, 0, kWindowWidth, 50);
|
| + // Create an empty window into which content is placed.
|
| + scoped_nsobject<InfoBubbleWindow> window(
|
| + [[InfoBubbleWindow alloc] initWithContentRect:contentRect
|
| + styleMask:NSBorderlessWindowMask
|
| + backing:NSBackingStoreBuffered
|
| + defer:NO]);
|
| +
|
| + NSPoint anchorPoint = AnchorPointFromParentWindow(parentWindow, 0);
|
| + if ((self = [super initWithWindow:window.get()
|
| + parentWindow:parentWindow
|
| + anchoredAt:anchorPoint])) {
|
| + model_.reset(model);
|
| + bridge_.reset(bridge);
|
| +
|
| + // Load the image refs.
|
| + ResourceBundle& rb = ResourceBundle::GetSharedInstance();
|
| + okImage_.reset([rb.GetNSImageNamed(IDR_PAGEINFO_GOOD) retain]);
|
| + DCHECK_GE(kImageSize, [okImage_ size].width);
|
| + DCHECK_GE(kImageSize, [okImage_ size].height);
|
| + warningMinorImage_.reset(
|
| + [rb.GetNSImageNamed(IDR_PAGEINFO_WARNING_MINOR) retain]);
|
| + DCHECK_GE(kImageSize, [warningMinorImage_ size].width);
|
| + DCHECK_GE(kImageSize, [warningMinorImage_ size].height);
|
| + warningMajorImage_.reset(
|
| + [rb.GetNSImageNamed(IDR_PAGEINFO_WARNING_MAJOR) retain]);
|
| + DCHECK_GE(kImageSize, [warningMajorImage_ size].width);
|
| + DCHECK_GE(kImageSize, [warningMajorImage_ size].height);
|
| + errorImage_.reset([rb.GetNSImageNamed(IDR_PAGEINFO_BAD) retain]);
|
| + DCHECK_GE(kImageSize, [errorImage_ size].width);
|
| + DCHECK_GE(kImageSize, [errorImage_ size].height);
|
| +
|
| + [[self bubble] setArrowLocation:info_bubble::kTopLeft];
|
| +
|
| + [self performLayout];
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (PageInfoModel*)model {
|
| + return model_.get();
|
| +}
|
| +
|
| +- (IBAction)showCertWindow:(id)sender {
|
| + DCHECK(certID_ != 0);
|
| + ShowCertificateViewerByID([self parentWindow], certID_);
|
| +}
|
| +
|
| +// This will create the subviews for the page info window. The general layout
|
| +// is 2 or 3 boxed and titled sections, each of which has a status image to
|
| +// provide visual feedback and a description that explains it. The description
|
| +// text is usually only 1 or 2 lines, but can be much longer. At the bottom of
|
| +// the window is a button to view the SSL certificate, which is disabled if
|
| +// not using HTTPS.
|
| +- (void)performLayout {
|
| + // |offset| is the Y position that should be drawn at next.
|
| + CGFloat offset = kVerticalSpacing;
|
| +
|
| + // Keep the new subviews in an array that gets replaced at the end.
|
| + NSMutableArray* subviews = [NSMutableArray array];
|
| +
|
| + // Build the window from bottom-up because Cocoa's coordinate origin is the
|
| + // lower left.
|
| + for (int i = model_->GetSectionCount() - 1; i >= 0; --i) {
|
| + PageInfoModel::SectionInfo info = model_->GetSectionInfo(i);
|
| +
|
| + // Only certain sections have images. This affects the X position.
|
| + BOOL hasImage = info.type == PageInfoModel::SECTION_INFO_IDENTITY ||
|
| + info.type == PageInfoModel::SECTION_INFO_CONNECTION;
|
| + CGFloat xPosition = (hasImage ? kTextXPosition : kTextXPositionNoImage);
|
| +
|
| + if (info.type == PageInfoModel::SECTION_INFO_IDENTITY) {
|
| + offset += [self addCertificateButtonToSubviews:subviews
|
| + atOffset:offset];
|
| + }
|
| +
|
| + // Create the description of the state.
|
| + offset += [self addDescriptionViewForInfo:info
|
| + toSubviews:subviews
|
| + atPoint:NSMakePoint(xPosition, offset)];
|
| +
|
| + // Add the title.
|
| + offset += kTitleSpacing;
|
| + offset += [self addTitleViewForInfo:info
|
| + toSubviews:subviews
|
| + atPoint:NSMakePoint(xPosition, offset)];
|
| +
|
| + // Insert the image subview for sections that are appropriate.
|
| + if (hasImage) {
|
| + [self addImageViewForInfo:info toSubviews:subviews atOffset:offset];
|
| + }
|
| +
|
| + // Add the separators.
|
| + offset += kVerticalSpacing;
|
| + if (i != 0) {
|
| + scoped_nsobject<NSBox> spacer(
|
| + [[NSBox alloc] initWithFrame:NSMakeRect(0, offset, kWindowWidth, 1)]);
|
| + [spacer setBoxType:NSBoxSeparator];
|
| + [spacer setBorderType:NSLineBorder];
|
| + [subviews addObject:spacer.get()];
|
| + offset += kVerticalSpacing;
|
| + }
|
| + }
|
| +
|
| + // Replace the window's content.
|
| + [[[self window] contentView] setSubviews:subviews];
|
| +
|
| + offset += kFramePadding;
|
| +
|
| + // TODO(rsesek): Remove constant value to account for http://crbug.com/57306.
|
| + NSRect windowFrame = NSMakeRect(0, 0, kWindowWidth, 50);
|
| + windowFrame.size.height += offset;
|
| + windowFrame.size = [[[self window] contentView] convertSize:windowFrame.size
|
| + toView:nil];
|
| + // Just setting |size| will cause the window to grow upwards. Shift the
|
| + // origin up by grow amount, which causes the window to grow downwards.
|
| + windowFrame.origin = AnchorPointFromParentWindow([self parentWindow],
|
| + NSHeight(windowFrame));
|
| +
|
| + // Resize the window. Only animate if the window is visible, otherwise it
|
| + // could be "growing" while it's opening, looking awkward.
|
| + [[self window] setFrame:windowFrame
|
| + display:YES
|
| + animate:[[self window] isVisible]];
|
| +}
|
| +
|
| +// Creates the button with a given |frame| that, when clicked, will show the
|
| +// SSL certificate information.
|
| +- (NSButton*)certificateButtonWithFrame:(NSRect)frame {
|
| + NSButton* certButton = [[[NSButton alloc] initWithFrame:frame] autorelease];
|
| + [certButton setTitle:
|
| + l10n_util::GetNSStringWithFixup(IDS_PAGEINFO_CERT_INFO_BUTTON)];
|
| + [certButton setButtonType:NSMomentaryPushInButton];
|
| + [certButton setBezelStyle:NSRoundRectBezelStyle];
|
| + [certButton setTarget:self];
|
| + [certButton setAction:@selector(showCertWindow:)];
|
| + return certButton;
|
| +}
|
| +
|
| +// Returns a weak reference to the NSImage instance to used, or nil if none, for
|
| +// the specified info |state|.
|
| +- (NSImage*)statusIconForState:(PageInfoModel::SectionInfoState)state {
|
| + switch (state) {
|
| + case PageInfoModel::SECTION_STATE_OK:
|
| + return okImage_.get();
|
| + case PageInfoModel::SECTION_STATE_WARNING_MINOR:
|
| + return warningMinorImage_.get();
|
| + case PageInfoModel::SECTION_STATE_WARNING_MAJOR:
|
| + return warningMajorImage_.get();
|
| + case PageInfoModel::SECTION_STATE_ERROR:
|
| + return errorImage_.get();
|
| + default:
|
| + return nil;
|
| + }
|
| +}
|
| +
|
| +// Sets proprties on the given |field| to act as the title or description labels
|
| +// in the bubble.
|
| +- (void)configureTextFieldAsLabel:(NSTextField*)textField {
|
| + [textField setEditable:NO];
|
| + [textField setDrawsBackground:NO];
|
| + [textField setBezeled:NO];
|
| +}
|
| +
|
| +// Adds the title text field at the given x,y position, and returns the y
|
| +// position for the next element.
|
| +- (CGFloat)addTitleViewForInfo:(const PageInfoModel::SectionInfo&)info
|
| + toSubviews:(NSMutableArray*)subviews
|
| + atPoint:(NSPoint)point {
|
| + NSRect frame = NSMakeRect(point.x, point.y, kTextWidth, kImageSpacing);
|
| + scoped_nsobject<NSTextField> titleField(
|
| + [[NSTextField alloc] initWithFrame:frame]);
|
| + [self configureTextFieldAsLabel:titleField.get()];
|
| + [titleField setStringValue:base::SysUTF16ToNSString(info.title)];
|
| + [titleField setFont:[NSFont boldSystemFontOfSize:11]];
|
| + frame.size.height +=
|
| + [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:
|
| + titleField];
|
| + [titleField setFrame:frame];
|
| + [subviews addObject:titleField.get()];
|
| + return NSHeight(frame);
|
| +}
|
| +
|
| +// Adds the description text field at the given x,y position, and returns the y
|
| +// position for the next element.
|
| +- (CGFloat)addDescriptionViewForInfo:(const PageInfoModel::SectionInfo&)info
|
| + toSubviews:(NSMutableArray*)subviews
|
| + atPoint:(NSPoint)point {
|
| + NSRect frame = NSMakeRect(point.x, point.y, kTextWidth, kImageSize);
|
| + scoped_nsobject<NSTextField> textField(
|
| + [[NSTextField alloc] initWithFrame:frame]);
|
| + [self configureTextFieldAsLabel:textField.get()];
|
| + [textField setStringValue:base::SysUTF16ToNSString(info.description)];
|
| + [textField setFont:[NSFont labelFontOfSize:11]];
|
| +
|
| + // If the text is oversized, resize the text field.
|
| + frame.size.height +=
|
| + [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:
|
| + textField];
|
| + [subviews addObject:textField.get()];
|
| + return NSHeight(frame);
|
| +}
|
| +
|
| +// Adds the certificate button at a pre-determined x position and the given y.
|
| +// Returns the y position for the next element.
|
| +- (CGFloat)addCertificateButtonToSubviews:(NSMutableArray*)subviews
|
| + atOffset:(CGFloat)offset {
|
| + // Create the certificate button. The frame will be fixed up by GTM, so
|
| + // use arbitrary values.
|
| + NSRect frame = NSMakeRect(kTextXPosition, offset, 100, 20);
|
| + NSButton* certButton = [self certificateButtonWithFrame:frame];
|
| + [subviews addObject:certButton];
|
| + [GTMUILocalizerAndLayoutTweaker sizeToFitView:certButton];
|
| +
|
| + // By default, assume that we don't have certificate information to show.
|
| + [certButton setEnabled:NO];
|
| + if (certID_) {
|
| + scoped_refptr<net::X509Certificate> cert;
|
| + CertStore::GetSharedInstance()->RetrieveCert(certID_, &cert);
|
| +
|
| + // Don't bother showing certificates if there isn't one. Gears runs
|
| + // with no OS root certificate.
|
| + if (cert.get() && cert->os_cert_handle()) {
|
| + [certButton setEnabled:YES];
|
| + }
|
| + }
|
| +
|
| + return NSHeight(frame) + kVerticalSpacing;
|
| +}
|
| +
|
| +// Adds the state image at a pre-determined x position and the given y. This
|
| +// does not affect the next Y position because the image is placed next to
|
| +// a text field that is larger and accounts for the image's size.
|
| +- (void)addImageViewForInfo:(const PageInfoModel::SectionInfo&)info
|
| + toSubviews:(NSMutableArray*)subviews
|
| + atOffset:(CGFloat)offset {
|
| + NSRect frame = NSMakeRect(kFramePadding, offset - kImageSize, kImageSize,
|
| + kImageSize);
|
| + scoped_nsobject<NSImageView> imageView(
|
| + [[NSImageView alloc] initWithFrame:frame]);
|
| + [imageView setImageFrameStyle:NSImageFrameNone];
|
| + [imageView setImage:[self statusIconForState:info.state]];
|
| + [subviews addObject:imageView.get()];
|
| +}
|
| +
|
| +@end
|
|
|