1 // Copyright (c) 2012 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/ui/cocoa/constrained_window/constrained_window_alert.h" 6 7 #import "base/logging.h" 8 #import "chrome/browser/ui/chrome_style.h" 9 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h" 10 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_control_utils.h" 11 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_window.h" 12 #import "chrome/browser/ui/cocoa/hover_close_button.h" 13 #include "skia/ext/skia_utils_mac.h" 14 #include "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h" 15 #include "ui/base/cocoa/controls/hyperlink_button_cell.h" 16 #include "ui/base/cocoa/window_size_constants.h" 17 18 namespace { 19 20 const CGFloat kWindowMinWidth = 500; 21 const CGFloat kButtonGap = 6; 22 23 } // namespace 24 25 @interface ConstrainedWindowAlert () 26 // Position the alert buttons within the given window width. 27 - (void)layoutButtonsWithWindowWidth:(CGFloat)windowWidth; 28 // Resize the given text field to fit within the window and position it starting 29 // at |yPos|. Returns the new value of yPos. 30 - (CGFloat)layoutTextField:(NSTextField*)textField 31 yPos:(CGFloat)yPos 32 windowWidth:(CGFloat)windowWidth; 33 // If a link has been set, resizes the link to fit within the window and 34 // position it starting at |yPos|. Returns the new value of yPos. 35 - (CGFloat)layoutLinkAtYPos:(CGFloat)yPos 36 windowWidth:(CGFloat)windowWidth; 37 // Positions the accessory view starting at yPos. Returns the new value of yPos. 38 - (CGFloat)layoutAccessoryViewAtYPos:(CGFloat)yPos; 39 // Update the position of the close button. 40 - (void)layoutCloseButtonWithWindowWidth:(CGFloat)windowWidth 41 windowHeight:(CGFloat)windowHeight; 42 @end 43 44 @implementation ConstrainedWindowAlert 45 46 - (id)init { 47 if ((self = [super init])) { 48 window_.reset([[ConstrainedWindowCustomWindow alloc] 49 initWithContentRect:ui::kWindowSizeDeterminedLater]); 50 NSView* contentView = [window_ contentView]; 51 52 informativeTextField_.reset([constrained_window::CreateLabel() retain]); 53 [contentView addSubview:informativeTextField_]; 54 messageTextField_.reset([constrained_window::CreateLabel() retain]); 55 [contentView addSubview:messageTextField_]; 56 57 closeButton_.reset( 58 [[WebUIHoverCloseButton alloc] initWithFrame:NSZeroRect]); 59 [contentView addSubview:closeButton_]; 60 } 61 return self; 62 } 63 64 - (NSString*)informativeText { 65 return [informativeTextField_ stringValue]; 66 } 67 68 - (void)setInformativeText:(NSString*)string { 69 [informativeTextField_ setAttributedStringValue: 70 constrained_window::GetAttributedLabelString( 71 string, 72 chrome_style::kTextFontStyle, 73 NSNaturalTextAlignment, 74 NSLineBreakByWordWrapping)]; 75 } 76 77 - (NSString*)messageText { 78 return [messageTextField_ stringValue]; 79 } 80 81 - (void)setMessageText:(NSString*)string { 82 [messageTextField_ setAttributedStringValue: 83 constrained_window::GetAttributedLabelString( 84 string, 85 chrome_style::kTitleFontStyle, 86 NSNaturalTextAlignment, 87 NSLineBreakByWordWrapping)]; 88 } 89 90 - (void)setLinkText:(NSString*)text target:(id)target action:(SEL)action { 91 if (![text length]) { 92 [linkView_ removeFromSuperview]; 93 linkView_.reset(nil); 94 return; 95 } 96 97 if (!linkView_.get()) { 98 linkView_.reset( 99 [[HyperlinkButtonCell buttonWithString:[NSString string]] retain]); 100 [[window_ contentView] addSubview:linkView_]; 101 } 102 103 [linkView_ setTitle:text]; 104 [linkView_ setTarget:target]; 105 [linkView_ setAction:action]; 106 } 107 108 - (NSView*)accessoryView { 109 return accessoryView_; 110 } 111 112 - (void)setAccessoryView:(NSView*)accessoryView { 113 [accessoryView_ removeFromSuperview]; 114 accessoryView_.reset([accessoryView retain]); 115 [[window_ contentView] addSubview:accessoryView_]; 116 } 117 118 - (NSArray*)buttons { 119 return buttons_; 120 } 121 122 - (NSButton*)closeButton { 123 return closeButton_; 124 } 125 126 - (NSWindow*)window { 127 return window_; 128 } 129 130 - (void)addButtonWithTitle:(NSString*)title 131 keyEquivalent:(NSString*)keyEquivalent 132 target:(id)target 133 action:(SEL)action { 134 if (!buttons_.get()) 135 buttons_.reset([[NSMutableArray alloc] init]); 136 base::scoped_nsobject<NSButton> button( 137 [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]); 138 [button setTitle:title]; 139 [button setKeyEquivalent:keyEquivalent]; 140 [button setTarget:target]; 141 [button setAction:action]; 142 [buttons_ addObject:button]; 143 [[window_ contentView] addSubview:button]; 144 } 145 146 - (void)layout { 147 // Button width. 148 CGFloat buttonWidth = 0; 149 for (NSButton* button in buttons_.get()) { 150 [button sizeToFit]; 151 NSSize size = [button frame].size; 152 buttonWidth += size.width; 153 } 154 if ([buttons_ count]) 155 buttonWidth += ([buttons_ count] - 1) * kButtonGap; 156 157 // Window width. 158 CGFloat windowWidth = buttonWidth; 159 if (accessoryView_.get()) 160 windowWidth = std::max(windowWidth, NSWidth([accessoryView_ frame])); 161 windowWidth += chrome_style::kHorizontalPadding * 2; 162 windowWidth = std::max(windowWidth, kWindowMinWidth); 163 164 // Layout controls. 165 [self layoutButtonsWithWindowWidth:windowWidth]; 166 CGFloat curY = [buttons_ count] ? NSMaxY([[buttons_ lastObject] frame]) 167 : chrome_style::kClientBottomPadding; 168 CGFloat availableMessageWidth = 169 windowWidth - chrome_style::GetCloseButtonSize() - kButtonGap; 170 curY = [self layoutLinkAtYPos:curY 171 windowWidth:availableMessageWidth]; 172 curY = [self layoutAccessoryViewAtYPos:curY]; 173 curY = [self layoutTextField:informativeTextField_ 174 yPos:curY 175 windowWidth:windowWidth]; 176 curY = [self layoutTextField:messageTextField_ 177 yPos:curY 178 windowWidth:availableMessageWidth]; 179 180 CGFloat windowHeight = curY + chrome_style::kTitleTopPadding; 181 [self layoutCloseButtonWithWindowWidth:windowWidth 182 windowHeight:windowHeight]; 183 184 // Update window frame. 185 NSRect windowFrame = NSMakeRect(0, 0, windowWidth, windowHeight); 186 windowFrame = [window_ frameRectForContentRect:windowFrame]; 187 [window_ setFrame:windowFrame display:NO]; 188 } 189 190 - (void)layoutButtonsWithWindowWidth:(CGFloat)windowWidth { 191 // Layout first 2 button right to left. 192 CGFloat curX = windowWidth - chrome_style::kHorizontalPadding; 193 const int buttonCount = [buttons_ count]; 194 for (int i = 0; i < std::min(2, buttonCount); ++i) { 195 NSButton* button = [buttons_ objectAtIndex:i]; 196 NSRect rect = [button frame]; 197 rect.origin.x = curX - NSWidth(rect); 198 rect.origin.y = chrome_style::kClientBottomPadding; 199 [button setFrameOrigin:rect.origin]; 200 curX = NSMinX(rect) - kButtonGap; 201 } 202 203 // Layout remaining buttons left to right. 204 curX = chrome_style::kHorizontalPadding; 205 for (int i = buttonCount - 1; i >= 2; --i) { 206 NSButton* button = [buttons_ objectAtIndex:i]; 207 [button setFrameOrigin: 208 NSMakePoint(curX, chrome_style::kClientBottomPadding)]; 209 curX += NSMaxX([button frame]) + kButtonGap; 210 } 211 } 212 213 - (CGFloat)layoutTextField:(NSTextField*)textField 214 yPos:(CGFloat)yPos 215 windowWidth:(CGFloat)windowWidth { 216 if (![[textField stringValue] length]) { 217 [textField setHidden:YES]; 218 return yPos; 219 } 220 221 [textField setHidden:NO]; 222 NSRect rect; 223 rect.origin.y = yPos + chrome_style::kRowPadding; 224 rect.origin.x = chrome_style::kHorizontalPadding; 225 rect.size.width = windowWidth - 226 chrome_style::kHorizontalPadding * 2; 227 rect.size.height = 1; 228 [textField setFrame:rect]; 229 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:textField]; 230 return NSMaxY([textField frame]); 231 } 232 233 - (CGFloat)layoutLinkAtYPos:(CGFloat)yPos 234 windowWidth:(CGFloat)windowWidth { 235 if (!linkView_.get()) 236 return yPos; 237 238 NSRect availableBounds = NSMakeRect( 239 0, 240 0, 241 windowWidth - chrome_style::kHorizontalPadding * 2, 242 CGFLOAT_MAX); 243 NSSize size = [[linkView_ cell] cellSizeForBounds:availableBounds]; 244 245 NSRect rect; 246 rect.origin.y = yPos + chrome_style::kRowPadding; 247 rect.origin.x = chrome_style::kHorizontalPadding; 248 rect.size = size; 249 [linkView_ setFrame:rect]; 250 return NSMaxY([linkView_ frame]); 251 } 252 253 - (CGFloat)layoutAccessoryViewAtYPos:(CGFloat)yPos { 254 if (!accessoryView_.get()) 255 return yPos; 256 NSRect frame = [accessoryView_ frame]; 257 frame.origin.y = yPos + chrome_style::kRowPadding; 258 frame.origin.x = chrome_style::kHorizontalPadding; 259 [accessoryView_ setFrameOrigin:frame.origin]; 260 return NSMaxY(frame); 261 } 262 263 - (void)layoutCloseButtonWithWindowWidth:(CGFloat)windowWidth 264 windowHeight:(CGFloat)windowHeight { 265 NSRect frame; 266 frame.size.width = chrome_style::GetCloseButtonSize(); 267 frame.size.height = chrome_style::GetCloseButtonSize(); 268 frame.origin.x = windowWidth - 269 chrome_style::kCloseButtonPadding - NSWidth(frame); 270 frame.origin.y = windowHeight - 271 chrome_style::kCloseButtonPadding - NSHeight(frame); 272 [closeButton_ setFrame:frame]; 273 } 274 275 - (NSButton*)linkView { 276 return linkView_; 277 } 278 279 @end 280