1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3 * Copyright © 2000, 2001, 2002, 2003 Marco Pesenti Gritti
4 * Copyright © 2003, 2004 Christian Persch
5 * Copyright © 2012 Igalia S.L.
6 *
7 * This file is part of Epiphany.
8 *
9 * Epiphany is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * Epiphany is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "config.h"
24 #include "ephy-encoding-dialog.h"
25
26 #include "ephy-debug.h"
27 #include "ephy-embed-container.h"
28 #include "ephy-embed-shell.h"
29 #include "ephy-embed-utils.h"
30 #include "ephy-embed.h"
31 #include "ephy-encodings.h"
32 #include "ephy-encoding-row.h"
33 #include "ephy-shell.h"
34
35 #include <glib/gi18n.h>
36 #include <gtk/gtk.h>
37 #include <webkit2/webkit2.h>
38
39 struct _EphyEncodingDialog {
40 GtkDialog parent_instance;
41
42 EphyEncodings *encodings;
43 EphyWindow *window;
44 EphyEmbed *embed;
45 gboolean update_embed_tag;
46 gboolean update_view_tag;
47 const char *selected_encoding;
48
49 /* from the UI file */
50 GtkStack *type_stack;
51 GtkSwitch *default_switch;
52 GtkListBox *list_box;
53 GtkListBox *recent_list_box;
54 GtkListBox *related_list_box;
55 GtkGrid *recent_grid;
56 GtkGrid *related_grid;
57 };
58
59 enum {
60 COL_TITLE_ELIDED,
61 COL_ENCODING,
62 NUM_COLS
63 };
64
65 enum {
66 PROP_0,
67 PROP_PARENT_WINDOW,
68 LAST_PROP
69 };
70
71 static GParamSpec *obj_properties[LAST_PROP];
72
G_DEFINE_TYPE(EphyEncodingDialog,ephy_encoding_dialog,GTK_TYPE_DIALOG)73 G_DEFINE_TYPE (EphyEncodingDialog, ephy_encoding_dialog, GTK_TYPE_DIALOG)
74
75 static void
76 select_encoding_row (GtkListBox *list_box,
77 EphyEncoding *encoding)
78 {
79 const char *target_encoding;
80 GList *rows, *r;
81
82 target_encoding = ephy_encoding_get_encoding (encoding);
83 rows = gtk_container_get_children (GTK_CONTAINER (list_box));
84
85 for (r = rows; r != NULL; r = r->next) {
86 EphyEncodingRow *ephy_encoding_row;
87 EphyEncoding *ephy_encoding;
88 const char *encoding_string = NULL;
89
90 ephy_encoding_row = EPHY_ENCODING_ROW (gtk_bin_get_child (GTK_BIN (r->data)));
91 ephy_encoding = ephy_encoding_row_get_encoding (ephy_encoding_row);
92 encoding_string = ephy_encoding_get_encoding (ephy_encoding);
93
94 if (g_strcmp0 (encoding_string, target_encoding) == 0) {
95 ephy_encoding_row_set_selected (ephy_encoding_row, TRUE);
96
97 gtk_list_box_select_row (list_box, GTK_LIST_BOX_ROW (r->data));
98 /* TODO scroll to row */
99
100 break;
101 }
102 }
103 g_list_free (rows);
104 }
105
106 static void
sync_encoding_against_embed(EphyEncodingDialog * dialog)107 sync_encoding_against_embed (EphyEncodingDialog *dialog)
108 {
109 const char *encoding;
110 gboolean is_automatic = FALSE;
111 WebKitWebView *view;
112
113 dialog->update_embed_tag = TRUE;
114
115 g_assert (EPHY_IS_EMBED (dialog->embed));
116 view = EPHY_GET_WEBKIT_WEB_VIEW_FROM_EMBED (dialog->embed);
117
118 encoding = webkit_web_view_get_custom_charset (view);
119 is_automatic = encoding == NULL;
120
121 if (!is_automatic) {
122 EphyEncoding *node;
123
124 node = ephy_encodings_get_encoding (dialog->encodings, encoding, TRUE);
125 g_assert (EPHY_IS_ENCODING (node));
126
127 /* Select the current encoding in the lists. */
128 select_encoding_row (dialog->list_box, node);
129 select_encoding_row (dialog->recent_list_box, node);
130 select_encoding_row (dialog->related_list_box, node);
131
132 /* TODO scroll the view so the active encoding is visible */
133 }
134 gtk_switch_set_active (dialog->default_switch, is_automatic);
135 gtk_switch_set_state (dialog->default_switch, is_automatic);
136 gtk_widget_set_sensitive (GTK_WIDGET (dialog->type_stack), !is_automatic);
137
138 dialog->update_embed_tag = FALSE;
139 }
140
141 static void
embed_net_stop_cb(EphyWebView * view,WebKitLoadEvent load_event,EphyEncodingDialog * dialog)142 embed_net_stop_cb (EphyWebView *view,
143 WebKitLoadEvent load_event,
144 EphyEncodingDialog *dialog)
145 {
146 if (ephy_web_view_is_loading (view) == FALSE)
147 sync_encoding_against_embed (dialog);
148 }
149
150 static void
ephy_encoding_dialog_detach_embed(EphyEncodingDialog * dialog)151 ephy_encoding_dialog_detach_embed (EphyEncodingDialog *dialog)
152 {
153 EphyEmbed **embedptr;
154
155 g_signal_handlers_disconnect_by_func (ephy_embed_get_web_view (dialog->embed),
156 G_CALLBACK (embed_net_stop_cb),
157 dialog);
158
159 embedptr = &dialog->embed;
160 g_object_remove_weak_pointer (G_OBJECT (dialog->embed),
161 (gpointer *)embedptr);
162 dialog->embed = NULL;
163 }
164
165 static void
ephy_encoding_dialog_attach_embed(EphyEncodingDialog * dialog)166 ephy_encoding_dialog_attach_embed (EphyEncodingDialog *dialog)
167 {
168 EphyEmbed *embed;
169 EphyEmbed **embedptr;
170
171 embed = ephy_embed_container_get_active_child (EPHY_EMBED_CONTAINER (dialog->window));
172 g_assert (EPHY_IS_EMBED (embed));
173
174 g_signal_connect (G_OBJECT (ephy_embed_get_web_view (embed)), "load-changed",
175 G_CALLBACK (embed_net_stop_cb), dialog);
176
177 dialog->embed = embed;
178
179 embedptr = &dialog->embed;
180 g_object_add_weak_pointer (G_OBJECT (dialog->embed),
181 (gpointer *)embedptr);
182 }
183
184 static void
ephy_encoding_dialog_sync_embed(EphyWindow * window,GParamSpec * pspec,EphyEncodingDialog * dialog)185 ephy_encoding_dialog_sync_embed (EphyWindow *window,
186 GParamSpec *pspec,
187 EphyEncodingDialog *dialog)
188 {
189 ephy_encoding_dialog_detach_embed (dialog);
190 ephy_encoding_dialog_attach_embed (dialog);
191 sync_encoding_against_embed (dialog);
192 }
193
194 static void
activate_choice(EphyEncodingDialog * dialog)195 activate_choice (EphyEncodingDialog *dialog)
196 {
197 WebKitWebView *view;
198
199 g_assert (EPHY_IS_EMBED (dialog->embed));
200 view = EPHY_GET_WEBKIT_WEB_VIEW_FROM_EMBED (dialog->embed);
201
202 if (gtk_switch_get_active (dialog->default_switch)) {
203 webkit_web_view_set_custom_charset (view, NULL);
204 } else if (dialog->selected_encoding != NULL) {
205 const char *code;
206
207 code = dialog->selected_encoding;
208
209 webkit_web_view_set_custom_charset (view, code);
210
211 ephy_encodings_add_recent (dialog->encodings, code);
212 }
213 }
214
215 static void
ephy_encoding_dialog_response_cb(GtkWidget * widget,int response,EphyEncodingDialog * dialog)216 ephy_encoding_dialog_response_cb (GtkWidget *widget,
217 int response,
218 EphyEncodingDialog *dialog)
219 {
220 gtk_widget_destroy (GTK_WIDGET (dialog));
221 }
222
223 static void
clean_selected_row(gpointer row,gpointer null_pointer)224 clean_selected_row (gpointer row,
225 gpointer null_pointer)
226 {
227 EphyEncodingRow *ephy_encoding_row;
228 ephy_encoding_row = EPHY_ENCODING_ROW (gtk_bin_get_child (GTK_BIN (row)));
229 ephy_encoding_row_set_selected (ephy_encoding_row, FALSE);
230 }
231
232 static void
clean_selected_list_box(GtkListBox * list_box)233 clean_selected_list_box (GtkListBox *list_box)
234 {
235 GList *rows;
236 rows = gtk_container_get_children (GTK_CONTAINER (list_box));
237 g_list_foreach (rows, (GFunc)clean_selected_row, NULL);
238 g_list_free (rows);
239 }
240
241 static void
clean_selected(EphyEncodingDialog * dialog)242 clean_selected (EphyEncodingDialog *dialog)
243 {
244 clean_selected_list_box (dialog->list_box);
245 clean_selected_list_box (dialog->recent_list_box);
246 clean_selected_list_box (dialog->related_list_box);
247 }
248
249 static void
row_activated_cb(GtkListBox * box,GtkListBoxRow * row,EphyEncodingDialog * dialog)250 row_activated_cb (GtkListBox *box,
251 GtkListBoxRow *row,
252 EphyEncodingDialog *dialog)
253 {
254 EphyEncodingRow *ephy_encoding_row;
255 EphyEncoding *ephy_encoding;
256 const char *selected_encoding;
257
258 if (dialog->update_embed_tag || dialog->update_view_tag)
259 return;
260
261 dialog->update_view_tag = TRUE;
262
263 ephy_encoding_row = EPHY_ENCODING_ROW (gtk_bin_get_child (GTK_BIN (row)));
264 ephy_encoding = ephy_encoding_row_get_encoding (ephy_encoding_row);
265 selected_encoding = ephy_encoding_get_encoding (ephy_encoding);
266
267 dialog->selected_encoding = selected_encoding;
268
269 clean_selected (dialog);
270 ephy_encoding_row_set_selected (ephy_encoding_row, TRUE);
271
272 activate_choice (dialog);
273
274 dialog->update_view_tag = FALSE;
275 }
276
277 static gboolean
default_switch_toggled_cb(GtkSwitch * default_switch,gboolean state,EphyEncodingDialog * dialog)278 default_switch_toggled_cb (GtkSwitch *default_switch,
279 gboolean state,
280 EphyEncodingDialog *dialog)
281 {
282 if (dialog->update_embed_tag || dialog->update_view_tag) {
283 gtk_switch_set_state (default_switch, !state); /* cancel switch change */
284 return TRUE;
285 }
286
287 dialog->update_view_tag = TRUE;
288
289 gtk_switch_set_active (default_switch, state);
290 gtk_switch_set_state (default_switch, state);
291
292 /* TODO if state == false && selected_encoding == NULL, select safe default in list, or find another solution */
293 if (state)
294 clean_selected (dialog);
295 activate_choice (dialog);
296
297 dialog->update_view_tag = FALSE;
298
299 return TRUE;
300 }
301
302 static void
show_all_button_clicked_cb(GtkButton * show_all_button,EphyEncodingDialog * dialog)303 show_all_button_clicked_cb (GtkButton *show_all_button,
304 EphyEncodingDialog *dialog)
305 {
306 gtk_stack_set_visible_child_name (dialog->type_stack, "scrolled-window");
307 }
308
309 static gint
sort_list_store(gconstpointer a,gconstpointer b,gpointer user_data)310 sort_list_store (gconstpointer a,
311 gconstpointer b,
312 gpointer user_data)
313 {
314 const char *encoding1 = ephy_encoding_get_title_elided ((EphyEncoding *)a);
315 const char *encoding2 = ephy_encoding_get_title_elided ((EphyEncoding *)b);
316
317 return g_strcmp0 (encoding1, encoding2);
318 }
319
320 static GtkWidget *
create_list_box_row(gpointer object,gpointer user_data)321 create_list_box_row (gpointer object,
322 gpointer user_data)
323 {
324 return GTK_WIDGET (ephy_encoding_row_new (EPHY_ENCODING (object)));
325 }
326
327 static void
add_list_item(EphyEncoding * encoding,GtkListBox * list_box)328 add_list_item (EphyEncoding *encoding,
329 GtkListBox *list_box)
330 {
331 gtk_container_add (GTK_CONTAINER (list_box), GTK_WIDGET (ephy_encoding_row_new (encoding)));
332 }
333
334 static int
sort_encodings(gconstpointer a,gconstpointer b)335 sort_encodings (gconstpointer a,
336 gconstpointer b)
337 {
338 EphyEncoding *enc1 = (EphyEncoding *)a;
339 EphyEncoding *enc2 = (EphyEncoding *)b;
340 const char *key1, *key2;
341
342 key1 = ephy_encoding_get_collation_key (enc1);
343 key2 = ephy_encoding_get_collation_key (enc2);
344
345 return strcmp (key1, key2);
346 }
347
348 static void
ephy_encoding_dialog_init(EphyEncodingDialog * dialog)349 ephy_encoding_dialog_init (EphyEncodingDialog *dialog)
350 {
351 GList *encodings, *p;
352 GListStore *store;
353
354 gtk_widget_init_template (GTK_WIDGET (dialog));
355
356 dialog->update_embed_tag = FALSE;
357 dialog->update_view_tag = FALSE;
358
359 dialog->encodings = ephy_embed_shell_get_encodings (EPHY_EMBED_SHELL (ephy_shell_get_default ()));
360
361 encodings = ephy_encodings_get_all (dialog->encodings);
362
363 store = g_list_store_new (EPHY_TYPE_ENCODING);
364 for (p = encodings; p; p = p->next) {
365 EphyEncoding *encoding = EPHY_ENCODING (p->data);
366 g_list_store_insert_sorted (store, encoding, sort_list_store, NULL);
367 }
368 g_list_free (encodings);
369
370 gtk_list_box_bind_model (dialog->list_box, G_LIST_MODEL (store),
371 create_list_box_row,
372 NULL, NULL);
373 }
374
375 static void
ephy_encoding_dialog_constructed(GObject * object)376 ephy_encoding_dialog_constructed (GObject *object)
377 {
378 EphyEncodingDialog *dialog;
379 WebKitWebView *view;
380 EphyEncoding *enc_node;
381 EphyLanguageGroup groups;
382 GList *recent;
383 GList *related = NULL;
384
385 /* selected encoding */
386 dialog = EPHY_ENCODING_DIALOG (object);
387
388 g_assert (EPHY_IS_EMBED (dialog->embed));
389 view = EPHY_GET_WEBKIT_WEB_VIEW_FROM_EMBED (dialog->embed);
390
391 dialog->selected_encoding = webkit_web_view_get_custom_charset (view);
392
393 g_object_bind_property (dialog->default_switch, "active", dialog->type_stack, "sensitive", G_BINDING_INVERT_BOOLEAN);
394
395 /* recent */
396 recent = ephy_encodings_get_recent (dialog->encodings);
397 if (recent != NULL) {
398 recent = g_list_sort (recent, (GCompareFunc)sort_encodings);
399 g_list_foreach (recent, (GFunc)add_list_item, dialog->recent_list_box);
400 } else
401 gtk_widget_hide (GTK_WIDGET (dialog->recent_grid));
402
403 /* related */
404 if (dialog->selected_encoding != NULL) {
405 enc_node = ephy_encodings_get_encoding (dialog->encodings, dialog->selected_encoding, TRUE);
406 g_assert (EPHY_IS_ENCODING (enc_node));
407 groups = ephy_encoding_get_language_groups (enc_node);
408
409 related = ephy_encodings_get_encodings (dialog->encodings, groups);
410 }
411 if (related != NULL) {
412 related = g_list_sort (related, (GCompareFunc)sort_encodings);
413 g_list_foreach (related, (GFunc)add_list_item, dialog->related_list_box);
414 } else
415 gtk_widget_hide (GTK_WIDGET (dialog->related_grid));
416
417 /* update list_boxes */
418 sync_encoding_against_embed (dialog);
419
420 /* chaining */
421 G_OBJECT_CLASS (ephy_encoding_dialog_parent_class)->constructed (object);
422 }
423
424 static void
ephy_encoding_dialog_dispose(GObject * object)425 ephy_encoding_dialog_dispose (GObject *object)
426 {
427 EphyEncodingDialog *dialog = EPHY_ENCODING_DIALOG (object);
428
429 g_signal_handlers_disconnect_by_func (dialog->window,
430 G_CALLBACK (ephy_encoding_dialog_sync_embed),
431 dialog);
432
433 if (dialog->embed != NULL)
434 ephy_encoding_dialog_detach_embed (dialog);
435
436 G_OBJECT_CLASS (ephy_encoding_dialog_parent_class)->dispose (object);
437 }
438
439 static void
ephy_encoding_dialog_set_parent_window(EphyEncodingDialog * dialog,EphyWindow * window)440 ephy_encoding_dialog_set_parent_window (EphyEncodingDialog *dialog,
441 EphyWindow *window)
442 {
443 g_assert (EPHY_IS_WINDOW (window));
444
445 g_signal_connect (G_OBJECT (window), "notify::active-child",
446 G_CALLBACK (ephy_encoding_dialog_sync_embed), dialog);
447
448 dialog->window = window;
449
450 ephy_encoding_dialog_attach_embed (dialog);
451 }
452
453 static void
ephy_encoding_dialog_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)454 ephy_encoding_dialog_set_property (GObject *object,
455 guint prop_id,
456 const GValue *value,
457 GParamSpec *pspec)
458 {
459 switch (prop_id) {
460 case PROP_PARENT_WINDOW:
461 ephy_encoding_dialog_set_parent_window (EPHY_ENCODING_DIALOG (object),
462 g_value_get_object (value));
463 break;
464 default:
465 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
466 break;
467 }
468 }
469
470 static void
ephy_encoding_dialog_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)471 ephy_encoding_dialog_get_property (GObject *object,
472 guint prop_id,
473 GValue *value,
474 GParamSpec *pspec)
475 {
476 switch (prop_id) {
477 case PROP_PARENT_WINDOW:
478 g_value_set_object (value, EPHY_ENCODING_DIALOG (object)->window);
479 break;
480 default:
481 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
482 break;
483 }
484 }
485
486 static void
ephy_encoding_dialog_class_init(EphyEncodingDialogClass * klass)487 ephy_encoding_dialog_class_init (EphyEncodingDialogClass *klass)
488 {
489 GObjectClass *object_class = G_OBJECT_CLASS (klass);
490 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
491
492 /* class creation */
493 object_class->constructed = ephy_encoding_dialog_constructed;
494 object_class->set_property = ephy_encoding_dialog_set_property;
495 object_class->get_property = ephy_encoding_dialog_get_property;
496 object_class->dispose = ephy_encoding_dialog_dispose;
497
498 obj_properties[PROP_PARENT_WINDOW] =
499 g_param_spec_object ("parent-window",
500 "Parent window",
501 "Parent window",
502 EPHY_TYPE_WINDOW,
503 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
504
505 g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
506
507 /* load from UI file */
508 gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/epiphany/gtk/encoding-dialog.ui");
509
510 gtk_widget_class_bind_template_child (widget_class, EphyEncodingDialog, type_stack);
511 gtk_widget_class_bind_template_child (widget_class, EphyEncodingDialog, default_switch);
512 gtk_widget_class_bind_template_child (widget_class, EphyEncodingDialog, list_box);
513 gtk_widget_class_bind_template_child (widget_class, EphyEncodingDialog, recent_list_box);
514 gtk_widget_class_bind_template_child (widget_class, EphyEncodingDialog, related_list_box);
515 gtk_widget_class_bind_template_child (widget_class, EphyEncodingDialog, recent_grid);
516 gtk_widget_class_bind_template_child (widget_class, EphyEncodingDialog, related_grid);
517
518 gtk_widget_class_bind_template_callback (widget_class, default_switch_toggled_cb);
519 gtk_widget_class_bind_template_callback (widget_class, ephy_encoding_dialog_response_cb);
520 gtk_widget_class_bind_template_callback (widget_class, row_activated_cb);
521 gtk_widget_class_bind_template_callback (widget_class, show_all_button_clicked_cb);
522 }
523
524 EphyEncodingDialog *
ephy_encoding_dialog_new(EphyWindow * parent)525 ephy_encoding_dialog_new (EphyWindow *parent)
526 {
527 return g_object_new (EPHY_TYPE_ENCODING_DIALOG,
528 "use-header-bar", TRUE,
529 "parent-window", parent,
530 NULL);
531 }
532