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 |