| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 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/gtk/options/content_exceptions_window_gtk.h" | |
| 6 | |
| 7 #include <set> | |
| 8 | |
| 9 #include "app/l10n_util.h" | |
| 10 #include "base/message_loop.h" | |
| 11 #include "base/utf_string_conversions.h" | |
| 12 #include "chrome/browser/gtk/gtk_util.h" | |
| 13 #include "chrome/browser/gtk/options/content_exception_editor.h" | |
| 14 #include "gfx/gtk_util.h" | |
| 15 #include "grit/generated_resources.h" | |
| 16 #include "grit/locale_settings.h" | |
| 17 | |
| 18 namespace { | |
| 19 | |
| 20 // Singletons for each possible exception window. | |
| 21 ContentExceptionsWindowGtk* instances[CONTENT_SETTINGS_NUM_TYPES] = { NULL }; | |
| 22 | |
| 23 } // namespace | |
| 24 | |
| 25 // static | |
| 26 void ContentExceptionsWindowGtk::ShowExceptionsWindow( | |
| 27 GtkWindow* parent, | |
| 28 HostContentSettingsMap* map, | |
| 29 HostContentSettingsMap* off_the_record_map, | |
| 30 ContentSettingsType type) { | |
| 31 DCHECK(map); | |
| 32 DCHECK(type < CONTENT_SETTINGS_NUM_TYPES); | |
| 33 // Geolocation exceptions are handled by SimpleContentExceptionsWindow. | |
| 34 DCHECK_NE(type, CONTENT_SETTINGS_TYPE_GEOLOCATION); | |
| 35 // Notification exceptions are handled by SimpleContentExceptionsWindow. | |
| 36 DCHECK_NE(type, CONTENT_SETTINGS_TYPE_NOTIFICATIONS); | |
| 37 | |
| 38 if (!instances[type]) { | |
| 39 // Create the options window. | |
| 40 instances[type] = | |
| 41 new ContentExceptionsWindowGtk(parent, map, off_the_record_map, type); | |
| 42 } else { | |
| 43 gtk_util::PresentWindow(instances[type]->dialog_, 0); | |
| 44 } | |
| 45 } | |
| 46 | |
| 47 ContentExceptionsWindowGtk::~ContentExceptionsWindowGtk() { | |
| 48 } | |
| 49 | |
| 50 ContentExceptionsWindowGtk::ContentExceptionsWindowGtk( | |
| 51 GtkWindow* parent, | |
| 52 HostContentSettingsMap* map, | |
| 53 HostContentSettingsMap* off_the_record_map, | |
| 54 ContentSettingsType type) | |
| 55 : allow_off_the_record_(off_the_record_map) { | |
| 56 // Build the list backing that GTK uses, along with an adapter which will | |
| 57 // sort stuff without changing the underlying backing store. | |
| 58 list_store_ = gtk_list_store_new(COL_COUNT, G_TYPE_STRING, G_TYPE_STRING, | |
| 59 PANGO_TYPE_STYLE); | |
| 60 sort_list_store_ = gtk_tree_model_sort_new_with_model( | |
| 61 GTK_TREE_MODEL(list_store_)); | |
| 62 treeview_ = gtk_tree_view_new_with_model(GTK_TREE_MODEL(sort_list_store_)); | |
| 63 g_object_unref(list_store_); | |
| 64 g_object_unref(sort_list_store_); | |
| 65 | |
| 66 // Set up the properties of the treeview | |
| 67 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_), TRUE); | |
| 68 g_signal_connect(treeview_, "row-activated", | |
| 69 G_CALLBACK(OnTreeViewRowActivateThunk), this); | |
| 70 | |
| 71 GtkTreeViewColumn* pattern_column = gtk_tree_view_column_new_with_attributes( | |
| 72 l10n_util::GetStringUTF8(IDS_EXCEPTIONS_PATTERN_HEADER).c_str(), | |
| 73 gtk_cell_renderer_text_new(), | |
| 74 "text", COL_PATTERN, | |
| 75 "style", COL_OTR, | |
| 76 NULL); | |
| 77 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_), pattern_column); | |
| 78 gtk_tree_view_column_set_sort_column_id(pattern_column, COL_PATTERN); | |
| 79 | |
| 80 GtkTreeViewColumn* action_column = gtk_tree_view_column_new_with_attributes( | |
| 81 l10n_util::GetStringUTF8(IDS_EXCEPTIONS_ACTION_HEADER).c_str(), | |
| 82 gtk_cell_renderer_text_new(), | |
| 83 "text", COL_ACTION, | |
| 84 "style", COL_OTR, | |
| 85 NULL); | |
| 86 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_), action_column); | |
| 87 gtk_tree_view_column_set_sort_column_id(action_column, COL_ACTION); | |
| 88 | |
| 89 treeview_selection_ = gtk_tree_view_get_selection( | |
| 90 GTK_TREE_VIEW(treeview_)); | |
| 91 gtk_tree_selection_set_mode(treeview_selection_, GTK_SELECTION_MULTIPLE); | |
| 92 g_signal_connect(treeview_selection_, "changed", | |
| 93 G_CALLBACK(OnTreeSelectionChangedThunk), this); | |
| 94 | |
| 95 // Bind |list_store_| to our C++ model. | |
| 96 model_.reset(new ContentExceptionsTableModel(map, off_the_record_map, type)); | |
| 97 model_adapter_.reset(new gtk_tree::TableAdapter(this, list_store_, | |
| 98 model_.get())); | |
| 99 // Force a reload of everything to copy data into |list_store_|. | |
| 100 model_adapter_->OnModelChanged(); | |
| 101 | |
| 102 dialog_ = gtk_dialog_new_with_buttons( | |
| 103 GetWindowTitle().c_str(), | |
| 104 parent, | |
| 105 static_cast<GtkDialogFlags>(GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR), | |
| 106 GTK_STOCK_CLOSE, | |
| 107 GTK_RESPONSE_CLOSE, | |
| 108 NULL); | |
| 109 gtk_window_set_default_size(GTK_WINDOW(dialog_), 500, -1); | |
| 110 // Allow browser windows to go in front of the options dialog in metacity. | |
| 111 gtk_window_set_type_hint(GTK_WINDOW(dialog_), GDK_WINDOW_TYPE_HINT_NORMAL); | |
| 112 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog_)->vbox), | |
| 113 gtk_util::kContentAreaSpacing); | |
| 114 | |
| 115 GtkWidget* hbox = gtk_hbox_new(FALSE, gtk_util::kControlSpacing); | |
| 116 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog_)->vbox), hbox); | |
| 117 | |
| 118 GtkWidget* treeview_box = gtk_vbox_new(FALSE, gtk_util::kControlSpacing); | |
| 119 | |
| 120 // Create a scrolled window to wrap the treeview widget. | |
| 121 GtkWidget* scrolled = gtk_scrolled_window_new(NULL, NULL); | |
| 122 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), | |
| 123 GTK_SHADOW_ETCHED_IN); | |
| 124 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), | |
| 125 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); | |
| 126 gtk_container_add(GTK_CONTAINER(scrolled), treeview_); | |
| 127 gtk_box_pack_start(GTK_BOX(treeview_box), scrolled, TRUE, TRUE, 0); | |
| 128 | |
| 129 // If we also have an OTR profile, inform the user that OTR exceptions are | |
| 130 // displayed in italics. | |
| 131 if (allow_off_the_record_) { | |
| 132 GtkWidget* incognito_label = gtk_label_new( | |
| 133 l10n_util::GetStringUTF8(IDS_EXCEPTIONS_OTR_IN_ITALICS).c_str()); | |
| 134 PangoAttrList* attributes = pango_attr_list_new(); | |
| 135 pango_attr_list_insert(attributes, | |
| 136 pango_attr_style_new(PANGO_STYLE_ITALIC)); | |
| 137 gtk_label_set_attributes(GTK_LABEL(incognito_label), attributes); | |
| 138 pango_attr_list_unref(attributes); | |
| 139 gtk_misc_set_alignment(GTK_MISC(incognito_label), 0, 0); | |
| 140 gtk_box_pack_start(GTK_BOX(treeview_box), incognito_label, FALSE, FALSE, 0); | |
| 141 } | |
| 142 | |
| 143 gtk_box_pack_start(GTK_BOX(hbox), treeview_box, TRUE, TRUE, 0); | |
| 144 | |
| 145 GtkWidget* button_box = gtk_vbox_new(FALSE, gtk_util::kControlSpacing); | |
| 146 | |
| 147 GtkWidget* add_button = gtk_util::BuildDialogButton(dialog_, | |
| 148 IDS_EXCEPTIONS_ADD_BUTTON, | |
| 149 GTK_STOCK_ADD); | |
| 150 g_signal_connect(add_button, "clicked", G_CALLBACK(AddThunk), this); | |
| 151 gtk_box_pack_start(GTK_BOX(button_box), add_button, FALSE, FALSE, 0); | |
| 152 | |
| 153 edit_button_ = gtk_util::BuildDialogButton(dialog_, | |
| 154 IDS_EXCEPTIONS_EDIT_BUTTON, | |
| 155 GTK_STOCK_EDIT); | |
| 156 g_signal_connect(edit_button_, "clicked", G_CALLBACK(EditThunk), this); | |
| 157 gtk_box_pack_start(GTK_BOX(button_box), edit_button_, FALSE, FALSE, 0); | |
| 158 | |
| 159 remove_button_ = gtk_util::BuildDialogButton(dialog_, | |
| 160 IDS_EXCEPTIONS_REMOVE_BUTTON, | |
| 161 GTK_STOCK_REMOVE); | |
| 162 g_signal_connect(remove_button_, "clicked", G_CALLBACK(RemoveThunk), this); | |
| 163 gtk_box_pack_start(GTK_BOX(button_box), remove_button_, FALSE, FALSE, 0); | |
| 164 | |
| 165 remove_all_button_ = gtk_util::BuildDialogButton( | |
| 166 dialog_, | |
| 167 IDS_EXCEPTIONS_REMOVEALL_BUTTON, | |
| 168 GTK_STOCK_CLEAR); | |
| 169 g_signal_connect(remove_all_button_, "clicked", G_CALLBACK(RemoveAllThunk), | |
| 170 this); | |
| 171 gtk_box_pack_start(GTK_BOX(button_box), remove_all_button_, FALSE, FALSE, 0); | |
| 172 | |
| 173 gtk_box_pack_start(GTK_BOX(hbox), button_box, FALSE, FALSE, 0); | |
| 174 | |
| 175 UpdateButtonState(); | |
| 176 | |
| 177 gtk_util::ShowDialogWithLocalizedSize(dialog_, | |
| 178 IDS_CONTENT_EXCEPTION_DIALOG_WIDTH_CHARS, | |
| 179 -1, | |
| 180 true); | |
| 181 | |
| 182 g_signal_connect(dialog_, "response", G_CALLBACK(gtk_widget_destroy), NULL); | |
| 183 g_signal_connect(dialog_, "destroy", G_CALLBACK(OnWindowDestroyThunk), this); | |
| 184 } | |
| 185 | |
| 186 void ContentExceptionsWindowGtk::SetColumnValues(int row, GtkTreeIter* iter) { | |
| 187 string16 pattern = model_->GetText(row, IDS_EXCEPTIONS_PATTERN_HEADER); | |
| 188 gtk_list_store_set(list_store_, iter, COL_PATTERN, | |
| 189 UTF16ToUTF8(pattern).c_str(), -1); | |
| 190 | |
| 191 string16 action = model_->GetText(row, IDS_EXCEPTIONS_ACTION_HEADER); | |
| 192 gtk_list_store_set(list_store_, iter, COL_ACTION, | |
| 193 UTF16ToUTF8(action).c_str(), -1); | |
| 194 | |
| 195 bool is_off_the_record = model_->entry_is_off_the_record(row); | |
| 196 PangoStyle style = | |
| 197 is_off_the_record ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL; | |
| 198 gtk_list_store_set(list_store_, iter, COL_OTR, style, -1); | |
| 199 } | |
| 200 | |
| 201 void ContentExceptionsWindowGtk::AcceptExceptionEdit( | |
| 202 const ContentSettingsPattern& pattern, | |
| 203 ContentSetting setting, | |
| 204 bool is_off_the_record, | |
| 205 int index, | |
| 206 bool is_new) { | |
| 207 DCHECK(!is_off_the_record || allow_off_the_record_); | |
| 208 | |
| 209 if (!is_new) | |
| 210 model_->RemoveException(index); | |
| 211 | |
| 212 model_->AddException(pattern, setting, is_off_the_record); | |
| 213 | |
| 214 int new_index = model_->IndexOfExceptionByPattern(pattern, is_off_the_record); | |
| 215 DCHECK_NE(-1, new_index); | |
| 216 | |
| 217 gtk_tree::SelectAndFocusRowNum(new_index, GTK_TREE_VIEW(treeview_)); | |
| 218 | |
| 219 UpdateButtonState(); | |
| 220 } | |
| 221 | |
| 222 void ContentExceptionsWindowGtk::UpdateButtonState() { | |
| 223 int num_selected = gtk_tree_selection_count_selected_rows( | |
| 224 treeview_selection_); | |
| 225 int row_count = gtk_tree_model_iter_n_children( | |
| 226 GTK_TREE_MODEL(sort_list_store_), NULL); | |
| 227 | |
| 228 // TODO(erg): http://crbug.com/34177 , support editing of more than one entry | |
| 229 // at a time. | |
| 230 gtk_widget_set_sensitive(edit_button_, num_selected == 1); | |
| 231 gtk_widget_set_sensitive(remove_button_, num_selected >= 1); | |
| 232 gtk_widget_set_sensitive(remove_all_button_, row_count > 0); | |
| 233 } | |
| 234 | |
| 235 void ContentExceptionsWindowGtk::Add(GtkWidget* widget) { | |
| 236 new ContentExceptionEditor(GTK_WINDOW(dialog_), | |
| 237 this, model_.get(), allow_off_the_record_, -1, | |
| 238 ContentSettingsPattern(), | |
| 239 CONTENT_SETTING_BLOCK, false); | |
| 240 } | |
| 241 | |
| 242 void ContentExceptionsWindowGtk::Edit(GtkWidget* widget) { | |
| 243 std::set<std::pair<int, int> > indices; | |
| 244 GetSelectedModelIndices(&indices); | |
| 245 DCHECK_GT(indices.size(), 0u); | |
| 246 int index = indices.begin()->first; | |
| 247 const HostContentSettingsMap::PatternSettingPair& entry = | |
| 248 model_->entry_at(index); | |
| 249 new ContentExceptionEditor(GTK_WINDOW(dialog_), this, model_.get(), | |
| 250 allow_off_the_record_, index, | |
| 251 entry.first, entry.second, | |
| 252 model_->entry_is_off_the_record(index)); | |
| 253 } | |
| 254 | |
| 255 void ContentExceptionsWindowGtk::Remove(GtkWidget* widget) { | |
| 256 std::set<std::pair<int, int> > model_selected_indices; | |
| 257 GetSelectedModelIndices(&model_selected_indices); | |
| 258 | |
| 259 int selected_row = gtk_tree_model_iter_n_children( | |
| 260 GTK_TREE_MODEL(sort_list_store_), NULL); | |
| 261 for (std::set<std::pair<int, int> >::reverse_iterator i = | |
| 262 model_selected_indices.rbegin(); | |
| 263 i != model_selected_indices.rend(); ++i) { | |
| 264 model_->RemoveException(i->first); | |
| 265 selected_row = std::min(selected_row, i->second); | |
| 266 } | |
| 267 | |
| 268 int row_count = model_->RowCount(); | |
| 269 if (row_count <= 0) | |
| 270 return; | |
| 271 if (selected_row >= row_count) | |
| 272 selected_row = row_count - 1; | |
| 273 gtk_tree::SelectAndFocusRowNum(selected_row, | |
| 274 GTK_TREE_VIEW(treeview_)); | |
| 275 | |
| 276 UpdateButtonState(); | |
| 277 } | |
| 278 | |
| 279 void ContentExceptionsWindowGtk::RemoveAll(GtkWidget* widget) { | |
| 280 model_->RemoveAll(); | |
| 281 UpdateButtonState(); | |
| 282 } | |
| 283 | |
| 284 std::string ContentExceptionsWindowGtk::GetWindowTitle() const { | |
| 285 switch (model_->content_type()) { | |
| 286 case CONTENT_SETTINGS_TYPE_COOKIES: | |
| 287 return l10n_util::GetStringUTF8(IDS_COOKIE_EXCEPTION_TITLE); | |
| 288 case CONTENT_SETTINGS_TYPE_IMAGES: | |
| 289 return l10n_util::GetStringUTF8(IDS_IMAGES_EXCEPTION_TITLE); | |
| 290 case CONTENT_SETTINGS_TYPE_JAVASCRIPT: | |
| 291 return l10n_util::GetStringUTF8(IDS_JS_EXCEPTION_TITLE); | |
| 292 case CONTENT_SETTINGS_TYPE_PLUGINS: | |
| 293 return l10n_util::GetStringUTF8(IDS_PLUGINS_EXCEPTION_TITLE); | |
| 294 case CONTENT_SETTINGS_TYPE_POPUPS: | |
| 295 return l10n_util::GetStringUTF8(IDS_POPUP_EXCEPTION_TITLE); | |
| 296 default: | |
| 297 NOTREACHED(); | |
| 298 } | |
| 299 return std::string(); | |
| 300 } | |
| 301 | |
| 302 void ContentExceptionsWindowGtk::GetSelectedModelIndices( | |
| 303 std::set<std::pair<int, int> >* indices) { | |
| 304 GtkTreeModel* model; | |
| 305 GList* paths = gtk_tree_selection_get_selected_rows(treeview_selection_, | |
| 306 &model); | |
| 307 for (GList* item = paths; item; item = item->next) { | |
| 308 GtkTreePath* sorted_path = reinterpret_cast<GtkTreePath*>(item->data); | |
| 309 int sorted_row = gtk_tree::GetRowNumForPath(sorted_path); | |
| 310 GtkTreePath* path = gtk_tree_model_sort_convert_path_to_child_path( | |
| 311 GTK_TREE_MODEL_SORT(sort_list_store_), sorted_path); | |
| 312 int row = gtk_tree::GetRowNumForPath(path); | |
| 313 gtk_tree_path_free(path); | |
| 314 indices->insert(std::make_pair(row, sorted_row)); | |
| 315 } | |
| 316 | |
| 317 g_list_foreach(paths, (GFunc)gtk_tree_path_free, NULL); | |
| 318 g_list_free(paths); | |
| 319 } | |
| 320 | |
| 321 void ContentExceptionsWindowGtk::OnTreeViewRowActivate( | |
| 322 GtkWidget* sender, | |
| 323 GtkTreePath* path, | |
| 324 GtkTreeViewColumn* column) { | |
| 325 Edit(sender); | |
| 326 } | |
| 327 | |
| 328 void ContentExceptionsWindowGtk::OnWindowDestroy(GtkWidget* widget) { | |
| 329 instances[model_->content_type()] = NULL; | |
| 330 MessageLoop::current()->DeleteSoon(FROM_HERE, this); | |
| 331 } | |
| 332 | |
| 333 void ContentExceptionsWindowGtk::OnTreeSelectionChanged( | |
| 334 GtkWidget* selection) { | |
| 335 UpdateButtonState(); | |
| 336 } | |
| OLD | NEW |