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