OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2010 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 #import "chrome/browser/cocoa/page_info_bubble_controller.h" |
| 6 |
| 7 #include "app/l10n_util_mac.h" |
| 8 #include "app/resource_bundle.h" |
| 9 #include "base/sys_string_conversions.h" |
| 10 #include "chrome/browser/cert_store.h" |
| 11 #include "chrome/browser/certificate_viewer.h" |
| 12 #import "chrome/browser/cocoa/browser_window_controller.h" |
| 13 #import "chrome/browser/cocoa/location_bar/location_bar_view_mac.h" |
| 14 #import "chrome/browser/cocoa/info_bubble_view.h" |
| 15 #import "chrome/browser/cocoa/info_bubble_window.h" |
| 16 #include "chrome/browser/profile.h" |
| 17 #include "grit/generated_resources.h" |
| 18 #include "grit/theme_resources.h" |
| 19 #include "net/base/cert_status_flags.h" |
| 20 #include "net/base/x509_certificate.h" |
| 21 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" |
| 22 |
| 23 @interface PageInfoBubbleController (Private) |
| 24 - (PageInfoModel*)model; |
| 25 - (NSButton*)certificateButtonWithFrame:(NSRect)frame; |
| 26 - (NSImage*)statusIconForState:(PageInfoModel::SectionInfoState)state; |
| 27 - (void)configureTextFieldAsLabel:(NSTextField*)textField; |
| 28 - (CGFloat)addTitleViewForInfo:(const PageInfoModel::SectionInfo&)info |
| 29 toSubviews:(NSMutableArray*)subviews |
| 30 atPoint:(NSPoint)point; |
| 31 - (CGFloat)addDescriptionViewForInfo:(const PageInfoModel::SectionInfo&)info |
| 32 toSubviews:(NSMutableArray*)subviews |
| 33 atPoint:(NSPoint)point; |
| 34 - (CGFloat)addCertificateButtonToSubviews:(NSMutableArray*)subviews |
| 35 atOffset:(CGFloat)offset; |
| 36 - (void)addImageViewForInfo:(const PageInfoModel::SectionInfo&)info |
| 37 toSubviews:(NSMutableArray*)subviews |
| 38 atOffset:(CGFloat)offset; |
| 39 @end |
| 40 |
| 41 namespace { |
| 42 |
| 43 // The width of the window, in view coordinates. The height will be determined |
| 44 // by the content. |
| 45 const NSInteger kWindowWidth = 380; |
| 46 |
| 47 // Spacing in between sections. |
| 48 const NSInteger kVerticalSpacing = 10; |
| 49 |
| 50 // Padding along on the X-axis between the window frame and content. |
| 51 const NSInteger kFramePadding = 20; |
| 52 |
| 53 // Spacing between the title and description text views. |
| 54 const NSInteger kTitleSpacing = 2; |
| 55 |
| 56 // Spacing between the image and the text. |
| 57 const NSInteger kImageSpacing = 10; |
| 58 |
| 59 // Square size of the image. |
| 60 const CGFloat kImageSize = 30; |
| 61 |
| 62 // The X position of the text fields. Variants for with and without an image. |
| 63 const CGFloat kTextXPositionNoImage = kFramePadding; |
| 64 const CGFloat kTextXPosition = kTextXPositionNoImage + kImageSize + |
| 65 kImageSpacing; |
| 66 |
| 67 // Width of the text fields. |
| 68 const CGFloat kTextWidth = kWindowWidth - (kImageSize + kImageSpacing + |
| 69 kFramePadding * 2); |
| 70 |
| 71 // Takes in the bubble's height and the parent window, which should be a |
| 72 // BrowserWindow, and gets the proper anchor point for the bubble. The point is |
| 73 // in screen coordinates. |
| 74 NSPoint AnchorPointFromParentWindow(NSWindow* parent, CGFloat bubbleHeight) { |
| 75 BrowserWindowController* controller = [parent windowController]; |
| 76 NSPoint origin = NSZeroPoint; |
| 77 if ([controller isKindOfClass:[BrowserWindowController class]]) { |
| 78 LocationBarViewMac* location_bar = [controller locationBarBridge]; |
| 79 if (location_bar) { |
| 80 NSPoint arrowTip = location_bar->GetPageInfoBubblePoint(); |
| 81 origin = [parent convertBaseToScreen:arrowTip]; |
| 82 origin.y -= bubbleHeight; |
| 83 } |
| 84 } |
| 85 return origin; |
| 86 } |
| 87 |
| 88 // Bridge that listens for change notifications from the model. |
| 89 class PageInfoModelBubbleBridge : public PageInfoModel::PageInfoModelObserver { |
| 90 public: |
| 91 PageInfoModelBubbleBridge() : controller_(nil) {} |
| 92 |
| 93 // PageInfoModelObserver implementation. |
| 94 virtual void ModelChanged() { |
| 95 [controller_ performLayout]; |
| 96 } |
| 97 |
| 98 // Sets the controller. |
| 99 void set_controller(PageInfoBubbleController* controller) { |
| 100 controller_ = controller; |
| 101 } |
| 102 |
| 103 private: |
| 104 PageInfoBubbleController* controller_; // weak |
| 105 }; |
| 106 |
| 107 } // namespace |
| 108 |
| 109 namespace browser { |
| 110 |
| 111 void ShowPageInfoBubble(gfx::NativeWindow parent, |
| 112 Profile* profile, |
| 113 const GURL& url, |
| 114 const NavigationEntry::SSLStatus& ssl, |
| 115 bool show_history) { |
| 116 PageInfoModelBubbleBridge* bridge = new PageInfoModelBubbleBridge(); |
| 117 PageInfoModel* model = |
| 118 new PageInfoModel(profile, url, ssl, show_history, bridge); |
| 119 PageInfoBubbleController* controller = |
| 120 [[PageInfoBubbleController alloc] initWithPageInfoModel:model |
| 121 modelObserver:bridge |
| 122 parentWindow:parent]; |
| 123 bridge->set_controller(controller); |
| 124 [controller setCertID:ssl.cert_id()]; |
| 125 [controller showWindow:nil]; |
| 126 } |
| 127 |
| 128 } // namespace browser |
| 129 |
| 130 @implementation PageInfoBubbleController |
| 131 |
| 132 @synthesize certID = certID_; |
| 133 |
| 134 - (id)initWithPageInfoModel:(PageInfoModel*)model |
| 135 modelObserver:(PageInfoModel::PageInfoModelObserver*)bridge |
| 136 parentWindow:(NSWindow*)parentWindow { |
| 137 // Use an arbitrary height because it will be changed by the bridge. |
| 138 NSRect contentRect = NSMakeRect(0, 0, kWindowWidth, 50); |
| 139 // Create an empty window into which content is placed. |
| 140 scoped_nsobject<InfoBubbleWindow> window( |
| 141 [[InfoBubbleWindow alloc] initWithContentRect:contentRect |
| 142 styleMask:NSBorderlessWindowMask |
| 143 backing:NSBackingStoreBuffered |
| 144 defer:NO]); |
| 145 |
| 146 NSPoint anchorPoint = AnchorPointFromParentWindow(parentWindow, 0); |
| 147 if ((self = [super initWithWindow:window.get() |
| 148 parentWindow:parentWindow |
| 149 anchoredAt:anchorPoint])) { |
| 150 model_.reset(model); |
| 151 bridge_.reset(bridge); |
| 152 |
| 153 // Load the image refs. |
| 154 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
| 155 okImage_.reset([rb.GetNSImageNamed(IDR_PAGEINFO_GOOD) retain]); |
| 156 DCHECK_GE(kImageSize, [okImage_ size].width); |
| 157 DCHECK_GE(kImageSize, [okImage_ size].height); |
| 158 warningMinorImage_.reset( |
| 159 [rb.GetNSImageNamed(IDR_PAGEINFO_WARNING_MINOR) retain]); |
| 160 DCHECK_GE(kImageSize, [warningMinorImage_ size].width); |
| 161 DCHECK_GE(kImageSize, [warningMinorImage_ size].height); |
| 162 warningMajorImage_.reset( |
| 163 [rb.GetNSImageNamed(IDR_PAGEINFO_WARNING_MAJOR) retain]); |
| 164 DCHECK_GE(kImageSize, [warningMajorImage_ size].width); |
| 165 DCHECK_GE(kImageSize, [warningMajorImage_ size].height); |
| 166 errorImage_.reset([rb.GetNSImageNamed(IDR_PAGEINFO_BAD) retain]); |
| 167 DCHECK_GE(kImageSize, [errorImage_ size].width); |
| 168 DCHECK_GE(kImageSize, [errorImage_ size].height); |
| 169 |
| 170 [[self bubble] setArrowLocation:info_bubble::kTopLeft]; |
| 171 |
| 172 [self performLayout]; |
| 173 } |
| 174 return self; |
| 175 } |
| 176 |
| 177 - (PageInfoModel*)model { |
| 178 return model_.get(); |
| 179 } |
| 180 |
| 181 - (IBAction)showCertWindow:(id)sender { |
| 182 DCHECK(certID_ != 0); |
| 183 ShowCertificateViewerByID([self parentWindow], certID_); |
| 184 } |
| 185 |
| 186 // This will create the subviews for the page info window. The general layout |
| 187 // is 2 or 3 boxed and titled sections, each of which has a status image to |
| 188 // provide visual feedback and a description that explains it. The description |
| 189 // text is usually only 1 or 2 lines, but can be much longer. At the bottom of |
| 190 // the window is a button to view the SSL certificate, which is disabled if |
| 191 // not using HTTPS. |
| 192 - (void)performLayout { |
| 193 // |offset| is the Y position that should be drawn at next. |
| 194 CGFloat offset = kVerticalSpacing; |
| 195 |
| 196 // Keep the new subviews in an array that gets replaced at the end. |
| 197 NSMutableArray* subviews = [NSMutableArray array]; |
| 198 |
| 199 // Build the window from bottom-up because Cocoa's coordinate origin is the |
| 200 // lower left. |
| 201 for (int i = model_->GetSectionCount() - 1; i >= 0; --i) { |
| 202 PageInfoModel::SectionInfo info = model_->GetSectionInfo(i); |
| 203 |
| 204 // Only certain sections have images. This affects the X position. |
| 205 BOOL hasImage = info.type == PageInfoModel::SECTION_INFO_IDENTITY || |
| 206 info.type == PageInfoModel::SECTION_INFO_CONNECTION; |
| 207 CGFloat xPosition = (hasImage ? kTextXPosition : kTextXPositionNoImage); |
| 208 |
| 209 if (info.type == PageInfoModel::SECTION_INFO_IDENTITY) { |
| 210 offset += [self addCertificateButtonToSubviews:subviews |
| 211 atOffset:offset]; |
| 212 } |
| 213 |
| 214 // Create the description of the state. |
| 215 offset += [self addDescriptionViewForInfo:info |
| 216 toSubviews:subviews |
| 217 atPoint:NSMakePoint(xPosition, offset)]; |
| 218 |
| 219 // Add the title. |
| 220 offset += kTitleSpacing; |
| 221 offset += [self addTitleViewForInfo:info |
| 222 toSubviews:subviews |
| 223 atPoint:NSMakePoint(xPosition, offset)]; |
| 224 |
| 225 // Insert the image subview for sections that are appropriate. |
| 226 if (hasImage) { |
| 227 [self addImageViewForInfo:info toSubviews:subviews atOffset:offset]; |
| 228 } |
| 229 |
| 230 // Add the separators. |
| 231 offset += kVerticalSpacing; |
| 232 if (i != 0) { |
| 233 scoped_nsobject<NSBox> spacer( |
| 234 [[NSBox alloc] initWithFrame:NSMakeRect(0, offset, kWindowWidth, 1)]); |
| 235 [spacer setBoxType:NSBoxSeparator]; |
| 236 [spacer setBorderType:NSLineBorder]; |
| 237 [subviews addObject:spacer.get()]; |
| 238 offset += kVerticalSpacing; |
| 239 } |
| 240 } |
| 241 |
| 242 // Replace the window's content. |
| 243 [[[self window] contentView] setSubviews:subviews]; |
| 244 |
| 245 offset += kFramePadding; |
| 246 |
| 247 // TODO(rsesek): Remove constant value to account for http://crbug.com/57306. |
| 248 NSRect windowFrame = NSMakeRect(0, 0, kWindowWidth, 50); |
| 249 windowFrame.size.height += offset; |
| 250 windowFrame.size = [[[self window] contentView] convertSize:windowFrame.size |
| 251 toView:nil]; |
| 252 // Just setting |size| will cause the window to grow upwards. Shift the |
| 253 // origin up by grow amount, which causes the window to grow downwards. |
| 254 windowFrame.origin = AnchorPointFromParentWindow([self parentWindow], |
| 255 NSHeight(windowFrame)); |
| 256 |
| 257 // Resize the window. Only animate if the window is visible, otherwise it |
| 258 // could be "growing" while it's opening, looking awkward. |
| 259 [[self window] setFrame:windowFrame |
| 260 display:YES |
| 261 animate:[[self window] isVisible]]; |
| 262 } |
| 263 |
| 264 // Creates the button with a given |frame| that, when clicked, will show the |
| 265 // SSL certificate information. |
| 266 - (NSButton*)certificateButtonWithFrame:(NSRect)frame { |
| 267 NSButton* certButton = [[[NSButton alloc] initWithFrame:frame] autorelease]; |
| 268 [certButton setTitle: |
| 269 l10n_util::GetNSStringWithFixup(IDS_PAGEINFO_CERT_INFO_BUTTON)]; |
| 270 [certButton setButtonType:NSMomentaryPushInButton]; |
| 271 [certButton setBezelStyle:NSRoundRectBezelStyle]; |
| 272 [certButton setTarget:self]; |
| 273 [certButton setAction:@selector(showCertWindow:)]; |
| 274 return certButton; |
| 275 } |
| 276 |
| 277 // Returns a weak reference to the NSImage instance to used, or nil if none, for |
| 278 // the specified info |state|. |
| 279 - (NSImage*)statusIconForState:(PageInfoModel::SectionInfoState)state { |
| 280 switch (state) { |
| 281 case PageInfoModel::SECTION_STATE_OK: |
| 282 return okImage_.get(); |
| 283 case PageInfoModel::SECTION_STATE_WARNING_MINOR: |
| 284 return warningMinorImage_.get(); |
| 285 case PageInfoModel::SECTION_STATE_WARNING_MAJOR: |
| 286 return warningMajorImage_.get(); |
| 287 case PageInfoModel::SECTION_STATE_ERROR: |
| 288 return errorImage_.get(); |
| 289 default: |
| 290 return nil; |
| 291 } |
| 292 } |
| 293 |
| 294 // Sets proprties on the given |field| to act as the title or description labels |
| 295 // in the bubble. |
| 296 - (void)configureTextFieldAsLabel:(NSTextField*)textField { |
| 297 [textField setEditable:NO]; |
| 298 [textField setDrawsBackground:NO]; |
| 299 [textField setBezeled:NO]; |
| 300 } |
| 301 |
| 302 // Adds the title text field at the given x,y position, and returns the y |
| 303 // position for the next element. |
| 304 - (CGFloat)addTitleViewForInfo:(const PageInfoModel::SectionInfo&)info |
| 305 toSubviews:(NSMutableArray*)subviews |
| 306 atPoint:(NSPoint)point { |
| 307 NSRect frame = NSMakeRect(point.x, point.y, kTextWidth, kImageSpacing); |
| 308 scoped_nsobject<NSTextField> titleField( |
| 309 [[NSTextField alloc] initWithFrame:frame]); |
| 310 [self configureTextFieldAsLabel:titleField.get()]; |
| 311 [titleField setStringValue:base::SysUTF16ToNSString(info.title)]; |
| 312 [titleField setFont:[NSFont boldSystemFontOfSize:11]]; |
| 313 frame.size.height += |
| 314 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField: |
| 315 titleField]; |
| 316 [titleField setFrame:frame]; |
| 317 [subviews addObject:titleField.get()]; |
| 318 return NSHeight(frame); |
| 319 } |
| 320 |
| 321 // Adds the description text field at the given x,y position, and returns the y |
| 322 // position for the next element. |
| 323 - (CGFloat)addDescriptionViewForInfo:(const PageInfoModel::SectionInfo&)info |
| 324 toSubviews:(NSMutableArray*)subviews |
| 325 atPoint:(NSPoint)point { |
| 326 NSRect frame = NSMakeRect(point.x, point.y, kTextWidth, kImageSize); |
| 327 scoped_nsobject<NSTextField> textField( |
| 328 [[NSTextField alloc] initWithFrame:frame]); |
| 329 [self configureTextFieldAsLabel:textField.get()]; |
| 330 [textField setStringValue:base::SysUTF16ToNSString(info.description)]; |
| 331 [textField setFont:[NSFont labelFontOfSize:11]]; |
| 332 |
| 333 // If the text is oversized, resize the text field. |
| 334 frame.size.height += |
| 335 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField: |
| 336 textField]; |
| 337 [subviews addObject:textField.get()]; |
| 338 return NSHeight(frame); |
| 339 } |
| 340 |
| 341 // Adds the certificate button at a pre-determined x position and the given y. |
| 342 // Returns the y position for the next element. |
| 343 - (CGFloat)addCertificateButtonToSubviews:(NSMutableArray*)subviews |
| 344 atOffset:(CGFloat)offset { |
| 345 // Create the certificate button. The frame will be fixed up by GTM, so |
| 346 // use arbitrary values. |
| 347 NSRect frame = NSMakeRect(kTextXPosition, offset, 100, 20); |
| 348 NSButton* certButton = [self certificateButtonWithFrame:frame]; |
| 349 [subviews addObject:certButton]; |
| 350 [GTMUILocalizerAndLayoutTweaker sizeToFitView:certButton]; |
| 351 |
| 352 // By default, assume that we don't have certificate information to show. |
| 353 [certButton setEnabled:NO]; |
| 354 if (certID_) { |
| 355 scoped_refptr<net::X509Certificate> cert; |
| 356 CertStore::GetSharedInstance()->RetrieveCert(certID_, &cert); |
| 357 |
| 358 // Don't bother showing certificates if there isn't one. Gears runs |
| 359 // with no OS root certificate. |
| 360 if (cert.get() && cert->os_cert_handle()) { |
| 361 [certButton setEnabled:YES]; |
| 362 } |
| 363 } |
| 364 |
| 365 return NSHeight(frame) + kVerticalSpacing; |
| 366 } |
| 367 |
| 368 // Adds the state image at a pre-determined x position and the given y. This |
| 369 // does not affect the next Y position because the image is placed next to |
| 370 // a text field that is larger and accounts for the image's size. |
| 371 - (void)addImageViewForInfo:(const PageInfoModel::SectionInfo&)info |
| 372 toSubviews:(NSMutableArray*)subviews |
| 373 atOffset:(CGFloat)offset { |
| 374 NSRect frame = NSMakeRect(kFramePadding, offset - kImageSize, kImageSize, |
| 375 kImageSize); |
| 376 scoped_nsobject<NSImageView> imageView( |
| 377 [[NSImageView alloc] initWithFrame:frame]); |
| 378 [imageView setImageFrameStyle:NSImageFrameNone]; |
| 379 [imageView setImage:[self statusIconForState:info.state]]; |
| 380 [subviews addObject:imageView.get()]; |
| 381 } |
| 382 |
| 383 @end |
OLD | NEW |