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/search_engine_dialog_controller.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/mac/mac_util.h"
     10 #include "base/sys_string_conversions.h"
     11 #include "base/time.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/search_engines/template_url.h"
     14 #include "chrome/browser/search_engines/template_url_model.h"
     15 #include "chrome/browser/search_engines/template_url_model_observer.h"
     16 #include "grit/generated_resources.h"
     17 #include "grit/theme_resources.h"
     18 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
     19 #include "ui/base/l10n/l10n_util_mac.h"
     20 #include "ui/base/resource/resource_bundle.h"
     21 #include "ui/gfx/image.h"
     22 
     23 // Horizontal spacing between search engine choices.
     24 const int kSearchEngineSpacing = 20;
     25 
     26 // Vertical spacing between the search engine logo and the button underneath.
     27 const int kLogoButtonSpacing = 10;
     28 
     29 // Width of a label used in place of a logo.
     30 const int kLogoLabelWidth = 170;
     31 
     32 // Height of a label used in place of a logo.
     33 const int kLogoLabelHeight = 25;
     34 
     35 @interface SearchEngineDialogController (Private)
     36 - (void)onTemplateURLModelChanged;
     37 - (void)buildSearchEngineView;
     38 - (NSView*)viewForSearchEngine:(const TemplateURL*)engine
     39                        atIndex:(size_t)index;
     40 - (IBAction)searchEngineSelected:(id)sender;
     41 @end
     42 
     43 class SearchEngineDialogControllerBridge :
     44     public base::RefCounted<SearchEngineDialogControllerBridge>,
     45     public TemplateURLModelObserver {
     46  public:
     47   SearchEngineDialogControllerBridge(SearchEngineDialogController* controller);
     48 
     49   // TemplateURLModelObserver
     50   virtual void OnTemplateURLModelChanged();
     51 
     52  private:
     53   SearchEngineDialogController* controller_;
     54 };
     55 
     56 SearchEngineDialogControllerBridge::SearchEngineDialogControllerBridge(
     57     SearchEngineDialogController* controller) : controller_(controller) {
     58 }
     59 
     60 void SearchEngineDialogControllerBridge::OnTemplateURLModelChanged() {
     61   [controller_ onTemplateURLModelChanged];
     62   MessageLoop::current()->QuitNow();
     63 }
     64 
     65 @implementation SearchEngineDialogController
     66 
     67 @synthesize profile = profile_;
     68 @synthesize randomize = randomize_;
     69 
     70 - (id)init {
     71   NSString* nibpath =
     72       [base::mac::MainAppBundle() pathForResource:@"SearchEngineDialog"
     73                                           ofType:@"nib"];
     74   self = [super initWithWindowNibPath:nibpath owner:self];
     75   if (self != nil) {
     76     bridge_ = new SearchEngineDialogControllerBridge(self);
     77   }
     78   return self;
     79 }
     80 
     81 - (void)dealloc {
     82   [super dealloc];
     83 }
     84 
     85 - (IBAction)showWindow:(id)sender {
     86   searchEnginesModel_ = profile_->GetTemplateURLModel();
     87   searchEnginesModel_->AddObserver(bridge_.get());
     88 
     89   if (searchEnginesModel_->loaded()) {
     90     MessageLoop::current()->PostTask(
     91         FROM_HERE,
     92         NewRunnableMethod(
     93             bridge_.get(),
     94             &SearchEngineDialogControllerBridge::OnTemplateURLModelChanged));
     95   } else {
     96     searchEnginesModel_->Load();
     97   }
     98   MessageLoop::current()->Run();
     99 }
    100 
    101 - (void)onTemplateURLModelChanged {
    102   searchEnginesModel_->RemoveObserver(bridge_.get());
    103 
    104   // Add the search engines in the search_engines_model_ to the buttons list.
    105   // The first three will always be from prepopulated data.
    106   std::vector<const TemplateURL*> templateUrls =
    107       searchEnginesModel_->GetTemplateURLs();
    108 
    109   // If we have fewer than two search engines, end the search engine dialog
    110   // immediately, leaving the imported default search engine setting intact.
    111   if (templateUrls.size() < 2) {
    112     return;
    113   }
    114 
    115   NSWindow* win = [self window];
    116 
    117   [win setBackgroundColor:[NSColor whiteColor]];
    118 
    119   NSImage* headerImage = ResourceBundle::GetSharedInstance().
    120       GetNativeImageNamed(IDR_SEARCH_ENGINE_DIALOG_TOP);
    121   [headerImageView_ setImage:headerImage];
    122 
    123   // Is the user's default search engine included in the first three
    124   // prepopulated set? If not, we need to expand the dialog to include a fourth
    125   // engine.
    126   const TemplateURL* defaultSearchEngine =
    127       searchEnginesModel_->GetDefaultSearchProvider();
    128 
    129   std::vector<const TemplateURL*>::iterator engineIter =
    130       templateUrls.begin();
    131   for (int i = 0; engineIter != templateUrls.end(); ++i, ++engineIter) {
    132     if (i < 3) {
    133       choices_.push_back(*engineIter);
    134     } else {
    135       if (*engineIter == defaultSearchEngine)
    136         choices_.push_back(*engineIter);
    137     }
    138   }
    139 
    140   // Randomize the order of the logos if the option has been set.
    141   if (randomize_) {
    142     int seed = static_cast<int>(base::Time::Now().ToInternalValue());
    143     srand(seed);
    144     std::random_shuffle(choices_.begin(), choices_.end());
    145   }
    146 
    147   [self buildSearchEngineView];
    148 
    149   // Display the dialog.
    150   NSInteger choice = [NSApp runModalForWindow:win];
    151   searchEnginesModel_->SetDefaultSearchProvider(choices_.at(choice));
    152 }
    153 
    154 - (void)buildSearchEngineView {
    155   scoped_nsobject<NSMutableArray> searchEngineViews
    156       ([[NSMutableArray alloc] init]);
    157 
    158   for (size_t i = 0; i < choices_.size(); ++i)
    159     [searchEngineViews addObject:[self viewForSearchEngine:choices_.at(i)
    160                                                    atIndex:i]];
    161 
    162   NSSize newOverallSize = NSZeroSize;
    163   for (NSView* view in searchEngineViews.get()) {
    164     NSRect engineFrame = [view frame];
    165     engineFrame.origin = NSMakePoint(newOverallSize.width, 0);
    166     [searchEngineView_ addSubview:view];
    167     [view setFrame:engineFrame];
    168     newOverallSize = NSMakeSize(
    169         newOverallSize.width + NSWidth(engineFrame) + kSearchEngineSpacing,
    170         std::max(newOverallSize.height, NSHeight(engineFrame)));
    171   }
    172   newOverallSize.width -= kSearchEngineSpacing;
    173 
    174   // Resize the window to fit (and because it's bound on all sides it will
    175   // resize the search engine view).
    176   NSSize currentOverallSize = [searchEngineView_ bounds].size;
    177   NSSize deltaSize = NSMakeSize(
    178       newOverallSize.width - currentOverallSize.width,
    179       newOverallSize.height - currentOverallSize.height);
    180   NSSize windowDeltaSize = [searchEngineView_ convertSize:deltaSize toView:nil];
    181   NSRect windowFrame = [[self window] frame];
    182   windowFrame.size.width += windowDeltaSize.width;
    183   windowFrame.size.height += windowDeltaSize.height;
    184   [[self window] setFrame:windowFrame display:NO];
    185 }
    186 
    187 - (NSView*)viewForSearchEngine:(const TemplateURL*)engine
    188                        atIndex:(size_t)index {
    189   bool useImages = false;
    190 #if defined(GOOGLE_CHROME_BUILD)
    191   useImages = true;
    192 #endif
    193 
    194   // Make the engine identifier.
    195   NSView* engineIdentifier = nil;  // either the logo or the text label
    196 
    197   int logoId = engine->logo_id();
    198   if (useImages && logoId > 0) {
    199     NSImage* logoImage =
    200         ResourceBundle::GetSharedInstance().GetNativeImageNamed(logoId);
    201     NSRect logoBounds = NSZeroRect;
    202     logoBounds.size = [logoImage size];
    203     NSImageView* logoView =
    204         [[[NSImageView alloc] initWithFrame:logoBounds] autorelease];
    205     [logoView setImage:logoImage];
    206     [logoView setEditable:NO];
    207 
    208     // Tooltip text provides accessibility.
    209     [logoView setToolTip:base::SysUTF16ToNSString(engine->short_name())];
    210     engineIdentifier = logoView;
    211   } else {
    212     // No logo -- we must show a text label.
    213     NSRect labelBounds = NSMakeRect(0, 0, kLogoLabelWidth, kLogoLabelHeight);
    214     NSTextField* labelField =
    215         [[[NSTextField alloc] initWithFrame:labelBounds] autorelease];
    216     [labelField setBezeled:NO];
    217     [labelField setEditable:NO];
    218     [labelField setSelectable:NO];
    219 
    220     scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
    221         [[NSMutableParagraphStyle alloc] init]);
    222     [paragraphStyle setAlignment:NSCenterTextAlignment];
    223     NSDictionary* attrs = [NSDictionary dictionaryWithObjectsAndKeys:
    224         [NSFont boldSystemFontOfSize:13], NSFontAttributeName,
    225         paragraphStyle.get(), NSParagraphStyleAttributeName,
    226         nil];
    227 
    228     NSString* value = base::SysUTF16ToNSString(engine->short_name());
    229     scoped_nsobject<NSAttributedString> attrValue(
    230         [[NSAttributedString alloc] initWithString:value
    231                                         attributes:attrs]);
    232 
    233     [labelField setAttributedStringValue:attrValue.get()];
    234 
    235     engineIdentifier = labelField;
    236   }
    237 
    238   // Make the "Choose" button.
    239   scoped_nsobject<NSButton> chooseButton(
    240       [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 34)]);
    241   [chooseButton setBezelStyle:NSRoundedBezelStyle];
    242   [[chooseButton cell] setFont:[NSFont systemFontOfSize:
    243       [NSFont systemFontSizeForControlSize:NSRegularControlSize]]];
    244   [chooseButton setTitle:l10n_util::GetNSStringWithFixup(IDS_FR_SEARCH_CHOOSE)];
    245   [GTMUILocalizerAndLayoutTweaker sizeToFitView:chooseButton.get()];
    246   [chooseButton setTag:index];
    247   [chooseButton setTarget:self];
    248   [chooseButton setAction:@selector(searchEngineSelected:)];
    249 
    250   // Put 'em together.
    251   NSRect engineIdentifierFrame = [engineIdentifier frame];
    252   NSRect chooseButtonFrame = [chooseButton frame];
    253 
    254   NSRect containingViewFrame = NSZeroRect;
    255   containingViewFrame.size.width += engineIdentifierFrame.size.width;
    256   containingViewFrame.size.height += engineIdentifierFrame.size.height;
    257   containingViewFrame.size.height += kLogoButtonSpacing;
    258   containingViewFrame.size.height += chooseButtonFrame.size.height;
    259 
    260   NSView* containingView =
    261       [[[NSView alloc] initWithFrame:containingViewFrame] autorelease];
    262 
    263   [containingView addSubview:engineIdentifier];
    264   engineIdentifierFrame.origin.y =
    265       chooseButtonFrame.size.height + kLogoButtonSpacing;
    266   [engineIdentifier setFrame:engineIdentifierFrame];
    267 
    268   [containingView addSubview:chooseButton];
    269   chooseButtonFrame.origin.x =
    270       int((containingViewFrame.size.width - chooseButtonFrame.size.width) / 2);
    271   [chooseButton setFrame:chooseButtonFrame];
    272 
    273   return containingView;
    274 }
    275 
    276 - (NSFont*)mainLabelFont {
    277   return [NSFont boldSystemFontOfSize:13];
    278 }
    279 
    280 - (IBAction)searchEngineSelected:(id)sender {
    281   [[self window] close];
    282   [NSApp stopModalWithCode:[sender tag]];
    283 }
    284 
    285 @end
    286