diff options
Diffstat (limited to 'chrome/browser/ui/views/first_run_search_engine_view.cc')
-rw-r--r-- | chrome/browser/ui/views/first_run_search_engine_view.cc | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/chrome/browser/ui/views/first_run_search_engine_view.cc b/chrome/browser/ui/views/first_run_search_engine_view.cc new file mode 100644 index 0000000..6688d9e --- /dev/null +++ b/chrome/browser/ui/views/first_run_search_engine_view.cc @@ -0,0 +1,430 @@ +// 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. + +#include "chrome/browser/views/first_run_search_engine_view.h" + +#include <algorithm> +#include <map> +#include <vector> + +#include "app/l10n_util.h" +#include "app/resource_bundle.h" +#include "base/i18n/rtl.h" +#include "base/rand_util.h" +#include "base/time.h" +#include "chrome/browser/options_window.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/search_engines/search_engine_type.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/browser/search_engines/template_url_model.h" +#include "gfx/canvas.h" +#include "gfx/font.h" +#include "grit/browser_resources.h" +#include "grit/chromium_strings.h" +#include "grit/google_chrome_strings.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "grit/theme_resources.h" +#include "views/controls/button/button.h" +#include "views/controls/image_view.h" +#include "views/controls/label.h" +#include "views/controls/separator.h" +#include "views/standard_layout.h" +#include "views/view_text_utils.h" +#include "views/widget/widget.h" +#include "views/window/window.h" + +using base::Time; + +namespace { + +// Size to scale logos down to if showing 4 instead of 3 choices. Logo images +// are all originally sized at 180 x 120 pixels, with the logo text baseline +// located 74 pixels beneath the top of the image. +const int kSmallLogoWidth = 132; +const int kSmallLogoHeight = 88; + +// Used to pad text label height so it fits nicely in view. +const int kLabelPadding = 25; + +} // namespace + +SearchEngineChoice::SearchEngineChoice(views::ButtonListener* listener, + const TemplateURL* search_engine, + bool use_small_logos) + : NativeButton(listener, l10n_util::GetString(IDS_FR_SEARCH_CHOOSE)), + is_image_label_(false), + search_engine_(search_engine), + slot_(0) { + bool use_images = false; +#if defined(GOOGLE_CHROME_BUILD) + use_images = true; +#endif + int logo_id = search_engine_->logo_id(); + if (use_images && logo_id != kNoSearchEngineLogo) { + is_image_label_ = true; + views::ImageView* logo_image = new views::ImageView(); + SkBitmap* logo_bmp = + ResourceBundle::GetSharedInstance().GetBitmapNamed(logo_id); + logo_image->SetImage(logo_bmp); + if (use_small_logos) + logo_image->SetImageSize(gfx::Size(kSmallLogoWidth, kSmallLogoHeight)); + // Tooltip text provides accessibility for low-vision users. + logo_image->SetTooltipText(search_engine_->short_name()); + choice_view_ = logo_image; + } else { + // No logo -- we must show a text label. + views::Label* logo_label = new views::Label(search_engine_->short_name()); + logo_label->SetColor(SK_ColorDKGRAY); + logo_label->SetFont(logo_label->font().DeriveFont(3, gfx::Font::BOLD)); + logo_label->SetHorizontalAlignment(views::Label::ALIGN_CENTER); + logo_label->SetTooltipText(search_engine_->short_name()); + logo_label->SetMultiLine(true); + logo_label->SizeToFit(kSmallLogoWidth); + choice_view_ = logo_label; + } + + // The accessible name of the button provides accessibility for + // screenreaders. It uses the browser name rather than the text of the + // button "Choose", since it's not obvious to a screenreader user which + // browser each button corresponds to. + SetAccessibleName(search_engine_->short_name()); +} + +int SearchEngineChoice::GetChoiceViewWidth() { + if (is_image_label_) + return choice_view_->GetPreferredSize().width(); + else + return kSmallLogoWidth; +} + +int SearchEngineChoice::GetChoiceViewHeight() { + if (!is_image_label_) { + // Labels need to be padded to look nicer. + return choice_view_->GetPreferredSize().height() + kLabelPadding; + } else { + return choice_view_->GetPreferredSize().height(); + } +} + +void SearchEngineChoice::SetChoiceViewBounds(int x, int y, int width, + int height) { + choice_view_->SetBounds(x, y, width, height); +} + +FirstRunSearchEngineView::FirstRunSearchEngineView( + Profile* profile, bool randomize) + : background_image_(NULL), + profile_(profile), + text_direction_is_rtl_(base::i18n::IsRTL()), + randomize_(randomize) { + // Don't show ourselves until all the search engines have loaded from + // the profile -- otherwise we have nothing to show. + SetVisible(false); + + // Start loading the search engines for the given profile. + search_engines_model_ = profile_->GetTemplateURLModel(); + if (search_engines_model_) { + DCHECK(!search_engines_model_->loaded()); + search_engines_model_->AddObserver(this); + search_engines_model_->Load(); + } else { + NOTREACHED(); + } + SetupControls(); +} + +FirstRunSearchEngineView::~FirstRunSearchEngineView() { + search_engines_model_->RemoveObserver(this); +} + +void FirstRunSearchEngineView::ButtonPressed(views::Button* sender, + const views::Event& event) { + SearchEngineChoice* choice = static_cast<SearchEngineChoice*>(sender); + TemplateURLModel* template_url_model = profile_->GetTemplateURLModel(); + DCHECK(template_url_model); + template_url_model->SetSearchEngineDialogSlot(choice->slot()); + const TemplateURL* default_search = choice->GetSearchEngine(); + if (default_search) + template_url_model->SetDefaultSearchProvider(default_search); + + MessageLoop::current()->Quit(); +} + +void FirstRunSearchEngineView::Paint(gfx::Canvas* canvas) { + // Fill in behind the background image with the standard gray toolbar color. + canvas->FillRectInt(SkColorSetRGB(237, 238, 237), 0, 0, width(), + background_image_->height()); + // The rest of the dialog background should be white. + DCHECK(height() > background_image_->height()); + canvas->FillRectInt(SK_ColorWHITE, 0, background_image_->height(), width(), + height() - background_image_->height()); +} + +void FirstRunSearchEngineView::OnTemplateURLModelChanged() { + using views::ImageView; + + // We only watch the search engine model change once, on load. Remove + // observer so we don't try to redraw if engines change under us. + search_engines_model_->RemoveObserver(this); + + // Add search engines in search_engines_model_ to buttons list. The + // first three will always be from prepopulated data. + std::vector<const TemplateURL*> template_urls = + search_engines_model_->GetTemplateURLs(); + + // If we have fewer than two search engines, end search engine dialog + // immediately, leaving imported default search engine setting intact. + if (template_urls.size() < 2) { + MessageLoop::current()->Quit(); + return; + } + + std::vector<const TemplateURL*>::iterator search_engine_iter; + + // Is user's default search engine included in first three prepopulated + // set? If not, we need to expand the dialog to include a fourth engine. + const TemplateURL* default_search_engine = + search_engines_model_->GetDefaultSearchProvider(); + // If the user's default choice is not in the first three search engines + // in template_urls, store it in |default_choice| and provide it as a + // fourth option. + SearchEngineChoice* default_choice = NULL; + + // First, see if we have 4 logos to show (in which case we use small logos). + // We show 4 logos when the default search engine the user has chosen is + // not one of the first three prepopulated engines. + if (template_urls.size() > 3) { + for (search_engine_iter = template_urls.begin() + 3; + search_engine_iter != template_urls.end(); + ++search_engine_iter) { + if (default_search_engine == *search_engine_iter) { + default_choice = new SearchEngineChoice(this, *search_engine_iter, + true); + } + } + } + + // Now that we know what size the logos should be, create new search engine + // choices for the view. If there are 2 search engines, only show 2 + // choices; for 3 or more, show 3 (unless the default is not one of the + // top 3, in which case show 4). + for (search_engine_iter = template_urls.begin(); + search_engine_iter < template_urls.begin() + + (template_urls.size() < 3 ? 2 : 3); + ++search_engine_iter) { + // Push first three engines into buttons: + SearchEngineChoice* choice = new SearchEngineChoice(this, + *search_engine_iter, default_choice != NULL); + search_engine_choices_.push_back(choice); + AddChildView(choice->GetView()); // The logo or text view. + AddChildView(choice); // The button associated with the choice. + } + // Push the default choice to the fourth position. + if (default_choice) { + search_engine_choices_.push_back(default_choice); + AddChildView(default_choice->GetView()); // The logo or text view. + AddChildView(default_choice); // The button associated with the choice. + } + + // Randomize order of logos if option has been set. + if (randomize_) { + std::random_shuffle(search_engine_choices_.begin(), + search_engine_choices_.end(), + base::RandGenerator); + // Assign to each choice the position in which it is shown on the screen. + std::vector<SearchEngineChoice*>::iterator it; + int slot = 0; + for (it = search_engine_choices_.begin(); + it != search_engine_choices_.end(); + it++) { + (*it)->set_slot(slot++); + } + } + + // Now that we know how many logos to show, lay out and become visible. + SetVisible(true); + Layout(); + SchedulePaint(); + + // If the widget has detected that a screenreader is running, change the + // button names from "Choose" to the name of the search engine. This works + // around a bug that JAWS ignores the accessible name of a native button. + if (GetWidget() && GetWidget()->IsAccessibleWidget()) { + std::vector<SearchEngineChoice*>::iterator it; + for (it = search_engine_choices_.begin(); + it != search_engine_choices_.end(); + it++) { + (*it)->SetLabel((*it)->GetSearchEngine()->short_name()); + } + } + + // This will tell screenreaders that they should read the full text + // of this dialog to the user now (rather than waiting for the user + // to explore it). + NotifyAccessibilityEvent(AccessibilityTypes::EVENT_ALERT); +} + +gfx::Size FirstRunSearchEngineView::GetPreferredSize() { + return views::Window::GetLocalizedContentsSize( + IDS_FIRSTRUN_SEARCH_ENGINE_SELECTION_WIDTH_CHARS, + IDS_FIRSTRUN_SEARCH_ENGINE_SELECTION_HEIGHT_LINES); +} + +void FirstRunSearchEngineView::SetupControls() { + using views::Background; + using views::ImageView; + using views::Label; + using views::NativeButton; + + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + background_image_ = new views::ImageView(); + background_image_->SetImage(rb.GetBitmapNamed(IDR_SEARCH_ENGINE_DIALOG_TOP)); + background_image_->EnableCanvasFlippingForRTLUI(true); + if (text_direction_is_rtl_) { + background_image_->SetHorizontalAlignment(ImageView::LEADING); + } else { + background_image_->SetHorizontalAlignment(ImageView::TRAILING); + } + + AddChildView(background_image_); + + int label_width = GetPreferredSize().width() - 2 * kPanelHorizMargin; + + // Add title and text asking the user to choose a search engine: + title_label_ = new Label(l10n_util::GetString( + IDS_FR_SEARCH_MAIN_LABEL)); + title_label_->SetColor(SK_ColorBLACK); + title_label_->SetFont(title_label_->font().DeriveFont(3, gfx::Font::BOLD)); + title_label_->SetMultiLine(true); + title_label_->SetHorizontalAlignment(Label::ALIGN_LEFT); + title_label_->SizeToFit(label_width); + AddChildView(title_label_); + + text_label_ = new Label(l10n_util::GetStringF(IDS_FR_SEARCH_TEXT, + l10n_util::GetString(IDS_PRODUCT_NAME))); + text_label_->SetColor(SK_ColorBLACK); + text_label_->SetFont(text_label_->font().DeriveFont(1, gfx::Font::NORMAL)); + text_label_->SetMultiLine(true); + text_label_->SetHorizontalAlignment(Label::ALIGN_LEFT); + text_label_->SizeToFit(label_width); + AddChildView(text_label_); +} + +void FirstRunSearchEngineView::Layout() { + // Disable the close button. + GetWindow()->EnableClose(false); + + gfx::Size pref_size = background_image_->GetPreferredSize(); + background_image_->SetBounds(0, 0, GetPreferredSize().width(), + pref_size.height()); + + // General vertical spacing between elements: + const int kVertSpacing = 8; + // Percentage of vertical space around logos to use for upper padding. + const double kUpperPaddingPercent = 0.4; + + int num_choices = search_engine_choices_.size(); + int label_width = GetPreferredSize().width() - 2 * kPanelHorizMargin; + int label_height = GetPreferredSize().height() - 2 * kPanelVertMargin; + + // Set title. + title_label_->SetBounds(kPanelHorizMargin, + pref_size.height() / 2 - title_label_->GetPreferredSize().height() / 2, + label_width, title_label_->GetPreferredSize().height()); + + int next_v_space = background_image_->height() + kVertSpacing * 2; + + // Set text describing search engine hooked into omnibox. + text_label_->SetBounds(kPanelHorizMargin, next_v_space, + label_width, + text_label_->GetPreferredSize().height()); + next_v_space = text_label_->y() + + text_label_->height() + kVertSpacing; + + // Logos and buttons + if (num_choices > 0) { + // All search engine logos are sized the same, so the size of the first is + // generally valid as the size of all. + int logo_width = search_engine_choices_[0]->GetChoiceViewWidth(); + int logo_height = search_engine_choices_[0]->GetChoiceViewHeight(); + int button_width = search_engine_choices_[0]->GetPreferredSize().width(); + int button_height = search_engine_choices_[0]->GetPreferredSize().height(); + + int logo_section_height = logo_height + kVertSpacing + button_height; + // Upper logo margin gives the amount of whitespace between the text label + // and the logo field. The total amount of whitespace available is equal + // to the height of the whole label subtracting the heights of the logo + // section itself, the top image, the text label, and vertical spacing + // between those elements. + int upper_logo_margin = + static_cast<int>((label_height - logo_section_height - + background_image_->height() - text_label_->height() + - kVertSpacing + kPanelVertMargin) * kUpperPaddingPercent); + + next_v_space = text_label_->y() + text_label_->height() + + upper_logo_margin; + + // The search engine logos (which all have equal size): + int logo_padding = + (label_width - (num_choices * logo_width)) / (num_choices + 1); + + search_engine_choices_[0]->SetChoiceViewBounds( + kPanelHorizMargin + logo_padding, next_v_space, logo_width, + logo_height); + + int next_h_space = search_engine_choices_[0]->GetView()->x() + + logo_width + logo_padding; + search_engine_choices_[1]->SetChoiceViewBounds( + next_h_space, next_v_space, logo_width, logo_height); + + next_h_space = search_engine_choices_[1]->GetView()->x() + logo_width + + logo_padding; + if (num_choices > 2) { + search_engine_choices_[2]->SetChoiceViewBounds( + next_h_space, next_v_space, logo_width, logo_height); + } + + if (num_choices > 3) { + next_h_space = search_engine_choices_[2]->GetView()->x() + logo_width + + logo_padding; + search_engine_choices_[3]->SetChoiceViewBounds( + next_h_space, next_v_space, logo_width, logo_height); + } + + next_v_space = search_engine_choices_[0]->GetView()->y() + logo_height + + kVertSpacing; + + // The buttons for search engine selection: + int button_padding = logo_padding + logo_width / 2 - button_width / 2; + + search_engine_choices_[0]->SetBounds(kPanelHorizMargin + button_padding, + next_v_space, button_width, + button_height); + + next_h_space = search_engine_choices_[0]->x() + logo_width + logo_padding; + search_engine_choices_[1]->SetBounds(next_h_space, next_v_space, + button_width, button_height); + next_h_space = search_engine_choices_[1]->x() + logo_width + logo_padding; + if (num_choices > 2) { + search_engine_choices_[2]->SetBounds(next_h_space, next_v_space, + button_width, button_height); + } + + if (num_choices > 3) { + next_h_space = search_engine_choices_[2]->x() + logo_width + + logo_padding; + search_engine_choices_[3]->SetBounds(next_h_space, next_v_space, + button_width, button_height); + } + } // if (search_engine_choices.size() > 0) +} + +AccessibilityTypes::Role FirstRunSearchEngineView::GetAccessibleRole() { + return AccessibilityTypes::ROLE_ALERT; +} + +std::wstring FirstRunSearchEngineView::GetWindowTitle() const { + return l10n_util::GetString(IDS_FIRSTRUN_DLG_TITLE); +} |