// 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/search_engine_dialog_controller.h" #include <algorithm> #include "base/mac/mac_util.h" #include "base/sys_string_conversions.h" #include "base/time.h" #include "chrome/browser/profiles/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" #include "ui/base/l10n/l10n_util_mac.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/image.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 base::RefCounted<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 = [base::mac::MainAppBundle() pathForResource:@"SearchEngineDialog" ofType:@"nib"]; self = [super initWithWindowNibPath:nibpath owner:self]; if (self != nil) { bridge_ = new SearchEngineDialogControllerBridge(self); } return self; } - (void)dealloc { [super dealloc]; } - (IBAction)showWindow:(id)sender { searchEnginesModel_ = profile_->GetTemplateURLModel(); searchEnginesModel_->AddObserver(bridge_.get()); if (searchEnginesModel_->loaded()) { MessageLoop::current()->PostTask( FROM_HERE, NewRunnableMethod( bridge_.get(), &SearchEngineDialogControllerBridge::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(). GetNativeImageNamed(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().GetNativeImageNamed(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::SysUTF16ToNSString(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::SysUTF16ToNSString(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