/*************************************************************************** - file: scripting.c - description: scripting for lessons & instructions ... ------------------- begin : Sun Dec 28, 2003 copyright : Jesse Andrews (C) 2003 email : tuxtype-dev@tux4kids.net ***************************************************************************/ /*************************************************************************** * * * 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 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "scripting.h" #define MAX_LESSONS 100 #include "SDL_extras.h" #include "convert_utf.h" #include "scandir.h" /* Local function prototypes: */ static void clear_items(itemType* i); static void clear_pages(pageType* p); static void close_script(void); static SDL_Color* get_color(const char* in); static int get_int(const char* in); static char* get_quote(const char* in); static char hex2int(char b, char s); static int load_script(const char* fn); static void run_script(void); static int is_xml_file(const struct dirent* xml_dirent); /************************************************************************/ /* */ /* "Public" functions (callable throughout program) */ /* */ /************************************************************************/ void InstructCascade(void) { char fn[FNLEN]; /* Try theme script first: */ if (!settings.use_english) sprintf( fn, "%s/scripts/cascade.xml", settings.theme_data_path); if (load_script( fn ) == 0) /* meaning successful load */ { run_script(); return; } /* If unsuccessful, fall back to default (English) script: */ sprintf( fn, "%s/scripts/cascade.xml", settings.default_data_path); if (load_script( fn ) != 0) return; // bail if any errors occur run_script(); } void InstructLaser(void) { char fn[FNLEN]; /* Try theme script first: */ if (!settings.use_english) sprintf( fn, "%s/scripts/laser.xml", settings.theme_data_path); if (load_script( fn ) == 0) /* meaning successful load */ { run_script(); return; } /* If unsuccessful, fall back to default (English) script: */ sprintf( fn, "%s/scripts/laser.xml", settings.default_data_path); if (load_script( fn ) != 0) return; // bail if any errors occur run_script(); } void ProjectInfo(void) { char fn[FNLEN]; /* Try theme script first: */ if (!settings.use_english) sprintf( fn, "%s/scripts/projectInfo.xml", settings.theme_data_path); if (load_script( fn ) == 0) /* meaning successful load */ { run_script(); return; } /* If unsuccessful, fall back to default (English) script: */ sprintf( fn, "%s/scripts/projectInfo.xml", settings.default_data_path); if (load_script( fn ) != 0) return; // bail if any errors occur run_script(); } /* This is the function that allows the user */ /* to select a lesson from the menu. */ /* FIXME we ought to display descriptive titles from the lessons, */ /* rather than just the filenames. We also should consider a */ /* "gold stars" system like in TuxMath - DSB */ int XMLLesson(void) { SDL_Surface* titles[MAX_LESSONS] = {NULL}; SDL_Surface* select[MAX_LESSONS] = {NULL}; SDL_Surface* left = NULL, *right = NULL; SDL_Rect leftRect, rightRect; SDL_Rect titleRects[8]; int nchars; struct dirent **script_list_dirents = NULL; int i = 0; int scriptIterator = 0; //Iterator over matching files in script dir int scripts = 0; //Iterator over accepted (& parsed) script files int num_scripts = 0; char script_path[FNLEN]; char script_filenames[MAX_LESSONS][FNLEN]; char fn[FNLEN]; int stop = 0; int loc = 0; int old_loc = 1; int found = 0; LOG("Entering XMLLesson():\n"); /* First look in theme path, if desired: */ if (!settings.use_english) { sprintf(script_path, "%s/scripts", settings.theme_data_path); if (CheckFile(script_path)) { DEBUGCODE {fprintf(stderr, "Using theme script dir: %s\n", script_path);} found = 1; } } /* Now look in default path if desired or needed: */ if (!found) { sprintf( script_path, "%s/scripts", settings.default_data_path); if (CheckFile(script_path)) { DEBUGCODE { fprintf(stderr, "Using theme script dir: %s\n", script_path); } found = 1; } } if (!found) { fprintf(stderr, "XMLLesson(): Error finding script directory!\n"); return 0; } /* If we get to here, we know there is at least a lesson script directory */ /* but not necessarily any valid files. */ DEBUGCODE { fprintf(stderr, "script_path is: %s\n", script_path); } /* create a list of all the .xml files */ num_scripts = scandir(script_path, &script_list_dirents, is_xml_file, alphasort); for (scriptIterator = 0, scripts = 0; scriptIterator < num_scripts && scripts < MAX_LESSONS; scriptIterator++) { /* Copy over the filename: */ nchars = snprintf(script_filenames[scripts], FNLEN, "%s", script_list_dirents[scriptIterator]->d_name); /* Skip (actually clobber) any invalid or undesired files: */ if (nchars < 0 || nchars >= FNLEN) continue; /* Don't show project info file or instructions files */ if (strcmp(script_filenames[scripts], "projectInfo.xml") == 0 || strcmp(script_filenames[scripts], "laser.xml") == 0 || strcmp(script_filenames[scripts], "cascade.xml") == 0) continue; DEBUGCODE { fprintf(stderr, "Found script file %d:\t%s\n", scripts, script_filenames[scripts]); } /* Increment the iterator for correctly-parsed lesson files */ scripts++; } // DEBUGCODE { fprintf(stderr, "Before undesired files screened out:\n"); for(i = 0; i < num_scripts; i++) fprintf(stderr, "script %d filename: %s\n", i, script_list_dirents[i]->d_name); fprintf(stderr, "After undesired files screened out:\n"); for(i = 0; i < scripts; i++) fprintf(stderr, "script %d filename: %s\n", i, script_filenames[i]); } /* Now free the individual dirents. We do this on a second pass */ /* because of the "continue" approach used to error handling. */ for (scriptIterator = 0; scriptIterator < num_scripts; scriptIterator++) free(script_list_dirents[scriptIterator]); free(script_list_dirents); /* Adjust num_scripts for any skipped files: */ num_scripts = scripts; //START OF OLD IMPLEMENTATION // num_scripts = 0; // script_dir = opendir(script_path); // do // { // script_file = readdir(script_dir); // if (!script_file) // break; // // /* must have at least '.xml' at the end */ // if (strlen(script_file->d_name) < 5) // continue; // // /* Don't show project info file or instructions files */ // if (strcmp(script_file->d_name, "projectInfo.xml") == 0 || // strcmp(script_file->d_name, "laser.xml") == 0 || // strcmp(script_file->d_name, "cascade.xml") == 0) // continue; // // // if (strcmp(&script_file->d_name[strlen(script_file->d_name) - 4],".xml")) // continue; // // sprintf(script_filenames[num_scripts], "%s", script_file->d_name); // num_scripts++; // DEBUGCODE { fprintf(stderr, "Adding XML file no. %d: %s\n", // num_scripts, script_filenames[num_scripts]); } // // } while (1); /* Leave loop when readdir() returns NULL */ // // closedir(script_dir); // END OF OLD IMPLEMENTATION DEBUGCODE { fprintf(stderr, "Found %d . xml file(s) in script dir\n", num_scripts); } /* let the user pick the lesson script */ for (i = 0; i < num_scripts; i++) { titles[i] = BlackOutline( script_filenames[i], DEFAULT_MENU_FONT_SIZE, &white ); select[i] = BlackOutline( script_filenames[i], DEFAULT_MENU_FONT_SIZE, &yellow); } left = LoadImage("left.png", IMG_ALPHA); right = LoadImage("right.png", IMG_ALPHA); LoadBothBkgds("main_bkg.png"); /* Get out if needed surface not loaded successfully: */ if (!CurrentBkgd() || !left || !right) { fprintf(stderr, "XMLLesson(): needed image not available\n"); for (i = 0; i < num_scripts; i++) { SDL_FreeSurface(titles[i]); SDL_FreeSurface(select[i]); titles[i] = select[i] = NULL; } SDL_FreeSurface(left); SDL_FreeSurface(right); left = right = NULL; return 0; } leftRect.w = left->w; leftRect.h = left->h; leftRect.x = screen->w/2 - 80 - (leftRect.w/2); leftRect.y = screen->h - 50; rightRect.w = right->w; rightRect.h = right->h; rightRect.x = screen->w/2 + 80 - (rightRect.w/2); rightRect.y = screen->h - 50; /* set initial rect sizes */ titleRects[0].y = 30; titleRects[0].w = titleRects[0].h = titleRects[0].x = 0; for (i = 1; i < 8; i++) { titleRects[i].y = titleRects[i - 1].y + 50; titleRects[i].w = titleRects[i].h = titleRects[i].x = 0; } /* Main event loop for this screen: */ while (!stop) { while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: return 0; /* Return control to the main program so we can exit cleanly */ break; case SDL_MOUSEMOTION: for (i = 0; (i < 8) && (loc - (loc % 8) + i < num_scripts); i++) if (inRect(titleRects[i], event.motion.x, event.motion.y )) { loc = loc - (loc % 8) + i; break; } break; case SDL_MOUSEBUTTONDOWN: if (inRect( leftRect, event.button.x, event.button.y )) { if (loc - (loc % 8) - 8 >= 0) { loc = loc - (loc % 8) - 8; break; } } if (inRect(rightRect, event.button.x, event.button.y)) { if (loc - (loc % 8) + 8 < num_scripts) { loc = loc - (loc % 8) + 8; break; } } for (i = 0; (i < 8) && (loc - (loc % 8) + i < num_scripts); i++) { if (inRect(titleRects[i], event.button.x, event.button.y)) { loc = loc - (loc % 8) + i; if(settings.use_english) sprintf(fn, "%s/scripts/%s", settings.default_data_path, script_filenames[loc]); else sprintf(fn, "%s/scripts/%s", settings.theme_data_path, script_filenames[loc]); stop = 1; break; } } break; case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_ESCAPE) { stop = 2; break; } if (event.key.keysym.sym == SDLK_RETURN) { if(settings.use_english) sprintf(fn, "%s/scripts/%s", settings.default_data_path, script_filenames[loc]); else sprintf(fn, "%s/scripts/%s", settings.theme_data_path, script_filenames[loc]); stop = 1; break; } if ((event.key.keysym.sym == SDLK_LEFT) || (event.key.keysym.sym == SDLK_PAGEUP)) { if (loc - (loc % 8) - 8 >= 0) loc = loc - (loc % 8) - 8; } if ((event.key.keysym.sym == SDLK_RIGHT) || (event.key.keysym.sym == SDLK_PAGEDOWN)) { if (loc - (loc % 8) + 8 < num_scripts) loc = (loc - (loc % 8) + 8); } if ((event.key.keysym.sym == SDLK_UP) || (event.key.keysym.sym == SDLK_k)) { if (loc > 0) loc--; } if ((event.key.keysym.sym == SDLK_DOWN) || (event.key.keysym.sym == SDLK_j)) { if (loc + 1 < num_scripts) loc++; } } } /* Redraw if we have changed location: */ if (old_loc != loc) { int start; SDL_BlitSurface(CurrentBkgd(), NULL, screen, NULL ); start = loc - (loc % 8); for (i = start; i < MIN(start + 8, num_scripts); i++) { titleRects[i % 8].x = screen->w/2 - (titles[i]->w/2); if (i == loc) /* Draw selected text in yellow: */ SDL_BlitSurface(select[loc], NULL, screen, &titleRects[i%8]); else /* Draw unselected text in white: */ SDL_BlitSurface(titles[i], NULL, screen, &titleRects[i%8]); } /* --- draw arrow buttons --- */ if (start > 0) SDL_BlitSurface(left, NULL, screen, &leftRect); if (start + 8 < num_scripts) SDL_BlitSurface(right, NULL, screen, &rightRect); SDL_UpdateRect(screen, 0, 0, 0 ,0); } SDL_Delay(40); old_loc = loc; } /* --- clear graphics before leaving function --- */ for (i = 0; i < num_scripts; i++) { if (titles[i]) SDL_FreeSurface(titles[i]); if (select[i]) SDL_FreeSurface(select[i]); titles[i] = select[i] = NULL; } SDL_FreeSurface(left); SDL_FreeSurface(right); left = right = NULL; /* Maybe overkill - about to be destroyed anyway */ FreeBothBkgds(); if (stop == 2) { SDL_ShowCursor(1); return 0; } /* Getting to here means "stop == 1", try to run chosen script: */ if (load_script(fn) != 0) { fprintf(stderr, "load_script() failed to load '%s'\n",fn); SDL_ShowCursor(1); return 0; // bail if any errors occur } DEBUGCODE { fprintf(stderr, "Attempting to run script: %s\n", fn); } run_script(); SDL_ShowCursor(1); LOG("Leave XMLLesson()\n"); return 1; } /************************************************************************/ /* */ /* "Private" functions (local to scripting.c) */ /* */ /************************************************************************/ static char* get_quote(const char* in) { int start, finish; char *out; for (start=0; start= strlen(in)) return 0; // return null string if no " found start++; // move past the " for (finish=start; finish= strlen(in)) return 0; // return null string if no " found out = malloc(finish - start + 2); snprintf(out, finish - start + 1, "%s", &in[start]); out[finish-start] = 0; return out; } static int get_int(const char* in) { char *t = get_quote(in); int ans=-1; if (t) { ans = atoi(t); free(t); } return ans; } static char hex2int(char b, char s) { char ans=0; if ((b>='0') && (b<='9')) ans=16*(b-'0'); else if ((b>='A') && (b<='F')) ans=16*(b-'A'+10); else if ((b>='a') && (b<='f')) ans=16*(b-'a'+10); if ((s>='0') && (s<='9')) ans+=(s-'0'); else if ((s>='A') && (s<='F')) ans+=(s-'A'+10); else if ((s>='a') && (s<='f')) ans+=(s-'a'+10); return ans; } static SDL_Color* get_color(const char* in) { char* col; SDL_Color* out=malloc(sizeof(SDL_Color)); col = get_quote(in); if ((strlen(col)==7) && (col[0] == '#')) { out->r = hex2int( col[1], col[2] ); out->g = hex2int( col[3], col[4] ); out->b = hex2int( col[5], col[6] ); } free(col); return out; } scriptType* curScript = NULL; pageType* curPage = NULL; itemType* curItem = NULL; static int load_script(const char* fn) { int i; char str[FNLEN]; FILE* f = NULL; DEBUGCODE { fprintf(stderr, "\nEnter load_script() - attempt to load '%s'\n", fn); } if (curScript) { LOG( "previous script in memory, removing now!\n"); close_script(); } f = fopen(fn, "r"); if (f == NULL) { fprintf(stderr, "error loading script %s\n", fn); return -1; } do { /* Compiler complains if we don't inspect result of fscanf() */ int fscanf_result = fscanf(f, "%[^\n]\n", str); if (fscanf_result == EOF) break; if (strncmp("",tmpStr, 3) == 0) { // move past the comment end tag tmpStr += 2; found = 1; } } // if the comment end was not found get another line if (!found) { fscanf_result = fscanf(f, "%[^\n]\n", str); tmpStr = str; } // we did find the end of the comment else { if (strlen(tmpStr) > 0) { // copy the rest of the line into str for processing strncpy(str, tmpStr, strlen(tmpStr)); str[strlen(tmpStr)] = '\0'; } else { // str needs another line, this one is used up fscanf_result = fscanf(f, "%[^\n]\n", str); tmpStr = str; } // if the next line is a comment, start all over again if (fscanf_result != EOF && strncmp("