// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "chrome/browser/ui/cocoa/first_run_dialog.h" #include "base/mac/mac_util.h" #include "base/memory/ref_counted.h" #import "base/memory/scoped_nsobject.h" #include "base/message_loop.h" #include "base/sys_string_conversions.h" #include "chrome/browser/first_run/first_run.h" #include "chrome/browser/first_run/first_run_dialog.h" #include "chrome/browser/google/google_util.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search_engines/template_url_model.h" #import "chrome/browser/ui/cocoa/search_engine_dialog_controller.h" #include "chrome/common/url_constants.h" #include "googleurl/src/gurl.h" #include "grit/locale_settings.h" #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" #include "ui/base/l10n/l10n_util_mac.h" #if defined(GOOGLE_CHROME_BUILD) #import "chrome/app/breakpad_mac.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/shell_integration.h" #include "chrome/common/pref_names.h" #include "chrome/installer/util/google_update_settings.h" #endif @interface FirstRunDialogController (PrivateMethods) // Show the dialog. - (void)show; @end namespace { // Compare function for -[NSArray sortedArrayUsingFunction:context:] that // sorts the views in Y order bottom up. NSInteger CompareFrameY(id view1, id view2, void* context) { CGFloat y1 = NSMinY([view1 frame]); CGFloat y2 = NSMinY([view2 frame]); if (y1 < y2) return NSOrderedAscending; else if (y1 > y2) return NSOrderedDescending; else return NSOrderedSame; } class FirstRunShowBridge : public base::RefCounted<FirstRunShowBridge> { public: FirstRunShowBridge(FirstRunDialogController* controller); void ShowDialog(); private: FirstRunDialogController* controller_; }; FirstRunShowBridge::FirstRunShowBridge( FirstRunDialogController* controller) : controller_(controller) { } void FirstRunShowBridge::ShowDialog() { [controller_ show]; MessageLoop::current()->QuitNow(); } // Show the search engine selection dialog. void ShowSearchEngineSelectionDialog(Profile* profile, bool randomize_search_engine_experiment) { scoped_nsobject<SearchEngineDialogController> dialog( [[SearchEngineDialogController alloc] init]); [dialog.get() setProfile:profile]; [dialog.get() setRandomize:randomize_search_engine_experiment]; [dialog.get() showWindow:nil]; } // Show the first run UI. void ShowFirstRun(Profile* profile) { #if defined(GOOGLE_CHROME_BUILD) // The purpose of the dialog is to ask the user to enable stats and crash // reporting. This setting may be controlled through configuration management // in enterprise scenarios. If that is the case, skip the dialog entirely, as // it's not worth bothering the user for only the default browser question // (which is likely to be forced in enterprise deployments anyway). const PrefService::Preference* metrics_reporting_pref = g_browser_process->local_state()->FindPreference( prefs::kMetricsReportingEnabled); if (!metrics_reporting_pref || !metrics_reporting_pref->IsManaged()) { scoped_nsobject<FirstRunDialogController> dialog( [[FirstRunDialogController alloc] init]); [dialog.get() showWindow:nil]; // If the dialog asked the user to opt-in for stats and crash reporting, // record the decision and enable the crash reporter if appropriate. bool stats_enabled = [dialog.get() statsEnabled]; GoogleUpdateSettings::SetCollectStatsConsent(stats_enabled); // Breakpad is normally enabled very early in the startup process. However, // on the first run it may not have been enabled due to the missing opt-in // from the user. If the user agreed now, enable breakpad if necessary. if (!IsCrashReporterEnabled() && stats_enabled) { InitCrashReporter(); InitCrashProcessInfo(); } // If selected set as default browser. BOOL make_default_browser = [dialog.get() makeDefaultBrowser]; if (make_default_browser) { bool success = ShellIntegration::SetAsDefaultBrowser(); DCHECK(success); } } #else // GOOGLE_CHROME_BUILD // We don't show the dialog in Chromium. #endif // GOOGLE_CHROME_BUILD FirstRun::CreateSentinel(); // Set preference to show first run bubble and welcome page. // Don't display the minimal bubble if there is no default search provider. TemplateURLModel* search_engines_model = profile->GetTemplateURLModel(); if (search_engines_model && search_engines_model->GetDefaultSearchProvider()) { FirstRun::SetShowFirstRunBubblePref(true); } FirstRun::SetShowWelcomePagePref(); } } // namespace namespace first_run { void ShowFirstRunDialog(Profile* profile, bool randomize_search_engine_experiment) { // If the default search is not managed via policy, ask the user to // choose a default. TemplateURLModel* model = profile->GetTemplateURLModel(); if (!FirstRun::SearchEngineSelectorDisallowed() || (model && !model->is_default_search_managed())) { ShowSearchEngineSelectionDialog(profile, randomize_search_engine_experiment); } ShowFirstRun(profile); } } // namespace first_run @implementation FirstRunDialogController @synthesize statsEnabled = statsEnabled_; @synthesize makeDefaultBrowser = makeDefaultBrowser_; - (id)init { NSString* nibpath = [base::mac::MainAppBundle() pathForResource:@"FirstRunDialog" ofType:@"nib"]; self = [super initWithWindowNibPath:nibpath owner:self]; if (self != nil) { // Bound to the dialog checkbox, default to true. makeDefaultBrowser_ = YES; } return self; } - (void)dealloc { [super dealloc]; } - (IBAction)showWindow:(id)sender { // The main MessageLoop has not yet run, but has been spun. If we call // -[NSApplication runModalForWindow:] we will hang <http://crbug.com/54248>. // Therefore the main MessageLoop is run so things work. scoped_refptr<FirstRunShowBridge> bridge(new FirstRunShowBridge(self)); MessageLoop::current()->PostTask( FROM_HERE, NewRunnableMethod(bridge.get(), &FirstRunShowBridge::ShowDialog)); MessageLoop::current()->Run(); } - (void)show { NSWindow* win = [self window]; if (!platform_util::CanSetAsDefaultBrowser()) { [setAsDefaultCheckbox_ setHidden:YES]; makeDefaultBrowser_ = NO; } // Only support the sizing the window once. DCHECK(!beenSized_) << "ShowWindow was called twice?"; if (!beenSized_) { beenSized_ = YES; DCHECK_GT([objectsToSize_ count], 0U); // Size everything to fit, collecting the widest growth needed (XIB provides // the min size, i.e.-never shrink, just grow). CGFloat largestWidthChange = 0.0; for (NSView* view in objectsToSize_) { DCHECK_NE(statsCheckbox_, view) << "Stats checkbox shouldn't be in list"; if (![view isHidden]) { NSSize delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:view]; DCHECK_EQ(delta.height, 0.0) << "Didn't expect anything to change heights"; if (largestWidthChange < delta.width) largestWidthChange = delta.width; } } // Make the window wide enough to fit everything. if (largestWidthChange > 0.0) { NSView* contentView = [win contentView]; NSRect windowFrame = [contentView convertRect:[win frame] fromView:nil]; windowFrame.size.width += largestWidthChange; windowFrame = [contentView convertRect:windowFrame toView:nil]; [win setFrame:windowFrame display:NO]; } // The stats checkbox gets some really long text, so it gets word wrapped // and then sized. DCHECK(statsCheckbox_); CGFloat statsCheckboxHeightChange = 0.0; [GTMUILocalizerAndLayoutTweaker wrapButtonTitleForWidth:statsCheckbox_]; statsCheckboxHeightChange = [GTMUILocalizerAndLayoutTweaker sizeToFitView:statsCheckbox_].height; // Walk bottom up shuffling for all the hidden views. NSArray* subViews = [[[win contentView] subviews] sortedArrayUsingFunction:CompareFrameY context:NULL]; CGFloat moveDown = 0.0; NSUInteger numSubViews = [subViews count]; for (NSUInteger idx = 0 ; idx < numSubViews ; ++idx) { NSView* view = [subViews objectAtIndex:idx]; // If the view is hidden, collect the amount to move everything above it // down, if it's not hidden, apply any shift down. if ([view isHidden]) { DCHECK_GT((numSubViews - 1), idx) << "Don't support top view being hidden"; NSView* nextView = [subViews objectAtIndex:(idx + 1)]; CGFloat viewBottom = [view frame].origin.y; CGFloat nextViewBottom = [nextView frame].origin.y; moveDown += nextViewBottom - viewBottom; } else { if (moveDown != 0.0) { NSPoint origin = [view frame].origin; origin.y -= moveDown; [view setFrameOrigin:origin]; } } // Special case, if this is the stats checkbox, everything above it needs // to get moved up by the amount it changed height. if (view == statsCheckbox_) { moveDown -= statsCheckboxHeightChange; } } // Resize the window for any height change from hidden views, etc. if (moveDown != 0.0) { NSView* contentView = [win contentView]; [contentView setAutoresizesSubviews:NO]; NSRect windowFrame = [contentView convertRect:[win frame] fromView:nil]; windowFrame.size.height -= moveDown; windowFrame = [contentView convertRect:windowFrame toView:nil]; [win setFrame:windowFrame display:NO]; [contentView setAutoresizesSubviews:YES]; } } // Neat weirdness in the below code - the Application menu stays enabled // while the window is open but selecting items from it (e.g. Quit) has // no effect. I'm guessing that this is an artifact of us being a // background-only application at this stage and displaying a modal // window. // Display dialog. [win center]; [NSApp runModalForWindow:win]; } - (IBAction)ok:(id)sender { [[self window] close]; [NSApp stopModal]; } - (IBAction)learnMore:(id)sender { GURL url = google_util::AppendGoogleLocaleParam( GURL(chrome::kLearnMoreReportingURL)); NSString* urlStr = base::SysUTF8ToNSString(url.spec());; NSURL* learnMoreUrl = [NSURL URLWithString:urlStr]; [[NSWorkspace sharedWorkspace] openURL:learnMoreUrl]; } @end