diff options
author | jhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-14 21:29:00 +0000 |
---|---|---|
committer | jhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-14 21:29:00 +0000 |
commit | 74c2d8475aa9e4b4aa5a574ce9fb80d0a411df4a (patch) | |
tree | 2d2c0d87fc224db5d5d7c9babe94d11d1822b021 /chrome | |
parent | 442f14853ac893c4c7d9dac7c9292f933250e039 (diff) | |
download | chromium_src-74c2d8475aa9e4b4aa5a574ce9fb80d0a411df4a.zip chromium_src-74c2d8475aa9e4b4aa5a574ce9fb80d0a411df4a.tar.gz chromium_src-74c2d8475aa9e4b4aa5a574ce9fb80d0a411df4a.tar.bz2 |
Revert 78081 - Options: Remove more unused dialogs.
BUG=75320
TEST=none
Review URL: http://codereview.chromium.org/6685042
TBR=jhawkins@chromium.org
Review URL: http://codereview.chromium.org/6675002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@78097 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/ui/gtk/browser_window_gtk.cc | 2 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/clear_browsing_data_dialog_gtk.cc | 249 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/clear_browsing_data_dialog_gtk.h | 66 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/keyword_editor_view.cc | 495 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/keyword_editor_view.h | 163 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/keyword_editor_view_unittest.cc | 271 | ||||
-rw-r--r-- | chrome/browser/ui/views/browser_dialogs.h | 4 | ||||
-rw-r--r-- | chrome/browser/ui/views/dialog_stubs_gtk.cc | 12 | ||||
-rw-r--r-- | chrome/browser/ui/views/edit_search_engine_dialog.cc | 269 | ||||
-rw-r--r-- | chrome/browser/ui/views/edit_search_engine_dialog.h | 105 | ||||
-rw-r--r-- | chrome/browser/ui/views/keyword_editor_view.cc | 308 | ||||
-rw-r--r-- | chrome/browser/ui/views/keyword_editor_view.h | 117 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 10 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 |
14 files changed, 2072 insertions, 0 deletions
diff --git a/chrome/browser/ui/gtk/browser_window_gtk.cc b/chrome/browser/ui/gtk/browser_window_gtk.cc index f881387..2a418b6 100644 --- a/chrome/browser/ui/gtk/browser_window_gtk.cc +++ b/chrome/browser/ui/gtk/browser_window_gtk.cc @@ -43,6 +43,7 @@ #include "chrome/browser/ui/gtk/browser_titlebar.h" #include "chrome/browser/ui/gtk/browser_toolbar_gtk.h" #include "chrome/browser/ui/gtk/cairo_cached_surface.h" +#include "chrome/browser/ui/gtk/clear_browsing_data_dialog_gtk.h" #include "chrome/browser/ui/gtk/collected_cookies_gtk.h" #include "chrome/browser/ui/gtk/create_application_shortcuts_dialog_gtk.h" #include "chrome/browser/ui/gtk/download_in_progress_dialog_gtk.h" @@ -57,6 +58,7 @@ #include "chrome/browser/ui/gtk/info_bubble_gtk.h" #include "chrome/browser/ui/gtk/infobars/infobar_container_gtk.h" #include "chrome/browser/ui/gtk/infobars/infobar_gtk.h" +#include "chrome/browser/ui/gtk/keyword_editor_view.h" #include "chrome/browser/ui/gtk/location_bar_view_gtk.h" #include "chrome/browser/ui/gtk/nine_box.h" #include "chrome/browser/ui/gtk/reload_button_gtk.h" diff --git a/chrome/browser/ui/gtk/clear_browsing_data_dialog_gtk.cc b/chrome/browser/ui/gtk/clear_browsing_data_dialog_gtk.cc new file mode 100644 index 0000000..86d45d8 --- /dev/null +++ b/chrome/browser/ui/gtk/clear_browsing_data_dialog_gtk.cc @@ -0,0 +1,249 @@ +// 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. + +#include "chrome/browser/ui/gtk/clear_browsing_data_dialog_gtk.h" + +#include <string> + +#include "chrome/browser/browsing_data_remover.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/gtk/browser_window_gtk.h" +#include "chrome/browser/ui/gtk/gtk_chrome_link_button.h" +#include "chrome/browser/ui/gtk/gtk_util.h" +#include "chrome/common/pref_names.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { + +// Returns true if the checkbox is checked. +gboolean IsChecked(GtkWidget* widget) { + return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); +} + +} // namespace + +// static +void ClearBrowsingDataDialogGtk::Show(GtkWindow* parent, Profile* profile) { + new ClearBrowsingDataDialogGtk(parent, profile); +} + +ClearBrowsingDataDialogGtk::ClearBrowsingDataDialogGtk(GtkWindow* parent, + Profile* profile) : + profile_(profile), remover_(NULL) { + // Build the dialog. + std::string dialog_name = l10n_util::GetStringUTF8( + IDS_CLEAR_BROWSING_DATA_TITLE); + dialog_ = gtk_dialog_new_with_buttons( + dialog_name.c_str(), + parent, + (GtkDialogFlags) (GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR), + NULL); + + GtkWidget* cancel_button = gtk_dialog_add_button(GTK_DIALOG(dialog_), + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT); + gtk_widget_grab_focus(cancel_button); + + gtk_util::AddButtonToDialog(dialog_, + l10n_util::GetStringUTF8(IDS_CLEAR_BROWSING_DATA_COMMIT).c_str(), + GTK_STOCK_APPLY, GTK_RESPONSE_ACCEPT); + + GtkWidget* content_area = GTK_DIALOG(dialog_)->vbox; + gtk_box_set_spacing(GTK_BOX(content_area), gtk_util::kContentAreaSpacing); + + GtkWidget* vbox = gtk_vbox_new(FALSE, gtk_util::kControlSpacing); + gtk_container_add(GTK_CONTAINER(content_area), vbox); + + // Label on top of the checkboxes. + GtkWidget* description = gtk_label_new( + l10n_util::GetStringUTF8(IDS_CLEAR_BROWSING_DATA_LABEL).c_str()); + gtk_misc_set_alignment(GTK_MISC(description), 0, 0); + gtk_box_pack_start(GTK_BOX(vbox), description, FALSE, FALSE, 0); + + // History checkbox. + del_history_checkbox_ = gtk_check_button_new_with_label( + l10n_util::GetStringUTF8(IDS_DEL_BROWSING_HISTORY_CHKBOX).c_str()); + gtk_box_pack_start(GTK_BOX(vbox), del_history_checkbox_, FALSE, FALSE, 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(del_history_checkbox_), + profile_->GetPrefs()->GetBoolean(prefs::kDeleteBrowsingHistory)); + g_signal_connect(del_history_checkbox_, "toggled", + G_CALLBACK(OnDialogWidgetClickedThunk), this); + + // Downloads checkbox. + del_downloads_checkbox_ = gtk_check_button_new_with_label( + l10n_util::GetStringUTF8(IDS_DEL_DOWNLOAD_HISTORY_CHKBOX).c_str()); + gtk_box_pack_start(GTK_BOX(vbox), del_downloads_checkbox_, FALSE, FALSE, 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(del_downloads_checkbox_), + profile_->GetPrefs()->GetBoolean(prefs::kDeleteDownloadHistory)); + g_signal_connect(del_downloads_checkbox_, "toggled", + G_CALLBACK(OnDialogWidgetClickedThunk), this); + + // Cache checkbox. + del_cache_checkbox_ = gtk_check_button_new_with_label( + l10n_util::GetStringUTF8(IDS_DEL_CACHE_CHKBOX).c_str()); + gtk_box_pack_start(GTK_BOX(vbox), del_cache_checkbox_, FALSE, FALSE, 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(del_cache_checkbox_), + profile_->GetPrefs()->GetBoolean(prefs::kDeleteCache)); + g_signal_connect(del_cache_checkbox_, "toggled", + G_CALLBACK(OnDialogWidgetClickedThunk), this); + + // Cookies checkbox. + del_cookies_checkbox_ = gtk_check_button_new_with_label( + l10n_util::GetStringUTF8(IDS_DEL_COOKIES_CHKBOX).c_str()); + gtk_box_pack_start(GTK_BOX(vbox), del_cookies_checkbox_, FALSE, FALSE, 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(del_cookies_checkbox_), + profile_->GetPrefs()->GetBoolean(prefs::kDeleteCookies)); + g_signal_connect(del_cookies_checkbox_, "toggled", + G_CALLBACK(OnDialogWidgetClickedThunk), this); + + // Passwords checkbox. + del_passwords_checkbox_ = gtk_check_button_new_with_label( + l10n_util::GetStringUTF8(IDS_DEL_PASSWORDS_CHKBOX).c_str()); + gtk_box_pack_start(GTK_BOX(vbox), del_passwords_checkbox_, FALSE, FALSE, 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(del_passwords_checkbox_), + profile_->GetPrefs()->GetBoolean(prefs::kDeletePasswords)); + g_signal_connect(del_passwords_checkbox_, "toggled", + G_CALLBACK(OnDialogWidgetClickedThunk), this); + + // Form data checkbox. + del_form_data_checkbox_ = gtk_check_button_new_with_label( + l10n_util::GetStringUTF8(IDS_DEL_FORM_DATA_CHKBOX).c_str()); + gtk_box_pack_start(GTK_BOX(vbox), del_form_data_checkbox_, FALSE, FALSE, 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(del_form_data_checkbox_), + profile_->GetPrefs()->GetBoolean(prefs::kDeleteFormData)); + g_signal_connect(del_form_data_checkbox_, "toggled", + G_CALLBACK(OnDialogWidgetClickedThunk), this); + + // Create a horizontal layout for the combo box and label. + GtkWidget* combo_hbox = gtk_hbox_new(FALSE, gtk_util::kLabelSpacing); + GtkWidget* time_period_label_ = gtk_label_new( + l10n_util::GetStringUTF8(IDS_CLEAR_BROWSING_DATA_TIME_LABEL).c_str()); + gtk_box_pack_start(GTK_BOX(combo_hbox), time_period_label_, FALSE, FALSE, 0); + + // Time period combo box items. + time_period_combobox_ = gtk_combo_box_new_text(); + gtk_combo_box_append_text(GTK_COMBO_BOX(time_period_combobox_), + l10n_util::GetStringUTF8(IDS_CLEAR_DATA_HOUR).c_str()); + gtk_combo_box_append_text(GTK_COMBO_BOX(time_period_combobox_), + l10n_util::GetStringUTF8(IDS_CLEAR_DATA_DAY).c_str()); + gtk_combo_box_append_text(GTK_COMBO_BOX(time_period_combobox_), + l10n_util::GetStringUTF8(IDS_CLEAR_DATA_WEEK).c_str()); + gtk_combo_box_append_text(GTK_COMBO_BOX(time_period_combobox_), + l10n_util::GetStringUTF8(IDS_CLEAR_DATA_4WEEKS).c_str()); + gtk_combo_box_append_text(GTK_COMBO_BOX(time_period_combobox_), + l10n_util::GetStringUTF8(IDS_CLEAR_DATA_EVERYTHING).c_str()); + gtk_combo_box_set_active(GTK_COMBO_BOX(time_period_combobox_), + profile_->GetPrefs()->GetInteger(prefs::kDeleteTimePeriod)); + gtk_box_pack_start(GTK_BOX(combo_hbox), + time_period_combobox_, FALSE, FALSE, 0); + g_signal_connect(time_period_combobox_, "changed", + G_CALLBACK(OnDialogWidgetClickedThunk), this); + + // Add the combo/label time period box to the vertical layout. + gtk_box_pack_start(GTK_BOX(vbox), combo_hbox, FALSE, FALSE, 0); + + // Add widgets for the area below the accept buttons. + GtkWidget* flash_link = gtk_chrome_link_button_new( + l10n_util::GetStringUTF8(IDS_FLASH_STORAGE_SETTINGS).c_str()); + g_signal_connect(G_OBJECT(flash_link), "clicked", + G_CALLBACK(OnFlashLinkClickedThunk), this); + GtkWidget* flash_link_hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(flash_link_hbox), flash_link, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(content_area), flash_link_hbox, FALSE, FALSE, 0); + + GtkWidget* separator = gtk_hseparator_new(); + gtk_box_pack_end(GTK_BOX(content_area), separator, FALSE, FALSE, 0); + + // Make sure we can move things around. + DCHECK_EQ(GTK_DIALOG(dialog_)->action_area->parent, content_area); + + // Now rearrange those because they're *above* the accept buttons...there's + // no way to place them in the correct position with gtk_box_pack_end() so + // manually move things into the correct order. + gtk_box_reorder_child(GTK_BOX(content_area), flash_link_hbox, -1); + gtk_box_reorder_child(GTK_BOX(content_area), separator, -1); + gtk_box_reorder_child(GTK_BOX(content_area), GTK_DIALOG(dialog_)->action_area, + -1); + + g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this); + + UpdateDialogButtons(); + + gtk_util::ShowDialogWithMinLocalizedWidth(dialog_, + IDS_CLEARDATA_DIALOG_WIDTH_CHARS); +} + +ClearBrowsingDataDialogGtk::~ClearBrowsingDataDialogGtk() { +} + +void ClearBrowsingDataDialogGtk::OnResponse(GtkWidget* dialog, + int response_id) { + if (response_id == GTK_RESPONSE_ACCEPT) { + PrefService* prefs = profile_->GetPrefs(); + prefs->SetBoolean(prefs::kDeleteBrowsingHistory, + IsChecked(del_history_checkbox_)); + prefs->SetBoolean(prefs::kDeleteDownloadHistory, + IsChecked(del_downloads_checkbox_)); + prefs->SetBoolean(prefs::kDeleteCache, + IsChecked(del_cache_checkbox_)); + prefs->SetBoolean(prefs::kDeleteCookies, + IsChecked(del_cookies_checkbox_)); + prefs->SetBoolean(prefs::kDeletePasswords, + IsChecked(del_passwords_checkbox_)); + prefs->SetBoolean(prefs::kDeleteFormData, + IsChecked(del_form_data_checkbox_)); + prefs->SetInteger(prefs::kDeleteTimePeriod, + gtk_combo_box_get_active(GTK_COMBO_BOX(time_period_combobox_))); + + int period_selected = gtk_combo_box_get_active( + GTK_COMBO_BOX(time_period_combobox_)); + + // BrowsingDataRemover deletes itself when done. + remover_ = new BrowsingDataRemover(profile_, + static_cast<BrowsingDataRemover::TimePeriod>(period_selected), + base::Time()); + remover_->Remove(GetCheckedItems()); + } + + delete this; + gtk_widget_destroy(dialog); +} + +void ClearBrowsingDataDialogGtk::OnDialogWidgetClicked(GtkWidget* widget) { + UpdateDialogButtons(); +} + +void ClearBrowsingDataDialogGtk::OnFlashLinkClicked(GtkWidget* button) { + // We open a new browser window so the Options dialog doesn't get lost + // behind other windows. + Browser* browser = Browser::Create(profile_); + browser->OpenURL(GURL(l10n_util::GetStringUTF8(IDS_FLASH_STORAGE_URL)), + GURL(), NEW_FOREGROUND_TAB, PageTransition::LINK); + browser->window()->Show(); +} + +void ClearBrowsingDataDialogGtk::UpdateDialogButtons() { + gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_), GTK_RESPONSE_ACCEPT, + GetCheckedItems() != 0); +} + +int ClearBrowsingDataDialogGtk::GetCheckedItems() { + int items = 0; + if (IsChecked(del_history_checkbox_)) + items |= BrowsingDataRemover::REMOVE_HISTORY; + if (IsChecked(del_downloads_checkbox_)) + items |= BrowsingDataRemover::REMOVE_DOWNLOADS; + if (IsChecked(del_cookies_checkbox_)) + items |= BrowsingDataRemover::REMOVE_COOKIES; + if (IsChecked(del_passwords_checkbox_)) + items |= BrowsingDataRemover::REMOVE_PASSWORDS; + if (IsChecked(del_form_data_checkbox_)) + items |= BrowsingDataRemover::REMOVE_FORM_DATA; + if (IsChecked(del_cache_checkbox_)) + items |= BrowsingDataRemover::REMOVE_CACHE; + return items; +} diff --git a/chrome/browser/ui/gtk/clear_browsing_data_dialog_gtk.h b/chrome/browser/ui/gtk/clear_browsing_data_dialog_gtk.h new file mode 100644 index 0000000..2782385 --- /dev/null +++ b/chrome/browser/ui/gtk/clear_browsing_data_dialog_gtk.h @@ -0,0 +1,66 @@ +// 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. + +#ifndef CHROME_BROWSER_UI_GTK_CLEAR_BROWSING_DATA_DIALOG_GTK_H_ +#define CHROME_BROWSER_UI_GTK_CLEAR_BROWSING_DATA_DIALOG_GTK_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "ui/base/gtk/gtk_signal.h" + +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkWindow GtkWindow; + +class BrowsingDataRemover; +class Profile; + +class ClearBrowsingDataDialogGtk { + public: + // Displays the dialog box to clear browsing data from |profile|. + static void Show(GtkWindow* parent, Profile* profile); + + private: + ClearBrowsingDataDialogGtk(GtkWindow* parent, Profile* profile); + ~ClearBrowsingDataDialogGtk(); + + // Handler to respond to OK and Cancel responses from the dialog. + CHROMEGTK_CALLBACK_1(ClearBrowsingDataDialogGtk, void, OnResponse, int); + + // Handler to respond to widget clicked actions from the dialog. + CHROMEGTK_CALLBACK_0(ClearBrowsingDataDialogGtk, void, OnDialogWidgetClicked); + + CHROMEGTK_CALLBACK_0(ClearBrowsingDataDialogGtk, void, OnFlashLinkClicked); + + // Enable or disable the dialog buttons depending on the state of the + // checkboxes. + void UpdateDialogButtons(); + + // Create a bitmask from the checkboxes of the dialog. + int GetCheckedItems(); + + // The dialog window. + GtkWidget* dialog_; + + // UI elements. + GtkWidget* del_history_checkbox_; + GtkWidget* del_downloads_checkbox_; + GtkWidget* del_cache_checkbox_; + GtkWidget* del_cookies_checkbox_; + GtkWidget* del_passwords_checkbox_; + GtkWidget* del_form_data_checkbox_; + GtkWidget* time_period_combobox_; + + // Our current profile. + Profile* profile_; + + // If non-null it means removal is in progress. BrowsingDataRemover takes care + // of deleting itself when done. + BrowsingDataRemover* remover_; + + DISALLOW_COPY_AND_ASSIGN(ClearBrowsingDataDialogGtk); +}; + + +#endif // CHROME_BROWSER_UI_GTK_CLEAR_BROWSING_DATA_DIALOG_GTK_H_ diff --git a/chrome/browser/ui/gtk/keyword_editor_view.cc b/chrome/browser/ui/gtk/keyword_editor_view.cc new file mode 100644 index 0000000..f1777d2 --- /dev/null +++ b/chrome/browser/ui/gtk/keyword_editor_view.cc @@ -0,0 +1,495 @@ +// 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. + +#include "chrome/browser/ui/gtk/keyword_editor_view.h" + +#include <string> + +#include "base/message_loop.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/metrics/user_metrics.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/ui/gtk/edit_search_engine_dialog.h" +#include "chrome/browser/ui/gtk/gtk_tree.h" +#include "chrome/browser/ui/gtk/gtk_util.h" +#include "chrome/browser/ui/search_engines/keyword_editor_controller.h" +#include "chrome/browser/ui/search_engines/template_url_table_model.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/gtk_util.h" + +namespace { + +// How many rows should be added to an index into the |table_model_| to get the +// corresponding row in |list_store_| +const int kFirstGroupRowOffset = 2; +const int kSecondGroupRowOffset = 5; + +KeywordEditorView* instance_ = NULL; + +} // namespace + +// static +void KeywordEditorView::Show(Profile* profile) { + DCHECK(profile); + // If this panel is opened from an Incognito window, closing that window can + // leave this with a stale pointer. Use the original profile instead. + // See http://crbug.com/23359. + profile = profile->GetOriginalProfile(); + if (!profile->GetTemplateURLModel()) + return; + + // If there's already an existing editor window, activate it. + if (instance_) { + gtk_util::PresentWindow(instance_->dialog_, 0); + } else { + instance_ = new KeywordEditorView(profile); + gtk_util::ShowDialogWithLocalizedSize(instance_->dialog_, + IDS_SEARCHENGINES_DIALOG_WIDTH_CHARS, + IDS_SEARCHENGINES_DIALOG_HEIGHT_LINES, + true); + } +} + +void KeywordEditorView::OnEditedKeyword(const TemplateURL* template_url, + const string16& title, + const string16& keyword, + const std::string& url) { + if (template_url) { + controller_->ModifyTemplateURL(template_url, title, keyword, url); + + // Force the make default button to update. + EnableControls(); + } else { + SelectModelRow(controller_->AddTemplateURL(title, keyword, url)); + } +} + +KeywordEditorView::~KeywordEditorView() { + controller_->url_model()->RemoveObserver(this); +} + +KeywordEditorView::KeywordEditorView(Profile* profile) + : profile_(profile), + controller_(new KeywordEditorController(profile)), + table_model_(controller_->table_model()) { + Init(); +} + +void KeywordEditorView::Init() { + std::string dialog_name = + l10n_util::GetStringUTF8(IDS_SEARCH_ENGINES_EDITOR_WINDOW_TITLE); + dialog_ = gtk_dialog_new_with_buttons( + dialog_name.c_str(), + NULL, + // Non-modal. + GTK_DIALOG_NO_SEPARATOR, + GTK_STOCK_CLOSE, + GTK_RESPONSE_CLOSE, + NULL); + + gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog_)->vbox), + gtk_util::kContentAreaSpacing); + + GtkWidget* hbox = gtk_hbox_new(FALSE, gtk_util::kControlSpacing); + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog_)->vbox), hbox); + + GtkWidget* scroll_window = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll_window), + GTK_SHADOW_ETCHED_IN); + gtk_box_pack_start(GTK_BOX(hbox), scroll_window, TRUE, TRUE, 0); + + list_store_ = gtk_list_store_new(COL_COUNT, + GDK_TYPE_PIXBUF, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, + G_TYPE_INT, + G_TYPE_BOOLEAN); + tree_ = gtk_tree_view_new_with_model(GTK_TREE_MODEL(list_store_)); + g_object_unref(list_store_); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_), TRUE); + gtk_tree_view_set_row_separator_func(GTK_TREE_VIEW(tree_), + OnCheckRowIsSeparator, + NULL, NULL); + g_signal_connect(tree_, "row-activated", + G_CALLBACK(OnRowActivated), this); + gtk_container_add(GTK_CONTAINER(scroll_window), tree_); + + GtkTreeViewColumn* title_column = gtk_tree_view_column_new(); + GtkCellRenderer* pixbuf_renderer = gtk_cell_renderer_pixbuf_new(); + gtk_tree_view_column_pack_start(title_column, pixbuf_renderer, FALSE); + gtk_tree_view_column_add_attribute(title_column, pixbuf_renderer, "pixbuf", + COL_FAVICON); + GtkCellRenderer* title_renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(title_column, title_renderer, TRUE); + gtk_tree_view_column_add_attribute(title_column, title_renderer, "text", + COL_TITLE); + gtk_tree_view_column_add_attribute(title_column, title_renderer, "weight", + COL_WEIGHT); + gtk_tree_view_column_add_attribute(title_column, title_renderer, "weight-set", + COL_WEIGHT_SET); + gtk_tree_view_column_set_title( + title_column, l10n_util::GetStringUTF8( + IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN).c_str()); + gtk_tree_view_append_column(GTK_TREE_VIEW(tree_), title_column); + + GtkTreeViewColumn* keyword_column = gtk_tree_view_column_new_with_attributes( + l10n_util::GetStringUTF8( + IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN).c_str(), + gtk_cell_renderer_text_new(), + "text", COL_KEYWORD, + NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(tree_), keyword_column); + + selection_ = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_)); + gtk_tree_selection_set_mode(selection_, GTK_SELECTION_SINGLE); + gtk_tree_selection_set_select_function(selection_, OnSelectionFilter, + NULL, NULL); + g_signal_connect(selection_, "changed", + G_CALLBACK(OnSelectionChanged), this); + + GtkWidget* button_box = gtk_vbox_new(FALSE, gtk_util::kControlSpacing); + gtk_box_pack_start(GTK_BOX(hbox), button_box, FALSE, FALSE, 0); + + add_button_ = gtk_button_new_with_mnemonic( + gfx::ConvertAcceleratorsFromWindowsStyle( + l10n_util::GetStringUTF8( + IDS_SEARCH_ENGINES_EDITOR_NEW_BUTTON)).c_str()); + g_signal_connect(add_button_, "clicked", + G_CALLBACK(OnAddButtonClicked), this); + gtk_box_pack_start(GTK_BOX(button_box), add_button_, FALSE, FALSE, 0); + + edit_button_ = gtk_button_new_with_label( + l10n_util::GetStringUTF8(IDS_SEARCH_ENGINES_EDITOR_EDIT_BUTTON).c_str()); + g_signal_connect(edit_button_, "clicked", + G_CALLBACK(OnEditButtonClicked), this); + gtk_box_pack_start(GTK_BOX(button_box), edit_button_, FALSE, FALSE, 0); + + remove_button_ = gtk_button_new_with_mnemonic( + gfx::ConvertAcceleratorsFromWindowsStyle( + l10n_util::GetStringUTF8( + IDS_SEARCH_ENGINES_EDITOR_REMOVE_BUTTON)).c_str()); + g_signal_connect(remove_button_, "clicked", + G_CALLBACK(OnRemoveButtonClicked), this); + gtk_box_pack_start(GTK_BOX(button_box), remove_button_, FALSE, FALSE, 0); + + make_default_button_ = gtk_button_new_with_label( + l10n_util::GetStringUTF8( + IDS_SEARCH_ENGINES_EDITOR_MAKE_DEFAULT_BUTTON).c_str()); + g_signal_connect(make_default_button_, "clicked", + G_CALLBACK(OnMakeDefaultButtonClicked), this); + gtk_box_pack_start(GTK_BOX(button_box), make_default_button_, FALSE, FALSE, + 0); + + controller_->url_model()->AddObserver(this); + table_model_->SetObserver(this); + table_model_->Reload(); + + EnableControls(); + + g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this); + g_signal_connect(dialog_, "destroy", G_CALLBACK(OnWindowDestroy), this); +} + +void KeywordEditorView::EnableControls() { + bool can_edit = false; + bool can_make_default = false; + bool can_remove = false; + int model_row = GetSelectedModelRow(); + if (model_row != -1) { + const TemplateURL* selected_url = controller_->GetTemplateURL(model_row); + can_edit = controller_->CanEdit(selected_url); + can_make_default = controller_->CanMakeDefault(selected_url); + can_remove = controller_->CanRemove(selected_url); + } + gtk_widget_set_sensitive(add_button_, controller_->loaded()); + gtk_widget_set_sensitive(edit_button_, can_edit); + gtk_widget_set_sensitive(remove_button_, can_remove); + gtk_widget_set_sensitive(make_default_button_, can_make_default); +} + +void KeywordEditorView::SetColumnValues(int model_row, GtkTreeIter* iter) { + SkBitmap bitmap = table_model_->GetIcon(model_row); + GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&bitmap); + gtk_list_store_set( + list_store_, iter, + COL_FAVICON, pixbuf, + // Dunno why, even with COL_WEIGHT_SET to FALSE here, the weight still + // has an effect. So we just set it to normal. + COL_WEIGHT, PANGO_WEIGHT_NORMAL, + COL_WEIGHT_SET, TRUE, + COL_TITLE, UTF16ToUTF8(table_model_->GetText( + model_row, IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN)).c_str(), + COL_KEYWORD, UTF16ToUTF8(table_model_->GetText( + model_row, IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN)).c_str(), + -1); + g_object_unref(pixbuf); +} + +int KeywordEditorView::GetListStoreRowForModelRow(int model_row) const { + if (model_row < model_second_group_index_) + return model_row + kFirstGroupRowOffset; + else + return model_row + kSecondGroupRowOffset; +} + +int KeywordEditorView::GetModelRowForPath(GtkTreePath* path) const { + gint* indices = gtk_tree_path_get_indices(path); + if (!indices) { + NOTREACHED(); + return -1; + } + if (indices[0] >= model_second_group_index_ + kSecondGroupRowOffset) + return indices[0] - kSecondGroupRowOffset; + return indices[0] - kFirstGroupRowOffset; +} + +int KeywordEditorView::GetModelRowForIter(GtkTreeIter* iter) const { + GtkTreePath* path = gtk_tree_model_get_path(GTK_TREE_MODEL(list_store_), + iter); + int model_row = GetModelRowForPath(path); + gtk_tree_path_free(path); + return model_row; +} + +int KeywordEditorView::GetSelectedModelRow() const { + GtkTreeIter iter; + if (!gtk_tree_selection_get_selected(selection_, NULL, &iter)) + return -1; + return GetModelRowForIter(&iter); +} + +void KeywordEditorView::SelectModelRow(int model_row) { + int row = GetListStoreRowForModelRow(model_row); + gtk_tree::SelectAndFocusRowNum(row, GTK_TREE_VIEW(tree_)); +} + +void KeywordEditorView::AddNodeToList(int model_row) { + GtkTreeIter iter; + int row = GetListStoreRowForModelRow(model_row); + if (row == 0) { + gtk_list_store_prepend(list_store_, &iter); + } else { + GtkTreeIter sibling; + gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store_), &sibling, + NULL, row - 1); + gtk_list_store_insert_after(list_store_, &iter, &sibling); + } + + SetColumnValues(model_row, &iter); +} + +void KeywordEditorView::OnModelChanged() { + model_second_group_index_ = table_model_->last_search_engine_index(); + gtk_list_store_clear(list_store_); + + ui::TableModel::Groups groups(table_model_->GetGroups()); + if (groups.size() != 2) { + NOTREACHED(); + return; + } + + GtkTreeIter iter; + // First group title. + gtk_list_store_append(list_store_, &iter); + gtk_list_store_set( + list_store_, &iter, + COL_WEIGHT, PANGO_WEIGHT_BOLD, + COL_WEIGHT_SET, TRUE, + COL_TITLE, UTF16ToUTF8(groups[0].title).c_str(), + COL_IS_HEADER, TRUE, + -1); + // First group separator. + gtk_list_store_append(list_store_, &iter); + gtk_list_store_set( + list_store_, &iter, + COL_IS_HEADER, TRUE, + COL_IS_SEPARATOR, TRUE, + -1); + + // Blank row between groups. + gtk_list_store_append(list_store_, &iter); + gtk_list_store_set( + list_store_, &iter, + COL_IS_HEADER, TRUE, + -1); + // Second group title. + gtk_list_store_append(list_store_, &iter); + gtk_list_store_set( + list_store_, &iter, + COL_WEIGHT, PANGO_WEIGHT_BOLD, + COL_WEIGHT_SET, TRUE, + COL_TITLE, UTF16ToUTF8(groups[1].title).c_str(), + COL_IS_HEADER, TRUE, + -1); + // Second group separator. + gtk_list_store_append(list_store_, &iter); + gtk_list_store_set( + list_store_, &iter, + COL_IS_HEADER, TRUE, + COL_IS_SEPARATOR, TRUE, + -1); + + for (int i = 0; i < table_model_->RowCount(); ++i) + AddNodeToList(i); +} + +void KeywordEditorView::OnItemsChanged(int start, int length) { + DCHECK(model_second_group_index_ == table_model_->last_search_engine_index()); + GtkTreeIter iter; + for (int i = 0; i < length; ++i) { + int row = GetListStoreRowForModelRow(start + i); + bool rv = gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store_), + &iter, NULL, row); + if (!rv) { + NOTREACHED(); + return; + } + SetColumnValues(start + i, &iter); + rv = gtk_tree_model_iter_next(GTK_TREE_MODEL(list_store_), &iter); + } +} + +void KeywordEditorView::OnItemsAdded(int start, int length) { + model_second_group_index_ = table_model_->last_search_engine_index(); + for (int i = 0; i < length; ++i) { + AddNodeToList(start + i); + } +} + +void KeywordEditorView::OnItemsRemoved(int start, int length) { + // This is quite likely not correct with removing multiple in one call, but + // that shouldn't happen since we only can select and modify/remove one at a + // time. + DCHECK_EQ(length, 1); + for (int i = 0; i < length; ++i) { + int row = GetListStoreRowForModelRow(start + i); + GtkTreeIter iter; + if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store_), &iter, + NULL, row)) { + NOTREACHED(); + return; + } + gtk_list_store_remove(list_store_, &iter); + } + model_second_group_index_ = table_model_->last_search_engine_index(); +} + +void KeywordEditorView::OnTemplateURLModelChanged() { + EnableControls(); +} + +// static +void KeywordEditorView::OnWindowDestroy(GtkWidget* widget, + KeywordEditorView* window) { + instance_ = NULL; + MessageLoop::current()->DeleteSoon(FROM_HERE, window); +} + +void KeywordEditorView::OnResponse(GtkWidget* dialog, int response_id) { + gtk_widget_destroy(dialog_); +} + +// static +gboolean KeywordEditorView::OnCheckRowIsSeparator(GtkTreeModel* model, + GtkTreeIter* iter, + gpointer user_data) { + gboolean is_separator; + gtk_tree_model_get(model, iter, COL_IS_SEPARATOR, &is_separator, -1); + return is_separator; +} + +// static +gboolean KeywordEditorView::OnSelectionFilter(GtkTreeSelection* selection, + GtkTreeModel* model, + GtkTreePath* path, + gboolean path_currently_selected, + gpointer user_data) { + GtkTreeIter iter; + if (!gtk_tree_model_get_iter(model, &iter, path)) { + NOTREACHED(); + return TRUE; + } + gboolean is_header; + gtk_tree_model_get(model, &iter, COL_IS_HEADER, &is_header, -1); + return !is_header; +} + +// static +void KeywordEditorView::OnSelectionChanged( + GtkTreeSelection* selection, KeywordEditorView* editor) { + editor->EnableControls(); +} + +// static +void KeywordEditorView::OnRowActivated(GtkTreeView* tree_view, + GtkTreePath* path, + GtkTreeViewColumn* column, + KeywordEditorView* editor) { + OnEditButtonClicked(NULL, editor); +} + +// static +void KeywordEditorView::OnAddButtonClicked(GtkButton* button, + KeywordEditorView* editor) { + new EditSearchEngineDialog( + GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET( + gtk_util::GetDialogWindow(editor->dialog_)))), + NULL, + editor, + editor->profile_); +} + +// static +void KeywordEditorView::OnEditButtonClicked(GtkButton* button, + KeywordEditorView* editor) { + int model_row = editor->GetSelectedModelRow(); + if (model_row == -1) + return; + + new EditSearchEngineDialog( + GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET( + gtk_util::GetDialogWindow(editor->dialog_)))), + editor->controller_->GetTemplateURL(model_row), + editor, + editor->profile_); +} + +// static +void KeywordEditorView::OnRemoveButtonClicked(GtkButton* button, + KeywordEditorView* editor) { + int model_row = editor->GetSelectedModelRow(); + if (model_row == -1) { + NOTREACHED(); + return; + } + editor->controller_->RemoveTemplateURL(model_row); + if (model_row >= editor->table_model_->RowCount()) + model_row = editor->table_model_->RowCount() - 1; + if (model_row >= 0) + editor->SelectModelRow(model_row); +} + +// static +void KeywordEditorView::OnMakeDefaultButtonClicked(GtkButton* button, + KeywordEditorView* editor) { + int model_row = editor->GetSelectedModelRow(); + if (model_row == -1) { + NOTREACHED(); + return; + } + int new_index = editor->controller_->MakeDefaultTemplateURL(model_row); + if (new_index > 0) { + editor->SelectModelRow(new_index); + } +} diff --git a/chrome/browser/ui/gtk/keyword_editor_view.h b/chrome/browser/ui/gtk/keyword_editor_view.h new file mode 100644 index 0000000..0bc68b5 --- /dev/null +++ b/chrome/browser/ui/gtk/keyword_editor_view.h @@ -0,0 +1,163 @@ +// 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. + +#ifndef CHROME_BROWSER_UI_GTK_KEYWORD_EDITOR_VIEW_H_ +#define CHROME_BROWSER_UI_GTK_KEYWORD_EDITOR_VIEW_H_ +#pragma once + +#include <gtk/gtk.h> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/scoped_ptr.h" +#include "base/string16.h" +#include "chrome/browser/search_engines/template_url_model_observer.h" +#include "chrome/browser/ui/search_engines/edit_search_engine_controller.h" +#include "ui/base/gtk/gtk_signal.h" +#include "ui/base/models/table_model_observer.h" + +class KeywordEditorController; +class Profile; +class TemplateURLTableModel; + +class KeywordEditorView : public ui::TableModelObserver, + public TemplateURLModelObserver, + public EditSearchEngineControllerDelegate { + public: + virtual ~KeywordEditorView(); + + // Create (if necessary) and show the keyword editor window. + static void Show(Profile* profile); + + // Overriden from EditSearchEngineControllerDelegate. + virtual void OnEditedKeyword(const TemplateURL* template_url, + const string16& title, + const string16& keyword, + const std::string& url); + private: + // Column ids for |list_store_|. + enum { + COL_FAVICON, + COL_TITLE, + COL_KEYWORD, + COL_IS_HEADER, + COL_IS_SEPARATOR, + COL_WEIGHT, + COL_WEIGHT_SET, + COL_COUNT, + }; + + explicit KeywordEditorView(Profile* profile); + void Init(); + + // Enable buttons based on selection state. + void EnableControls(); + + // Set the column values for |row| of |table_model_| in the |list_store_| at + // |iter|. + void SetColumnValues(int row, GtkTreeIter* iter); + + // Get the row number in the GtkListStore corresponding to |model_row|. + int GetListStoreRowForModelRow(int model_row) const; + + // Get the row number in the TemplateURLTableModel corresponding to |path|. + int GetModelRowForPath(GtkTreePath* path) const; + + // Get the row number in the TemplateURLTableModel corresponding to |iter|. + int GetModelRowForIter(GtkTreeIter* iter) const; + + // Get the row number in the TemplateURLTableModel of the current selection, + // or -1 if no row is selected. + int GetSelectedModelRow() const; + + // Select the row in the |tree_| corresponding to |model_row|. + void SelectModelRow(int model_row); + + // Add the values from |row| of |table_model_|. + void AddNodeToList(int row); + + // ui::TableModelObserver implementation. + virtual void OnModelChanged(); + virtual void OnItemsChanged(int start, int length); + virtual void OnItemsAdded(int start, int length); + virtual void OnItemsRemoved(int start, int length); + + // TemplateURLModelObserver notification. + virtual void OnTemplateURLModelChanged(); + + // Callback for window destruction. + static void OnWindowDestroy(GtkWidget* widget, KeywordEditorView* window); + + // Callback for dialog buttons. + CHROMEGTK_CALLBACK_1(KeywordEditorView, void, OnResponse, int); + + // Callback checking whether a row should be drawn as a separator. + static gboolean OnCheckRowIsSeparator(GtkTreeModel* model, + GtkTreeIter* iter, + gpointer user_data); + + // Callback checking whether a row may be selected. We use some rows in the + // table as headers/separators for the groups, which should not be selectable. + static gboolean OnSelectionFilter(GtkTreeSelection* selection, + GtkTreeModel* model, + GtkTreePath* path, + gboolean path_currently_selected, + gpointer user_data); + + // Callback for when user selects something. + static void OnSelectionChanged(GtkTreeSelection* selection, + KeywordEditorView* editor); + + // Callbacks for user actions modifying the table. + static void OnRowActivated(GtkTreeView* tree_view, + GtkTreePath* path, + GtkTreeViewColumn* column, + KeywordEditorView* editor); + static void OnAddButtonClicked(GtkButton* button, + KeywordEditorView* editor); + static void OnEditButtonClicked(GtkButton* button, + KeywordEditorView* editor); + static void OnRemoveButtonClicked(GtkButton* button, + KeywordEditorView* editor); + static void OnMakeDefaultButtonClicked(GtkButton* button, + KeywordEditorView* editor); + + // The table listing the search engines. + GtkWidget* tree_; + GtkListStore* list_store_; + GtkTreeSelection* selection_; + + // Buttons for acting on the table. + GtkWidget* add_button_; + GtkWidget* edit_button_; + GtkWidget* remove_button_; + GtkWidget* make_default_button_; + + // The containing dialog. + GtkWidget* dialog_; + + // The profile. + Profile* profile_; + + scoped_ptr<KeywordEditorController> controller_; + + TemplateURLTableModel* table_model_; + + // We store our own index of the start of the second group within the model, + // as when OnItemsRemoved is called the value in the model is already updated + // but we need the old value to know which row to remove from the + // |list_store_|. + int model_second_group_index_; + + friend class KeywordEditorViewTest; + FRIEND_TEST_ALL_PREFIXES(KeywordEditorViewTest, Empty); + FRIEND_TEST_ALL_PREFIXES(KeywordEditorViewTest, Add); + FRIEND_TEST_ALL_PREFIXES(KeywordEditorViewTest, MakeDefault); + FRIEND_TEST_ALL_PREFIXES(KeywordEditorViewTest, Remove); + FRIEND_TEST_ALL_PREFIXES(KeywordEditorViewTest, Edit); + + DISALLOW_COPY_AND_ASSIGN(KeywordEditorView); +}; + +#endif // CHROME_BROWSER_UI_GTK_KEYWORD_EDITOR_VIEW_H_ diff --git a/chrome/browser/ui/gtk/keyword_editor_view_unittest.cc b/chrome/browser/ui/gtk/keyword_editor_view_unittest.cc new file mode 100644 index 0000000..fa88efd --- /dev/null +++ b/chrome/browser/ui/gtk/keyword_editor_view_unittest.cc @@ -0,0 +1,271 @@ +// 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. + +#include "chrome/browser/ui/gtk/keyword_editor_view.h" + +#include <gtk/gtk.h> + +#include <string> +#include <vector> + +#include "base/string16.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/browser/search_engines/template_url_model.h" +#include "chrome/browser/ui/gtk/gtk_tree.h" +#include "chrome/browser/ui/search_engines/template_url_table_model.h" +#include "chrome/test/testing_profile.h" +#include "testing/gtest/include/gtest/gtest.h" + +class KeywordEditorViewTest : public testing::Test { + public: + virtual void SetUp() { + profile_.reset(new TestingProfile()); + profile_->CreateTemplateURLModel(); + } + + TemplateURL* AddToModel(const std::string& name, + const std::string& keyword, + bool make_default) { + TemplateURL* template_url = new TemplateURL(); + template_url->set_short_name(UTF8ToUTF16(name)); + template_url->set_keyword(UTF8ToUTF16(keyword)); + template_url->SetURL("http://example.com/{searchTerms}", 0, 0); + profile_->GetTemplateURLModel()->Add(template_url); + if (make_default) + profile_->GetTemplateURLModel()->SetDefaultSearchProvider(template_url); + return template_url; + } + + int GetSelectedRowNum(const KeywordEditorView& editor) { + GtkTreeIter iter; + if (!gtk_tree_selection_get_selected(editor.selection_, NULL, &iter)) + return -1; + return gtk_tree::GetRowNumForIter(GTK_TREE_MODEL(editor.list_store_), + &iter); + } + + // Get the search engines displayed in the dialog in the order they are + // displayed, as a comma seperated string. + // The headers are included as "!,_" for the first group header and "_,@,_" + // for the second group header (This allows the tests to ensure the headers + // aren't accidentally misplaced/moved.) + // Ex: EXPECT_STREQ("!,_,A (Default),_,@,_,B", + // GetDisplayedEngines(editor).c_str()); + std::string GetDisplayedEngines(const KeywordEditorView& editor) { + ui::TableModel::Groups groups(editor.table_model_->GetGroups()); + std::vector<std::string> parts; + GtkTreeModel* tree_model = GTK_TREE_MODEL(editor.list_store_); + GtkTreeIter iter; + if (!gtk_tree_model_get_iter_first(tree_model, &iter)) + return std::string(); + while (true) { + gchar* name; + gboolean is_header; + gtk_tree_model_get(tree_model, &iter, + KeywordEditorView::COL_TITLE, &name, + KeywordEditorView::COL_IS_HEADER, &is_header, + -1); + if (name && UTF16ToUTF8(groups[0].title) == name) + parts.push_back("!"); + else if (name && UTF16ToUTF8(groups[1].title) == name) + parts.push_back("@"); + else if (is_header) + parts.push_back("_"); + else if (name) + parts.push_back(name); + else + parts.push_back("???"); + if (name) + g_free(name); + if (!gtk_tree_model_iter_next(tree_model, &iter)) + break; + } + return JoinString(parts, ','); + } + + protected: + MessageLoopForUI message_loop_; + scoped_ptr<TestingProfile> profile_; +}; + +TEST_F(KeywordEditorViewTest, Empty) { + KeywordEditorView editor(profile_.get()); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.add_button_)); + EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(editor.edit_button_)); + EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(editor.remove_button_)); + EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(editor.make_default_button_)); + EXPECT_STREQ("!,_,_,@,_", GetDisplayedEngines(editor).c_str()); + EXPECT_EQ(-1, GetSelectedRowNum(editor)); +} + +TEST_F(KeywordEditorViewTest, Add) { + AddToModel("A1", "k1", true); + KeywordEditorView editor(profile_.get()); + EXPECT_STREQ("!,_,A1 (Default),_,@,_", GetDisplayedEngines(editor).c_str()); + EXPECT_EQ(-1, GetSelectedRowNum(editor)); + + editor.OnEditedKeyword(NULL, ASCIIToUTF16("B"), ASCIIToUTF16("b"), + "example.com"); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.add_button_)); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.edit_button_)); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.remove_button_)); + EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(editor.make_default_button_)); + EXPECT_STREQ("!,_,A1 (Default),_,@,_,B", GetDisplayedEngines(editor).c_str()); + EXPECT_EQ(6, GetSelectedRowNum(editor)); + + editor.OnEditedKeyword(NULL, ASCIIToUTF16("C"), ASCIIToUTF16("c"), + "example.com/{searchTerms}"); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.add_button_)); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.edit_button_)); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.remove_button_)); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.make_default_button_)); + EXPECT_STREQ("!,_,A1 (Default),_,@,_,B,C", + GetDisplayedEngines(editor).c_str()); + EXPECT_EQ(7, GetSelectedRowNum(editor)); + + editor.OnEditedKeyword(NULL, ASCIIToUTF16("D"), ASCIIToUTF16("d"), + "example.com"); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.add_button_)); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.edit_button_)); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.remove_button_)); + EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(editor.make_default_button_)); + EXPECT_STREQ("!,_,A1 (Default),_,@,_,B,C,D", + GetDisplayedEngines(editor).c_str()); + EXPECT_EQ(8, GetSelectedRowNum(editor)); +} + +TEST_F(KeywordEditorViewTest, MakeDefault) { + AddToModel("A", "a", true); + AddToModel("B", "b", false); + AddToModel("C", "c", false); + AddToModel("D", "d", false); + KeywordEditorView editor(profile_.get()); + EXPECT_STREQ("!,_,A (Default),_,@,_,B,C,D", + GetDisplayedEngines(editor).c_str()); + + GtkTreeIter iter; + gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(editor.list_store_), + &iter, NULL, 6); + gtk_tree_selection_select_iter(editor.selection_, &iter); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.make_default_button_)); + + gtk_button_clicked(GTK_BUTTON(editor.make_default_button_)); + EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(editor.remove_button_)); + EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(editor.make_default_button_)); + EXPECT_STREQ("!,_,A,B (Default),_,@,_,C,D", + GetDisplayedEngines(editor).c_str()); + EXPECT_EQ(3, GetSelectedRowNum(editor)); + + gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(editor.list_store_), + &iter, NULL, 8); + gtk_tree_selection_select_iter(editor.selection_, &iter); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.make_default_button_)); + + gtk_button_clicked(GTK_BUTTON(editor.make_default_button_)); + EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(editor.remove_button_)); + EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(editor.make_default_button_)); + EXPECT_STREQ("!,_,A,B,D (Default),_,@,_,C", + GetDisplayedEngines(editor).c_str()); + EXPECT_EQ(4, GetSelectedRowNum(editor)); + + gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(editor.list_store_), + &iter, NULL, 2); + gtk_tree_selection_select_iter(editor.selection_, &iter); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.make_default_button_)); + + gtk_button_clicked(GTK_BUTTON(editor.make_default_button_)); + EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(editor.remove_button_)); + EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(editor.make_default_button_)); + EXPECT_STREQ("!,_,A (Default),B,D,_,@,_,C", + GetDisplayedEngines(editor).c_str()); + EXPECT_EQ(2, GetSelectedRowNum(editor)); + + gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(editor.list_store_), + &iter, NULL, 4); + gtk_tree_selection_select_iter(editor.selection_, &iter); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.make_default_button_)); + + gtk_button_clicked(GTK_BUTTON(editor.make_default_button_)); + EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(editor.remove_button_)); + EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(editor.make_default_button_)); + EXPECT_STREQ("!,_,A,B,D (Default),_,@,_,C", + GetDisplayedEngines(editor).c_str()); + EXPECT_EQ(4, GetSelectedRowNum(editor)); +} + +TEST_F(KeywordEditorViewTest, Remove) { + AddToModel("A", "a", true); + AddToModel("B", "b", true); + AddToModel("C", "c", false); + AddToModel("D", "d", false); + KeywordEditorView editor(profile_.get()); + EXPECT_STREQ("!,_,A,B (Default),_,@,_,C,D", + GetDisplayedEngines(editor).c_str()); + + GtkTreeIter iter; + gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(editor.list_store_), + &iter, NULL, 2); + gtk_tree_selection_select_iter(editor.selection_, &iter); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.remove_button_)); + + gtk_button_clicked(GTK_BUTTON(editor.remove_button_)); + EXPECT_STREQ("!,_,B (Default),_,@,_,C,D", + GetDisplayedEngines(editor).c_str()); + EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(editor.remove_button_)); + EXPECT_EQ(2, GetSelectedRowNum(editor)); + + gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(editor.list_store_), + &iter, NULL, 6); + gtk_tree_selection_select_iter(editor.selection_, &iter); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.remove_button_)); + + gtk_button_clicked(GTK_BUTTON(editor.remove_button_)); + EXPECT_STREQ("!,_,B (Default),_,@,_,D", + GetDisplayedEngines(editor).c_str()); + EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(editor.remove_button_)); + EXPECT_EQ(6, GetSelectedRowNum(editor)); + + gtk_button_clicked(GTK_BUTTON(editor.remove_button_)); + EXPECT_STREQ("!,_,B (Default),_,@,_", + GetDisplayedEngines(editor).c_str()); + EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(editor.remove_button_)); + EXPECT_EQ(2, GetSelectedRowNum(editor)); +} + +TEST_F(KeywordEditorViewTest, Edit) { + TemplateURL* a = AddToModel("A", "a", true); + TemplateURL* b = AddToModel("B", "b", true); + TemplateURL* c = AddToModel("C", "c", false); + TemplateURL* d = AddToModel("D", "d", false); + KeywordEditorView editor(profile_.get()); + EXPECT_STREQ("!,_,A,B (Default),_,@,_,C,D", + GetDisplayedEngines(editor).c_str()); + + editor.OnEditedKeyword(a, ASCIIToUTF16("AA"), ASCIIToUTF16("a"), + "example.com/{searchTerms}"); + EXPECT_STREQ("!,_,AA,B (Default),_,@,_,C,D", + GetDisplayedEngines(editor).c_str()); + + editor.OnEditedKeyword(b, ASCIIToUTF16("BB"), ASCIIToUTF16("b"), + "foo.example.com/{searchTerms}"); + EXPECT_STREQ("!,_,AA,BB (Default),_,@,_,C,D", + GetDisplayedEngines(editor).c_str()); + + editor.OnEditedKeyword(b, ASCIIToUTF16("BBB"), ASCIIToUTF16("b"), + "example.com"); + EXPECT_STREQ("!,_,AA,BBB,_,@,_,C,D", + GetDisplayedEngines(editor).c_str()); + + editor.OnEditedKeyword(d, ASCIIToUTF16("DD"), ASCIIToUTF16("d"), + "example.com"); + EXPECT_STREQ("!,_,AA,BBB,_,@,_,C,DD", + GetDisplayedEngines(editor).c_str()); + + editor.OnEditedKeyword(c, ASCIIToUTF16("CC"), ASCIIToUTF16("cc"), + "example.com"); + EXPECT_STREQ("!,_,AA,BBB,_,@,_,CC,DD", + GetDisplayedEngines(editor).c_str()); +} diff --git a/chrome/browser/ui/views/browser_dialogs.h b/chrome/browser/ui/views/browser_dialogs.h index 295aeec..d5be7e9 100644 --- a/chrome/browser/ui/views/browser_dialogs.h +++ b/chrome/browser/ui/views/browser_dialogs.h @@ -47,6 +47,10 @@ void ShowBugReportView(views::Window* parent, Profile* profile, TabContents* tab); +// Shows the "Clear browsing data" dialog box. See ClearBrowsingDataView. +void ShowClearBrowsingDataView(gfx::NativeWindow parent, + Profile* profile); + // Shows or hides the global bookmark bubble for the star button. void ShowBookmarkBubbleView(views::Window* parent, const gfx::Rect& bounds, diff --git a/chrome/browser/ui/views/dialog_stubs_gtk.cc b/chrome/browser/ui/views/dialog_stubs_gtk.cc index e496110..00cd889 100644 --- a/chrome/browser/ui/views/dialog_stubs_gtk.cc +++ b/chrome/browser/ui/views/dialog_stubs_gtk.cc @@ -10,8 +10,10 @@ #include "base/logging.h" #include "chrome/browser/fonts_languages_window.h" #include "chrome/browser/ui/gtk/about_chrome_dialog.h" +#include "chrome/browser/ui/gtk/clear_browsing_data_dialog_gtk.h" #include "chrome/browser/ui/gtk/collected_cookies_gtk.h" #include "chrome/browser/ui/gtk/edit_search_engine_dialog.h" +#include "chrome/browser/ui/gtk/keyword_editor_view.h" #include "chrome/browser/ui/gtk/repost_form_warning_gtk.h" #include "chrome/browser/ui/gtk/task_manager_gtk.h" #include "chrome/browser/ui/options/options_window.h" @@ -22,6 +24,16 @@ namespace browser { +void ShowClearBrowsingDataView(views::Widget* parent, + Profile* profile) { + ClearBrowsingDataDialogGtk::Show(GTK_WINDOW(parent->GetNativeView()), + profile); +} + +void ShowKeywordEditorView(Profile* profile) { + KeywordEditorView::Show(profile); +} + void ShowTaskManager() { TaskManagerGtk::Show(false); } diff --git a/chrome/browser/ui/views/edit_search_engine_dialog.cc b/chrome/browser/ui/views/edit_search_engine_dialog.cc new file mode 100644 index 0000000..a727fdb --- /dev/null +++ b/chrome/browser/ui/views/edit_search_engine_dialog.cc @@ -0,0 +1,269 @@ +// 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. + +#include "chrome/browser/ui/views/edit_search_engine_dialog.h" + +#include "base/i18n/rtl.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/browser/ui/search_engines/edit_search_engine_controller.h" +#include "googleurl/src/gurl.h" +#include "grit/app_resources.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "views/controls/image_view.h" +#include "views/controls/label.h" +#include "views/controls/table/table_view.h" +#include "views/controls/textfield/textfield.h" +#include "views/layout/grid_layout.h" +#include "views/layout/layout_constants.h" +#include "views/window/window.h" + +using views::GridLayout; +using views::ImageView; +using views::Textfield; + + +namespace { +// Converts a URL as understood by TemplateURL to one appropriate for display +// to the user. +std::wstring GetDisplayURL(const TemplateURL& turl) { + return turl.url() ? turl.url()->DisplayURL() : std::wstring(); +} +} // namespace + +namespace browser { + +void EditSearchEngine(gfx::NativeWindow parent, + const TemplateURL* template_url, + EditSearchEngineControllerDelegate* delegate, + Profile* profile) { + EditSearchEngineDialog::Show(parent, template_url, delegate, profile); +} + +} // namespace browser + +EditSearchEngineDialog::EditSearchEngineDialog( + const TemplateURL* template_url, + EditSearchEngineControllerDelegate* delegate, + Profile* profile) + : controller_(new EditSearchEngineController(template_url, + delegate, + profile)) { + Init(); +} + +// static +void EditSearchEngineDialog::Show(gfx::NativeWindow parent, + const TemplateURL* template_url, + EditSearchEngineControllerDelegate* delegate, + Profile* profile) { + EditSearchEngineDialog* contents = + new EditSearchEngineDialog(template_url, delegate, profile); + // Window interprets an empty rectangle as needing to query the content for + // the size as well as centering relative to the parent. + views::Window::CreateChromeWindow(parent, gfx::Rect(), contents); + contents->window()->Show(); + contents->GetDialogClientView()->UpdateDialogButtons(); + contents->title_tf_->SelectAll(); + contents->title_tf_->RequestFocus(); +} + +bool EditSearchEngineDialog::IsModal() const { + return true; +} + +std::wstring EditSearchEngineDialog::GetWindowTitle() const { + return UTF16ToWide(l10n_util::GetStringUTF16(controller_->template_url() ? + IDS_SEARCH_ENGINES_EDITOR_EDIT_WINDOW_TITLE : + IDS_SEARCH_ENGINES_EDITOR_NEW_WINDOW_TITLE)); +} + +bool EditSearchEngineDialog::IsDialogButtonEnabled( + MessageBoxFlags::DialogButton button) const { + if (button == MessageBoxFlags::DIALOGBUTTON_OK) { + return (controller_->IsKeywordValid(WideToUTF16(keyword_tf_->text())) && + controller_->IsTitleValid(WideToUTF16(title_tf_->text())) && + controller_->IsURLValid(WideToUTF8(url_tf_->text()))); + } + return true; +} + +bool EditSearchEngineDialog::Cancel() { + controller_->CleanUpCancelledAdd(); + return true; +} + +bool EditSearchEngineDialog::Accept() { + controller_->AcceptAddOrEdit(WideToUTF16(title_tf_->text()), + WideToUTF16(keyword_tf_->text()), + WideToUTF8(url_tf_->text())); + return true; +} + +views::View* EditSearchEngineDialog::GetContentsView() { + return this; +} + +void EditSearchEngineDialog::ContentsChanged(Textfield* sender, + const std::wstring& new_contents) { + GetDialogClientView()->UpdateDialogButtons(); + UpdateImageViews(); +} + +bool EditSearchEngineDialog::HandleKeyEvent( + Textfield* sender, + const views::KeyEvent& key_event) { + return false; +} + +void EditSearchEngineDialog::Init() { + // Create the views we'll need. + if (controller_->template_url()) { + title_tf_ = + CreateTextfield(controller_->template_url()->short_name(), false); + keyword_tf_ = CreateTextfield(controller_->template_url()->keyword(), true); + url_tf_ = + CreateTextfield(GetDisplayURL(*controller_->template_url()), false); + // We don't allow users to edit prepopulate URLs. This is done as + // occasionally we need to update the URL of prepopulated TemplateURLs. + url_tf_->SetReadOnly(controller_->template_url()->prepopulate_id() != 0); + } else { + title_tf_ = CreateTextfield(std::wstring(), false); + keyword_tf_ = CreateTextfield(std::wstring(), true); + url_tf_ = CreateTextfield(std::wstring(), false); + } + title_iv_ = new ImageView(); + keyword_iv_ = new ImageView(); + url_iv_ = new ImageView(); + + UpdateImageViews(); + + const int related_x = views::kRelatedControlHorizontalSpacing; + const int related_y = views::kRelatedControlVerticalSpacing; + const int unrelated_y = views::kUnrelatedControlVerticalSpacing; + + // View and GridLayout take care of deleting GridLayout for us. + GridLayout* layout = GridLayout::CreatePanel(this); + SetLayoutManager(layout); + + // Define the structure of the layout. + + // For the buttons. + views::ColumnSet* column_set = layout->AddColumnSet(0); + column_set->AddPaddingColumn(1, 0); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, related_x); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->LinkColumnSizes(1, 3, -1); + + // For the Textfields. + column_set = layout->AddColumnSet(1); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, related_x); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, related_x); + column_set->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + + // For the description. + column_set = layout->AddColumnSet(2); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + + // Add the contents. + layout->StartRow(0, 1); + layout->AddView(CreateLabel(IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_LABEL)); + layout->AddView(title_tf_); + layout->AddView(title_iv_); + + layout->StartRowWithPadding(0, 1, 0, related_y); + layout->AddView(CreateLabel(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_LABEL)); + layout->AddView(keyword_tf_); + layout->AddView(keyword_iv_); + + layout->StartRowWithPadding(0, 1, 0, related_y); + layout->AddView(CreateLabel(IDS_SEARCH_ENGINES_EDITOR_URL_LABEL)); + layout->AddView(url_tf_); + layout->AddView(url_iv_); + + // On RTL UIs (such as Arabic and Hebrew) the description text is not + // displayed correctly since it contains the substring "%s". This substring + // is not interpreted by the Unicode BiDi algorithm as an LTR string and + // therefore the end result is that the following right to left text is + // displayed: ".three two s% one" (where 'one', 'two', etc. are words in + // Hebrew). + // + // In order to fix this problem we transform the substring "%s" so that it + // is displayed correctly when rendered in an RTL context. + layout->StartRowWithPadding(0, 2, 0, unrelated_y); + std::wstring description = UTF16ToWide(l10n_util::GetStringUTF16( + IDS_SEARCH_ENGINES_EDITOR_URL_DESCRIPTION_LABEL)); + if (base::i18n::IsRTL()) { + const std::wstring reversed_percent(L"s%"); + std::wstring::size_type percent_index = + description.find(L"%s", static_cast<std::wstring::size_type>(0)); + if (percent_index != std::wstring::npos) + description.replace(percent_index, + reversed_percent.length(), + reversed_percent); + } + + views::Label* description_label = new views::Label(description); + description_label->SetHorizontalAlignment(views::Label::ALIGN_LEFT); + layout->AddView(description_label); + + layout->AddPaddingRow(0, related_y); +} + +views::Label* EditSearchEngineDialog::CreateLabel(int message_id) { + views::Label* label = + new views::Label(UTF16ToWide(l10n_util::GetStringUTF16(message_id))); + label->SetHorizontalAlignment(views::Label::ALIGN_LEFT); + return label; +} + +Textfield* EditSearchEngineDialog::CreateTextfield(const std::wstring& text, + bool lowercase) { + Textfield* text_field = new Textfield( + lowercase ? Textfield::STYLE_LOWERCASE : Textfield::STYLE_DEFAULT); + text_field->SetText(text); + text_field->SetController(this); + return text_field; +} + +void EditSearchEngineDialog::UpdateImageViews() { + UpdateImageView(keyword_iv_, + controller_->IsKeywordValid(WideToUTF16(keyword_tf_->text())), + IDS_SEARCH_ENGINES_INVALID_KEYWORD_TT); + UpdateImageView(url_iv_, controller_->IsURLValid(WideToUTF8(url_tf_->text())), + IDS_SEARCH_ENGINES_INVALID_URL_TT); + UpdateImageView(title_iv_, + controller_->IsTitleValid(WideToUTF16(title_tf_->text())), + IDS_SEARCH_ENGINES_INVALID_TITLE_TT); +} + +void EditSearchEngineDialog::UpdateImageView(ImageView* image_view, + bool is_valid, + int invalid_message_id) { + if (is_valid) { + image_view->SetTooltipText(std::wstring()); + image_view->SetImage( + ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_INPUT_GOOD)); + } else { + image_view->SetTooltipText( + UTF16ToWide(l10n_util::GetStringUTF16(invalid_message_id))); + image_view->SetImage( + ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_INPUT_ALERT)); + } +} diff --git a/chrome/browser/ui/views/edit_search_engine_dialog.h b/chrome/browser/ui/views/edit_search_engine_dialog.h new file mode 100644 index 0000000..12ae190 --- /dev/null +++ b/chrome/browser/ui/views/edit_search_engine_dialog.h @@ -0,0 +1,105 @@ +// 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. + +// EditSearchEngineDialog provides text fields for editing a keyword: the title, +// url and actual keyword. It is used by the KeywordEditorView of the Options +// dialog, and also on its own to confirm the addition of a keyword added by +// the ExternalJSObject via the RenderView. + +#ifndef CHROME_BROWSER_UI_VIEWS_EDIT_SEARCH_ENGINE_DIALOG_H_ +#define CHROME_BROWSER_UI_VIEWS_EDIT_SEARCH_ENGINE_DIALOG_H_ +#pragma once + +#include <windows.h> + +#include "views/controls/textfield/textfield_controller.h" +#include "views/window/dialog_delegate.h" + +namespace views { +class Label; +class ImageView; +class Window; +} + +class EditSearchEngineController; +class EditSearchEngineControllerDelegate; +class Profile; +class TemplateURL; +class TemplateURLModel; + +class EditSearchEngineDialog : public views::View, + public views::TextfieldController, + public views::DialogDelegate { + public: + // The |template_url| and/or |delegate| may be NULL. + EditSearchEngineDialog(const TemplateURL* template_url, + EditSearchEngineControllerDelegate* delegate, + Profile* profile); + virtual ~EditSearchEngineDialog() {} + + // Shows the dialog to the user. + static void Show(gfx::NativeWindow parent, + const TemplateURL* template_url, + EditSearchEngineControllerDelegate* delegate, + Profile* profile); + + // views::DialogDelegate: + virtual bool IsModal() const; + virtual std::wstring GetWindowTitle() const; + virtual bool IsDialogButtonEnabled( + MessageBoxFlags::DialogButton button) const; + virtual bool Cancel(); + virtual bool Accept(); + virtual views::View* GetContentsView(); + + // views::TextfieldController: + // Updates whether the user can accept the dialog as well as updating image + // views showing whether value is valid. + virtual void ContentsChanged(views::Textfield* sender, + const std::wstring& new_contents); + virtual bool HandleKeyEvent(views::Textfield* sender, + const views::KeyEvent& key_event); + + private: + void Init(); + + // Create a Label containing the text with the specified message id. + views::Label* CreateLabel(int message_id); + + // Creates a text field with the specified text. If |lowercase| is true, the + // Textfield is configured to map all input to lower case. + views::Textfield* CreateTextfield(const std::wstring& text, bool lowercase); + + // Invokes UpdateImageView for each of the images views. + void UpdateImageViews(); + + // Updates the tooltip and image of the image view based on is_valid. If + // is_valid is false the tooltip of the image view is set to the message with + // id invalid_message_id, otherwise the tooltip is set to the empty text. + void UpdateImageView(views::ImageView* image_view, + bool is_valid, + int invalid_message_id); + + // Used to parent window to. May be NULL or an invalid window. + HWND parent_; + + // View containing the buttons, text fields ... + views::View* view_; + + // Text fields. + views::Textfield* title_tf_; + views::Textfield* keyword_tf_; + views::Textfield* url_tf_; + + // Shows error images. + views::ImageView* title_iv_; + views::ImageView* keyword_iv_; + views::ImageView* url_iv_; + + scoped_ptr<EditSearchEngineController> controller_; + + DISALLOW_COPY_AND_ASSIGN(EditSearchEngineDialog); +}; + +#endif // CHROME_BROWSER_UI_VIEWS_EDIT_SEARCH_ENGINE_DIALOG_H_ diff --git a/chrome/browser/ui/views/keyword_editor_view.cc b/chrome/browser/ui/views/keyword_editor_view.cc new file mode 100644 index 0000000..d8125e1 --- /dev/null +++ b/chrome/browser/ui/views/keyword_editor_view.cc @@ -0,0 +1,308 @@ +// 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. + +#include "chrome/browser/ui/views/keyword_editor_view.h" + +#include <string> +#include <vector> + +#include "base/stl_util-inl.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.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/ui/search_engines/template_url_table_model.h" +#include "chrome/browser/ui/views/browser_dialogs.h" +#include "chrome/browser/ui/views/first_run_search_engine_view.h" +#include "chrome/common/pref_names.h" +#include "googleurl/src/gurl.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "grit/theme_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/point.h" +#include "views/background.h" +#include "views/controls/button/native_button.h" +#include "views/controls/table/table_view.h" +#include "views/controls/textfield/textfield.h" +#include "views/layout/grid_layout.h" +#include "views/layout/layout_constants.h" +#include "views/widget/widget.h" +#include "views/window/dialog_delegate.h" +#include "views/window/window.h" + +using views::GridLayout; +using views::NativeButton; + +namespace browser { + +// Declared in browser_dialogs.h so others don't have to depend on our header. +void ShowKeywordEditorView(Profile* profile) { + KeywordEditorView::Show(profile); +} + +} // namespace browser + + +// KeywordEditorView ---------------------------------------------------------- + +// If non-null, there is an open editor and this is the window it is contained +// in. +static views::Window* open_window = NULL; + +// static +// The typical case for showing a KeywordEditorView does not involve an +// observer, so use this function signature generally. +void KeywordEditorView::Show(Profile* profile) { + KeywordEditorView::ShowAndObserve(profile, NULL); +} + +// static +void KeywordEditorView::ShowAndObserve(Profile* profile, + SearchEngineSelectionObserver* observer) { + // If this panel is opened from an Incognito window, closing that window can + // leave this with a stale pointer. Use the original profile instead. + // See http://crbug.com/23359. + profile = profile ->GetOriginalProfile(); + if (!profile->GetTemplateURLModel()) + return; + + if (open_window != NULL) + open_window->CloseWindow(); + DCHECK(!open_window); + + // Both of these will be deleted when the dialog closes. + KeywordEditorView* keyword_editor = new KeywordEditorView(profile, observer); + + // Initialize the UI. By passing in an empty rect KeywordEditorView is + // queried for its preferred size. + open_window = views::Window::CreateChromeWindow(NULL, gfx::Rect(), + keyword_editor); + + open_window->Show(); +} + +KeywordEditorView::KeywordEditorView(Profile* profile, + SearchEngineSelectionObserver* observer) + : profile_(profile), + observer_(observer), + controller_(new KeywordEditorController(profile)), + default_chosen_(false) { + DCHECK(controller_->url_model()); + controller_->url_model()->AddObserver(this); + Init(); +} + +KeywordEditorView::~KeywordEditorView() { + table_view_->SetModel(NULL); + controller_->url_model()->RemoveObserver(this); +} + +void KeywordEditorView::OnEditedKeyword(const TemplateURL* template_url, + const string16& title, + const string16& keyword, + const std::string& url) { + if (template_url) { + controller_->ModifyTemplateURL(template_url, title, keyword, url); + + // Force the make default button to update. + OnSelectionChanged(); + } else { + table_view_->Select(controller_->AddTemplateURL(title, keyword, url)); + } +} + + +gfx::Size KeywordEditorView::GetPreferredSize() { + return gfx::Size(views::Window::GetLocalizedContentsSize( + IDS_SEARCHENGINES_DIALOG_WIDTH_CHARS, + IDS_SEARCHENGINES_DIALOG_HEIGHT_LINES)); +} + +bool KeywordEditorView::CanResize() const { + return true; +} + +std::wstring KeywordEditorView::GetWindowTitle() const { + return UTF16ToWide( + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_WINDOW_TITLE)); +} + +std::wstring KeywordEditorView::GetWindowName() const { + return UTF8ToWide(prefs::kKeywordEditorWindowPlacement); +} + +int KeywordEditorView::GetDialogButtons() const { + return MessageBoxFlags::DIALOGBUTTON_CANCEL; +} + +bool KeywordEditorView::Accept() { + open_window = NULL; + return true; +} + +bool KeywordEditorView::Cancel() { + open_window = NULL; + return true; +} + +views::View* KeywordEditorView::GetContentsView() { + return this; +} + +void KeywordEditorView::Init() { + std::vector<ui::TableColumn> columns; + columns.push_back( + ui::TableColumn(IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN, + ui::TableColumn::LEFT, -1, .75)); + columns.back().sortable = true; + columns.push_back( + ui::TableColumn(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN, + ui::TableColumn::LEFT, -1, .25)); + columns.back().sortable = true; + table_view_ = new views::TableView(controller_->table_model(), columns, + views::ICON_AND_TEXT, false, true, true); + table_view_->SetObserver(this); + + add_button_ = new views::NativeButton(this, UTF16ToWide( + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_NEW_BUTTON))); + add_button_->SetEnabled(controller_->loaded()); + add_button_->AddAccelerator( + views::Accelerator(ui::VKEY_A, false, false, true)); + add_button_->SetAccessibleKeyboardShortcut(L"A"); + + edit_button_ = new views::NativeButton(this, UTF16ToWide( + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_EDIT_BUTTON))); + edit_button_->SetEnabled(false); + + remove_button_ = new views::NativeButton(this, UTF16ToWide( + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_REMOVE_BUTTON))); + remove_button_->SetEnabled(false); + remove_button_->AddAccelerator( + views::Accelerator(ui::VKEY_R, false, false, true)); + remove_button_->SetAccessibleKeyboardShortcut(L"R"); + + make_default_button_ = new views::NativeButton( + this, + UTF16ToWide(l10n_util::GetStringUTF16( + IDS_SEARCH_ENGINES_EDITOR_MAKE_DEFAULT_BUTTON))); + make_default_button_->SetEnabled(false); + + InitLayoutManager(); +} + +void KeywordEditorView::InitLayoutManager() { + const int related_x = views::kRelatedControlHorizontalSpacing; + const int related_y = views::kRelatedControlVerticalSpacing; + const int unrelated_y = views::kUnrelatedControlVerticalSpacing; + + GridLayout* contents_layout = GridLayout::CreatePanel(this); + SetLayoutManager(contents_layout); + + // For the table and buttons. + views::ColumnSet* column_set = contents_layout->AddColumnSet(0); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, related_x); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + + contents_layout->StartRow(0, 0); + contents_layout->AddView(table_view_, 1, 8, GridLayout::FILL, + GridLayout::FILL); + contents_layout->AddView(add_button_); + + contents_layout->StartRowWithPadding(0, 0, 0, related_y); + contents_layout->SkipColumns(2); + contents_layout->AddView(edit_button_); + + contents_layout->StartRowWithPadding(0, 0, 0, related_y); + contents_layout->SkipColumns(2); + contents_layout->AddView(remove_button_); + + contents_layout->StartRowWithPadding(0, 0, 0, related_y); + contents_layout->SkipColumns(2); + contents_layout->AddView(make_default_button_); + + contents_layout->AddPaddingRow(1, 0); +} + +void KeywordEditorView::OnSelectionChanged() { + bool only_one_url_left = + controller_->url_model()->GetTemplateURLs().size() == 1; + if (table_view_->SelectedRowCount() == 1) { + const TemplateURL* selected_url = + controller_->GetTemplateURL(table_view_->FirstSelectedRow()); + edit_button_->SetEnabled(controller_->CanEdit(selected_url)); + make_default_button_->SetEnabled(controller_->CanMakeDefault(selected_url)); + remove_button_->SetEnabled(!only_one_url_left && + controller_->CanRemove(selected_url)); + } else { + edit_button_->SetEnabled(false); + make_default_button_->SetEnabled(false); + for (views::TableView::iterator i = table_view_->SelectionBegin(); + i != table_view_->SelectionEnd(); ++i) { + const TemplateURL* selected_url = controller_->GetTemplateURL(*i); + if (!controller_->CanRemove(selected_url)) { + remove_button_->SetEnabled(false); + return; + } + } + remove_button_->SetEnabled(!only_one_url_left); + } +} + +void KeywordEditorView::OnDoubleClick() { + if (edit_button_->IsEnabled()) { + DWORD pos = GetMessagePos(); + gfx::Point cursor_point(pos); + views::MouseEvent event(ui::ET_MOUSE_RELEASED, + cursor_point.x(), cursor_point.y(), + ui::EF_LEFT_BUTTON_DOWN); + ButtonPressed(edit_button_, event); + } +} + +void KeywordEditorView::ButtonPressed( + views::Button* sender, const views::Event& event) { + if (sender == add_button_) { + browser::EditSearchEngine(GetWindow()->GetNativeWindow(), NULL, this, + profile_); + } else if (sender == remove_button_) { + DCHECK_GT(table_view_->SelectedRowCount(), 0); + int last_view_row = -1; + for (views::TableView::iterator i = table_view_->SelectionBegin(); + i != table_view_->SelectionEnd(); ++i) { + last_view_row = table_view_->ModelToView(*i); + controller_->RemoveTemplateURL(*i); + } + if (last_view_row >= controller_->table_model()->RowCount()) + last_view_row = controller_->table_model()->RowCount() - 1; + if (last_view_row >= 0) + table_view_->Select(table_view_->ViewToModel(last_view_row)); + } else if (sender == edit_button_) { + const int selected_row = table_view_->FirstSelectedRow(); + const TemplateURL* template_url = + controller_->GetTemplateURL(selected_row); + browser::EditSearchEngine(GetWindow()->GetNativeWindow(), template_url, + this, profile_); + } else if (sender == make_default_button_) { + MakeDefaultTemplateURL(); + } else { + NOTREACHED(); + } +} + +void KeywordEditorView::OnTemplateURLModelChanged() { + add_button_->SetEnabled(controller_->loaded()); +} + +void KeywordEditorView::MakeDefaultTemplateURL() { + int new_index = + controller_->MakeDefaultTemplateURL(table_view_->FirstSelectedRow()); + if (new_index >= 0) + table_view_->Select(new_index); + default_chosen_ = true; +} diff --git a/chrome/browser/ui/views/keyword_editor_view.h b/chrome/browser/ui/views/keyword_editor_view.h new file mode 100644 index 0000000..746119e --- /dev/null +++ b/chrome/browser/ui/views/keyword_editor_view.h @@ -0,0 +1,117 @@ +// 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. + +#ifndef CHROME_BROWSER_UI_VIEWS_KEYWORD_EDITOR_VIEW_H_ +#define CHROME_BROWSER_UI_VIEWS_KEYWORD_EDITOR_VIEW_H_ +#pragma once + +#include <Windows.h> + +#include "base/string16.h" +#include "chrome/browser/search_engines/template_url_model_observer.h" +#include "chrome/browser/ui/search_engines/edit_search_engine_controller.h" +#include "chrome/browser/ui/search_engines/keyword_editor_controller.h" +#include "views/controls/button/button.h" +#include "views/controls/table/table_view_observer.h" +#include "views/view.h" +#include "views/window/dialog_delegate.h" + +namespace views { +class Label; +class NativeButton; +} + +namespace { +class BorderView; +} + +class SearchEngineSelectionObserver; +class SkBitmap; +class TemplateURL; + +// KeywordEditorView allows the user to edit keywords. + +class KeywordEditorView : public views::View, + public views::TableViewObserver, + public views::ButtonListener, + public TemplateURLModelObserver, + public views::DialogDelegate, + public EditSearchEngineControllerDelegate { + public: + // Shows the KeywordEditorView for the specified profile. If there is a + // KeywordEditorView already open, it is closed and a new one is shown. + static void Show(Profile* profile); + + // Shows the KeywordEditorView for the specified profile, and passes in + // an observer to be called back on view close. + static void ShowAndObserve(Profile* profile, + SearchEngineSelectionObserver* observer); + + KeywordEditorView(Profile* profile, + SearchEngineSelectionObserver* observer); + + virtual ~KeywordEditorView(); + + // Overridden from EditSearchEngineControllerDelegate. + // Calls AddTemplateURL or ModifyTemplateURL as appropriate. + virtual void OnEditedKeyword(const TemplateURL* template_url, + const string16& title, + const string16& keyword, + const std::string& url); + + // Overridden to invoke Layout. + virtual gfx::Size GetPreferredSize(); + + // views::DialogDelegate methods: + virtual bool CanResize() const; + virtual std::wstring GetWindowTitle() const; + virtual std::wstring GetWindowName() const; + virtual int GetDialogButtons() const; + virtual bool Accept(); + virtual bool Cancel(); + virtual views::View* GetContentsView(); + + private: + void Init(); + + // Creates the layout and adds the views to it. + void InitLayoutManager(); + + // TableViewObserver method. Updates buttons contingent on the selection. + virtual void OnSelectionChanged(); + // Edits the selected item. + virtual void OnDoubleClick(); + + // Button::ButtonListener method. + virtual void ButtonPressed(views::Button* sender, const views::Event& event); + + // TemplateURLModelObserver notification. + virtual void OnTemplateURLModelChanged(); + + // Toggles whether the selected keyword is the default search provider. + void MakeDefaultTemplateURL(); + + // The profile. + Profile* profile_; + + // Observer gets a callback when the KeywordEditorView closes. + SearchEngineSelectionObserver* observer_; + + scoped_ptr<KeywordEditorController> controller_; + + // True if the user has set a default search engine in this dialog. + bool default_chosen_; + + // All the views are added as children, so that we don't need to delete + // them directly. + views::TableView* table_view_; + views::NativeButton* add_button_; + views::NativeButton* edit_button_; + views::NativeButton* remove_button_; + views::NativeButton* make_default_button_; + + DISALLOW_COPY_AND_ASSIGN(KeywordEditorView); +}; + +#endif // CHROME_BROWSER_UI_VIEWS_KEYWORD_EDITOR_VIEW_H_ diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 8e5a78b..d26f309 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -2532,6 +2532,8 @@ 'browser/ui/gtk/certificate_viewer.h', 'browser/ui/gtk/chrome_gtk_frame.cc', 'browser/ui/gtk/chrome_gtk_frame.h', + 'browser/ui/gtk/clear_browsing_data_dialog_gtk.cc', + 'browser/ui/gtk/clear_browsing_data_dialog_gtk.h', 'browser/ui/gtk/collected_cookies_gtk.cc', 'browser/ui/gtk/collected_cookies_gtk.h', 'browser/ui/gtk/constrained_html_delegate_gtk.cc', @@ -2631,6 +2633,8 @@ 'browser/ui/gtk/instant_confirm_dialog_gtk.h', 'browser/ui/gtk/js_modal_dialog_gtk.cc', 'browser/ui/gtk/js_modal_dialog_gtk.h', + 'browser/ui/gtk/keyword_editor_view.cc', + 'browser/ui/gtk/keyword_editor_view.h', 'browser/ui/gtk/location_bar_view_gtk.cc', 'browser/ui/gtk/location_bar_view_gtk.h', 'browser/ui/gtk/menu_bar_helper.cc', @@ -2960,6 +2964,8 @@ 'browser/ui/views/js_modal_dialog_views.h', 'browser/ui/views/keyboard_overlay_delegate.cc', 'browser/ui/views/keyboard_overlay_delegate.h', + 'browser/ui/views/keyword_editor_view.cc', + 'browser/ui/views/keyword_editor_view.h', 'browser/ui/views/list_background.h', 'browser/ui/views/local_storage_info_view.cc', 'browser/ui/views/local_storage_info_view.h', @@ -3701,6 +3707,8 @@ ['include', '^browser/ui/gtk/certificate_viewer.h'], ['include', '^browser/ui/gtk/chrome_gtk_frame.cc'], ['include', '^browser/ui/gtk/chrome_gtk_frame.h'], + ['include', '^browser/ui/gtk/clear_browsing_data_dialog_gtk.cc'], + ['include', '^browser/ui/gtk/clear_browsing_data_dialog_gtk.h'], ['include', '^browser/ui/gtk/collected_cookies_gtk.cc'], ['include', '^browser/ui/gtk/collected_cookies_gtk.h'], ['include', '^browser/ui/gtk/constrained_window_gtk.cc'], @@ -3735,6 +3743,8 @@ ['include', '^browser/ui/gtk/importer/import_lock_dialog_gtk.h'], ['include', '^browser/ui/gtk/importer/import_progress_dialog_gtk.cc'], ['include', '^browser/ui/gtk/importer/import_progress_dialog_gtk.h'], + ['include', '^browser/ui/gtk/keyword_editor_view.cc'], + ['include', '^browser/ui/gtk/keyword_editor_view.h'], ['include', '^browser/ui/gtk/nine_box.cc'], ['include', '^browser/ui/gtk/nine_box.h'], ['include', '^browser/ui/gtk/owned_widget_gtk.cc'], diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index bbbbc9c..fcf5ba0 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1304,6 +1304,7 @@ 'browser/ui/gtk/gtk_chrome_shrinkable_hbox_unittest.cc', 'browser/ui/gtk/gtk_expanded_container_unittest.cc', 'browser/ui/gtk/gtk_theme_provider_unittest.cc', + 'browser/ui/gtk/keyword_editor_view_unittest.cc', 'browser/ui/gtk/reload_button_gtk_unittest.cc', 'browser/ui/gtk/status_icons/status_tray_gtk_unittest.cc', 'browser/ui/gtk/tabs/tab_renderer_gtk_unittest.cc', |