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