• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "chrome/browser/ui/views/first_run_search_engine_view.h"
6 
7 #include <algorithm>
8 #include <map>
9 #include <vector>
10 
11 #include "base/i18n/rtl.h"
12 #include "base/rand_util.h"
13 #include "base/time.h"
14 #include "base/utf_string_conversions.h"
15 #include "chrome/browser/first_run/first_run.h"
16 #include "chrome/browser/first_run/first_run_dialog.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/search_engines/search_engine_type.h"
19 #include "chrome/browser/search_engines/template_url.h"
20 #include "chrome/browser/search_engines/template_url_model.h"
21 #include "chrome/browser/ui/options/options_window.h"
22 #include "grit/chromium_strings.h"
23 #include "grit/generated_resources.h"
24 #include "grit/google_chrome_strings.h"
25 #include "grit/locale_settings.h"
26 #include "grit/theme_resources.h"
27 #include "ui/base/accessibility/accessible_view_state.h"
28 #include "ui/base/l10n/l10n_util.h"
29 #include "ui/base/resource/resource_bundle.h"
30 #include "ui/gfx/canvas.h"
31 #include "ui/gfx/font.h"
32 #include "views/controls/button/button.h"
33 #include "views/controls/image_view.h"
34 #include "views/controls/label.h"
35 #include "views/controls/separator.h"
36 #include "views/focus/accelerator_handler.h"
37 #include "views/layout/layout_constants.h"
38 #include "views/view_text_utils.h"
39 #include "views/widget/widget.h"
40 #include "views/window/window.h"
41 
42 namespace {
43 
44 // Size to scale logos down to if showing 4 instead of 3 choices. Logo images
45 // are all originally sized at 180 x 120 pixels, with the logo text baseline
46 // located 74 pixels beneath the top of the image.
47 const int kSmallLogoWidth = 132;
48 const int kSmallLogoHeight = 88;
49 
50 // Used to pad text label height so it fits nicely in view.
51 const int kLabelPadding = 25;
52 
53 }  // namespace
54 
55 namespace first_run {
56 
ShowFirstRunDialog(Profile * profile,bool randomize_search_engine_experiment)57 void ShowFirstRunDialog(Profile* profile,
58                         bool randomize_search_engine_experiment) {
59   // If the default search is managed via policy, we don't ask the user to
60   // choose.
61   TemplateURLModel* model = profile->GetTemplateURLModel();
62   if (FirstRun::SearchEngineSelectorDisallowed() || !model ||
63       model->is_default_search_managed()) {
64     return;
65   }
66 
67   views::Window* window = views::Window::CreateChromeWindow(
68       NULL,
69       gfx::Rect(),
70       new FirstRunSearchEngineView(
71           profile, randomize_search_engine_experiment));
72   DCHECK(window);
73 
74   window->SetIsAlwaysOnTop(true);
75   window->Show();
76   views::AcceleratorHandler accelerator_handler;
77   MessageLoopForUI::current()->Run(&accelerator_handler);
78   window->CloseWindow();
79 }
80 
81 }  // namespace first_run
82 
SearchEngineChoice(views::ButtonListener * listener,const TemplateURL * search_engine,bool use_small_logos)83 SearchEngineChoice::SearchEngineChoice(views::ButtonListener* listener,
84                                        const TemplateURL* search_engine,
85                                        bool use_small_logos)
86     : NativeButton(
87           listener,
88           UTF16ToWide(l10n_util::GetStringUTF16(IDS_FR_SEARCH_CHOOSE))),
89       is_image_label_(false),
90       search_engine_(search_engine),
91       slot_(0) {
92   bool use_images = false;
93 #if defined(GOOGLE_CHROME_BUILD)
94   use_images = true;
95 #endif
96   int logo_id = search_engine_->logo_id();
97   if (use_images && logo_id != kNoSearchEngineLogo) {
98     is_image_label_ = true;
99     views::ImageView* logo_image = new views::ImageView();
100     SkBitmap* logo_bmp =
101         ResourceBundle::GetSharedInstance().GetBitmapNamed(logo_id);
102     logo_image->SetImage(logo_bmp);
103     if (use_small_logos)
104       logo_image->SetImageSize(gfx::Size(kSmallLogoWidth, kSmallLogoHeight));
105     // Tooltip text provides accessibility for low-vision users.
106     logo_image->SetTooltipText(search_engine_->short_name());
107     choice_view_ = logo_image;
108   } else {
109     // No logo -- we must show a text label.
110     views::Label* logo_label = new views::Label(search_engine_->short_name());
111     logo_label->SetColor(SK_ColorDKGRAY);
112     logo_label->SetFont(logo_label->font().DeriveFont(3, gfx::Font::BOLD));
113     logo_label->SetHorizontalAlignment(views::Label::ALIGN_CENTER);
114     logo_label->SetTooltipText(search_engine_->short_name());
115     logo_label->SetMultiLine(true);
116     logo_label->SizeToFit(kSmallLogoWidth);
117     choice_view_ = logo_label;
118   }
119 
120   // The accessible name of the button provides accessibility for
121   // screenreaders. It uses the browser name rather than the text of the
122   // button "Choose", since it's not obvious to a screenreader user which
123   // browser each button corresponds to.
124   SetAccessibleName(WideToUTF16Hack(search_engine_->short_name()));
125 }
126 
GetChoiceViewWidth()127 int SearchEngineChoice::GetChoiceViewWidth() {
128   if (is_image_label_)
129     return choice_view_->GetPreferredSize().width();
130   else
131     return kSmallLogoWidth;
132 }
133 
GetChoiceViewHeight()134 int SearchEngineChoice::GetChoiceViewHeight() {
135   if (!is_image_label_) {
136     // Labels need to be padded to look nicer.
137     return choice_view_->GetPreferredSize().height() + kLabelPadding;
138   } else {
139     return choice_view_->GetPreferredSize().height();
140   }
141 }
142 
SetChoiceViewBounds(int x,int y,int width,int height)143 void SearchEngineChoice::SetChoiceViewBounds(int x, int y, int width,
144                                              int height) {
145   choice_view_->SetBounds(x, y, width, height);
146 }
147 
FirstRunSearchEngineView(Profile * profile,bool randomize)148 FirstRunSearchEngineView::FirstRunSearchEngineView(
149     Profile* profile, bool randomize)
150     : background_image_(NULL),
151       profile_(profile),
152       text_direction_is_rtl_(base::i18n::IsRTL()),
153       randomize_(randomize) {
154   // Don't show ourselves until all the search engines have loaded from
155   // the profile -- otherwise we have nothing to show.
156   SetVisible(false);
157 
158   // Start loading the search engines for the given profile.
159   search_engines_model_ = profile_->GetTemplateURLModel();
160   if (search_engines_model_) {
161     DCHECK(!search_engines_model_->loaded());
162     search_engines_model_->AddObserver(this);
163     search_engines_model_->Load();
164   } else {
165     NOTREACHED();
166   }
167   SetupControls();
168 }
169 
~FirstRunSearchEngineView()170 FirstRunSearchEngineView::~FirstRunSearchEngineView() {
171   search_engines_model_->RemoveObserver(this);
172 }
173 
ButtonPressed(views::Button * sender,const views::Event & event)174 void FirstRunSearchEngineView::ButtonPressed(views::Button* sender,
175                                              const views::Event& event) {
176   SearchEngineChoice* choice = static_cast<SearchEngineChoice*>(sender);
177   TemplateURLModel* template_url_model = profile_->GetTemplateURLModel();
178   DCHECK(template_url_model);
179   template_url_model->SetSearchEngineDialogSlot(choice->slot());
180   const TemplateURL* default_search = choice->GetSearchEngine();
181   if (default_search)
182     template_url_model->SetDefaultSearchProvider(default_search);
183 
184   MessageLoop::current()->Quit();
185 }
186 
OnPaint(gfx::Canvas * canvas)187 void FirstRunSearchEngineView::OnPaint(gfx::Canvas* canvas) {
188   // Fill in behind the background image with the standard gray toolbar color.
189   canvas->FillRectInt(SkColorSetRGB(237, 238, 237), 0, 0, width(),
190                       background_image_->height());
191   // The rest of the dialog background should be white.
192   DCHECK(height() > background_image_->height());
193   canvas->FillRectInt(SK_ColorWHITE, 0, background_image_->height(), width(),
194                       height() - background_image_->height());
195 }
196 
OnTemplateURLModelChanged()197 void FirstRunSearchEngineView::OnTemplateURLModelChanged() {
198   using views::ImageView;
199 
200   // We only watch the search engine model change once, on load.  Remove
201   // observer so we don't try to redraw if engines change under us.
202   search_engines_model_->RemoveObserver(this);
203 
204   // Add search engines in search_engines_model_ to buttons list.  The
205   // first three will always be from prepopulated data.
206   std::vector<const TemplateURL*> template_urls =
207       search_engines_model_->GetTemplateURLs();
208 
209   // If we have fewer than two search engines, end search engine dialog
210   // immediately, leaving imported default search engine setting intact.
211   if (template_urls.size() < 2) {
212     MessageLoop::current()->Quit();
213     return;
214   }
215 
216   std::vector<const TemplateURL*>::iterator search_engine_iter;
217 
218   // Is user's default search engine included in first three prepopulated
219   // set?  If not, we need to expand the dialog to include a fourth engine.
220   const TemplateURL* default_search_engine =
221       search_engines_model_->GetDefaultSearchProvider();
222   // If the user's default choice is not in the first three search engines
223   // in template_urls, store it in |default_choice| and provide it as a
224   // fourth option.
225   SearchEngineChoice* default_choice = NULL;
226 
227   // First, see if we have 4 logos to show (in which case we use small logos).
228   // We show 4 logos when the default search engine the user has chosen is
229   // not one of the first three prepopulated engines.
230   if (template_urls.size() > 3) {
231     for (search_engine_iter = template_urls.begin() + 3;
232          search_engine_iter != template_urls.end();
233          ++search_engine_iter) {
234       if (default_search_engine == *search_engine_iter) {
235         default_choice = new SearchEngineChoice(this, *search_engine_iter,
236                                                 true);
237       }
238     }
239   }
240 
241   // Now that we know what size the logos should be, create new search engine
242   // choices for the view.  If there are 2 search engines, only show 2
243   // choices; for 3 or more, show 3 (unless the default is not one of the
244   // top 3, in which case show 4).
245   for (search_engine_iter = template_urls.begin();
246        search_engine_iter < template_urls.begin() +
247            (template_urls.size() < 3 ? 2 : 3);
248        ++search_engine_iter) {
249     // Push first three engines into buttons:
250     SearchEngineChoice* choice = new SearchEngineChoice(this,
251         *search_engine_iter, default_choice != NULL);
252     search_engine_choices_.push_back(choice);
253     AddChildView(choice->GetView());  // The logo or text view.
254     AddChildView(choice);  // The button associated with the choice.
255   }
256   // Push the default choice to the fourth position.
257   if (default_choice) {
258     search_engine_choices_.push_back(default_choice);
259     AddChildView(default_choice->GetView());  // The logo or text view.
260     AddChildView(default_choice);  // The button associated with the choice.
261   }
262 
263   // Randomize order of logos if option has been set.
264   if (randomize_) {
265     std::random_shuffle(search_engine_choices_.begin(),
266                         search_engine_choices_.end(),
267                         base::RandGenerator);
268     // Assign to each choice the position in which it is shown on the screen.
269     std::vector<SearchEngineChoice*>::iterator it;
270     int slot = 0;
271     for (it = search_engine_choices_.begin();
272          it != search_engine_choices_.end();
273          it++) {
274       (*it)->set_slot(slot++);
275     }
276   }
277 
278   // Now that we know how many logos to show, lay out and become visible.
279   SetVisible(true);
280   Layout();
281   SchedulePaint();
282 
283   // If the widget has detected that a screenreader is running, change the
284   // button names from "Choose" to the name of the search engine. This works
285   // around a bug that JAWS ignores the accessible name of a native button.
286   if (GetWidget() && GetWidget()->IsAccessibleWidget()) {
287     std::vector<SearchEngineChoice*>::iterator it;
288     for (it = search_engine_choices_.begin();
289          it != search_engine_choices_.end();
290          it++) {
291       (*it)->SetLabel((*it)->GetSearchEngine()->short_name());
292     }
293   }
294 
295   // This will tell screenreaders that they should read the full text
296   // of this dialog to the user now (rather than waiting for the user
297   // to explore it).
298   GetWidget()->NotifyAccessibilityEvent(
299       this, ui::AccessibilityTypes::EVENT_ALERT, true);
300 }
301 
GetPreferredSize()302 gfx::Size FirstRunSearchEngineView::GetPreferredSize() {
303   return views::Window::GetLocalizedContentsSize(
304       IDS_FIRSTRUN_SEARCH_ENGINE_SELECTION_WIDTH_CHARS,
305       IDS_FIRSTRUN_SEARCH_ENGINE_SELECTION_HEIGHT_LINES);
306 }
307 
SetupControls()308 void FirstRunSearchEngineView::SetupControls() {
309   using views::Background;
310   using views::ImageView;
311   using views::Label;
312   using views::NativeButton;
313 
314   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
315   background_image_ = new views::ImageView();
316   background_image_->SetImage(rb.GetBitmapNamed(IDR_SEARCH_ENGINE_DIALOG_TOP));
317   background_image_->EnableCanvasFlippingForRTLUI(true);
318   if (text_direction_is_rtl_) {
319     background_image_->SetHorizontalAlignment(ImageView::LEADING);
320   } else {
321     background_image_->SetHorizontalAlignment(ImageView::TRAILING);
322   }
323 
324   AddChildView(background_image_);
325 
326   int label_width = GetPreferredSize().width() - 2 * views::kPanelHorizMargin;
327 
328   // Add title and text asking the user to choose a search engine:
329   title_label_ = new Label(UTF16ToWide(l10n_util::GetStringUTF16(
330       IDS_FR_SEARCH_MAIN_LABEL)));
331   title_label_->SetColor(SK_ColorBLACK);
332   title_label_->SetFont(title_label_->font().DeriveFont(3, gfx::Font::BOLD));
333   title_label_->SetMultiLine(true);
334   title_label_->SetHorizontalAlignment(Label::ALIGN_LEFT);
335   title_label_->SizeToFit(label_width);
336   AddChildView(title_label_);
337 
338   text_label_ = new Label(UTF16ToWide(l10n_util::GetStringFUTF16(
339       IDS_FR_SEARCH_TEXT,
340       l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))));
341   text_label_->SetColor(SK_ColorBLACK);
342   text_label_->SetFont(text_label_->font().DeriveFont(1, gfx::Font::NORMAL));
343   text_label_->SetMultiLine(true);
344   text_label_->SetHorizontalAlignment(Label::ALIGN_LEFT);
345   text_label_->SizeToFit(label_width);
346   AddChildView(text_label_);
347 }
348 
Layout()349 void FirstRunSearchEngineView::Layout() {
350   // Disable the close button.
351   GetWindow()->EnableClose(false);
352 
353   gfx::Size pref_size = background_image_->GetPreferredSize();
354   background_image_->SetBounds(0, 0, GetPreferredSize().width(),
355                                pref_size.height());
356 
357   // General vertical spacing between elements:
358   const int kVertSpacing = 8;
359   // Percentage of vertical space around logos to use for upper padding.
360   const double kUpperPaddingPercent = 0.4;
361 
362   int num_choices = search_engine_choices_.size();
363   int label_width = GetPreferredSize().width() - 2 * views::kPanelHorizMargin;
364   int label_height = GetPreferredSize().height() - 2 * views::kPanelVertMargin;
365 
366   // Set title.
367   title_label_->SetBounds(
368       views::kPanelHorizMargin,
369       pref_size.height() / 2 - title_label_->GetPreferredSize().height() / 2,
370       label_width,
371       title_label_->GetPreferredSize().height());
372 
373   int next_v_space = background_image_->height() + kVertSpacing * 2;
374 
375   // Set text describing search engine hooked into omnibox.
376   text_label_->SetBounds(views::kPanelHorizMargin,
377                          next_v_space,
378                          label_width,
379                          text_label_->GetPreferredSize().height());
380   next_v_space = text_label_->y() +
381                  text_label_->height() + kVertSpacing;
382 
383   // Logos and buttons
384   if (num_choices > 0) {
385     // All search engine logos are sized the same, so the size of the first is
386     // generally valid as the size of all.
387     int logo_width = search_engine_choices_[0]->GetChoiceViewWidth();
388     int logo_height = search_engine_choices_[0]->GetChoiceViewHeight();
389     int button_width = search_engine_choices_[0]->GetPreferredSize().width();
390     int button_height = search_engine_choices_[0]->GetPreferredSize().height();
391 
392     int logo_section_height = logo_height + kVertSpacing + button_height;
393     // Upper logo margin gives the amount of whitespace between the text label
394     // and the logo field.  The total amount of whitespace available is equal
395     // to the height of the whole label subtracting the heights of the logo
396     // section itself, the top image, the text label, and vertical spacing
397     // between those elements.
398     int upper_logo_margin =
399         static_cast<int>((label_height - logo_section_height -
400             background_image_->height() - text_label_->height()
401             - kVertSpacing + views::kPanelVertMargin) * kUpperPaddingPercent);
402 
403     next_v_space = text_label_->y() + text_label_->height() +
404                    upper_logo_margin;
405 
406     // The search engine logos (which all have equal size):
407     int logo_padding =
408         (label_width - (num_choices * logo_width)) / (num_choices + 1);
409 
410     search_engine_choices_[0]->SetChoiceViewBounds(
411         views::kPanelHorizMargin + logo_padding, next_v_space, logo_width,
412         logo_height);
413 
414     int next_h_space = search_engine_choices_[0]->GetView()->x() +
415                        logo_width + logo_padding;
416     search_engine_choices_[1]->SetChoiceViewBounds(
417         next_h_space, next_v_space, logo_width, logo_height);
418 
419     next_h_space = search_engine_choices_[1]->GetView()->x() + logo_width +
420                    logo_padding;
421     if (num_choices > 2) {
422       search_engine_choices_[2]->SetChoiceViewBounds(
423           next_h_space, next_v_space, logo_width, logo_height);
424     }
425 
426     if (num_choices > 3) {
427       next_h_space = search_engine_choices_[2]->GetView()->x() + logo_width +
428                      logo_padding;
429       search_engine_choices_[3]->SetChoiceViewBounds(
430           next_h_space, next_v_space, logo_width, logo_height);
431     }
432 
433     next_v_space = search_engine_choices_[0]->GetView()->y() + logo_height +
434                    kVertSpacing;
435 
436     // The buttons for search engine selection:
437     int button_padding = logo_padding + logo_width / 2 - button_width / 2;
438 
439     search_engine_choices_[0]->SetBounds(
440         views::kPanelHorizMargin + button_padding, next_v_space,
441         button_width, button_height);
442 
443     next_h_space = search_engine_choices_[0]->x() + logo_width + logo_padding;
444     search_engine_choices_[1]->SetBounds(next_h_space, next_v_space,
445                                          button_width, button_height);
446     next_h_space = search_engine_choices_[1]->x() + logo_width + logo_padding;
447     if (num_choices > 2) {
448       search_engine_choices_[2]->SetBounds(next_h_space, next_v_space,
449                                            button_width, button_height);
450     }
451 
452     if (num_choices > 3) {
453       next_h_space = search_engine_choices_[2]->x() + logo_width +
454                      logo_padding;
455       search_engine_choices_[3]->SetBounds(next_h_space, next_v_space,
456                                            button_width, button_height);
457     }
458   }  // if (search_engine_choices.size() > 0)
459 }
460 
GetAccessibleState(ui::AccessibleViewState * state)461 void FirstRunSearchEngineView::GetAccessibleState(
462     ui::AccessibleViewState* state) {
463   state->role = ui::AccessibilityTypes::ROLE_ALERT;
464 }
465 
GetWindowTitle() const466 std::wstring FirstRunSearchEngineView::GetWindowTitle() const {
467   return UTF16ToWide(l10n_util::GetStringUTF16(IDS_FIRSTRUN_DLG_TITLE));
468 }
469