/* * htmlchars.c - this file is part of Geany, a fast and lightweight IDE * * Copyright 2006-2008 Enrico Tröger * Copyright 2007-2008 Nick Treleaven * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * $Id$ */ /* HTML Characters plugin (Inserts HTML character entities like '&') */ #include "geany.h" #include "support.h" #include "plugindata.h" #include "document.h" #include "keybindings.h" #include "ui_utils.h" #include "pluginmacros.h" PluginFields *plugin_fields; GeanyData *geany_data; GeanyFunctions *geany_functions; PLUGIN_VERSION_CHECK(64) PLUGIN_SET_INFO(_("HTML Characters"), _("Inserts HTML character entities like '&'."), VERSION, _("The Geany developer team")) /* Keybinding(s) */ enum { KB_INSERT_HTML_CHARS, KB_COUNT }; PLUGIN_KEY_GROUP(html_chars, KB_COUNT) enum { COLUMN_CHARACTER, COLUMN_HTML_NAME, N_COLUMNS }; static GtkWidget *sc_dialog = NULL; static GtkTreeStore *sc_store = NULL; static GtkTreeView *sc_tree = NULL; static void sc_on_tools_show_dialog_insert_special_chars_response (GtkDialog *dialog, gint response, gpointer user_data); static void sc_on_tree_row_activated (GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col, gpointer user_data); static void sc_fill_store(GtkTreeStore *store); static gboolean sc_insert(GtkTreeModel *model, GtkTreeIter *iter); static void tools_show_dialog_insert_special_chars(void) { if (sc_dialog == NULL) { gint height; GtkCellRenderer *renderer; GtkTreeViewColumn *column; GtkWidget *swin, *vbox, *label; sc_dialog = gtk_dialog_new_with_buttons( _("Special Characters"), GTK_WINDOW(main_widgets->window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, _("_Insert"), GTK_RESPONSE_OK, NULL); vbox = p_ui->dialog_vbox_new(GTK_DIALOG(sc_dialog)); gtk_box_set_spacing(GTK_BOX(vbox), 6); gtk_widget_set_name(sc_dialog, "GeanyDialog"); height = GEANY_WINDOW_MINIMAL_HEIGHT; gtk_window_set_default_size(GTK_WINDOW(sc_dialog), height * 8 / 10, height); gtk_dialog_set_default_response(GTK_DIALOG(sc_dialog), GTK_RESPONSE_CANCEL); label = gtk_label_new(_("Choose a special character from the list below and double click on it or use the button to insert it at the current cursor position.")); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); sc_tree = GTK_TREE_VIEW(gtk_tree_view_new()); sc_store = gtk_tree_store_new(N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING); gtk_tree_view_set_model(GTK_TREE_VIEW(sc_tree), GTK_TREE_MODEL(sc_store)); renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes( _("Character"), renderer, "text", COLUMN_CHARACTER, NULL); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_append_column(GTK_TREE_VIEW(sc_tree), column); renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes( _("HTML (name)"), renderer, "text", COLUMN_HTML_NAME, NULL); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_append_column(GTK_TREE_VIEW(sc_tree), column); swin = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW(swin), GTK_WIDGET(sc_tree)); gtk_box_pack_start(GTK_BOX(vbox), swin, TRUE, TRUE, 0); g_signal_connect((gpointer) sc_tree, "row-activated", G_CALLBACK(sc_on_tree_row_activated), NULL); g_signal_connect((gpointer) sc_dialog, "response", G_CALLBACK(sc_on_tools_show_dialog_insert_special_chars_response), NULL); g_signal_connect((gpointer) sc_dialog, "delete_event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); sc_fill_store(sc_store); /*gtk_tree_view_expand_all(special_characters_tree);*/ gtk_tree_view_set_search_column(sc_tree, COLUMN_HTML_NAME); } gtk_widget_show_all(sc_dialog); } /* fill the tree model with data ** TODO move this in a file and make it extendable for more data types */ static void sc_fill_store(GtkTreeStore *store) { GtkTreeIter iter; GtkTreeIter *parent_iter = NULL; guint i; gchar *chars[][2] = { { _("HTML characters"), NULL }, { "\"", """ }, { "&", "&" }, { "<", "<" }, { ">", ">" }, { _("ISO 8859-1 characters"), NULL }, { " ", " " }, { "¡", "¡" }, { "¢", "¢" }, { "£", "£" }, { "¤", "¤" }, { "¥", "¥" }, { "¦", "¦" }, { "§", "§" }, { "¨", "¨" }, { "©", "©" }, { "®", "®" }, { "«", "«" }, { "»", "»" }, { "¬", "¬" }, { " ", "­" }, { "¯", "¯" }, { "°", "°" }, { "±", "±" }, { "¹", "¹" }, { "²", "²" }, { "³", "³" }, { "¼", "¼" }, { "½", "½" }, { "¾", "¾" }, { "×", "×" }, { "÷", "÷" }, { "´", "´" }, { "µ", "µ" }, { "¶", "¶" }, { "·", "·" }, { "¸", "¸" }, { "ª", "ª" }, { "º", "º" }, { "¿", "¿" }, { "À", "À" }, { "Á", "Á" }, { "Â", "Â" }, { "Ã", "Ã" }, { "Ä", "Ä" }, { "Å", "Å" }, { "Æ", "Æ" }, { "Ç", "Ç" }, { "È", "È" }, { "É", "É" }, { "Ê", "Ê" }, { "Ë", "Ë" }, { "Ì", "Ì" }, { "Í", "Í" }, { "Î", "Î" }, { "Ï", "Ï" }, { "Ð", "Ð" }, { "Ñ", "Ñ" }, { "Ò", "Ò" }, { "Ó", "Ó" }, { "Ô", "Ô" }, { "Õ", "Õ" }, { "Ö", "Ö" }, { "Ø", "Ø" }, { "Ù", "Ù" }, { "Ú", "Ú" }, { "Û", "Û" }, { "Ü", "Ü" }, { "Ý", "Ý" }, { "Þ", "Þ" }, { "ß", "ß" }, { "à", "à" }, { "á", "á" }, { "â", "â" }, { "ã", "ã" }, { "ä", "ä" }, { "å", "å" }, { "æ", "æ" }, { "ç", "ç" }, { "è", "è" }, { "é", "é" }, { "ê", "ê" }, { "ë", "ë" }, { "ì", "ì" }, { "í", "í" }, { "î", "î" }, { "ï", "ï" }, { "ð", "ð" }, { "ñ", "ñ" }, { "ò", "ò" }, { "ó", "ó" }, { "ô", "ô" }, { "õ", "õ" }, { "ö", "ö" }, { "ø", "ø" }, { "ù", "ù" }, { "ú", "ú" }, { "û", "û" }, { "ü", "ü" }, { "ý", "ý" }, { "þ", "þ" }, { "ÿ", "ÿ" }, { _("Greek characters"), NULL }, { "Α", "Α" }, { "α", "α" }, { "Β", "Β" }, { "β", "β" }, { "Γ", "Γ" }, { "γ", "γ" }, { "Δ", "Δ" }, { "δ", "Δ" }, { "δ", "δ" }, { "Ε", "Ε" }, { "ε", "ε" }, { "Ζ", "Ζ" }, { "ζ", "ζ" }, { "Η", "Η" }, { "η", "η" }, { "Θ", "Θ" }, { "θ", "θ" }, { "Ι", "Ι" }, { "ι", "ι" }, { "Κ", "Κ" }, { "κ", "κ" }, { "Λ", "Λ" }, { "λ", "λ" }, { "Μ", "Μ" }, { "μ", "μ" }, { "Ν", "Ν" }, { "ν", "ν" }, { "Ξ", "Ξ" }, { "ξ", "ξ" }, { "Ο", "Ο" }, { "ο", "ο" }, { "Π", "Π" }, { "π", "π" }, { "Ρ", "Ρ" }, { "ρ", "ρ" }, { "Σ", "Σ" }, { "ς", "ς" }, { "σ", "σ" }, { "Τ", "Τ" }, { "τ", "τ" }, { "Υ", "Υ" }, { "υ", "υ" }, { "Φ", "Φ" }, { "φ", "φ" }, { "Χ", "Χ" }, { "χ", "χ" }, { "Ψ", "Ψ" }, { "ψ", "ψ" }, { "Ω", "Ω" }, { "ω", "ω" }, { "ϑ", "ϑ" }, { "ϒ", "ϒ" }, { "ϖ", "ϖ" }, { _("Mathematical characters"), NULL }, { "∀", "∀" }, { "∂", "∂" }, { "∃", "∃" }, { "∅", "∅" }, { "∇", "∇" }, { "∈", "∈" }, { "∉", "∉" }, { "∋", "∋" }, { "∏", "∏" }, { "∑", "∑" }, { "−", "−" }, { "∗", "∗" }, { "√", "√" }, { "∝", "∝" }, { "∞", "∞" }, { "∠", "∠" }, { "∧", "∧" }, { "∨", "∨" }, { "∩", "∩" }, { "∪", "∪" }, { "∫", "∫" }, { "∴", "∴" }, { "∼", "∼" }, { "≅", "≅" }, { "≈", "≈" }, { "≠", "≠" }, { "≡", "≡" }, { "≤", "≤" }, { "≥", "≥" }, { "⊂", "⊂" }, { "⊃", "⊃" }, { "⊄", "⊄" }, { "⊆", "⊆" }, { "⊇", "⊇" }, { "⊕", "⊕" }, { "⊗", "⊗" }, { "⊥", "⊥" }, { "⋅", "⋅" }, { "◊", "◊" }, { _("Technical characters"), NULL }, { "⌈", "⌈" }, { "⌉", "⌉" }, { "⌊", "⌊" }, { "⌋", "⌋" }, { "〈", "⟨" }, { "〉", "⟩" }, { _("Arrow characters"), NULL }, { "←", "←" }, { "↑", "↑" }, { "→", "→" }, { "↓", "↓" }, { "↔", "↔" }, { "↵", "↵" }, { "⇐", "⇐" }, { "⇑", "⇑" }, { "⇒", "⇒" }, { "⇓", "⇓" }, { "⇔", "⇔" }, { _("Punctuation characters"), NULL }, { "–", "–" }, { "—", "—" }, { "‘", "‘" }, { "’", "’" }, { "‚", "‚" }, { "“", "“" }, { "”", "”" }, { "„", "„" }, { "†", "†" }, { "‡", "‡" }, { "…", "…" }, { "‰", "‰" }, { "‹", "‹" }, { "›", "›" }, { _("Miscellaneous characters"), NULL }, { "•", "•" }, { "′", "′" }, { "″", "″" }, { "‾", "‾" }, { "⁄", "⁄" }, { "℘", "℘" }, { "ℑ", "ℑ" }, { "ℜ", "ℜ" }, { "™", "™" }, { "€", "€" }, { "ℵ", "ℵ" }, { "♠", "♠" }, { "♣", "♣" }, { "♥", "♥" }, { "♦", "♦" }, { "Œ", "Œ" }, { "œ", "œ" }, { "Š", "Š" }, { "š", "š" }, { "Ÿ", "Ÿ" }, { "ƒ", "ƒ" }, }; for (i = 0; i < G_N_ELEMENTS(chars); i++) { if (chars[i][1] == NULL) { /* add a category */ gtk_tree_store_append(store, &iter, NULL); gtk_tree_store_set(store, &iter, COLUMN_CHARACTER, chars[i][0], -1); if (parent_iter != NULL) gtk_tree_iter_free(parent_iter); parent_iter = gtk_tree_iter_copy(&iter); } else { /* add child to parent_iter */ gtk_tree_store_append(store, &iter, parent_iter); gtk_tree_store_set(store, &iter, COLUMN_CHARACTER, chars[i][0], COLUMN_HTML_NAME, chars[i][1], -1); } } } /* just inserts the HTML_NAME coloumn of the selected row at current position * returns only TRUE if a valid selection(i.e. no category) could be found */ static gboolean sc_insert(GtkTreeModel *model, GtkTreeIter *iter) { gint idx = p_document->get_cur_idx(); gboolean result = FALSE; if (DOC_IDX_VALID(idx)) { gchar *str; gint pos = p_sci->get_current_position(documents[idx]->sci); gtk_tree_model_get(model, iter, COLUMN_HTML_NAME, &str, -1); if (str && *str) { p_sci->insert_text(documents[idx]->sci, pos, str); g_free(str); result = TRUE; } } return result; } static void sc_on_tools_show_dialog_insert_special_chars_response(GtkDialog *dialog, gint response, gpointer user_data) { if (response == GTK_RESPONSE_OK) { GtkTreeSelection *selection; GtkTreeModel *model; GtkTreeIter iter; selection = gtk_tree_view_get_selection(sc_tree); if (gtk_tree_selection_get_selected(selection, &model, &iter)) { /* only hide dialog if selection was not a category */ if (sc_insert(model, &iter)) gtk_widget_hide(GTK_WIDGET(dialog)); } } else gtk_widget_hide(GTK_WIDGET(dialog)); } static void sc_on_tree_row_activated(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col, gpointer user_data) { GtkTreeIter iter; GtkTreeModel *model = GTK_TREE_MODEL(sc_store); if (gtk_tree_model_get_iter(model, &iter, path)) { /* only hide dialog if selection was not a category */ if (sc_insert(model, &iter)) gtk_widget_hide(sc_dialog); else { /* double click on a category to toggle the expand or collapse it */ if (gtk_tree_view_row_expanded(sc_tree, path)) gtk_tree_view_collapse_row(sc_tree, path); else gtk_tree_view_expand_row(sc_tree, path, FALSE); } } } /* Callback when the menu item is clicked */ static void item_activate(GtkMenuItem *menuitem, gpointer gdata) { /* refuse opening the dialog if we don't have an active tab */ gint idx = p_document->get_cur_idx(); if (idx == -1 || ! documents[idx]->is_valid) return; tools_show_dialog_insert_special_chars(); } static void kb_activate(G_GNUC_UNUSED guint key_id) { item_activate(NULL, NULL); } /* Called by Geany to initialize the plugin */ void plugin_init(GeanyData *data) { GtkWidget *demo_item; const gchar *menu_text = _("_Insert Special HTML Characters"); gchar *kb_label = _("Insert Special HTML Characters"); /* Add an item to the Tools menu */ demo_item = gtk_menu_item_new_with_mnemonic(menu_text); gtk_widget_show(demo_item); gtk_container_add(GTK_CONTAINER(main_widgets->tools_menu), demo_item); g_signal_connect(G_OBJECT(demo_item), "activate", G_CALLBACK(item_activate), NULL); /* disable menu_item when there are no documents open */ plugin_fields->menu_item = demo_item; plugin_fields->flags = PLUGIN_IS_DOCUMENT_SENSITIVE; /* setup keybindings */ p_keybindings->set_item(plugin_key_group, KB_INSERT_HTML_CHARS, kb_activate, 0, 0, "insert_html_chars", kb_label, demo_item); } /* Destroy widgets */ void plugin_cleanup(void) { gtk_widget_destroy(plugin_fields->menu_item); if (sc_dialog != NULL) gtk_widget_destroy(sc_dialog); }