Commit a05da910 authored by platypro's avatar platypro

Added basic PDF support. Many widgets have been changed.

parent bbd8f84e
......@@ -4,22 +4,11 @@ Quiz Grinder TODO
General
* [X] Command-line interface for backend
* [X] Static exercises
* [ ] Persistent users
Quiz export types
* [X] Network quiz
* [X] raw JSON export
* [ ] PDF export
* [ ] Exam export (Many of one quiz, with QR codes)
* [ ] Party quiz
Widgets
* [X] TextView
* [X] SVGView
* [X] NumberInput
* [X] TextInput
* [X] ChoiceInput
* [ ] Custom widgets
* [X] PDF export
Docs
* [ ] Building and deploying
......@@ -28,3 +17,7 @@ Docs
* [ ] Creating problems
* [ ] Web front-end
* [ ] Widget, Lua API, and Exercise reference
Future
* [ ] Exam export (Many of one quiz, with QR codes)
* [ ] Party quiz
......@@ -2,7 +2,9 @@ cmake_minimum_required(VERSION 3.0)
project(quizgrinder C)
set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_C_FLAGS -Wall)
set(CMAKE_C_FLAGS "-Wall -O0")
option(QUIZGRINDER_BUILD_PDF "PDF Support" ON)
find_package(Threads)
find_package(PkgConfig)
......@@ -17,12 +19,6 @@ set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR} ${CMAKE_INSTALL_PREFIX}/lib
configure_file("src/paths.h.in" "${CMAKE_CURRENT_BINARY_DIR}/paths.h")
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${DEPS_INCLUDE}
src
)
set(SRCS
src/quizgrinder.c
src/exercise.c
......@@ -34,6 +30,15 @@ set(SRCS
${DEPS_SRC}
)
IF(QUIZGRINDER_BUILD_PDF)
pkg_search_module(CAIRO cairo)
pkg_search_module(RSVG librsvg-2.0)
pkg_search_module(PANGO pango)
pkg_search_module(PANGOCAIRO pangocairo)
set(SRCS ${SRCS} src/pdf.c)
add_definitions(-D_QUIZGRINDER_BUILD_PDF)
ENDIF()
add_executable(quizgrinder ${SRCS})
install(TARGETS quizgrinder DESTINATION ${CMAKE_INSTALL_FULL_BINDIR})
......@@ -48,3 +53,25 @@ target_link_libraries(quizgrinder
${DEPS_LIBS}
${CMAKE_THREAD_LIBS_INIT}
)
target_include_directories(quizgrinder PUBLIC
${CMAKE_CURRENT_BINARY_DIR}
${DEPS_INCLUDE}
src
)
if(QUIZGRINDER_BUILD_PDF)
target_link_libraries(quizgrinder
${CAIRO_LIBRARIES}
${RSVG_LIBRARIES}
${PANGO_LIBRARIES}
${PANGOCAIRO_LIBRARIES}
)
message(STATUS ${RSVG_INCLUDE_DIRS})
target_include_directories(quizgrinder PUBLIC
${CAIRO_INCLUDE_DIRS}
${RSVG_INCLUDE_DIRS}
${PANGO_INCLUDE_DIRS}
${PANGOCAIRO_INCLUDE_DIRS}
)
endif()
add_subdirectory(lua)
#libpmustache build settings
if(QUIZGRINDER_BUILD_PDF)
SET(PMUSTACHE_BUILD_GIO true)
endif()
add_subdirectory(libpmustache)
#WJElement build settings
......
Subproject commit 45493414a796fb57d7b23cebe128dd9b45c45198
Subproject commit 43aed4c0188aee04cdd78aad6d1cec6f5f8eb5e8
......@@ -17,4 +17,7 @@
#include <stdint.h>
#include <stdbool.h>
#define STR(a) #a
#define XSTR(a) STR(a)
#endif
......@@ -112,7 +112,7 @@ bool user_nextQuestion(PG_STATE* state, USER* u, WJWriter w)
} else WJWUInt32(KEYCOUNT(KEY_HINT), 0, w);
WJWString(KEY_CALCULATOR, u->questionAt->eref->calculator, true, w);
WJWString(KEY_NAME, u->questionAt->eref->name, true, w);
exercise_writeWidgets(state, u->questionAt, w);
exercise_writeJSONWidgets(state, u->questionAt, w);
return true;
}
......@@ -200,7 +200,7 @@ bool session_common_process(PG_STATE* state, WJElement request, WJWriter respons
u->hintAt = u->hintAt->next;
}
else
exercise_writeSolutions(state, u->questionAt, response);
exercise_writeJSONSolutions(state, u->questionAt, response);
}
}
else if(!strcmp(action, ACTION_QUESTION_CHECK))
......
......@@ -44,7 +44,7 @@ EXERCISE* mkExercise(PG_STATE* state)
return exer;
}
EXERTEMPLATE* exercise_mkTemplate(QUESTION* q)
EXERTEMPLATE* exercise_mkTemplate(PG_STATE* state, QUESTION* q, char* name)
{
//Add one to question template list
EXERTEMPLATE* prop = calloc(1, sizeof(EXERTEMPLATE));
......@@ -52,6 +52,44 @@ EXERTEMPLATE* exercise_mkTemplate(QUESTION* q)
prop->next = q->templates;
q->templates = prop;
TEMPLATECONTAINER* tc = state->exercise.templates;
while(tc)
{
if(!strcmp(tc->filename, name))
break;
tc = tc->next;
}
if(!tc)
{
tc = calloc(1, sizeof(TEMPLATECONTAINER));
if(!tc) return false;
size_t l = strlen(q->eref->basepath) + strlen(name) + 3;
char* filename = alloca(l);
snprintf(filename, l, "%s/t%s", q->eref->basepath, name);
tc->src = mustache_eatFile(filename);
if(!tc->src)
{
printf("Could not load template %s\n", filename);
free(tc);
return false;
}
size_t flen = strlen(name);
tc->filename = malloc(flen + 1);
if(!tc->filename) return false;
strcpy(tc->filename, name);
tc->template = mustache_mkIndex(tc->src, 0);
tc->next = state->exercise.templates;
state->exercise.templates = tc;
}
prop->builder.template = tc->template;
return prop;
}
......@@ -130,10 +168,11 @@ char* exercise_load(PG_STATE* state, char* filename)
if(!exer)
{
char* exerPath = malloc(strlen(state->path_exercise) + strlen(exerId) + 1);
size_t exerPath_len = strlen(state->path_exercise) + strlen(exerId) + 2;
char* exerPath = malloc(exerPath_len);
if(!exerPath) { return NULL; }
sprintf(exerPath, "%s/%s", state->path_exercise, exerId);
snprintf(exerPath, exerPath_len, "%s/%s", state->path_exercise, exerId);
char* file = exercise_loadResource(exerPath, "exercise.json");
FILE* f = fopen(file, "r");
......@@ -178,17 +217,28 @@ char* exercise_load(PG_STATE* state, char* filename)
else if(_IS(name, WJR_TYPE_ARRAY, KEYLIST(KEY_TEMPLATE)))
{
char* tobj;
void* baseContext;
while((tobj = WJRNext(name, 15, exer_rdr)))
{
EXERTEMPLATE* etemp = exercise_mkTemplate(&exer->staticQuestion);
EXERTEMPLATE* etemp = NULL;
char* telem;
char* srcName = NULL;
while((telem = WJRNext(tobj, 15, exer_rdr)))
{
if(_IS(telem, WJR_TYPE_STRING, KEY_PATH))
etemp->src = WJRStringLoad(NULL, exer_rdr);
srcName = WJRStringLoad(NULL, exer_rdr);
else if(_IS(telem, WJR_TYPE_OBJECT, KEY_SUBS))
etemp->subs = WJEOpenDocument(exer_rdr, telem, NULL, NULL);
baseContext = WJEOpenDocument(exer_rdr, telem, NULL, NULL);
}
if(srcName)
{
etemp = exercise_mkTemplate(state, &exer->staticQuestion, srcName);
etemp->builder.escape = NULL;
etemp->builder.provider = &WJEMustacheProvider;
}
if(etemp)
etemp->builder.baseContext = baseContext;
if(srcName) free(srcName);
}
}
else if(_IS(name, WJR_TYPE_ARRAY, KEYLIST(KEY_WIDGET)))
......@@ -426,47 +476,42 @@ size_t writeWJW(char *data, size_t length, void *userdata)
return (WJWStringN(NULL, data, length, FALSE, w) ? length : 0);
}
bool expandTemplate(struct PG_State* state, QUESTION* q, EXERTEMPLATE* temp, WJWriter w)
PROCESSEDSTRING exercise_processString(QUESTION* q, char* src)
{
TEMPLATECONTAINER* tc = state->exercise.templates;
while(tc)
{
if(!strcmp(tc->filename, temp->src))
break;
tc = tc->next;
}
if(!tc)
PROCESSEDSTRING result = {0};
result.string = src;
result.type = PSTYPE_STRING;
while(src)
{
tc = calloc(1, sizeof(TEMPLATECONTAINER));
if(!tc) return false;
size_t l = strlen(q->eref->basepath) + strlen(temp->src) + 3;
char* filename = alloca(l);
snprintf(filename, l, "%s/t%s", q->eref->basepath, temp->src);
tc->src = mustache_eatFile(filename);
if(!tc->src)
if(*src == '\\' || *src != '$')
return result;
else if(!strncmp(src + 1, KEY_TEMPLATE "<", strlen(KEY_TEMPLATE) + 1))
{
printf("Could not load template %s\n", filename);
free(tc);
return false;
uint32_t spid = strtoul(src + strlen(KEY_TEMPLATE) + 2, NULL, 0);
EXERTEMPLATE* temp = q->templates;
while(temp)
{
if(temp->id == spid)
{
result.template = &temp->builder;
result.type = PSTYPE_TEMPLATE;
return result;
}
temp = temp->next;
}
return result;
}
size_t flen = strlen(temp->src);
tc->filename = malloc(flen + 1);
if(!tc->filename) return false;
strcpy(tc->filename, temp->src);
tc->template = mustache_mkIndex(tc->src, 0);
tc->next = state->exercise.templates;
state->exercise.templates = tc;
else if(!strncmp(src + 1, KEY_SVG ":", strlen(KEY_SVG) + 1))
{
result.class = PSCLASS_SVG;
result.string += strlen(KEY_SVG) + 2;
src += strlen(KEY_SVG) + 2;
}
else return result;
}
PMUS_CONTEXT ctx = {.provider=WJEMustacheProvider, .write=writeWJW, .template=tc->template};
mustache_generate(&ctx, w, temp->subs);
return true;
return result;
}
bool writeWidget(PG_STATE* state, QUESTION* question, WJElement q, WJWriter w, char* name)
......@@ -483,24 +528,19 @@ bool writeWidget(PG_STATE* state, QUESTION* question, WJElement q, WJWriter w, c
if(child->type == WJR_TYPE_STRING)
{
char* str = WJEString(child, NULL, WJE_GET, NULL);
if(*str == '\\' || *str != '$')
WJEWriteDocument(child, w, child->name);
else if(!strncmp(str + 1, KEY_TEMPLATE ":", strlen(KEY_TEMPLATE)+1))
PROCESSEDSTRING proc = exercise_processString(question, str);
switch(proc.type)
{
uint32_t spid = strtoul(str + strlen(KEY_TEMPLATE) + 2, NULL, 0);
EXERTEMPLATE* temp = question->templates;
while(temp)
{
if(temp->id == spid)
{
WJWString(child->name, "", false, w);
WJWString(NULL,
expandTemplate(state, question, temp, w) ? "" : "ERROR", true, w);
break;
}
temp = temp->next;
}
case PSTYPE_STRING:
WJWString(child->name, proc.string, true, w);
break;
case PSTYPE_TEMPLATE:
WJWString(child->name, proc.class == PSCLASS_SVG ? "$svg:" : "", false, w);
mustache_generate(proc.template, writeWJW, w);
WJWString(NULL, "", true, w);
break;
default:
printf("Could not understand special property \"%s\"\n", str);
}
}
else if(child->type == WJR_TYPE_ARRAY || child->type == WJR_TYPE_OBJECT)
......@@ -515,7 +555,7 @@ bool writeWidget(PG_STATE* state, QUESTION* question, WJElement q, WJWriter w, c
return true;
}
bool exercise_writeWidgets(struct PG_State* state, QUESTION* question, WJWriter w)
bool exercise_writeJSONWidgets(struct PG_State* state, QUESTION* question, WJWriter w)
{
WJWOpenArray(KEYLIST(KEY_WIDGET), w);
......@@ -536,7 +576,7 @@ bool exercise_writeWidgets(struct PG_State* state, QUESTION* question, WJWriter
return true;
}
bool exercise_writeSolutions(struct PG_State* state, QUESTION* question, WJWriter w)
bool exercise_writeJSONSolutions(struct PG_State* state, QUESTION* question, WJWriter w)
{
WIDGET* wid = question->widgets;
WJWOpenArray(KEY_SOLUTION, w);
......@@ -558,9 +598,9 @@ bool exercise_writeSolutions(struct PG_State* state, QUESTION* question, WJWrite
bool widget_checkAnswer(WIDGET* w, char* ans, char* uans)
{
if(!ans || !uans) return false;
if(!strcmp(w->type, "textInput"))
if(!strcmp(w->type, WIDGET_TEXT_INPUT))
{
if(!WJEBool(w->options, "caps", WJE_GET, FALSE))
if(!WJEBool(w->options, KEY_TEXTINPUT_CAPS, WJE_GET, FALSE))
{
while (tolower(*ans) && (tolower(*ans) == tolower(*uans)))
ans++, uans++;
......@@ -568,18 +608,18 @@ bool widget_checkAnswer(WIDGET* w, char* ans, char* uans)
}
return !strcmp(ans, uans);
}
else if(!strcmp(w->type, "numberInput"))
else if(!strcmp(w->type, WIDGET_NUMBER_INPUT))
{
char* end;
double nuans = strtod(uans, &end);
if(*end) return false;
double nans = strtod( ans, &end);
if(*end) return false;
uint32_t mindp = WJEUInt32(w->options, "mindp", WJE_GET, FALSE);
uint32_t mindp = WJEUInt32(w->options, KEY_NUMBERINPUT_MINDP, WJE_GET, FALSE);
double exponent = pow(10, mindp);
return (round(nuans * exponent) == round(nans * exponent));
}
else if(!strcmp(w->type, "choiceInput"))
else if(!strcmp(w->type, WIDGET_CHOICE_INPUT))
{
// This has a specific format and length, just compare it.
return !strcmp(ans, uans);
......@@ -638,9 +678,7 @@ bool destroyQ_internal(QUESTION* quiz, bool freeWidgetFields)
while(temp)
{
EXERTEMPLATE* temp_next = temp->next;
WJECloseDocument(temp->subs);
if(freeWidgetFields)
free(temp->src);
WJECloseDocument(temp->builder.baseContext);
free(temp);
temp = temp_next;
}
......@@ -689,6 +727,7 @@ void exercise_cleanup(PG_STATE* state)
EXERCISE* exer_next = exer->next;
WJECloseDocument(exer->baseOptions);
WJECloseDocument(exer->staticStore);
free(exer->basepath);
free(exer->calculator);
free(exer->name);
free(exer->id);
......
......@@ -37,6 +37,13 @@ typedef enum StaticType {
#define STATTYPE_PURE_ "pure"
#define STATTYPE_STATIC_ "static"
#define WIDGET_TEXT_INPUT "textInput"
#define WIDGET_TEXT_VIEW "textView"
#define WIDGET_LINE_INPUT "lineInput"
#define WIDGET_NUMBER_INPUT "numberInput"
#define WIDGET_IMAGE_VIEW "imageView"
#define WIDGET_CHOICE_INPUT "choiceInput"
typedef struct Question
{
struct Exercise* eref;
......@@ -44,6 +51,7 @@ typedef struct Question
struct Widget* widget_last;
WJElement hints;
struct ExerTemplate* templates;
} QUESTION;
typedef struct Exercise
......@@ -61,6 +69,7 @@ typedef struct Exercise
STATICTYPE staticType;
char* id;
} EXERCISE;
typedef struct ExerciseRef
......@@ -85,8 +94,7 @@ typedef struct ExerTemplate
{
struct ExerTemplate* next;
uint32_t id;
char* src;
WJElement subs;
PMUS_BUILDER builder;
} EXERTEMPLATE;
typedef struct Widget
......@@ -141,14 +149,39 @@ typedef struct Quiz
uint16_t prime;
} QUIZ;
typedef enum ProcessedString_Type
{
PSTYPE_INVALID,
PSTYPE_TEMPLATE,
PSTYPE_STRING,
} PROCESSEDSTRING_TYPE;
typedef enum ProcessedString_Class
{
PSCLASS_NORMAL,
PSCLASS_SVG
} PROCESSEDSTRING_CLASS;
typedef struct ProcessedString
{
struct ProcessedString* child;
PROCESSEDSTRING_TYPE type;
PROCESSEDSTRING_CLASS class;
union {
PMUS_BUILDER* template;
char* string;
};
} PROCESSEDSTRING;
extern bool exercise_loadAll(struct PG_State* state);
extern char* exercise_loadOne(struct PG_State* state, char* set);
extern PROBLEMSET* exercise_getSet(struct PG_State* state, char* name);
extern GENMODE exercise_toGenMode(char* name);
extern EXERTEMPLATE* exercise_mkTemplate(QUESTION* q);
extern EXERTEMPLATE* exercise_mkTemplate(struct PG_State* state, QUESTION* q, char* name);
extern uint8_t exercise_checkAnswer(QUESTION* question, WJElement* solutions);
extern bool exercise_writeWidgets(struct PG_State* state, QUESTION* question, WJWriter w);
extern bool exercise_writeSolutions(struct PG_State* state, QUESTION* question, WJWriter w);
PROCESSEDSTRING exercise_processString(QUESTION* q, char* src);
extern bool exercise_writeJSONWidgets(struct PG_State* state, QUESTION* question, WJWriter w);
extern bool exercise_writeJSONSolutions(struct PG_State* state, QUESTION* question, WJWriter w);
extern void exercise_cleanup(struct PG_State* state);
extern QUIZ* exercise_mkQuiz(struct PG_State* state, PROBLEMSET* pset, GENMODE type, uint8_t numQuestions);
......
......@@ -36,6 +36,24 @@
#define KEY_TEMPLATE "template"
#define KEY_PATH "path"
#define KEY_SUBS "subs"
#define KEY_SVG "svg"
#define KEY_IMAGE "image"
// Widget keys
#define KEY_TEXTVIEW_TEXT "text"
#define KEY_TEXTVIEW_SHOWINSOLUTION "showInSolution"
#define KEY_TEXTINPUT_CAPS "caps"
#define KEY_TEXTINPUT_HINT "hint"
#define KEY_TEXTINPUT_LENGTH "length"
#define KEY_NUMBERINPUT_MINDP "mindp"
#define KEY_CHOICEINPUT_CHOICES "choices"
#define KEY_CHOICEINPUT_DISPLAY "display"
#define KEY_CHOICEINPUT_NUMSELECTIONS "numSelections"
#define KEY_IMAGEVIEW_SRC "src"
#define KEYLIST(k) k "s"
#define KEYCOUNT(k) k "Count"
......
This diff is collapsed.
/* Aeden McClain (c) 2019
* web: https://www.platypro.net
* email: dev@platypro.net
* License info at bottom.
*
* This file is a part of libpmustache.
*/
#ifndef INC_PDF_H
#define INC_PDF_H
#include <cairo/cairo.h>
#include <pango/pangocairo.h>
struct PG_State;
struct ProblemSet;
#define DEFAULT_PDF_MARGIN 12
#define DEFAULT_PDF_PAGE_WIDTH 210
#define DEFAULT_PDF_PAGE_HEIGHT 297
#define DEFAULT_PDF_HEADERS "Name,Student #;Block"
#define PDF_QNUM_PADDING 25
#define PDF_TITLE_HEIGHT 20
#define PDF_TEXT_HEIGHT 10
#define PDF_WPADDING_Y 5
#define PDF_WPADDING_X 50
#define PDF_QPADDING 18
#define PDF_FIELD_WIDTH 200
#define PDF_SHORT_FIELD_SIZE 100
#define PDF_SHORT_FIELD_PADDING 10
#define PDF_CHOICE_INPUT_PADDING 3
#define PDF_CHOICE_INPUT_INDSIZE 20
typedef enum SolutionType
{
SOLTYPE_NORMAL,
SOLTYPE_INPUT,
SOLTYPE_BREAK,
} SOLUTIONTYPE;
struct SolutionBuffer
{
struct SolutionBuffer* next;
SOLUTIONTYPE type;
uint16_t qNum;
// Solution data appended to end of struct
};
struct WidgetDrawCtx
{
cairo_surface_t* surface_base;
cairo_t* cairo_base;
cairo_surface_t* surface;
cairo_t* cairo;
cairo_matrix_t matrix;
PangoContext* textContext;
PangoLayout* textLayout;
PangoAttrList* attr_special;
PangoAttrList* attr_title;
struct SolutionBuffer** solution;
double hLeft; //! Handled by pdf_question_begin and pdf_question_end
uint16_t exerciseAt;
double w;
double h;
double m;
};
typedef struct PG_Pdf
{
char* headers;
double pageWidth;
double pageHeight;
double margin;
} PG_PDF;
extern void pdf_init(struct PG_State* state);
extern bool gen_pdf(struct PG_State* state, struct ProblemSet* pset);
#endif /* INC_PDF_H */
/* 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 <http://www.gnu.org/licenses/>.
*/
This diff is collapsed.
......@@ -13,10 +13,14 @@
#include "script.h"
#include "daemon/daemon.h"
#include "daemon/http.h"
#include "pdf.h"
typedef enum GenType
{
GTYPE_JSON
GTYPE_JSON,
#ifdef _QUIZGRINDER_BUILD_PDF
GTYPE_PDF
#endif
} GENTYPE;
......@@ -26,11 +30,13 @@ typedef struct PG_State
PG_SCRIPT script;
PG_DAEMON daemon;
PG_HTTP http;
PG_PDF pdf;
char* path_exercise;
char* path_pset;
char* gen_file;
char* gen_sfile;
char* gen_pset;
GENTYPE gen_type;
GENMODE gen_mode;
......
......@@ -13,6 +13,7 @@
#include <libgen.h>
#include <dirent.h>
#include <stdlib.h>
#include <pmustache/provider.wje.h>
#include "quizgrinder.h"
#include "pcg_basic.h"
......@@ -73,6 +74,12 @@ QUESTION* getActiveQuestion(lua_State* L)
return ((QUESTION*) lua_topointer(L,-1));
}
PG_STATE* getState(lua_State* L)
{
lua_getfield(L, LUA_REGISTRYINDEX, LUA_KEY_STATE);
return ((PG_STATE*) lua_topointer(L,-1));
}
EXERCISEREF* getActiveRef(lua_State* L)
{
lua_getfield(L, LUA_REGISTRYINDEX, LUA_KEY_EXERCISE);
......@@ -183,12 +190,14 @@ int mkTemplate(lua_State* L)
assert_argtype(L, 2, LUA_TTABLE);
QUESTION* q = getActiveQuestion(L);
EXERTEMPLATE* prop = exercise_mkTemplate(q);
prop->src = (char*) lua_tostring(L, 1);
prop->subs = luaTableToElement(L, NULL, 2, NULL);
PG_STATE* state = getState(L);
EXERTEMPLATE* prop = exercise_mkTemplate(state, q, (char*) lua_tostring(L, 1));
prop->builder.baseContext = luaTableToElement(L, NULL, 2, NULL);
prop->builder.escape = NULL;
prop->builder.provider = &WJEMustacheProvider;
char buffer[23];
snprintf(buffer, 23, "$template:%ul", prop->id);
snprintf(buffer, 23, "$template<%ul>", prop->id);
lua_pushstring(L, buffer);
return 1;
......@@ -217,7 +226,6 @@ int getStatic(lua_State* L)
int getStaticCount(lua_State* L)
{
assert_argnum(L, 1);
//assert_argtype(L, 1, LUA_TSTRING);
EXERCISEREF* ref = getActiveRef(L);
WJElement element = ref->exercise->staticStore;
......@@ -308,6 +316,9 @@ bool script_genQuiz(PG_STATE* scr, QUESTION* question, EXERCISEREF* exercise)
lua_pushlightuserdata(L, exercise);
lua_setfield(L, LUA_REGISTRYINDEX, LUA_KEY_EXERCISE);
lua_pushlightuserdata(L, scr);
lua_setfield(L, LUA_REGISTRYINDEX, LUA_KEY_STATE);
//Load function table and copy out the function
lua_getfield(L, LUA_REGISTRYINDEX, LUA_KEY_SCRIPTS);
lua_getfield(L, -1, exercise->exercise->id);
......
......@@ -17,6 +17,7 @@
#define LUA_KEY_QUESTION "qg_question"
#define LUA_KEY_EXERCISE "qg_exercise"
#define LUA_KEY_SCRIPTS "qg_scripts"
#define LUA_KEY_STATE "qg_state"
#define FUN_CREATEWIDGET "createWidget"
#define FUN_CREATEHINT "createHint"
......
......@@ -13,13 +13,10 @@ else
end
createWidget("textView", nil, {text = qtext})
createWidget("svgView", nil, {
src = mkTemplate("default.svg", thisElement)
})
createWidget("imageView", nil, {src = "$svg:" .. mkTemplate("default.svg", thisElement)})