Home | History | Annotate | Download | only in views
      1 // Copyright (c) 2012 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/edit_search_engine_dialog.h"
      6 
      7 #include "base/i18n/case_conversion.h"
      8 #include "base/i18n/rtl.h"
      9 #include "base/strings/string_util.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
     12 #include "chrome/browser/ui/search_engines/edit_search_engine_controller.h"
     13 #include "chrome/browser/ui/views/constrained_window_views.h"
     14 #include "chrome/grit/generated_resources.h"
     15 #include "components/search_engines/template_url.h"
     16 #include "grit/theme_resources.h"
     17 #include "ui/base/l10n/l10n_util.h"
     18 #include "ui/base/resource/resource_bundle.h"
     19 #include "ui/events/event.h"
     20 #include "ui/views/controls/image_view.h"
     21 #include "ui/views/controls/label.h"
     22 #include "ui/views/controls/textfield/textfield.h"
     23 #include "ui/views/layout/grid_layout.h"
     24 #include "ui/views/layout/layout_constants.h"
     25 #include "ui/views/widget/widget.h"
     26 #include "ui/views/window/dialog_client_view.h"
     27 #include "url/gurl.h"
     28 
     29 using views::GridLayout;
     30 using views::Textfield;
     31 
     32 namespace chrome {
     33 
     34 void EditSearchEngine(gfx::NativeWindow parent,
     35                       TemplateURL* template_url,
     36                       EditSearchEngineControllerDelegate* delegate,
     37                       Profile* profile) {
     38   EditSearchEngineDialog::Show(parent, template_url, delegate, profile);
     39 }
     40 
     41 }  // namespace chrome
     42 
     43 EditSearchEngineDialog::EditSearchEngineDialog(
     44     TemplateURL* template_url,
     45     EditSearchEngineControllerDelegate* delegate,
     46     Profile* profile)
     47     : controller_(new EditSearchEngineController(template_url,
     48                                                  delegate,
     49                                                  profile)) {
     50   Init();
     51 }
     52 
     53 EditSearchEngineDialog::~EditSearchEngineDialog() {
     54 }
     55 
     56 // static
     57 void EditSearchEngineDialog::Show(gfx::NativeWindow parent,
     58                                   TemplateURL* template_url,
     59                                   EditSearchEngineControllerDelegate* delegate,
     60                                   Profile* profile) {
     61   EditSearchEngineDialog* contents =
     62       new EditSearchEngineDialog(template_url, delegate, profile);
     63   // Window interprets an empty rectangle as needing to query the content for
     64   // the size as well as centering relative to the parent.
     65   CreateBrowserModalDialogViews(contents, parent);
     66   contents->GetWidget()->Show();
     67   contents->GetDialogClientView()->UpdateDialogButtons();
     68   contents->title_tf_->SelectAll(true);
     69   contents->title_tf_->RequestFocus();
     70 }
     71 
     72 ui::ModalType EditSearchEngineDialog::GetModalType() const {
     73   return ui::MODAL_TYPE_WINDOW;
     74 }
     75 
     76 base::string16 EditSearchEngineDialog::GetWindowTitle() const {
     77   return l10n_util::GetStringUTF16(controller_->template_url() ?
     78       IDS_SEARCH_ENGINES_EDITOR_EDIT_WINDOW_TITLE :
     79       IDS_SEARCH_ENGINES_EDITOR_NEW_WINDOW_TITLE);
     80 }
     81 
     82 bool EditSearchEngineDialog::IsDialogButtonEnabled(
     83     ui::DialogButton button) const {
     84   if (button == ui::DIALOG_BUTTON_OK) {
     85     return (controller_->IsKeywordValid(keyword_tf_->text()) &&
     86             controller_->IsTitleValid(title_tf_->text()) &&
     87             controller_->IsURLValid(base::UTF16ToUTF8(url_tf_->text())));
     88   }
     89   return true;
     90 }
     91 
     92 bool EditSearchEngineDialog::Cancel() {
     93   controller_->CleanUpCancelledAdd();
     94   return true;
     95 }
     96 
     97 bool EditSearchEngineDialog::Accept() {
     98   controller_->AcceptAddOrEdit(title_tf_->text(), keyword_tf_->text(),
     99                                base::UTF16ToUTF8(url_tf_->text()));
    100   return true;
    101 }
    102 
    103 void EditSearchEngineDialog::ContentsChanged(
    104     Textfield* sender,
    105     const base::string16& new_contents) {
    106   // Force the keyword text to be lowercase, keep the caret or selection.
    107   if (sender == keyword_tf_ && !sender->HasCompositionText()) {
    108     // TODO(msw): Prevent textfield scrolling with selection model changes here.
    109     const gfx::SelectionModel selection_model = sender->GetSelectionModel();
    110     sender->SetText(base::i18n::ToLower(new_contents));
    111     sender->SelectSelectionModel(selection_model);
    112   }
    113 
    114   GetDialogClientView()->UpdateDialogButtons();
    115   UpdateImageViews();
    116 }
    117 
    118 bool EditSearchEngineDialog::HandleKeyEvent(
    119     Textfield* sender,
    120     const ui::KeyEvent& key_event) {
    121   return false;
    122 }
    123 
    124 void EditSearchEngineDialog::Init() {
    125   // Create the views we'll need.
    126   if (controller_->template_url()) {
    127     title_tf_ = CreateTextfield(controller_->template_url()->short_name());
    128     keyword_tf_ = CreateTextfield(controller_->template_url()->keyword());
    129     url_tf_ = CreateTextfield(
    130         controller_->template_url()->url_ref().DisplayURL(
    131             UIThreadSearchTermsData(controller_->profile())));
    132     // We don't allow users to edit prepopulate URLs. This is done as
    133     // occasionally we need to update the URL of prepopulated TemplateURLs.
    134     url_tf_->SetReadOnly(controller_->template_url()->prepopulate_id() != 0);
    135   } else {
    136     title_tf_ = CreateTextfield(base::string16());
    137     keyword_tf_ = CreateTextfield(base::string16());
    138     url_tf_ = CreateTextfield(base::string16());
    139   }
    140   title_iv_ = new views::ImageView();
    141   keyword_iv_ = new views::ImageView();
    142   url_iv_ = new views::ImageView();
    143 
    144   UpdateImageViews();
    145 
    146   const int related_x = views::kRelatedControlHorizontalSpacing;
    147   const int related_y = views::kRelatedControlVerticalSpacing;
    148   const int unrelated_y = views::kUnrelatedControlVerticalSpacing;
    149 
    150   // View and GridLayout take care of deleting GridLayout for us.
    151   GridLayout* layout = GridLayout::CreatePanel(this);
    152   SetLayoutManager(layout);
    153 
    154   // Define the structure of the layout.
    155 
    156   // For the buttons.
    157   views::ColumnSet* column_set = layout->AddColumnSet(0);
    158   column_set->AddPaddingColumn(1, 0);
    159   column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0,
    160                         GridLayout::USE_PREF, 0, 0);
    161   column_set->AddPaddingColumn(0, related_x);
    162   column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0,
    163                         GridLayout::USE_PREF, 0, 0);
    164   column_set->LinkColumnSizes(1, 3, -1);
    165 
    166   // For the Textfields.
    167   column_set = layout->AddColumnSet(1);
    168   column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
    169                         GridLayout::USE_PREF, 0, 0);
    170   column_set->AddPaddingColumn(0, related_x);
    171   column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1,
    172                         GridLayout::USE_PREF, 0, 0);
    173   column_set->AddPaddingColumn(0, related_x);
    174   column_set->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0,
    175                         GridLayout::USE_PREF, 0, 0);
    176 
    177   // For the description.
    178   column_set = layout->AddColumnSet(2);
    179   column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0,
    180                         GridLayout::USE_PREF, 0, 0);
    181 
    182   // Add the contents.
    183   layout->StartRow(0, 1);
    184   layout->AddView(CreateLabel(IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_LABEL));
    185   layout->AddView(title_tf_);
    186   layout->AddView(title_iv_);
    187 
    188   layout->StartRowWithPadding(0, 1, 0, related_y);
    189   layout->AddView(CreateLabel(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_LABEL));
    190   layout->AddView(keyword_tf_);
    191   layout->AddView(keyword_iv_);
    192 
    193   layout->StartRowWithPadding(0, 1, 0, related_y);
    194   layout->AddView(CreateLabel(IDS_SEARCH_ENGINES_EDITOR_URL_LABEL));
    195   layout->AddView(url_tf_);
    196   layout->AddView(url_iv_);
    197 
    198   // On RTL UIs (such as Arabic and Hebrew) the description text is not
    199   // displayed correctly since it contains the substring "%s". This substring
    200   // is not interpreted by the Unicode BiDi algorithm as an LTR string and
    201   // therefore the end result is that the following right to left text is
    202   // displayed: ".three two s% one" (where 'one', 'two', etc. are words in
    203   // Hebrew).
    204   //
    205   // In order to fix this problem we transform the substring "%s" so that it
    206   // is displayed correctly when rendered in an RTL context.
    207   layout->StartRowWithPadding(0, 2, 0, unrelated_y);
    208   base::string16 description = l10n_util::GetStringUTF16(
    209       IDS_SEARCH_ENGINES_EDITOR_URL_DESCRIPTION_LABEL);
    210   if (base::i18n::IsRTL()) {
    211     const base::string16 reversed_percent(base::ASCIIToUTF16("s%"));
    212     base::string16::size_type percent_index =
    213         description.find(base::ASCIIToUTF16("%s"),
    214                          static_cast<base::string16::size_type>(0));
    215     if (percent_index != base::string16::npos)
    216       description.replace(percent_index,
    217                           reversed_percent.length(),
    218                           reversed_percent);
    219   }
    220 
    221   views::Label* description_label = new views::Label(description);
    222   description_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    223   layout->AddView(description_label);
    224 
    225   layout->AddPaddingRow(0, related_y);
    226 }
    227 
    228 views::Label* EditSearchEngineDialog::CreateLabel(int message_id) {
    229   views::Label* label =
    230       new views::Label(l10n_util::GetStringUTF16(message_id));
    231   label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    232   return label;
    233 }
    234 
    235 Textfield* EditSearchEngineDialog::CreateTextfield(const base::string16& text) {
    236   Textfield* text_field = new Textfield();
    237   text_field->SetText(text);
    238   text_field->set_controller(this);
    239   return text_field;
    240 }
    241 
    242 void EditSearchEngineDialog::UpdateImageViews() {
    243   UpdateImageView(keyword_iv_, controller_->IsKeywordValid(keyword_tf_->text()),
    244                   IDS_SEARCH_ENGINES_INVALID_KEYWORD_TT);
    245   UpdateImageView(url_iv_,
    246                   controller_->IsURLValid(base::UTF16ToUTF8(url_tf_->text())),
    247                   IDS_SEARCH_ENGINES_INVALID_URL_TT);
    248   UpdateImageView(title_iv_, controller_->IsTitleValid(title_tf_->text()),
    249                   IDS_SEARCH_ENGINES_INVALID_TITLE_TT);
    250 }
    251 
    252 void EditSearchEngineDialog::UpdateImageView(views::ImageView* image_view,
    253                                              bool is_valid,
    254                                              int invalid_message_id) {
    255   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    256   if (is_valid) {
    257     image_view->SetTooltipText(base::string16());
    258     image_view->SetImage(rb.GetImageSkiaNamed(IDR_INPUT_GOOD));
    259   } else {
    260     image_view->SetTooltipText(l10n_util::GetStringUTF16(invalid_message_id));
    261     image_view->SetImage(rb.GetImageSkiaNamed(IDR_INPUT_ALERT));
    262   }
    263 }
    264