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