Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7)

Side by Side Diff: chrome/browser/ui/cocoa/options/preferences_window_controller.mm

Issue 6621076: [Mac] Remove native/Cocoa preferences. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase switch removal Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698