Back to index

gcompris  8.2.2
menu.c
Go to the documentation of this file.
00001 /* gcompris - menu.c
00002  *
00003  * Time-stamp: <2006/08/20 10:45:56 bruno>
00004  *
00005  * Copyright (C) 2000-2006 Bruno Coudoin
00006  *
00007  *   This program is free software; you can redistribute it and/or modify
00008  *   it under the terms of the GNU General Public License as published by
00009  *   the Free Software Foundation; either version 2 of the License, or
00010  *   (at your option) any later version.
00011  *
00012  *   This program is distributed in the hope that it will be useful,
00013  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  *   GNU General Public License for more details.
00016  *
00017  *   You should have received a copy of the GNU General Public License
00018  *   along with this program; if not, write to the Free Software
00019  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00020  */
00021 
00022 #include <math.h>
00023 #include <string.h>
00024 #include <time.h>
00025 
00026 /* libxml includes */
00027 #include <libxml/tree.h>
00028 #include <libxml/parser.h>
00029 #include <libxml/parserInternals.h>
00030 
00031 #include "gcompris.h"
00032 
00033 GcomprisBoard *_read_xml_file(GcomprisBoard *gcomprisBoard, char *fname, gboolean db);
00034 
00035 /* List of all available boards  */
00036 static GList *boards_list = NULL;
00037 
00038 void gc_menu_board_free(GcomprisBoard *board);
00039 
00040 GList *gc_menu_get_boards()
00041 {
00042   return boards_list;
00043 }
00044 
00045 /*
00046  * Thanks for George Lebl <jirka@5z.com> for his Genealogy example
00047  * for all the XML stuff there
00048  */
00049 
00050 static void
00051 _add_xml_to_data(xmlDocPtr doc, xmlNodePtr xmlnode, GNode * child,
00052                       GcomprisBoard *gcomprisBoard, gboolean db)
00053 {
00054   GcomprisProperties *properties = gc_prop_get();
00055   gchar *title=NULL;
00056   gchar *description=NULL;
00057   gchar *prerequisite=NULL;
00058   gchar *goal=NULL;
00059   gchar *manual=NULL;
00060   gchar *credit=NULL;
00061 
00062   if(/* if the node has no name */
00063      !xmlnode->name ||
00064      /* or if the name is not "Board" */
00065      (g_strcasecmp((char *)xmlnode->name,"Board")!=0)
00066      )
00067     return;
00068 
00069   /* get the type of the board */
00070   gcomprisBoard->type = (char *)xmlGetProp(xmlnode, BAD_CAST "type");
00071 
00072   /* get the specific mode for this board */
00073   gcomprisBoard->mode                      = (char *)xmlGetProp(xmlnode, BAD_CAST "mode");
00074   gcomprisBoard->name                      = (char *)xmlGetProp(xmlnode, BAD_CAST "name");
00075   gcomprisBoard->icon_name          = (char *)xmlGetProp(xmlnode, BAD_CAST "icon");
00076   gcomprisBoard->author                    = (char *)xmlGetProp(xmlnode, BAD_CAST "author");
00077   gcomprisBoard->boarddir           = (char *)xmlGetProp(xmlnode, BAD_CAST "boarddir");
00078   gcomprisBoard->mandatory_sound_file      = (char *)xmlGetProp(xmlnode, BAD_CAST "mandatory_sound_file");
00079   gcomprisBoard->mandatory_sound_dataset = (char *)xmlGetProp(xmlnode, BAD_CAST "mandatory_sound_dataset");
00080 
00081   gchar *path                            = (char *)xmlGetProp(xmlnode, BAD_CAST "section");
00082   if (strlen(path)==1){
00083     g_free(path);
00084     path = g_strdup("");
00085     if (strcmp(gcomprisBoard->name,"root")==0)
00086     {
00087       g_free(gcomprisBoard->name);
00088       gcomprisBoard->name = g_strdup("");
00089     }
00090   }
00091 
00092   gcomprisBoard->section            = path;
00093 
00094   gcomprisBoard->title = NULL;
00095   gcomprisBoard->description = NULL;
00096   gcomprisBoard->prerequisite = NULL;
00097   gcomprisBoard->goal = NULL;
00098   gcomprisBoard->manual = NULL;
00099   gcomprisBoard->credit = NULL;
00100 
00101   gcomprisBoard->difficulty        = (char *)xmlGetProp(xmlnode, BAD_CAST "difficulty");
00102   if(gcomprisBoard->difficulty == NULL)
00103     gcomprisBoard->difficulty             = g_strdup("0");
00104 
00105   /* Update the difficulty max */
00106   if(properties->difficulty_max < atoi(gcomprisBoard->difficulty))
00107     properties->difficulty_max = atoi(gcomprisBoard->difficulty);
00108 
00109   for (xmlnode = xmlnode->xmlChildrenNode; xmlnode != NULL; xmlnode = xmlnode->next) {
00110     if (xmlHasProp(xmlnode, BAD_CAST "lang"))
00111       continue;
00112 
00113     /* get the title of the board */
00114     if (!strcmp((char *)xmlnode->name, "title"))
00115       {
00116        title = (char *)xmlNodeListGetString(doc, xmlnode->xmlChildrenNode, 0);
00117        gcomprisBoard->title = reactivate_newline(gettext(title));
00118       }
00119 
00120     /* get the description of the board */
00121     if (!strcmp((char *)xmlnode->name, "description"))
00122       {
00123        description = (char *)xmlNodeListGetString(doc, xmlnode->xmlChildrenNode, 0);
00124        gcomprisBoard->description = reactivate_newline(gettext(description));
00125       }
00126 
00127     /* get the help prerequisite help of the board */
00128     if (!strcmp((char *)xmlnode->name, "prerequisite"))
00129       {
00130        if(gcomprisBoard->prerequisite)
00131          g_free(gcomprisBoard->prerequisite);
00132 
00133        prerequisite = (char *)xmlNodeListGetString(doc,  xmlnode->xmlChildrenNode, 0);
00134        gcomprisBoard->prerequisite = reactivate_newline(gettext(prerequisite));
00135       }
00136 
00137     /* get the help goal of the board */
00138     if (!strcmp((char *)xmlnode->name, "goal"))
00139       {
00140        if(gcomprisBoard->goal)
00141          g_free(gcomprisBoard->goal);
00142 
00143        goal = (char *)xmlNodeListGetString(doc,  xmlnode->xmlChildrenNode, 0);
00144        gcomprisBoard->goal = reactivate_newline(gettext(goal));
00145       }
00146 
00147     /* get the help user manual of the board */
00148     if (!strcmp((char *)xmlnode->name, "manual"))
00149       {
00150        if(gcomprisBoard->manual)
00151          g_free(gcomprisBoard->manual);
00152 
00153        manual = (char *)xmlNodeListGetString(doc,  xmlnode->xmlChildrenNode, 0);
00154        gcomprisBoard->manual = reactivate_newline(gettext(manual));
00155       }
00156 
00157     /* get the help user credit of the board */
00158     if (!strcmp((char *)xmlnode->name, "credit"))
00159       {
00160        if(gcomprisBoard->credit)
00161          g_free(gcomprisBoard->credit);
00162 
00163        credit =(char *) xmlNodeListGetString(doc,  xmlnode->xmlChildrenNode, 0);
00164        gcomprisBoard->credit = reactivate_newline(gettext(credit));
00165       }
00166 
00167     /* Display the resource on stdout */
00168     if (properties->display_resource
00169        && !strcmp((char *)xmlnode->name, "resource")
00170        && gc_profile_get_current())
00171       {
00172        if(gc_db_is_activity_in_profile(gc_profile_get_current(), gcomprisBoard->name))
00173          {
00174            char *resource = (char *)xmlNodeListGetString(doc, xmlnode->xmlChildrenNode, 0);
00175            printf("%s\n", resource);
00176          }
00177       }
00178 
00179   }
00180 
00181 
00182   /* Check all mandatory field are specified */
00183   if(gcomprisBoard->name        == NULL ||
00184      gcomprisBoard->type        == NULL ||
00185      gcomprisBoard->icon_name   == NULL ||
00186      gcomprisBoard->section     == NULL ||
00187      gcomprisBoard->title       == NULL ||
00188      gcomprisBoard->description == NULL) {
00189     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");
00190   }
00191 
00192   if (db){
00193     gc_db_board_update( &gcomprisBoard->board_id,
00194                      &gcomprisBoard->section_id,
00195                      gcomprisBoard->name,
00196                      gcomprisBoard->section,
00197                      gcomprisBoard->author,
00198                      gcomprisBoard->type,
00199                      gcomprisBoard->mode,
00200                      atoi(gcomprisBoard->difficulty),
00201                      gcomprisBoard->icon_name,
00202                      gcomprisBoard->boarddir,
00203                      gcomprisBoard->mandatory_sound_file,
00204                      gcomprisBoard->mandatory_sound_dataset,
00205                      gcomprisBoard->filename,
00206                      /* Don't put translated text in the base */
00207                      title,
00208                      description,
00209                      prerequisite,
00210                      goal,
00211                      manual,
00212                      credit
00213                      );
00214 
00215     g_warning("db board written %d in %d  %s/%s",
00216              gcomprisBoard->board_id, gcomprisBoard->section_id,
00217              gcomprisBoard->section, gcomprisBoard->name);
00218 
00219 
00220   }
00221 
00222   g_free(title);
00223   g_free(description);
00224   g_free(prerequisite);
00225   g_free(goal);
00226   g_free(manual);
00227   g_free(credit);
00228 
00229 }
00230 
00231 /* parse the doc, add it to our internal structures and to the clist */
00232 static void
00233 parse_doc(xmlDocPtr doc, GcomprisBoard *gcomprisBoard, gboolean db)
00234 {
00235   xmlNodePtr node;
00236 
00237   /* find <Board> nodes and add them to the list, this just
00238      loops through all the children of the root of the document */
00239   for(node = doc->children->children; node != NULL; node = node->next) {
00240     /* add the board to the list, there are no children so
00241        we pass NULL as the node of the child */
00242     _add_xml_to_data(doc, node, NULL, gcomprisBoard, db);
00243   }
00244 }
00245 
00246 
00247 
00248 /* read an xml file into our memory structures and update our view,
00249    dump any old data we have in memory if we can load a new set
00250    Return a newly allocated GcomprisBoard or NULL if the parsing failed
00251 */
00252 GcomprisBoard *
00253 _read_xml_file(GcomprisBoard *gcomprisBoard,
00254               char *fname,
00255               gboolean db)
00256 {
00257   GcomprisProperties *properties = gc_prop_get();
00258   gchar *filename;
00259   /* pointer to the new doc */
00260   xmlDocPtr doc;
00261 
00262   g_return_val_if_fail(fname!=NULL, NULL);
00263 
00264   filename = g_strdup(fname);
00265 
00266   /* if the file doesn't exist */
00267   if(!g_file_test ((filename), G_FILE_TEST_EXISTS))
00268     {
00269       g_free(filename);
00270 
00271       /* if the file doesn't exist, try with our default prefix */
00272       filename = g_strdup_printf("%s/%s", properties->package_data_dir, fname);
00273 
00274       if(!g_file_test ((filename), G_FILE_TEST_EXISTS))
00275        {
00276          g_warning("Couldn't find file %s !", fname);
00277          g_warning("Couldn't find file %s !", filename);
00278          g_free(filename);
00279          g_free(gcomprisBoard);
00280          return NULL;
00281        }
00282 
00283     }
00284 
00285   /* parse the new file and put the result into newdoc */
00286   doc = xmlParseFile(filename);
00287 
00288   /* in case something went wrong */
00289   if(!doc) {
00290     g_warning("Oops, the parsing of %s failed", filename);
00291     return NULL;
00292   }
00293 
00294   if(/* if there is no root element */
00295      !doc->children ||
00296      /* if it doesn't have a name */
00297      !doc->children->name ||
00298      /* if it isn't a GCompris node */
00299      g_strcasecmp((char *)doc->children->name,"GCompris")!=0) {
00300     xmlFreeDoc(doc);
00301     g_free(gcomprisBoard);
00302     g_warning("Oops, the file %s is not for gcompris", filename);
00303     return NULL;
00304   }
00305 
00306   /* Store the file that belong to this board for trace and further need */
00307   gcomprisBoard->filename=filename;
00308 
00309   /* parse our document and replace old data */
00310   parse_doc(doc, gcomprisBoard, db);
00311 
00312   xmlFreeDoc(doc);
00313 
00314   gcomprisBoard->board_ready=FALSE;
00315   gcomprisBoard->canvas=gc_get_canvas();
00316 
00317   gcomprisBoard->gmodule      = NULL;
00318   gcomprisBoard->gmodule_file = NULL;
00319 
00320   /* Fixed since I use the canvas own pixel_per_unit scheme */
00321   gcomprisBoard->width  = BOARDWIDTH;
00322   gcomprisBoard->height = BOARDHEIGHT;
00323 
00324   return gcomprisBoard;
00325 }
00326 
00327 /* Return the first board with the given section
00328  */
00329 GcomprisBoard *
00330 gc_menu_section_get(gchar *section)
00331 {
00332   GList *list = NULL;
00333 
00334   for(list = boards_list; list != NULL; list = list->next) {
00335     GcomprisBoard *board = list->data;
00336 
00337     gchar *fullname = NULL;
00338 
00339     fullname = g_strdup_printf("%s/%s",
00340                             board->section, board->name);
00341 
00342     if (strcmp (fullname, section) == 0){
00343       g_free(fullname);
00344       return board;
00345     }
00346     g_free(fullname);
00347 
00348   }
00349   g_warning("gc_menu_section_get searching '%s' but NOT FOUND\n", section);
00350   return NULL;
00351 }
00352 
00353 static int
00354 boardlist_compare_func(const void *a, const void *b)
00355 {
00356   return strcasecmp(((GcomprisBoard *) a)->difficulty, ((GcomprisBoard *) b)->difficulty);
00357 }
00358 
00366 int
00367 gc_menu_has_activity(gchar *section, gchar *name)
00368 {
00369   GList *list = NULL;
00370   GcomprisProperties *properties = gc_prop_get();
00371   gchar *section_name = g_strdup_printf("%s/%s", section, name);
00372 
00373   if (strlen(section)==1)
00374     return 1;
00375 
00376   for(list = boards_list; list != NULL; list = list->next) {
00377     GcomprisBoard *board = list->data;
00378 
00379     if ( (!properties->experimental) &&
00380         (strcmp (board->name, "experimental") == 0))
00381       continue;
00382 
00383     if ((strcmp (section_name, board->section) == 0) &&
00384        (strlen(board->name) != 0) &&
00385        gc_board_check_file(board))
00386        {
00387          if((strcmp(board->type, "menu") == 0) &&
00388             strcmp(board->section, section) != 0)
00389            {
00390              /* We must check this menu is not empty recursively */
00391              if(gc_menu_has_activity(board->section, board->name))
00392               {
00393                 g_free(section_name);
00394                 return(1);
00395               }
00396            }
00397          else
00398            {
00399              g_free(section_name);
00400              return 1;
00401            }
00402        }
00403     }
00404 
00405   g_free(section_name);
00406   return 0;
00407 }
00408 
00409 /* Return the list of boards in the given section
00410  * Boards are sorted depending on their difficulty value
00411  */
00412 GList *gc_menu_getlist(gchar *section)
00413 {
00414   GList *list = NULL;
00415   GList *result_list = NULL;
00416 
00417   GcomprisProperties *properties = gc_prop_get();
00418 
00419   if(!section){
00420     g_error("gc_menu_getlist called with section == NULL !");
00421     return NULL;
00422   }
00423 
00424   if (strlen(section)==1)
00425     section = "";
00426 
00427   for(list = boards_list; list != NULL; list = list->next) {
00428     GcomprisBoard *board = list->data;
00429 
00430     if ( (!properties->experimental) &&
00431         (strcmp (board->name, "experimental") == 0))
00432       continue;
00433 
00434     if (strcmp (section, board->section) == 0) {
00435       if (strlen(board->name) != 0)
00436        {
00437          if(strcmp(board->type, "menu") == 0)
00438            {
00439              /* We must check first this menu is not empty */
00440              if(gc_menu_has_activity(board->section, board->name))
00441               result_list = g_list_append(result_list, board);
00442            }
00443          else
00444            {
00445              result_list = g_list_append(result_list, board);
00446            }
00447        }
00448     }
00449   }
00450 
00451   /* Sort the list now */
00452   result_list = g_list_sort(result_list, boardlist_compare_func);
00453 
00454   return result_list;
00455 }
00456 
00461 int
00462 file_end_with_xml(const gchar *file)
00463 {
00464 
00465   if(strlen(file)<4)
00466     return 0;
00467 
00468   return (strncmp (&file[strlen(file)-4], ".xml", 4) == 0);
00469 }
00470 
00471 static void cleanup_menus() {
00472   GList *list = NULL;
00473 
00474   for(list = boards_list; list != NULL; list = list->next) {
00475     GcomprisBoard *gcomprisBoard = list->data;
00476 
00477     _read_xml_file(gcomprisBoard, gcomprisBoard->filename, FALSE);
00478   }
00479 }
00480 
00481 
00482 /*
00483  * suppress_int_from_list
00484  */
00485 
00486 static GList *suppress_int_from_list(GList *list, int value)
00487 {
00488 
00489   GList *cell = list;
00490   int *data;
00491 
00492   while (cell != NULL){
00493     data = cell->data;
00494     if (*data==value){
00495       g_free(data);
00496       return g_list_remove(list, data);
00497     }
00498     cell = cell->next;
00499   }
00500   g_warning("suppress_int_from_list value %d not found", value);
00501   return list;
00502 }
00503 
00504 static gboolean compare_id(gconstpointer data1, gconstpointer data2)
00505 {
00506   int *i = (int *) data1;
00507   int *j = (int *) data2;
00508 
00509   if (*i == *j)
00510     return 0;
00511   else
00512     return -1;
00513 }
00514 
00515 /*
00516  * gc_menu_load
00517  *
00518  * Load all the menu it can from the given dirname
00519  *
00520  */
00521 void gc_menu_load_dir(char *dirname, gboolean db){
00522   const gchar   *one_dirent;
00523   GDir          *dir;
00524   GcomprisProperties *properties = gc_prop_get();
00525   GList *list_old_boards_id = NULL;
00526 
00527   if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) {
00528     g_warning("Failed to parse board in '%s' because it's not a directory\n", dirname);
00529     return;
00530   }
00531 
00532   dir = g_dir_open(dirname, 0, NULL);
00533 
00534   if (!dir) {
00535     g_warning("gc_menu_load : no menu found in %s", dirname);
00536     return;
00537   } else {
00538     if (db)
00539       list_old_boards_id = gc_db_get_board_id(list_old_boards_id);
00540 
00541     while((one_dirent = g_dir_read_name(dir)) != NULL) {
00542       /* add the board to the list */
00543       GcomprisBoard *gcomprisBoard = NULL;
00544       gchar *filename;
00545 
00546       filename = g_strdup_printf("%s/%s",
00547                              dirname, one_dirent);
00548 
00549       if(!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
00550        g_free(filename);
00551        continue;
00552       }
00553 
00554       if(file_end_with_xml(one_dirent)) {
00555        gcomprisBoard = g_malloc0 (sizeof (GcomprisBoard));
00556        gcomprisBoard->board_dir = g_strdup(dirname);
00557 
00558        /* Need to be initialized here because _read_xml_file is used also to reread        */
00559        /* the locale data                                                           */
00560        /* And we don't want in this case to loose the current plugin                       */
00561        gcomprisBoard->plugin=NULL;
00562        gcomprisBoard->previous_board=NULL;
00563 
00564        GcomprisBoard *board_read = _read_xml_file(gcomprisBoard, filename, db);
00565        if (board_read){
00566          list_old_boards_id = suppress_int_from_list(list_old_boards_id, board_read->board_id);
00567          if (properties->administration)
00568            boards_list = g_list_append(boards_list, board_read);
00569          else {
00570            if ((strncmp(board_read->section,
00571                       "/administration",
00572                       strlen("/administration"))!=0)) {
00573               boards_list = g_list_append(boards_list, board_read);
00574              }
00575       else
00576           gc_menu_board_free(board_read);
00577            }
00578          }
00579     else
00580         gc_menu_board_free(gcomprisBoard);
00581        }
00582       g_free(filename);
00583     }
00584   }
00585 
00586   if (db){
00587     /* remove suppressed boards from db */
00588     while (list_old_boards_id != NULL){
00589       int *data=list_old_boards_id->data;
00590       gc_db_remove_board(*data);
00591       list_old_boards_id=g_list_remove(list_old_boards_id, data);
00592       g_free(data);
00593     }
00594 
00595   }
00596 
00597   g_dir_close(dir);
00598 }
00599 
00600 
00601 /* load all the menus xml files in the gcompris path
00602  * into our memory structures.
00603  */
00604 void gc_menu_load()
00605 {
00606   GcomprisProperties *properties = gc_prop_get();
00607 
00608   if(boards_list)
00609     {
00610       cleanup_menus();
00611       return;
00612     }
00613 
00614   if ((!properties->reread_menu) && gc_db_check_boards())
00615     {
00616       boards_list = gc_menu_load_db(boards_list);
00617 
00618       if (!properties->administration)
00619        {
00620          GList *out_boards = NULL;
00621          GList *list = NULL;
00622          GcomprisBoard *board;
00623 
00624          for (list = boards_list; list != NULL; list = list->next)
00625            {
00626              board = (GcomprisBoard *)list->data;
00627              if (g_list_find_custom(gc_profile_get_current()->activities,
00628                                  &(board->board_id), compare_id))
00629               out_boards = g_list_append(out_boards, board);
00630            }
00631          for (list = out_boards; list != NULL; list = list->next)
00632            boards_list = g_list_remove(boards_list, list->data);
00633        }
00634     }
00635   else
00636     {
00637       int db = (gc_profile_get_current() ? TRUE: FALSE);
00638       properties->reread_menu = TRUE;
00639       gc_menu_load_dir(properties->package_data_dir, db);
00640       GDate *today = g_date_new();
00641       g_date_set_time (today, time (NULL));
00642 
00643       gchar date[11];
00644       g_date_strftime (date, 11, "%F", today);
00645       gc_db_set_date(date);
00646       gc_db_set_version(VERSION);
00647       g_date_free(today);
00648     }
00649 
00650 
00651   if (properties->local_directory){
00652     gchar *board_dir = g_strdup_printf("%s/boards/", properties->local_directory);
00653     gc_menu_load_dir(board_dir, FALSE);
00654     g_free(board_dir);
00655   }
00656 }
00657 
00658 
00659 void gc_menu_board_free(GcomprisBoard *board)
00660 {
00661     g_free(board->type);
00662     g_free(board->board_dir);
00663     g_free(board->mode);
00664 
00665     g_free(board->name);
00666     g_free(board->title);
00667     g_free(board->description);
00668     g_free(board->icon_name);
00669     g_free(board->author);
00670     g_free(board->boarddir);
00671     g_free(board->filename);
00672     g_free(board->difficulty);
00673     g_free(board->mandatory_sound_file);
00674     g_free(board->mandatory_sound_dataset);
00675 
00676     g_free(board->section);
00677     g_free(board->menuposition);
00678 
00679     g_free(board->prerequisite);
00680     g_free(board->goal);
00681     g_free(board->manual);
00682     g_free(board->credit);
00683 
00684     if (board->gmodule)
00685         g_module_close(board->gmodule);
00686     g_free(board->gmodule_file);
00687 
00688     g_free(board);
00689 }
00690 
00691 void gc_menu_destroy(void)
00692 {
00693     GList * list;
00694     for(list = boards_list ; list ; list = list -> next)
00695     {
00696         gc_menu_board_free((GcomprisBoard *) list->data);
00697     }
00698 }