[gq-commit] gq/src state.c,NONE,1.1 state.h,NONE,1.1
Status: Beta
Brought to you by:
sur5r
From: <sta...@us...> - 2003-10-05 22:15:46
|
Update of /cvsroot/gqclient/gq/src In directory sc8-pr-cvs1:/tmp/cvs-serv6431 Added Files: state.c state.h Log Message: * An experimental framework for GUI state persistency --- NEW FILE: state.c --- /* GQ -- a GTK-based LDAP client is Copyright (C) 1998-2003 Bert Vermeulen Copyright (C) 2002-2003 Peter Stamfest This file is Copyright (c) 2003 by Peter Stamfest <pe...@st...> This program is released under the Gnu General Public License with the additional exemption that compiling, linking, and/or using OpenSSL is allowed. 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* A hash mapping state-names to hashes containing mappings of settings to lists of values. This is not elegant, but just the data structure required. */ #include "config.h" #include <stdlib.h> #include <assert.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <gtk/gtk.h> #include "xmlparse.h" #include "configfile.h" #include "util.h" #include "errorchain.h" #include "xmlutil.h" #include "i18n.h" enum state_value_type { SV_int = 1, SV_char = 2, SV_list = 3, }; static struct tokenlist token_value_type_names[] = { { SV_int, "integer" }, { SV_char, "string" }, { SV_list, "list" }, { 0, "" } }; /* Oh-uh, It looks like a registry! I would't wonder if this file would always end up having 666 lines... */ struct state_entity { GHashTable *values; GHashTable *entities; /* sub entities */ }; struct state_value { enum state_value_type type; union { int *int_val; char *char_val; GList *list_val; } val; void (*free_list_element)(void *); }; static GHashTable *entities = NULL; struct state_value *new_state_value(enum state_value_type t) { struct state_value *v = g_malloc0(sizeof(struct state_value)); v->type = t; switch (t) { case SV_int: v->val.int_val = g_malloc0(sizeof(int)); break; case SV_char: v->val.char_val = g_strdup(""); break; case SV_list: v->val.list_val = NULL; break; default: abort(); } return v; } void free_state_value(struct state_value *v) { assert(v); switch (v->type) { case SV_int: g_free(v->val.int_val); break; case SV_char: if (v->val.char_val) g_free(v->val.char_val); break; case SV_list: if (v->val.list_val && v->free_list_element) { g_list_foreach(v->val.list_val, (GFunc) v->free_list_element, NULL); } if (v->val.list_val) { g_list_free(v->val.list_val); } break; default: abort(); } g_free(v); } struct state_entity *new_state_entity() { struct state_entity *e = g_malloc0(sizeof(struct state_entity)); e->values = g_hash_table_new(g_str_hash, g_str_equal); e->entities = g_hash_table_new(g_str_hash, g_str_equal); return e; } static gboolean ghr_free_value(char *key, struct state_value *v, gpointer ud) { g_free_if(key); free_state_value(v); return TRUE; } void free_state_entity(struct state_entity *e); static gboolean ghr_free_entity(char *key, struct state_entity *e, gpointer ud) { g_free_if(key); free_state_entity(e); return TRUE; } void free_state_entity(struct state_entity *e) { assert(e); g_hash_table_foreach_remove(e->values, (GHRFunc) ghr_free_value, NULL); g_hash_table_foreach_remove(e->entities, (GHRFunc) ghr_free_entity, NULL); g_hash_table_destroy(e->values); g_hash_table_destroy(e->entities); } struct state_entity *lookup_entity(const char *entity_name) { struct state_entity *e; if (!entities) { entities = g_hash_table_new(g_str_hash, g_str_equal); } e = g_hash_table_lookup(entities, entity_name); if (e) { return e; } e = new_state_entity(); g_hash_table_insert(entities, g_strdup(entity_name), e); return e; } int lookup_state_value_get_int(const char *state_name, const char *value_name, int def) { struct state_value *val; struct state_entity *e = lookup_entity(state_name); assert(e); assert(e->values); val = g_hash_table_lookup(e->values, value_name); if (val) { if (val->type == SV_int) { return *(val->val.int_val); } } else { val = new_state_value(SV_int); *(val->val.int_val) = def; g_hash_table_insert(e->values, g_strdup(value_name), val); } return def; } void state_value_set_int(const char *state_name, const char *value_name, int n) { struct state_value *val; struct state_entity *e = lookup_entity(state_name); assert(e); assert(e->values); val = g_hash_table_lookup(e->values, value_name); if (val) { if (val->type == SV_int) { *(val->val.int_val) = n; } } else { val = new_state_value(SV_int); *(val->val.int_val) = n; g_hash_table_insert(e->values, g_strdup(value_name), val); } } static void width_height_state(GtkWidget *w, GtkAllocation *allocation, char *name) { state_value_set_int(name, "width", allocation->width); state_value_set_int(name, "height", allocation->height); } static void window_realized(GtkWidget *w, char *name) { GdkWindow *win = w->window; if (win && config->restore_window_positions) { int x = lookup_state_value_get_int(name, "x", -1); int y = lookup_state_value_get_int(name, "y", -1); if (x >= 0 && y >= 0) gdk_window_move(win, x, y); } } static void window_unrealized(GtkWidget *w, char *name) { GdkWindow *win = w->window; if (win) { int x, y; gdk_window_get_position(win, &x, &y); state_value_set_int(name, "x", x); state_value_set_int(name, "y", y); } } GtkWidget *stateful_gtk_window_new(GtkWindowType type, const char *name, int w, int h) { GtkWidget *wid = gtk_window_new(type); char *ud = g_strdup(name); gtk_object_set_data_full(GTK_OBJECT(wid), "name", ud, g_free); if (config->restore_window_sizes) { gtk_window_set_default_size(GTK_WINDOW(wid), lookup_state_value_get_int(name, "width", w), lookup_state_value_get_int(name, "height", h)); } else { gtk_window_set_default_size(GTK_WINDOW(wid), w, h); } gtk_signal_connect(GTK_OBJECT(wid), "size-allocate", GTK_SIGNAL_FUNC(width_height_state), (gpointer) ud); gtk_signal_connect_after(GTK_OBJECT(wid), "realize", GTK_SIGNAL_FUNC(window_realized), (gpointer) ud); gtk_signal_connect(GTK_OBJECT(wid), "unrealize", GTK_SIGNAL_FUNC(window_unrealized), (gpointer) ud); return wid; } /* filename_config returns the name of the config file. The returned pointer must g_free'd. */ gchar *filename_state(int context) { gchar *rcpath = NULL; char *home; home = homedir(); if(home == NULL) { error_push(context, _("you have no home directory!")); return(NULL); } /* need add'l "/", thus add some extra chars */ rcpath = g_malloc(strlen(home) + strlen(STATEFILE) + 3); sprintf(rcpath, "%s/%s", home, STATEFILE); g_free(home); return(rcpath); } static void save_single_value(const char *value_name, struct state_value *v, struct writeconfig *wc) { int len = strlen(value_name) + 80; char *msg = g_malloc(len); snprintf(msg, len, "<state-value name='%s' type='%s'>\n", value_name, detokenize(token_value_type_names, v->type)); config_write(wc, msg); wc->indent++; switch (v->type) { case SV_int: snprintf(msg, len, "%d\n", *(v->val.int_val)); config_write(wc, msg); break; case SV_char: if (v->val.char_val) { config_write(wc, v->val.char_val); } break; case SV_list: abort(); break; default: abort(); } g_free(msg); wc->indent--; config_write(wc, "</state-value>\n"); } static void save_single_entity(const char *state_name, struct state_entity *e, struct writeconfig *wc) { int len = strlen(state_name) + 80; char *msg = g_malloc(len); snprintf(msg, len, "<entity name='%s'>\n", state_name); config_write(wc, msg); g_free(msg); wc->indent++; g_hash_table_foreach(e->values, (GHFunc) save_single_value, wc); g_hash_table_foreach(e->entities, (GHFunc) save_single_entity, wc); wc->indent--; config_write(wc, "</entity>\n"); } void save_state() { int save_context = error_new_context(_("Error saving statefile")); struct writeconfig *wc; gchar *statefile = filename_state(save_context); struct stat sfile; int mode = S_IRUSR|S_IWUSR; int fd; char *tmpstatefile = NULL; if (!statefile) { error_flush(save_context); return; } wc = g_malloc0(sizeof(struct writeconfig)); wc->indent = 0; /* write to temp file... */ tmpstatefile = g_malloc(strlen(statefile) + 10); strcpy(tmpstatefile, statefile); strcat(tmpstatefile, ".new"); /* check mode of original file. */ if(stat(statefile, &sfile) == 0) { mode = sfile.st_mode & (S_IRUSR|S_IWUSR); } fd = open(tmpstatefile, O_CREAT | O_WRONLY, mode); if (fd < 0) { error_push(save_context, _("Unable to open %1$s for writing:\n%2$s\n"), tmpstatefile, strerror(errno)); g_free(tmpstatefile); return; } wc->outfile = fdopen(fd, "w"); #if GTK_MAJOR >= 2 config_write(wc, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"); #else { char xmldecl[255]; extern const char *gq_codeset; snprintf(xmldecl, sizeof(xmldecl), "<?xml version=\"1.0\" encoding=\"%s\" standalone=\"yes\"?>\n", gq_codeset); config_write(wc, xmldecl); } #endif config_write(wc, "<gq-state>\n"); wc->indent++; g_hash_table_foreach(entities, (GHFunc) save_single_entity, wc); wc->indent--; config_write(wc, "</gq-state>\n"); fclose(wc->outfile); g_free(wc); rename(tmpstatefile, statefile); g_free(tmpstatefile); } static void gq_stateS(struct parser_context *ctx, struct tagstack_entry *e) { e->data = g_hash_table_new(g_str_hash, g_str_equal); e->free_data = (free_func) g_hash_table_destroy; } static void gq_stateE(struct parser_context *ctx, struct tagstack_entry *e) { if (ctx->user_data) { struct parser_comm *comm = (struct parser_comm *)ctx->user_data; comm->result = e->data; e->data = NULL; e->free_data = NULL; } else { /* FIXME: the free callback should clean up */ } } static void entityS(struct parser_context *ctx, struct tagstack_entry *e) { struct state_entity *ent = new_state_entity(); e->data = ent; e->free_data = (free_func) free_state_entity; } static void entityE(struct parser_context *ctx, struct tagstack_entry *e) { GHashTable *entities = peek_tag(ctx->stack, 1)->data; struct state_entity *ent = e->data; int i; for (i = 0 ; e->attrs[i] ; i += 2) { if (strcmp("name", e->attrs[i]) == 0) { g_hash_table_insert(entities, g_strdup(e->attrs[i+1]), ent); e->data = NULL; e->free_data = NULL; break; } } } static void entity_entityE(struct parser_context *ctx, struct tagstack_entry *e) { struct state_entity *parent = peek_tag(ctx->stack, 1)->data; struct state_entity *ent = e->data; int i; for (i = 0 ; e->attrs[i] ; i += 2) { if (strcmp("name", e->attrs[i]) == 0) { g_hash_table_insert(parent->entities, g_strdup(e->attrs[i+1]), ent); e->data = NULL; e->free_data = NULL; break; } } } static void state_valueE(struct parser_context *ctx, struct tagstack_entry *e) { struct state_entity *ent = peek_tag(ctx->stack, 1)->data; const char *n = NULL; enum state_value_type t = 0; int i; for (i = 0 ; e->attrs[i] ; i += 2) { if (strcmp("name", e->attrs[i]) == 0) { n = e->attrs[i+1]; } else if (strcmp("type", e->attrs[i]) == 0) { t = tokenize(token_value_type_names, e->attrs[i+1]); } } if (n != NULL && t != 0) { struct state_value *v = new_state_value(t); char *ep; switch (t) { case SV_int: *(v->val.int_val) = strtol(e->cdata, &ep, 10); /* FIXME handle error */ break; case SV_char: v->val.char_val = g_strdup(e->cdata); break; case SV_list: abort(); break; default: abort(); } g_hash_table_insert(ent->values, g_strdup(n), v); } } static struct xml_tag state_tags[] = { { "gq-state", 0, gq_stateS, gq_stateE, { NULL }, }, { "entity", 0, entityS, entityE, { "gq-state", NULL }, }, { "entity", 0, entityS, entity_entityE, { "entity", NULL }, }, { "state-value", 0, NULL, state_valueE, { "entity", NULL }, }, { NULL } }; GHashTable *process_statefile_XML(int error_context, const char *filename) { xmlSAXHandler *handler = g_malloc0(sizeof(xmlSAXHandler)); int rc; struct parser_comm comm; comm.error_context = error_context; comm.result = NULL; handler->error = (errorSAXFunc) XMLerrorHandler; handler->fatalError = (fatalErrorSAXFunc) XMLfatalErrorHandler; handler->warning = (warningSAXFunc) XMLwarningHandler; rc = XMLparse(state_tags, handler, &comm, filename); g_free(handler); if (rc != 0) { /* FIXME - cleanup failed parsing attempt */ comm.result = NULL; } return comm.result; } void init_state() { int load_context = error_new_context(_("Error loading statefile")); gchar *statefile = filename_state(load_context); if (statefile) { struct stat sfile; if (stat(statefile, &sfile) == 0) { entities = process_statefile_XML(load_context, statefile); } g_free(statefile); } error_flush(load_context); } --- NEW FILE: state.h --- /* GQ -- a GTK-based LDAP client Copyright (C) 1998-2003 Bert Vermeulen Copyright (C) 2002-2003 by Peter Stamfest This program is released under the Gnu General Public License with the additional exemption that compiling, linking, and/or using OpenSSL is allowed. 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* $Id: state.h,v 1.1 2003/10/05 22:15:31 stamfest Exp $ */ #ifndef GQ_STATE_H_INCLUDED #define GQ_STATE_H_INCLUDED #include "config.h" GtkWidget *stateful_gtk_window_new(GtkWindowType type, const char *name, int w, int h); void init_state(); void save_state(); #endif /* Local Variables: c-basic-offset: 4 End: */ |