2005-09-15 09:58:31 -04:00
|
|
|
/* Options variables manipulation core */
|
|
|
|
|
|
|
|
#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"
|
2007-08-30 18:03:14 -04:00
|
|
|
#include "config/domain.h"
|
2005-09-15 09:58:31 -04:00
|
|
|
#include "config/options.h"
|
|
|
|
#include "config/opttypes.h"
|
|
|
|
#include "dialogs/status.h"
|
|
|
|
#include "document/document.h"
|
|
|
|
#include "globhist/globhist.h"
|
|
|
|
#include "intl/charsets.h"
|
|
|
|
#include "intl/gettext/libintl.h"
|
|
|
|
#include "main/main.h" /* shrink_memory() */
|
|
|
|
#include "main/select.h"
|
|
|
|
#include "network/connection.h"
|
|
|
|
#include "session/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 */
|
|
|
|
|
2007-07-26 15:39:08 -04:00
|
|
|
static INIT_LIST_OF(struct option, options_root_tree);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
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 *);
|
2007-07-26 15:39:08 -04:00
|
|
|
static void free_options_tree(LIST_OF(struct option) *, int recursive);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
#ifdef CONFIG_DEBUG
|
|
|
|
/* Detect ending '.' (and some others) in options captions.
|
|
|
|
* It will emit a message in debug mode only. --Zas */
|
|
|
|
|
2005-11-11 01:17:50 -05:00
|
|
|
#define bad_punct(c) (c != ')' && c != '>' && !isquote(c) && ispunct(c))
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
static void
|
|
|
|
check_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 CONFIG_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_punct
|
|
|
|
|
|
|
|
static void
|
|
|
|
check_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 CONFIG_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
|
|
|
|
}
|
|
|
|
|
2009-08-15 21:08:59 -04:00
|
|
|
/*! @relates option */
|
2005-09-15 09:58:31 -04:00
|
|
|
static void
|
|
|
|
debug_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;
|
|
|
|
|
2008-02-02 08:59:32 -05:00
|
|
|
/** Get record of option of given name, or NULL if there's no such option.
|
|
|
|
*
|
|
|
|
* If the specified option is an ::OPT_ALIAS, this function returns the
|
|
|
|
* alias, rather than the option to which the alias refers. It must
|
|
|
|
* work this way because the alias may have the ::OPT_ALIAS_NEGATE flag.
|
|
|
|
* Instead, if the caller tries to read or set the value of the alias,
|
|
|
|
* the functions associated with ::OPT_ALIAS will forward the operation
|
2009-08-15 21:08:59 -04:00
|
|
|
* to the underlying option. However, see indirect_option().
|
|
|
|
*
|
|
|
|
* @relates option */
|
2005-09-15 09:58:31 -04:00
|
|
|
struct option *
|
2007-01-28 07:01:24 -05:00
|
|
|
get_opt_rec(struct option *tree, const unsigned char *name_)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
|
|
|
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]. */
|
2016-04-20 15:03:27 -04:00
|
|
|
if ((sep = strrchr((const char *)name, '.'))) {
|
2005-09-15 09:58:31 -04:00
|
|
|
*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) {
|
2016-04-20 12:05:00 -04:00
|
|
|
struct option *template_ = get_opt_rec(tree, "_template_");
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2016-04-20 12:05:00 -04:00
|
|
|
assertm(template_ != NULL, "Requested %s should be autocreated but "
|
2005-09-15 09:58:31 -04:00
|
|
|
"%.*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. */
|
|
|
|
|
2016-04-20 12:05:00 -04:00
|
|
|
option = copy_option(template_, 0);
|
2005-09-15 09:58:31 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2009-08-15 21:08:59 -04:00
|
|
|
/** Get record of option of given name, or NULL if there's no such option. But
|
2005-09-15 09:58:31 -04:00
|
|
|
* do not create the option if it doesn't exist and there's autocreation
|
2009-08-15 21:08:59 -04:00
|
|
|
* enabled.
|
|
|
|
* @relates option */
|
2005-09-15 09:58:31 -04:00
|
|
|
struct option *
|
2007-01-28 07:01:24 -05:00
|
|
|
get_opt_rec_real(struct option *tree, const unsigned char *name)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
|
|
|
struct option *opt;
|
|
|
|
|
|
|
|
no_autocreate = 1;
|
|
|
|
opt = get_opt_rec(tree, name);
|
|
|
|
no_autocreate = 0;
|
|
|
|
return opt;
|
|
|
|
}
|
|
|
|
|
2008-02-03 06:57:13 -05:00
|
|
|
/** If @a opt is an alias, return the option to which it refers.
|
|
|
|
*
|
|
|
|
* @warning Because the alias may have the ::OPT_ALIAS_NEGATE flag,
|
|
|
|
* the caller must not access the value of the returned option as if
|
|
|
|
* it were also the value of the alias. However, it is safe to access
|
2009-08-15 21:08:59 -04:00
|
|
|
* flags such as ::OPT_MUST_SAVE and ::OPT_DELETED.
|
|
|
|
*
|
|
|
|
* @relates option */
|
2008-02-03 06:57:13 -05:00
|
|
|
struct option *
|
|
|
|
indirect_option(struct option *alias)
|
|
|
|
{
|
|
|
|
struct option *real;
|
|
|
|
|
|
|
|
if (alias->type != OPT_ALIAS) return alias; /* not an error */
|
|
|
|
|
|
|
|
real = get_opt_rec(config_options, alias->value.string);
|
|
|
|
assertm(real != NULL, "%s aliased to unknown option %s!",
|
|
|
|
alias->name, alias->value.string);
|
|
|
|
if_assert_failed return alias;
|
|
|
|
|
|
|
|
return real;
|
|
|
|
}
|
|
|
|
|
2009-08-15 21:08:59 -04:00
|
|
|
/** Fetch pointer to value of certain option. It is guaranteed to never return
|
|
|
|
* NULL. Note that you are supposed to use wrapper get_opt().
|
|
|
|
* @relates option */
|
2005-09-15 09:58:31 -04:00
|
|
|
union option_value *
|
|
|
|
get_opt_(
|
|
|
|
#ifdef CONFIG_DEBUG
|
|
|
|
unsigned char *file, int line, enum option_type option_type,
|
|
|
|
#endif
|
2007-08-28 12:41:18 -04:00
|
|
|
struct option *tree, unsigned char *name, struct session *ses)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
2007-08-28 12:41:18 -04:00
|
|
|
struct option *opt = NULL;
|
|
|
|
|
2007-08-28 13:29:41 -04:00
|
|
|
/* If given a session and the option is shadowed in that session's
|
|
|
|
* options tree, return the shadow. */
|
|
|
|
if (ses && ses->option)
|
|
|
|
opt = get_opt_rec_real(ses->option, name);
|
2007-08-28 12:41:18 -04:00
|
|
|
|
2007-08-31 05:48:58 -04:00
|
|
|
/* If given a session, no session-specific option was found, and the
|
|
|
|
* option has a shadow in the domain tree that matches the current
|
|
|
|
* document in that session, return that shadow. */
|
|
|
|
if (!opt && ses)
|
2007-08-30 17:50:55 -04:00
|
|
|
opt = get_domain_option_from_session(name, ses);
|
2007-08-28 12:41:18 -04:00
|
|
|
|
|
|
|
/* Else, return the real option. */
|
|
|
|
if (!opt)
|
|
|
|
opt = get_opt_rec(tree, name);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
#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:
|
|
|
|
if (opt->value.number < opt->min
|
|
|
|
|| opt->value.number > opt->max)
|
2005-10-25 13:10:30 -04:00
|
|
|
elinks_internal("Option %s has invalid value %d!", name, opt->value.number);
|
2005-09-15 09:58:31 -04:00
|
|
|
break;
|
2006-06-26 11:49:59 -04:00
|
|
|
case OPT_LONG:
|
|
|
|
if (opt->value.big_number < opt->min
|
|
|
|
|| opt->value.big_number > opt->max)
|
|
|
|
elinks_internal("Option %s has invalid value %ld!", name, opt->value.big_number);
|
|
|
|
break;
|
2005-09-15 09:58:31 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2009-08-15 21:08:59 -04:00
|
|
|
/*! @relates option */
|
2005-09-15 09:58:31 -04:00
|
|
|
static void
|
|
|
|
add_opt_sort(struct option *tree, struct option *option, int abi)
|
|
|
|
{
|
2007-07-26 15:39:08 -04:00
|
|
|
LIST_OF(struct option) *cat = tree->value.tree;
|
|
|
|
LIST_OF(struct listbox_item) *bcat = &tree->box_item->child;
|
2005-09-15 09:58:31 -04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-15 21:08:59 -04:00
|
|
|
/** Add option to tree.
|
|
|
|
* @relates option */
|
2005-09-15 09:58:31 -04:00
|
|
|
static void
|
|
|
|
add_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);
|
|
|
|
|
2007-03-11 06:22:02 -04:00
|
|
|
assertm(tree != NULL, "Missing option tree for '%s'", path);
|
2005-09-15 09:58:31 -04:00
|
|
|
if (!tree->value.tree) return;
|
|
|
|
|
|
|
|
object_nolock(option, "option");
|
|
|
|
|
|
|
|
if (option->box_item && option->name && !strcmp(option->name, "_template_"))
|
2007-08-28 12:41:18 -04:00
|
|
|
option->box_item->visible = get_opt_bool("config.show_template", NULL);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
if (tree->flags & OPT_AUTOCREATE && !option->desc) {
|
2016-04-20 12:05:00 -04:00
|
|
|
struct option *template_ = get_opt_rec(tree, "_template_");
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2016-04-20 12:05:00 -04:00
|
|
|
assert(template_);
|
|
|
|
option->desc = template_->desc;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2009-08-15 21:08:59 -04:00
|
|
|
/*! @relates option */
|
2005-09-15 09:58:31 -04:00
|
|
|
static inline struct listbox_item *
|
|
|
|
init_option_listbox_item(struct option *option)
|
|
|
|
{
|
|
|
|
struct listbox_item *item = mem_calloc(1, sizeof(*item));
|
|
|
|
|
|
|
|
if (!item) return NULL;
|
|
|
|
|
|
|
|
init_list(item->child);
|
|
|
|
item->visible = 1;
|
|
|
|
item->udata = option;
|
|
|
|
item->type = (option->type == OPT_TREE) ? BI_FOLDER : BI_LEAF;
|
|
|
|
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
2009-08-15 21:08:59 -04:00
|
|
|
/*! @relates option */
|
2005-09-15 09:58:31 -04:00
|
|
|
struct option *
|
|
|
|
add_opt(struct option *tree, unsigned char *path, unsigned char *capt,
|
|
|
|
unsigned char *name, enum option_flags flags, enum option_type type,
|
2006-07-05 09:11:53 -04:00
|
|
|
long min, long max, longptr_T value, unsigned char *desc)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
|
|
|
|
/* 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) {
|
2006-06-26 11:33:00 -04:00
|
|
|
mem_free(option);
|
2005-09-15 09:58:31 -04:00
|
|
|
return NULL;
|
|
|
|
}
|
2007-07-26 15:39:08 -04:00
|
|
|
option->value.tree = (LIST_OF(struct option) *) value;
|
2005-09-15 09:58:31 -04:00
|
|
|
break;
|
|
|
|
case OPT_STRING:
|
|
|
|
if (!value) {
|
2006-06-26 11:33:00 -04:00
|
|
|
mem_free(option);
|
2005-09-15 09:58:31 -04:00
|
|
|
return NULL;
|
|
|
|
}
|
2006-07-05 09:11:53 -04:00
|
|
|
option->value.string = (unsigned char *) value;
|
2005-09-15 09:58:31 -04:00
|
|
|
break;
|
|
|
|
case OPT_ALIAS:
|
2006-07-05 09:11:53 -04:00
|
|
|
option->value.string = (unsigned char *) value;
|
2005-09-15 09:58:31 -04:00
|
|
|
break;
|
|
|
|
case OPT_BOOL:
|
|
|
|
case OPT_INT:
|
|
|
|
case OPT_CODEPAGE:
|
2006-06-26 11:37:10 -04:00
|
|
|
option->value.number = (int) value;
|
|
|
|
break;
|
2005-09-15 09:58:31 -04:00
|
|
|
case OPT_LONG:
|
2006-06-26 11:49:59 -04:00
|
|
|
option->value.big_number = (long) value; /* FIXME: cast from void * */
|
2005-09-15 09:58:31 -04:00
|
|
|
break;
|
|
|
|
case OPT_COLOR:
|
2006-07-05 09:11:53 -04:00
|
|
|
decode_color((unsigned char *) value, strlen((unsigned char *) value),
|
2005-09-15 09:58:31 -04:00
|
|
|
&option->value.color);
|
|
|
|
break;
|
|
|
|
case OPT_COMMAND:
|
2006-07-05 09:11:53 -04:00
|
|
|
option->value.command = (void *) value;
|
2005-09-15 09:58:31 -04:00
|
|
|
break;
|
|
|
|
case OPT_LANGUAGE:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2006-06-26 11:33:00 -04:00
|
|
|
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) {
|
|
|
|
mem_free(option);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
2006-07-27 03:51:10 -04:00
|
|
|
|
2005-09-15 09:58:31 -04:00
|
|
|
add_opt_rec(tree, path, option);
|
|
|
|
return option;
|
|
|
|
}
|
|
|
|
|
2009-08-15 21:08:59 -04:00
|
|
|
/*! @relates option */
|
2005-09-15 09:58:31 -04:00
|
|
|
static void
|
|
|
|
done_option(struct option *option)
|
|
|
|
{
|
|
|
|
switch (option->type) {
|
|
|
|
case OPT_STRING:
|
|
|
|
mem_free_if(option->value.string);
|
|
|
|
break;
|
|
|
|
case OPT_TREE:
|
|
|
|
mem_free_if(option->value.tree);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (option->box_item)
|
|
|
|
done_listbox_item(&option_browser, option->box_item);
|
|
|
|
|
|
|
|
if (option->flags & OPT_ALLOC) {
|
|
|
|
mem_free_if(option->name);
|
|
|
|
mem_free(option);
|
|
|
|
} else if (!option->capt) {
|
|
|
|
/* We are probably dealing with a built-in autocreated option
|
|
|
|
* that will be attempted to be deleted when shutting down. */
|
|
|
|
/* Clear it so nothing will be done later. */
|
|
|
|
memset(option, 0, sizeof(*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. */
|
|
|
|
|
2009-08-15 21:08:59 -04:00
|
|
|
/*! @relates option */
|
2005-09-15 09:58:31 -04:00
|
|
|
static void
|
|
|
|
delete_option_do(struct option *option, int recursive)
|
|
|
|
{
|
|
|
|
if (option->next) {
|
|
|
|
del_from_list(option);
|
|
|
|
option->prev = option->next = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (recursive == -1) {
|
|
|
|
ERROR("Orphaned option %s", option->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (option->type == OPT_TREE && option->value.tree
|
|
|
|
&& !list_empty(*option->value.tree)) {
|
|
|
|
if (!recursive) {
|
|
|
|
if (option->flags & OPT_AUTOCREATE) {
|
|
|
|
recursive = 1;
|
|
|
|
} else {
|
|
|
|
ERROR("Orphaned unregistered "
|
|
|
|
"option in subtree %s!",
|
|
|
|
option->name);
|
|
|
|
recursive = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
free_options_tree(option->value.tree, recursive);
|
|
|
|
}
|
|
|
|
|
|
|
|
done_option(option);
|
|
|
|
}
|
|
|
|
|
2009-08-15 21:08:59 -04:00
|
|
|
/*! @relates option */
|
2005-09-15 09:58:31 -04:00
|
|
|
void
|
|
|
|
mark_option_as_deleted(struct option *option)
|
|
|
|
{
|
|
|
|
if (option->type == OPT_TREE) {
|
|
|
|
struct option *unmarked;
|
|
|
|
|
|
|
|
assert(option->value.tree);
|
|
|
|
|
|
|
|
foreach (unmarked, *option->value.tree)
|
|
|
|
mark_option_as_deleted(unmarked);
|
|
|
|
}
|
|
|
|
|
|
|
|
option->box_item->visible = 0;
|
|
|
|
|
|
|
|
option->flags |= (OPT_TOUCHED | OPT_DELETED);
|
|
|
|
}
|
|
|
|
|
2009-08-15 21:08:59 -04:00
|
|
|
/*! @relates option */
|
2005-09-15 09:58:31 -04:00
|
|
|
void
|
|
|
|
delete_option(struct option *option)
|
|
|
|
{
|
|
|
|
delete_option_do(option, 1);
|
|
|
|
}
|
|
|
|
|
2009-08-15 21:08:59 -04:00
|
|
|
/*! @relates option */
|
2005-09-15 09:58:31 -04:00
|
|
|
struct option *
|
2016-04-20 12:05:00 -04:00
|
|
|
copy_option(struct option *template_, int flags)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
|
|
|
struct option *option = mem_calloc(1, sizeof(*option));
|
|
|
|
|
|
|
|
if (!option) return NULL;
|
|
|
|
|
2016-04-20 12:05:00 -04:00
|
|
|
option->name = null_or_stracpy(template_->name);
|
|
|
|
option->flags = (template_->flags | OPT_ALLOC);
|
|
|
|
option->type = template_->type;
|
|
|
|
option->min = template_->min;
|
|
|
|
option->max = template_->max;
|
|
|
|
option->capt = template_->capt;
|
|
|
|
option->desc = template_->desc;
|
|
|
|
option->change_hook = template_->change_hook;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2007-08-28 13:11:52 -04:00
|
|
|
if (!(flags & CO_NO_LISTBOX_ITEM))
|
|
|
|
option->box_item = init_option_listbox_item(option);
|
2005-09-15 09:58:31 -04:00
|
|
|
if (option->box_item) {
|
2016-04-20 12:05:00 -04:00
|
|
|
if (template_->box_item) {
|
|
|
|
option->box_item->type = template_->box_item->type;
|
|
|
|
option->box_item->depth = template_->box_item->depth;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-20 12:05:00 -04:00
|
|
|
if (option_types[template_->type].dup) {
|
|
|
|
option_types[template_->type].dup(option, template_, flags);
|
2005-09-15 09:58:31 -04:00
|
|
|
} else {
|
2016-04-20 12:05:00 -04:00
|
|
|
option->value = template_->value;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return option;
|
|
|
|
}
|
|
|
|
|
2009-08-15 21:08:59 -04:00
|
|
|
/** Return the shadow option in @a shadow_tree of @a option in @a tree.
|
|
|
|
* If @a option isn't yet shadowed in @a shadow_tree, shadow it
|
|
|
|
* (i.e. create a copy in @a shadow_tree) along with any ancestors
|
|
|
|
* that aren't shadowed. */
|
2007-08-28 13:11:52 -04:00
|
|
|
struct option *
|
|
|
|
get_option_shadow(struct option *option, struct option *tree,
|
|
|
|
struct option *shadow_tree)
|
|
|
|
{
|
|
|
|
|
|
|
|
struct option *shadow_option = NULL;
|
|
|
|
|
|
|
|
assert(option);
|
|
|
|
assert(tree);
|
|
|
|
assert(shadow_tree);
|
|
|
|
|
|
|
|
if (option == tree) {
|
|
|
|
shadow_option = shadow_tree;
|
|
|
|
} else if (option->root && option->name) {
|
|
|
|
struct option *shadow_root;
|
|
|
|
|
|
|
|
shadow_root = get_option_shadow(option->root, tree,
|
|
|
|
shadow_tree);
|
|
|
|
if (!shadow_root) return NULL;
|
|
|
|
|
|
|
|
shadow_option = get_opt_rec_real(shadow_root, option->name);
|
|
|
|
if (!shadow_option) {
|
|
|
|
shadow_option = copy_option(option,
|
|
|
|
CO_SHALLOW
|
|
|
|
| CO_NO_LISTBOX_ITEM);
|
|
|
|
if (shadow_option) {
|
|
|
|
shadow_option->root = shadow_root;
|
|
|
|
/* No need to sort, is there? It isn't shown
|
|
|
|
* in the options manager. -- Miciah */
|
|
|
|
add_to_list_end(*shadow_root->value.tree,
|
|
|
|
shadow_option);
|
2007-08-29 09:34:43 -04:00
|
|
|
|
|
|
|
shadow_option->flags |= OPT_TOUCHED;
|
2007-08-28 13:11:52 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return shadow_option;
|
|
|
|
}
|
|
|
|
|
2007-08-30 16:29:40 -04:00
|
|
|
|
2009-08-15 21:08:59 -04:00
|
|
|
/*! @relates option */
|
2007-07-26 15:39:08 -04:00
|
|
|
LIST_OF(struct option) *
|
2005-09-15 09:58:31 -04:00
|
|
|
init_options_tree(void)
|
|
|
|
{
|
2007-07-26 15:39:08 -04:00
|
|
|
LIST_OF(struct option) *ptr = mem_alloc(sizeof(*ptr));
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
if (ptr) init_list(*ptr);
|
|
|
|
return ptr;
|
|
|
|
}
|
|
|
|
|
2009-08-15 21:08:59 -04:00
|
|
|
/** Some default pre-autocreated options. Doh. */
|
2005-09-15 09:58:31 -04:00
|
|
|
static inline void
|
|
|
|
register_autocreated_options(void)
|
|
|
|
{
|
|
|
|
/* TODO: Use table-driven initialization. --jonas */
|
2009-04-26 10:16:00 -04:00
|
|
|
get_opt_int("terminal.linux.type", NULL) = TERM_LINUX;
|
|
|
|
get_opt_int("terminal.linux.colors", NULL) = COLOR_MODE_16;
|
2007-08-28 12:41:18 -04:00
|
|
|
get_opt_bool("terminal.linux.m11_hack", NULL) = 1;
|
2009-04-26 10:16:00 -04:00
|
|
|
get_opt_int("terminal.vt100.type", NULL) = TERM_VT100;
|
|
|
|
get_opt_int("terminal.vt110.type", NULL) = TERM_VT100;
|
|
|
|
get_opt_int("terminal.xterm.type", NULL) = TERM_VT100;
|
2007-08-28 12:41:18 -04:00
|
|
|
get_opt_bool("terminal.xterm.underline", NULL) = 1;
|
2009-04-26 10:16:00 -04:00
|
|
|
get_opt_int("terminal.xterm-color.type", NULL) = TERM_VT100;
|
2007-08-28 12:41:18 -04:00
|
|
|
get_opt_int("terminal.xterm-color.colors", NULL) = COLOR_MODE_16;
|
|
|
|
get_opt_bool("terminal.xterm-color.underline", NULL) = 1;
|
2005-09-15 09:58:31 -04:00
|
|
|
#ifdef CONFIG_88_COLORS
|
2009-04-26 10:16:00 -04:00
|
|
|
get_opt_int("terminal.xterm-88color.type", NULL) = TERM_VT100;
|
2007-08-28 12:41:18 -04:00
|
|
|
get_opt_int("terminal.xterm-88color.colors", NULL) = COLOR_MODE_88;
|
|
|
|
get_opt_bool("terminal.xterm-88color.underline", NULL) = 1;
|
2005-09-15 09:58:31 -04:00
|
|
|
#endif
|
2009-01-01 14:59:57 -05:00
|
|
|
get_opt_int("terminal.rxvt-unicode.type", NULL) = 1;
|
|
|
|
#ifdef CONFIG_88_COLORS
|
|
|
|
get_opt_int("terminal.rxvt-unicode.colors", NULL) = COLOR_MODE_88;
|
|
|
|
#else
|
|
|
|
get_opt_int("terminal.rxvt-unicode.colors", NULL) = COLOR_MODE_16;
|
|
|
|
#endif
|
|
|
|
get_opt_bool("terminal.rxvt-unicode.italic", NULL) = 1;
|
|
|
|
get_opt_bool("terminal.rxvt-unicode.underline", NULL) = 1;
|
2005-09-15 09:58:31 -04:00
|
|
|
#ifdef CONFIG_256_COLORS
|
2009-04-26 10:16:00 -04:00
|
|
|
get_opt_int("terminal.xterm-256color.type", NULL) = TERM_VT100;
|
2007-08-28 12:41:18 -04:00
|
|
|
get_opt_int("terminal.xterm-256color.colors", NULL) = COLOR_MODE_256;
|
|
|
|
get_opt_bool("terminal.xterm-256color.underline", NULL) = 1;
|
2009-03-27 14:44:46 -04:00
|
|
|
get_opt_int("terminal.fbterm.type", NULL) = TERM_FBTERM;
|
|
|
|
get_opt_int("terminal.fbterm.colors", NULL) = COLOR_MODE_256;
|
|
|
|
get_opt_bool("terminal.fbterm.underline", NULL) = 0;
|
2005-09-15 09:58:31 -04:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
bug 764: Initialize the right member of union option_value
INIT_OPTION used to initialize union option_value at compile time by
casting the default value to LIST_OF(struct option) *, which is the
type of the first member. On sparc64 and other big-endian systems
where sizeof(int) < sizeof(struct list_head *), this tended to leave
option->value.number as zero, thus messing up OPT_INT and OPT_BOOL
at least. OPT_LONG however tended to work right.
This would be easy to fix with C99 designated initializers,
but doc/hacking.txt says ELinks must be kept C89 compatible.
Another solution would be to make register_options() read the
value from option->value.tree (the first member), cast it back
to the right type, and write it to the appropriate member;
but that would still require somewhat dubious conversions
between integers, data pointers, and function pointers.
So here's a rather more invasive solution. Add struct option_init,
which is somewhat similar to struct option but has non-overlapping
members for different types of values, to ensure nothing is lost
in compile-time conversions. Move unsigned char *path from struct
option_info to struct option_init, and replace struct option_info
with a union that contains struct option_init and struct option.
Now, this union can be initialized with no portability problems,
and register_options() then moves the values from struct option_init
to their final places in struct option.
In my x86 ELinks build with plenty of options configured in, this
change bloated the text section by 340 bytes but compressed the data
section by 2784 bytes, presumably because union option_info is a
pointer smaller than struct option_info was.
(cherry picked from elinks-0.12 commit e5f6592ee20780a61f70feeb1f9e17631b9c5835)
Conflicts:
src/protocol/fsp/fsp.c: All options had been removed in 0.13.GIT.
src/protocol/smb/smb2.c: Ditto.
2009-08-15 15:39:07 -04:00
|
|
|
extern union option_info cmdline_options_info[];
|
2016-04-20 15:23:55 -04:00
|
|
|
|
|
|
|
#include "config/options.inc"
|
|
|
|
|
|
|
|
static int
|
|
|
|
change_hook_cache(struct session *ses, struct option *current, struct option *changed)
|
|
|
|
{
|
|
|
|
shrink_memory(0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
change_hook_connection(struct session *ses, struct option *current, struct option *changed)
|
|
|
|
{
|
|
|
|
register_check_queue();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
change_hook_html(struct session *ses, struct option *current, struct option *changed)
|
|
|
|
{
|
|
|
|
foreach (ses, sessions) ses->tab->resize = 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
change_hook_insert_mode(struct session *ses, struct option *current, struct option *changed)
|
|
|
|
{
|
|
|
|
update_status();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
change_hook_active_link(struct session *ses, struct option *current, struct option *changed)
|
|
|
|
{
|
|
|
|
update_cached_document_options(ses);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
change_hook_terminal(struct session *ses, struct option *current, struct option *changed)
|
|
|
|
{
|
|
|
|
cls_redraw_all_terminals();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
change_hook_ui(struct session *ses, struct option *current, struct option *changed)
|
|
|
|
{
|
|
|
|
update_status();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Make option templates visible or invisible in the option manager.
|
|
|
|
* This is called once on startup, and then each time the value of the
|
|
|
|
* "config.show_template" option is changed.
|
|
|
|
*
|
|
|
|
* @param tree
|
|
|
|
* The option tree whose children should be affected.
|
|
|
|
*
|
|
|
|
* @param show
|
|
|
|
* A set of bits:
|
|
|
|
* - The 0x01 bit means templates should be made visible.
|
|
|
|
* If the bit is clear, templates become invisible instead.
|
|
|
|
* - The 0x02 bit means @a tree is itself part of a template,
|
|
|
|
* and so all of its children should be affected, regardless
|
|
|
|
* of whether they are templates of their own.
|
|
|
|
*
|
|
|
|
* Deleted options are never visible.
|
|
|
|
*
|
|
|
|
* @relates option */
|
|
|
|
static void
|
|
|
|
update_visibility(LIST_OF(struct option) *tree, int show)
|
|
|
|
{
|
|
|
|
struct option *opt;
|
|
|
|
|
|
|
|
foreach (opt, *tree) {
|
|
|
|
if (opt->flags & OPT_DELETED) continue;
|
|
|
|
|
|
|
|
if (!strcmp(opt->name, "_template_")) {
|
|
|
|
if (opt->box_item)
|
|
|
|
opt->box_item->visible = (show & 1);
|
|
|
|
|
|
|
|
if (opt->type == OPT_TREE)
|
|
|
|
update_visibility(opt->value.tree, show | 2);
|
|
|
|
} else {
|
|
|
|
if (opt->box_item && (show & 2))
|
|
|
|
opt->box_item->visible = (show & 1);
|
|
|
|
|
|
|
|
if (opt->type == OPT_TREE)
|
|
|
|
update_visibility(opt->value.tree, show);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
change_hook_stemplate(struct session *ses, struct option *current, struct option *changed)
|
|
|
|
{
|
|
|
|
update_visibility(config_options->value.tree, changed->value.number);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
change_hook_language(struct session *ses, struct option *current, struct option *changed)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_NLS
|
|
|
|
set_language(changed->value.number);
|
|
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct change_hook_info change_hooks[] = {
|
|
|
|
{ "config.show_template", change_hook_stemplate },
|
|
|
|
{ "connection", change_hook_connection },
|
|
|
|
{ "document.browse", change_hook_html },
|
|
|
|
{ "document.browse.forms.insert_mode",
|
|
|
|
change_hook_insert_mode },
|
|
|
|
{ "document.browse.links.active_link",
|
|
|
|
change_hook_active_link },
|
|
|
|
{ "document.cache", change_hook_cache },
|
|
|
|
{ "document.codepage", change_hook_html },
|
|
|
|
{ "document.colors", change_hook_html },
|
|
|
|
{ "document.html", change_hook_html },
|
|
|
|
{ "document.plain", change_hook_html },
|
|
|