/* $Id: xmleditor.c,v 1.18 2011/04/16 21:33:50 khorben Exp $ */ static char const _copyright[] = "Copyright (c) 2010 Pierre Pronchery "; /* This file is part of DeforaOS Desktop XMLEditor */ static char const _license[] = "This program is free software; you can redistribute it and/or modify\n" "it under the terms of the GNU General Public License as published by the\n" "Free Software Foundation, version 3 of the License.\n" "\n" "This program is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n" "\n" "You should have received a copy of the GNU General Public License\n" "along with this program. If not, see .\n"; #include #include #include #include #include #include #include #include #include "callbacks.h" #include "xmleditor.h" #include "../config.h" #define _(string) gettext(string) #define N_(string) (string) /* XMLEditor */ /* private */ /* types */ typedef enum _XMLEditorColumn { XEC_TAGNAME = 0, XEC_ATTRIBUTE_NAME, XEC_ATTRIBUTE_VALUE, XEC_DATA, XEC_ENTITY } XMLEditorColumn; #define XEC_LAST XEC_ENTITY #define XEC_COUNT (XEC_LAST + 1) struct _XMLEditor { XML * xml; gboolean modified; /* widgets */ GtkWidget * window; GtkTreeStore * store; GtkWidget * view; GtkWidget * statusbar; /* about */ GtkWidget * ab_window; }; /* variables */ static char const * _authors[] = { "Pierre Pronchery ", NULL }; #ifdef EMBEDDED static DesktopAccel _xmleditor_accel[] = { { G_CALLBACK(on_close), GDK_CONTROL_MASK, GDK_KEY_W }, { G_CALLBACK(on_new), GDK_CONTROL_MASK, GDK_KEY_N }, { G_CALLBACK(on_open), GDK_CONTROL_MASK, GDK_KEY_O }, { G_CALLBACK(on_preferences), GDK_CONTROL_MASK, GDK_KEY_P }, { G_CALLBACK(on_save), GDK_CONTROL_MASK, GDK_KEY_S }, { G_CALLBACK(on_save_as), GDK_CONTROL_MASK | GDK_SHIFT_MASK, GDK_KEY_S }, { NULL, 0, 0 } }; #endif #ifndef EMBEDDED static DesktopMenu _xmleditor_menu_file[] = { { N_("_New"), G_CALLBACK(on_file_new), GTK_STOCK_NEW, GDK_CONTROL_MASK, GDK_KEY_N }, { N_("_Open"), G_CALLBACK(on_file_open), GTK_STOCK_OPEN, GDK_CONTROL_MASK, GDK_KEY_O }, { "", NULL, NULL, 0, 0 }, { N_("_Save"), G_CALLBACK(on_file_save), GTK_STOCK_SAVE, GDK_CONTROL_MASK, GDK_KEY_S }, { N_("_Save as..."), G_CALLBACK(on_file_save_as), GTK_STOCK_SAVE_AS, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GDK_KEY_S }, { "", NULL, NULL, 0, 0 }, { N_("_Close"), G_CALLBACK(on_file_close), GTK_STOCK_CLOSE, GDK_CONTROL_MASK, GDK_KEY_W }, { NULL, NULL, NULL, 0, 0 } }; static DesktopMenu _xmleditor_menu_edit[] = { { N_("_Preferences"), G_CALLBACK(on_edit_preferences), GTK_STOCK_PREFERENCES, GDK_CONTROL_MASK, GDK_KEY_P }, { NULL, NULL, NULL, 0, 0 } }; static DesktopMenu _xmleditor_menu_view[] = { { N_("_Expand all"), G_CALLBACK(on_view_expand_all), NULL, 0, GDK_KEY_asterisk }, { N_("_Collapse all"), G_CALLBACK(on_view_collapse_all), NULL, 0, GDK_KEY_slash }, { NULL, NULL, NULL, 0, 0 } }; static DesktopMenu _xmleditor_menu_help[] = { { N_("_About"), G_CALLBACK(on_help_about), #if GTK_CHECK_VERSION(2, 6, 0) GTK_STOCK_ABOUT, 0, 0 }, #else NULL, 0, 0 }, #endif { NULL, NULL, NULL, 0, 0 } }; static DesktopMenubar _xmleditor_menubar[] = { { N_("_File"), _xmleditor_menu_file }, { N_("_Edit"), _xmleditor_menu_edit }, { N_("_View"), _xmleditor_menu_view }, { N_("_Help"), _xmleditor_menu_help }, { NULL, NULL } }; #endif static DesktopToolbar _xmleditor_toolbar[] = { { N_("New"), G_CALLBACK(on_new), GTK_STOCK_NEW, 0, 0, NULL }, { N_("Open"), G_CALLBACK(on_open), GTK_STOCK_OPEN, 0, 0, NULL }, { "", NULL, NULL, 0, 0, NULL }, { N_("Save"), G_CALLBACK(on_save), GTK_STOCK_SAVE, 0, 0, NULL }, { N_("Save as"), G_CALLBACK(on_save_as), GTK_STOCK_SAVE_AS, 0, 0, NULL }, #ifdef EMBEDDED { "", NULL, NULL, 0, 0, NULL }, { N_("Preferences"), G_CALLBACK(on_preferences), GTK_STOCK_PREFERENCES, 0, 0, NULL }, #endif { NULL, NULL, NULL, 0, 0, NULL } }; /* public */ /* functions */ /* xmleditor_new */ static void _new_set_title(XMLEditor * xmleditor); XMLEditor * xmleditor_new(void) { XMLEditor * xmleditor; GtkAccelGroup * group; GtkSettings * settings; GtkWidget * vbox; GtkWidget * widget; GtkCellRenderer * renderer; GtkTreeViewColumn * column; if((xmleditor = malloc(sizeof(*xmleditor))) == NULL) return NULL; settings = gtk_settings_get_default(); xmleditor->xml = NULL; xmleditor->modified = FALSE; /* widgets */ group = gtk_accel_group_new(); xmleditor->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_add_accel_group(GTK_WINDOW(xmleditor->window), group); gtk_window_set_default_size(GTK_WINDOW(xmleditor->window), 600, 400); _new_set_title(xmleditor); #if GTK_CHECK_VERSION(2, 6, 0) gtk_window_set_icon_name(GTK_WINDOW(xmleditor->window), "text-editor"); #endif g_signal_connect_swapped(G_OBJECT(xmleditor->window), "delete-event", G_CALLBACK(on_closex), xmleditor); vbox = gtk_vbox_new(FALSE, 0); /* menubar */ #ifndef EMBEDDED widget = desktop_menubar_create(_xmleditor_menubar, xmleditor, group); gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 0); #else desktop_accel_create(_xmleditor_accel, xmleditor, group); #endif /* toolbar */ widget = desktop_toolbar_create(_xmleditor_toolbar, xmleditor, group); gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 0); /* view */ widget = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); xmleditor->store = gtk_tree_store_new(XEC_COUNT, G_TYPE_STRING, /* XEC_TAGNAME */ G_TYPE_STRING, /* XEC_ATTRIBUTE_NAME */ G_TYPE_STRING, /* XEC_ATTRIBUTE_VALUE */ G_TYPE_STRING, /* XEC_DATA */ G_TYPE_STRING); /* XEC_ENTITY */ xmleditor->view = gtk_tree_view_new_with_model(GTK_TREE_MODEL( xmleditor->store)); #if 0 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(xmleditor->view), FALSE); #endif gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(xmleditor->view), TRUE); renderer = gtk_cell_renderer_text_new(); g_object_set(G_OBJECT(renderer), "editable", TRUE, NULL); g_signal_connect(G_OBJECT(renderer), "edited", G_CALLBACK( on_tag_name_edited), xmleditor); column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer, "text", XEC_TAGNAME, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(xmleditor->view), column); renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes(_("Entity"), renderer, "text", XEC_ENTITY, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(xmleditor->view), column); renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes(_("Data"), renderer, "text", XEC_DATA, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(xmleditor->view), column); gtk_container_add(GTK_CONTAINER(widget), xmleditor->view); gtk_box_pack_start(GTK_BOX(vbox), widget, TRUE, TRUE, 0); /* statusbar */ xmleditor->statusbar = gtk_statusbar_new(); gtk_box_pack_start(GTK_BOX(vbox), xmleditor->statusbar, FALSE, FALSE, 0); /* about */ xmleditor->ab_window = NULL; gtk_container_add(GTK_CONTAINER(xmleditor->window), vbox); gtk_window_set_focus(GTK_WINDOW(xmleditor->window), xmleditor->view); gtk_widget_show_all(xmleditor->window); return xmleditor; } static void _new_set_title(XMLEditor * xmleditor) { char buf[256]; snprintf(buf, sizeof(buf), "%s%s", _("XML editor - "), (xmleditor->xml == NULL) ? _("(Untitled)") : xml_get_filename(xmleditor->xml)); gtk_window_set_title(GTK_WINDOW(xmleditor->window), buf); } /* xmleditor_delete */ void xmleditor_delete(XMLEditor * xmleditor) { #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif if(xmleditor->xml != NULL) xml_delete(xmleditor->xml); free(xmleditor); } /* useful */ /* xmleditor_about */ static gboolean _about_on_closex(GtkWidget * widget); void xmleditor_about(XMLEditor * xmleditor) { if(xmleditor->ab_window != NULL) { gtk_widget_show(xmleditor->ab_window); return; } xmleditor->ab_window = desktop_about_dialog_new(); gtk_window_set_transient_for(GTK_WINDOW(xmleditor->ab_window), GTK_WINDOW(xmleditor->window)); g_signal_connect(G_OBJECT(xmleditor->ab_window), "delete-event", G_CALLBACK(_about_on_closex), NULL); desktop_about_dialog_set_authors(xmleditor->ab_window, _authors); desktop_about_dialog_set_copyright(xmleditor->ab_window, _copyright); desktop_about_dialog_set_license(xmleditor->ab_window, _license); desktop_about_dialog_set_logo_icon_name(xmleditor->ab_window, "text-editor"); desktop_about_dialog_set_name(xmleditor->ab_window, PACKAGE); desktop_about_dialog_set_version(xmleditor->ab_window, VERSION); gtk_widget_show(xmleditor->ab_window); } static gboolean _about_on_closex(GtkWidget * widget) { gtk_widget_hide(widget); return TRUE; } /* xmleditor_error */ int xmleditor_error(XMLEditor * xmleditor, char const * message, int ret) { GtkWidget * dialog; dialog = gtk_message_dialog_new(GTK_WINDOW(xmleditor->window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", #if GTK_CHECK_VERSION(2, 6, 0) _("Error")); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", #endif message); gtk_window_set_title(GTK_WINDOW(dialog), _("Error")); g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK( gtk_widget_destroy), NULL); gtk_widget_show(dialog); return ret; } /* xmleditor_close */ gboolean xmleditor_close(XMLEditor * xmleditor) { GtkWidget * dialog; int res; #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif if(xmleditor->modified == FALSE) { gtk_main_quit(); return FALSE; } dialog = gtk_message_dialog_new(GTK_WINDOW(xmleditor->window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, "%s", #if GTK_CHECK_VERSION(2, 6, 0) _("Warning")); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", #endif _("There are unsaved changes.\n" "Discard or save them?")); gtk_dialog_add_buttons(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_DISCARD, GTK_RESPONSE_REJECT, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); gtk_window_set_title(GTK_WINDOW(dialog), _("Warning")); res = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); if(res == GTK_RESPONSE_CANCEL) return TRUE; else if(res == GTK_RESPONSE_ACCEPT && xmleditor_save(xmleditor) != TRUE) return TRUE; gtk_main_quit(); return FALSE; } /* xmleditor_collapse_all */ void xmleditor_collapse_all(XMLEditor * xmleditor) { gtk_tree_view_collapse_all(GTK_TREE_VIEW(xmleditor->view)); } /* xmleditor_expand_all */ void xmleditor_expand_all(XMLEditor * xmleditor) { gtk_tree_view_expand_all(GTK_TREE_VIEW(xmleditor->view)); } /* xmleditor_open */ static void _open_document_node(XMLEditor * xmleditor, XMLNode * node, GtkTreeIter * parent); void xmleditor_open(XMLEditor * xmleditor, char const * filename) { XMLDocument * doc; /* FIXME handle errors */ gtk_tree_store_clear(xmleditor->store); if(filename == NULL) return; if(xmleditor->xml != NULL) xml_delete(xmleditor->xml); if((xmleditor->xml = xml_new(NULL, filename)) == NULL) { xmleditor_error(xmleditor, error_get(), 0); return; } if((doc = xml_get_document(xmleditor->xml)) != NULL) _open_document_node(xmleditor, doc->root, NULL); _new_set_title(xmleditor); /* XXX make it a generic private function */ } static void _open_document_node(XMLEditor * xmleditor, XMLNode * node, GtkTreeIter * parent) { size_t i; GtkTreeIter iter; GtkTreeIter child; if(node == NULL) return; switch(node->type) { case XML_NODE_TYPE_DATA: gtk_tree_store_append(xmleditor->store, &iter, parent); gtk_tree_store_set(xmleditor->store, &iter, XEC_DATA, node->data.buffer, -1); break; case XML_NODE_TYPE_ENTITY: gtk_tree_store_append(xmleditor->store, &iter, parent); gtk_tree_store_set(xmleditor->store, &iter, XEC_ENTITY, node->entity.name, -1); break; case XML_NODE_TYPE_TAG: gtk_tree_store_append(xmleditor->store, &iter, parent); gtk_tree_store_set(xmleditor->store, &iter, XEC_TAGNAME, node->tag.name, -1); for(i = 0; i < node->tag.attributes_cnt; i++) { gtk_tree_store_append(xmleditor->store, &child, &iter); gtk_tree_store_set(xmleditor->store, &child, XEC_ATTRIBUTE_NAME, node->tag.attributes[i]->name, XEC_ATTRIBUTE_VALUE, node->tag.attributes[i]->value, -1); } for(i = 0; i < node->tag.childs_cnt; i++) _open_document_node(xmleditor, node->tag.childs[i], &iter); break; } } /* xmleditor_open_dialog */ void xmleditor_open_dialog(XMLEditor * xmleditor) { GtkWidget * dialog; char * filename = NULL; dialog = gtk_file_chooser_dialog_new(_("Open file..."), GTK_WINDOW(xmleditor->window), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER( dialog)); gtk_widget_destroy(dialog); if(filename == NULL) return; xmleditor_open(xmleditor, filename); g_free(filename); } /* xmleditor_save */ static void _save_do(XMLEditor * xmleditor, FILE * fp, GtkTreeIter * parent); gboolean xmleditor_save(XMLEditor * xmleditor) { char const * filename; FILE * fp; char * buf; GtkTreeIter iter; if(xmleditor->xml == NULL) return xmleditor_save_as_dialog(xmleditor); filename = xml_get_filename(xmleditor->xml); if((fp = fopen(filename, "w")) == NULL) { buf = g_strdup_printf("%s: %s", filename, strerror(errno)); xmleditor_error(xmleditor, buf, 1); g_free(buf); return FALSE; } if(gtk_tree_model_iter_children(GTK_TREE_MODEL(xmleditor->store), &iter, NULL) == TRUE) _save_do(xmleditor, fp, &iter); fclose(fp); return TRUE; } static void _save_do(XMLEditor * xmleditor, FILE * fp, GtkTreeIter * parent) { GtkTreeModel * model = GTK_TREE_MODEL(xmleditor->store); GtkTreeIter child; gchar * name; gboolean valid; gchar * aname; gchar * avalue; gchar * tag; gchar * data; gchar * entity; gtk_tree_model_get(model, parent, XEC_TAGNAME, &name, -1); fprintf(fp, "<%s", name); /* attributes */ gtk_tree_model_iter_children(model, &child, parent); for(valid = gtk_tree_model_iter_children(model, &child, parent); valid == TRUE; valid = gtk_tree_model_iter_next(model, &child)) { gtk_tree_model_get(model, &child, XEC_ATTRIBUTE_NAME, &aname, XEC_ATTRIBUTE_VALUE, &avalue, -1); if(aname != NULL) fprintf(fp, " %s=\"%s\"", aname, (avalue != NULL) ? avalue : aname); g_free(aname); g_free(avalue); } fprintf(fp, ">"); /* content */ gtk_tree_model_iter_children(model, &child, parent); for(valid = gtk_tree_model_iter_children(model, &child, parent); valid == TRUE; valid = gtk_tree_model_iter_next(model, &child)) { gtk_tree_model_get(model, &child, XEC_TAGNAME, &tag, XEC_DATA, &data, XEC_ENTITY, &entity, -1); if(tag != NULL) _save_do(xmleditor, fp, &child); else if(data != NULL) fprintf(fp, "%s", data); else if(entity != NULL) fprintf(fp, "&%s;", entity); g_free(tag); g_free(data); g_free(entity); } fprintf(fp, "", name); g_free(name); } /* xmleditor_save_as */ gboolean xmleditor_save_as(XMLEditor * xmleditor, char const * filename) { struct stat st; GtkWidget * dialog; int res; if(stat(filename, &st) == 0) { dialog = gtk_message_dialog_new(GTK_WINDOW(xmleditor->window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_YES_NO, "%s", #if GTK_CHECK_VERSION(2, 6, 0) _("Warning")); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG( dialog), "%s", #endif _("This file already exists. Overwrite?")); gtk_window_set_title(GTK_WINDOW(dialog), _("Warning")); res = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); if(res == GTK_RESPONSE_NO) return FALSE; } /* FIXME implement */ return TRUE; } /* xmleditor_save_as_dialog */ gboolean xmleditor_save_as_dialog(XMLEditor * xmleditor) { GtkWidget * dialog; char * filename = NULL; gboolean ret; dialog = gtk_file_chooser_dialog_new(_("Save as..."), GTK_WINDOW(xmleditor->window), GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER( dialog)); gtk_widget_destroy(dialog); if(filename == NULL) return FALSE; ret = xmleditor_save_as(xmleditor, filename); g_free(filename); return ret; } /* xmleditor_tag_set_name */ void xmleditor_tag_set_name(XMLEditor * xmleditor, GtkTreePath * treepath, char const * name) { GtkTreeModel * model = GTK_TREE_MODEL(xmleditor->store); GtkTreeIter iter; xmleditor->modified = TRUE; gtk_tree_model_get_iter(model, &iter, treepath); gtk_tree_store_set(xmleditor->store, &iter, XEC_TAGNAME, name, -1); }