Home | History | Annotate | Download | only in cocoa
      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/first_run_dialog.h"
      6 
      7 #include "base/mac/mac_util.h"
      8 #include "base/memory/ref_counted.h"
      9 #import "base/memory/scoped_nsobject.h"
     10 #include "base/message_loop.h"
     11 #include "base/sys_string_conversions.h"
     12 #include "chrome/browser/first_run/first_run.h"
     13 #include "chrome/browser/first_run/first_run_dialog.h"
     14 #include "chrome/browser/google/google_util.h"
     15 #include "chrome/browser/platform_util.h"
     16 #include "chrome/browser/profiles/profile.h"
     17 #include "chrome/browser/search_engines/template_url_model.h"
     18 #import "chrome/browser/ui/cocoa/search_engine_dialog_controller.h"
     19 #include "chrome/common/url_constants.h"
     20 #include "googleurl/src/gurl.h"
     21 #include "grit/locale_settings.h"
     22 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
     23 #include "ui/base/l10n/l10n_util_mac.h"
     24 
     25 #if defined(GOOGLE_CHROME_BUILD)
     26 #import "chrome/app/breakpad_mac.h"
     27 #include "chrome/browser/browser_process.h"
     28 #include "chrome/browser/prefs/pref_service.h"
     29 #include "chrome/browser/shell_integration.h"
     30 #include "chrome/common/pref_names.h"
     31 #include "chrome/installer/util/google_update_settings.h"
     32 #endif
     33 
     34 @interface FirstRunDialogController (PrivateMethods)
     35 // Show the dialog.
     36 - (void)show;
     37 @end
     38 
     39 namespace {
     40 
     41 // Compare function for -[NSArray sortedArrayUsingFunction:context:] that
     42 // sorts the views in Y order bottom up.
     43 NSInteger CompareFrameY(id view1, id view2, void* context) {
     44   CGFloat y1 = NSMinY([view1 frame]);
     45   CGFloat y2 = NSMinY([view2 frame]);
     46   if (y1 < y2)
     47     return NSOrderedAscending;
     48   else if (y1 > y2)
     49     return NSOrderedDescending;
     50   else
     51     return NSOrderedSame;
     52 }
     53 
     54 class FirstRunShowBridge : public base::RefCounted<FirstRunShowBridge> {
     55  public:
     56   FirstRunShowBridge(FirstRunDialogController* controller);
     57 
     58   void ShowDialog();
     59  private:
     60   FirstRunDialogController* controller_;
     61 };
     62 
     63 FirstRunShowBridge::FirstRunShowBridge(
     64     FirstRunDialogController* controller) : controller_(controller) {
     65 }
     66 
     67 void FirstRunShowBridge::ShowDialog() {
     68   [controller_ show];
     69   MessageLoop::current()->QuitNow();
     70 }
     71 
     72 // Show the search engine selection dialog.
     73 void ShowSearchEngineSelectionDialog(Profile* profile,
     74                                      bool randomize_search_engine_experiment) {
     75   scoped_nsobject<SearchEngineDialogController> dialog(
     76       [[SearchEngineDialogController alloc] init]);
     77   [dialog.get() setProfile:profile];
     78   [dialog.get() setRandomize:randomize_search_engine_experiment];
     79 
     80   [dialog.get() showWindow:nil];
     81 }
     82 
     83 // Show the first run UI.
     84 void ShowFirstRun(Profile* profile) {
     85 #if defined(GOOGLE_CHROME_BUILD)
     86   // The purpose of the dialog is to ask the user to enable stats and crash
     87   // reporting. This setting may be controlled through configuration management
     88   // in enterprise scenarios. If that is the case, skip the dialog entirely, as
     89   // it's not worth bothering the user for only the default browser question
     90   // (which is likely to be forced in enterprise deployments anyway).
     91   const PrefService::Preference* metrics_reporting_pref =
     92       g_browser_process->local_state()->FindPreference(
     93           prefs::kMetricsReportingEnabled);
     94   if (!metrics_reporting_pref || !metrics_reporting_pref->IsManaged()) {
     95     scoped_nsobject<FirstRunDialogController> dialog(
     96         [[FirstRunDialogController alloc] init]);
     97 
     98     [dialog.get() showWindow:nil];
     99 
    100     // If the dialog asked the user to opt-in for stats and crash reporting,
    101     // record the decision and enable the crash reporter if appropriate.
    102     bool stats_enabled = [dialog.get() statsEnabled];
    103     GoogleUpdateSettings::SetCollectStatsConsent(stats_enabled);
    104 
    105     // Breakpad is normally enabled very early in the startup process.  However,
    106     // on the first run it may not have been enabled due to the missing opt-in
    107     // from the user.  If the user agreed now, enable breakpad if necessary.
    108     if (!IsCrashReporterEnabled() && stats_enabled) {
    109       InitCrashReporter();
    110       InitCrashProcessInfo();
    111     }
    112 
    113     // If selected set as default browser.
    114     BOOL make_default_browser = [dialog.get() makeDefaultBrowser];
    115     if (make_default_browser) {
    116       bool success = ShellIntegration::SetAsDefaultBrowser();
    117       DCHECK(success);
    118     }
    119   }
    120 #else  // GOOGLE_CHROME_BUILD
    121   // We don't show the dialog in Chromium.
    122 #endif  // GOOGLE_CHROME_BUILD
    123 
    124   FirstRun::CreateSentinel();
    125 
    126   // Set preference to show first run bubble and welcome page.
    127   // Don't display the minimal bubble if there is no default search provider.
    128   TemplateURLModel* search_engines_model = profile->GetTemplateURLModel();
    129   if (search_engines_model &&
    130       search_engines_model->GetDefaultSearchProvider()) {
    131     FirstRun::SetShowFirstRunBubblePref(true);
    132   }
    133   FirstRun::SetShowWelcomePagePref();
    134 }
    135 
    136 }  // namespace
    137 
    138 namespace first_run {
    139 
    140 void ShowFirstRunDialog(Profile* profile,
    141                         bool randomize_search_engine_experiment) {
    142   // If the default search is not managed via policy, ask the user to
    143   // choose a default.
    144   TemplateURLModel* model = profile->GetTemplateURLModel();
    145   if (!FirstRun::SearchEngineSelectorDisallowed() ||
    146       (model && !model->is_default_search_managed())) {
    147     ShowSearchEngineSelectionDialog(profile,
    148                                     randomize_search_engine_experiment);
    149   }
    150   ShowFirstRun(profile);
    151 }
    152 
    153 }  // namespace first_run
    154 
    155 @implementation FirstRunDialogController
    156 
    157 @synthesize statsEnabled = statsEnabled_;
    158 @synthesize makeDefaultBrowser = makeDefaultBrowser_;
    159 
    160 - (id)init {
    161   NSString* nibpath =
    162       [base::mac::MainAppBundle() pathForResource:@"FirstRunDialog"
    163                                           ofType:@"nib"];
    164   self = [super initWithWindowNibPath:nibpath owner:self];
    165   if (self != nil) {
    166     // Bound to the dialog checkbox, default to true.
    167     makeDefaultBrowser_ = YES;
    168   }
    169   return self;
    170 }
    171 
    172 - (void)dealloc {
    173   [super dealloc];
    174 }
    175 
    176 - (IBAction)showWindow:(id)sender {
    177   // The main MessageLoop has not yet run, but has been spun. If we call
    178   // -[NSApplication runModalForWindow:] we will hang <http://crbug.com/54248>.
    179   // Therefore the main MessageLoop is run so things work.
    180 
    181   scoped_refptr<FirstRunShowBridge> bridge(new FirstRunShowBridge(self));
    182   MessageLoop::current()->PostTask(
    183       FROM_HERE,
    184       NewRunnableMethod(bridge.get(),
    185                         &FirstRunShowBridge::ShowDialog));
    186   MessageLoop::current()->Run();
    187 }
    188 
    189 - (void)show {
    190   NSWindow* win = [self window];
    191 
    192   if (!platform_util::CanSetAsDefaultBrowser()) {
    193     [setAsDefaultCheckbox_ setHidden:YES];
    194     makeDefaultBrowser_ = NO;
    195   }
    196 
    197   // Only support the sizing the window once.
    198   DCHECK(!beenSized_) << "ShowWindow was called twice?";
    199   if (!beenSized_) {
    200     beenSized_ = YES;
    201     DCHECK_GT([objectsToSize_ count], 0U);
    202 
    203     // Size everything to fit, collecting the widest growth needed (XIB provides
    204     // the min size, i.e.-never shrink, just grow).
    205     CGFloat largestWidthChange = 0.0;
    206     for (NSView* view in objectsToSize_) {
    207       DCHECK_NE(statsCheckbox_, view) << "Stats checkbox shouldn't be in list";
    208       if (![view isHidden]) {
    209         NSSize delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:view];
    210         DCHECK_EQ(delta.height, 0.0)
    211             << "Didn't expect anything to change heights";
    212         if (largestWidthChange < delta.width)
    213           largestWidthChange = delta.width;
    214       }
    215     }
    216 
    217     // Make the window wide enough to fit everything.
    218     if (largestWidthChange > 0.0) {
    219       NSView* contentView = [win contentView];
    220       NSRect windowFrame = [contentView convertRect:[win frame] fromView:nil];
    221       windowFrame.size.width += largestWidthChange;
    222       windowFrame = [contentView convertRect:windowFrame toView:nil];
    223       [win setFrame:windowFrame display:NO];
    224     }
    225 
    226     // The stats checkbox gets some really long text, so it gets word wrapped
    227     // and then sized.
    228     DCHECK(statsCheckbox_);
    229     CGFloat statsCheckboxHeightChange = 0.0;
    230     [GTMUILocalizerAndLayoutTweaker wrapButtonTitleForWidth:statsCheckbox_];
    231     statsCheckboxHeightChange =
    232         [GTMUILocalizerAndLayoutTweaker sizeToFitView:statsCheckbox_].height;
    233 
    234     // Walk bottom up shuffling for all the hidden views.
    235     NSArray* subViews =
    236         [[[win contentView] subviews] sortedArrayUsingFunction:CompareFrameY
    237                                                        context:NULL];
    238     CGFloat moveDown = 0.0;
    239     NSUInteger numSubViews = [subViews count];
    240     for (NSUInteger idx = 0 ; idx < numSubViews ; ++idx) {
    241       NSView* view = [subViews objectAtIndex:idx];
    242 
    243       // If the view is hidden, collect the amount to move everything above it
    244       // down, if it's not hidden, apply any shift down.
    245       if ([view isHidden]) {
    246         DCHECK_GT((numSubViews - 1), idx)
    247             << "Don't support top view being hidden";
    248         NSView* nextView = [subViews objectAtIndex:(idx + 1)];
    249         CGFloat viewBottom = [view frame].origin.y;
    250         CGFloat nextViewBottom = [nextView frame].origin.y;
    251         moveDown += nextViewBottom - viewBottom;
    252       } else {
    253         if (moveDown != 0.0) {
    254           NSPoint origin = [view frame].origin;
    255           origin.y -= moveDown;
    256           [view setFrameOrigin:origin];
    257         }
    258       }
    259       // Special case, if this is the stats checkbox, everything above it needs
    260       // to get moved up by the amount it changed height.
    261       if (view == statsCheckbox_) {
    262         moveDown -= statsCheckboxHeightChange;
    263       }
    264     }
    265 
    266     // Resize the window for any height change from hidden views, etc.
    267     if (moveDown != 0.0) {
    268       NSView* contentView = [win contentView];
    269       [contentView setAutoresizesSubviews:NO];
    270       NSRect windowFrame = [contentView convertRect:[win frame] fromView:nil];
    271       windowFrame.size.height -= moveDown;
    272       windowFrame = [contentView convertRect:windowFrame toView:nil];
    273       [win setFrame:windowFrame display:NO];
    274       [contentView setAutoresizesSubviews:YES];
    275     }
    276 
    277   }
    278 
    279   // Neat weirdness in the below code - the Application menu stays enabled
    280   // while the window is open but selecting items from it (e.g. Quit) has
    281   // no effect.  I'm guessing that this is an artifact of us being a
    282   // background-only application at this stage and displaying a modal
    283   // window.
    284 
    285   // Display dialog.
    286   [win center];
    287   [NSApp runModalForWindow:win];
    288 }
    289 
    290 - (IBAction)ok:(id)sender {
    291   [[self window] close];
    292   [NSApp stopModal];
    293 }
    294 
    295 - (IBAction)learnMore:(id)sender {
    296   GURL url = google_util::AppendGoogleLocaleParam(
    297       GURL(chrome::kLearnMoreReportingURL));
    298   NSString* urlStr = base::SysUTF8ToNSString(url.spec());;
    299   NSURL* learnMoreUrl = [NSURL URLWithString:urlStr];
    300   [[NSWorkspace sharedWorkspace] openURL:learnMoreUrl];
    301 }
    302 
    303 @end
    304