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