OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "app/l10n_util_mac.h" | 5 #include "app/l10n_util_mac.h" |
6 #include "app/resource_bundle.h" | 6 #include "app/resource_bundle.h" |
7 #include "base/file_version_info.h" | |
8 #include "base/logging.h" | 7 #include "base/logging.h" |
9 #include "base/mac_util.h" | 8 #include "base/mac_util.h" |
10 #include "base/string_util.h" | 9 #include "base/string_util.h" |
11 #include "base/sys_string_conversions.h" | 10 #include "base/sys_string_conversions.h" |
12 #import "chrome/app/keystone_glue.h" | 11 #import "chrome/app/keystone_glue.h" |
13 #include "chrome/browser/browser_list.h" | 12 #include "chrome/browser/browser_list.h" |
14 #import "chrome/browser/cocoa/about_window_controller.h" | 13 #import "chrome/browser/cocoa/about_window_controller.h" |
15 #import "chrome/browser/cocoa/background_tile_view.h" | 14 #import "chrome/browser/cocoa/background_tile_view.h" |
16 #include "chrome/browser/cocoa/restart_browser.h" | 15 #include "chrome/browser/cocoa/restart_browser.h" |
17 #include "grit/chromium_strings.h" | 16 #include "grit/chromium_strings.h" |
18 #include "grit/generated_resources.h" | 17 #include "grit/generated_resources.h" |
19 #include "grit/theme_resources.h" | 18 #include "grit/theme_resources.h" |
20 #include "grit/locale_settings.h" | 19 #include "grit/locale_settings.h" |
21 #include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" | 20 #include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" |
22 | 21 |
23 NSString* const kUserClosedAboutNotification = | |
24 @"kUserClosedAboutNotification"; | |
25 | |
26 @interface AboutWindowController (Private) | |
27 - (KeystoneGlue*)defaultKeystoneGlue; | |
28 - (void)startProgressMessageID:(uint32_t)messageID; | |
29 - (void)startProgressMessage:(NSString*)message; | |
30 - (void)stopProgressMessage:(NSString*)message imageID:(uint32_t)imageID; | |
31 @end | |
32 | |
33 namespace { | 22 namespace { |
34 | 23 |
35 // Keystone doesn't give us error numbers on some results, so we just make | |
36 // our own for reporting in the UI. | |
37 const int kUpdateInstallFailed = 128; | |
38 const int kUpdateInstallFailedToStart = 129; | |
39 | |
40 void AttributedStringAppendString(NSMutableAttributedString* attr_str, | 24 void AttributedStringAppendString(NSMutableAttributedString* attr_str, |
41 NSString* str) { | 25 NSString* str) { |
42 // You might think doing [[attr_str mutableString] appendString:str] would | 26 // You might think doing [[attr_str mutableString] appendString:str] would |
43 // work, but it causes any trailing style to get extened, meaning as we | 27 // work, but it causes any trailing style to get extened, meaning as we |
44 // append links, they grow to include the new text, not what we want. | 28 // append links, they grow to include the new text, not what we want. |
45 NSAttributedString* new_attr_str = | 29 NSAttributedString* new_attr_str = |
46 [[[NSAttributedString alloc] initWithString:str] autorelease]; | 30 [[[NSAttributedString alloc] initWithString:str] autorelease]; |
47 [attr_str appendAttributedString:new_attr_str]; | 31 [attr_str appendAttributedString:new_attr_str]; |
48 } | 32 } |
49 | 33 |
50 void AttributedStringAppendHyperlink(NSMutableAttributedString* attr_str, | 34 void AttributedStringAppendHyperlink(NSMutableAttributedString* attr_str, |
51 NSString* text, NSString* url_str) { | 35 NSString* text, NSString* url_str) { |
52 | |
53 // Figure out the range of the text we're adding and add the text. | 36 // Figure out the range of the text we're adding and add the text. |
54 NSRange range = NSMakeRange([attr_str length], [text length]); | 37 NSRange range = NSMakeRange([attr_str length], [text length]); |
55 AttributedStringAppendString(attr_str, text); | 38 AttributedStringAppendString(attr_str, text); |
56 | 39 |
57 // Add the link | 40 // Add the link |
58 [attr_str addAttribute:NSLinkAttributeName value:url_str range:range]; | 41 [attr_str addAttribute:NSLinkAttributeName value:url_str range:range]; |
59 | 42 |
60 // Blue and underlined | 43 // Blue and underlined |
61 [attr_str addAttribute:NSForegroundColorAttributeName | 44 [attr_str addAttribute:NSForegroundColorAttributeName |
62 value:[NSColor blueColor] | 45 value:[NSColor blueColor] |
63 range:range]; | 46 range:range]; |
64 [attr_str addAttribute:NSUnderlineStyleAttributeName | 47 [attr_str addAttribute:NSUnderlineStyleAttributeName |
65 value:[NSNumber numberWithInt:NSSingleUnderlineStyle] | 48 value:[NSNumber numberWithInt:NSSingleUnderlineStyle] |
66 range:range]; | 49 range:range]; |
67 [attr_str addAttribute:NSCursorAttributeName | 50 [attr_str addAttribute:NSCursorAttributeName |
68 value:[NSCursor pointingHandCursor] | 51 value:[NSCursor pointingHandCursor] |
69 range:range]; | 52 range:range]; |
70 } | 53 } |
71 | 54 |
72 } // namespace | 55 } // namespace |
73 | 56 |
74 NSAttributedString* BuildAboutWindowLegalTextBlock() { | 57 @interface AboutWindowController(Private) |
| 58 |
| 59 // Launches a check for available updates. |
| 60 - (void)checkForUpdate; |
| 61 |
| 62 // Notification callback, called with the status of asynchronous |
| 63 // -checkForUpdate and -updateNow: operations. |
| 64 - (void)updateStatus:(NSNotification*)notification; |
| 65 |
| 66 // These methods maintain the image (or throbber) and text displayed regarding |
| 67 // update status. -setUpdateThrobberMessage: starts a progress throbber and |
| 68 // sets the text. -setUpdateImage:message: displays an image and sets the |
| 69 // text. |
| 70 - (void)setUpdateThrobberMessage:(NSString*)message; |
| 71 - (void)setUpdateImage:(int)imageID message:(NSString*)message; |
| 72 |
| 73 @end // @interface AboutWindowController(Private) |
| 74 |
| 75 const NSString* const kUserClosedAboutNotification = |
| 76 @"UserClosedAboutNotification"; |
| 77 |
| 78 @implementation AboutWindowController |
| 79 |
| 80 - (id)initWithProfile:(Profile*)profile { |
| 81 NSString* nibPath = [mac_util::MainAppBundle() pathForResource:@"About" |
| 82 ofType:@"nib"]; |
| 83 if ((self = [super initWithWindowNibPath:nibPath owner:self])) { |
| 84 profile_ = profile; |
| 85 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; |
| 86 [center addObserver:self |
| 87 selector:@selector(updateStatus:) |
| 88 name:kAutoupdateStatusNotification |
| 89 object:nil]; |
| 90 } |
| 91 return self; |
| 92 } |
| 93 |
| 94 - (void)dealloc { |
| 95 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 96 [super dealloc]; |
| 97 } |
| 98 |
| 99 - (void)awakeFromNib { |
| 100 NSBundle* bundle = mac_util::MainAppBundle(); |
| 101 NSString* chromeVersion = |
| 102 [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; |
| 103 |
| 104 #if defined(GOOGLE_CHROME_BUILD) |
| 105 NSString* version = chromeVersion; |
| 106 #else // GOOGLE_CHROME_BUILD |
| 107 // The format string is not localized, but this is how the displayed version |
| 108 // is built on Windows too. |
| 109 NSString* svnRevision = [bundle objectForInfoDictionaryKey:@"SVNRevision"]; |
| 110 NSString* version = |
| 111 [NSString stringWithFormat:@"%@ (%@)", chromeVersion, svnRevision]; |
| 112 #endif // GOOGLE_CHROME_BUILD |
| 113 |
| 114 [version_ setStringValue:version]; |
| 115 |
| 116 // Put the two images into the UI. |
| 117 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
| 118 NSImage* backgroundImage = rb.GetNSImageNamed(IDR_ABOUT_BACKGROUND_COLOR); |
| 119 DCHECK(backgroundImage); |
| 120 [backgroundView_ setTileImage:backgroundImage]; |
| 121 NSImage* logoImage = rb.GetNSImageNamed(IDR_ABOUT_BACKGROUND); |
| 122 DCHECK(logoImage); |
| 123 [logoView_ setImage:logoImage]; |
| 124 |
| 125 [[legalText_ textStorage] setAttributedString:[[self class] legalTextBlock]]; |
| 126 |
| 127 // Resize our text view now so that the |updateShift| below is set |
| 128 // correctly. The About box has its controls manually positioned, so we need |
| 129 // to calculate how much larger (or smaller) our text box is and store that |
| 130 // difference in |legalShift|. We do something similar with |updateShift| |
| 131 // below, which is either 0, or the amount of space to offset the window size |
| 132 // because the view that contains the update button has been removed because |
| 133 // this build doesn't have KeyStone. |
| 134 NSRect oldLegalRect = [legalBlock_ frame]; |
| 135 [legalText_ sizeToFit]; |
| 136 NSRect newRect = oldLegalRect; |
| 137 newRect.size.height = [legalText_ frame].size.height; |
| 138 [legalBlock_ setFrame:newRect]; |
| 139 CGFloat legalShift = newRect.size.height - oldLegalRect.size.height; |
| 140 |
| 141 KeystoneGlue* keystoneGlue = [KeystoneGlue defaultKeystoneGlue]; |
| 142 CGFloat updateShift; |
| 143 if (keystoneGlue) { |
| 144 NSNotification* recentNotification = [keystoneGlue recentNotification]; |
| 145 NSDictionary* recentDictionary = [recentNotification userInfo]; |
| 146 AutoupdateStatus recentStatus = static_cast<AutoupdateStatus>( |
| 147 [[recentDictionary objectForKey:kAutoupdateStatusStatus] intValue]); |
| 148 if (recentStatus == kAutoupdateInstallFailed) { |
| 149 // A previous update attempt was unsuccessful, but no About box was |
| 150 // around to report status. Use the saved notification to set up the |
| 151 // About box with the error message, and to allow another chance to |
| 152 // install the update. |
| 153 [self updateStatus:recentNotification]; |
| 154 } else { |
| 155 [self checkForUpdate]; |
| 156 } |
| 157 |
| 158 updateShift = 0.0; |
| 159 } else { |
| 160 // Hide all the update UI |
| 161 [updateBlock_ setHidden:YES]; |
| 162 |
| 163 // Figure out the amount being removed by taking out the update block |
| 164 // and its spacing. |
| 165 updateShift = NSMinY([legalBlock_ frame]) - NSMinY([updateBlock_ frame]); |
| 166 |
| 167 NSRect legalFrame = [legalBlock_ frame]; |
| 168 legalFrame.origin.y -= updateShift; |
| 169 [legalBlock_ setFrame:legalFrame]; |
| 170 } |
| 171 |
| 172 NSRect backgroundFrame = [backgroundView_ frame]; |
| 173 backgroundFrame.origin.y += legalShift - updateShift; |
| 174 [backgroundView_ setFrame:backgroundFrame]; |
| 175 |
| 176 NSSize windowDelta = NSMakeSize(0.0, legalShift - updateShift); |
| 177 |
| 178 [GTMUILocalizerAndLayoutTweaker |
| 179 resizeWindowWithoutAutoResizingSubViews:[self window] |
| 180 delta:windowDelta]; |
| 181 } |
| 182 |
| 183 - (void)windowWillClose:(NSNotification*)notification { |
| 184 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; |
| 185 [center postNotificationName:kUserClosedAboutNotification object:self]; |
| 186 } |
| 187 |
| 188 - (void)setUpdateThrobberMessage:(NSString*)message { |
| 189 [updateStatusIndicator_ setHidden:YES]; |
| 190 |
| 191 [spinner_ setHidden:NO]; |
| 192 [spinner_ startAnimation:self]; |
| 193 |
| 194 [updateText_ setStringValue:message]; |
| 195 } |
| 196 |
| 197 - (void)setUpdateImage:(int)imageID message:(NSString*)message { |
| 198 [spinner_ stopAnimation:self]; |
| 199 [spinner_ setHidden:YES]; |
| 200 |
| 201 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
| 202 NSImage* statusImage = rb.GetNSImageNamed(imageID); |
| 203 DCHECK(statusImage); |
| 204 [updateStatusIndicator_ setImage:statusImage]; |
| 205 [updateStatusIndicator_ setHidden:NO]; |
| 206 |
| 207 [updateText_ setStringValue:message]; |
| 208 } |
| 209 |
| 210 - (void)checkForUpdate { |
| 211 [self setUpdateThrobberMessage: |
| 212 l10n_util::GetNSStringWithFixup(IDS_UPGRADE_CHECK_STARTED)]; |
| 213 [[KeystoneGlue defaultKeystoneGlue] checkForUpdate]; |
| 214 |
| 215 // Upon completion, kAutoupdateStatusNotification will be posted, and |
| 216 // -updateStatus: will be called. |
| 217 } |
| 218 |
| 219 - (IBAction)updateNow:(id)sender { |
| 220 updateTriggered_ = YES; |
| 221 |
| 222 // Don't let someone click "Update Now" twice! |
| 223 [updateNowButton_ setEnabled:NO]; |
| 224 [self setUpdateThrobberMessage: |
| 225 l10n_util::GetNSStringWithFixup(IDS_UPGRADE_STARTED)]; |
| 226 [[KeystoneGlue defaultKeystoneGlue] installUpdate]; |
| 227 |
| 228 // Upon completion, kAutoupdateStatusNotification will be posted, and |
| 229 // -updateStatus: will be called. |
| 230 } |
| 231 |
| 232 - (void)updateStatus:(NSNotification*)notification { |
| 233 NSDictionary* dictionary = [notification userInfo]; |
| 234 AutoupdateStatus status = static_cast<AutoupdateStatus>( |
| 235 [[dictionary objectForKey:kAutoupdateStatusStatus] intValue]); |
| 236 |
| 237 // Don't assume |version| is a real string. It may be nil. |
| 238 NSString* version = [dictionary objectForKey:kAutoupdateStatusVersion]; |
| 239 |
| 240 int imageID; |
| 241 NSString* message; |
| 242 |
| 243 switch (status) { |
| 244 case kAutoupdateCurrent: |
| 245 imageID = IDR_UPDATE_UPTODATE; |
| 246 message = l10n_util::GetNSStringFWithFixup( |
| 247 IDS_UPGRADE_ALREADY_UP_TO_DATE, |
| 248 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), |
| 249 base::SysNSStringToUTF16(version)); |
| 250 |
| 251 break; |
| 252 |
| 253 case kAutoupdateAvailable: |
| 254 imageID = IDR_UPDATE_AVAILABLE; |
| 255 message = l10n_util::GetNSStringFWithFixup( |
| 256 IDS_UPGRADE_AVAILABLE, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); |
| 257 [updateNowButton_ setEnabled:YES]; |
| 258 |
| 259 break; |
| 260 |
| 261 case kAutoupdateInstalled: |
| 262 { |
| 263 imageID = IDR_UPDATE_UPTODATE; |
| 264 string16 productName = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME); |
| 265 if (version) { |
| 266 message = l10n_util::GetNSStringFWithFixup( |
| 267 IDS_UPGRADE_SUCCESSFUL, |
| 268 productName, |
| 269 base::SysNSStringToUTF16(version)); |
| 270 } else { |
| 271 message = l10n_util::GetNSStringFWithFixup( |
| 272 IDS_UPGRADE_SUCCESSFUL_NOVERSION, productName); |
| 273 } |
| 274 |
| 275 // TODO(mark): Turn the button in the dialog into a restart button |
| 276 // instead of springing this sheet or dialog. |
| 277 NSWindow* window = [self window]; |
| 278 NSWindow* restartDialogParent = [window isVisible] ? window : nil; |
| 279 restart_browser::RequestRestart(restartDialogParent); |
| 280 } |
| 281 |
| 282 break; |
| 283 |
| 284 case kAutoupdateInstallFailed: |
| 285 // Allow another chance. |
| 286 [updateNowButton_ setEnabled:YES]; |
| 287 |
| 288 // Fall through. |
| 289 |
| 290 case kAutoupdateCheckFailed: |
| 291 // TODO(mark): Keystone doesn't currently indicate when a check for |
| 292 // updates failed. Fix that. |
| 293 imageID = IDR_UPDATE_FAIL; |
| 294 message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR, |
| 295 IntToString16(status)); |
| 296 |
| 297 break; |
| 298 |
| 299 default: |
| 300 NOTREACHED(); |
| 301 return; |
| 302 } |
| 303 |
| 304 [self setUpdateImage:imageID message:message]; |
| 305 |
| 306 // Since the update status is now displayed in an About box, the saved state |
| 307 // can be cleared. If the About box is closed and then reopened, this will |
| 308 // let it start out with a clean slate and not be affected by past failures. |
| 309 [[KeystoneGlue defaultKeystoneGlue] clearRecentNotification]; |
| 310 } |
| 311 |
| 312 - (BOOL)textView:(NSTextView *)aTextView |
| 313 clickedOnLink:(id)link |
| 314 atIndex:(NSUInteger)charIndex { |
| 315 // We always create a new window, so there's no need to try to re-use |
| 316 // an existing one just to pass in the NEW_WINDOW disposition. |
| 317 Browser* browser = Browser::Create(profile_); |
| 318 if (browser) { |
| 319 browser->OpenURL(GURL([link UTF8String]), GURL(), NEW_WINDOW, |
| 320 PageTransition::LINK); |
| 321 } |
| 322 return YES; |
| 323 } |
| 324 |
| 325 - (NSTextView*)legalText { |
| 326 return legalText_; |
| 327 } |
| 328 |
| 329 - (NSButton*)updateButton { |
| 330 return updateNowButton_; |
| 331 } |
| 332 |
| 333 - (NSTextField*)updateText { |
| 334 return updateText_; |
| 335 } |
| 336 |
| 337 + (NSAttributedString*)legalTextBlock { |
75 // Windows builds this up in a very complex way, we're just trying to model | 338 // Windows builds this up in a very complex way, we're just trying to model |
76 // it the best we can to get all the information in (they actually do it | 339 // it the best we can to get all the information in (they actually do it |
77 // but created Labels and Links that they carefully place to make it appear | 340 // but created Labels and Links that they carefully place to make it appear |
78 // to be a paragraph of text). | 341 // to be a paragraph of text). |
79 // src/chrome/browser/views/about_chrome_view.cc AboutChromeView::Init() | 342 // src/chrome/browser/views/about_chrome_view.cc AboutChromeView::Init() |
80 | 343 |
81 NSMutableAttributedString* legal_block = | 344 NSMutableAttributedString* legal_block = |
82 [[[NSMutableAttributedString alloc] init] autorelease]; | 345 [[[NSMutableAttributedString alloc] init] autorelease]; |
83 [legal_block beginEditing]; | 346 [legal_block beginEditing]; |
84 | 347 |
85 NSString* copyright = l10n_util::GetNSString(IDS_ABOUT_VERSION_COPYRIGHT); | 348 NSString* copyright = |
| 349 l10n_util::GetNSStringWithFixup(IDS_ABOUT_VERSION_COPYRIGHT); |
86 AttributedStringAppendString(legal_block, copyright); | 350 AttributedStringAppendString(legal_block, copyright); |
87 | 351 |
88 // These are the markers directly in IDS_ABOUT_VERSION_LICENSE | 352 // These are the markers directly in IDS_ABOUT_VERSION_LICENSE |
89 NSString* kBeginLinkChr = @"BEGIN_LINK_CHR"; | 353 NSString* kBeginLinkChr = @"BEGIN_LINK_CHR"; |
90 NSString* kBeginLinkOss = @"BEGIN_LINK_OSS"; | 354 NSString* kBeginLinkOss = @"BEGIN_LINK_OSS"; |
91 NSString* kEndLinkChr = @"END_LINK_CHR"; | 355 NSString* kEndLinkChr = @"END_LINK_CHR"; |
92 NSString* kEndLinkOss = @"END_LINK_OSS"; | 356 NSString* kEndLinkOss = @"END_LINK_OSS"; |
93 // The CHR link should go to here | 357 // The CHR link should go to here |
94 NSString* kChromiumProject = l10n_util::GetNSString(IDS_CHROMIUM_PROJECT_URL); | 358 NSString* kChromiumProject = l10n_util::GetNSString(IDS_CHROMIUM_PROJECT_URL); |
95 // The OSS link should go to here | 359 // The OSS link should go to here |
96 NSString* kAcknowledgements = @"about:credits"; | 360 NSString* kAcknowledgements = @"about:credits"; |
97 | 361 |
98 // Now fetch the license string and deal with the markers | 362 // Now fetch the license string and deal with the markers |
99 | 363 |
100 NSString* license = l10n_util::GetNSString(IDS_ABOUT_VERSION_LICENSE); | 364 NSString* license = |
| 365 l10n_util::GetNSStringWithFixup(IDS_ABOUT_VERSION_LICENSE); |
101 | 366 |
102 NSRange begin_chr = [license rangeOfString:kBeginLinkChr]; | 367 NSRange begin_chr = [license rangeOfString:kBeginLinkChr]; |
103 NSRange begin_oss = [license rangeOfString:kBeginLinkOss]; | 368 NSRange begin_oss = [license rangeOfString:kBeginLinkOss]; |
104 NSRange end_chr = [license rangeOfString:kEndLinkChr]; | 369 NSRange end_chr = [license rangeOfString:kEndLinkChr]; |
105 NSRange end_oss = [license rangeOfString:kEndLinkOss]; | 370 NSRange end_oss = [license rangeOfString:kEndLinkOss]; |
106 DCHECK(begin_chr.location != NSNotFound); | 371 DCHECK(begin_chr.location != NSNotFound); |
107 DCHECK(begin_oss.location != NSNotFound); | 372 DCHECK(begin_oss.location != NSNotFound); |
108 DCHECK(end_chr.location != NSNotFound); | 373 DCHECK(end_chr.location != NSNotFound); |
109 DCHECK(end_oss.location != NSNotFound); | 374 DCHECK(end_oss.location != NSNotFound); |
110 | 375 |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
159 NSString* kTOS = @"about:terms"; | 424 NSString* kTOS = @"about:terms"; |
160 // Following Window. There is one marker in the string for where the terms | 425 // Following Window. There is one marker in the string for where the terms |
161 // link goes, but the text of the link comes from a second string resources. | 426 // link goes, but the text of the link comes from a second string resources. |
162 std::vector<size_t> url_offsets; | 427 std::vector<size_t> url_offsets; |
163 std::wstring w_about_terms = l10n_util::GetStringF(IDS_ABOUT_TERMS_OF_SERVICE, | 428 std::wstring w_about_terms = l10n_util::GetStringF(IDS_ABOUT_TERMS_OF_SERVICE, |
164 std::wstring(), | 429 std::wstring(), |
165 std::wstring(), | 430 std::wstring(), |
166 &url_offsets); | 431 &url_offsets); |
167 DCHECK_EQ(url_offsets.size(), 1U); | 432 DCHECK_EQ(url_offsets.size(), 1U); |
168 NSString* about_terms = base::SysWideToNSString(w_about_terms); | 433 NSString* about_terms = base::SysWideToNSString(w_about_terms); |
169 NSString* terms_link_text = l10n_util::GetNSString(IDS_TERMS_OF_SERVICE); | 434 NSString* terms_link_text = |
| 435 l10n_util::GetNSStringWithFixup(IDS_TERMS_OF_SERVICE); |
170 | 436 |
171 AttributedStringAppendString(legal_block, @"\n\n"); | 437 AttributedStringAppendString(legal_block, @"\n\n"); |
172 sub_str = [about_terms substringToIndex:url_offsets[0]]; | 438 sub_str = [about_terms substringToIndex:url_offsets[0]]; |
173 AttributedStringAppendString(legal_block, sub_str); | 439 AttributedStringAppendString(legal_block, sub_str); |
174 AttributedStringAppendHyperlink(legal_block, terms_link_text, kTOS); | 440 AttributedStringAppendHyperlink(legal_block, terms_link_text, kTOS); |
175 sub_str = [about_terms substringFromIndex:url_offsets[0]]; | 441 sub_str = [about_terms substringFromIndex:url_offsets[0]]; |
176 AttributedStringAppendString(legal_block, sub_str); | 442 AttributedStringAppendString(legal_block, sub_str); |
177 #endif // defined(GOOGLE_CHROME_BUILD) | 443 #endif // GOOGLE_CHROME_BUILD |
178 | 444 |
179 // We need to explicitly select Lucida Grande because once we click on | 445 // We need to explicitly select Lucida Grande because once we click on |
180 // the NSTextView, it changes to Helvetica 12 otherwise. | 446 // the NSTextView, it changes to Helvetica 12 otherwise. |
181 NSRange string_range = NSMakeRange(0, [legal_block length]); | 447 NSRange string_range = NSMakeRange(0, [legal_block length]); |
182 [legal_block addAttribute:NSFontAttributeName | 448 [legal_block addAttribute:NSFontAttributeName |
183 value:[NSFont labelFontOfSize:11] | 449 value:[NSFont labelFontOfSize:11] |
184 range:string_range]; | 450 range:string_range]; |
185 | 451 |
186 [legal_block endEditing]; | 452 [legal_block endEditing]; |
187 return legal_block; | 453 return legal_block; |
188 } | 454 } |
189 | 455 |
190 @implementation AboutWindowController | 456 @end // @implementation AboutWindowController |
191 | |
192 - (id)initWithProfile:(Profile*)profile { | |
193 NSString* nibpath = [mac_util::MainAppBundle() pathForResource:@"About" | |
194 ofType:@"nib"]; | |
195 self = [super initWithWindowNibPath:nibpath owner:self]; | |
196 if (self) { | |
197 profile_ = profile; | |
198 } | |
199 return self; | |
200 } | |
201 | |
202 - (void)awakeFromNib { | |
203 // Set our current version. | |
204 scoped_ptr<FileVersionInfo> version_info( | |
205 FileVersionInfo::CreateFileVersionInfoForCurrentModule()); | |
206 std::wstring version(version_info->product_version()); | |
207 #if !defined(GOOGLE_CHROME_BUILD) | |
208 // Yes, Windows does this raw since it is only in Chromium builds | |
209 // src/chrome/browser/views/about_chrome_view.cc AboutChromeView::Init() | |
210 version += L" ("; | |
211 version += version_info->last_change(); | |
212 version += L")"; | |
213 #endif | |
214 NSString* nsversion = base::SysWideToNSString(version); | |
215 [version_ setStringValue:nsversion]; | |
216 | |
217 // Put the two images into the ui | |
218 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
219 NSImage* backgroundImage = rb.GetNSImageNamed(IDR_ABOUT_BACKGROUND_COLOR); | |
220 DCHECK(backgroundImage); | |
221 [backgroundView_ setTileImage:backgroundImage]; | |
222 NSImage* logoImage = rb.GetNSImageNamed(IDR_ABOUT_BACKGROUND); | |
223 DCHECK(logoImage); | |
224 [logoView_ setImage:logoImage]; | |
225 | |
226 [[legalText_ textStorage] | |
227 setAttributedString:BuildAboutWindowLegalTextBlock()]; | |
228 | |
229 // Resize our text view now so that the |updateShift| below is set | |
230 // correctly. The about box has its controls manually positioned, so we need | |
231 // to calculate how much larger (or smaller) our text box is and store that | |
232 // difference in |legalShift|. We do something similar with |updateShift| | |
233 // below, which is either 0, or the amount of space to offset the window size | |
234 // because the view that contains the update button has been removed because | |
235 // this build doesn't have KeyStone. | |
236 NSRect oldLegalRect = [legalBlock_ frame]; | |
237 [legalText_ sizeToFit]; | |
238 NSRect newRect = oldLegalRect; | |
239 newRect.size.height = [legalText_ frame].size.height; | |
240 [legalBlock_ setFrame:newRect]; | |
241 CGFloat legalShift = newRect.size.height - oldLegalRect.size.height; | |
242 | |
243 KeystoneGlue* keystone = [self defaultKeystoneGlue]; | |
244 CGFloat updateShift = 0.0; | |
245 if (keystone) { | |
246 // Initiate an update check. | |
247 if ([keystone checkForUpdate:self]) { | |
248 [self startProgressMessageID:IDS_UPGRADE_CHECK_STARTED]; | |
249 } | |
250 } else { | |
251 // Hide all the update UI | |
252 [updateBlock_ setHidden:YES]; | |
253 // Figure out the amount we're removing by taking about the update block | |
254 // (and it's spacing). | |
255 updateShift = NSMinY([legalBlock_ frame]) - NSMinY([updateBlock_ frame]); | |
256 } | |
257 | |
258 // Adjust the sizes/locations. | |
259 NSRect rect = [legalBlock_ frame]; | |
260 rect.origin.y -= updateShift; | |
261 [legalBlock_ setFrame:rect]; | |
262 | |
263 rect = [backgroundView_ frame]; | |
264 rect.origin.y = rect.origin.y - updateShift + legalShift; | |
265 [backgroundView_ setFrame:rect]; | |
266 | |
267 NSSize windowDelta = NSMakeSize(0, (legalShift - updateShift)); | |
268 [GTMUILocalizerAndLayoutTweaker | |
269 resizeWindowWithoutAutoResizingSubViews:[self window] | |
270 delta:windowDelta]; | |
271 } | |
272 | |
273 - (KeystoneGlue*)defaultKeystoneGlue { | |
274 return [KeystoneGlue defaultKeystoneGlue]; | |
275 } | |
276 | |
277 - (void)startProgressMessageID:(uint32_t)messageID { | |
278 NSString* message = l10n_util::GetNSStringWithFixup(messageID); | |
279 [self startProgressMessage:message]; | |
280 } | |
281 | |
282 - (void)startProgressMessage:(NSString*)message { | |
283 [updateStatusIndicator_ setHidden:YES]; | |
284 [spinner_ setHidden:NO]; | |
285 [spinner_ startAnimation:self]; | |
286 | |
287 [updateText_ setStringValue:message]; | |
288 } | |
289 | |
290 - (void)stopProgressMessage:(NSString*)message imageID:(uint32_t)imageID { | |
291 [spinner_ stopAnimation:self]; | |
292 [spinner_ setHidden:YES]; | |
293 if (imageID) { | |
294 [updateStatusIndicator_ setHidden:NO]; | |
295 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
296 NSImage* statusImage = rb.GetNSImageNamed(imageID); | |
297 DCHECK(statusImage); | |
298 [updateStatusIndicator_ setImage:statusImage]; | |
299 } | |
300 | |
301 [updateText_ setStringValue:message]; | |
302 } | |
303 | |
304 // Callback from KeystoneGlue; implementation of KeystoneGlueCallbacks protocol. | |
305 // Warning: latest version may be nil if not set in server config. | |
306 - (void)upToDateCheckCompleted:(BOOL)updatesAvailable | |
307 latestVersion:(NSString*)latestVersion { | |
308 uint32_t imageID; | |
309 NSString* message; | |
310 if (updatesAvailable) { | |
311 newVersionAvailable_.reset([latestVersion copy]); | |
312 | |
313 // Window UI doesn't put the version number in the string. | |
314 imageID = IDR_UPDATE_AVAILABLE; | |
315 message = | |
316 l10n_util::GetNSStringF(IDS_UPGRADE_AVAILABLE, | |
317 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); | |
318 [updateNowButton_ setEnabled:YES]; | |
319 } else { | |
320 // NOTE: This is can be a lie, Keystone does not provide us with an error if | |
321 // it was not able to reach the server. So we can't completely map to the | |
322 // Windows UI. | |
323 | |
324 // Keystone does not provide the version number when we are up to date so to | |
325 // maintain the UI, we just go fetch our version and call it good. | |
326 scoped_ptr<FileVersionInfo> version_info( | |
327 FileVersionInfo::CreateFileVersionInfoForCurrentModule()); | |
328 std::wstring version(version_info->product_version()); | |
329 | |
330 // TODO: We really should check to see if what is on disk is newer then what | |
331 // is running and report it as such. (Windows has some messages that can | |
332 // help with this.) http://crbug.com/13165 | |
333 | |
334 imageID = IDR_UPDATE_UPTODATE; | |
335 message = | |
336 l10n_util::GetNSStringF(IDS_UPGRADE_ALREADY_UP_TO_DATE, | |
337 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), | |
338 WideToUTF16(version)); | |
339 } | |
340 [self stopProgressMessage:message imageID:imageID]; | |
341 } | |
342 | |
343 - (void)windowWillClose:(NSNotification*)notification { | |
344 // If an update has ever been triggered, we force reuse of the same About Box. | |
345 // This gives us 2 things: | |
346 // 1. If an update is ongoing and the window was closed we would have | |
347 // no way of getting status. | |
348 // 2. If we have a "Please restart" message we want it to stay there. | |
349 if (updateTriggered_) | |
350 return; | |
351 | |
352 [[NSNotificationCenter defaultCenter] | |
353 postNotificationName:kUserClosedAboutNotification | |
354 object:self]; | |
355 } | |
356 | |
357 // Callback from KeystoneGlue; implementation of KeystoneGlueCallbacks protocol. | |
358 - (void)updateCompleted:(BOOL)successful installs:(int)installs { | |
359 uint32_t imageID; | |
360 NSString* message; | |
361 if (successful && installs) { | |
362 imageID = IDR_UPDATE_UPTODATE; | |
363 if ([newVersionAvailable_.get() length]) { | |
364 message = | |
365 l10n_util::GetNSStringF(IDS_UPGRADE_SUCCESSFUL, | |
366 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), | |
367 base::SysNSStringToUTF16( | |
368 newVersionAvailable_.get())); | |
369 } else { | |
370 message = | |
371 l10n_util::GetNSStringF(IDS_UPGRADE_SUCCESSFUL_NOVERSION, | |
372 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); | |
373 } | |
374 | |
375 // Tell the user to restart their browser. | |
376 restart_browser::RequestRestart(nil); | |
377 | |
378 } else { | |
379 imageID = IDR_UPDATE_FAIL; | |
380 message = | |
381 l10n_util::GetNSStringF(IDS_UPGRADE_ERROR, | |
382 IntToString16(kUpdateInstallFailed)); | |
383 | |
384 // Allow a second chance. | |
385 [updateNowButton_ setEnabled:YES]; | |
386 } | |
387 | |
388 [self stopProgressMessage:message imageID:imageID]; | |
389 } | |
390 | |
391 - (IBAction)updateNow:(id)sender { | |
392 updateTriggered_ = YES; | |
393 | |
394 // Don't let someone click "Update Now" twice! | |
395 [updateNowButton_ setEnabled:NO]; | |
396 if ([[self defaultKeystoneGlue] startUpdate:self]) { | |
397 // Clear any previous error message from the throbber area. | |
398 [self startProgressMessageID:IDS_UPGRADE_STARTED]; | |
399 } else { | |
400 NSString* message = | |
401 l10n_util::GetNSStringF(IDS_UPGRADE_ERROR, | |
402 IntToString16(kUpdateInstallFailedToStart)); | |
403 [self stopProgressMessage:message imageID:IDR_UPDATE_FAIL]; | |
404 } | |
405 } | |
406 | |
407 - (BOOL)textView:(NSTextView *)aTextView | |
408 clickedOnLink:(id)link | |
409 atIndex:(NSUInteger)charIndex { | |
410 // We always create a new window, so there's no need to try to re-use | |
411 // an existing one just to pass in the NEW_WINDOW disposition. | |
412 Browser* browser = Browser::Create(profile_); | |
413 if (browser) | |
414 browser->OpenURL(GURL([link UTF8String]), GURL(), NEW_WINDOW, | |
415 PageTransition::LINK); | |
416 return YES; | |
417 } | |
418 | |
419 - (NSTextView*)legalText { | |
420 return legalText_; | |
421 } | |
422 | |
423 - (NSButton*)updateButton { | |
424 return updateNowButton_; | |
425 } | |
426 | |
427 - (NSTextField*)updateText { | |
428 return updateText_; | |
429 } | |
430 | |
431 @end | |
432 | |
OLD | NEW |