diff options
author | avi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-09-27 20:25:04 +0000 |
---|---|---|
committer | avi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-09-27 20:25:04 +0000 |
commit | 3ef4755e9b102647240a5c281690332fe44be34b (patch) | |
tree | 9b26ce5b65f9b24efd356dd5a7f8eec3b114e3cc /chrome/browser/cocoa | |
parent | 011b93dc3cc8eb6ef80626a2de340ee66f4ea663 (diff) | |
download | chromium_src-3ef4755e9b102647240a5c281690332fe44be34b.zip chromium_src-3ef4755e9b102647240a5c281690332fe44be34b.tar.gz chromium_src-3ef4755e9b102647240a5c281690332fe44be34b.tar.bz2 |
Add search engine selection dialog for Mac, do new-style first-run.
SearchEngineDialog.xib: creation
FirstRunDialog.xib: removal of import and cancel UI elements
BUG=47651
TEST=reset profile, run. Make sure goto/chromefirstrun is satisfied.
Review URL: http://codereview.chromium.org/3223010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@60699 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/cocoa')
-rw-r--r-- | chrome/browser/cocoa/first_run_dialog.h | 15 | ||||
-rw-r--r-- | chrome/browser/cocoa/first_run_dialog.mm | 108 | ||||
-rw-r--r-- | chrome/browser/cocoa/search_engine_dialog_controller.h | 45 | ||||
-rw-r--r-- | chrome/browser/cocoa/search_engine_dialog_controller.mm | 279 |
4 files changed, 369 insertions, 78 deletions
diff --git a/chrome/browser/cocoa/first_run_dialog.h b/chrome/browser/cocoa/first_run_dialog.h index a96ed17..3e575a3 100644 --- a/chrome/browser/cocoa/first_run_dialog.h +++ b/chrome/browser/cocoa/first_run_dialog.h @@ -13,14 +13,8 @@ // us improve Chromium. @interface FirstRunDialogController : NSWindowController { @private - BOOL userDidCancel_; BOOL statsEnabled_; - BOOL statsCheckboxHidden_; BOOL makeDefaultBrowser_; - BOOL importBookmarks_; - int browserImportSelectedIndex_; - NSArray* browserImportList_; - BOOL browserImportListHidden_; IBOutlet NSArray* objectsToSize_; IBOutlet NSButton* statsCheckbox_; @@ -30,21 +24,12 @@ // Called when the "Start Google Chrome" button is pressed. - (IBAction)ok:(id)sender; -// Cancel button calls this. -- (IBAction)cancel:(id)sender; - // Called when the "Learn More" button is pressed. - (IBAction)learnMore:(id)sender; // Properties for bindings. -@property(assign, nonatomic) BOOL userDidCancel; @property(assign, nonatomic) BOOL statsEnabled; -@property(assign, nonatomic) BOOL statsCheckboxHidden; @property(assign, nonatomic) BOOL makeDefaultBrowser; -@property(assign, nonatomic) BOOL importBookmarks; -@property(assign, nonatomic) int browserImportSelectedIndex; -@property(retain, nonatomic) NSArray* browserImportList; -@property(assign, nonatomic) BOOL browserImportListHidden; @end diff --git a/chrome/browser/cocoa/first_run_dialog.mm b/chrome/browser/cocoa/first_run_dialog.mm index c71750e..123b80b 100644 --- a/chrome/browser/cocoa/first_run_dialog.mm +++ b/chrome/browser/cocoa/first_run_dialog.mm @@ -1,19 +1,21 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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/cocoa/first_run_dialog.h" #include "app/l10n_util_mac.h" -#include "base/logging.h" #include "base/mac_util.h" -#import "base/scoped_nsobject.h" -#include "chrome/browser/browser_process.h" -#include "chrome/browser/prefs/pref_service.h" -#include "chrome/common/pref_names.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" #include "grit/locale_settings.h" #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" +@interface FirstRunDialogController (PrivateMethods) +// Show the dialog. +- (void)show; +@end + namespace { // Compare function for -[NSArray sortedArrayUsingFunction:context:] that @@ -29,18 +31,30 @@ NSInteger CompareFrameY(id view1, id view2, void* context) { 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(); +} + }; @implementation FirstRunDialogController -@synthesize userDidCancel = userDidCancel_; @synthesize statsEnabled = statsEnabled_; -@synthesize statsCheckboxHidden = statsCheckboxHidden_; @synthesize makeDefaultBrowser = makeDefaultBrowser_; -@synthesize importBookmarks = importBookmarks_; -@synthesize browserImportSelectedIndex = browserImportSelectedIndex_; -@synthesize browserImportList = browserImportList_; -@synthesize browserImportListHidden = browserImportListHidden_; - (id)init { NSString* nibpath = @@ -50,31 +64,28 @@ NSInteger CompareFrameY(id view1, id view2, void* context) { if (self != nil) { // Bound to the dialog checkbox, default to true. statsEnabled_ = YES; - importBookmarks_ = YES; - -#if defined(GOOGLE_CHROME_BUILD) - // If the send stats option is controlled by enterprise configuration - // management, hide the checkbox. - const PrefService::Preference* metrics_reporting_pref = - g_browser_process->local_state()->FindPreference( - prefs::kMetricsReportingEnabled); - if (metrics_reporting_pref && metrics_reporting_pref->IsManaged()) - statsCheckboxHidden_ = YES; -#else - // In Chromium builds all stats reporting is disabled so there's no reason - // to display the checkbox - the setting is always OFF. - statsCheckboxHidden_ = YES; -#endif // !GOOGLE_CHROME_BUILD } return self; } - (void)dealloc { - [browserImportList_ release]; [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]; // Only support the sizing the window once. @@ -106,15 +117,13 @@ NSInteger CompareFrameY(id view1, id view2, void* context) { [win setFrame:windowFrame display:NO]; } - // The stats checkbox (if visible) gets some really long text, so it gets - // word wrapped and then sized. + // The stats checkbox gets some really long text, so it gets word wrapped + // and then sized. DCHECK(statsCheckbox_); CGFloat statsCheckboxHeightChange = 0.0; - if (![self statsCheckboxHidden]) { - [GTMUILocalizerAndLayoutTweaker wrapButtonTitleForWidth:statsCheckbox_]; - statsCheckboxHeightChange = - [GTMUILocalizerAndLayoutTweaker sizeToFitView:statsCheckbox_].height; - } + [GTMUILocalizerAndLayoutTweaker wrapButtonTitleForWidth:statsCheckbox_]; + statsCheckboxHeightChange = + [GTMUILocalizerAndLayoutTweaker sizeToFitView:statsCheckbox_].height; // Walk bottom up shuffling for all the hidden views. NSArray* subViews = @@ -172,42 +181,15 @@ NSInteger CompareFrameY(id view1, id view2, void* context) { [NSApp runModalForWindow:win]; } -- (void)closeDialog { +- (IBAction)ok:(id)sender { [[self window] close]; [NSApp stopModal]; } -- (IBAction)ok:(id)sender { - [self closeDialog]; -} - -- (IBAction)cancel:(id)sender { - [self closeDialog]; - [self setUserDidCancel:YES]; -} - - (IBAction)learnMore:(id)sender { NSString* urlStr = l10n_util::GetNSString(IDS_LEARN_MORE_REPORTING_URL); NSURL* learnMoreUrl = [NSURL URLWithString:urlStr]; [[NSWorkspace sharedWorkspace] openURL:learnMoreUrl]; } -// Custom property getters - -- (BOOL)importBookmarks { - // If the UI for browser import is hidden, report the choice as off. - if ([self browserImportListHidden]) { - return NO; - } - return importBookmarks_; -} - -- (BOOL)statsEnabled { - // If the UI for stats is hidden, report the choice as off. - if ([self statsCheckboxHidden]) { - return NO; - } - return statsEnabled_; -} - @end diff --git a/chrome/browser/cocoa/search_engine_dialog_controller.h b/chrome/browser/cocoa/search_engine_dialog_controller.h new file mode 100644 index 0000000..a410614 --- /dev/null +++ b/chrome/browser/cocoa/search_engine_dialog_controller.h @@ -0,0 +1,45 @@ +// Copyright (c) 2010 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 <Cocoa/Cocoa.h> + +#include <vector> + +#import "base/scoped_nsobject.h" +#include "base/scoped_ptr.h" + +class Profile; +class SearchEngineDialogControllerBridge; +class TemplateURL; +class TemplateURLModel; + +// Class that acts as a controller for the search engine choice dialog. +@interface SearchEngineDialogController : NSWindowController { + @private + // Our current profile. + Profile* profile_; + + // If logos are to be displayed in random order. Used for UX testing. + bool randomize_; + + // Owned by the profile_. + TemplateURLModel* searchEnginesModel_; + + // Bridge to the C++ world. + scoped_ptr<SearchEngineDialogControllerBridge> bridge_; + + // Offered search engine choices. + std::vector<const TemplateURL*> choices_; + + IBOutlet NSImageView* headerImageView_; + IBOutlet NSView* searchEngineView_; +} + +@property(assign, nonatomic) Profile* profile; +@property(assign, nonatomic) bool randomize; + +// Properties for bindings. +@property(readonly) NSFont* mainLabelFont; + +@end diff --git a/chrome/browser/cocoa/search_engine_dialog_controller.mm b/chrome/browser/cocoa/search_engine_dialog_controller.mm new file mode 100644 index 0000000..4863c07 --- /dev/null +++ b/chrome/browser/cocoa/search_engine_dialog_controller.mm @@ -0,0 +1,279 @@ +// Copyright (c) 2010 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/cocoa/search_engine_dialog_controller.h" + +#include <algorithm> + +#include "app/l10n_util_mac.h" +#include "app/resource_bundle.h" +#include "base/mac_util.h" +#include "base/nsimage_cache_mac.h" +#include "base/sys_string_conversions.h" +#include "base/time.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/browser/search_engines/template_url_model.h" +#include "chrome/browser/search_engines/template_url_model_observer.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" + +// Horizontal spacing between search engine choices. +const int kSearchEngineSpacing = 20; + +// Vertical spacing between the search engine logo and the button underneath. +const int kLogoButtonSpacing = 10; + +// Width of a label used in place of a logo. +const int kLogoLabelWidth = 170; + +// Height of a label used in place of a logo. +const int kLogoLabelHeight = 25; + +@interface SearchEngineDialogController (Private) +- (void)onTemplateURLModelChanged; +- (void)buildSearchEngineView; +- (NSView*)viewForSearchEngine:(const TemplateURL*)engine + atIndex:(size_t)index; +- (IBAction)searchEngineSelected:(id)sender; +@end + +class SearchEngineDialogControllerBridge : public TemplateURLModelObserver { + public: + SearchEngineDialogControllerBridge(SearchEngineDialogController* controller); + + // TemplateURLModelObserver + virtual void OnTemplateURLModelChanged(); + + private: + SearchEngineDialogController* controller_; +}; + +SearchEngineDialogControllerBridge::SearchEngineDialogControllerBridge( + SearchEngineDialogController* controller) : controller_(controller) { +} + +void SearchEngineDialogControllerBridge::OnTemplateURLModelChanged() { + [controller_ onTemplateURLModelChanged]; + MessageLoop::current()->QuitNow(); +} + +@implementation SearchEngineDialogController + +@synthesize profile = profile_; +@synthesize randomize = randomize_; + +- (id)init { + NSString* nibpath = + [mac_util::MainAppBundle() pathForResource:@"SearchEngineDialog" + ofType:@"nib"]; + self = [super initWithWindowNibPath:nibpath owner:self]; + if (self != nil) { + bridge_.reset(new SearchEngineDialogControllerBridge(self)); + } + return self; +} + +- (void)dealloc { + [super dealloc]; +} + +- (IBAction)showWindow:(id)sender { + searchEnginesModel_ = profile_->GetTemplateURLModel(); + searchEnginesModel_->AddObserver(bridge_.get()); + + if (searchEnginesModel_->loaded()) { + [self onTemplateURLModelChanged]; + } else { + searchEnginesModel_->Load(); + MessageLoop::current()->Run(); + } +} + +- (void)onTemplateURLModelChanged { + searchEnginesModel_->RemoveObserver(bridge_.get()); + + // Add the search engines in the search_engines_model_ to the buttons list. + // The first three will always be from prepopulated data. + std::vector<const TemplateURL*> templateUrls = + searchEnginesModel_->GetTemplateURLs(); + + // If we have fewer than two search engines, end the search engine dialog + // immediately, leaving the imported default search engine setting intact. + if (templateUrls.size() < 2) { + return; + } + + NSWindow* win = [self window]; + + [win setBackgroundColor:[NSColor whiteColor]]; + + NSImage* headerImage = ResourceBundle::GetSharedInstance(). + GetNSImageNamed(IDR_SEARCH_ENGINE_DIALOG_TOP); + [headerImageView_ setImage:headerImage]; + + // Is the user's default search engine included in the first three + // prepopulated set? If not, we need to expand the dialog to include a fourth + // engine. + const TemplateURL* defaultSearchEngine = + searchEnginesModel_->GetDefaultSearchProvider(); + + std::vector<const TemplateURL*>::iterator engineIter = + templateUrls.begin(); + for (int i = 0; engineIter != templateUrls.end(); ++i, ++engineIter) { + if (i < 3) { + choices_.push_back(*engineIter); + } else { + if (*engineIter == defaultSearchEngine) + choices_.push_back(*engineIter); + } + } + + // Randomize the order of the logos if the option has been set. + if (randomize_) { + int seed = static_cast<int>(base::Time::Now().ToInternalValue()); + srand(seed); + std::random_shuffle(choices_.begin(), choices_.end()); + } + + [self buildSearchEngineView]; + + // Display the dialog. + NSInteger choice = [NSApp runModalForWindow:win]; + searchEnginesModel_->SetDefaultSearchProvider(choices_.at(choice)); +} + +- (void)buildSearchEngineView { + scoped_nsobject<NSMutableArray> searchEngineViews + ([[NSMutableArray alloc] init]); + + for (size_t i = 0; i < choices_.size(); ++i) + [searchEngineViews addObject:[self viewForSearchEngine:choices_.at(i) + atIndex:i]]; + + NSSize newOverallSize = NSZeroSize; + for (NSView* view in searchEngineViews.get()) { + NSRect engineFrame = [view frame]; + engineFrame.origin = NSMakePoint(newOverallSize.width, 0); + [searchEngineView_ addSubview:view]; + [view setFrame:engineFrame]; + newOverallSize = NSMakeSize( + newOverallSize.width + NSWidth(engineFrame) + kSearchEngineSpacing, + std::max(newOverallSize.height, NSHeight(engineFrame))); + } + newOverallSize.width -= kSearchEngineSpacing; + + // Resize the window to fit (and because it's bound on all sides it will + // resize the search engine view). + NSSize currentOverallSize = [searchEngineView_ bounds].size; + NSSize deltaSize = NSMakeSize( + newOverallSize.width - currentOverallSize.width, + newOverallSize.height - currentOverallSize.height); + NSSize windowDeltaSize = [searchEngineView_ convertSize:deltaSize toView:nil]; + NSRect windowFrame = [[self window] frame]; + windowFrame.size.width += windowDeltaSize.width; + windowFrame.size.height += windowDeltaSize.height; + [[self window] setFrame:windowFrame display:NO]; +} + +- (NSView*)viewForSearchEngine:(const TemplateURL*)engine + atIndex:(size_t)index { + bool useImages = false; +#if defined(GOOGLE_CHROME_BUILD) + useImages = true; +#endif + + // Make the engine identifier. + NSView* engineIdentifier = nil; // either the logo or the text label + + int logoId = engine->logo_id(); + if (useImages && logoId > 0) { + NSImage* logoImage = + ResourceBundle::GetSharedInstance().GetNSImageNamed(logoId); + NSRect logoBounds = NSZeroRect; + logoBounds.size = [logoImage size]; + NSImageView* logoView = + [[[NSImageView alloc] initWithFrame:logoBounds] autorelease]; + [logoView setImage:logoImage]; + [logoView setEditable:NO]; + + // Tooltip text provides accessibility. + [logoView setToolTip:base::SysWideToNSString(engine->short_name())]; + engineIdentifier = logoView; + } else { + // No logo -- we must show a text label. + NSRect labelBounds = NSMakeRect(0, 0, kLogoLabelWidth, kLogoLabelHeight); + NSTextField* labelField = + [[[NSTextField alloc] initWithFrame:labelBounds] autorelease]; + [labelField setBezeled:NO]; + [labelField setEditable:NO]; + [labelField setSelectable:NO]; + + scoped_nsobject<NSMutableParagraphStyle> paragraphStyle( + [[NSMutableParagraphStyle alloc] init]); + [paragraphStyle setAlignment:NSCenterTextAlignment]; + NSDictionary* attrs = [NSDictionary dictionaryWithObjectsAndKeys: + [NSFont boldSystemFontOfSize:13], NSFontAttributeName, + paragraphStyle.get(), NSParagraphStyleAttributeName, + nil]; + + NSString* value = base::SysWideToNSString(engine->short_name()); + scoped_nsobject<NSAttributedString> attrValue( + [[NSAttributedString alloc] initWithString:value + attributes:attrs]); + + [labelField setAttributedStringValue:attrValue.get()]; + + engineIdentifier = labelField; + } + + // Make the "Choose" button. + scoped_nsobject<NSButton> chooseButton( + [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 34)]); + [chooseButton setBezelStyle:NSRoundedBezelStyle]; + [[chooseButton cell] setFont:[NSFont systemFontOfSize: + [NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; + [chooseButton setTitle:l10n_util::GetNSStringWithFixup(IDS_FR_SEARCH_CHOOSE)]; + [GTMUILocalizerAndLayoutTweaker sizeToFitView:chooseButton.get()]; + [chooseButton setTag:index]; + [chooseButton setTarget:self]; + [chooseButton setAction:@selector(searchEngineSelected:)]; + + // Put 'em together. + NSRect engineIdentifierFrame = [engineIdentifier frame]; + NSRect chooseButtonFrame = [chooseButton frame]; + + NSRect containingViewFrame = NSZeroRect; + containingViewFrame.size.width += engineIdentifierFrame.size.width; + containingViewFrame.size.height += engineIdentifierFrame.size.height; + containingViewFrame.size.height += kLogoButtonSpacing; + containingViewFrame.size.height += chooseButtonFrame.size.height; + + NSView* containingView = + [[[NSView alloc] initWithFrame:containingViewFrame] autorelease]; + + [containingView addSubview:engineIdentifier]; + engineIdentifierFrame.origin.y = + chooseButtonFrame.size.height + kLogoButtonSpacing; + [engineIdentifier setFrame:engineIdentifierFrame]; + + [containingView addSubview:chooseButton]; + chooseButtonFrame.origin.x = + int((containingViewFrame.size.width - chooseButtonFrame.size.width) / 2); + [chooseButton setFrame:chooseButtonFrame]; + + return containingView; +} + +- (NSFont*)mainLabelFont { + return [NSFont boldSystemFontOfSize:13]; +} + +- (IBAction)searchEngineSelected:(id)sender { + [[self window] close]; + [NSApp stopModalWithCode:[sender tag]]; +} + +@end |