diff options
Diffstat (limited to 'src/terminal-encoding.c')
-rw-r--r-- | src/terminal-encoding.c | 614 |
1 files changed, 614 insertions, 0 deletions
diff --git a/src/terminal-encoding.c b/src/terminal-encoding.c new file mode 100644 index 0000000..25d343b --- /dev/null +++ b/src/terminal-encoding.c @@ -0,0 +1,614 @@ +/* + * Copyright © 2002 Red Hat, Inc. + * Copyright © 2008 Christian Persch + * + * Mate-terminal is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Mate-terminal is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <string.h> + +#include <gtk/gtk.h> + +#include "terminal-app.h" +#include "terminal-debug.h" +#include "terminal-encoding.h" +#include "terminal-intl.h" +#include "terminal-profile.h" +#include "terminal-util.h" + +/* Overview + * + * There's a list of character sets stored in mateconf, indicating + * which encodings to display in the encoding menu. + * + * We have a pre-canned list of available encodings + * (hardcoded in the table below) that can be added to + * the encoding menu, and to give a human-readable name + * to certain encodings. + * + * If the mateconf list contains an encoding not in the + * predetermined table, then that encoding is + * labeled "user defined" but still appears in the menu. + */ + +static const struct { + const char *charset; + const char *name; +} encodings[] = { + { "ISO-8859-1", N_("Western") }, + { "ISO-8859-2", N_("Central European") }, + { "ISO-8859-3", N_("South European") }, + { "ISO-8859-4", N_("Baltic") }, + { "ISO-8859-5", N_("Cyrillic") }, + { "ISO-8859-6", N_("Arabic") }, + { "ISO-8859-7", N_("Greek") }, + { "ISO-8859-8", N_("Hebrew Visual") }, + { "ISO-8859-8-I", N_("Hebrew") }, + { "ISO-8859-9", N_("Turkish") }, + { "ISO-8859-10", N_("Nordic") }, + { "ISO-8859-13", N_("Baltic") }, + { "ISO-8859-14", N_("Celtic") }, + { "ISO-8859-15", N_("Western") }, + { "ISO-8859-16", N_("Romanian") }, + { "UTF-8", N_("Unicode") }, + { "ARMSCII-8", N_("Armenian") }, + { "BIG5", N_("Chinese Traditional") }, + { "BIG5-HKSCS", N_("Chinese Traditional") }, + { "CP866", N_("Cyrillic/Russian") }, + { "EUC-JP", N_("Japanese") }, + { "EUC-KR", N_("Korean") }, + { "EUC-TW", N_("Chinese Traditional") }, + { "GB18030", N_("Chinese Simplified") }, + { "GB2312", N_("Chinese Simplified") }, + { "GBK", N_("Chinese Simplified") }, + { "GEORGIAN-PS", N_("Georgian") }, + { "IBM850", N_("Western") }, + { "IBM852", N_("Central European") }, + { "IBM855", N_("Cyrillic") }, + { "IBM857", N_("Turkish") }, + { "IBM862", N_("Hebrew") }, + { "IBM864", N_("Arabic") }, + { "ISO-2022-JP", N_("Japanese") }, + { "ISO-2022-KR", N_("Korean") }, + { "ISO-IR-111", N_("Cyrillic") }, + { "KOI8-R", N_("Cyrillic") }, + { "KOI8-U", N_("Cyrillic/Ukrainian") }, + { "MAC_ARABIC", N_("Arabic") }, + { "MAC_CE", N_("Central European") }, + { "MAC_CROATIAN", N_("Croatian") }, + { "MAC-CYRILLIC", N_("Cyrillic") }, + { "MAC_DEVANAGARI", N_("Hindi") }, + { "MAC_FARSI", N_("Persian") }, + { "MAC_GREEK", N_("Greek") }, + { "MAC_GUJARATI", N_("Gujarati") }, + { "MAC_GURMUKHI", N_("Gurmukhi") }, + { "MAC_HEBREW", N_("Hebrew") }, + { "MAC_ICELANDIC", N_("Icelandic") }, + { "MAC_ROMAN", N_("Western") }, + { "MAC_ROMANIAN", N_("Romanian") }, + { "MAC_TURKISH", N_("Turkish") }, + { "MAC_UKRAINIAN", N_("Cyrillic/Ukrainian") }, + { "SHIFT_JIS", N_("Japanese") }, + { "TCVN", N_("Vietnamese") }, + { "TIS-620", N_("Thai") }, + { "UHC", N_("Korean") }, + { "VISCII", N_("Vietnamese") }, + { "WINDOWS-1250", N_("Central European") }, + { "WINDOWS-1251", N_("Cyrillic") }, + { "WINDOWS-1252", N_("Western") }, + { "WINDOWS-1253", N_("Greek") }, + { "WINDOWS-1254", N_("Turkish") }, + { "WINDOWS-1255", N_("Hebrew") }, + { "WINDOWS-1256", N_("Arabic") }, + { "WINDOWS-1257", N_("Baltic") }, + { "WINDOWS-1258", N_("Vietnamese") }, +#if 0 + /* These encodings do NOT pass-through ASCII, so are always rejected. + * FIXME: why are they in this table; or rather why do we need + * the ASCII pass-through requirement? + */ + { "UTF-7", N_("Unicode") }, + { "UTF-16", N_("Unicode") }, + { "UCS-2", N_("Unicode") }, + { "UCS-4", N_("Unicode") }, + { "JOHAB", N_("Korean") }, +#endif +}; + +typedef struct { + GtkWidget *dialog; + GtkListStore *base_store; + GtkTreeView *available_tree_view; + GtkTreeSelection *available_selection; + GtkTreeModel *available_model; + GtkTreeView *active_tree_view; + GtkTreeSelection *active_selection; + GtkTreeModel *active_model; + GtkWidget *add_button; + GtkWidget *remove_button; +} EncodingDialogData; + +static GtkWidget *encoding_dialog = NULL; + +TerminalEncoding * +terminal_encoding_new (const char *charset, + const char *display_name, + gboolean is_custom, + gboolean force_valid) +{ + TerminalEncoding *encoding; + + encoding = g_slice_new (TerminalEncoding); + encoding->refcount = 1; + encoding->id = g_strdup (charset); + encoding->name = g_strdup (display_name); + encoding->valid = encoding->validity_checked = force_valid; + encoding->is_custom = is_custom; + encoding->is_active = FALSE; + + return encoding; +} + +TerminalEncoding* +terminal_encoding_ref (TerminalEncoding *encoding) +{ + g_return_val_if_fail (encoding != NULL, NULL); + + encoding->refcount++; + return encoding; +} + +void +terminal_encoding_unref (TerminalEncoding *encoding) +{ + if (--encoding->refcount > 0) + return; + + g_free (encoding->name); + g_free (encoding->id); + g_slice_free (TerminalEncoding, encoding); +} + +const char * +terminal_encoding_get_id (TerminalEncoding *encoding) +{ + g_return_val_if_fail (encoding != NULL, NULL); + + return encoding->id; +} + +const char * +terminal_encoding_get_charset (TerminalEncoding *encoding) +{ + g_return_val_if_fail (encoding != NULL, NULL); + + if (strcmp (encoding->id, "current") == 0) + { + const char *charset; + + g_get_charset (&charset); + return charset; + } + + return encoding->id; +} + +gboolean +terminal_encoding_is_valid (TerminalEncoding *encoding) +{ + /* All of the printing ASCII characters from space (32) to the tilde (126) */ + static const char ascii_sample[] = + " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; + char *converted; + gsize bytes_read = 0, bytes_written = 0; + GError *error = NULL; + + if (encoding->validity_checked) + return encoding->valid; + + /* Test that the encoding is a proper superset of ASCII (which naive + * apps are going to use anyway) by attempting to validate the text + * using the current encoding. This also flushes out any encodings + * which the underlying GIConv implementation can't support. + */ + converted = g_convert (ascii_sample, sizeof (ascii_sample) - 1, + terminal_encoding_get_charset (encoding), "UTF-8", + &bytes_read, &bytes_written, &error); + + /* The encoding is only valid if ASCII passes through cleanly. */ + encoding->valid = (bytes_read == (sizeof (ascii_sample) - 1)) && + (converted != NULL) && + (strcmp (converted, ascii_sample) == 0); + +#ifdef MATE_ENABLE_DEBUG + _TERMINAL_DEBUG_IF (TERMINAL_DEBUG_ENCODINGS) + { + if (!encoding->valid) + { + _terminal_debug_print (TERMINAL_DEBUG_ENCODINGS, + "Rejecting encoding %s as invalid:\n", + terminal_encoding_get_charset (encoding)); + _terminal_debug_print (TERMINAL_DEBUG_ENCODINGS, + " input \"%s\"\n", + ascii_sample); + _terminal_debug_print (TERMINAL_DEBUG_ENCODINGS, + " output \"%s\" bytes read %u written %u\n", + converted ? converted : "(null)", bytes_read, bytes_written); + if (error) + _terminal_debug_print (TERMINAL_DEBUG_ENCODINGS, + " Error: %s\n", + error->message); + } + else + _terminal_debug_print (TERMINAL_DEBUG_ENCODINGS, + "Encoding %s is valid\n\n", + terminal_encoding_get_charset (encoding)); + } +#endif + + g_clear_error (&error); + g_free (converted); + + encoding->validity_checked = TRUE; + return encoding->valid; +} + +GType +terminal_encoding_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) { + type = g_boxed_type_register_static (I_("TerminalEncoding"), + (GBoxedCopyFunc) terminal_encoding_ref, + (GBoxedFreeFunc) terminal_encoding_unref); + } + + return type; +} + +static void +update_active_encodings_mateconf (void) +{ + GSList *list, *l; + GSList *strings = NULL; + MateConfClient *conf; + + list = terminal_app_get_active_encodings (terminal_app_get ()); + for (l = list; l != NULL; l = l->next) + { + TerminalEncoding *encoding = (TerminalEncoding *) l->data; + + strings = g_slist_prepend (strings, (gpointer) terminal_encoding_get_id (encoding)); + } + + conf = mateconf_client_get_default (); + mateconf_client_set_list (conf, + CONF_GLOBAL_PREFIX"/active_encodings", + MATECONF_VALUE_STRING, + strings, + NULL); + g_object_unref (conf); + + g_slist_free (strings); + g_slist_foreach (list, (GFunc) terminal_encoding_unref, NULL); + g_slist_free (list); +} + +static void +response_callback (GtkWidget *window, + int id, + EncodingDialogData *data) +{ + if (id == GTK_RESPONSE_HELP) + terminal_util_show_help ("mate-terminal-encoding-add", GTK_WINDOW (window)); + else + gtk_widget_destroy (GTK_WIDGET (window)); +} + +enum +{ + COLUMN_NAME, + COLUMN_CHARSET, + COLUMN_DATA, + N_COLUMNS +}; + +static void +selection_changed_cb (GtkTreeSelection *selection, + EncodingDialogData *data) +{ + GtkWidget *button; + gboolean have_selection; + + if (selection == data->available_selection) + button = data->add_button; + else if (selection == data->active_selection) + button = data->remove_button; + else + g_assert_not_reached (); + + have_selection = gtk_tree_selection_get_selected (selection, NULL, NULL); + gtk_widget_set_sensitive (button, have_selection); +} + +static void +button_clicked_cb (GtkWidget *button, + EncodingDialogData *data) +{ + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter filter_iter, iter; + TerminalEncoding *encoding; + + if (button == data->add_button) + selection = data->available_selection; + else if (button == data->remove_button) + selection = data->active_selection; + else + g_assert_not_reached (); + + if (!gtk_tree_selection_get_selected (selection, &model, &filter_iter)) + return; + + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), + &iter, + &filter_iter); + + model = GTK_TREE_MODEL (data->base_store); + gtk_tree_model_get (model, &iter, COLUMN_DATA, &encoding, -1); + g_assert (encoding != NULL); + + if (button == data->add_button) + encoding->is_active = TRUE; + else if (button == data->remove_button) + encoding->is_active = FALSE; + else + g_assert_not_reached (); + + terminal_encoding_unref (encoding); + + /* We don't need to emit row-changed here, since updating the mateconf pref + * will update the models. + */ + update_active_encodings_mateconf (); +} + +static void +liststore_insert_encoding (gpointer key, + TerminalEncoding *encoding, + GtkListStore *store) +{ + GtkTreeIter iter; + + if (!terminal_encoding_is_valid (encoding)) + return; + + gtk_list_store_insert_with_values (store, &iter, -1, + COLUMN_CHARSET, terminal_encoding_get_charset (encoding), + COLUMN_NAME, encoding->name, + COLUMN_DATA, encoding, + -1); +} + +static gboolean +filter_active_encodings (GtkTreeModel *child_model, + GtkTreeIter *child_iter, + gpointer data) +{ + TerminalEncoding *encoding; + gboolean active = GPOINTER_TO_UINT (data); + gboolean visible; + + gtk_tree_model_get (child_model, child_iter, COLUMN_DATA, &encoding, -1); + visible = active ? encoding->is_active : !encoding->is_active; + terminal_encoding_unref (encoding); + + return visible; +} + +static GtkTreeModel * +encodings_create_treemodel (GtkListStore *base_store, + gboolean active) +{ + GtkTreeModel *model; + + model = gtk_tree_model_filter_new (GTK_TREE_MODEL (base_store), NULL); + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (model), + filter_active_encodings, + GUINT_TO_POINTER (active), NULL); + + return model; +} + +static void +encodings_list_changed_cb (TerminalApp *app, + EncodingDialogData *data) +{ + gtk_list_store_clear (data->base_store); + + g_hash_table_foreach (terminal_app_get_encodings (app), (GHFunc) liststore_insert_encoding, data->base_store); +} + +static void +encoding_dialog_data_free (EncodingDialogData *data) +{ + g_signal_handlers_disconnect_by_func (terminal_app_get (), + G_CALLBACK (encodings_list_changed_cb), + data); + + g_free (data); +} + +void +terminal_encoding_dialog_show (GtkWindow *transient_parent) +{ + TerminalApp *app; + GtkCellRenderer *cell_renderer; + GtkTreeViewColumn *column; + GtkTreeModel *model; + EncodingDialogData *data; + + if (encoding_dialog) + { + gtk_window_set_transient_for (GTK_WINDOW (encoding_dialog), transient_parent); + gtk_window_present (GTK_WINDOW (encoding_dialog)); + return; + } + + data = g_new (EncodingDialogData, 1); + + if (!terminal_util_load_builder_file ("encodings-dialog.ui", + "encodings-dialog", &data->dialog, + "add-button", &data->add_button, + "remove-button", &data->remove_button, + "available-treeview", &data->available_tree_view, + "displayed-treeview", &data->active_tree_view, + NULL)) + { + g_free (data); + return; + } + + g_object_set_data_full (G_OBJECT (data->dialog), "GT::Data", data, (GDestroyNotify) encoding_dialog_data_free); + + gtk_window_set_transient_for (GTK_WINDOW (data->dialog), transient_parent); + gtk_window_set_role (GTK_WINDOW (data->dialog), "mate-terminal-encodings"); + g_signal_connect (data->dialog, "response", + G_CALLBACK (response_callback), data); + + /* buttons */ + g_signal_connect (data->add_button, "clicked", + G_CALLBACK (button_clicked_cb), data); + + g_signal_connect (data->remove_button, "clicked", + G_CALLBACK (button_clicked_cb), data); + + /* Tree view of available encodings */ + /* Column 1 */ + cell_renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("_Description"), + cell_renderer, + "text", COLUMN_NAME, + NULL); + gtk_tree_view_append_column (data->available_tree_view, column); + gtk_tree_view_column_set_sort_column_id (column, COLUMN_NAME); + + /* Column 2 */ + cell_renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("_Encoding"), + cell_renderer, + "text", COLUMN_CHARSET, + NULL); + gtk_tree_view_append_column (data->available_tree_view, column); + gtk_tree_view_column_set_sort_column_id (column, COLUMN_CHARSET); + + data->available_selection = gtk_tree_view_get_selection (data->available_tree_view); + gtk_tree_selection_set_mode (data->available_selection, GTK_SELECTION_BROWSE); + + g_signal_connect (data->available_selection, "changed", + G_CALLBACK (selection_changed_cb), data); + + /* Tree view of selected encodings */ + /* Column 1 */ + cell_renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("_Description"), + cell_renderer, + "text", COLUMN_NAME, + NULL); + gtk_tree_view_append_column (data->active_tree_view, column); + gtk_tree_view_column_set_sort_column_id (column, COLUMN_NAME); + + /* Column 2 */ + cell_renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("_Encoding"), + cell_renderer, + "text", COLUMN_CHARSET, + NULL); + gtk_tree_view_append_column (data->active_tree_view, column); + gtk_tree_view_column_set_sort_column_id (column, COLUMN_CHARSET); + + /* Add the data */ + + data->active_selection = gtk_tree_view_get_selection (data->active_tree_view); + gtk_tree_selection_set_mode (data->active_selection, GTK_SELECTION_BROWSE); + + g_signal_connect (data->active_selection, "changed", + G_CALLBACK (selection_changed_cb), data); + + data->base_store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, TERMINAL_TYPE_ENCODING); + + app = terminal_app_get (); + encodings_list_changed_cb (app, data); + g_signal_connect (app, "encoding-list-changed", + G_CALLBACK (encodings_list_changed_cb), data); + + /* Now turn on sorting */ + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (data->base_store), + COLUMN_NAME, + GTK_SORT_ASCENDING); + + model = encodings_create_treemodel (data->base_store, FALSE); + gtk_tree_view_set_model (data->available_tree_view, model); + g_object_unref (model); + + model = encodings_create_treemodel (data->base_store, TRUE); + gtk_tree_view_set_model (data->active_tree_view, model); + g_object_unref (model); + + g_object_unref (data->base_store); + + gtk_window_present (GTK_WINDOW (data->dialog)); + + encoding_dialog = data->dialog; + g_signal_connect (data->dialog, "destroy", + G_CALLBACK (gtk_widget_destroyed), &encoding_dialog); +} + +GHashTable * +terminal_encodings_get_builtins (void) +{ + GHashTable *encodings_hashtable; + guint i; + TerminalEncoding *encoding; + + encodings_hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, + (GDestroyNotify) terminal_encoding_unref); + + + /* Placeholder entry for the current locale's charset */ + encoding = terminal_encoding_new ("current", + _("Current Locale"), + FALSE, + TRUE); + g_hash_table_insert (encodings_hashtable, + (gpointer) terminal_encoding_get_id (encoding), + encoding); + + for (i = 0; i < G_N_ELEMENTS (encodings); ++i) + { + encoding = terminal_encoding_new (encodings[i].charset, + _(encodings[i].name), + FALSE, + FALSE); + g_hash_table_insert (encodings_hashtable, + (gpointer) terminal_encoding_get_id (encoding), + encoding); + } + + return encodings_hashtable; +} |