Signal patch
[dia.git] / lib / sheet.c
blobcbcb66e6fedffe3063dc3232052a464de70780a3
1 /* Dia -- an diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
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.
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.
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.
19 #include <config.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #ifdef HAVE_UNISTD_H
26 #include <unistd.h>
27 #endif
28 #include <glib.h>
29 #include <libxml/tree.h>
30 #include <libxml/parser.h>
31 #include <libxml/xmlmemory.h>
32 #include "dia_xml_libxml.h"
33 #include <string.h>
35 #include "intl.h"
37 #include "sheet.h"
38 #include "message.h"
39 #include "object.h"
40 #include "dia_dirs.h"
42 static GSList *sheets = NULL;
44 Sheet *
45 new_sheet(char *name, gchar *description, char *filename, SheetScope scope,
46 Sheet *shadowing)
48 Sheet *sheet;
50 sheet = g_new(Sheet, 1);
52 sheet->name = g_strdup(name);
53 sheet->description = g_strdup(description);
55 sheet->filename = filename;
56 sheet->scope = scope;
57 sheet->shadowing = shadowing;
58 sheet->objects = NULL;
59 return sheet;
62 void
63 sheet_prepend_sheet_obj(Sheet *sheet, SheetObject *obj)
65 DiaObjectType *type;
67 type = object_get_type(obj->object_type);
68 if (type == NULL) {
69 message_warning("DiaObject '%s' needed in sheet '%s' was not found.\n"
70 "It will not be available for use.",
71 obj->object_type, sheet->name);
72 } else {
73 sheet->objects = g_slist_prepend( sheet->objects, (gpointer) obj);
77 void
78 sheet_append_sheet_obj(Sheet *sheet, SheetObject *obj)
80 DiaObjectType *type;
82 type = object_get_type(obj->object_type);
83 if (type == NULL) {
84 message_warning("DiaObject '%s' needed in sheet '%s' was not found.\n"
85 "It will not be availible for use.",
86 obj->object_type, sheet->name);
87 } else {
88 sheet->objects = g_slist_append( sheet->objects, (gpointer) obj);
92 void
93 register_sheet(Sheet *sheet)
95 sheets = g_slist_append(sheets, (gpointer) sheet);
98 GSList *
99 get_sheets_list(void)
101 return sheets;
104 /* Sheet file management */
106 static void load_sheets_from_dir(const gchar *directory, SheetScope scope);
107 static void load_register_sheet(const gchar *directory,const gchar *filename,
108 SheetScope scope);
110 static gint
111 dia_sheet_sort_callback(gconstpointer a, gconstpointer b)
113 return strcmp(((Sheet *)(a))->name, ((Sheet *)(b))->name);
116 void
117 dia_sort_sheets(void)
119 sheets = g_slist_sort(sheets, dia_sheet_sort_callback);
122 void load_all_sheets(void) {
123 char *sheet_path;
124 char *home_dir;
126 home_dir = dia_config_filename("sheets");
127 if (home_dir) {
128 load_sheets_from_dir(home_dir, SHEET_SCOPE_USER);
129 g_free(home_dir);
132 sheet_path = getenv("DIA_SHEET_PATH");
133 if (sheet_path) {
134 char **dirs = g_strsplit(sheet_path,G_SEARCHPATH_SEPARATOR_S,0);
135 int i;
137 for (i=0; dirs[i] != NULL; i++)
138 load_sheets_from_dir(dirs[i], SHEET_SCOPE_SYSTEM);
139 g_strfreev(dirs);
140 } else {
141 char *thedir = dia_get_data_directory("sheets");
142 load_sheets_from_dir(thedir, SHEET_SCOPE_SYSTEM);
143 g_free(thedir);
146 /* Sorting their sheets alphabetically makes user merging easier */
148 dia_sort_sheets();
151 static void
152 load_sheets_from_dir(const gchar *directory, SheetScope scope)
154 GDir *dp;
155 const char *dentry;
156 gchar *p;
158 dp = g_dir_open(directory, 0, NULL);
159 if (!dp) return;
161 while ( (dentry = g_dir_read_name(dp)) ) {
162 gchar *filename = g_strconcat(directory,G_DIR_SEPARATOR_S,
163 dentry,NULL);
165 if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
166 g_free(filename);
167 continue;
170 /* take only .sheet files */
171 p = filename + strlen(filename) - 6 /* strlen(".sheet") */;
172 if (0!=strncmp(p,".sheet",6)) {
173 g_free(filename);
174 continue;
177 load_register_sheet(directory, filename, scope);
178 g_free(filename);
182 g_dir_close(dp);
185 static void
186 load_register_sheet(const gchar *dirname, const gchar *filename,
187 SheetScope scope)
189 xmlDocPtr doc;
190 xmlNsPtr ns;
191 xmlNodePtr node, contents,subnode,root;
192 char *tmp;
193 gchar *name = NULL, *description = NULL;
194 int name_score = -1;
195 int descr_score = -1;
196 Sheet *sheet = NULL;
197 GSList *sheetp;
198 gboolean set_line_break = FALSE;
199 gboolean name_is_gmalloced = FALSE;
200 Sheet *shadowing = NULL;
201 Sheet *shadowing_sheet = NULL;
203 /* the XML fun begins here. */
205 doc = xmlDoParseFile(filename);
206 if (!doc) return;
207 root = doc->xmlRootNode;
208 while (root && (root->type != XML_ELEMENT_NODE)) root=root->next;
209 if (!root) return;
210 if (xmlIsBlankNode(root)) return;
212 if (!(ns = xmlSearchNsByHref(doc,root,
213 DIA_XML_NAME_SPACE_BASE "dia-sheet-ns"))) {
214 g_warning("could not find sheet namespace");
215 xmlFreeDoc(doc);
216 return;
218 if ((root->ns != ns) || (strcmp(root->name,"sheet"))) {
219 g_warning("root element was %s -- expecting sheet",
220 doc->xmlRootNode->name);
221 xmlFreeDoc(doc);
222 return;
225 contents = NULL;
226 for (node = root->xmlChildrenNode; node != NULL; node = node->next) {
227 if (xmlIsBlankNode(node)) continue;
228 if (node->type != XML_ELEMENT_NODE)
229 continue;
231 if (node->ns == ns && !strcmp(node->name, "name")) {
232 gint score;
234 /* compare the xml:lang property on this element to see if we get a
235 * better language match. LibXML seems to throw away attribute
236 * namespaces, so we use "lang" instead of "xml:lang" */
237 tmp = xmlGetProp(node, "xml:lang");
238 if (!tmp) tmp = xmlGetProp(node, "lang");
239 score = intl_score_locale(tmp);
240 if (tmp) xmlFree(tmp);
242 if (name_score < 0 || score < name_score) {
243 name_score = score;
244 if (name) xmlFree(name);
245 name = xmlNodeGetContent(node);
247 } else if (node->ns == ns && !strcmp(node->name, "description")) {
248 gint score;
250 /* compare the xml:lang property on this element to see if we get a
251 * better language match. LibXML seems to throw away attribute
252 * namespaces, so we use "lang" instead of "xml:lang" */
253 tmp = xmlGetProp(node, "xml:lang");
254 if (!tmp) tmp = xmlGetProp(node, "lang");
255 score = intl_score_locale(tmp);
256 if (tmp) xmlFree(tmp);
258 if (descr_score < 0 || score < descr_score) {
259 descr_score = score;
260 if (description) xmlFree(description);
261 description = xmlNodeGetContent(node);
264 } else if (node->ns == ns && !strcmp(node->name, "contents")) {
265 contents = node;
269 if (!name || !contents) {
270 g_warning("No <name> and/or <contents> in sheet %s--skipping", filename);
271 xmlFreeDoc(doc);
272 if (name) xmlFree(name);
273 if (description) xmlFree(description);
274 return;
277 /* Notify the user when we load a sheet that appears to be an updated
278 version of a sheet loaded previously (i.e. from ~/.dia/sheets). */
280 sheetp = get_sheets_list();
281 while (sheetp)
283 if (sheetp->data && !strcmp(((Sheet *)(sheetp->data))->name, name))
285 struct stat first_file, this_file;
286 int stat_ret;
288 stat_ret = stat(((Sheet *)(sheetp->data))->filename, &first_file);
289 g_assert(!stat_ret);
291 stat_ret = stat(filename, &this_file);
292 g_assert(!stat_ret);
294 if (this_file.st_mtime > first_file.st_mtime)
296 gchar *tmp = g_strdup_printf("%s [Copy of system]", name);
297 message_notice("The system sheet '%s' appears to be more recent"
298 " than your custom\n"
299 "version and has been loaded as '%s' for this session."
300 "\n\n"
301 "Move new objects (if any) from '%s' into your custom"
302 " sheet\n"
303 "or remove '%s', using the 'Sheets and Objects' dialog.",
304 name, tmp, tmp, tmp);
305 xmlFree(name);
306 name = tmp;
307 name_is_gmalloced = TRUE;
308 shadowing = sheetp->data; /* This copy-of-system sheet shadows
309 a user sheet */
311 else
313 /* The already-created user sheet shadows this sheet (which will be
314 invisible), but we don't know this sheet's address yet */
315 shadowing_sheet = sheetp->data;
318 sheetp = g_slist_next(sheetp);
321 sheet = new_sheet(name, description, g_strdup(filename), scope, shadowing);
323 if (shadowing_sheet)
324 shadowing_sheet->shadowing = sheet; /* Hilarious :-) */
326 if (name_is_gmalloced == TRUE)
327 g_free(name);
328 else
329 xmlFree(name);
330 xmlFree(description);
332 for (node = contents->xmlChildrenNode ; node != NULL; node = node->next) {
333 SheetObject *sheet_obj;
334 DiaObjectType *otype;
335 gchar *iconname = NULL;
337 int subdesc_score = -1;
338 gchar *objdesc = NULL;
340 gint intdata = 0;
341 gchar *chardata = NULL;
343 gboolean has_intdata = FALSE;
344 gboolean has_icon_on_sheet = FALSE;
346 if (xmlIsBlankNode(node)) continue;
348 if (node->type != XML_ELEMENT_NODE)
349 continue;
350 if (node->ns != ns) continue;
351 if (!strcmp(node->name,"object")) {
352 /* nothing */
353 } else if (!strcmp(node->name,"shape")) {
354 g_message("%s: you should use object tags rather than shape tags now",
355 filename);
356 } else if (!strcmp(node->name,"br")) {
357 /* Line break tag. */
358 set_line_break = TRUE;
359 continue;
360 } else
361 continue; /* unknown tag */
363 tmp = xmlGetProp(node,"intdata");
364 if (tmp) {
365 char *p;
366 intdata = (gint)strtol(tmp,&p,0);
367 if (*p != 0) intdata = 0;
368 xmlFree(tmp);
369 has_intdata = TRUE;
371 chardata = xmlGetProp(node,"chardata");
372 /* TODO.... */
373 if (chardata) xmlFree(chardata);
375 for (subnode = node->xmlChildrenNode;
376 subnode != NULL ;
377 subnode = subnode->next) {
378 if (xmlIsBlankNode(subnode)) continue;
380 if (subnode->ns == ns && !strcmp(subnode->name, "description")) {
381 gint score;
383 /* compare the xml:lang property on this element to see if we get a
384 * better language match. LibXML seems to throw away attribute
385 * namespaces, so we use "lang" instead of "xml:lang" */
387 tmp = xmlGetProp(subnode, "xml:lang");
388 if (!tmp) tmp = xmlGetProp(subnode, "lang");
389 score = intl_score_locale(tmp);
390 if (tmp) xmlFree(tmp);
392 if (subdesc_score < 0 || score < subdesc_score) {
393 subdesc_score = score;
394 if (objdesc) xmlFree(objdesc);
395 objdesc = xmlNodeGetContent(subnode);
398 } else if (subnode->ns == ns && !strcmp(subnode->name,"icon")) {
399 tmp = xmlNodeGetContent(subnode);
400 iconname = g_strconcat(dirname,G_DIR_SEPARATOR_S,tmp,NULL);
401 has_icon_on_sheet = TRUE;
402 if (tmp) xmlFree(tmp);
406 tmp = xmlGetProp(node,"name");
408 sheet_obj = g_new(SheetObject,1);
409 sheet_obj->object_type = g_strdup(tmp);
410 sheet_obj->description = g_strdup(objdesc);
411 xmlFree(objdesc); objdesc = NULL;
413 sheet_obj->pixmap = NULL;
414 sheet_obj->user_data = (void *)intdata; /* XXX modify user_data type ? */
415 sheet_obj->user_data_type = has_intdata ? USER_DATA_IS_INTDATA /* sure, */
416 : USER_DATA_IS_OTHER; /* why not */
417 sheet_obj->pixmap_file = iconname;
418 sheet_obj->has_icon_on_sheet = has_icon_on_sheet;
419 sheet_obj->line_break = set_line_break;
420 set_line_break = FALSE;
422 if ((otype = object_get_type(tmp)) == NULL) {
423 /* Don't complain. This does happen when disabling plug-ins too.
424 g_warning("object_get_type(%s) returned NULL", tmp); */
425 if (sheet_obj->description) g_free(sheet_obj->description);
426 g_free(sheet_obj->pixmap_file);
427 g_free(sheet_obj->object_type);
428 g_free(sheet_obj);
429 if (tmp) xmlFree(tmp);
430 continue;
433 /* set defaults */
434 if (sheet_obj->pixmap_file == NULL) {
435 g_assert(otype->pixmap || otype->pixmap_file);
436 sheet_obj->pixmap = otype->pixmap;
437 sheet_obj->pixmap_file = otype->pixmap_file;
438 sheet_obj->has_icon_on_sheet = has_icon_on_sheet;
440 if (sheet_obj->user_data == NULL
441 && sheet_obj->user_data_type != USER_DATA_IS_INTDATA)
442 sheet_obj->user_data = otype->default_user_data;
443 else
444 sheet_obj->user_data_type = USER_DATA_IS_INTDATA;
446 if (tmp) xmlFree(tmp);
448 /* we don't need to fix up the icon and descriptions for simple objects,
449 since they don't have their own description, and their icon is
450 already automatically handled. */
451 sheet_append_sheet_obj(sheet,sheet_obj);
454 if (!shadowing_sheet)
455 register_sheet(sheet);
457 xmlFreeDoc(doc);