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 |