diff options
author | mvglasow <michael@vonglasow.com> | 2015-11-11 16:30:20 +0100 |
---|---|---|
committer | mvglasow <michael@vonglasow.com> | 2015-11-11 16:30:20 +0100 |
commit | fc19b6bc98c5c8a453a8d53e4ecd687f06e6b465 (patch) | |
tree | ee60e599e4deedc8fec86bf855e2a5907003785d | |
parent | 9768ab8641c6ba59d036902617960a995c2f9784 (diff) | |
parent | 65ada954039573daf863e25ede19b714c8620858 (diff) | |
download | navit-fc19b6bc98c5c8a453a8d53e4ecd687f06e6b465.tar.gz |
Merge pull request #37 from mvglasow/trac1334R6347
Merge:Trac1334
-rw-r--r-- | navit/command.c | 259 | ||||
-rw-r--r-- | navit/osd.c | 16 |
2 files changed, 253 insertions, 22 deletions
diff --git a/navit/command.c b/navit/command.c index 812abbc90..7ebfda4f5 100644 --- a/navit/command.c +++ b/navit/command.c @@ -28,14 +28,26 @@ osd[@type=="xxx"].active=0;osd[@type=="yyy"].active=0 */ +/** + * The result, or interim result, of evaluating a saved command. + */ struct result { - struct attr attr; + struct attr attr; /**< The attribute. If {@code allocated} is true, it stores an object that was + * successfully retrieved. Else it is either a placeholder or a constant value. + */ double val; - const char *var; - int varlen; - const char *attrn; - int attrnlen; - int allocated; + const char *var; /**< If {@code allocated} is false, the name of the object to be resolved. + * Else, it is the name of the object successfully retrieved and stored in + * {@code attr}, or {@code NULL} if retrieval failed. + * Only the first {@code varlen} characters are significant. + */ + int varlen; /**< Number of significant characters in {@code var} */ + const char *attrn; /**< The name of an object that has been resolved but not yet retrieved, + * {@code NULL} otherwise. Only the first {@code attrnlen} characters are + * significant. + */ + int attrnlen; /**< Number of significant characters in {@code attrn} */ + int allocated; /**< Whether the result has been calculated */ }; struct result_list { @@ -50,11 +62,17 @@ struct context { struct result res; }; +/** + * Information about a callback function for a saved command. + */ struct command_saved_cb { - struct callback *cb; + struct callback *cb; /**< The callback function */ struct attr attr; }; +/** + * A saved command. + */ struct command_saved { struct context ctx; struct result res; @@ -63,8 +81,10 @@ struct command_saved { struct callback *idle_cb; struct callback *register_cb; /**< Callback to register all the callbacks **/ struct event_idle *register_ev; /**< Idle event to register all the callbacks **/ - struct attr context_attr; - int num_cbs; + struct attr context_attr; /**< The root of the object hierarchy, which will be assumed as + * the parent of all unqualified or partially qualified object + * references. **/ + int num_cbs; /**< Number of entries in {@code cbs} **/ struct command_saved_cb *cbs; /**< List of callbacks for this saved command **/ struct callback *cb; /**< Callback that should be called when we re-evaluate **/ int error; @@ -212,6 +232,21 @@ command_attr_type(struct result *res) return attr_from_name(attrn); } +/** + * @brief Retrieves an attribute from an object. + * + * This function will retrieve the first matching attribute by calling the {@code get_attr} method for + * the object type. If {@code object} does not refer to a valid object, or the {@code get_attr} method + * for the object type could not be defined, the function fails and zero is returned. + * + * @param ctx The context (ignored) + * @param object The object for which the attribute is to be retrieved. + * @param attr_type The type of attribute to retrieve + * @param ret Points to a {@code struct attr} to which the attribute will be copied + * + * @return True if a matching attribute was found, false if no matching attribute was found or an error + * occurred + */ static int command_object_get_attr(struct context *ctx, struct attr *object, enum attr_type attr_type, struct attr *ret) { @@ -245,12 +280,34 @@ command_object_remove_attr(struct context *ctx, struct attr *object, struct attr } +/** + * @brief Retrieves the current value of an attribute and stores it in {@code res}. + * + * If {@code ctx->skip} is true, the function aborts and no action is taken. + * + * Before calling this function, object references in {@code res} must be resolved. That is, + * {@code res->attr} holds a copy of {@code ctx->attr} and the first {@code res->attrnlen} characters of + * {@code res->attrn} correspond to the object name. + * + * After this function completes, {@code res->allocated} is true, and {@code res->attrn} and + * {@code res->attrnlen} are reset. + * + * If the attribute was successfully retrieved, the first {@code res->varlen} characters of + * {@code res->var} correspond to an object name and {@code res->attr} holds the attribute. + * + * If the attribute could not be retrieved, {@code res->attr.type} is set to {@code attr_none}, and + * {@code res->var} and {@code res->varlen} are reset. + * + * @param ctx The context + * @param res The result + */ static void command_get_attr(struct context *ctx, struct result *res) { int result; struct result tmp={{0,},}; enum attr_type attr_type=command_attr_type(res); + enum attr_type parent_type = res->attr.type; /* for debugging only */ if (ctx->skip) return; result=command_object_get_attr(ctx, &res->attr, attr_type, &tmp.attr); @@ -258,9 +315,11 @@ command_get_attr(struct context *ctx, struct result *res) *res=tmp; res->allocated=1; if (result) { + dbg(lvl_debug, "successfully retrieved '%s' from '%s'\n", attr_to_name(attr_type), attr_to_name(parent_type)); res->var=res->attrn; res->varlen=res->attrnlen; } else { + dbg(lvl_warning, "could not retrieve '%s' from '%s'\n", attr_to_name(attr_type), attr_to_name(parent_type)); result_free(res); res->attr.type=attr_none; res->var=NULL; @@ -296,6 +355,20 @@ command_set_attr(struct context *ctx, struct result *res, struct result *newres) *res=*newres; } +/** + * @brief Resolves an object reference. + * + * Prior to calling this function, {@code res} must contain a valid, unresolved object reference: + * {@code res->attr.type} must be {@code attr_none}, and the first {@code res->varlen} characters of + * {@code res->var} must correspond to an object name. + * + * After the function returns, {@code res->attr} holds a copy of {@code ctx->attr} and the first + * {@code res->attrnlen} characters of {@code res->attrn} correspond to the object name. + * {@code res->var} and {@code res->varlen} are reset. + * + * @param ctx The context + * @param res The result + */ static void resolve_object(struct context *ctx, struct result *res) { @@ -308,6 +381,24 @@ resolve_object(struct context *ctx, struct result *res) } } +/** + * @brief Resolves and retrieves an object and stores it in {@code res}. + * + * Prior to calling this function, {@code res} must contain a valid, unresolved object reference: + * {@code res->attr.type} must be {@code attr_none}, and the first {@code res->varlen} characters of + * {@code res->var} must correspond to an object name. + * + * If {@code ctx->skip} is true, the object reference will be resolved but the object will not be + * retrieved: the first {@code res->attrnlen} characters of {@code res->attrn} correspond to the object + * name after the function returns, while {@code res->var} and {@code res->varlen} are reset. + * + * If {@code ctx->skip} is false, {@code res->allocated} is true after this function completes. The + * object is stored in {@code res->attr} if it was successfully retrieved, otherwise {@code res->var} + * and {@code res->varlen} are reset. + * + * @param ctx The context + * @param res The result + */ static void resolve(struct context *ctx, struct result *res) { @@ -567,13 +658,42 @@ result_set(struct context *ctx, enum set_type set_type, const char *op, int len, ctx->error=internal; } +/** + * @brief Evaluates a value and stores its result. + * + * This function evaluates the first value in {@code ctx->expr}. A value can be either an object name + * (such as {@code vehicle.position_speed}) or a literal value. + * + * If evaluation is successful, the result is stored in {@code res->attr}. + * + * If an object name is encountered, the result has an attribute type of {@code attr_none} and the first + * {@code res->varlen} characters of {@code res->var} will point to the object name. + * + * If a literal value is encountered, the result's attribute type is set to the corresponding generic + * data type and its value is stored with the attribute. + * + * After this function returns, {@code ctx->expr} contains the rest of the expression string, which was + * not evaluated. Leading spaces before the value will be discarded with the value. + * + * If {@code ctx->expr}, after eliminating any leading whitespace, does not begin with a valid value, + * one of the following errors is stored in {@code ctx->error}: + * <ul> + * <li>{@code illegal_number_format} An illegal number format, such as a second decimal dot, was + * encountered.</li> + * <li>{@code missing_double_quote} A double quote without a matching second double quote was found.</li> + * <li>{@code eof_reached} The expression string is empty.</li> + * <li>{@code illegal_character} The expression string begins with a character which is illegal in a + * value. This may happen when the expression string begins with an operator.</li> + * </ul> + * + * @param ctx The context to evaluate + * @param res Points to a {@code struct res} in which the result will be stored + */ static void eval_value(struct context *ctx, struct result *res) { const char *op; int dots=0; - op=ctx->expr; - result_free(res); res->varlen=0; @@ -581,9 +701,11 @@ eval_value(struct context *ctx, struct result *res) { res->attrnlen=0; res->attrn=NULL; - while (g_ascii_isspace(*op)) { - op++; + while (g_ascii_isspace(*(ctx->expr))) { + ctx->expr++; } + op = ctx->expr; + if ((op[0] >= 'a' && op[0] <= 'z') || (op[0] >= 'A' && op[0] <= 'Z') || op[0] == '_') { const char *s=op; for (;;) { @@ -650,11 +772,37 @@ eval_value(struct context *ctx, struct result *res) { if (!*op) ctx->error=eof_reached; else { - dbg(lvl_error,"illegal character 0x%x\n",*op); + /* + * If we get here, ctx->expr does not begin with a variable or a literal value. This is not an + * error if this function is being called to test if an expression begins with a value. + */ + dbg(lvl_debug, "character 0x%x is illegal in a value\n",*op); ctx->error=illegal_character; } } +/** + * @brief Retrieves the next object reference from an expression. + * + * This function scans the expression string {@code ctx->expr} for the next object reference. Anything + * other than an object reference (whitespace characters, literal values, operators and even illegal + * characters) is discarded until either the end of the string is reached or an object reference is + * encountered. + * + * After this function completes successfully, {@code res->attr.type} is {@code attr_none} and the first + * {@code res->varlen} characters of {@code res->var} point to the object name. + * + * Object names retrieved by this function are unqualified, i.e. {@code vehicle.position_speed} will be + * retrieved as {@code vehicle} on the first call (return value 2) and {@code position_speed} on the + * second call (return value 1). + * + * @param ctx The context + * @param res Points to a {@code struct result} where the result will be stored. + * + * @return If a complete object name has been retrieved, the return value is 1. If a partial object name + * has been retrieved (e.g. {@code vehicle} from {@code vehicle.position_speed}), the return value is 2. + * If no object references were found, the return value is 0. + */ static int get_next_object(struct context *ctx, struct result *res) { @@ -1495,9 +1643,20 @@ command_saved_error (struct command_saved *cs) return cs->error; } +/** + * @brief Idle function to evaluate a command + * + * This function is called from an idle loop for asynchronous evaluation but may also be called in-line. + * + * The result of the evaluation can be retrieved from {@code cs->res} after this function returns. If an + * error occurred, it will be stored in {@code cs->error}. + * + * @param cs The command to evaluate + */ static void command_saved_evaluate_idle (struct command_saved *cs) { + dbg(lvl_debug, "enter: cs=%p, cs->command=%s\n", cs, cs->command); // Only run once at a time if (cs->idle_ev) { event_remove_idle(cs->idle_ev); @@ -1517,9 +1676,21 @@ command_saved_evaluate_idle (struct command_saved *cs) } } +/** + * @brief Evaluates a command + * + * This function examines {@code cs->async} to determine if the command should be evaluated immediately. + * If {@code cs->async} is true, an idle event is registered to register the command. Else the command + * is evaluated immediately and the result can be retrieved immediately after this function returns. + * + * See {@link command_saved_evaluate_idle(struct command_saved *)} for details. + * + * @param cs The command to evaluate + */ static void command_saved_evaluate(struct command_saved *cs) { + dbg(lvl_debug, "enter: cs=%p, cs->async=%d, cs->command=%s\n", cs, cs->async, cs->command); if (!cs->async) { command_saved_evaluate_idle(cs); return; @@ -1536,6 +1707,11 @@ command_saved_evaluate(struct command_saved *cs) cs->idle_ev = event_add_idle(100, cs->idle_cb); } +/** + * @brief Recreates all callbacks for a saved command + * + * @param cs The saved command + */ static void command_saved_callbacks_changed(struct command_saved *cs) { @@ -1544,6 +1720,8 @@ command_saved_callbacks_changed(struct command_saved *cs) struct object_func *func; struct attr attr; + dbg(lvl_debug, "enter: cs=%p, cs->async=%d, cs->command=%s\n", cs, cs->async, cs->command); + if (cs->register_ev) { event_remove_idle(cs->register_ev); cs->register_ev = NULL; @@ -1573,24 +1751,52 @@ command_saved_callbacks_changed(struct command_saved *cs) command_register_callbacks(cs); } +/** + * @brief Registers callbacks for a saved command + * + * This function registers callbacks for each attribute used in a saved command, causing the command to + * be re-evaluated whenever its value might change. + * + * This function will fail if an object used in the expression could not be resolved. This may happen + * during startup if this function is called before all objects have been created. In this case, the + * caller should schedule the function to be called again at a later time. + * + * It will also fail if an error is encountered. This can be determined by examining + * {@code cs->ctx.error} after the function returns. + * + * @param cs The command + * + * @return True if all callbacks were successfully registered, false if the function failed + */ static int command_register_callbacks(struct command_saved *cs) { - struct attr prev,cb_attr,attr; + struct attr prev; /* The parent of the next object which will be retrieved. */ + struct attr cb_attr; int status; struct object_func *func; struct callback *cb; + int tmpoffset; /* For debugging. Because we work with pointers into the same string instance. + * we can figure out offsets by using simple pointer arithmetics. + */ - attr = cs->context_attr; + dbg(lvl_debug, "enter: cs=%p, cs->async=%d, cs->command=%s\n", cs, cs->async, cs->command); cs->ctx.expr = cs->command; - cs->ctx.attr = &attr; prev = cs->context_attr; while ((status = get_next_object(&cs->ctx, &cs->res)) != 0) { + tmpoffset = cs->res.var - cs->command; + cs->ctx.attr = &prev; resolve(&cs->ctx, &cs->res); - if (cs->ctx.error || (cs->res.attr.type == attr_none)) { - // We could not resolve an object, perhaps because it has not been created + if (cs->ctx.error) { + /* An error occurred while parsing the command */ + tmpoffset = cs->ctx.expr - cs->command; + dbg(lvl_error, "parsing error: cs=%p, cs->ctx.error=%d\n\t%s\n\t%*s\n", cs, cs->ctx.error, cs->command, tmpoffset + 1, "^"); + return 0; + } else if (cs->res.attr.type == attr_none) { + /* We could not resolve an object, perhaps because it has not been created */ + dbg(lvl_error, "could not resolve object in cs=%p:\n\t%s\n\t%*s\n", cs, cs->command, tmpoffset + 1, "^"); return 0; } @@ -1600,7 +1806,6 @@ command_register_callbacks(struct command_saved *cs) if (func->add_attr) { if (status == 2) { // This is not the final attribute name cb = callback_new_attr_1(callback_cast(command_saved_callbacks_changed), cs->res.attr.type, (void*)cs); - attr = cs->res.attr; } else if (status == 1) { // This is the final attribute name cb = callback_new_attr_1(callback_cast(command_saved_evaluate), cs->res.attr.type, (void*)cs); cs->ctx.attr = &cs->context_attr; @@ -1619,7 +1824,7 @@ command_register_callbacks(struct command_saved *cs) func->add_attr(prev.u.data, &cb_attr); } else { - dbg(lvl_error, "Could not add callback because add_attr is missing for type %i}n", prev.type); + dbg(lvl_error, "Could not add callback because add_attr is missing for type %i\n", prev.type); } } @@ -1632,15 +1837,26 @@ command_register_callbacks(struct command_saved *cs) command_saved_evaluate_idle(cs); + dbg(lvl_debug, "done: cs=%p, cs->command=%s\n", cs, cs->command); return 1; } +/** + * @brief Creates a new saved command. + * + * @param command The command string + * @param attr The context attribute for the saved command + * @param cb The callback to call whenver the command is re-evaluated + * @param async Whether the saved command should be flagged as asynchronous, causing it to be evaluated + * in an idle callback + */ struct command_saved * command_saved_attr_new(char *command, struct attr *attr, struct callback *cb, int async) { struct command_saved *ret; ret = g_new0(struct command_saved, 1); + dbg(lvl_debug, "enter, ret=%p, command=%s\n", ret, command); ret->command = g_strdup(command); ret->context_attr = *attr; ret->cb = cb; @@ -1649,6 +1865,7 @@ command_saved_attr_new(char *command, struct attr *attr, struct callback *cb, in if (!command_register_callbacks(ret)) { // We try this as an idle call again + dbg(lvl_debug, "could not register callbacks, will retry as an idle call\n"); ret->register_cb = callback_new_1(callback_cast(command_saved_callbacks_changed), ret); ret->register_ev = event_add_idle(300, ret->register_cb); } diff --git a/navit/osd.c b/navit/osd.c index d5841b1ec..8bc0eb50c 100644 --- a/navit/osd.c +++ b/navit/osd.c @@ -237,11 +237,24 @@ osd_std_keypress(struct osd_item *item, struct navit *nav, char *key) osd_evaluate_command(item, nav); } +/** + * @brief Configures or unconfigures an OSD item. + * + * This method evaluates the result of the last execution of {@code cs}. If it evaluates to true, the + * item is configured, else it is unconfigured. (A configured item is displayed on the screen and can + * respond to user input, an unconfigured item is invisible and cannot receive user input.) + * + * If an error occurred during evaluation of {@code cs}, the item's configuration state is not changed. + * + * @param item The OSD item to reconfigure + * @param cs The command to evaluate + */ static void osd_std_reconfigure(struct osd_item *item, struct command_saved *cs) { char *err = NULL; /* Error description */ + dbg(lvl_debug, "enter, item=%p, cs=%p\n", item, cs); if (!command_saved_error(cs)) { item->configured = !! command_saved_get_int(cs); if (item->gr && !(item->flags & 16)) @@ -336,7 +349,7 @@ osd_std_config(struct osd_item *item, struct navit *navit) struct attr attr; char *err = NULL; /* Error description */ - dbg(lvl_debug,"enter\n"); + dbg(lvl_debug, "enter, item=%p, enable_cs=%p\n", item, item->enable_cs); if (item->enable_cs) { item->reconfig_cb = callback_new_1(callback_cast(osd_std_reconfigure), item); command_saved_set_cb(item->enable_cs, item->reconfig_cb); @@ -360,6 +373,7 @@ osd_std_config(struct osd_item *item, struct navit *navit) void osd_set_std_config(struct navit *nav, struct osd_item *item) { + dbg(lvl_debug, "enter, item=%p\n", item); item->cb = callback_new_attr_2(callback_cast(osd_std_config), attr_osd_configuration, item, nav); navit_add_callback(nav, item->cb); osd_std_config(item, nav); |