/* gcompris - menu.c
*
* Copyright (C) 2000-2006 Bruno Coudoin
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
#include
#include
#include
/* libxml includes */
#include
#include
#include
#include "gcompris.h"
GcomprisBoard *_read_xml_file(GcomprisBoard *gcomprisBoard, char *fname, gboolean db);
/* List of all available boards */
static GList *boards_list = NULL;
void gc_menu_board_free(GcomprisBoard *board);
GList *gc_menu_get_boards()
{
return boards_list;
}
/*
* Thanks for George Lebl for his Genealogy example
* for all the XML stuff there
*/
static void
_add_xml_to_data(xmlDocPtr doc, xmlNodePtr xmlnode, GNode * child,
GcomprisBoard *gcomprisBoard, gboolean db)
{
GcomprisProperties *properties = gc_prop_get();
gchar *title=NULL;
gchar *description=NULL;
gchar *prerequisite=NULL;
gchar *goal=NULL;
gchar *manual=NULL;
gchar *credit=NULL;
if(/* if the node has no name */
!xmlnode->name ||
/* or if the name is not "Board" */
(g_strcasecmp((char *)xmlnode->name,"Board")!=0)
)
return;
/* get the type of the board */
gcomprisBoard->type = (char *)xmlGetProp(xmlnode, BAD_CAST "type");
/* get the specific mode for this board */
gcomprisBoard->mode = (char *)xmlGetProp(xmlnode, BAD_CAST "mode");
gcomprisBoard->name = (char *)xmlGetProp(xmlnode, BAD_CAST "name");
gcomprisBoard->icon_name = (char *)xmlGetProp(xmlnode, BAD_CAST "icon");
gcomprisBoard->author = (char *)xmlGetProp(xmlnode, BAD_CAST "author");
gcomprisBoard->boarddir = (char *)xmlGetProp(xmlnode, BAD_CAST "boarddir");
gcomprisBoard->mandatory_sound_file = (char *)xmlGetProp(xmlnode, BAD_CAST "mandatory_sound_file");
gcomprisBoard->mandatory_sound_dataset = (char *)xmlGetProp(xmlnode, BAD_CAST "mandatory_sound_dataset");
gchar *path = (char *)xmlGetProp(xmlnode, BAD_CAST "section");
if (strlen(path)==1){
g_free(path);
path = g_strdup("");
if (strcmp(gcomprisBoard->name,"root")==0)
{
g_free(gcomprisBoard->name);
gcomprisBoard->name = g_strdup("");
}
}
gcomprisBoard->section = path;
gcomprisBoard->title = NULL;
gcomprisBoard->description = NULL;
gcomprisBoard->prerequisite = NULL;
gcomprisBoard->goal = NULL;
gcomprisBoard->manual = NULL;
gcomprisBoard->credit = NULL;
gcomprisBoard->difficulty = (char *)xmlGetProp(xmlnode, BAD_CAST "difficulty");
if(gcomprisBoard->difficulty == NULL)
gcomprisBoard->difficulty = g_strdup("0");
/* Update the difficulty max */
if(properties->difficulty_max < atoi(gcomprisBoard->difficulty))
properties->difficulty_max = atoi(gcomprisBoard->difficulty);
for (xmlnode = xmlnode->xmlChildrenNode; xmlnode != NULL; xmlnode = xmlnode->next) {
if (xmlHasProp(xmlnode, BAD_CAST "lang"))
continue;
/* get the title of the board */
if (!strcmp((char *)xmlnode->name, "title"))
{
title = (char *)xmlNodeListGetString(doc, xmlnode->xmlChildrenNode, 0);
gcomprisBoard->title = reactivate_newline(gettext(title));
}
/* get the description of the board */
if (!strcmp((char *)xmlnode->name, "description"))
{
description = (char *)xmlNodeListGetString(doc, xmlnode->xmlChildrenNode, 0);
gcomprisBoard->description = reactivate_newline(gettext(description));
}
/* get the help prerequisite help of the board */
if (!strcmp((char *)xmlnode->name, "prerequisite"))
{
if(gcomprisBoard->prerequisite)
g_free(gcomprisBoard->prerequisite);
prerequisite = (char *)xmlNodeListGetString(doc, xmlnode->xmlChildrenNode, 0);
gcomprisBoard->prerequisite = reactivate_newline(gettext(prerequisite));
}
/* get the help goal of the board */
if (!strcmp((char *)xmlnode->name, "goal"))
{
if(gcomprisBoard->goal)
g_free(gcomprisBoard->goal);
goal = (char *)xmlNodeListGetString(doc, xmlnode->xmlChildrenNode, 0);
gcomprisBoard->goal = reactivate_newline(gettext(goal));
}
/* get the help user manual of the board */
if (!strcmp((char *)xmlnode->name, "manual"))
{
if(gcomprisBoard->manual)
g_free(gcomprisBoard->manual);
manual = (char *)xmlNodeListGetString(doc, xmlnode->xmlChildrenNode, 0);
gcomprisBoard->manual = reactivate_newline(gettext(manual));
}
/* get the help user credit of the board */
if (!strcmp((char *)xmlnode->name, "credit"))
{
if(gcomprisBoard->credit)
g_free(gcomprisBoard->credit);
credit =(char *) xmlNodeListGetString(doc, xmlnode->xmlChildrenNode, 0);
gcomprisBoard->credit = reactivate_newline(gettext(credit));
}
/* Display the resource on stdout */
if (properties->display_resource
&& !strcmp((char *)xmlnode->name, "resource")
&& gc_profile_get_current())
{
if(gc_db_is_activity_in_profile(gc_profile_get_current(), gcomprisBoard->name))
{
char *resource = (char *)xmlNodeListGetString(doc, xmlnode->xmlChildrenNode, 0);
printf("%s\n", resource);
}
}
}
/* Check all mandatory field are specified */
if(gcomprisBoard->name == NULL ||
gcomprisBoard->type == NULL ||
gcomprisBoard->icon_name == NULL ||
gcomprisBoard->section == NULL ||
gcomprisBoard->title == NULL ||
gcomprisBoard->description == NULL) {
g_error("failed to read a mandatory field for this board (mandatory fields are name type icon_name difficulty section title description). check the board xml file is complete, perhaps xml-i18n-tools did not generate the file properly");
}
if (db){
gc_db_board_update( &gcomprisBoard->board_id,
&gcomprisBoard->section_id,
gcomprisBoard->name,
gcomprisBoard->section,
gcomprisBoard->author,
gcomprisBoard->type,
gcomprisBoard->mode,
atoi(gcomprisBoard->difficulty),
gcomprisBoard->icon_name,
gcomprisBoard->boarddir,
gcomprisBoard->mandatory_sound_file,
gcomprisBoard->mandatory_sound_dataset,
gcomprisBoard->filename,
/* Don't put translated text in the base */
title,
description,
prerequisite,
goal,
manual,
credit
);
g_warning("db board written %d in %d %s/%s",
gcomprisBoard->board_id, gcomprisBoard->section_id,
gcomprisBoard->section, gcomprisBoard->name);
}
g_free(title);
g_free(description);
g_free(prerequisite);
g_free(goal);
g_free(manual);
g_free(credit);
}
/* parse the doc, add it to our internal structures and to the clist */
static void
parse_doc(xmlDocPtr doc, GcomprisBoard *gcomprisBoard, gboolean db)
{
xmlNodePtr node;
/* find nodes and add them to the list, this just
loops through all the children of the root of the document */
for(node = doc->children->children; node != NULL; node = node->next) {
/* add the board to the list, there are no children so
we pass NULL as the node of the child */
_add_xml_to_data(doc, node, NULL, gcomprisBoard, db);
}
}
/* read an xml file into our memory structures and update our view,
dump any old data we have in memory if we can load a new set
Return a newly allocated GcomprisBoard or NULL if the parsing failed
*/
GcomprisBoard *
_read_xml_file(GcomprisBoard *gcomprisBoard,
char *fname,
gboolean db)
{
GcomprisProperties *properties = gc_prop_get();
gchar *filename;
/* pointer to the new doc */
xmlDocPtr doc;
g_return_val_if_fail(fname!=NULL, NULL);
filename = g_strdup(fname);
/* if the file doesn't exist */
if(!g_file_test ((filename), G_FILE_TEST_EXISTS))
{
g_free(filename);
/* if the file doesn't exist, try with our default prefix */
filename = g_strdup_printf("%s/%s", properties->package_data_dir, fname);
if(!g_file_test ((filename), G_FILE_TEST_EXISTS))
{
g_warning("Couldn't find file %s !", fname);
g_warning("Couldn't find file %s !", filename);
g_free(filename);
g_free(gcomprisBoard);
return NULL;
}
}
/* parse the new file and put the result into newdoc */
doc = xmlParseFile(filename);
/* in case something went wrong */
if(!doc) {
g_warning("Oops, the parsing of %s failed", filename);
return NULL;
}
if(/* if there is no root element */
!doc->children ||
/* if it doesn't have a name */
!doc->children->name ||
/* if it isn't a GCompris node */
g_strcasecmp((char *)doc->children->name,"GCompris")!=0) {
xmlFreeDoc(doc);
g_free(gcomprisBoard);
g_warning("Oops, the file %s is not for gcompris", filename);
return NULL;
}
/* Store the file that belong to this board for trace and further need */
gcomprisBoard->filename=filename;
/* parse our document and replace old data */
parse_doc(doc, gcomprisBoard, db);
xmlFreeDoc(doc);
gcomprisBoard->board_ready=FALSE;
gcomprisBoard->canvas=gc_get_canvas();
gcomprisBoard->gmodule = NULL;
gcomprisBoard->gmodule_file = NULL;
/* Fixed since I use the canvas own pixel_per_unit scheme */
gcomprisBoard->width = BOARDWIDTH;
gcomprisBoard->height = BOARDHEIGHT;
return gcomprisBoard;
}
/* Return the first board with the given section
*/
GcomprisBoard *
gc_menu_section_get(gchar *section)
{
GList *list = NULL;
for(list = boards_list; list != NULL; list = list->next) {
GcomprisBoard *board = list->data;
gchar *fullname = NULL;
fullname = g_strdup_printf("%s/%s",
board->section, board->name);
if (strcmp (fullname, section) == 0){
g_free(fullname);
return board;
}
g_free(fullname);
}
g_warning("gc_menu_section_get searching '%s' but NOT FOUND\n", section);
return NULL;
}
static int
boardlist_compare_func(const void *a, const void *b)
{
return strcasecmp(((GcomprisBoard *) a)->difficulty, ((GcomprisBoard *) b)->difficulty);
}
/** Return true is there are at least one activity in the given section
*
* \param section: the section to check
*
* \return 1 if there is at least one activity, 0 instead
*
*/
int
gc_menu_has_activity(gchar *section, gchar *name)
{
GList *list = NULL;
GcomprisProperties *properties = gc_prop_get();
gchar *section_name = g_strdup_printf("%s/%s", section, name);
if (strlen(section)==1)
return 1;
for(list = boards_list; list != NULL; list = list->next) {
GcomprisBoard *board = list->data;
if ( (!properties->experimental) &&
(strcmp (board->name, "experimental") == 0))
continue;
if ((strcmp (section_name, board->section) == 0) &&
(strlen(board->name) != 0) &&
gc_board_check_file(board))
{
if((strcmp(board->type, "menu") == 0) &&
strcmp(board->section, section) != 0)
{
/* We must check this menu is not empty recursively */
if(gc_menu_has_activity(board->section, board->name))
{
g_free(section_name);
return(1);
}
}
else
{
g_free(section_name);
return 1;
}
}
}
g_free(section_name);
return 0;
}
/* Return the list of boards in the given section
* Boards are sorted depending on their difficulty value
*/
GList *gc_menu_getlist(gchar *section)
{
GList *list = NULL;
GList *result_list = NULL;
GcomprisProperties *properties = gc_prop_get();
if(!section){
g_error("gc_menu_getlist called with section == NULL !");
return NULL;
}
if (strlen(section)==1)
section = "";
for(list = boards_list; list != NULL; list = list->next) {
GcomprisBoard *board = list->data;
if ( (!properties->experimental) &&
(strcmp (board->name, "experimental") == 0))
continue;
if (strcmp (section, board->section) == 0) {
if (strlen(board->name) != 0)
{
if(strcmp(board->type, "menu") == 0)
{
/* We must check first this menu is not empty */
if(gc_menu_has_activity(board->section, board->name))
result_list = g_list_append(result_list, board);
}
else
{
result_list = g_list_append(result_list, board);
}
}
}
}
/* Sort the list now */
result_list = g_list_sort(result_list, boardlist_compare_func);
return result_list;
}
/** Select only files with .xml extention
*
* Return 1 if the given file end in .xml 0 else
*/
int
file_end_with_xml(const gchar *file)
{
if(strlen(file)<4)
return 0;
return (strncmp (&file[strlen(file)-4], ".xml", 4) == 0);
}
static void cleanup_menus() {
GList *list = NULL;
for(list = boards_list; list != NULL; list = list->next) {
GcomprisBoard *gcomprisBoard = list->data;
_read_xml_file(gcomprisBoard, gcomprisBoard->filename, FALSE);
}
}
/*
* suppress_int_from_list
*/
static GList *suppress_int_from_list(GList *list, int value)
{
GList *cell = list;
int *data;
while (cell != NULL){
data = cell->data;
if (*data==value){
g_free(data);
return g_list_remove(list, data);
}
cell = cell->next;
}
g_warning("suppress_int_from_list value %d not found", value);
return list;
}
static gboolean compare_id(gconstpointer data1, gconstpointer data2)
{
int *i = (int *) data1;
int *j = (int *) data2;
if (*i == *j)
return 0;
else
return -1;
}
/*
* gc_menu_load
*
* Load all the menu it can from the given dirname
*
*/
void gc_menu_load_dir(char *dirname, gboolean db){
const gchar *one_dirent;
GDir *dir;
GcomprisProperties *properties = gc_prop_get();
GList *list_old_boards_id = NULL;
if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) {
g_warning("Failed to parse board in '%s' because it's not a directory\n", dirname);
return;
}
dir = g_dir_open(dirname, 0, NULL);
if (!dir) {
g_warning("gc_menu_load : no menu found in %s", dirname);
return;
} else {
if (db)
list_old_boards_id = gc_db_get_board_id(list_old_boards_id);
while((one_dirent = g_dir_read_name(dir)) != NULL) {
/* add the board to the list */
GcomprisBoard *gcomprisBoard = NULL;
gchar *filename;
filename = g_strdup_printf("%s/%s",
dirname, one_dirent);
if(!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
g_free(filename);
continue;
}
if(file_end_with_xml(one_dirent)) {
gcomprisBoard = g_malloc0 (sizeof (GcomprisBoard));
gcomprisBoard->board_dir = g_strdup(dirname);
/* Need to be initialized here because _read_xml_file is used also to reread */
/* the locale data */
/* And we don't want in this case to loose the current plugin */
gcomprisBoard->plugin=NULL;
gcomprisBoard->previous_board=NULL;
GcomprisBoard *board_read = _read_xml_file(gcomprisBoard, filename, db);
if (board_read){
list_old_boards_id = suppress_int_from_list(list_old_boards_id, board_read->board_id);
if (properties->administration)
boards_list = g_list_append(boards_list, board_read);
else {
if ((strncmp(board_read->section,
"/administration",
strlen("/administration"))!=0)) {
boards_list = g_list_append(boards_list, board_read);
}
else
gc_menu_board_free(board_read);
}
}
else
gc_menu_board_free(gcomprisBoard);
}
g_free(filename);
}
}
if (db){
/* remove suppressed boards from db */
while (list_old_boards_id != NULL){
int *data=list_old_boards_id->data;
gc_db_remove_board(*data);
list_old_boards_id=g_list_remove(list_old_boards_id, data);
g_free(data);
}
}
g_dir_close(dir);
}
/* load all the menus xml files in the gcompris path
* into our memory structures.
*/
void gc_menu_load()
{
GcomprisProperties *properties = gc_prop_get();
if(boards_list)
{
cleanup_menus();
return;
}
if ((!properties->reread_menu) && gc_db_check_boards())
{
boards_list = gc_menu_load_db(boards_list);
if (!properties->administration)
{
GList *out_boards = NULL;
GList *list = NULL;
GcomprisBoard *board;
for (list = boards_list; list != NULL; list = list->next)
{
board = (GcomprisBoard *)list->data;
if (g_list_find_custom(gc_profile_get_current()->activities,
&(board->board_id), compare_id))
out_boards = g_list_append(out_boards, board);
}
for (list = out_boards; list != NULL; list = list->next)
boards_list = g_list_remove(boards_list, list->data);
}
}
else
{
int db = (gc_profile_get_current() ? TRUE: FALSE);
properties->reread_menu = TRUE;
gc_menu_load_dir(properties->package_data_dir, db);
/* use GTimeVal for portability */
GDate *today = g_date_new();
#if (GLIB_MAJOR_VERSION > 2) && (GLIB_MINOR_VERSION > 10)
GTimeVal now;
g_get_current_time (&now);
g_date_set_time_val (today, &now);
#else
g_date_set_time (today, time (NULL));
#endif
gchar date[11];
g_date_strftime (date, 11, "%F", today);
gc_db_set_date(date);
gc_db_set_version(VERSION);
g_date_free(today);
}
if (properties->local_directory){
gchar *board_dir = g_strdup_printf("%s/boards/", properties->local_directory);
gc_menu_load_dir(board_dir, FALSE);
g_free(board_dir);
}
}
void gc_menu_board_free(GcomprisBoard *board)
{
g_free(board->type);
g_free(board->board_dir);
g_free(board->mode);
g_free(board->name);
g_free(board->title);
g_free(board->description);
g_free(board->icon_name);
g_free(board->author);
g_free(board->boarddir);
g_free(board->filename);
g_free(board->difficulty);
g_free(board->mandatory_sound_file);
g_free(board->mandatory_sound_dataset);
g_free(board->section);
g_free(board->menuposition);
g_free(board->prerequisite);
g_free(board->goal);
g_free(board->manual);
g_free(board->credit);
if (board->gmodule)
g_module_close(board->gmodule);
g_free(board->gmodule_file);
g_free(board);
}
void gc_menu_destroy(void)
{
GList * list;
for(list = boards_list ; list ; list = list -> next)
{
gc_menu_board_free((GcomprisBoard *) list->data);
}
}