2006-12-03 Dimitris Glezos <dimitris@glezos.com>
[dia.git] / lib / sheet.c
blobcb7bf0d56630185326d751d1788ad7f5b455548e
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 /** Sort the list of sheets by *locale*.
112 static gint
113 dia_sheet_sort_callback(gconstpointer a, gconstpointer b)
115 return g_utf8_collate(gettext( ((Sheet *)(a))->name ),
116 gettext( ((Sheet *)(b))->name ));
119 void
120 dia_sort_sheets(void)
122 sheets = g_slist_sort(sheets, dia_sheet_sort_callback);
125 void load_all_sheets(void) {
126 char *sheet_path;
127 char *home_dir;
129 home_dir = dia_config_filename("sheets");
130 if (home_dir) {
131 load_sheets_from_dir(home_dir, SHEET_SCOPE_USER);
132 g_free(home_dir);
135 sheet_path = getenv("DIA_SHEET_PATH");
136 if (sheet_path) {
137 char **dirs = g_strsplit(sheet_path,G_SEARCHPATH_SEPARATOR_S,0);
138 int i;
140 for (i=0; dirs[i] != NULL; i++)
141 load_sheets_from_dir(dirs[i], SHEET_SCOPE_SYSTEM);
142 g_strfreev(dirs);
143 } else {
144 char *thedir = dia_get_data_directory("sheets");
145 load_sheets_from_dir(thedir, SHEET_SCOPE_SYSTEM);
146 g_free(thedir);
149 /* Sorting their sheets alphabetically makes user merging easier */
151 dia_sort_sheets();
154 static void
155 load_sheets_from_dir(const gchar *directory, SheetScope scope)
157 GDir *dp;
158 const char *dentry;
159 gchar *p;
161 dp = g_dir_open(directory, 0, NULL);
162 if (!dp) return;
164 while ( (dentry = g_dir_read_name(dp)) ) {
165 gchar *filename = g_strconcat(directory,G_DIR_SEPARATOR_S,
166 dentry,NULL);
168 if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
169 g_free(filename);
170 continue;
173 /* take only .sheet files */
174 p = filename + strlen(filename) - 6 /* strlen(".sheet") */;
175 if (0!=strncmp(p,".sheet",6)) {
176 g_free(filename);
177 continue;
180 load_register_sheet(directory, filename, scope);
181 g_free(filename);
185 g_dir_close(dp);
188 static void
189 load_register_sheet(const gchar *dirname, const gchar *filename,
190 SheetScope scope)
192 xmlDocPtr doc;
193 xmlNsPtr ns;
194 xmlNodePtr node, contents,subnode,root;
195 char *tmp;
196 gchar *name = NULL, *description = NULL;
197 int name_score = -1;
198 int descr_score = -1;
199 Sheet *sheet = NULL;
200 GSList *sheetp;
201 gboolean set_line_break = FALSE;
202 gboolean name_is_gmalloced = FALSE;
203 Sheet *shadowing = NULL;
204 Sheet *shadowing_sheet = NULL;
206 /* the XML fun begins here. */
208 doc = xmlDoParseFile(filename);
209 if (!doc) return;
210 root = doc->xmlRootNode;
211 while (root && (root->type != XML_ELEMENT_NODE)) root=root->next;
212 if (!root) return;
213 if (xmlIsBlankNode(root)) return;
215 if (!(ns = xmlSearchNsByHref(doc,root,
216 DIA_XML_NAME_SPACE_BASE "dia-sheet-ns"))) {
217 g_warning("could not find sheet namespace");
218 xmlFreeDoc(doc);
219 return;
221 if ((root->ns != ns) || (strcmp(root->name,"sheet"))) {
222 g_warning("root element was %s -- expecting sheet",
223 doc->xmlRootNode->name);
224 xmlFreeDoc(doc);
225 return;
228 contents = NULL;
229 for (node = root->xmlChildrenNode; node != NULL; node = node->next) {
230 if (xmlIsBlankNode(node)) continue;
231 if (node->type != XML_ELEMENT_NODE)
232 continue;
234 if (node->ns == ns && !strcmp(node->name, "name")) {
235 gint score;
237 /* compare the xml:lang property on this element to see if we get a
238 * better language match. LibXML seems to throw away attribute
239 * namespaces, so we use "lang" instead of "xml:lang" */
240 /* Now using the C locale for internal sheet names, instead gettexting
241 * when the name is used in the menus. Not going to figure out the
242 * XML lang system more than absolutely necessary now. --LC
245 tmp = xmlGetProp(node, "xml:lang");
246 if (!tmp) tmp = xmlGetProp(node, "lang");
248 score = intl_score_locale("C");
250 if (tmp) xmlFree(tmp);
253 if (name_score < 0 || score < name_score) {
254 name_score = score;
255 if (name) xmlFree(name);
256 name = xmlNodeGetContent(node);
258 } else if (node->ns == ns && !strcmp(node->name, "description")) {
259 gint score;
261 /* compare the xml:lang property on this element to see if we get a
262 * better language match. LibXML seems to throw away attribute
263 * namespaces, so we use "lang" instead of "xml:lang" */
264 tmp = xmlGetProp(node, "xml:lang");
265 if (!tmp) tmp = xmlGetProp(node, "lang");
266 score = intl_score_locale(tmp);
267 if (tmp) xmlFree(tmp);
269 if (descr_score < 0 || score < descr_score) {
270 descr_score = score;
271 if (description) xmlFree(description);
272 description = xmlNodeGetContent(node);
275 } else if (node->ns == ns && !strcmp(node->name, "contents")) {
276 contents = node;
280 if (!name || !contents) {
281 g_warning("No <name> and/or <contents> in sheet %s--skipping", filename);
282 xmlFreeDoc(doc);
283 if (name) xmlFree(name);
284 if (description) xmlFree(description);
285 return;
288 /* Notify the user when we load a sheet that appears to be an updated
289 version of a sheet loaded previously (i.e. from ~/.dia/sheets). */
291 sheetp = get_sheets_list();
292 while (sheetp)
294 if (sheetp->data && !strcmp(((Sheet *)(sheetp->data))->name, name))
296 struct stat first_file, this_file;
297 int stat_ret;
299 stat_ret = stat(((Sheet *)(sheetp->data))->filename, &first_file);
300 g_assert(!stat_ret);
302 stat_ret = stat(filename, &this_file);
303 g_assert(!stat_ret);
305 if (this_file.st_mtime > first_file.st_mtime)
307 gchar *tmp = g_strdup_printf("%s [Copy of system]", name);
308 message_notice("The system sheet '%s' appears to be more recent"
309 " than your custom\n"
310 "version and has been loaded as '%s' for this session."
311 "\n\n"
312 "Move new objects (if any) from '%s' into your custom"
313 " sheet\n"
314 "or remove '%s', using the 'Sheets and Objects' dialog.",
315 name, tmp, tmp, tmp);
316 xmlFree(name);
317 name = tmp;
318 name_is_gmalloced = TRUE;
319 shadowing = sheetp->data; /* This copy-of-system sheet shadows
320 a user sheet */
322 else
324 /* The already-created user sheet shadows this sheet (which will be
325 invisible), but we don't know this sheet's address yet */
326 shadowing_sheet = sheetp->data;
329 sheetp = g_slist_next(sheetp);
332 sheet = new_sheet(name, description, g_strdup(filename), scope, shadowing);
334 if (shadowing_sheet)
335 shadowing_sheet->shadowing = sheet; /* Hilarious :-) */
337 if (name_is_gmalloced == TRUE)
338 g_free(name);
339 else
340 xmlFree(name);
341 xmlFree(description);
343 for (node = contents->xmlChildrenNode ; node != NULL; node = node->next) {
344 SheetObject *sheet_obj;
345 DiaObjectType *otype;
346 gchar *iconname = NULL;
348 int subdesc_score = -1;
349 gchar *objdesc = NULL;
351 gint intdata = 0;
352 gchar *chardata = NULL;
354 gboolean has_intdata = FALSE;
355 gboolean has_icon_on_sheet = FALSE;
357 if (xmlIsBlankNode(node)) continue;
359 if (node->type != XML_ELEMENT_NODE)
360 continue;
361 if (node->ns != ns) continue;
362 if (!strcmp(node->name,"object")) {
363 /* nothing */
364 } else if (!strcmp(node->name,"shape")) {
365 g_message("%s: you should use object tags rather than shape tags now",
366 filename);
367 } else if (!strcmp(node->name,"br")) {
368 /* Line break tag. */
369 set_line_break = TRUE;
370 continue;
371 } else
372 continue; /* unknown tag */
374 tmp = xmlGetProp(node,"intdata");
375 if (tmp) {
376 char *p;
377 intdata = (gint)strtol(tmp,&p,0);
378 if (*p != 0) intdata = 0;
379 xmlFree(tmp);
380 has_intdata = TRUE;
382 chardata = xmlGetProp(node,"chardata");
383 /* TODO.... */
384 if (chardata) xmlFree(chardata);
386 for (subnode = node->xmlChildrenNode;
387 subnode != NULL ;
388 subnode = subnode->next) {
389 if (xmlIsBlankNode(subnode)) continue;
391 if (subnode->ns == ns && !strcmp(subnode->name, "description")) {
392 gint score;
394 /* compare the xml:lang property on this element to see if we get a
395 * better language match. LibXML seems to throw away attribute
396 * namespaces, so we use "lang" instead of "xml:lang" */
398 tmp = xmlGetProp(subnode, "xml:lang");
399 if (!tmp) tmp = xmlGetProp(subnode, "lang");
400 score = intl_score_locale(tmp);
401 if (tmp) xmlFree(tmp);
403 if (subdesc_score < 0 || score < subdesc_score) {
404 subdesc_score = score;
405 if (objdesc) xmlFree(objdesc);
406 objdesc = xmlNodeGetContent(subnode);
409 } else if (subnode->ns == ns && !strcmp(subnode->name,"icon")) {
410 tmp = xmlNodeGetContent(subnode);
411 iconname = g_strconcat(dirname,G_DIR_SEPARATOR_S,tmp,NULL);
412 has_icon_on_sheet = TRUE;
413 if (tmp) xmlFree(tmp);
417 tmp = xmlGetProp(node,"name");
419 sheet_obj = g_new(SheetObject,1);
420 sheet_obj->object_type = g_strdup(tmp);
421 sheet_obj->description = g_strdup(objdesc);
422 xmlFree(objdesc); objdesc = NULL;
424 sheet_obj->pixmap = NULL;
425 sheet_obj->user_data = (void *)intdata; /* XXX modify user_data type ? */
426 sheet_obj->user_data_type = has_intdata ? USER_DATA_IS_INTDATA /* sure, */
427 : USER_DATA_IS_OTHER; /* why not */
428 sheet_obj->pixmap_file = iconname;
429 sheet_obj->has_icon_on_sheet = has_icon_on_sheet;
430 sheet_obj->line_break = set_line_break;
431 set_line_break = FALSE;
433 if ((otype = object_get_type(tmp)) == NULL) {
434 /* Don't complain. This does happen when disabling plug-ins too.
435 g_warning("object_get_type(%s) returned NULL", tmp); */
436 if (sheet_obj->description) g_free(sheet_obj->description);
437 g_free(sheet_obj->pixmap_file);
438 g_free(sheet_obj->object_type);
439 g_free(sheet_obj);
440 if (tmp) xmlFree(tmp);
441 continue;
444 /* set defaults */
445 if (sheet_obj->pixmap_file == NULL) {
446 g_assert(otype->pixmap || otype->pixmap_file);
447 sheet_obj->pixmap = otype->pixmap;
448 sheet_obj->pixmap_file = otype->pixmap_file;
449 sheet_obj->has_icon_on_sheet = has_icon_on_sheet;
451 if (sheet_obj->user_data == NULL
452 && sheet_obj->user_data_type != USER_DATA_IS_INTDATA)
453 sheet_obj->user_data = otype->default_user_data;
454 else
455 sheet_obj->user_data_type = USER_DATA_IS_INTDATA;
457 if (tmp) xmlFree(tmp);
459 /* we don't need to fix up the icon and descriptions for simple objects,
460 since they don't have their own description, and their icon is
461 already automatically handled. */
462 sheet_append_sheet_obj(sheet,sheet_obj);
465 if (!shadowing_sheet)
466 register_sheet(sheet);
468 xmlFreeDoc(doc);