1 /* Dia -- an diagram creation/manipulation program
2  * Copyright (C) 2003 Lars Clausen
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18 
19 /* persistence.c -- functions that handle persistent stores, such as
20  * window positions and sizes, font menu, document history etc.
21  */
22 
23 #include "config.h"
24 
25 #include <stdio.h>
26 #include <string.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #ifdef HAVE_UNISTD_H
30 #include <unistd.h>
31 #endif
32 
33 #include "persistence.h"
34 #include "dia_dirs.h"
35 #include "dia_xml_libxml.h"
36 #include "dia_xml.h"
37 #include "message.h"
38 
39 #include <gtk/gtk.h>
40 #include <libxml/tree.h>
41 
42 /* Hash table from window role (string) to PersistentWindow structure.
43  */
44 static GHashTable *persistent_windows, *persistent_entrystrings, *persistent_lists;
45 static GHashTable *persistent_integers, *persistent_reals;
46 static GHashTable *persistent_booleans, *persistent_strings;
47 static GHashTable *persistent_colors;
48 
49 static GHashTable *
_dia_hash_table_str_any_new(void)50 _dia_hash_table_str_any_new (void)
51 {
52   /* the key is const, the value gets freed */
53   return g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
54 }
55 
56 /* *********************** LOADING FUNCTIONS *********************** */
57 
58 typedef void (*PersistenceLoadFunc)(gchar *role, xmlNodePtr node);
59 
60 static void
persistence_load_window(gchar * role,xmlNodePtr node)61 persistence_load_window(gchar *role, xmlNodePtr node)
62 {
63   AttributeNode attr;
64   PersistentWindow *wininfo = g_new0(PersistentWindow, 1);
65 
66   attr = composite_find_attribute(node, "xpos");
67   if (attr != NULL)
68     wininfo->x = data_int(attribute_first_data(attr));
69   attr = composite_find_attribute(node, "ypos");
70   if (attr != NULL)
71     wininfo->y = data_int(attribute_first_data(attr));
72   attr = composite_find_attribute(node, "width");
73   if (attr != NULL)
74     wininfo->width = data_int(attribute_first_data(attr));
75   attr = composite_find_attribute(node, "height");
76   if (attr != NULL)
77     wininfo->height = data_int(attribute_first_data(attr));
78   attr = composite_find_attribute(node, "isopen");
79   if (attr != NULL)
80     wininfo->isopen = data_boolean(attribute_first_data(attr));
81 
82   g_hash_table_insert(persistent_windows, role, wininfo);
83 }
84 
85 /** Load a persistent string into the strings hashtable */
86 static void
persistence_load_entrystring(gchar * role,xmlNodePtr node)87 persistence_load_entrystring(gchar *role, xmlNodePtr node)
88 {
89   AttributeNode attr;
90   gchar *string = NULL;
91 
92   /* Find the contents? */
93   attr = composite_find_attribute(node, "stringvalue");
94   if (attr != NULL)
95     string = data_string(attribute_first_data(attr));
96   else
97     return;
98 
99   if (string != NULL)
100     g_hash_table_insert(persistent_entrystrings, role, string);
101 }
102 
103 static void
persistence_load_list(gchar * role,xmlNodePtr node)104 persistence_load_list(gchar *role, xmlNodePtr node)
105 {
106   AttributeNode attr;
107   gchar *string = NULL;
108 
109   /* Find the contents? */
110   attr = composite_find_attribute(node, "listvalue");
111   if (attr != NULL)
112     string = data_string(attribute_first_data(attr));
113   else
114     return;
115 
116   if (string != NULL) {
117     gchar **strings = g_strsplit(string, "\n", -1);
118     PersistentList *plist;
119     GList *list = NULL;
120     int i;
121     for (i = 0; strings[i] != NULL; i++) {
122       list = g_list_append(list, g_strdup(strings[i]));
123     }
124     /* This frees the strings, too? */
125     g_strfreev(strings);
126     /* yes but not the other one --hb */
127     g_free (string);
128     plist = g_new(PersistentList, 1);
129     plist->glist = list;
130     plist->role = role;
131     plist->sorted = FALSE;
132     plist->max_members = G_MAXINT;
133     g_hash_table_insert(persistent_lists, role, plist);
134   }
135 }
136 
137 static void
persistence_load_integer(gchar * role,xmlNodePtr node)138 persistence_load_integer(gchar *role, xmlNodePtr node)
139 {
140   AttributeNode attr;
141 
142   /* Find the contents? */
143   attr = composite_find_attribute(node, "intvalue");
144   if (attr != NULL) {
145     gint *integer = g_new(gint, 1);
146     *integer = data_int(attribute_first_data(attr));
147     g_hash_table_insert(persistent_integers, role, integer);
148   }
149 }
150 
151 static void
persistence_load_real(gchar * role,xmlNodePtr node)152 persistence_load_real(gchar *role, xmlNodePtr node)
153 {
154   AttributeNode attr;
155 
156   /* Find the contents? */
157   attr = composite_find_attribute(node, "realvalue");
158   if (attr != NULL) {
159     real *realval = g_new(real, 1);
160     *realval = data_real(attribute_first_data(attr));
161     g_hash_table_insert(persistent_reals, role, realval);
162   }
163 }
164 
165 static void
persistence_load_boolean(gchar * role,xmlNodePtr node)166 persistence_load_boolean(gchar *role, xmlNodePtr node)
167 {
168   AttributeNode attr;
169 
170   /* Find the contents? */
171   attr = composite_find_attribute(node, "booleanvalue");
172   if (attr != NULL) {
173     gboolean *booleanval = g_new(gboolean, 1);
174     *booleanval = data_boolean(attribute_first_data(attr));
175     g_hash_table_insert(persistent_booleans, role, booleanval);
176   }
177 }
178 
179 static void
persistence_load_string(gchar * role,xmlNodePtr node)180 persistence_load_string(gchar *role, xmlNodePtr node)
181 {
182   AttributeNode attr;
183 
184   /* Find the contents? */
185   attr = composite_find_attribute(node, "stringvalue");
186   if (attr != NULL) {
187     gchar *stringval = data_string(attribute_first_data(attr));
188     g_hash_table_insert(persistent_strings, role, stringval);
189   }
190 }
191 
192 static void
persistence_load_color(gchar * role,xmlNodePtr node)193 persistence_load_color(gchar *role, xmlNodePtr node)
194 {
195   AttributeNode attr;
196 
197   /* Find the contents? */
198   attr = composite_find_attribute(node, "colorvalue");
199   if (attr != NULL) {
200     Color *colorval = g_new(Color, 1);
201     data_color(attribute_first_data(attr), colorval);
202     g_hash_table_insert(persistent_colors, role, colorval);
203   }
204 }
205 
206 static GHashTable *type_handlers;
207 
208 /** Load the named type of entries using the given function.
209  * func is a void (*func)(gchar *role, xmlNodePtr *node)
210  */
211 static void
persistence_load_type(xmlNodePtr node)212 persistence_load_type(xmlNodePtr node)
213 {
214   const gchar *typename = (gchar *) node->name;
215   gchar *name;
216 
217   PersistenceLoadFunc func =
218     (PersistenceLoadFunc)g_hash_table_lookup(type_handlers, typename);
219   if (func == NULL) {
220     return;
221   }
222 
223   name = (gchar *)xmlGetProp(node, (const xmlChar *)"role");
224   if (name == NULL) {
225     return;
226   }
227 
228   (*func)(name, node);
229   node = node->next;
230 }
231 
232 static void
persistence_set_type_handler(gchar * name,PersistenceLoadFunc func)233 persistence_set_type_handler(gchar *name, PersistenceLoadFunc func)
234 {
235   if (type_handlers == NULL)
236     type_handlers = g_hash_table_new(g_str_hash,g_str_equal);
237 
238   g_hash_table_insert(type_handlers, name, (gpointer)func);
239 }
240 
241 static void
persistence_init()242 persistence_init()
243 {
244   persistence_set_type_handler("window", persistence_load_window);
245   persistence_set_type_handler("entrystring", persistence_load_entrystring);
246   persistence_set_type_handler("list", persistence_load_list);
247   persistence_set_type_handler("integer", persistence_load_integer);
248   persistence_set_type_handler("real", persistence_load_real);
249   persistence_set_type_handler("boolean", persistence_load_boolean);
250   persistence_set_type_handler("string", persistence_load_string);
251   persistence_set_type_handler("color", persistence_load_color);
252 
253   if (persistent_windows == NULL) {
254     persistent_windows = _dia_hash_table_str_any_new();
255   }
256   if (persistent_entrystrings == NULL) {
257     persistent_entrystrings = _dia_hash_table_str_any_new();
258   }
259   if (persistent_lists == NULL) {
260     persistent_lists = _dia_hash_table_str_any_new();
261   }
262   if (persistent_integers == NULL) {
263     persistent_integers = _dia_hash_table_str_any_new();
264   }
265   if (persistent_reals == NULL) {
266     persistent_reals = _dia_hash_table_str_any_new();
267   }
268   if (persistent_booleans == NULL) {
269     persistent_booleans = _dia_hash_table_str_any_new();
270   }
271   if (persistent_strings == NULL) {
272     persistent_strings = _dia_hash_table_str_any_new();
273   }
274   if (persistent_colors == NULL) {
275     persistent_colors = _dia_hash_table_str_any_new();
276   }
277 }
278 
279 /* Load all persistent data. */
280 void
persistence_load()281 persistence_load()
282 {
283   xmlDocPtr doc;
284   gchar *filename = dia_config_filename("persistence");
285 
286   persistence_init();
287 
288   if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
289     g_free (filename);
290     return;
291   }
292   doc = xmlDiaParseFile(filename);
293   if (doc != NULL) {
294     if (doc->xmlRootNode != NULL) {
295       xmlNsPtr namespace = xmlSearchNs(doc, doc->xmlRootNode, (const xmlChar *)"dia");
296       if (!xmlStrcmp (doc->xmlRootNode->name, (const xmlChar *)"persistence") &&
297 	  namespace != NULL) {
298 	xmlNodePtr child_node = doc->xmlRootNode->children;
299 	for (; child_node != NULL; child_node = child_node->next) {
300 	  persistence_load_type(child_node);
301 	}
302       }
303     }
304     xmlFreeDoc(doc);
305   }
306   g_free(filename);
307 }
308 
309 /* *********************** SAVING FUNCTIONS *********************** */
310 
311 /* Save the position of a window  */
312 static void
persistence_save_window(gpointer key,gpointer value,gpointer data)313 persistence_save_window(gpointer key, gpointer value, gpointer data)
314 {
315   xmlNodePtr tree = (xmlNodePtr)data;
316   PersistentWindow *window_pos = (PersistentWindow *)value;
317   ObjectNode window;
318 
319   window = (ObjectNode)xmlNewChild(tree, NULL, (const xmlChar *)"window", NULL);
320 
321   xmlSetProp(window, (const xmlChar *)"role", (xmlChar *) key);
322   data_add_int(new_attribute(window, "xpos"), window_pos->x);
323   data_add_int(new_attribute(window, "ypos"), window_pos->y);
324   data_add_int(new_attribute(window, "width"), window_pos->width);
325   data_add_int(new_attribute(window, "height"), window_pos->height);
326   data_add_boolean(new_attribute(window, "isopen"), window_pos->isopen);
327 
328 }
329 
330 /* Save the contents of a string  */
331 static void
persistence_save_list(gpointer key,gpointer value,gpointer data)332 persistence_save_list(gpointer key, gpointer value, gpointer data)
333 {
334   xmlNodePtr tree = (xmlNodePtr)data;
335   ObjectNode listnode;
336   GString *buf;
337   GList *items;
338 
339   listnode = (ObjectNode)xmlNewChild(tree, NULL, (const xmlChar *)"list", NULL);
340 
341   xmlSetProp(listnode, (const xmlChar *)"role", (xmlChar *) key);
342   /* Make a string out of the list */
343   buf = g_string_new("");
344   for (items = ((PersistentList*)value)->glist; items != NULL;
345        items = g_list_next(items)) {
346     g_string_append(buf, (gchar *)items->data);
347     if (g_list_next(items) != NULL)
348       g_string_append(buf, "\n");
349   }
350 
351   data_add_string(new_attribute(listnode, "listvalue"), buf->str);
352   g_string_free(buf, TRUE);
353 }
354 
355 static void
persistence_save_integer(gpointer key,gpointer value,gpointer data)356 persistence_save_integer(gpointer key, gpointer value, gpointer data)
357 {
358   xmlNodePtr tree = (xmlNodePtr)data;
359   ObjectNode integernode;
360 
361   integernode = (ObjectNode)xmlNewChild(tree, NULL, (const xmlChar *)"integer", NULL);
362 
363   xmlSetProp(integernode, (const xmlChar *)"role", (xmlChar *)key);
364   data_add_int(new_attribute(integernode, "intvalue"), *(gint *)value);
365 }
366 
367 static void
persistence_save_real(gpointer key,gpointer value,gpointer data)368 persistence_save_real(gpointer key, gpointer value, gpointer data)
369 {
370   xmlNodePtr tree = (xmlNodePtr)data;
371   ObjectNode realnode;
372 
373   realnode = (ObjectNode)xmlNewChild(tree, NULL, (const xmlChar *)"real", NULL);
374 
375   xmlSetProp(realnode, (const xmlChar *)"role", (xmlChar *)key);
376   data_add_real(new_attribute(realnode, "realvalue"), *(real *)value);
377 }
378 
379 static void
persistence_save_boolean(gpointer key,gpointer value,gpointer data)380 persistence_save_boolean(gpointer key, gpointer value, gpointer data)
381 {
382   xmlNodePtr tree = (xmlNodePtr)data;
383   ObjectNode booleannode;
384 
385   booleannode = (ObjectNode)xmlNewChild(tree, NULL, (const xmlChar *)"boolean", NULL);
386 
387   xmlSetProp(booleannode, (const xmlChar *)"role", (xmlChar *)key);
388   data_add_boolean(new_attribute(booleannode, "booleanvalue"), *(gboolean *)value);
389 }
390 
391 static void
persistence_save_string(gpointer key,gpointer value,gpointer data)392 persistence_save_string(gpointer key, gpointer value, gpointer data)
393 {
394   xmlNodePtr tree = (xmlNodePtr)data;
395   ObjectNode stringnode;
396 
397   stringnode = (ObjectNode)xmlNewChild(tree, NULL, (const xmlChar *)"string", NULL);
398 
399   xmlSetProp(stringnode, (const xmlChar *)"role", (xmlChar *)key);
400   data_add_string(new_attribute(stringnode, "stringvalue"), (gchar *)value);
401 }
402 
403 static void
persistence_save_color(gpointer key,gpointer value,gpointer data)404 persistence_save_color(gpointer key, gpointer value, gpointer data)
405 {
406   xmlNodePtr tree = (xmlNodePtr)data;
407   ObjectNode colornode;
408 
409   colornode = (ObjectNode)xmlNewChild(tree, NULL, (const xmlChar *)"color", NULL);
410 
411   xmlSetProp(colornode, (const xmlChar *)"role", (xmlChar *)key);
412   data_add_color(new_attribute(colornode, "colorvalue"), (Color *)value);
413 }
414 
415 
416 static void
persistence_save_type(xmlDocPtr doc,GHashTable * entries,GHFunc func)417 persistence_save_type(xmlDocPtr doc, GHashTable *entries, GHFunc func)
418 {
419   if (entries != NULL && g_hash_table_size(entries) != 0) {
420     g_hash_table_foreach(entries, func, doc->xmlRootNode);
421   }
422 }
423 
424 /* Save all persistent data. */
425 void
persistence_save()426 persistence_save()
427 {
428   xmlDocPtr doc;
429   xmlNs *name_space;
430   gchar *filename = dia_config_filename("persistence");
431 
432   doc = xmlNewDoc((const xmlChar *)"1.0");
433   doc->encoding = xmlStrdup((const xmlChar *)"UTF-8");
434   doc->xmlRootNode = xmlNewDocNode(doc, NULL, (const xmlChar *)"persistence", NULL);
435 
436   name_space = xmlNewNs(doc->xmlRootNode,
437                         (const xmlChar *) DIA_XML_NAME_SPACE_BASE,
438 			(const xmlChar *)"dia");
439   xmlSetNs(doc->xmlRootNode, name_space);
440 
441   persistence_save_type(doc, persistent_windows, persistence_save_window);
442   persistence_save_type(doc, persistent_entrystrings, persistence_save_string);
443   persistence_save_type(doc, persistent_lists, persistence_save_list);
444   persistence_save_type(doc, persistent_integers, persistence_save_integer);
445   persistence_save_type(doc, persistent_reals, persistence_save_real);
446   persistence_save_type(doc, persistent_booleans, persistence_save_boolean);
447   persistence_save_type(doc, persistent_strings, persistence_save_string);
448   persistence_save_type(doc, persistent_colors, persistence_save_color);
449 
450   xmlDiaSaveFile(filename, doc);
451   g_free(filename);
452   xmlFreeDoc(doc);
453 }
454 
455 /* *********************** USAGE FUNCTIONS *********************** */
456 
457 /* ********* WINDOWS ********* */
458 
459 /* Returns the name used for a window in persistence.
460  */
461 static const gchar *
persistence_get_window_name(GtkWindow * window)462 persistence_get_window_name(GtkWindow *window)
463 {
464   const gchar *name = gtk_window_get_role(window);
465   if (name == NULL) {
466     g_warning("Internal:  Window %s has no role.", gtk_window_get_title(window));
467     return NULL;
468   }
469   return name;
470 }
471 
472 static void
persistence_store_window_info(GtkWindow * window,PersistentWindow * wininfo,gboolean isclosed)473 persistence_store_window_info(GtkWindow *window, PersistentWindow *wininfo,
474 			      gboolean isclosed)
475 {
476   /* Drawable means visible & mapped, what we usually think of as open. */
477   if (!isclosed) {
478     gtk_window_get_position(window, &wininfo->x, &wininfo->y);
479     gtk_window_get_size(window, &wininfo->width, &wininfo->height);
480     wininfo->isopen = TRUE;
481   } else {
482     wininfo->isopen = FALSE;
483   }
484 }
485 
486 /** Update the persistent information for a window.
487  * @param window The GTK window object being stored.
488  * @param isclosed Whether the window should be stored as closed or not.
489  * In some cases, the window's open/close state is not updated by the time
490  * the handler is called.
491  */
492 static void
persistence_update_window(GtkWindow * window,gboolean isclosed)493 persistence_update_window(GtkWindow *window, gboolean isclosed)
494 {
495   const gchar *name = persistence_get_window_name(window);
496   PersistentWindow *wininfo;
497 
498   if (name == NULL) return;
499 
500   if (persistent_windows == NULL) {
501     persistent_windows = _dia_hash_table_str_any_new();
502   }
503   wininfo = (PersistentWindow *)g_hash_table_lookup(persistent_windows, name);
504 
505   if (wininfo != NULL) {
506     persistence_store_window_info(window, wininfo, isclosed);
507   } else {
508     wininfo = g_new0(PersistentWindow, 1);
509     persistence_store_window_info(window, wininfo, FALSE);
510     g_hash_table_insert(persistent_windows, (gchar *)name, wininfo);
511   }
512   if (wininfo->window != NULL && wininfo->window != window) {
513     g_object_unref(wininfo->window);
514     wininfo->window = NULL;
515   }
516   if (wininfo->window == NULL) {
517     wininfo->window = window;
518     g_object_ref(window);
519   }
520   /* catch the transistion */
521   wininfo->isopen = !isclosed;
522 }
523 
524 /** Handler for window-related events that should cause persistent storage
525  * changes.
526  * @param window The GTK window to store for.
527  * @param event the GDK event that caused us to be called.  Note that the
528  * window state hasn't been updated by the event yet.
529  * @param data Userdata passed when adding signal handler.
530  */
531 static gboolean
persistence_window_event_handler(GtkWindow * window,GdkEvent * event,gpointer data)532 persistence_window_event_handler(GtkWindow *window, GdkEvent *event, gpointer data)
533 {
534   switch (event->type) {
535   case GDK_UNMAP :
536     dia_log_message ("unmap (%s)", persistence_get_window_name(window));
537     break;
538   case GDK_MAP :
539     dia_log_message  ("map (%s)", persistence_get_window_name(window));
540     break;
541   case GDK_CONFIGURE :
542     dia_log_message ("configure (%s)", persistence_get_window_name(window));
543     break;
544   }
545   persistence_update_window(window, !(GTK_WIDGET_MAPPED(window)));
546   /* continue processing */
547   return FALSE;
548 }
549 
550 /**
551  * Handler for when a window has been opened or closed.
552  * @param window The GTK window to store for.
553  * @param data Userdata passed when adding signal handler.
554  */
555 static gboolean
persistence_hide_show_window(GtkWindow * window,gpointer data)556 persistence_hide_show_window(GtkWindow *window, gpointer data)
557 {
558   persistence_update_window(window, !GTK_WIDGET_MAPPED(window));
559   return FALSE;
560 }
561 
562 /**
563  * If the screen size has changed some persistent info maybe out of the visible area.
564  * This function checks that stored coordinates are at least paritally visible on some
565  * monitor. In GDK parlance a screen can have multiple monitors.
566  */
567 static gboolean
wininfo_in_range(const PersistentWindow * wininfo)568 wininfo_in_range (const PersistentWindow *wininfo)
569 {
570   GdkScreen *screen = gdk_screen_get_default ();
571   gint num_monitors = gdk_screen_get_n_monitors (screen), i;
572   GdkRectangle rwin = {wininfo->x, wininfo->y, wininfo->width, wininfo->height};
573   GdkRectangle rres = {0, 0, 0, 0};
574 
575   for (i = 0; i < num_monitors; ++i) {
576     GdkRectangle rmon;
577 
578     gdk_screen_get_monitor_geometry (screen, i, &rmon);
579 
580     gdk_rectangle_intersect (&rwin, &rmon, &rres);
581     if (rres.width * rres.height > 0)
582       break;
583   }
584 
585   return (rres.width * rres.height > 0);
586 }
587 
588 /* Call this function after the window has a role assigned to use any
589  * persistence information about the window.
590  */
591 void
persistence_register_window(GtkWindow * window)592 persistence_register_window(GtkWindow *window)
593 {
594   const gchar *name = persistence_get_window_name(window);
595   PersistentWindow *wininfo;
596 
597   if (name == NULL) return;
598   if (persistent_windows == NULL) {
599     persistent_windows = _dia_hash_table_str_any_new();
600   }
601   wininfo = (PersistentWindow *)g_hash_table_lookup(persistent_windows, name);
602   if (wininfo != NULL) {
603     if (wininfo_in_range (wininfo)) {
604       /* only restore position if partially visible */
605       gtk_window_move(window, wininfo->x, wininfo->y);
606       gtk_window_resize(window, wininfo->width, wininfo->height);
607     }
608     if (wininfo->isopen) gtk_widget_show(GTK_WIDGET(window));
609   } else {
610     wininfo = g_new0(PersistentWindow, 1);
611     gtk_window_get_position(window, &wininfo->x, &wininfo->y);
612     gtk_window_get_size(window, &wininfo->width, &wininfo->height);
613     /* Drawable means visible & mapped, what we usually think of as open. */
614     wininfo->isopen = GTK_WIDGET_DRAWABLE(GTK_WIDGET(window));
615     g_hash_table_insert(persistent_windows, (gchar *)name, wininfo);
616   }
617   if (wininfo->window != NULL && wininfo->window != window) {
618     g_object_unref(wininfo->window);
619     wininfo->window = NULL;
620   }
621   if (wininfo->window == NULL) {
622     wininfo->window = window;
623     g_object_ref(window);
624   }
625 
626   g_signal_connect(GTK_OBJECT(window), "configure-event",
627 		   G_CALLBACK(persistence_window_event_handler), NULL);
628   g_signal_connect(GTK_OBJECT(window), "map-event",
629 		   G_CALLBACK(persistence_window_event_handler), NULL);
630   g_signal_connect(GTK_OBJECT(window), "unmap-event",
631 		   G_CALLBACK(persistence_window_event_handler), NULL);
632 
633   g_signal_connect(GTK_OBJECT(window), "hide",
634 		   G_CALLBACK(persistence_hide_show_window), NULL);
635   g_signal_connect(GTK_OBJECT(window), "show",
636 		   G_CALLBACK(persistence_hide_show_window), NULL);
637 }
638 
639 /** Call this function at start-up to have a window creation function
640  * called if the window should be opened at startup.
641  * If no persistence information is available for the given role,
642  * nothing happens.
643  * @arg role The role of the window, as will be set by gtk_window_set_role()
644  * @arg createfunc A 0-argument function that creates the window.  This
645  * function will be called if the persistence information indicates that the
646  * window should be open.  The function should create and show the window.
647  */
648 void
persistence_register_window_create(gchar * role,NullaryFunc * func)649 persistence_register_window_create(gchar *role, NullaryFunc *func)
650 {
651   PersistentWindow *wininfo;
652 
653   if (role == NULL) return;
654   if (persistent_windows == NULL) return;
655   wininfo = (PersistentWindow *)g_hash_table_lookup(persistent_windows, role);
656   if (wininfo != NULL) {
657     (*func)();
658   }
659 }
660 
661 
662 /* ********* STRING ENTRIES ********** */
663 
664 static gboolean
persistence_update_string_entry(GtkWidget * widget,GdkEvent * event,gpointer userdata)665 persistence_update_string_entry(GtkWidget *widget, GdkEvent *event,
666 				gpointer userdata)
667 {
668   gchar *role = (gchar*)userdata;
669 
670   if (event->type == GDK_FOCUS_CHANGE) {
671     gchar *string = (gchar *)g_hash_table_lookup(persistent_entrystrings, role);
672     const gchar *entrystring = gtk_entry_get_text(GTK_ENTRY(widget));
673     if (string == NULL || strcmp(string, entrystring) != 0) {
674       g_hash_table_insert(persistent_entrystrings, role, g_strdup(entrystring));
675     }
676   }
677 
678   return FALSE;
679 }
680 
681 /** Change the contents of the persistently stored string entry.
682  * If widget is non-null, it is updated to reflect the change.
683  * This can be used e.g. for when a dialog is cancelled and the old
684  * contents should be restored.
685  */
686 gboolean
persistence_change_string_entry(gchar * role,gchar * string,GtkWidget * widget)687 persistence_change_string_entry(gchar *role, gchar *string,
688 				GtkWidget *widget)
689 {
690   gchar *old_string = (gchar*)g_hash_table_lookup(persistent_entrystrings, role);
691   if (old_string != NULL) {
692     if (widget != NULL) {
693       gtk_entry_set_text(GTK_ENTRY(widget), string);
694     }
695     g_hash_table_insert(persistent_entrystrings, role, g_strdup(string));
696   }
697 
698   return FALSE;
699 }
700 
701 /** Register a string in a GtkEntry for persistence.
702  * This should include not only a unique name, but some way to update
703  * whereever the string is used.
704  */
705 void
persistence_register_string_entry(gchar * role,GtkWidget * entry)706 persistence_register_string_entry(gchar *role, GtkWidget *entry)
707 {
708   gchar *string;
709   if (role == NULL) return;
710   if (persistent_entrystrings == NULL) {
711     persistent_entrystrings = _dia_hash_table_str_any_new();
712   }
713   string = (gchar *)g_hash_table_lookup(persistent_entrystrings, role);
714   if (string != NULL) {
715     gtk_entry_set_text(GTK_ENTRY(entry), string);
716   } else {
717     string = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
718     g_hash_table_insert(persistent_entrystrings, role, string);
719   }
720   g_signal_connect(G_OBJECT(entry), "event",
721 		   G_CALLBACK(persistence_update_string_entry), role);
722 }
723 
724 /* ********* LISTS ********** */
725 
726 /* Lists are used for e.g. recent files, selected fonts, etc.
727  * Anywhere where the user occasionally picks from a long list and
728  * is likely to reuse the items.
729  */
730 
731 PersistentList *
persistence_register_list(const gchar * role)732 persistence_register_list(const gchar *role)
733 {
734   PersistentList *list;
735   if (role == NULL) return NULL;
736   if (persistent_lists == NULL) {
737     persistent_lists = _dia_hash_table_str_any_new();
738   } else {
739     list = (PersistentList *)g_hash_table_lookup(persistent_lists, role);
740     if (list != NULL) {
741       return list;
742     }
743   }
744   list = g_new(PersistentList, 1);
745   list->role = role;
746   list->glist = NULL;
747   list->sorted = FALSE;
748   list->max_members = G_MAXINT;
749   g_hash_table_insert(persistent_lists, (gchar *)role, list);
750   return list;
751 }
752 
753 PersistentList *
persistent_list_get(const gchar * role)754 persistent_list_get(const gchar *role)
755 {
756   PersistentList *list;
757   if (role == NULL) return NULL;
758   if (persistent_lists != NULL) {
759     list = (PersistentList *)g_hash_table_lookup(persistent_lists, role);
760     if (list != NULL) {
761       return list;
762     }
763   }
764   /* Not registered! */
765   return NULL;
766 }
767 
768 GList *
persistent_list_get_glist(const gchar * role)769 persistent_list_get_glist(const gchar *role)
770 {
771   PersistentList *plist = persistent_list_get(role);
772   if (plist == NULL) return NULL;
773   return plist->glist;
774 }
775 
776 static GList *
persistent_list_cut_length(GList * list,guint length)777 persistent_list_cut_length(GList *list, guint length)
778 {
779   while (g_list_length(list) > length) {
780     GList *last = g_list_last(list);
781     /* Leaking data?  See not in persistent_list_add */
782     list = g_list_remove_link(list, last);
783     g_list_free(last);
784   }
785   return list;
786 }
787 
788 /** Add a new entry to this persistent list.
789  * @param role The name of a persistent list.
790  * @param item An entry to add.
791  * @returns FALSE if the entry already existed in the list, TRUE otherwise.
792  */
793 gboolean
persistent_list_add(const gchar * role,const gchar * item)794 persistent_list_add(const gchar *role, const gchar *item)
795 {
796   PersistentList *plist = persistent_list_get(role);
797   if(plist == NULL) {
798     g_warning("Can't find list for %s when adding %s", role, item);
799     return TRUE;
800   }
801   if (plist->sorted) {
802     /* Sorting not implemented yet. */
803     return TRUE;
804   } else {
805     gboolean existed = FALSE;
806     GList *tmplist = plist->glist;
807     GList *old_elem = g_list_find_custom(tmplist, item, (GCompareFunc)g_ascii_strcasecmp);
808     while (old_elem != NULL) {
809       tmplist = g_list_remove_link(tmplist, old_elem);
810       /* Don't free this, as it makes recent_files go boom after
811        * selecting a file there several times.  Yes, it should be strdup'd,
812        * but it isn't.
813        */
814       /*g_free(old_elem->data);*/
815       g_list_free_1(old_elem);
816       old_elem = g_list_find_custom(tmplist, item, (GCompareFunc)g_ascii_strcasecmp);
817       existed = TRUE;
818     }
819     tmplist = g_list_prepend(tmplist, g_strdup(item));
820     tmplist = persistent_list_cut_length(tmplist, plist->max_members);
821     plist->glist = tmplist;
822     return existed;
823   }
824 }
825 
826 void
persistent_list_set_max_length(const gchar * role,gint max)827 persistent_list_set_max_length(const gchar *role, gint max)
828 {
829   PersistentList *plist = persistent_list_get(role);
830   plist->max_members = max;
831   plist->glist = persistent_list_cut_length(plist->glist, max);
832 }
833 
834 /** Remove an item from the persistent list.
835  * @param role The name of the persistent list.
836  * @param role The entry to remove.
837  * @returns TRUE if the item existed in the list, FALSE otherwise.
838  */
839 gboolean
persistent_list_remove(const gchar * role,const gchar * item)840 persistent_list_remove(const gchar *role, const gchar *item)
841 {
842   PersistentList *plist = persistent_list_get(role);
843   /* Leaking data?  See not in persistent_list_add */
844   GList *entry = g_list_find_custom(plist->glist, item, (GCompareFunc)g_ascii_strcasecmp);
845   if (entry != NULL) {
846     plist->glist = g_list_remove_link(plist->glist, entry);
847     g_free(entry->data);
848     return TRUE;
849   } else return FALSE;
850 }
851 
852 void
persistent_list_remove_all(const gchar * role)853 persistent_list_remove_all(const gchar *role)
854 {
855   PersistentList *plist = persistent_list_get(role);
856   persistent_list_cut_length(plist->glist, 0);
857   plist->glist = NULL;
858 }
859 
860 typedef struct {
861   PersistenceCallback func;
862   GObject *watch;
863   gpointer userdata;
864 } ListenerData;
865 
866 /** Add a listener to updates on the list, so that if another
867  * instance changes the list, menus and such can be updated.
868  * @param role The name of the persistent list to watch.
869  * @param func A function to call when the list is updated, takes
870  * the given userdata.
871  * @param userdata Data passed back into the callback function.
872  */
873 void
persistent_list_add_listener(const gchar * role,PersistenceCallback func,GObject * watch,gpointer userdata)874 persistent_list_add_listener(const gchar *role, PersistenceCallback func,
875 			     GObject *watch, gpointer userdata)
876 {
877   PersistentList *plist = persistent_list_get(role);
878   ListenerData *listener;
879 
880   if (plist != NULL) {
881     listener = g_new(ListenerData, 1);
882     listener->func = func;
883     listener->watch = watch;
884     g_object_add_weak_pointer(watch, (gpointer)&listener->watch);
885     listener->userdata = userdata;
886     plist->listeners = g_list_append(plist->listeners, listener);
887   }
888 }
889 
890 /* ********* INTEGERS ********** */
891 gint
persistence_register_integer(gchar * role,int defaultvalue)892 persistence_register_integer(gchar *role, int defaultvalue)
893 {
894   gint *integer;
895   if (role == NULL) return 0;
896   if (persistent_integers == NULL) {
897     persistent_integers = _dia_hash_table_str_any_new();
898   }
899   integer = (gint *)g_hash_table_lookup(persistent_integers, role);
900   if (integer == NULL) {
901     integer = g_new(gint, 1);
902     *integer = defaultvalue;
903     g_hash_table_insert(persistent_integers, role, integer);
904   }
905   return *integer;
906 }
907 
908 gint
persistence_get_integer(gchar * role)909 persistence_get_integer(gchar *role)
910 {
911   gint *integer;
912   if (persistent_integers == NULL) {
913     g_warning("No persistent integers to get for %s!", role);
914     return 0;
915   }
916   integer = (gint *)g_hash_table_lookup(persistent_integers, role);
917   if (integer != NULL) return *integer;
918   g_warning("No integer to get for %s", role);
919   return 0;
920 }
921 
922 void
persistence_set_integer(gchar * role,gint newvalue)923 persistence_set_integer(gchar *role, gint newvalue)
924 {
925   gint *integer;
926   if (persistent_integers == NULL) {
927     g_warning("No persistent integers yet for %s!", role);
928     return;
929   }
930   integer = (gint *)g_hash_table_lookup(persistent_integers, role);
931   if (integer != NULL)
932     *integer = newvalue;
933   else
934     g_warning("No integer to set for %s", role);
935 }
936 
937 /* ********* REALS ********** */
938 real
persistence_register_real(gchar * role,real defaultvalue)939 persistence_register_real(gchar *role, real defaultvalue)
940 {
941   real *realval;
942   if (role == NULL) return 0;
943   if (persistent_reals == NULL) {
944     persistent_reals = _dia_hash_table_str_any_new();
945   }
946   realval = (real *)g_hash_table_lookup(persistent_reals, role);
947   if (realval == NULL) {
948     realval = g_new(real, 1);
949     *realval = defaultvalue;
950     g_hash_table_insert(persistent_reals, role, realval);
951   }
952   return *realval;
953 }
954 
955 real
persistence_get_real(gchar * role)956 persistence_get_real(gchar *role)
957 {
958   real *realval;
959   if (persistent_reals == NULL) {
960     g_warning("No persistent reals to get for %s!", role);
961     return 0;
962   }
963   realval = (real *)g_hash_table_lookup(persistent_reals, role);
964   if (realval != NULL) return *realval;
965   g_warning("No real to get for %s", role);
966   return 0.0;
967 }
968 
969 void
persistence_set_real(gchar * role,real newvalue)970 persistence_set_real(gchar *role, real newvalue)
971 {
972   real *realval;
973   if (persistent_reals == NULL) {
974     g_warning("No persistent reals yet for %s!", role);
975     return;
976   }
977   realval = (real *)g_hash_table_lookup(persistent_reals, role);
978   if (realval != NULL)
979     *realval = newvalue;
980   else
981     g_warning("No real to set for %s", role);
982 }
983 
984 
985 /* ********* BOOLEANS ********** */
986 /** Returns true if the given role has been registered. */
987 gboolean
persistence_boolean_is_registered(const gchar * role)988 persistence_boolean_is_registered(const gchar *role)
989 {
990   gboolean *booleanval;
991   if (role == NULL) return 0;
992   if (persistent_booleans == NULL) {
993     persistent_booleans = _dia_hash_table_str_any_new();
994   }
995   booleanval = (gboolean *)g_hash_table_lookup(persistent_booleans, role);
996   return booleanval != NULL;
997 }
998 
999 gboolean
persistence_register_boolean(const gchar * role,gboolean defaultvalue)1000 persistence_register_boolean(const gchar *role, gboolean defaultvalue)
1001 {
1002   gboolean *booleanval;
1003   if (role == NULL) return 0;
1004   if (persistent_booleans == NULL) {
1005     persistent_booleans = _dia_hash_table_str_any_new();
1006   }
1007   booleanval = (gboolean *)g_hash_table_lookup(persistent_booleans, role);
1008   if (booleanval == NULL) {
1009     booleanval = g_new(gboolean, 1);
1010     *booleanval = defaultvalue;
1011     g_hash_table_insert(persistent_booleans, role, booleanval);
1012   }
1013   return *booleanval;
1014 }
1015 
1016 gboolean
persistence_get_boolean(const gchar * role)1017 persistence_get_boolean(const gchar *role)
1018 {
1019   gboolean *booleanval;
1020   if (persistent_booleans == NULL) {
1021     g_warning("No persistent booleans to get for %s!", role);
1022     return FALSE;
1023   }
1024   booleanval = (gboolean *)g_hash_table_lookup(persistent_booleans, role);
1025   if (booleanval != NULL) return *booleanval;
1026   g_warning("No boolean to get for %s", role);
1027   return FALSE;
1028 }
1029 
1030 void
persistence_set_boolean(const gchar * role,gboolean newvalue)1031 persistence_set_boolean(const gchar *role, gboolean newvalue)
1032 {
1033   gboolean *booleanval;
1034   if (persistent_booleans == NULL) {
1035     g_warning("No persistent booleans yet for %s!", role);
1036     return;
1037   }
1038   booleanval = (gboolean *)g_hash_table_lookup(persistent_booleans, role);
1039   if (booleanval != NULL)
1040     *booleanval = newvalue;
1041   else
1042     g_warning("No boolean to set for %s", role);
1043 }
1044 
1045 /* ********* STRINGS ********** */
1046 /** Register a string in persistence.
1047  * @param role The name used to refer to the string.  Must be unique within
1048  *             registered strings (and preferably with all registered items)
1049  * @param defaultvalue A value to use if the role does not exist yet.
1050  * @returns The value that role has after registering.  The caller is
1051  *          responsible for freeing this memory.  It will never be the same
1052  *          memory as defaultvalue.
1053  */
1054 gchar *
persistence_register_string(gchar * role,gchar * defaultvalue)1055 persistence_register_string(gchar *role, gchar *defaultvalue)
1056 {
1057   gchar *stringval;
1058   if (role == NULL) return 0;
1059   if (persistent_strings == NULL) {
1060     persistent_strings = _dia_hash_table_str_any_new();
1061   }
1062   stringval = (gchar *)g_hash_table_lookup(persistent_strings, role);
1063   if (stringval == NULL) {
1064     stringval = g_strdup(defaultvalue);
1065     g_hash_table_insert(persistent_strings, role, stringval);
1066   }
1067   return g_strdup(stringval);
1068 }
1069 
1070 gchar *
persistence_get_string(gchar * role)1071 persistence_get_string(gchar *role)
1072 {
1073   gchar *stringval;
1074   if (persistent_strings == NULL) {
1075     g_warning("No persistent strings to get for %s!", role);
1076     return NULL;
1077   }
1078   stringval = (gchar *)g_hash_table_lookup(persistent_strings, role);
1079   if (stringval != NULL) return g_strdup(stringval);
1080   g_warning("No string to get for %s", role);
1081   return NULL;
1082 }
1083 
1084 void
persistence_set_string(gchar * role,const gchar * newvalue)1085 persistence_set_string(gchar *role, const gchar *newvalue)
1086 {
1087   gchar *stringval;
1088   if (persistent_strings == NULL) {
1089     g_warning("No persistent strings yet for %s!", role);
1090     return;
1091   }
1092   stringval = (gchar *)g_hash_table_lookup(persistent_strings, role);
1093   if (stringval != NULL) {
1094     g_hash_table_insert(persistent_strings, role, g_strdup(newvalue));
1095   } else {
1096     g_hash_table_remove(persistent_strings, role);
1097   }
1098 }
1099 
1100 /* ********* COLORS ********** */
1101 /* Remember that colors returned are private, not to be deallocated.
1102  * They will be smashed in some undefined way by persistence_set_color */
1103 Color *
persistence_register_color(gchar * role,Color * defaultvalue)1104 persistence_register_color(gchar *role, Color *defaultvalue)
1105 {
1106   Color *colorval;
1107   if (role == NULL) return 0;
1108   if (persistent_colors == NULL) {
1109     persistent_colors = _dia_hash_table_str_any_new();
1110   }
1111   colorval = (Color *)g_hash_table_lookup(persistent_colors, role);
1112   if (colorval == NULL) {
1113     colorval = g_new(Color, 1);
1114     *colorval = *defaultvalue;
1115     g_hash_table_insert(persistent_colors, role, colorval);
1116   }
1117   return colorval;
1118 }
1119 
1120 Color *
persistence_get_color(gchar * role)1121 persistence_get_color(gchar *role)
1122 {
1123   Color *colorval;
1124   if (persistent_colors == NULL) {
1125     g_warning("No persistent colors to get for %s!", role);
1126     return 0;
1127   }
1128   colorval = (Color *)g_hash_table_lookup(persistent_colors, role);
1129   if (colorval != NULL) return colorval;
1130   g_warning("No color to get for %s", role);
1131   return 0;
1132 }
1133 
1134 void
persistence_set_color(gchar * role,Color * newvalue)1135 persistence_set_color(gchar *role, Color *newvalue)
1136 {
1137   Color *colorval;
1138   if (persistent_colors == NULL) {
1139     g_warning("No persistent colors yet for %s!", role);
1140     return;
1141   }
1142   colorval = (Color *)g_hash_table_lookup(persistent_colors, role);
1143   if (colorval != NULL)
1144     *colorval = *newvalue;
1145   else
1146     g_warning("No color to set for %s", role);
1147 }
1148