| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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/preferences_window_controller.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "app/l10n_util.h" | |
| 10 #include "app/l10n_util_mac.h" | |
| 11 #include "app/resource_bundle.h" | |
| 12 #include "base/logging.h" | |
| 13 #include "base/mac/mac_util.h" | |
| 14 #include "base/mac/scoped_aedesc.h" | |
| 15 #include "base/string16.h" | |
| 16 #include "base/string_util.h" | |
| 17 #include "base/sys_string_conversions.h" | |
| 18 #include "chrome/browser/autofill/autofill_dialog.h" | |
| 19 #include "chrome/browser/autofill/autofill_type.h" | |
| 20 #include "chrome/browser/autofill/personal_data_manager.h" | |
| 21 #include "chrome/browser/browser_process.h" | |
| 22 #include "chrome/browser/download/download_manager.h" | |
| 23 #include "chrome/browser/download/download_prefs.h" | |
| 24 #include "chrome/browser/extensions/extension_service.h" | |
| 25 #include "chrome/browser/google/google_util.h" | |
| 26 #include "chrome/browser/instant/instant_confirm_dialog.h" | |
| 27 #include "chrome/browser/instant/instant_controller.h" | |
| 28 #include "chrome/browser/metrics/metrics_service.h" | |
| 29 #include "chrome/browser/metrics/user_metrics.h" | |
| 30 #include "chrome/browser/net/url_fixer_upper.h" | |
| 31 #include "chrome/browser/policy/managed_prefs_banner_base.h" | |
| 32 #include "chrome/browser/prefs/pref_service.h" | |
| 33 #include "chrome/browser/prefs/session_startup_pref.h" | |
| 34 #include "chrome/browser/profiles/profile.h" | |
| 35 #include "chrome/browser/renderer_host/resource_dispatcher_host.h" | |
| 36 #include "chrome/browser/safe_browsing/safe_browsing_service.h" | |
| 37 #include "chrome/browser/shell_integration.h" | |
| 38 #include "chrome/browser/sync/profile_sync_service.h" | |
| 39 #include "chrome/browser/sync/sync_ui_util.h" | |
| 40 #include "chrome/browser/tab_contents/tab_contents.h" | |
| 41 #include "chrome/browser/ui/browser.h" | |
| 42 #include "chrome/browser/ui/browser_list.h" | |
| 43 #import "chrome/browser/ui/cocoa/clear_browsing_data_controller.h" | |
| 44 #import "chrome/browser/ui/cocoa/content_settings_dialog_controller.h" | |
| 45 #import "chrome/browser/ui/cocoa/custom_home_pages_model.h" | |
| 46 #import "chrome/browser/ui/cocoa/font_language_settings_controller.h" | |
| 47 #import "chrome/browser/ui/cocoa/import_settings_dialog.h" | |
| 48 #import "chrome/browser/ui/cocoa/keyword_editor_cocoa_controller.h" | |
| 49 #import "chrome/browser/ui/cocoa/l10n_util.h" | |
| 50 #import "chrome/browser/ui/cocoa/search_engine_list_model.h" | |
| 51 #import "chrome/browser/ui/cocoa/vertical_gradient_view.h" | |
| 52 #import "chrome/browser/ui/cocoa/window_size_autosaver.h" | |
| 53 #include "chrome/browser/ui/options/options_util.h" | |
| 54 #include "chrome/browser/ui/options/options_window.h" | |
| 55 #include "chrome/browser/ui/options/show_options_url.h" | |
| 56 #include "chrome/common/notification_details.h" | |
| 57 #include "chrome/common/notification_observer.h" | |
| 58 #include "chrome/common/notification_type.h" | |
| 59 #include "chrome/common/pref_names.h" | |
| 60 #include "chrome/common/url_constants.h" | |
| 61 #include "chrome/installer/util/google_update_settings.h" | |
| 62 #include "grit/chromium_strings.h" | |
| 63 #include "grit/generated_resources.h" | |
| 64 #include "grit/locale_settings.h" | |
| 65 #include "grit/theme_resources.h" | |
| 66 #import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h" | |
| 67 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" | |
| 68 | |
| 69 namespace { | |
| 70 | |
| 71 // Colors for the managed preferences warning banner. | |
| 72 static const double kBannerGradientColorTop[3] = | |
| 73 {255.0 / 255.0, 242.0 / 255.0, 183.0 / 255.0}; | |
| 74 static const double kBannerGradientColorBottom[3] = | |
| 75 {250.0 / 255.0, 230.0 / 255.0, 145.0 / 255.0}; | |
| 76 static const double kBannerStrokeColor = 135.0 / 255.0; | |
| 77 | |
| 78 // Tag id for retrieval via viewWithTag in NSView (from IB). | |
| 79 static const uint32 kBasicsStartupPageTableTag = 1000; | |
| 80 | |
| 81 bool IsNewTabUIURLString(const GURL& url) { | |
| 82 return url == GURL(chrome::kChromeUINewTabURL); | |
| 83 } | |
| 84 | |
| 85 // Helper that sizes two buttons to fit in a row keeping their spacing, returns | |
| 86 // the total horizontal size change. | |
| 87 CGFloat SizeToFitButtonPair(NSButton* leftButton, NSButton* rightButton) { | |
| 88 CGFloat widthShift = 0.0; | |
| 89 | |
| 90 NSSize delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:leftButton]; | |
| 91 DCHECK_EQ(delta.height, 0.0) << "Height changes unsupported"; | |
| 92 widthShift += delta.width; | |
| 93 | |
| 94 if (widthShift != 0.0) { | |
| 95 NSPoint origin = [rightButton frame].origin; | |
| 96 origin.x += widthShift; | |
| 97 [rightButton setFrameOrigin:origin]; | |
| 98 } | |
| 99 delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:rightButton]; | |
| 100 DCHECK_EQ(delta.height, 0.0) << "Height changes unsupported"; | |
| 101 widthShift += delta.width; | |
| 102 | |
| 103 return widthShift; | |
| 104 } | |
| 105 | |
| 106 // The different behaviors for the "pref group" auto sizing. | |
| 107 enum AutoSizeGroupBehavior { | |
| 108 kAutoSizeGroupBehaviorVerticalToFit, | |
| 109 kAutoSizeGroupBehaviorVerticalFirstToFit, | |
| 110 kAutoSizeGroupBehaviorHorizontalToFit, | |
| 111 kAutoSizeGroupBehaviorHorizontalFirstGrows, | |
| 112 kAutoSizeGroupBehaviorFirstTwoAsRowVerticalToFit | |
| 113 }; | |
| 114 | |
| 115 // Helper to tweak the layout of the "pref groups" and also ripple any height | |
| 116 // changes from one group to the next groups' origins. | |
| 117 // |views| is an ordered list of views with first being the label for the | |
| 118 // group and the rest being top down or left to right ordering of the views. | |
| 119 // The label is assumed to already be the same height as all the views it is | |
| 120 // next too. | |
| 121 CGFloat AutoSizeGroup(NSArray* views, AutoSizeGroupBehavior behavior, | |
| 122 CGFloat verticalShift) { | |
| 123 DCHECK_GE([views count], 2U) << "Should be at least a label and a control"; | |
| 124 NSTextField* label = [views objectAtIndex:0]; | |
| 125 DCHECK([label isKindOfClass:[NSTextField class]]) | |
| 126 << "First view should be the label for the group"; | |
| 127 | |
| 128 // Auto size the label to see if we need more vertical space for its localized | |
| 129 // string. | |
| 130 CGFloat labelHeightChange = | |
| 131 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:label]; | |
| 132 | |
| 133 CGFloat localVerticalShift = 0.0; | |
| 134 switch (behavior) { | |
| 135 case kAutoSizeGroupBehaviorVerticalToFit: { | |
| 136 // Walk bottom up doing the sizing and moves. | |
| 137 for (NSUInteger index = [views count] - 1; index > 0; --index) { | |
| 138 NSView* view = [views objectAtIndex:index]; | |
| 139 NSSize delta = cocoa_l10n_util::WrapOrSizeToFit(view); | |
| 140 DCHECK_GE(delta.height, 0.0) << "Should NOT shrink in height"; | |
| 141 if (localVerticalShift) { | |
| 142 NSPoint origin = [view frame].origin; | |
| 143 origin.y += localVerticalShift; | |
| 144 [view setFrameOrigin:origin]; | |
| 145 } | |
| 146 localVerticalShift += delta.height; | |
| 147 } | |
| 148 break; | |
| 149 } | |
| 150 case kAutoSizeGroupBehaviorVerticalFirstToFit: { | |
| 151 // Just size the top one. | |
| 152 NSView* view = [views objectAtIndex:1]; | |
| 153 NSSize delta = cocoa_l10n_util::WrapOrSizeToFit(view); | |
| 154 DCHECK_GE(delta.height, 0.0) << "Should NOT shrink in height"; | |
| 155 localVerticalShift += delta.height; | |
| 156 break; | |
| 157 } | |
| 158 case kAutoSizeGroupBehaviorHorizontalToFit: { | |
| 159 // Walk left to right doing the sizing and moves. | |
| 160 // NOTE: Don't worry about vertical, assume it always fits. | |
| 161 CGFloat horizontalShift = 0.0; | |
| 162 NSUInteger count = [views count]; | |
| 163 for (NSUInteger index = 1; index < count; ++index) { | |
| 164 NSView* view = [views objectAtIndex:index]; | |
| 165 NSSize delta = cocoa_l10n_util::WrapOrSizeToFit(view); | |
| 166 DCHECK_GE(delta.height, 0.0) << "Should NOT shrink in height"; | |
| 167 if (horizontalShift) { | |
| 168 NSPoint origin = [view frame].origin; | |
| 169 origin.x += horizontalShift; | |
| 170 [view setFrameOrigin:origin]; | |
| 171 } | |
| 172 horizontalShift += delta.width; | |
| 173 } | |
| 174 break; | |
| 175 } | |
| 176 case kAutoSizeGroupBehaviorHorizontalFirstGrows: { | |
| 177 // Walk right to left doing the sizing and moves, then apply the space | |
| 178 // collected into the first. | |
| 179 // NOTE: Don't worry about vertical, assume it always all fits. | |
| 180 CGFloat horizontalShift = 0.0; | |
| 181 for (NSUInteger index = [views count] - 1; index > 1; --index) { | |
| 182 NSView* view = [views objectAtIndex:index]; | |
| 183 NSSize delta = cocoa_l10n_util::WrapOrSizeToFit(view); | |
| 184 DCHECK_GE(delta.height, 0.0) << "Should NOT shrink in height"; | |
| 185 horizontalShift -= delta.width; | |
| 186 NSPoint origin = [view frame].origin; | |
| 187 origin.x += horizontalShift; | |
| 188 [view setFrameOrigin:origin]; | |
| 189 } | |
| 190 if (horizontalShift) { | |
| 191 NSView* view = [views objectAtIndex:1]; | |
| 192 NSSize delta = NSMakeSize(horizontalShift, 0.0); | |
| 193 [GTMUILocalizerAndLayoutTweaker | |
| 194 resizeViewWithoutAutoResizingSubViews:view | |
| 195 delta:delta]; | |
| 196 } | |
| 197 break; | |
| 198 } | |
| 199 case kAutoSizeGroupBehaviorFirstTwoAsRowVerticalToFit: { | |
| 200 // Start out like kAutoSizeGroupBehaviorVerticalToFit but don't do | |
| 201 // the first two. Then handle the two as a row, but apply any | |
| 202 // vertical shift. | |
| 203 // All but the first two (in the row); walk bottom up. | |
| 204 for (NSUInteger index = [views count] - 1; index > 2; --index) { | |
| 205 NSView* view = [views objectAtIndex:index]; | |
| 206 NSSize delta = cocoa_l10n_util::WrapOrSizeToFit(view); | |
| 207 DCHECK_GE(delta.height, 0.0) << "Should NOT shrink in height"; | |
| 208 if (localVerticalShift) { | |
| 209 NSPoint origin = [view frame].origin; | |
| 210 origin.y += localVerticalShift; | |
| 211 [view setFrameOrigin:origin]; | |
| 212 } | |
| 213 localVerticalShift += delta.height; | |
| 214 } | |
| 215 // Deal with the two for the horizontal row. Size the second one. | |
| 216 CGFloat horizontalShift = 0.0; | |
| 217 NSView* view = [views objectAtIndex:2]; | |
| 218 NSSize delta = cocoa_l10n_util::WrapOrSizeToFit(view); | |
| 219 DCHECK_GE(delta.height, 0.0) << "Should NOT shrink in height"; | |
| 220 horizontalShift -= delta.width; | |
| 221 NSPoint origin = [view frame].origin; | |
| 222 origin.x += horizontalShift; | |
| 223 if (localVerticalShift) { | |
| 224 origin.y += localVerticalShift; | |
| 225 } | |
| 226 [view setFrameOrigin:origin]; | |
| 227 // Now expand the first item in the row to consume the space opened up. | |
| 228 view = [views objectAtIndex:1]; | |
| 229 if (horizontalShift) { | |
| 230 NSSize delta = NSMakeSize(horizontalShift, 0.0); | |
| 231 [GTMUILocalizerAndLayoutTweaker | |
| 232 resizeViewWithoutAutoResizingSubViews:view | |
| 233 delta:delta]; | |
| 234 } | |
| 235 // And move it up by any amount needed from the previous items. | |
| 236 if (localVerticalShift) { | |
| 237 NSPoint origin = [view frame].origin; | |
| 238 origin.y += localVerticalShift; | |
| 239 [view setFrameOrigin:origin]; | |
| 240 } | |
| 241 break; | |
| 242 } | |
| 243 default: | |
| 244 NOTREACHED(); | |
| 245 break; | |
| 246 } | |
| 247 | |
| 248 // If the label grew more then the views, the other views get an extra shift. | |
| 249 // Otherwise, move the label to its top is aligned with the other views. | |
| 250 CGFloat nonLabelShift = 0.0; | |
| 251 if (labelHeightChange > localVerticalShift) { | |
| 252 // Since the lable is taller, centering the other views looks best, just | |
| 253 // shift the views by 1/2 of the size difference. | |
| 254 nonLabelShift = (labelHeightChange - localVerticalShift) / 2.0; | |
| 255 } else { | |
| 256 NSPoint origin = [label frame].origin; | |
| 257 origin.y += localVerticalShift - labelHeightChange; | |
| 258 [label setFrameOrigin:origin]; | |
| 259 } | |
| 260 | |
| 261 // Apply the input shift requested along with any the shift from label being | |
| 262 // taller then the rest of the group. | |
| 263 for (NSView* view in views) { | |
| 264 NSPoint origin = [view frame].origin; | |
| 265 origin.y += verticalShift; | |
| 266 if (view != label) { | |
| 267 origin.y += nonLabelShift; | |
| 268 } | |
| 269 [view setFrameOrigin:origin]; | |
| 270 } | |
| 271 | |
| 272 // Return how much the group grew. | |
| 273 return localVerticalShift + nonLabelShift; | |
| 274 } | |
| 275 | |
| 276 // Helper to remove a view and move everything above it down to take over the | |
| 277 // space. | |
| 278 void RemoveViewFromView(NSView* view, NSView* toRemove) { | |
| 279 // Sort bottom up so we can spin over what is above it. | |
| 280 NSArray* views = | |
| 281 [[view subviews] sortedArrayUsingFunction:cocoa_l10n_util::CompareFrameY | |
| 282 context:NULL]; | |
| 283 | |
| 284 // Find where |toRemove| was. | |
| 285 NSUInteger index = [views indexOfObject:toRemove]; | |
| 286 DCHECK_NE(index, NSNotFound); | |
| 287 NSUInteger count = [views count]; | |
| 288 CGFloat shrinkHeight = 0; | |
| 289 if (index < (count - 1)) { | |
| 290 // If we're not the topmost control, the amount to shift is the bottom of | |
| 291 // |toRemove| to the bottom of the view above it. | |
| 292 CGFloat shiftDown = | |
| 293 NSMinY([[views objectAtIndex:index + 1] frame]) - | |
| 294 NSMinY([toRemove frame]); | |
| 295 | |
| 296 // Now cycle over the views above it moving them down. | |
| 297 for (++index; index < count; ++index) { | |
| 298 NSView* view = [views objectAtIndex:index]; | |
| 299 NSPoint origin = [view frame].origin; | |
| 300 origin.y -= shiftDown; | |
| 301 [view setFrameOrigin:origin]; | |
| 302 } | |
| 303 | |
| 304 shrinkHeight = shiftDown; | |
| 305 } else if (index > 0) { | |
| 306 // If we're the topmost control, there's nothing to shift but we want to | |
| 307 // shrink until the top edge of the second-topmost control, unless it is | |
| 308 // actually higher than the topmost control (since we're sorting by the | |
| 309 // bottom edge). | |
| 310 shrinkHeight = std::max(0.f, | |
| 311 NSMaxY([toRemove frame]) - | |
| 312 NSMaxY([[views objectAtIndex:index - 1] frame])); | |
| 313 } | |
| 314 // If we only have one control, don't do any resizing (for now). | |
| 315 | |
| 316 // Remove |toRemove|. | |
| 317 [toRemove removeFromSuperview]; | |
| 318 | |
| 319 [GTMUILocalizerAndLayoutTweaker | |
| 320 resizeViewWithoutAutoResizingSubViews:view | |
| 321 delta:NSMakeSize(0, -shrinkHeight)]; | |
| 322 } | |
| 323 | |
| 324 // Simply removes all the views in |toRemove|. | |
| 325 void RemoveGroupFromView(NSView* view, NSArray* toRemove) { | |
| 326 for (NSView* viewToRemove in toRemove) { | |
| 327 RemoveViewFromView(view, viewToRemove); | |
| 328 } | |
| 329 } | |
| 330 | |
| 331 // Helper to tweak the layout of the "Under the Hood" content by autosizing all | |
| 332 // the views and moving things up vertically. Special case the two controls for | |
| 333 // download location as they are horizontal, and should fill the row. Special | |
| 334 // case "Content Settings" and "Clear browsing data" as they are horizontal as | |
| 335 // well. | |
| 336 CGFloat AutoSizeUnderTheHoodContent(NSView* view, | |
| 337 NSPathControl* downloadLocationControl, | |
| 338 NSButton* downloadLocationButton) { | |
| 339 CGFloat verticalShift = 0.0; | |
| 340 | |
| 341 // Loop bottom up through the views sizing and shifting. | |
| 342 NSArray* views = | |
| 343 [[view subviews] sortedArrayUsingFunction:cocoa_l10n_util::CompareFrameY | |
| 344 context:NULL]; | |
| 345 for (NSView* view in views) { | |
| 346 NSSize delta = cocoa_l10n_util::WrapOrSizeToFit(view); | |
| 347 DCHECK_GE(delta.height, 0.0) << "Should NOT shrink in height"; | |
| 348 if (verticalShift) { | |
| 349 NSPoint origin = [view frame].origin; | |
| 350 origin.y += verticalShift; | |
| 351 [view setFrameOrigin:origin]; | |
| 352 } | |
| 353 verticalShift += delta.height; | |
| 354 | |
| 355 // The Download Location controls go in a row with the button aligned to the | |
| 356 // right edge and the path control using all the rest of the space. | |
| 357 if (view == downloadLocationButton) { | |
| 358 NSPoint origin = [downloadLocationButton frame].origin; | |
| 359 origin.x -= delta.width; | |
| 360 [downloadLocationButton setFrameOrigin:origin]; | |
| 361 NSSize controlSize = [downloadLocationControl frame].size; | |
| 362 controlSize.width -= delta.width; | |
| 363 [downloadLocationControl setFrameSize:controlSize]; | |
| 364 } | |
| 365 } | |
| 366 | |
| 367 return verticalShift; | |
| 368 } | |
| 369 | |
| 370 } // namespace | |
| 371 | |
| 372 //------------------------------------------------------------------------- | |
| 373 | |
| 374 @interface PreferencesWindowController(Private) | |
| 375 // Callback when preferences are changed. |prefName| is the name of the | |
| 376 // pref that has changed. | |
| 377 - (void)prefChanged:(std::string*)prefName; | |
| 378 // Callback when sync state has changed. syncService_ needs to be | |
| 379 // queried to find out what happened. | |
| 380 - (void)syncStateChanged; | |
| 381 // Record the user performed a certain action and save the preferences. | |
| 382 - (void)recordUserAction:(const UserMetricsAction&) action; | |
| 383 - (void)registerPrefObservers; | |
| 384 - (void)configureInstant; | |
| 385 | |
| 386 // KVC setter methods. | |
| 387 - (void)setNewTabPageIsHomePageIndex:(NSInteger)val; | |
| 388 - (void)setHomepageURL:(NSString*)urlString; | |
| 389 - (void)setRestoreOnStartupIndex:(NSInteger)type; | |
| 390 - (void)setShowHomeButton:(BOOL)value; | |
| 391 - (void)setPasswordManagerEnabledIndex:(NSInteger)value; | |
| 392 - (void)setIsUsingDefaultTheme:(BOOL)value; | |
| 393 - (void)setShowAlternateErrorPages:(BOOL)value; | |
| 394 - (void)setUseSuggest:(BOOL)value; | |
| 395 - (void)setDnsPrefetch:(BOOL)value; | |
| 396 - (void)setSafeBrowsing:(BOOL)value; | |
| 397 - (void)setMetricsReporting:(BOOL)value; | |
| 398 - (void)setAskForSaveLocation:(BOOL)value; | |
| 399 - (void)setFileHandlerUIEnabled:(BOOL)value; | |
| 400 - (void)setTranslateEnabled:(BOOL)value; | |
| 401 - (void)setTabsToLinks:(BOOL)value; | |
| 402 - (void)displayPreferenceViewForPage:(OptionsPage)page | |
| 403 animate:(BOOL)animate; | |
| 404 - (void)resetSubViews; | |
| 405 - (void)initBannerStateForPage:(OptionsPage)page; | |
| 406 | |
| 407 // KVC getter methods. | |
| 408 - (BOOL)fileHandlerUIEnabled; | |
| 409 @end | |
| 410 | |
| 411 namespace PreferencesWindowControllerInternal { | |
| 412 | |
| 413 // A C++ class registered for changes in preferences. Bridges the | |
| 414 // notification back to the PWC. | |
| 415 class PrefObserverBridge : public NotificationObserver, | |
| 416 public ProfileSyncServiceObserver { | |
| 417 public: | |
| 418 PrefObserverBridge(PreferencesWindowController* controller) | |
| 419 : controller_(controller) {} | |
| 420 | |
| 421 virtual ~PrefObserverBridge() {} | |
| 422 | |
| 423 // Overridden from NotificationObserver: | |
| 424 virtual void Observe(NotificationType type, | |
| 425 const NotificationSource& source, | |
| 426 const NotificationDetails& details) { | |
| 427 if (type == NotificationType::PREF_CHANGED) | |
| 428 [controller_ prefChanged:Details<std::string>(details).ptr()]; | |
| 429 } | |
| 430 | |
| 431 // Overridden from ProfileSyncServiceObserver. | |
| 432 virtual void OnStateChanged() { | |
| 433 [controller_ syncStateChanged]; | |
| 434 } | |
| 435 | |
| 436 private: | |
| 437 PreferencesWindowController* controller_; // weak, owns us | |
| 438 }; | |
| 439 | |
| 440 // Tracks state for a managed prefs banner and triggers UI updates through the | |
| 441 // PreferencesWindowController as appropriate. | |
| 442 class ManagedPrefsBannerState : public policy::ManagedPrefsBannerBase { | |
| 443 public: | |
| 444 virtual ~ManagedPrefsBannerState() { } | |
| 445 | |
| 446 explicit ManagedPrefsBannerState(PreferencesWindowController* controller, | |
| 447 OptionsPage page, | |
| 448 PrefService* local_state, | |
| 449 PrefService* prefs) | |
| 450 : policy::ManagedPrefsBannerBase(local_state, prefs, page), | |
| 451 controller_(controller), | |
| 452 page_(page) { } | |
| 453 | |
| 454 BOOL IsVisible() { | |
| 455 return DetermineVisibility(); | |
| 456 } | |
| 457 | |
| 458 protected: | |
| 459 // Overridden from ManagedPrefsBannerBase. | |
| 460 virtual void OnUpdateVisibility() { | |
| 461 [controller_ switchToPage:page_ animate:YES]; | |
| 462 } | |
| 463 | |
| 464 private: | |
| 465 PreferencesWindowController* controller_; // weak, owns us | |
| 466 OptionsPage page_; // current options page | |
| 467 }; | |
| 468 | |
| 469 } // namespace PreferencesWindowControllerInternal | |
| 470 | |
| 471 @implementation PreferencesWindowController | |
| 472 | |
| 473 @synthesize restoreButtonsEnabled = restoreButtonsEnabled_; | |
| 474 @synthesize restoreURLsEnabled = restoreURLsEnabled_; | |
| 475 @synthesize showHomeButtonEnabled = showHomeButtonEnabled_; | |
| 476 @synthesize defaultSearchEngineEnabled = defaultSearchEngineEnabled_; | |
| 477 @synthesize passwordManagerChoiceEnabled = passwordManagerChoiceEnabled_; | |
| 478 @synthesize passwordManagerButtonEnabled = passwordManagerButtonEnabled_; | |
| 479 @synthesize autoFillSettingsButtonEnabled = autoFillSettingsButtonEnabled_; | |
| 480 @synthesize showAlternateErrorPagesEnabled = showAlternateErrorPagesEnabled_; | |
| 481 @synthesize useSuggestEnabled = useSuggestEnabled_; | |
| 482 @synthesize dnsPrefetchEnabled = dnsPrefetchEnabled_; | |
| 483 @synthesize safeBrowsingEnabled = safeBrowsingEnabled_; | |
| 484 @synthesize metricsReportingEnabled = metricsReportingEnabled_; | |
| 485 @synthesize proxiesConfigureButtonEnabled = proxiesConfigureButtonEnabled_; | |
| 486 | |
| 487 - (id)initWithProfile:(Profile*)profile initialPage:(OptionsPage)initialPage { | |
| 488 DCHECK(profile); | |
| 489 // Use initWithWindowNibPath:: instead of initWithWindowNibName: so we | |
| 490 // can override it in a unit test. | |
| 491 NSString* nibPath = [base::mac::MainAppBundle() | |
| 492 pathForResource:@"Preferences" | |
| 493 ofType:@"nib"]; | |
| 494 if ((self = [super initWithWindowNibPath:nibPath owner:self])) { | |
| 495 profile_ = profile->GetOriginalProfile(); | |
| 496 initialPage_ = initialPage; | |
| 497 prefs_ = profile->GetPrefs(); | |
| 498 DCHECK(prefs_); | |
| 499 observer_.reset( | |
| 500 new PreferencesWindowControllerInternal::PrefObserverBridge(self)); | |
| 501 | |
| 502 // Set up the model for the custom home page table. The KVO observation | |
| 503 // tells us when the number of items in the array changes. The normal | |
| 504 // observation tells us when one of the URLs of an item changes. | |
| 505 customPagesSource_.reset([[CustomHomePagesModel alloc] | |
| 506 initWithProfile:profile_]); | |
| 507 const SessionStartupPref startupPref = | |
| 508 SessionStartupPref::GetStartupPref(prefs_); | |
| 509 [customPagesSource_ setURLs:startupPref.urls]; | |
| 510 | |
| 511 // Set up the model for the default search popup. Register for notifications | |
| 512 // about when the model changes so we can update the selection in the view. | |
| 513 searchEngineModel_.reset( | |
| 514 [[SearchEngineListModel alloc] | |
| 515 initWithModel:profile->GetTemplateURLModel()]); | |
| 516 [[NSNotificationCenter defaultCenter] | |
| 517 addObserver:self | |
| 518 selector:@selector(searchEngineModelChanged:) | |
| 519 name:kSearchEngineListModelChangedNotification | |
| 520 object:searchEngineModel_.get()]; | |
| 521 | |
| 522 // This needs to be done before awakeFromNib: because the bindings set up | |
| 523 // in the nib rely on it. | |
| 524 [self registerPrefObservers]; | |
| 525 | |
| 526 // Use one animation so we can stop it if the user clicks quickly, and | |
| 527 // start the new animation. | |
| 528 animation_.reset([[NSViewAnimation alloc] init]); | |
| 529 // Make this the delegate so it can remove the old view at the end of the | |
| 530 // animation (once it is faded out). | |
| 531 [animation_ setDelegate:self]; | |
| 532 [animation_ setAnimationBlockingMode:NSAnimationNonblocking]; | |
| 533 | |
| 534 // TODO(akalin): handle incognito profiles? The windows version of this | |
| 535 // (in chrome/browser/ui/views/options/content_page_view.cc) just does what | |
| 536 // we do below. | |
| 537 syncService_ = profile_->GetProfileSyncService(); | |
| 538 | |
| 539 // TODO(akalin): This color is taken from kSyncLabelErrorBgColor in | |
| 540 // content_page_view.cc. Either decomp that color out into a | |
| 541 // function/variable that is referenced by both this file and | |
| 542 // content_page_view.cc, or maybe pick a more suitable color. | |
| 543 syncErrorBackgroundColor_.reset( | |
| 544 [[NSColor colorWithDeviceRed:0xff/255.0 | |
| 545 green:0x9a/255.0 | |
| 546 blue:0x9a/255.0 | |
| 547 alpha:1.0] retain]); | |
| 548 | |
| 549 // Disable the |autoFillSettingsButton_| if we have no | |
| 550 // |personalDataManager|. | |
| 551 PersonalDataManager* personalDataManager = | |
| 552 profile_->GetPersonalDataManager(); | |
| 553 [autoFillSettingsButton_ setHidden:(personalDataManager == NULL)]; | |
| 554 bool autofill_disabled_by_policy = | |
| 555 autoFillEnabled_.IsManaged() && !autoFillEnabled_.GetValue(); | |
| 556 [self setAutoFillSettingsButtonEnabled:!autofill_disabled_by_policy]; | |
| 557 [self setPasswordManagerChoiceEnabled:!askSavePasswords_.IsManaged()]; | |
| 558 [self setPasswordManagerButtonEnabled: | |
| 559 !askSavePasswords_.IsManaged() || askSavePasswords_.GetValue()]; | |
| 560 | |
| 561 // Initialize the enabled state of the elements on the general tab. | |
| 562 [self setShowHomeButtonEnabled:!showHomeButton_.IsManaged()]; | |
| 563 [self setEnabledStateOfRestoreOnStartup]; | |
| 564 [self setDefaultSearchEngineEnabled:![searchEngineModel_ isDefaultManaged]]; | |
| 565 | |
| 566 // Initialize UI state for the advanced page. | |
| 567 [self setShowAlternateErrorPagesEnabled:!alternateErrorPages_.IsManaged()]; | |
| 568 [self setUseSuggestEnabled:!useSuggest_.IsManaged()]; | |
| 569 [self setDnsPrefetchEnabled:!dnsPrefetch_.IsManaged()]; | |
| 570 [self setSafeBrowsingEnabled:!safeBrowsing_.IsManaged()]; | |
| 571 [self setMetricsReportingEnabled:!metricsReporting_.IsManaged()]; | |
| 572 proxyPrefs_.reset( | |
| 573 PrefSetObserver::CreateProxyPrefSetObserver(prefs_, observer_.get())); | |
| 574 [self setProxiesConfigureButtonEnabled:!proxyPrefs_->IsManaged()]; | |
| 575 } | |
| 576 return self; | |
| 577 } | |
| 578 | |
| 579 - (void)awakeFromNib { | |
| 580 | |
| 581 // Validate some assumptions in debug builds. | |
| 582 | |
| 583 // "Basics", "Personal Stuff", and "Under the Hood" views should be the same | |
| 584 // width. They should be the same width so they are laid out to look as good | |
| 585 // as possible at that width with controls just having to wrap if their text | |
| 586 // is too long. | |
| 587 DCHECK_EQ(NSWidth([basicsView_ frame]), NSWidth([personalStuffView_ frame])) | |
| 588 << "Basics and Personal Stuff should be the same widths"; | |
| 589 DCHECK_EQ(NSWidth([basicsView_ frame]), NSWidth([underTheHoodView_ frame])) | |
| 590 << "Basics and Under the Hood should be the same widths"; | |
| 591 // "Under the Hood" content should always be skinnier than the scroller it | |
| 592 // goes into (we resize it). | |
| 593 DCHECK_LE(NSWidth([underTheHoodContentView_ frame]), | |
| 594 [underTheHoodScroller_ contentSize].width) | |
| 595 << "The Under the Hood content should be narrower than the content " | |
| 596 "of the scroller it goes into"; | |
| 597 | |
| 598 #if !defined(GOOGLE_CHROME_BUILD) | |
| 599 // "Enable logging" (breakpad and stats) is only in Google Chrome builds, | |
| 600 // remove the checkbox and slide everything above it down. | |
| 601 RemoveViewFromView(underTheHoodContentView_, enableLoggingCheckbox_); | |
| 602 #endif // !defined(GOOGLE_CHROME_BUILD) | |
| 603 | |
| 604 // There are four problem children within the groups: | |
| 605 // Basics - Default Browser | |
| 606 // Personal Stuff - Sync | |
| 607 // Personal Stuff - Themes | |
| 608 // Personal Stuff - Browser Data | |
| 609 // These four have buttons that with some localizations are wider then the | |
| 610 // view. So the four get manually laid out before doing the general work so | |
| 611 // the views/window can be made wide enough to fit them. The layout in the | |
| 612 // general pass is a noop for these buttons (since they are already sized). | |
| 613 | |
| 614 // Size the default browser button. | |
| 615 const NSUInteger kDefaultBrowserGroupCount = 3; | |
| 616 const NSUInteger kDefaultBrowserButtonIndex = 1; | |
| 617 DCHECK_EQ([basicsGroupDefaultBrowser_ count], kDefaultBrowserGroupCount) | |
| 618 << "Expected only two items in Default Browser group"; | |
| 619 NSButton* defaultBrowserButton = | |
| 620 [basicsGroupDefaultBrowser_ objectAtIndex:kDefaultBrowserButtonIndex]; | |
| 621 NSSize defaultBrowserChange = | |
| 622 [GTMUILocalizerAndLayoutTweaker sizeToFitView:defaultBrowserButton]; | |
| 623 DCHECK_EQ(defaultBrowserChange.height, 0.0) | |
| 624 << "Button should have been right height in nib"; | |
| 625 | |
| 626 [self configureInstant]; | |
| 627 | |
| 628 // Size the sync row. | |
| 629 CGFloat syncRowChange = SizeToFitButtonPair(syncButton_, | |
| 630 syncCustomizeButton_); | |
| 631 | |
| 632 // Size the themes row. | |
| 633 const NSUInteger kThemeGroupCount = 3; | |
| 634 const NSUInteger kThemeResetButtonIndex = 1; | |
| 635 const NSUInteger kThemeThemesButtonIndex = 2; | |
| 636 DCHECK_EQ([personalStuffGroupThemes_ count], kThemeGroupCount) | |
| 637 << "Expected only two items in Themes group"; | |
| 638 CGFloat themeRowChange = SizeToFitButtonPair( | |
| 639 [personalStuffGroupThemes_ objectAtIndex:kThemeResetButtonIndex], | |
| 640 [personalStuffGroupThemes_ objectAtIndex:kThemeThemesButtonIndex]); | |
| 641 | |
| 642 // Size the Privacy and Clear buttons that make a row in Under the Hood. | |
| 643 CGFloat privacyRowChange = SizeToFitButtonPair(contentSettingsButton_, | |
| 644 clearDataButton_); | |
| 645 // Under the Hood view is narrower (then the other panes) in the nib, subtract | |
| 646 // out the amount it was already going to grow to match the other panes when | |
| 647 // calculating how much the row needs things to grow. | |
| 648 privacyRowChange -= | |
| 649 ([underTheHoodScroller_ contentSize].width - | |
| 650 NSWidth([underTheHoodContentView_ frame])); | |
| 651 | |
| 652 // Find the most any row changed in size. | |
| 653 CGFloat maxWidthChange = std::max(defaultBrowserChange.width, syncRowChange); | |
| 654 maxWidthChange = std::max(maxWidthChange, themeRowChange); | |
| 655 maxWidthChange = std::max(maxWidthChange, privacyRowChange); | |
| 656 | |
| 657 // If any grew wider, make the views wider. If they all shrank, they fit the | |
| 658 // existing view widths, so no change is needed//. | |
| 659 if (maxWidthChange > 0.0) { | |
| 660 NSSize viewSize = [basicsView_ frame].size; | |
| 661 viewSize.width += maxWidthChange; | |
| 662 [basicsView_ setFrameSize:viewSize]; | |
| 663 viewSize = [personalStuffView_ frame].size; | |
| 664 viewSize.width += maxWidthChange; | |
| 665 [personalStuffView_ setFrameSize:viewSize]; | |
| 666 } | |
| 667 | |
| 668 // Now that we have the width needed for Basics and Personal Stuff, lay out | |
| 669 // those pages bottom up making sure the strings fit and moving things up as | |
| 670 // needed. | |
| 671 | |
| 672 CGFloat newWidth = NSWidth([basicsView_ frame]); | |
| 673 CGFloat verticalShift = 0.0; | |
| 674 verticalShift += AutoSizeGroup(basicsGroupDefaultBrowser_, | |
| 675 kAutoSizeGroupBehaviorVerticalFirstToFit, | |
| 676 verticalShift); | |
| 677 verticalShift += AutoSizeGroup( | |
| 678 basicsGroupSearchEngine_, | |
| 679 kAutoSizeGroupBehaviorFirstTwoAsRowVerticalToFit, | |
| 680 verticalShift); | |
| 681 verticalShift += AutoSizeGroup(basicsGroupToolbar_, | |
| 682 kAutoSizeGroupBehaviorVerticalToFit, | |
| 683 verticalShift); | |
| 684 verticalShift += AutoSizeGroup(basicsGroupHomePage_, | |
| 685 kAutoSizeGroupBehaviorVerticalToFit, | |
| 686 verticalShift); | |
| 687 verticalShift += AutoSizeGroup(basicsGroupStartup_, | |
| 688 kAutoSizeGroupBehaviorVerticalFirstToFit, | |
| 689 verticalShift); | |
| 690 [GTMUILocalizerAndLayoutTweaker | |
| 691 resizeViewWithoutAutoResizingSubViews:basicsView_ | |
| 692 delta:NSMakeSize(0.0, verticalShift)]; | |
| 693 | |
| 694 verticalShift = 0.0; | |
| 695 verticalShift += AutoSizeGroup(personalStuffGroupThemes_, | |
| 696 kAutoSizeGroupBehaviorHorizontalToFit, | |
| 697 verticalShift); | |
| 698 verticalShift += AutoSizeGroup(personalStuffGroupBrowserData_, | |
| 699 kAutoSizeGroupBehaviorVerticalToFit, | |
| 700 verticalShift); | |
| 701 verticalShift += AutoSizeGroup(personalStuffGroupAutofill_, | |
| 702 kAutoSizeGroupBehaviorVerticalToFit, | |
| 703 verticalShift); | |
| 704 verticalShift += AutoSizeGroup(personalStuffGroupPasswords_, | |
| 705 kAutoSizeGroupBehaviorVerticalToFit, | |
| 706 verticalShift); | |
| 707 // TODO(akalin): Here we rely on the initial contents of the sync | |
| 708 // group's text field/link field to be large enough to hold all | |
| 709 // possible messages so that we don't have to re-layout when sync | |
| 710 // state changes. This isn't perfect, since e.g. some sync messages | |
| 711 // use the user's e-mail address (which may be really long), and the | |
| 712 // link field is usually not shown (leaving a big empty space). | |
| 713 // Rethink sync preferences UI for Mac. | |
| 714 verticalShift += AutoSizeGroup(personalStuffGroupSync_, | |
| 715 kAutoSizeGroupBehaviorVerticalToFit, | |
| 716 verticalShift); | |
| 717 [GTMUILocalizerAndLayoutTweaker | |
| 718 resizeViewWithoutAutoResizingSubViews:personalStuffView_ | |
| 719 delta:NSMakeSize(0.0, verticalShift)]; | |
| 720 | |
| 721 if (syncService_) { | |
| 722 syncService_->AddObserver(observer_.get()); | |
| 723 // Update the controls according to the initial state. | |
| 724 [self syncStateChanged]; | |
| 725 } else { | |
| 726 // If sync is disabled we don't want to show the sync controls at all. | |
| 727 RemoveGroupFromView(personalStuffView_, personalStuffGroupSync_); | |
| 728 } | |
| 729 | |
| 730 // Make the window as wide as the views. | |
| 731 NSWindow* prefsWindow = [self window]; | |
| 732 NSView* prefsContentView = [prefsWindow contentView]; | |
| 733 NSRect frame = [prefsContentView convertRect:[prefsWindow frame] | |
| 734 fromView:nil]; | |
| 735 frame.size.width = newWidth; | |
| 736 frame = [prefsContentView convertRect:frame toView:nil]; | |
| 737 [prefsWindow setFrame:frame display:NO]; | |
| 738 | |
| 739 // The Under the Hood prefs is a scroller, it shouldn't get any border, so it | |
| 740 // gets resized to be as wide as the window ended up. | |
| 741 NSSize underTheHoodSize = [underTheHoodView_ frame].size; | |
| 742 underTheHoodSize.width = newWidth; | |
| 743 [underTheHoodView_ setFrameSize:underTheHoodSize]; | |
| 744 | |
| 745 // Widen the Under the Hood content so things can rewrap to the full width. | |
| 746 NSSize underTheHoodContentSize = [underTheHoodContentView_ frame].size; | |
| 747 underTheHoodContentSize.width = [underTheHoodScroller_ contentSize].width; | |
| 748 [underTheHoodContentView_ setFrameSize:underTheHoodContentSize]; | |
| 749 | |
| 750 // Now that Under the Hood is the right width, auto-size to the new width to | |
| 751 // get the final height. | |
| 752 verticalShift = AutoSizeUnderTheHoodContent(underTheHoodContentView_, | |
| 753 downloadLocationControl_, | |
| 754 downloadLocationButton_); | |
| 755 [GTMUILocalizerAndLayoutTweaker | |
| 756 resizeViewWithoutAutoResizingSubViews:underTheHoodContentView_ | |
| 757 delta:NSMakeSize(0.0, verticalShift)]; | |
| 758 underTheHoodContentSize = [underTheHoodContentView_ frame].size; | |
| 759 | |
| 760 // Put the Under the Hood content view into the scroller and scroll it to the | |
| 761 // top. | |
| 762 [underTheHoodScroller_ setDocumentView:underTheHoodContentView_]; | |
| 763 [underTheHoodContentView_ scrollPoint: | |
| 764 NSMakePoint(0, underTheHoodContentSize.height)]; | |
| 765 | |
| 766 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
| 767 NSImage* alertIcon = rb.GetNativeImageNamed(IDR_WARNING); | |
| 768 DCHECK(alertIcon); | |
| 769 [managedPrefsBannerWarningImage_ setImage:alertIcon]; | |
| 770 | |
| 771 [self initBannerStateForPage:initialPage_]; | |
| 772 [self switchToPage:initialPage_ animate:NO]; | |
| 773 | |
| 774 // Save/restore position based on prefs. | |
| 775 if (g_browser_process && g_browser_process->local_state()) { | |
| 776 sizeSaver_.reset([[WindowSizeAutosaver alloc] | |
| 777 initWithWindow:[self window] | |
| 778 prefService:g_browser_process->local_state() | |
| 779 path:prefs::kPreferencesWindowPlacement]); | |
| 780 } | |
| 781 | |
| 782 // Initialize the banner gradient and stroke color. | |
| 783 NSColor* bannerStartingColor = | |
| 784 [NSColor colorWithCalibratedRed:kBannerGradientColorTop[0] | |
| 785 green:kBannerGradientColorTop[1] | |
| 786 blue:kBannerGradientColorTop[2] | |
| 787 alpha:1.0]; | |
| 788 NSColor* bannerEndingColor = | |
| 789 [NSColor colorWithCalibratedRed:kBannerGradientColorBottom[0] | |
| 790 green:kBannerGradientColorBottom[1] | |
| 791 blue:kBannerGradientColorBottom[2] | |
| 792 alpha:1.0]; | |
| 793 scoped_nsobject<NSGradient> bannerGradient( | |
| 794 [[NSGradient alloc] initWithStartingColor:bannerStartingColor | |
| 795 endingColor:bannerEndingColor]); | |
| 796 [managedPrefsBannerView_ setGradient:bannerGradient]; | |
| 797 | |
| 798 NSColor* bannerStrokeColor = | |
| 799 [NSColor colorWithCalibratedWhite:kBannerStrokeColor | |
| 800 alpha:1.0]; | |
| 801 [managedPrefsBannerView_ setStrokeColor:bannerStrokeColor]; | |
| 802 | |
| 803 // Set accessibility related attributes. | |
| 804 NSTableView* tableView = [basicsView_ viewWithTag:kBasicsStartupPageTableTag]; | |
| 805 NSString* description = | |
| 806 l10n_util::GetNSStringWithFixup(IDS_OPTIONS_STARTUP_SHOW_PAGES); | |
| 807 [tableView accessibilitySetOverrideValue:description | |
| 808 forAttribute:NSAccessibilityDescriptionAttribute]; | |
| 809 } | |
| 810 | |
| 811 - (void)dealloc { | |
| 812 if (syncService_) { | |
| 813 syncService_->RemoveObserver(observer_.get()); | |
| 814 } | |
| 815 [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
| 816 [animation_ setDelegate:nil]; | |
| 817 [animation_ stopAnimation]; | |
| 818 [super dealloc]; | |
| 819 } | |
| 820 | |
| 821 // Xcode 3.1.x version of Interface Builder doesn't do a lot for editing | |
| 822 // toolbars in XIB. So the toolbar's delegate is set to the controller so it | |
| 823 // can tell the toolbar what items are selectable. | |
| 824 - (NSArray*)toolbarSelectableItemIdentifiers:(NSToolbar*)toolbar { | |
| 825 DCHECK(toolbar == toolbar_); | |
| 826 return [[toolbar_ items] valueForKey:@"itemIdentifier"]; | |
| 827 } | |
| 828 | |
| 829 // Register our interest in the preferences we're displaying so if anything | |
| 830 // else in the UI changes them we will be updated. | |
| 831 - (void)registerPrefObservers { | |
| 832 if (!prefs_) return; | |
| 833 | |
| 834 // Basics panel | |
| 835 registrar_.Init(prefs_); | |
| 836 registrar_.Add(prefs::kURLsToRestoreOnStartup, observer_.get()); | |
| 837 restoreOnStartup_.Init(prefs::kRestoreOnStartup, prefs_, observer_.get()); | |
| 838 newTabPageIsHomePage_.Init(prefs::kHomePageIsNewTabPage, | |
| 839 prefs_, observer_.get()); | |
| 840 homepage_.Init(prefs::kHomePage, prefs_, observer_.get()); | |
| 841 showHomeButton_.Init(prefs::kShowHomeButton, prefs_, observer_.get()); | |
| 842 instantEnabled_.Init(prefs::kInstantEnabled, prefs_, observer_.get()); | |
| 843 | |
| 844 // Personal Stuff panel | |
| 845 askSavePasswords_.Init(prefs::kPasswordManagerEnabled, | |
| 846 prefs_, observer_.get()); | |
| 847 autoFillEnabled_.Init(prefs::kAutoFillEnabled, prefs_, observer_.get()); | |
| 848 currentTheme_.Init(prefs::kCurrentThemeID, prefs_, observer_.get()); | |
| 849 | |
| 850 // Under the hood panel | |
| 851 alternateErrorPages_.Init(prefs::kAlternateErrorPagesEnabled, | |
| 852 prefs_, observer_.get()); | |
| 853 useSuggest_.Init(prefs::kSearchSuggestEnabled, prefs_, observer_.get()); | |
| 854 dnsPrefetch_.Init(prefs::kDnsPrefetchingEnabled, prefs_, observer_.get()); | |
| 855 safeBrowsing_.Init(prefs::kSafeBrowsingEnabled, prefs_, observer_.get()); | |
| 856 autoOpenFiles_.Init( | |
| 857 prefs::kDownloadExtensionsToOpen, prefs_, observer_.get()); | |
| 858 translateEnabled_.Init(prefs::kEnableTranslate, prefs_, observer_.get()); | |
| 859 tabsToLinks_.Init(prefs::kWebkitTabsToLinks, prefs_, observer_.get()); | |
| 860 | |
| 861 // During unit tests, there is no local state object, so we fall back to | |
| 862 // the prefs object (where we've explicitly registered this pref so we | |
| 863 // know it's there). | |
| 864 PrefService* local = g_browser_process->local_state(); | |
| 865 if (!local) | |
| 866 local = prefs_; | |
| 867 metricsReporting_.Init(prefs::kMetricsReportingEnabled, | |
| 868 local, observer_.get()); | |
| 869 defaultDownloadLocation_.Init(prefs::kDownloadDefaultDirectory, prefs_, | |
| 870 observer_.get()); | |
| 871 askForSaveLocation_.Init(prefs::kPromptForDownload, prefs_, observer_.get()); | |
| 872 | |
| 873 // We don't need to observe changes in this value. | |
| 874 lastSelectedPage_.Init(prefs::kOptionsWindowLastTabIndex, local, NULL); | |
| 875 } | |
| 876 | |
| 877 // Called when the window wants to be closed. | |
| 878 - (BOOL)windowShouldClose:(id)sender { | |
| 879 // Stop any animation and clear the delegate to avoid stale pointers. | |
| 880 [animation_ setDelegate:nil]; | |
| 881 [animation_ stopAnimation]; | |
| 882 | |
| 883 return YES; | |
| 884 } | |
| 885 | |
| 886 // Called when the user hits the escape key. Closes the window. | |
| 887 - (void)cancel:(id)sender { | |
| 888 [[self window] performClose:self]; | |
| 889 } | |
| 890 | |
| 891 // Record the user performed a certain action and save the preferences. | |
| 892 - (void)recordUserAction:(const UserMetricsAction &)action { | |
| 893 UserMetrics::RecordAction(action, profile_); | |
| 894 if (prefs_) | |
| 895 prefs_->ScheduleSavePersistentPrefs(); | |
| 896 } | |
| 897 | |
| 898 // Returns the set of keys that |key| depends on for its value so it can be | |
| 899 // re-computed when any of those change as well. | |
| 900 + (NSSet*)keyPathsForValuesAffectingValueForKey:(NSString*)key { | |
| 901 NSSet* paths = [super keyPathsForValuesAffectingValueForKey:key]; | |
| 902 if ([key isEqualToString:@"isHomepageURLEnabled"]) { | |
| 903 paths = [paths setByAddingObject:@"newTabPageIsHomePageIndex"]; | |
| 904 paths = [paths setByAddingObject:@"homepageURL"]; | |
| 905 } else if ([key isEqualToString:@"restoreURLsEnabled"]) { | |
| 906 paths = [paths setByAddingObject:@"restoreOnStartupIndex"]; | |
| 907 } else if ([key isEqualToString:@"isHomepageChoiceEnabled"]) { | |
| 908 paths = [paths setByAddingObject:@"newTabPageIsHomePageIndex"]; | |
| 909 paths = [paths setByAddingObject:@"homepageURL"]; | |
| 910 } else if ([key isEqualToString:@"newTabPageIsHomePageIndex"]) { | |
| 911 paths = [paths setByAddingObject:@"homepageURL"]; | |
| 912 } else if ([key isEqualToString:@"hompageURL"]) { | |
| 913 paths = [paths setByAddingObject:@"newTabPageIsHomePageIndex"]; | |
| 914 } else if ([key isEqualToString:@"isDefaultBrowser"]) { | |
| 915 paths = [paths setByAddingObject:@"defaultBrowser"]; | |
| 916 } else if ([key isEqualToString:@"defaultBrowserTextColor"]) { | |
| 917 paths = [paths setByAddingObject:@"defaultBrowser"]; | |
| 918 } else if ([key isEqualToString:@"defaultBrowserText"]) { | |
| 919 paths = [paths setByAddingObject:@"defaultBrowser"]; | |
| 920 } | |
| 921 return paths; | |
| 922 } | |
| 923 | |
| 924 // Launch the Keychain Access app. | |
| 925 - (void)launchKeychainAccess { | |
| 926 NSString* const kKeychainBundleId = @"com.apple.keychainaccess"; | |
| 927 [[NSWorkspace sharedWorkspace] | |
| 928 launchAppWithBundleIdentifier:kKeychainBundleId | |
| 929 options:0L | |
| 930 additionalEventParamDescriptor:nil | |
| 931 launchIdentifier:nil]; | |
| 932 } | |
| 933 | |
| 934 //------------------------------------------------------------------------- | |
| 935 // Basics panel | |
| 936 | |
| 937 // Sets the home page preferences for kNewTabPageIsHomePage and kHomePage. If a | |
| 938 // blank or null-host URL is passed in we revert to using NewTab page | |
| 939 // as the Home page. Note: using SetValue() causes the observers not to fire, | |
| 940 // which is actually a good thing as we could end up in a state where setting | |
| 941 // the homepage to an empty url would automatically reset the prefs back to | |
| 942 // using the NTP, so we'd be never be able to change it. | |
| 943 - (void)setHomepage:(const GURL&)homepage { | |
| 944 if (IsNewTabUIURLString(homepage)) { | |
| 945 newTabPageIsHomePage_.SetValueIfNotManaged(true); | |
| 946 homepage_.SetValueIfNotManaged(std::string()); | |
| 947 } else if (!homepage.is_valid()) { | |
| 948 newTabPageIsHomePage_.SetValueIfNotManaged(true); | |
| 949 if (!homepage.has_host()) | |
| 950 homepage_.SetValueIfNotManaged(std::string()); | |
| 951 } else { | |
| 952 homepage_.SetValueIfNotManaged(homepage.spec()); | |
| 953 } | |
| 954 } | |
| 955 | |
| 956 // Callback when preferences are changed by someone modifying the prefs backend | |
| 957 // externally. |prefName| is the name of the pref that has changed. Unlike on | |
| 958 // Windows, we don't need to use this method for initializing, that's handled by | |
| 959 // Cocoa Bindings. | |
| 960 // Handles prefs for the "Basics" panel. | |
| 961 - (void)basicsPrefChanged:(std::string*)prefName { | |
| 962 if (*prefName == prefs::kRestoreOnStartup) { | |
| 963 const SessionStartupPref startupPref = | |
| 964 SessionStartupPref::GetStartupPref(prefs_); | |
| 965 [self setRestoreOnStartupIndex:startupPref.type]; | |
| 966 [self setEnabledStateOfRestoreOnStartup]; | |
| 967 } else if (*prefName == prefs::kURLsToRestoreOnStartup) { | |
| 968 [customPagesSource_ reloadURLs]; | |
| 969 [self setEnabledStateOfRestoreOnStartup]; | |
| 970 } else if (*prefName == prefs::kHomePageIsNewTabPage) { | |
| 971 NSInteger useNewTabPage = newTabPageIsHomePage_.GetValue() ? 0 : 1; | |
| 972 [self setNewTabPageIsHomePageIndex:useNewTabPage]; | |
| 973 } else if (*prefName == prefs::kHomePage) { | |
| 974 NSString* value = base::SysUTF8ToNSString(homepage_.GetValue()); | |
| 975 [self setHomepageURL:value]; | |
| 976 } else if (*prefName == prefs::kShowHomeButton) { | |
| 977 [self setShowHomeButton:showHomeButton_.GetValue() ? YES : NO]; | |
| 978 [self setShowHomeButtonEnabled:!showHomeButton_.IsManaged()]; | |
| 979 } else if (*prefName == prefs::kInstantEnabled) { | |
| 980 [self configureInstant]; | |
| 981 } | |
| 982 } | |
| 983 | |
| 984 // Returns the index of the selected cell in the "on startup" matrix based | |
| 985 // on the "restore on startup" pref. The ordering of the cells is in the | |
| 986 // same order as the pref. | |
| 987 - (NSInteger)restoreOnStartupIndex { | |
| 988 const SessionStartupPref pref = SessionStartupPref::GetStartupPref(prefs_); | |
| 989 return pref.type; | |
| 990 } | |
| 991 | |
| 992 // A helper function that takes the startup session type, grabs the URLs to | |
| 993 // restore, and saves it all in prefs. | |
| 994 - (void)saveSessionStartupWithType:(SessionStartupPref::Type)type { | |
| 995 SessionStartupPref pref; | |
| 996 pref.type = type; | |
| 997 pref.urls = [customPagesSource_.get() URLs]; | |
| 998 SessionStartupPref::SetStartupPref(prefs_, pref); | |
| 999 } | |
| 1000 | |
| 1001 // Sets the pref based on the index of the selected cell in the matrix and | |
| 1002 // marks the appropriate user metric. | |
| 1003 - (void)setRestoreOnStartupIndex:(NSInteger)type { | |
| 1004 SessionStartupPref::Type startupType = | |
| 1005 static_cast<SessionStartupPref::Type>(type); | |
| 1006 switch (startupType) { | |
| 1007 case SessionStartupPref::DEFAULT: | |
| 1008 [self recordUserAction:UserMetricsAction("Options_Startup_Homepage")]; | |
| 1009 break; | |
| 1010 case SessionStartupPref::LAST: | |
| 1011 [self recordUserAction:UserMetricsAction("Options_Startup_LastSession")]; | |
| 1012 break; | |
| 1013 case SessionStartupPref::URLS: | |
| 1014 [self recordUserAction:UserMetricsAction("Options_Startup_Custom")]; | |
| 1015 break; | |
| 1016 default: | |
| 1017 NOTREACHED(); | |
| 1018 } | |
| 1019 [self saveSessionStartupWithType:startupType]; | |
| 1020 } | |
| 1021 | |
| 1022 // Enables or disables the restoreOnStartup elements | |
| 1023 - (void) setEnabledStateOfRestoreOnStartup { | |
| 1024 const SessionStartupPref startupPref = | |
| 1025 SessionStartupPref::GetStartupPref(prefs_); | |
| 1026 [self setRestoreButtonsEnabled:!SessionStartupPref::TypeIsManaged(prefs_)]; | |
| 1027 [self setRestoreURLsEnabled:!SessionStartupPref::URLsAreManaged(prefs_) && | |
| 1028 [self restoreOnStartupIndex] == SessionStartupPref::URLS]; | |
| 1029 } | |
| 1030 | |
| 1031 // Getter for the |customPagesSource| property for bindings. | |
| 1032 - (CustomHomePagesModel*)customPagesSource { | |
| 1033 return customPagesSource_.get(); | |
| 1034 } | |
| 1035 | |
| 1036 // Called when the selection in the table changes. If a flag is set indicating | |
| 1037 // that we're waiting for a special select message, edit the cell. Otherwise | |
| 1038 // just ignore it, we don't normally care. | |
| 1039 - (void)tableViewSelectionDidChange:(NSNotification*)aNotification { | |
| 1040 if (pendingSelectForEdit_) { | |
| 1041 NSTableView* table = [aNotification object]; | |
| 1042 NSUInteger selectedRow = [table selectedRow]; | |
| 1043 [table editColumn:0 row:selectedRow withEvent:nil select:YES]; | |
| 1044 pendingSelectForEdit_ = NO; | |
| 1045 } | |
| 1046 } | |
| 1047 | |
| 1048 // Called when the user hits the (+) button for adding a new homepage to the | |
| 1049 // list. This will also attempt to make the new item editable so the user can | |
| 1050 // just start typing. | |
| 1051 - (IBAction)addHomepage:(id)sender { | |
| 1052 [customPagesArrayController_ add:sender]; | |
| 1053 | |
| 1054 // When the new item is added to the model, the array controller will select | |
| 1055 // it. We'll watch for that notification (because we are the table view's | |
| 1056 // delegate) and then make the cell editable. Note that this can't be | |
| 1057 // accomplished simply by subclassing the array controller's add method (I | |
| 1058 // did try). The update of the table is asynchronous with the controller | |
| 1059 // updating the model. | |
| 1060 pendingSelectForEdit_ = YES; | |
| 1061 } | |
| 1062 | |
| 1063 // Called when the user hits the (-) button for removing the selected items in | |
| 1064 // the homepage table. The controller does all the work. | |
| 1065 - (IBAction)removeSelectedHomepages:(id)sender { | |
| 1066 [customPagesArrayController_ remove:sender]; | |
| 1067 } | |
| 1068 | |
| 1069 // Add all entries for all open browsers with our profile. | |
| 1070 - (IBAction)useCurrentPagesAsHomepage:(id)sender { | |
| 1071 std::vector<GURL> urls; | |
| 1072 for (BrowserList::const_iterator browserIter = BrowserList::begin(); | |
| 1073 browserIter != BrowserList::end(); ++browserIter) { | |
| 1074 Browser* browser = *browserIter; | |
| 1075 if (browser->profile() != profile_) | |
| 1076 continue; // Only want entries for open profile. | |
| 1077 | |
| 1078 for (int tabIndex = 0; tabIndex < browser->tab_count(); ++tabIndex) { | |
| 1079 TabContents* tab = browser->GetTabContentsAt(tabIndex); | |
| 1080 if (tab->ShouldDisplayURL()) { | |
| 1081 const GURL url = browser->GetTabContentsAt(tabIndex)->GetURL(); | |
| 1082 if (!url.is_empty()) | |
| 1083 urls.push_back(url); | |
| 1084 } | |
| 1085 } | |
| 1086 } | |
| 1087 [customPagesSource_ setURLs:urls]; | |
| 1088 } | |
| 1089 | |
| 1090 enum { kHomepageNewTabPage, kHomepageURL }; | |
| 1091 | |
| 1092 // Here's a table describing the desired characteristics of the homepage choice | |
| 1093 // radio value, it's enabled state and the URL field enabled state. They depend | |
| 1094 // on the values of the managed bits for homepage (m_hp) and | |
| 1095 // homepageIsNewTabPage (m_ntp) preferences, as well as the value of the | |
| 1096 // homepageIsNewTabPage preference (ntp) and whether the homepage preference | |
| 1097 // is equal to the new tab page URL (hpisntp). | |
| 1098 // | |
| 1099 // m_hp m_ntp ntp hpisntp | choice value | choice enabled | URL field enabled | |
| 1100 // -------------------------------------------------------------------------- | |
| 1101 // 0 0 0 0 | homepage | 1 | 1 | |
| 1102 // 0 0 0 1 | new tab page | 1 | 0 | |
| 1103 // 0 0 1 0 | new tab page | 1 | 0 | |
| 1104 // 0 0 1 1 | new tab page | 1 | 0 | |
| 1105 // 0 1 0 0 | homepage | 0 | 1 | |
| 1106 // 0 1 0 1 | homepage | 0 | 1 | |
| 1107 // 0 1 1 0 | new tab page | 0 | 0 | |
| 1108 // 0 1 1 1 | new tab page | 0 | 0 | |
| 1109 // 1 0 0 0 | homepage | 1 | 0 | |
| 1110 // 1 0 0 1 | new tab page | 0 | 0 | |
| 1111 // 1 0 1 0 | new tab page | 1 | 0 | |
| 1112 // 1 0 1 1 | new tab page | 0 | 0 | |
| 1113 // 1 1 0 0 | homepage | 0 | 0 | |
| 1114 // 1 1 0 1 | new tab page | 0 | 0 | |
| 1115 // 1 1 1 0 | new tab page | 0 | 0 | |
| 1116 // 1 1 1 1 | new tab page | 0 | 0 | |
| 1117 // | |
| 1118 // thus, we have: | |
| 1119 // | |
| 1120 // choice value is new tab page === ntp || (hpisntp && (m_hp || !m_ntp)) | |
| 1121 // choice enabled === !m_ntp && !(m_hp && hpisntp) | |
| 1122 // URL field enabled === !ntp && !mhp && !(hpisntp && !m_ntp) | |
| 1123 // | |
| 1124 // which also make sense if you think about them. | |
| 1125 | |
| 1126 // Checks whether the homepage URL refers to the new tab page. | |
| 1127 - (BOOL)isHomepageNewTabUIURL { | |
| 1128 return IsNewTabUIURLString(GURL(homepage_.GetValue().c_str())); | |
| 1129 } | |
| 1130 | |
| 1131 // Returns the index of the selected cell in the "home page" marix based on | |
| 1132 // the "new tab is home page" pref. Sadly, the ordering is reversed from the | |
| 1133 // pref value. | |
| 1134 - (NSInteger)newTabPageIsHomePageIndex { | |
| 1135 return newTabPageIsHomePage_.GetValue() || | |
| 1136 ([self isHomepageNewTabUIURL] && | |
| 1137 (homepage_.IsManaged() || !newTabPageIsHomePage_.IsManaged())) ? | |
| 1138 kHomepageNewTabPage : kHomepageURL; | |
| 1139 } | |
| 1140 | |
| 1141 // Sets the pref based on the given index into the matrix and marks the | |
| 1142 // appropriate user metric. | |
| 1143 - (void)setNewTabPageIsHomePageIndex:(NSInteger)index { | |
| 1144 bool useNewTabPage = index == kHomepageNewTabPage ? true : false; | |
| 1145 if (useNewTabPage) { | |
| 1146 [self recordUserAction:UserMetricsAction("Options_Homepage_UseNewTab")]; | |
| 1147 } else { | |
| 1148 [self recordUserAction:UserMetricsAction("Options_Homepage_UseURL")]; | |
| 1149 if ([self isHomepageNewTabUIURL]) | |
| 1150 homepage_.SetValueIfNotManaged(std::string()); | |
| 1151 } | |
| 1152 newTabPageIsHomePage_.SetValueIfNotManaged(useNewTabPage); | |
| 1153 } | |
| 1154 | |
| 1155 // Check whether the new tab and URL homepage radios should be enabled, i.e. if | |
| 1156 // the corresponding preference is not managed through configuration policy. | |
| 1157 - (BOOL)isHomepageChoiceEnabled { | |
| 1158 return !newTabPageIsHomePage_.IsManaged() && | |
| 1159 !(homepage_.IsManaged() && [self isHomepageNewTabUIURL]); | |
| 1160 } | |
| 1161 | |
| 1162 // Returns whether or not the homepage URL text field should be enabled | |
| 1163 // based on if the new tab page is the home page. | |
| 1164 - (BOOL)isHomepageURLEnabled { | |
| 1165 return !newTabPageIsHomePage_.GetValue() && !homepage_.IsManaged() && | |
| 1166 !([self isHomepageNewTabUIURL] && !newTabPageIsHomePage_.IsManaged()); | |
| 1167 } | |
| 1168 | |
| 1169 // Returns the homepage URL. | |
| 1170 - (NSString*)homepageURL { | |
| 1171 NSString* value = base::SysUTF8ToNSString(homepage_.GetValue()); | |
| 1172 return [self isHomepageNewTabUIURL] ? nil : value; | |
| 1173 } | |
| 1174 | |
| 1175 // Sets the homepage URL to |urlString| with some fixing up. | |
| 1176 - (void)setHomepageURL:(NSString*)urlString { | |
| 1177 // If the text field contains a valid URL, sync it to prefs. We run it | |
| 1178 // through the fixer upper to allow input like "google.com" to be converted | |
| 1179 // to something valid ("http://google.com"). | |
| 1180 std::string unfixedURL = urlString ? base::SysNSStringToUTF8(urlString) : | |
| 1181 chrome::kChromeUINewTabURL; | |
| 1182 [self setHomepage:URLFixerUpper::FixupURL(unfixedURL, std::string())]; | |
| 1183 } | |
| 1184 | |
| 1185 // Returns whether the home button should be checked based on the preference. | |
| 1186 - (BOOL)showHomeButton { | |
| 1187 return showHomeButton_.GetValue() ? YES : NO; | |
| 1188 } | |
| 1189 | |
| 1190 // Sets the backend pref for whether or not the home button should be displayed | |
| 1191 // based on |value|. | |
| 1192 - (void)setShowHomeButton:(BOOL)value { | |
| 1193 if (value) | |
| 1194 [self recordUserAction:UserMetricsAction( | |
| 1195 "Options_Homepage_ShowHomeButton")]; | |
| 1196 else | |
| 1197 [self recordUserAction:UserMetricsAction( | |
| 1198 "Options_Homepage_HideHomeButton")]; | |
| 1199 showHomeButton_.SetValueIfNotManaged(value ? true : false); | |
| 1200 } | |
| 1201 | |
| 1202 // Getter for the |searchEngineModel| property for bindings. | |
| 1203 - (id)searchEngineModel { | |
| 1204 return searchEngineModel_.get(); | |
| 1205 } | |
| 1206 | |
| 1207 // Bindings for the search engine popup. We not binding directly to the model | |
| 1208 // in order to siphon off the setter so we can record the metric. If we're | |
| 1209 // doing it with one, might as well do it with both. | |
| 1210 - (NSUInteger)searchEngineSelectedIndex { | |
| 1211 return [searchEngineModel_ defaultIndex]; | |
| 1212 } | |
| 1213 | |
| 1214 - (void)setSearchEngineSelectedIndex:(NSUInteger)index { | |
| 1215 [self recordUserAction:UserMetricsAction("Options_SearchEngineChanged")]; | |
| 1216 [searchEngineModel_ setDefaultIndex:index]; | |
| 1217 } | |
| 1218 | |
| 1219 // Called when the search engine model changes. Update the selection in the | |
| 1220 // popup by tickling the bindings with the new value. | |
| 1221 - (void)searchEngineModelChanged:(NSNotification*)notify { | |
| 1222 [self setSearchEngineSelectedIndex:[self searchEngineSelectedIndex]]; | |
| 1223 [self setDefaultSearchEngineEnabled:![searchEngineModel_ isDefaultManaged]]; | |
| 1224 | |
| 1225 } | |
| 1226 | |
| 1227 - (IBAction)manageSearchEngines:(id)sender { | |
| 1228 [KeywordEditorCocoaController showKeywordEditor:profile_]; | |
| 1229 } | |
| 1230 | |
| 1231 - (IBAction)toggleInstant:(id)sender { | |
| 1232 if (instantEnabled_.GetValue()) { | |
| 1233 InstantController::Disable(profile_); | |
| 1234 } else { | |
| 1235 [instantCheckbox_ setState:NSOffState]; | |
| 1236 browser::ShowInstantConfirmDialogIfNecessary([self window], profile_); | |
| 1237 } | |
| 1238 } | |
| 1239 | |
| 1240 // Sets the state of the Instant checkbox and adds the type information to the | |
| 1241 // label. | |
| 1242 - (void)configureInstant { | |
| 1243 bool enabled = instantEnabled_.GetValue(); | |
| 1244 NSInteger state = enabled ? NSOnState : NSOffState; | |
| 1245 [instantCheckbox_ setState:state]; | |
| 1246 } | |
| 1247 | |
| 1248 - (IBAction)learnMoreAboutInstant:(id)sender { | |
| 1249 browser::ShowOptionsURL(profile_, browser::InstantLearnMoreURL()); | |
| 1250 } | |
| 1251 | |
| 1252 // Called when the user clicks the button to make Chromium the default | |
| 1253 // browser. Registers http and https. | |
| 1254 - (IBAction)makeDefaultBrowser:(id)sender { | |
| 1255 [self willChangeValueForKey:@"defaultBrowser"]; | |
| 1256 | |
| 1257 ShellIntegration::SetAsDefaultBrowser(); | |
| 1258 [self recordUserAction:UserMetricsAction("Options_SetAsDefaultBrowser")]; | |
| 1259 // If the user made Chrome the default browser, then he/she arguably wants | |
| 1260 // to be notified when that changes. | |
| 1261 prefs_->SetBoolean(prefs::kCheckDefaultBrowser, true); | |
| 1262 | |
| 1263 // Tickle KVO so that the UI updates. | |
| 1264 [self didChangeValueForKey:@"defaultBrowser"]; | |
| 1265 } | |
| 1266 | |
| 1267 // Returns the Chromium default browser state. | |
| 1268 - (ShellIntegration::DefaultBrowserState)isDefaultBrowser { | |
| 1269 return ShellIntegration::IsDefaultBrowser(); | |
| 1270 } | |
| 1271 | |
| 1272 // Returns the text color of the "chromium is your default browser" text (green | |
| 1273 // for yes, red for no). | |
| 1274 - (NSColor*)defaultBrowserTextColor { | |
| 1275 ShellIntegration::DefaultBrowserState state = [self isDefaultBrowser]; | |
| 1276 return (state == ShellIntegration::IS_DEFAULT_BROWSER) ? | |
| 1277 [NSColor colorWithCalibratedRed:0.0 green:135.0/255.0 blue:0 alpha:1.0] : | |
| 1278 [NSColor colorWithCalibratedRed:135.0/255.0 green:0 blue:0 alpha:1.0]; | |
| 1279 } | |
| 1280 | |
| 1281 // Returns the text for the "chromium is your default browser" string dependent | |
| 1282 // on if Chromium actually is or not. | |
| 1283 - (NSString*)defaultBrowserText { | |
| 1284 ShellIntegration::DefaultBrowserState state = [self isDefaultBrowser]; | |
| 1285 int stringId; | |
| 1286 if (state == ShellIntegration::IS_DEFAULT_BROWSER) | |
| 1287 stringId = IDS_OPTIONS_DEFAULTBROWSER_DEFAULT; | |
| 1288 else if (state == ShellIntegration::NOT_DEFAULT_BROWSER) | |
| 1289 stringId = IDS_OPTIONS_DEFAULTBROWSER_NOTDEFAULT; | |
| 1290 else | |
| 1291 stringId = IDS_OPTIONS_DEFAULTBROWSER_UNKNOWN; | |
| 1292 string16 text = | |
| 1293 l10n_util::GetStringFUTF16(stringId, | |
| 1294 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); | |
| 1295 return base::SysUTF16ToNSString(text); | |
| 1296 } | |
| 1297 | |
| 1298 //------------------------------------------------------------------------- | |
| 1299 // User Data panel | |
| 1300 | |
| 1301 // Since passwords and forms are radio groups, 'enabled' is index 0 and | |
| 1302 // 'disabled' is index 1. Yay. | |
| 1303 const int kEnabledIndex = 0; | |
| 1304 const int kDisabledIndex = 1; | |
| 1305 | |
| 1306 // Callback when preferences are changed. |prefName| is the name of the pref | |
| 1307 // that has changed. Unlike on Windows, we don't need to use this method for | |
| 1308 // initializing, that's handled by Cocoa Bindings. | |
| 1309 // Handles prefs for the "Personal Stuff" panel. | |
| 1310 - (void)userDataPrefChanged:(std::string*)prefName { | |
| 1311 if (*prefName == prefs::kPasswordManagerEnabled) { | |
| 1312 [self setPasswordManagerEnabledIndex:askSavePasswords_.GetValue() ? | |
| 1313 kEnabledIndex : kDisabledIndex]; | |
| 1314 [self setPasswordManagerChoiceEnabled:!askSavePasswords_.IsManaged()]; | |
| 1315 [self setPasswordManagerButtonEnabled: | |
| 1316 !askSavePasswords_.IsManaged() || askSavePasswords_.GetValue()]; | |
| 1317 } | |
| 1318 if (*prefName == prefs::kAutoFillEnabled) { | |
| 1319 bool autofill_disabled_by_policy = | |
| 1320 autoFillEnabled_.IsManaged() && !autoFillEnabled_.GetValue(); | |
| 1321 [self setAutoFillSettingsButtonEnabled:!autofill_disabled_by_policy]; | |
| 1322 } | |
| 1323 if (*prefName == prefs::kCurrentThemeID) { | |
| 1324 [self setIsUsingDefaultTheme:currentTheme_.GetValue().length() == 0]; | |
| 1325 } | |
| 1326 } | |
| 1327 | |
| 1328 // Called to launch the Keychain Access app to show the user's stored | |
| 1329 // passwords. | |
| 1330 - (IBAction)showSavedPasswords:(id)sender { | |
| 1331 [self recordUserAction:UserMetricsAction("Options_ShowPasswordsExceptions")]; | |
| 1332 [self launchKeychainAccess]; | |
| 1333 } | |
| 1334 | |
| 1335 // Called to show the Auto Fill Settings dialog. | |
| 1336 - (IBAction)showAutoFillSettings:(id)sender { | |
| 1337 [self recordUserAction:UserMetricsAction("Options_ShowAutoFillSettings")]; | |
| 1338 | |
| 1339 PersonalDataManager* personalDataManager = profile_->GetPersonalDataManager(); | |
| 1340 if (!personalDataManager) { | |
| 1341 // Should not reach here because button is disabled when | |
| 1342 // |personalDataManager| is NULL. | |
| 1343 NOTREACHED(); | |
| 1344 return; | |
| 1345 } | |
| 1346 | |
| 1347 ShowAutoFillDialog(NULL, personalDataManager, profile_); | |
| 1348 } | |
| 1349 | |
| 1350 // Called to import data from other browsers (Safari, Firefox, etc). | |
| 1351 - (IBAction)importData:(id)sender { | |
| 1352 UserMetrics::RecordAction(UserMetricsAction("Import_ShowDlg"), profile_); | |
| 1353 [ImportSettingsDialogController showImportSettingsDialogForProfile:profile_]; | |
| 1354 } | |
| 1355 | |
| 1356 - (IBAction)resetThemeToDefault:(id)sender { | |
| 1357 [self recordUserAction:UserMetricsAction("Options_ThemesReset")]; | |
| 1358 profile_->ClearTheme(); | |
| 1359 } | |
| 1360 | |
| 1361 - (IBAction)themesGallery:(id)sender { | |
| 1362 [self recordUserAction:UserMetricsAction("Options_ThemesGallery")]; | |
| 1363 Browser* browser = BrowserList::GetLastActive(); | |
| 1364 | |
| 1365 if (!browser || !browser->GetSelectedTabContents()) | |
| 1366 browser = Browser::Create(profile_); | |
| 1367 browser->OpenThemeGalleryTabAndActivate(); | |
| 1368 } | |
| 1369 | |
| 1370 // Called when the "stop syncing" confirmation dialog started by | |
| 1371 // doSyncAction is finished. Stop syncing only If the user clicked | |
| 1372 // OK. | |
| 1373 - (void)stopSyncAlertDidEnd:(NSAlert*)alert | |
| 1374 returnCode:(int)returnCode | |
| 1375 contextInfo:(void*)contextInfo { | |
| 1376 DCHECK(syncService_ && !syncService_->IsManaged()); | |
| 1377 if (returnCode == NSAlertFirstButtonReturn) { | |
| 1378 syncService_->DisableForUser(); | |
| 1379 ProfileSyncService::SyncEvent(ProfileSyncService::STOP_FROM_OPTIONS); | |
| 1380 } | |
| 1381 } | |
| 1382 | |
| 1383 // Called when the user clicks the multi-purpose sync button in the | |
| 1384 // "Personal Stuff" pane. | |
| 1385 - (IBAction)doSyncAction:(id)sender { | |
| 1386 DCHECK(syncService_ && !syncService_->IsManaged()); | |
| 1387 if (syncService_->HasSyncSetupCompleted()) { | |
| 1388 // If sync setup has completed that means the sync button was a | |
| 1389 // "stop syncing" button. Bring up a confirmation dialog before | |
| 1390 // actually stopping syncing (see stopSyncAlertDidEnd). | |
| 1391 scoped_nsobject<NSAlert> alert([[NSAlert alloc] init]); | |
| 1392 [alert addButtonWithTitle:l10n_util::GetNSStringWithFixup( | |
| 1393 IDS_SYNC_STOP_SYNCING_CONFIRM_BUTTON_LABEL)]; | |
| 1394 [alert addButtonWithTitle:l10n_util::GetNSStringWithFixup( | |
| 1395 IDS_CANCEL)]; | |
| 1396 [alert setMessageText:l10n_util::GetNSStringWithFixup( | |
| 1397 IDS_SYNC_STOP_SYNCING_DIALOG_TITLE)]; | |
| 1398 [alert setInformativeText:l10n_util::GetNSStringFWithFixup( | |
| 1399 IDS_SYNC_STOP_SYNCING_EXPLANATION_LABEL, | |
| 1400 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))]; | |
| 1401 [alert setAlertStyle:NSWarningAlertStyle]; | |
| 1402 const SEL kEndSelector = | |
| 1403 @selector(stopSyncAlertDidEnd:returnCode:contextInfo:); | |
| 1404 [alert beginSheetModalForWindow:[self window] | |
| 1405 modalDelegate:self | |
| 1406 didEndSelector:kEndSelector | |
| 1407 contextInfo:NULL]; | |
| 1408 } else { | |
| 1409 // Otherwise, the sync button was a "sync my bookmarks" button. | |
| 1410 // Kick off the sync setup process. | |
| 1411 syncService_->ShowLoginDialog(NULL); | |
| 1412 ProfileSyncService::SyncEvent(ProfileSyncService::START_FROM_OPTIONS); | |
| 1413 } | |
| 1414 } | |
| 1415 | |
| 1416 // Called when the user clicks on the link to the privacy dashboard. | |
| 1417 - (IBAction)showPrivacyDashboard:(id)sender { | |
| 1418 Browser* browser = BrowserList::GetLastActive(); | |
| 1419 | |
| 1420 if (!browser || !browser->GetSelectedTabContents()) | |
| 1421 browser = Browser::Create(profile_); | |
| 1422 browser->OpenPrivacyDashboardTabAndActivate(); | |
| 1423 } | |
| 1424 | |
| 1425 // Called when the user clicks the "Customize Sync" button in the | |
| 1426 // "Personal Stuff" pane. Spawns a dialog-modal sheet that cleans | |
| 1427 // itself up on close. | |
| 1428 - (IBAction)doSyncCustomize:(id)sender { | |
| 1429 syncService_->ShowConfigure(NULL); | |
| 1430 } | |
| 1431 | |
| 1432 - (IBAction)doSyncReauthentication:(id)sender { | |
| 1433 DCHECK(syncService_ && !syncService_->IsManaged()); | |
| 1434 syncService_->ShowLoginDialog(NULL); | |
| 1435 } | |
| 1436 | |
| 1437 - (void)setPasswordManagerEnabledIndex:(NSInteger)value { | |
| 1438 if (value == kEnabledIndex) | |
| 1439 [self recordUserAction:UserMetricsAction( | |
| 1440 "Options_PasswordManager_Enable")]; | |
| 1441 else | |
| 1442 [self recordUserAction:UserMetricsAction( | |
| 1443 "Options_PasswordManager_Disable")]; | |
| 1444 askSavePasswords_.SetValueIfNotManaged(value == kEnabledIndex ? true : false); | |
| 1445 } | |
| 1446 | |
| 1447 - (NSInteger)passwordManagerEnabledIndex { | |
| 1448 return askSavePasswords_.GetValue() ? kEnabledIndex : kDisabledIndex; | |
| 1449 } | |
| 1450 | |
| 1451 - (void)setIsUsingDefaultTheme:(BOOL)value { | |
| 1452 if (value) | |
| 1453 [self recordUserAction:UserMetricsAction( | |
| 1454 "Options_IsUsingDefaultTheme_Enable")]; | |
| 1455 else | |
| 1456 [self recordUserAction:UserMetricsAction( | |
| 1457 "Options_IsUsingDefaultTheme_Disable")]; | |
| 1458 } | |
| 1459 | |
| 1460 - (BOOL)isUsingDefaultTheme { | |
| 1461 return currentTheme_.GetValue().length() == 0; | |
| 1462 } | |
| 1463 | |
| 1464 //------------------------------------------------------------------------- | |
| 1465 // Under the hood panel | |
| 1466 | |
| 1467 // Callback when preferences are changed. |prefName| is the name of the pref | |
| 1468 // that has changed. Unlike on Windows, we don't need to use this method for | |
| 1469 // initializing, that's handled by Cocoa Bindings. | |
| 1470 // Handles prefs for the "Under the hood" panel. | |
| 1471 - (void)underHoodPrefChanged:(std::string*)prefName { | |
| 1472 if (*prefName == prefs::kAlternateErrorPagesEnabled) { | |
| 1473 [self setShowAlternateErrorPages: | |
| 1474 alternateErrorPages_.GetValue() ? YES : NO]; | |
| 1475 [self setShowAlternateErrorPagesEnabled:!alternateErrorPages_.IsManaged()]; | |
| 1476 } | |
| 1477 else if (*prefName == prefs::kSearchSuggestEnabled) { | |
| 1478 [self setUseSuggest:useSuggest_.GetValue() ? YES : NO]; | |
| 1479 [self setUseSuggestEnabled:!useSuggest_.IsManaged()]; | |
| 1480 } | |
| 1481 else if (*prefName == prefs::kDnsPrefetchingEnabled) { | |
| 1482 [self setDnsPrefetch:dnsPrefetch_.GetValue() ? YES : NO]; | |
| 1483 [self setDnsPrefetchEnabled:!dnsPrefetch_.IsManaged()]; | |
| 1484 } | |
| 1485 else if (*prefName == prefs::kSafeBrowsingEnabled) { | |
| 1486 [self setSafeBrowsing:safeBrowsing_.GetValue() ? YES : NO]; | |
| 1487 [self setSafeBrowsingEnabled:!safeBrowsing_.IsManaged()]; | |
| 1488 } | |
| 1489 else if (*prefName == prefs::kMetricsReportingEnabled) { | |
| 1490 [self setMetricsReporting:metricsReporting_.GetValue() ? YES : NO]; | |
| 1491 [self setMetricsReportingEnabled:!metricsReporting_.IsManaged()]; | |
| 1492 } | |
| 1493 else if (*prefName == prefs::kDownloadDefaultDirectory) { | |
| 1494 // Poke KVO. | |
| 1495 [self willChangeValueForKey:@"defaultDownloadLocation"]; | |
| 1496 [self didChangeValueForKey:@"defaultDownloadLocation"]; | |
| 1497 } | |
| 1498 else if (*prefName == prefs::kPromptForDownload) { | |
| 1499 [self setAskForSaveLocation:askForSaveLocation_.GetValue() ? YES : NO]; | |
| 1500 } | |
| 1501 else if (*prefName == prefs::kEnableTranslate) { | |
| 1502 [self setTranslateEnabled:translateEnabled_.GetValue() ? YES : NO]; | |
| 1503 } | |
| 1504 else if (*prefName == prefs::kWebkitTabsToLinks) { | |
| 1505 [self setTabsToLinks:tabsToLinks_.GetValue() ? YES : NO]; | |
| 1506 } | |
| 1507 else if (*prefName == prefs::kDownloadExtensionsToOpen) { | |
| 1508 // Poke KVC. | |
| 1509 [self setFileHandlerUIEnabled:[self fileHandlerUIEnabled]]; | |
| 1510 } | |
| 1511 else if (proxyPrefs_->IsObserved(*prefName)) { | |
| 1512 [self setProxiesConfigureButtonEnabled:!proxyPrefs_->IsManaged()]; | |
| 1513 } | |
| 1514 } | |
| 1515 | |
| 1516 // Set the new download path and notify the UI via KVO. | |
| 1517 - (void)downloadPathPanelDidEnd:(NSOpenPanel*)panel | |
| 1518 code:(NSInteger)returnCode | |
| 1519 context:(void*)context { | |
| 1520 if (returnCode == NSOKButton) { | |
| 1521 [self recordUserAction:UserMetricsAction("Options_SetDownloadDirectory")]; | |
| 1522 NSURL* path = [[panel URLs] lastObject]; // We only allow 1 item. | |
| 1523 [self willChangeValueForKey:@"defaultDownloadLocation"]; | |
| 1524 defaultDownloadLocation_.SetValue(base::SysNSStringToUTF8([path path])); | |
| 1525 [self didChangeValueForKey:@"defaultDownloadLocation"]; | |
| 1526 } | |
| 1527 } | |
| 1528 | |
| 1529 // Bring up an open panel to allow the user to set a new downloads location. | |
| 1530 - (void)browseDownloadLocation:(id)sender { | |
| 1531 NSOpenPanel* panel = [NSOpenPanel openPanel]; | |
| 1532 [panel setAllowsMultipleSelection:NO]; | |
| 1533 [panel setCanChooseFiles:NO]; | |
| 1534 [panel setCanChooseDirectories:YES]; | |
| 1535 NSString* path = base::SysUTF8ToNSString(defaultDownloadLocation_.GetValue()); | |
| 1536 [panel beginSheetForDirectory:path | |
| 1537 file:nil | |
| 1538 types:nil | |
| 1539 modalForWindow:[self window] | |
| 1540 modalDelegate:self | |
| 1541 didEndSelector:@selector(downloadPathPanelDidEnd:code:context:) | |
| 1542 contextInfo:NULL]; | |
| 1543 } | |
| 1544 | |
| 1545 // Called to clear user's browsing data. This puts up an application-modal | |
| 1546 // dialog to guide the user through clearing the data. | |
| 1547 - (IBAction)clearData:(id)sender { | |
| 1548 [ClearBrowsingDataController | |
| 1549 showClearBrowsingDialogForProfile:profile_]; | |
| 1550 } | |
| 1551 | |
| 1552 // Opens the "Content Settings" dialog. | |
| 1553 - (IBAction)showContentSettings:(id)sender { | |
| 1554 [ContentSettingsDialogController | |
| 1555 showContentSettingsForType:CONTENT_SETTINGS_TYPE_DEFAULT | |
| 1556 profile:profile_]; | |
| 1557 } | |
| 1558 | |
| 1559 - (IBAction)privacyLearnMore:(id)sender { | |
| 1560 GURL url = google_util::AppendGoogleLocaleParam( | |
| 1561 GURL(chrome::kPrivacyLearnMoreURL)); | |
| 1562 // We open a new browser window so the Options dialog doesn't get lost | |
| 1563 // behind other windows. | |
| 1564 browser::ShowOptionsURL(profile_, url); | |
| 1565 } | |
| 1566 | |
| 1567 - (IBAction)resetAutoOpenFiles:(id)sender { | |
| 1568 profile_->GetDownloadManager()->download_prefs()->ResetAutoOpen(); | |
| 1569 [self recordUserAction:UserMetricsAction("Options_ResetAutoOpenFiles")]; | |
| 1570 } | |
| 1571 | |
| 1572 - (IBAction)openProxyPreferences:(id)sender { | |
| 1573 NSArray* itemsToOpen = [NSArray arrayWithObject:[NSURL fileURLWithPath: | |
| 1574 @"/System/Library/PreferencePanes/Network.prefPane"]]; | |
| 1575 | |
| 1576 const char* proxyPrefCommand = "Proxies"; | |
| 1577 base::mac::ScopedAEDesc<> openParams; | |
| 1578 OSStatus status = AECreateDesc('ptru', | |
| 1579 proxyPrefCommand, | |
| 1580 strlen(proxyPrefCommand), | |
| 1581 openParams.OutPointer()); | |
| 1582 LOG_IF(ERROR, status != noErr) << "Failed to create open params: " << status; | |
| 1583 | |
| 1584 LSLaunchURLSpec launchSpec = { 0 }; | |
| 1585 launchSpec.itemURLs = (CFArrayRef)itemsToOpen; | |
| 1586 launchSpec.passThruParams = openParams; | |
| 1587 launchSpec.launchFlags = kLSLaunchAsync | kLSLaunchDontAddToRecents; | |
| 1588 LSOpenFromURLSpec(&launchSpec, NULL); | |
| 1589 } | |
| 1590 | |
| 1591 // Returns whether the alternate error page checkbox should be checked based | |
| 1592 // on the preference. | |
| 1593 - (BOOL)showAlternateErrorPages { | |
| 1594 return alternateErrorPages_.GetValue() ? YES : NO; | |
| 1595 } | |
| 1596 | |
| 1597 // Sets the backend pref for whether or not the alternate error page checkbox | |
| 1598 // should be displayed based on |value|. | |
| 1599 - (void)setShowAlternateErrorPages:(BOOL)value { | |
| 1600 if (value) | |
| 1601 [self recordUserAction:UserMetricsAction( | |
| 1602 "Options_LinkDoctorCheckbox_Enable")]; | |
| 1603 else | |
| 1604 [self recordUserAction:UserMetricsAction( | |
| 1605 "Options_LinkDoctorCheckbox_Disable")]; | |
| 1606 alternateErrorPages_.SetValueIfNotManaged(value ? true : false); | |
| 1607 } | |
| 1608 | |
| 1609 // Returns whether the suggest checkbox should be checked based on the | |
| 1610 // preference. | |
| 1611 - (BOOL)useSuggest { | |
| 1612 return useSuggest_.GetValue() ? YES : NO; | |
| 1613 } | |
| 1614 | |
| 1615 // Sets the backend pref for whether or not the suggest checkbox should be | |
| 1616 // displayed based on |value|. | |
| 1617 - (void)setUseSuggest:(BOOL)value { | |
| 1618 if (value) | |
| 1619 [self recordUserAction:UserMetricsAction( | |
| 1620 "Options_UseSuggestCheckbox_Enable")]; | |
| 1621 else | |
| 1622 [self recordUserAction:UserMetricsAction( | |
| 1623 "Options_UseSuggestCheckbox_Disable")]; | |
| 1624 useSuggest_.SetValueIfNotManaged(value ? true : false); | |
| 1625 } | |
| 1626 | |
| 1627 // Returns whether the DNS prefetch checkbox should be checked based on the | |
| 1628 // preference. | |
| 1629 - (BOOL)dnsPrefetch { | |
| 1630 return dnsPrefetch_.GetValue() ? YES : NO; | |
| 1631 } | |
| 1632 | |
| 1633 // Sets the backend pref for whether or not the DNS prefetch checkbox should be | |
| 1634 // displayed based on |value|. | |
| 1635 - (void)setDnsPrefetch:(BOOL)value { | |
| 1636 if (value) | |
| 1637 [self recordUserAction:UserMetricsAction( | |
| 1638 "Options_DnsPrefetchCheckbox_Enable")]; | |
| 1639 else | |
| 1640 [self recordUserAction:UserMetricsAction( | |
| 1641 "Options_DnsPrefetchCheckbox_Disable")]; | |
| 1642 dnsPrefetch_.SetValueIfNotManaged(value ? true : false); | |
| 1643 } | |
| 1644 | |
| 1645 // Returns whether the safe browsing checkbox should be checked based on the | |
| 1646 // preference. | |
| 1647 - (BOOL)safeBrowsing { | |
| 1648 return safeBrowsing_.GetValue() ? YES : NO; | |
| 1649 } | |
| 1650 | |
| 1651 // Sets the backend pref for whether or not the safe browsing checkbox should be | |
| 1652 // displayed based on |value|. | |
| 1653 - (void)setSafeBrowsing:(BOOL)value { | |
| 1654 if (value) | |
| 1655 [self recordUserAction:UserMetricsAction( | |
| 1656 "Options_SafeBrowsingCheckbox_Enable")]; | |
| 1657 else | |
| 1658 [self recordUserAction:UserMetricsAction( | |
| 1659 "Options_SafeBrowsingCheckbox_Disable")]; | |
| 1660 safeBrowsing_.SetValueIfNotManaged(value ? true : false); | |
| 1661 SafeBrowsingService* safeBrowsingService = | |
| 1662 g_browser_process->resource_dispatcher_host()->safe_browsing_service(); | |
| 1663 MessageLoop::current()->PostTask( | |
| 1664 FROM_HERE, | |
| 1665 NewRunnableMethod(safeBrowsingService, | |
| 1666 &SafeBrowsingService::OnEnable, | |
| 1667 safeBrowsing_.GetValue())); | |
| 1668 } | |
| 1669 | |
| 1670 // Returns whether the metrics reporting checkbox should be checked based on the | |
| 1671 // preference. | |
| 1672 - (BOOL)metricsReporting { | |
| 1673 return metricsReporting_.GetValue() ? YES : NO; | |
| 1674 } | |
| 1675 | |
| 1676 // Sets the backend pref for whether or not the metrics reporting checkbox | |
| 1677 // should be displayed based on |value|. | |
| 1678 - (void)setMetricsReporting:(BOOL)value { | |
| 1679 if (value) | |
| 1680 [self recordUserAction:UserMetricsAction( | |
| 1681 "Options_MetricsReportingCheckbox_Enable")]; | |
| 1682 else | |
| 1683 [self recordUserAction:UserMetricsAction( | |
| 1684 "Options_MetricsReportingCheckbox_Disable")]; | |
| 1685 | |
| 1686 // TODO(pinkerton): windows shows a dialog here telling the user they need to | |
| 1687 // restart for this to take effect. http://crbug.com/34653 | |
| 1688 metricsReporting_.SetValueIfNotManaged(value ? true : false); | |
| 1689 | |
| 1690 bool enabled = metricsReporting_.GetValue(); | |
| 1691 GoogleUpdateSettings::SetCollectStatsConsent(enabled); | |
| 1692 bool update_pref = GoogleUpdateSettings::GetCollectStatsConsent(); | |
| 1693 if (enabled != update_pref) { | |
| 1694 DVLOG(1) << "GENERAL SECTION: Unable to set crash report status to " | |
| 1695 << enabled; | |
| 1696 } | |
| 1697 // Only change the pref if GoogleUpdateSettings::GetCollectStatsConsent | |
| 1698 // succeeds. | |
| 1699 enabled = update_pref; | |
| 1700 | |
| 1701 MetricsService* metrics = g_browser_process->metrics_service(); | |
| 1702 DCHECK(metrics); | |
| 1703 if (metrics) { | |
| 1704 metrics->SetUserPermitsUpload(enabled); | |
| 1705 if (enabled) | |
| 1706 metrics->Start(); | |
| 1707 else | |
| 1708 metrics->Stop(); | |
| 1709 } | |
| 1710 } | |
| 1711 | |
| 1712 - (NSURL*)defaultDownloadLocation { | |
| 1713 NSString* pathString = | |
| 1714 base::SysUTF8ToNSString(defaultDownloadLocation_.GetValue()); | |
| 1715 return [NSURL fileURLWithPath:pathString]; | |
| 1716 } | |
| 1717 | |
| 1718 - (BOOL)askForSaveLocation { | |
| 1719 return askForSaveLocation_.GetValue(); | |
| 1720 } | |
| 1721 | |
| 1722 - (void)setAskForSaveLocation:(BOOL)value { | |
| 1723 if (value) { | |
| 1724 [self recordUserAction:UserMetricsAction( | |
| 1725 "Options_AskForSaveLocation_Enable")]; | |
| 1726 } else { | |
| 1727 [self recordUserAction:UserMetricsAction( | |
| 1728 "Options_AskForSaveLocation_Disable")]; | |
| 1729 } | |
| 1730 askForSaveLocation_.SetValue(value); | |
| 1731 } | |
| 1732 | |
| 1733 - (BOOL)fileHandlerUIEnabled { | |
| 1734 if (!profile_->GetDownloadManager()) // Not set in unit tests. | |
| 1735 return NO; | |
| 1736 return profile_->GetDownloadManager()->download_prefs()->IsAutoOpenUsed(); | |
| 1737 } | |
| 1738 | |
| 1739 - (void)setFileHandlerUIEnabled:(BOOL)value { | |
| 1740 [resetFileHandlersButton_ setEnabled:value]; | |
| 1741 } | |
| 1742 | |
| 1743 - (BOOL)translateEnabled { | |
| 1744 return translateEnabled_.GetValue(); | |
| 1745 } | |
| 1746 | |
| 1747 - (void)setTranslateEnabled:(BOOL)value { | |
| 1748 if (value) { | |
| 1749 [self recordUserAction:UserMetricsAction("Options_Translate_Enable")]; | |
| 1750 } else { | |
| 1751 [self recordUserAction:UserMetricsAction("Options_Translate_Disable")]; | |
| 1752 } | |
| 1753 translateEnabled_.SetValue(value); | |
| 1754 } | |
| 1755 | |
| 1756 - (BOOL)tabsToLinks { | |
| 1757 return tabsToLinks_.GetValue(); | |
| 1758 } | |
| 1759 | |
| 1760 - (void)setTabsToLinks:(BOOL)value { | |
| 1761 if (value) { | |
| 1762 [self recordUserAction:UserMetricsAction("Options_TabsToLinks_Enable")]; | |
| 1763 } else { | |
| 1764 [self recordUserAction:UserMetricsAction("Options_TabsToLinks_Disable")]; | |
| 1765 } | |
| 1766 tabsToLinks_.SetValue(value); | |
| 1767 } | |
| 1768 | |
| 1769 - (void)fontAndLanguageEndSheet:(NSWindow*)sheet | |
| 1770 returnCode:(NSInteger)returnCode | |
| 1771 contextInfo:(void*)context { | |
| 1772 [sheet close]; | |
| 1773 [sheet orderOut:self]; | |
| 1774 fontLanguageSettings_ = nil; | |
| 1775 } | |
| 1776 | |
| 1777 - (IBAction)changeFontAndLanguageSettings:(id)sender { | |
| 1778 // Intentionally leak the controller as it will clean itself up when the | |
| 1779 // sheet closes. | |
| 1780 fontLanguageSettings_ = | |
| 1781 [[FontLanguageSettingsController alloc] initWithProfile:profile_]; | |
| 1782 [NSApp beginSheet:[fontLanguageSettings_ window] | |
| 1783 modalForWindow:[self window] | |
| 1784 modalDelegate:self | |
| 1785 didEndSelector:@selector(fontAndLanguageEndSheet:returnCode:contextInfo:) | |
| 1786 contextInfo:nil]; | |
| 1787 } | |
| 1788 | |
| 1789 // Called to launch the Keychain Access app to show the user's stored | |
| 1790 // certificates. Note there's no way to script the app to auto-select the | |
| 1791 // certificates. | |
| 1792 - (IBAction)showCertificates:(id)sender { | |
| 1793 [self recordUserAction:UserMetricsAction("Options_ManagerCerts")]; | |
| 1794 [self launchKeychainAccess]; | |
| 1795 } | |
| 1796 | |
| 1797 - (IBAction)resetToDefaults:(id)sender { | |
| 1798 // The alert will clean itself up in the did-end selector. | |
| 1799 NSAlert* alert = [[NSAlert alloc] init]; | |
| 1800 [alert setMessageText:l10n_util::GetNSString(IDS_OPTIONS_RESET_MESSAGE)]; | |
| 1801 NSButton* resetButton = [alert addButtonWithTitle: | |
| 1802 l10n_util::GetNSString(IDS_OPTIONS_RESET_OKLABEL)]; | |
| 1803 [resetButton setKeyEquivalent:@""]; | |
| 1804 NSButton* cancelButton = [alert addButtonWithTitle: | |
| 1805 l10n_util::GetNSString(IDS_OPTIONS_RESET_CANCELLABEL)]; | |
| 1806 [cancelButton setKeyEquivalent:@"\r"]; | |
| 1807 | |
| 1808 [alert beginSheetModalForWindow:[self window] | |
| 1809 modalDelegate:self | |
| 1810 didEndSelector:@selector(resetToDefaults:returned:context:) | |
| 1811 contextInfo:nil]; | |
| 1812 } | |
| 1813 | |
| 1814 - (void)resetToDefaults:(NSAlert*)alert | |
| 1815 returned:(NSInteger)code | |
| 1816 context:(void*)context { | |
| 1817 if (code == NSAlertFirstButtonReturn) { | |
| 1818 OptionsUtil::ResetToDefaults(profile_); | |
| 1819 } | |
| 1820 [alert autorelease]; | |
| 1821 } | |
| 1822 | |
| 1823 //------------------------------------------------------------------------- | |
| 1824 | |
| 1825 // Callback when preferences are changed. |prefName| is the name of the | |
| 1826 // pref that has changed and should not be NULL. | |
| 1827 - (void)prefChanged:(std::string*)prefName { | |
| 1828 DCHECK(prefName); | |
| 1829 if (!prefName) return; | |
| 1830 [self basicsPrefChanged:prefName]; | |
| 1831 [self userDataPrefChanged:prefName]; | |
| 1832 [self underHoodPrefChanged:prefName]; | |
| 1833 } | |
| 1834 | |
| 1835 // Callback when sync service state has changed. | |
| 1836 // | |
| 1837 // TODO(akalin): Decomp this out since a lot of it is copied from the | |
| 1838 // Windows version. | |
| 1839 // TODO(akalin): Change the background of the status label/link on error. | |
| 1840 - (void)syncStateChanged { | |
| 1841 DCHECK(syncService_); | |
| 1842 | |
| 1843 string16 statusLabel, linkLabel; | |
| 1844 sync_ui_util::MessageType status = | |
| 1845 sync_ui_util::GetStatusLabels(syncService_, &statusLabel, &linkLabel); | |
| 1846 bool managed = syncService_->IsManaged(); | |
| 1847 | |
| 1848 [syncButton_ setEnabled:!syncService_->WizardIsVisible()]; | |
| 1849 NSString* buttonLabel; | |
| 1850 if (syncService_->HasSyncSetupCompleted()) { | |
| 1851 buttonLabel = l10n_util::GetNSStringWithFixup( | |
| 1852 IDS_SYNC_STOP_SYNCING_BUTTON_LABEL); | |
| 1853 [syncCustomizeButton_ setHidden:false]; | |
| 1854 } else if (syncService_->SetupInProgress()) { | |
| 1855 buttonLabel = l10n_util::GetNSStringWithFixup( | |
| 1856 IDS_SYNC_NTP_SETUP_IN_PROGRESS); | |
| 1857 [syncCustomizeButton_ setHidden:true]; | |
| 1858 } else { | |
| 1859 buttonLabel = l10n_util::GetNSStringWithFixup( | |
| 1860 IDS_SYNC_START_SYNC_BUTTON_LABEL); | |
| 1861 [syncCustomizeButton_ setHidden:true]; | |
| 1862 } | |
| 1863 [syncCustomizeButton_ setEnabled:!managed]; | |
| 1864 [syncButton_ setTitle:buttonLabel]; | |
| 1865 [syncButton_ setEnabled:!managed]; | |
| 1866 | |
| 1867 [syncStatus_ setStringValue:base::SysUTF16ToNSString(statusLabel)]; | |
| 1868 [syncLink_ setHidden:linkLabel.empty()]; | |
| 1869 [syncLink_ setTitle:base::SysUTF16ToNSString(linkLabel)]; | |
| 1870 [syncLink_ setEnabled:!managed]; | |
| 1871 | |
| 1872 NSButtonCell* syncLinkCell = static_cast<NSButtonCell*>([syncLink_ cell]); | |
| 1873 if (!syncStatusNoErrorBackgroundColor_) { | |
| 1874 DCHECK(!syncLinkNoErrorBackgroundColor_); | |
| 1875 // We assume that the sync controls start off in a non-error | |
| 1876 // state. | |
| 1877 syncStatusNoErrorBackgroundColor_.reset( | |
| 1878 [[syncStatus_ backgroundColor] retain]); | |
| 1879 syncLinkNoErrorBackgroundColor_.reset( | |
| 1880 [[syncLinkCell backgroundColor] retain]); | |
| 1881 } | |
| 1882 if (status == sync_ui_util::SYNC_ERROR) { | |
| 1883 [syncStatus_ setBackgroundColor:syncErrorBackgroundColor_]; | |
| 1884 [syncLinkCell setBackgroundColor:syncErrorBackgroundColor_]; | |
| 1885 } else { | |
| 1886 [syncStatus_ setBackgroundColor:syncStatusNoErrorBackgroundColor_]; | |
| 1887 [syncLinkCell setBackgroundColor:syncLinkNoErrorBackgroundColor_]; | |
| 1888 } | |
| 1889 } | |
| 1890 | |
| 1891 // Show the preferences window. | |
| 1892 - (IBAction)showPreferences:(id)sender { | |
| 1893 [self showWindow:sender]; | |
| 1894 } | |
| 1895 | |
| 1896 - (IBAction)toolbarButtonSelected:(id)sender { | |
| 1897 DCHECK([sender isKindOfClass:[NSToolbarItem class]]); | |
| 1898 OptionsPage page = [self getPageForToolbarItem:sender]; | |
| 1899 [self displayPreferenceViewForPage:page animate:YES]; | |
| 1900 } | |
| 1901 | |
| 1902 // Helper to update the window to display a preferences view for a page. | |
| 1903 - (void)displayPreferenceViewForPage:(OptionsPage)page | |
| 1904 animate:(BOOL)animate { | |
| 1905 NSWindow* prefsWindow = [self window]; | |
| 1906 | |
| 1907 // Needs to go *after* the call to [self window], which triggers | |
| 1908 // awakeFromNib if necessary. | |
| 1909 NSView* prefsView = [self getPrefsViewForPage:page]; | |
| 1910 NSView* contentView = [prefsWindow contentView]; | |
| 1911 | |
| 1912 // Make sure we aren't being told to display the same thing again. | |
| 1913 if (currentPrefsView_ == prefsView && | |
| 1914 managedPrefsBannerVisible_ == bannerState_->IsVisible()) { | |
| 1915 return; | |
| 1916 } | |
| 1917 | |
| 1918 // Remember new options page as current page. | |
| 1919 if (page != OPTIONS_PAGE_DEFAULT) | |
| 1920 lastSelectedPage_.SetValue(page); | |
| 1921 | |
| 1922 // Stop any running animation, and reset the subviews to the new state. We | |
| 1923 // re-add any views we need for animation later. | |
| 1924 [animation_ stopAnimation]; | |
| 1925 NSView* oldPrefsView = currentPrefsView_; | |
| 1926 currentPrefsView_ = prefsView; | |
| 1927 [self resetSubViews]; | |
| 1928 | |
| 1929 // Update the banner state. | |
| 1930 [self initBannerStateForPage:page]; | |
| 1931 BOOL showBanner = bannerState_->IsVisible(); | |
| 1932 | |
| 1933 // Update the window title. | |
| 1934 NSToolbarItem* toolbarItem = [self getToolbarItemForPage:page]; | |
| 1935 [prefsWindow setTitle:[toolbarItem label]]; | |
| 1936 | |
| 1937 // Calculate new frames for the subviews. | |
| 1938 NSRect prefsViewFrame = [prefsView frame]; | |
| 1939 NSRect contentViewFrame = [contentView frame]; | |
| 1940 NSRect bannerViewFrame = [managedPrefsBannerView_ frame]; | |
| 1941 | |
| 1942 // Determine what height the managed prefs banner will use. | |
| 1943 CGFloat bannerViewHeight = showBanner ? NSHeight(bannerViewFrame) : 0.0; | |
| 1944 | |
| 1945 if (animate) { | |
| 1946 // NSViewAnimation doesn't seem to honor subview resizing as it animates the | |
| 1947 // Window's frame. So instead of trying to get the top in the right place, | |
| 1948 // just set the origin where it should be at the end, and let the fade/size | |
| 1949 // slide things into the right spot. | |
| 1950 prefsViewFrame.origin.y = 0.0; | |
| 1951 } else { | |
| 1952 // The prefView is anchored to the top of its parent, so set its origin so | |
| 1953 // that the top is where it should be. When the window's frame is set, the | |
| 1954 // origin will be adjusted to keep it in the right spot. | |
| 1955 prefsViewFrame.origin.y = NSHeight(contentViewFrame) - | |
| 1956 NSHeight(prefsViewFrame) - bannerViewHeight; | |
| 1957 } | |
| 1958 bannerViewFrame.origin.y = NSHeight(prefsViewFrame); | |
| 1959 bannerViewFrame.size.width = NSWidth(contentViewFrame); | |
| 1960 [prefsView setFrame:prefsViewFrame]; | |
| 1961 | |
| 1962 // Figure out the size of the window. | |
| 1963 NSRect windowFrame = [contentView convertRect:[prefsWindow frame] | |
| 1964 fromView:nil]; | |
| 1965 CGFloat titleToolbarHeight = | |
| 1966 NSHeight(windowFrame) - NSHeight(contentViewFrame); | |
| 1967 windowFrame.size.height = | |
| 1968 NSHeight(prefsViewFrame) + titleToolbarHeight + bannerViewHeight; | |
| 1969 DCHECK_GE(NSWidth(windowFrame), NSWidth(prefsViewFrame)) | |
| 1970 << "Initial width set wasn't wide enough."; | |
| 1971 windowFrame = [contentView convertRect:windowFrame toView:nil]; | |
| 1972 windowFrame.origin.y = NSMaxY([prefsWindow frame]) - NSHeight(windowFrame); | |
| 1973 | |
| 1974 // Now change the size. | |
| 1975 if (animate) { | |
| 1976 NSMutableArray* animations = [NSMutableArray arrayWithCapacity:4]; | |
| 1977 if (oldPrefsView != prefsView) { | |
| 1978 // Fade between prefs views if they change. | |
| 1979 [contentView addSubview:oldPrefsView | |
| 1980 positioned:NSWindowBelow | |
| 1981 relativeTo:nil]; | |
| 1982 [animations addObject: | |
| 1983 [NSDictionary dictionaryWithObjectsAndKeys: | |
| 1984 oldPrefsView, NSViewAnimationTargetKey, | |
| 1985 NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey, | |
| 1986 nil]]; | |
| 1987 [animations addObject: | |
| 1988 [NSDictionary dictionaryWithObjectsAndKeys: | |
| 1989 prefsView, NSViewAnimationTargetKey, | |
| 1990 NSViewAnimationFadeInEffect, NSViewAnimationEffectKey, | |
| 1991 nil]]; | |
| 1992 } else { | |
| 1993 // Make sure the prefs pane ends up in the right position in case we | |
| 1994 // manipulate the banner. | |
| 1995 [animations addObject: | |
| 1996 [NSDictionary dictionaryWithObjectsAndKeys: | |
| 1997 prefsView, NSViewAnimationTargetKey, | |
| 1998 [NSValue valueWithRect:prefsViewFrame], | |
| 1999 NSViewAnimationEndFrameKey, | |
| 2000 nil]]; | |
| 2001 } | |
| 2002 if (showBanner != managedPrefsBannerVisible_) { | |
| 2003 // Slide the warning banner in or out of view. | |
| 2004 [animations addObject: | |
| 2005 [NSDictionary dictionaryWithObjectsAndKeys: | |
| 2006 managedPrefsBannerView_, NSViewAnimationTargetKey, | |
| 2007 [NSValue valueWithRect:bannerViewFrame], | |
| 2008 NSViewAnimationEndFrameKey, | |
| 2009 nil]]; | |
| 2010 } | |
| 2011 // Window resize animation. | |
| 2012 [animations addObject: | |
| 2013 [NSDictionary dictionaryWithObjectsAndKeys: | |
| 2014 prefsWindow, NSViewAnimationTargetKey, | |
| 2015 [NSValue valueWithRect:windowFrame], NSViewAnimationEndFrameKey, | |
| 2016 nil]]; | |
| 2017 [animation_ setViewAnimations:animations]; | |
| 2018 // The default duration is 0.5s, which actually feels slow in here, so speed | |
| 2019 // it up a bit. | |
| 2020 [animation_ gtm_setDuration:0.2 | |
| 2021 eventMask:NSLeftMouseUpMask]; | |
| 2022 [animation_ startAnimation]; | |
| 2023 } else { | |
| 2024 // If not animating, odds are we don't want to display either (because it | |
| 2025 // is initial window setup). | |
| 2026 [prefsWindow setFrame:windowFrame display:NO]; | |
| 2027 [managedPrefsBannerView_ setFrame:bannerViewFrame]; | |
| 2028 } | |
| 2029 | |
| 2030 managedPrefsBannerVisible_ = showBanner; | |
| 2031 } | |
| 2032 | |
| 2033 - (void)resetSubViews { | |
| 2034 // Reset subviews to current prefs view and banner, remove any views that | |
| 2035 // might have been left over from previous state or animation. | |
| 2036 NSArray* subviews = [NSArray arrayWithObjects: | |
| 2037 currentPrefsView_, managedPrefsBannerView_, nil]; | |
| 2038 [[[self window] contentView] setSubviews:subviews]; | |
| 2039 [[self window] setInitialFirstResponder:currentPrefsView_]; | |
| 2040 } | |
| 2041 | |
| 2042 - (void)animationDidEnd:(NSAnimation*)animation { | |
| 2043 DCHECK_EQ(animation_.get(), animation); | |
| 2044 // Animation finished, reset subviews to current prefs view and the banner. | |
| 2045 [self resetSubViews]; | |
| 2046 } | |
| 2047 | |
| 2048 // Reinitializes the banner state tracker object to watch for managed bits of | |
| 2049 // preferences relevant to the given options |page|. | |
| 2050 - (void)initBannerStateForPage:(OptionsPage)page { | |
| 2051 page = [self normalizePage:page]; | |
| 2052 | |
| 2053 // During unit tests, there is no local state object, so we fall back to | |
| 2054 // the prefs object (where we've explicitly registered this pref so we | |
| 2055 // know it's there). | |
| 2056 PrefService* local = g_browser_process->local_state(); | |
| 2057 if (!local) | |
| 2058 local = prefs_; | |
| 2059 bannerState_.reset( | |
| 2060 new PreferencesWindowControllerInternal::ManagedPrefsBannerState( | |
| 2061 self, page, local, prefs_)); | |
| 2062 } | |
| 2063 | |
| 2064 - (void)switchToPage:(OptionsPage)page animate:(BOOL)animate { | |
| 2065 [self displayPreferenceViewForPage:page animate:animate]; | |
| 2066 NSToolbarItem* toolbarItem = [self getToolbarItemForPage:page]; | |
| 2067 [toolbar_ setSelectedItemIdentifier:[toolbarItem itemIdentifier]]; | |
| 2068 } | |
| 2069 | |
| 2070 // Called when the window is being closed. Send out a notification that the user | |
| 2071 // is done editing preferences. Make sure there are no pending field editors | |
| 2072 // by clearing the first responder. | |
| 2073 - (void)windowWillClose:(NSNotification*)notification { | |
| 2074 // Setting the first responder to the window ends any in-progress field | |
| 2075 // editor. This will update the model appropriately so there's nothing left | |
| 2076 // to do. | |
| 2077 if (![[self window] makeFirstResponder:[self window]]) { | |
| 2078 // We've hit a recalcitrant field editor, force it to go away. | |
| 2079 [[self window] endEditingFor:nil]; | |
| 2080 } | |
| 2081 [self autorelease]; | |
| 2082 } | |
| 2083 | |
| 2084 - (void)controlTextDidEndEditing:(NSNotification*)notification { | |
| 2085 [customPagesSource_ validateURLs]; | |
| 2086 } | |
| 2087 | |
| 2088 @end | |
| 2089 | |
| 2090 @implementation PreferencesWindowController(Testing) | |
| 2091 | |
| 2092 - (IntegerPrefMember*)lastSelectedPage { | |
| 2093 return &lastSelectedPage_; | |
| 2094 } | |
| 2095 | |
| 2096 - (NSToolbar*)toolbar { | |
| 2097 return toolbar_; | |
| 2098 } | |
| 2099 | |
| 2100 - (NSView*)basicsView { | |
| 2101 return basicsView_; | |
| 2102 } | |
| 2103 | |
| 2104 - (NSView*)personalStuffView { | |
| 2105 return personalStuffView_; | |
| 2106 } | |
| 2107 | |
| 2108 - (NSView*)underTheHoodView { | |
| 2109 return underTheHoodView_; | |
| 2110 } | |
| 2111 | |
| 2112 - (OptionsPage)normalizePage:(OptionsPage)page { | |
| 2113 if (page == OPTIONS_PAGE_DEFAULT) { | |
| 2114 // Get the last visited page from local state. | |
| 2115 page = static_cast<OptionsPage>(lastSelectedPage_.GetValue()); | |
| 2116 if (page == OPTIONS_PAGE_DEFAULT) { | |
| 2117 page = OPTIONS_PAGE_GENERAL; | |
| 2118 } | |
| 2119 } | |
| 2120 return page; | |
| 2121 } | |
| 2122 | |
| 2123 - (NSToolbarItem*)getToolbarItemForPage:(OptionsPage)page { | |
| 2124 NSUInteger pageIndex = (NSUInteger)[self normalizePage:page]; | |
| 2125 NSArray* items = [toolbar_ items]; | |
| 2126 NSUInteger itemCount = [items count]; | |
| 2127 DCHECK_GE(pageIndex, 0U); | |
| 2128 if (pageIndex >= itemCount) { | |
| 2129 NOTIMPLEMENTED(); | |
| 2130 pageIndex = 0; | |
| 2131 } | |
| 2132 DCHECK_GT(itemCount, 0U); | |
| 2133 return [items objectAtIndex:pageIndex]; | |
| 2134 } | |
| 2135 | |
| 2136 - (OptionsPage)getPageForToolbarItem:(NSToolbarItem*)toolbarItem { | |
| 2137 // Tags are set in the nib file. | |
| 2138 switch ([toolbarItem tag]) { | |
| 2139 case 0: // Basics | |
| 2140 return OPTIONS_PAGE_GENERAL; | |
| 2141 case 1: // Personal Stuff | |
| 2142 return OPTIONS_PAGE_CONTENT; | |
| 2143 case 2: // Under the Hood | |
| 2144 return OPTIONS_PAGE_ADVANCED; | |
| 2145 default: | |
| 2146 NOTIMPLEMENTED(); | |
| 2147 return OPTIONS_PAGE_GENERAL; | |
| 2148 } | |
| 2149 } | |
| 2150 | |
| 2151 - (NSView*)getPrefsViewForPage:(OptionsPage)page { | |
| 2152 // The views will be NULL if this is mistakenly called before awakeFromNib. | |
| 2153 DCHECK(basicsView_); | |
| 2154 DCHECK(personalStuffView_); | |
| 2155 DCHECK(underTheHoodView_); | |
| 2156 page = [self normalizePage:page]; | |
| 2157 switch (page) { | |
| 2158 case OPTIONS_PAGE_GENERAL: | |
| 2159 return basicsView_; | |
| 2160 case OPTIONS_PAGE_CONTENT: | |
| 2161 return personalStuffView_; | |
| 2162 case OPTIONS_PAGE_ADVANCED: | |
| 2163 return underTheHoodView_; | |
| 2164 case OPTIONS_PAGE_DEFAULT: | |
| 2165 case OPTIONS_PAGE_COUNT: | |
| 2166 LOG(DFATAL) << "Invalid page value " << page; | |
| 2167 } | |
| 2168 return basicsView_; | |
| 2169 } | |
| 2170 | |
| 2171 @end | |
| OLD | NEW |