📄 options.c
字号:
/* Options variables manipulation core *//* $Id: options.c,v 1.468.2.4 2005/04/05 21:08:40 jonas Exp $ */#ifdef HAVE_CONFIG_H#include "config.h"#endif#include <ctype.h>#include <string.h>#include "elinks.h"#include "bfu/dialog.h"#include "cache/cache.h"#include "config/conf.h"#include "config/dialogs.h"#include "config/options.h"#include "config/opttypes.h"#include "dialogs/status.h"#include "document/options.h"#include "globhist/globhist.h"#include "intl/charsets.h"#include "intl/gettext/libintl.h"#include "lowlevel/select.h"#include "main.h" /* shrink_memory() */#include "sched/session.h"#include "terminal/color.h"#include "terminal/screen.h"#include "terminal/terminal.h"#include "util/color.h"#include "util/error.h"#include "util/memory.h"#include "util/string.h"#include "viewer/text/draw.h"/* TODO? In the past, covered by shadow and legends, remembered only by the * ELinks Elders now, options were in hashes (it was not for a long time, after * we started to use dynamic options lists and before we really started to use * hierarchic options). Hashes might be swift and deft, but they had a flaw and * the flaw showed up as the fatal flaw. They were unsorted, and it was * unfriendly to mere mortal users, without pasky's options handlers in their * brain, but their own poor-written software. And thus pasky went and rewrote * options so that they were in lists from then to now and for all the ages of * men, to the glory of mankind. However, one true hero may arise in future * fabulous and implement possibility to have both lists and hashes for trees, * as it may be useful for some supernatural entities. And when that age will * come... *//* TODO: We should remove special case for root options and use some auxiliary * (struct option *) instead. This applies to bookmarks, global history and * listbox items as well, though. --pasky */static INIT_LIST_HEAD(options_root_tree);static struct option options_root = INIT_OPTION( /* name: */ "", /* flags: */ 0, /* type: */ OPT_TREE, /* min, max: */ 0, 0, /* value: */ &options_root_tree, /* desc: */ "", /* capt: */ NULL);struct option *config_options;struct option *cmdline_options;static void add_opt_rec(struct option *, unsigned char *, struct option *);static void free_options_tree(struct list_head *, int recursive);#ifdef CONFIG_DEBUG/* Detect ending '.' (and some others) in options captions. * It will emit a message in debug mode only. --Zas */#define bad_punct(c) (c != ')' && !isquote(c) && ispunct(c))static voidcheck_caption(unsigned char *caption){ int len; unsigned char c; if (!caption) return; len = strlen(caption); if (!len) return; c = caption[len - 1]; if (isspace(c) || bad_punct(c)) DBG("bad char at end of caption [%s]", caption);#ifdef ENABLE_NLS caption = gettext(caption); len = strlen(caption); if (!len) return; c = caption[len - 1]; if (isspace(c) || bad_punct(c)) DBG("bad char at end of i18n caption [%s]", caption);#endif}#undef bad_punctstatic voidcheck_description(unsigned char *desc){ int len; unsigned char c; if (!desc) return; len = strlen(desc); if (!len) return; c = desc[len - 1]; if (isspace(c)) DBG("bad char at end of description [%s]", desc);#ifdef ENABLE_NLS desc = gettext(desc); len = strlen(desc); if (!len) return; if (ispunct(c) != ispunct(desc[len - 1])) DBG("punctuation char possibly missing at end of i18n description [%s]", desc); c = desc[len - 1]; if (isspace(c)) DBG("bad char at end of i18n description [%s]", desc);#endif}static voiddebug_check_option_syntax(struct option *option){ if (!option) return; check_caption(option->capt); check_description(option->desc);}#else#define debug_check_option_syntax(option)#endif/********************************************************************** Options interface**********************************************************************//* If option name contains dots, they are created as "categories" - first, * first category is retrieved from list, taken as a list, second category * is retrieved etc. *//* Ugly kludge */static int no_autocreate = 0;/* Get record of option of given name, or NULL if there's no such option. */struct option *get_opt_rec(struct option *tree, unsigned char *name_){ struct option *option; unsigned char *aname = stracpy(name_); unsigned char *name = aname; unsigned char *sep; if (!aname) return NULL; /* We iteratively call get_opt_rec() each for path_elements-1, getting * appropriate tree for it and then resolving [path_elements]. */ if ((sep = strrchr(name, '.'))) { *sep = '\0'; tree = get_opt_rec(tree, name); if (!tree || tree->type != OPT_TREE || tree->flags & OPT_HIDDEN) {#if 0 DBG("ERROR in get_opt_rec() crawl: %s (%d) -> %s", name, tree ? tree->type : -1, sep + 1);#endif mem_free(aname); return NULL; } *sep = '.'; name = sep + 1; } foreach (option, *tree->value.tree) { if (option->name && !strcmp(option->name, name)) { mem_free(aname); return option; } } if (tree && tree->flags & OPT_AUTOCREATE && !no_autocreate) { struct option *template = get_opt_rec(tree, "_template_"); assertm(template, "Requested %s should be autocreated but " "%.*s._template_ is missing!", name_, sep - name_, name_); if_assert_failed { mem_free(aname); return NULL; } /* We will just create the option and return pointer to it * automagically. And, we will create it by cloning _template_ * option. By having _template_ OPT_AUTOCREATE and _template_ * inside, you can have even multi-level autocreating. */ option = copy_option(template); if (!option) { mem_free(aname); return NULL; } mem_free_set(&option->name, stracpy(name)); add_opt_rec(tree, "", option); mem_free(aname); return option; } mem_free(aname); return NULL;}/* Get record of option of given name, or NULL if there's no such option. But * do not create the option if it doesn't exist and there's autocreation * enabled. */struct option *get_opt_rec_real(struct option *tree, unsigned char *name){ struct option *opt; no_autocreate = 1; opt = get_opt_rec(tree, name); no_autocreate = 0; return opt;}/* Fetch pointer to value of certain option. It is guaranteed to never return * NULL. Note that you are supposed to use wrapper get_opt(). */union option_value *get_opt_(#ifdef CONFIG_DEBUG unsigned char *file, int line, enum option_type option_type,#endif struct option *tree, unsigned char *name){ struct option *opt = get_opt_rec(tree, name);#ifdef CONFIG_DEBUG errfile = file; errline = line; if (!opt) elinks_internal("Attempted to fetch nonexisting option %s!", name); /* Various sanity checks. */ if (option_type != opt->type) DBG("get_opt_*(\"%s\") @ %s:%d: call with wrapper for %s for option of type %s", name, file, line, get_option_type_name(option_type), get_option_type_name(opt->type)); switch (opt->type) { case OPT_TREE: if (!opt->value.tree) elinks_internal("Option %s has no value!", name); break; case OPT_ALIAS: elinks_internal("Invalid use of alias %s for option %s!", name, opt->value.string); break; case OPT_STRING: if (!opt->value.string) elinks_internal("Option %s has no value!", name); break; case OPT_BOOL: case OPT_INT: case OPT_LONG: if (opt->value.number < opt->min || opt->value.number > opt->max) elinks_internal("Option %s has invalid value!", name); break; case OPT_COMMAND: if (!opt->value.command) elinks_internal("Option %s has no value!", name); break; case OPT_CODEPAGE: /* TODO: check these too. */ case OPT_LANGUAGE: case OPT_COLOR: break; }#endif return &opt->value;}static voidadd_opt_sort(struct option *tree, struct option *option, int abi){ struct list_head *cat = tree->value.tree; struct list_head *bcat = &tree->box_item->child; struct option *pos; /* The list is empty, just add it there. */ if (list_empty(*cat)) { add_to_list(*cat, option); if (abi) add_to_list(*bcat, option->box_item); /* This fits as the last list entry, add it there. This * optimizes the most expensive BUT most common case ;-). */ } else if ((option->type != OPT_TREE || ((struct option *) cat->prev)->type == OPT_TREE) && strcmp(((struct option *) cat->prev)->name, option->name) <= 0) {append: add_to_list_end(*cat, option); if (abi) add_to_list_end(*bcat, option->box_item); /* At the end of the list is tree and we are ordinary. That's * clear case then. */ } else if (option->type != OPT_TREE && ((struct option *) cat->prev)->type == OPT_TREE) { goto append; /* Scan the list linearly. This could be probably optimized ie. * to choose direction based on the first letter or so. */ } else { struct listbox_item *bpos = (struct listbox_item *) bcat; foreach (pos, *cat) { /* First move the box item to the current position but * only if the position has not been marked as deleted * and actually has a box_item -- else we will end up * 'overflowing' and causing assertion failure. */ if (!(pos->flags & OPT_DELETED) && pos->box_item) { bpos = bpos->next; assert(bpos != (struct listbox_item *) bcat); } if ((option->type != OPT_TREE || pos->type == OPT_TREE) && strcmp(pos->name, option->name) <= 0) continue; /* Ordinary options always sort behind trees. */ if (option->type != OPT_TREE && pos->type == OPT_TREE) continue; /* The (struct option) add_at_pos() can mess up the * order so that we add the box_item to itself, so * better do it first. */ /* Always ensure that them _template_ options are * before anything else so a lonely autocreated option * (with _template_ options set to invisible) will be * connected with an upper corner (ascii: `-) instead * of a rotated T (ascii: +-) when displaying it. */ if (option->type == pos->type && *option->name <= '_' && !strcmp(pos->name, "_template_")) { if (abi) add_at_pos(bpos, option->box_item); add_at_pos(pos, option); break; } if (abi) add_at_pos(bpos->prev, option->box_item); add_at_pos(pos->prev, option); break; } assert(pos != (struct option *) cat); assert(bpos != (struct listbox_item *) bcat); }}/* Add option to tree. */static voidadd_opt_rec(struct option *tree, unsigned char *path, struct option *option){ int abi = 0; assert(path && option && tree); if (*path) tree = get_opt_rec(tree, path); assertm(tree, "Missing option tree for '%s'", path); if (!tree->value.tree) return; object_nolock(option, "option"); if (option->box_item && option->name && !strcmp(option->name, "_template_")) option->box_item->visible = get_opt_bool("config.show_template"); if (tree->flags & OPT_AUTOCREATE && !option->desc) { struct option *template = get_opt_rec(tree, "_template_"); assert(template); option->desc = template->desc; } option->root = tree; abi = (tree->box_item && option->box_item); if (abi) { /* The config_root tree is a just a placeholder for the * box_items, it actually isn't a real box_item by itself; * these ghosts are indicated by the fact that they have * NULL @next. */ if (tree->box_item->next) { option->box_item->depth = tree->box_item->depth + 1; } } if (tree->flags & OPT_SORT) { add_opt_sort(tree, option, abi); } else { add_to_list_end(*tree->value.tree, option); if (abi) add_to_list_end(tree->box_item->child, option->box_item); } update_hierbox_browser(&option_browser);}static inline struct listbox_item *init_option_listbox_item(struct option *option){ struct listbox_item *box = mem_calloc(1, sizeof(*box)); if (!box) return NULL; init_list(box->child); box->visible = 1; box->udata = option; box->type = (option->type == OPT_TREE) ? BI_FOLDER : BI_LEAF; return box;}struct option *add_opt(struct option *tree, unsigned char *path, unsigned char *capt, unsigned char *name, enum option_flags flags, enum option_type type, int min, int max, void *value, unsigned char *desc){ struct option *option = mem_calloc(1, sizeof(*option)); if (!option) return NULL; option->name = stracpy(name); if (!option->name) { mem_free(option); return NULL; } option->flags = (flags | OPT_ALLOC); option->type = type; option->min = min; option->max = max; option->capt = capt; option->desc = desc; debug_check_option_syntax(option); if (option->type != OPT_ALIAS && ((tree->flags & OPT_LISTBOX) || (option->flags & OPT_LISTBOX))) { option->box_item = init_option_listbox_item(option); if (!option->box_item) { delete_option(option); return NULL; } } /* XXX: For allocated values we allocate in the add_opt_<type>() macro. * This involves OPT_TREE and OPT_STRING. */ switch (type) { case OPT_TREE: if (!value) { delete_option(option); return NULL; } option->value.tree = value; break; case OPT_STRING: if (!value) { delete_option(option); return NULL; } option->value.string = value; break; case OPT_ALIAS: option->value.string = value; break; case OPT_BOOL: case OPT_INT: case OPT_CODEPAGE: case OPT_LONG: option->value.number = (long) value; break; case OPT_COLOR: decode_color(value, strlen((unsigned char *) value), &option->value.color); break; case OPT_COMMAND: option->value.command = value; break; case OPT_LANGUAGE: break; } add_opt_rec(tree, path, option); return option;}/* The namespace may start to seem a bit chaotic here; it indeed is, maybe the * function names above should be renamed and only macros should keep their old * short names. *//* The simple rule I took as an apologize is that functions which take already * completely filled (struct option *) have long name and functions which take * only option specs have short name. */static voiddelete_option_do(struct option *option, int recursive){ if (option->next) del_from_list(option); if (recursive == -1) { ERROR("Orphaned option %s", option->name); } switch (option->type) { case OPT_STRING: mem_free_if(option->value.string); break; case OPT_TREE: if (!option->value.tree) break; if (!recursive && !list_empty(*option->value.tree)) { if (option->flags & OPT_AUTOCREATE) { recursive = 1; } else { ERROR("Orphaned unregistered " "option in subtree %s!", option->name); recursive = -1;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -