2007-11-18 12:20:17 +00:00
|
|
|
# Property Key Handler
|
2007-11-18 13:30:30 +00:00
|
|
|
# ------------------------------------------------------------------
|
|
|
|
# This is an extension mainly targeted at developers. It implements
|
|
|
|
# some useful tools for dealing with internal properties if enabled
|
|
|
|
# (Menu->Debug->Configure Development Extensions). To use this feature,
|
|
|
|
# press the '/'-key, then type a property path (using the <TAB> key to
|
|
|
|
# complete property path elements if possible), or a search string ...
|
2007-11-18 12:20:17 +00:00
|
|
|
#
|
|
|
|
#
|
|
|
|
# Commands:
|
|
|
|
#
|
|
|
|
# <property>=<value><CR> -> set property to value
|
|
|
|
# <property><CR> -> print property and value to screen and terminal
|
|
|
|
# <property>* -> print property and all children to terminal
|
2008-05-14 10:23:47 +00:00
|
|
|
# <property>! -> add property to display list (reset list with /!)
|
2007-11-18 12:20:17 +00:00
|
|
|
# <property>: -> open property browser in this property's directory
|
2009-05-06 20:37:40 +00:00
|
|
|
# <string>? -> print all properties whose path or value contains this string
|
2007-11-18 12:20:17 +00:00
|
|
|
#
|
|
|
|
#
|
|
|
|
# Keys:
|
|
|
|
#
|
2007-11-18 13:30:30 +00:00
|
|
|
# <CR> ... carriage return or enter, to confirm some operations
|
|
|
|
# <TAB> ... complete property path element (if possible), or
|
|
|
|
# cycle through available elements
|
|
|
|
# <Shift-TAB> ... like <TAB> but cycles backwards
|
|
|
|
# <CurUp>/<CurDown> ... switch back/forth in the history
|
|
|
|
# <Escape> ... cancel the operation
|
2009-05-06 20:37:40 +00:00
|
|
|
# <Shift-Backspace> ... remove last whole path element
|
2007-11-18 12:20:17 +00:00
|
|
|
#
|
|
|
|
#
|
|
|
|
# Colors:
|
|
|
|
#
|
|
|
|
# white ... syntactically correct path to not yet existing property
|
|
|
|
# green ... path to existing property
|
|
|
|
# red ... broken path syntax (e.g. "/foo*bar" ... '*' not allowed)
|
|
|
|
# yellow ... while typing in value for a valid property path
|
2007-11-26 16:12:37 +00:00
|
|
|
# magenta ... while typing search string (except when first character is '/')
|
2007-11-18 12:20:17 +00:00
|
|
|
#
|
|
|
|
#
|
|
|
|
# For example, to open the property browser in /position/, type '/p<TAB>:'.
|
|
|
|
|
|
|
|
|
2007-11-26 16:12:37 +00:00
|
|
|
var listener = nil;
|
2007-11-27 16:28:52 +00:00
|
|
|
var input = nil; # what the user typed (doesn't contain unconfirmed autocompleted parts)
|
|
|
|
var text = nil; # what is shown in the popup
|
2007-11-18 12:20:17 +00:00
|
|
|
var state = nil;
|
|
|
|
|
|
|
|
var completion = [];
|
|
|
|
var completion_pos = -1;
|
|
|
|
var history = [];
|
|
|
|
var history_pos = -1;
|
|
|
|
|
|
|
|
|
2007-11-26 16:12:37 +00:00
|
|
|
var start = func {
|
2007-11-27 16:28:52 +00:00
|
|
|
state = parse_input(text = "");
|
|
|
|
handle_key(`/`, 0);
|
|
|
|
|
2007-11-26 16:12:37 +00:00
|
|
|
listener = setlistener("/devices/status/keyboard/event", func(event) {
|
|
|
|
if (!event.getNode("pressed").getValue())
|
|
|
|
return;
|
|
|
|
var key = event.getNode("key");
|
|
|
|
var shift = event.getNode("modifier/shift").getValue();
|
|
|
|
if (handle_key(key.getValue(), shift))
|
2008-04-26 13:27:27 +00:00
|
|
|
key.setValue(-1); # drop key event
|
2007-11-26 16:12:37 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2007-11-18 12:20:17 +00:00
|
|
|
|
2007-11-26 16:12:37 +00:00
|
|
|
var stop = func(save_history = 0) {
|
|
|
|
removelistener(listener);
|
2007-11-27 16:28:52 +00:00
|
|
|
if (save_history and (!size(history) or !streq(history[-1], text)))
|
|
|
|
append(history, text);
|
2007-11-27 01:40:23 +00:00
|
|
|
|
|
|
|
history_pos = size(history);
|
2007-11-27 16:28:52 +00:00
|
|
|
gui.popdown();
|
2007-11-26 16:12:37 +00:00
|
|
|
}
|
|
|
|
|
2007-11-18 12:20:17 +00:00
|
|
|
|
2007-11-26 16:12:37 +00:00
|
|
|
var handle_key = func(key, shift) {
|
2007-11-18 12:20:17 +00:00
|
|
|
if (key == 357) { # up
|
|
|
|
set_history(-1);
|
|
|
|
|
|
|
|
} elsif (key == 359) { # down
|
|
|
|
set_history(1);
|
|
|
|
|
|
|
|
} elsif (key == `\n` or key == `\r`) {
|
|
|
|
if (state.error)
|
|
|
|
return 1;
|
|
|
|
if (state.value != nil)
|
|
|
|
setprop(state.path, state.value);
|
|
|
|
|
|
|
|
var n = props.globals.getNode(state.path);
|
|
|
|
var s = state.path;
|
|
|
|
if (n != nil) {
|
|
|
|
print_prop(n);
|
|
|
|
var v = n.getValue();
|
|
|
|
s ~= " = " ~ (v == nil ? "<nil>" : v);
|
|
|
|
} else {
|
|
|
|
s ~= " does not exist";
|
|
|
|
}
|
|
|
|
screen.log.write(s, 1, 1, 1);
|
2007-11-26 16:12:37 +00:00
|
|
|
stop(1);
|
|
|
|
return 1;
|
2007-11-18 12:20:17 +00:00
|
|
|
|
|
|
|
} elsif (key == 27) { # escape -> cancel
|
2007-11-26 16:12:37 +00:00
|
|
|
stop(0);
|
2007-11-18 12:20:17 +00:00
|
|
|
return 1;
|
|
|
|
|
2008-04-26 13:27:27 +00:00
|
|
|
} elsif (key == `\t`) { # tab
|
2007-11-27 16:28:52 +00:00
|
|
|
if (size(text) and text[0] == `/`) {
|
|
|
|
text = complete(input, shift ? -1 : 1);
|
|
|
|
build_completion(input);
|
|
|
|
var n = call(func { props.globals.getNode(text) }, [], var err = []);
|
2007-11-20 17:02:13 +00:00
|
|
|
if (!size(err) and n != nil and n.getAttribute("children") and size(completion) == 1)
|
2007-11-18 12:20:17 +00:00
|
|
|
handle_key(`/`, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
} elsif (key == 8) { # backspace
|
|
|
|
if (shift) { # + shift: remove one path element
|
2007-11-27 16:28:52 +00:00
|
|
|
input = text = state.parent.getPath();
|
|
|
|
if (text == "")
|
2007-11-18 12:20:17 +00:00
|
|
|
handle_key(`/`, 0);
|
|
|
|
} else {
|
2007-11-27 16:28:52 +00:00
|
|
|
input = text = substr(text, 0, size(text) - 1);
|
2014-04-18 21:49:11 +00:00
|
|
|
if (text == "")
|
|
|
|
stop(); # nothing in our field? close the dialog
|
2007-11-18 12:20:17 +00:00
|
|
|
}
|
|
|
|
completion_pos = -1;
|
|
|
|
|
2007-11-25 20:36:57 +00:00
|
|
|
} elsif (!string.isprint(key)) {
|
|
|
|
return 0; # pass other funny events
|
|
|
|
|
|
|
|
} elsif (key == `?` and state.value == nil) {
|
2007-11-27 16:28:52 +00:00
|
|
|
print("\n-- property search: '", text, "' ----------------------------------");
|
|
|
|
search(props.globals, text);
|
2007-11-25 20:36:57 +00:00
|
|
|
print("-- done --\n");
|
2007-11-26 16:12:37 +00:00
|
|
|
stop(0);
|
2007-11-25 20:36:57 +00:00
|
|
|
return 1;
|
|
|
|
|
2008-05-14 10:23:47 +00:00
|
|
|
} elsif (key == `!` and state.node != nil and state.value == nil) {
|
|
|
|
if (!state.node.getPath()) {
|
|
|
|
screen.property_display.reset();
|
|
|
|
stop(0);
|
|
|
|
} else {
|
|
|
|
screen.property_display.add(state.node);
|
|
|
|
stop(1);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
|
2007-11-25 20:36:57 +00:00
|
|
|
} elsif (key == `*` and state.node != nil and state.value == nil) {
|
|
|
|
debug.tree(state.node);
|
2007-11-26 16:12:37 +00:00
|
|
|
stop(1);
|
|
|
|
return 1;
|
2007-11-25 20:36:57 +00:00
|
|
|
|
|
|
|
} elsif (key == `:` and state.node != nil and state.value == nil) {
|
|
|
|
var n = state.node.getAttribute("children") ? state.node : state.parent;
|
|
|
|
gui.property_browser(n);
|
2007-11-26 16:12:37 +00:00
|
|
|
stop(1);
|
|
|
|
return 1;
|
2007-11-25 20:36:57 +00:00
|
|
|
|
2007-11-18 12:20:17 +00:00
|
|
|
} else {
|
2007-11-27 16:28:52 +00:00
|
|
|
text ~= chr(key);
|
|
|
|
input = text;
|
2007-11-18 12:20:17 +00:00
|
|
|
completion_pos = -1;
|
|
|
|
history_pos = size(history);
|
|
|
|
}
|
|
|
|
|
2007-11-27 16:28:52 +00:00
|
|
|
state = parse_input(text);
|
|
|
|
build_completion(input);
|
2007-11-18 12:20:17 +00:00
|
|
|
|
|
|
|
var color = nil;
|
2008-04-26 13:27:27 +00:00
|
|
|
if (size(text) and text[0] != `/`) # search mode (magenta)
|
2007-11-18 12:20:17 +00:00
|
|
|
color = set_color(1, 0.4, 0.9);
|
|
|
|
elsif (state.error) # error mode (red)
|
|
|
|
color = set_color(1, 0.4, 0.4);
|
|
|
|
elsif (state.value != nil) # value edit mode (yellow)
|
|
|
|
color = set_color(1, 0.8, 0);
|
|
|
|
elsif (state.node != nil) # existing node (green)
|
|
|
|
color = set_color(0.7, 1, 0.7);
|
|
|
|
|
2007-11-27 16:28:52 +00:00
|
|
|
gui.popupTip(text, 1000000, color);
|
|
|
|
return 1; # discard key event
|
2007-11-18 12:20:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var parse_input = func(expr) {
|
|
|
|
var path = expr;
|
|
|
|
var value = nil;
|
|
|
|
|
|
|
|
if ((var pos = find("=", expr)) >= 0) {
|
|
|
|
path = substr(expr, 0, pos);
|
|
|
|
value = substr(expr, pos + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
# split argument in parent and name
|
|
|
|
var last = 0;
|
|
|
|
while ((var pos = find("/", path, last + 1)) > 0)
|
|
|
|
last = pos;
|
|
|
|
var parent = substr(path, 0, last); # without trailing /
|
|
|
|
var raw_name = substr(path, last + 1);
|
|
|
|
var name = raw_name;
|
|
|
|
if ((var pos = find("[", name)) >= 0)
|
|
|
|
name = substr(name, 0, pos);
|
|
|
|
var node = nil;
|
|
|
|
|
|
|
|
# run dangerous operations in cage (the paths might be invalid)
|
|
|
|
call(func {
|
|
|
|
parent = props.globals.getNode(parent);
|
|
|
|
node = props.globals.getNode(path);
|
|
|
|
}, [], var error = []);
|
|
|
|
|
|
|
|
return {
|
|
|
|
error: size(error),
|
|
|
|
path: path,
|
|
|
|
value: value,
|
|
|
|
raw_name: raw_name, # "binding[1"
|
|
|
|
name: name, # "binding"
|
|
|
|
parent: parent,
|
|
|
|
node: node,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var build_completion = func(in) {
|
|
|
|
completion = [];
|
|
|
|
var s = parse_input(in);
|
2008-04-26 23:29:52 +00:00
|
|
|
if (s.error or s.parent == nil)
|
2007-11-18 12:20:17 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
foreach (var c; s.parent.getChildren()) {
|
|
|
|
var index = c.getIndex();
|
|
|
|
var name = c.getName();
|
|
|
|
var fullname = name;
|
|
|
|
if (index > 0)
|
|
|
|
fullname ~= "[" ~ index ~ "]";
|
2007-11-26 16:42:43 +00:00
|
|
|
if (substr(fullname, 0, size(s.raw_name)) == s.raw_name)
|
|
|
|
append(completion, [fullname, name, index]);
|
2007-11-18 12:20:17 +00:00
|
|
|
}
|
2007-11-25 20:36:57 +00:00
|
|
|
completion = sort(completion, func(a, b) cmp(a[1], b[1]) or a[2] - b[2]);
|
2007-11-18 12:20:17 +00:00
|
|
|
#print(debug.string([completion_pos, completion]), "\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var complete = func(in, step) {
|
|
|
|
if (state.parent == nil or state.value != nil)
|
|
|
|
return in; # can't complete broken path or assignment
|
|
|
|
|
|
|
|
completion_pos += step;
|
|
|
|
if (completion_pos < 0)
|
|
|
|
completion_pos = size(completion) - 1;
|
|
|
|
elsif (completion_pos >= size(completion))
|
|
|
|
completion_pos = 0;
|
|
|
|
|
|
|
|
if (completion_pos < size(completion))
|
|
|
|
in = state.parent.getPath() ~ "/" ~ completion[completion_pos][0];
|
|
|
|
|
|
|
|
return in;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var set_history = func(step) {
|
|
|
|
history_pos += step;
|
|
|
|
if (history_pos < 0) {
|
|
|
|
history_pos = 0;
|
|
|
|
} elsif (history_pos >= size(history)) {
|
|
|
|
history_pos = size(history);
|
2007-11-27 16:28:52 +00:00
|
|
|
text = "";
|
2007-11-18 12:20:17 +00:00
|
|
|
} else {
|
2007-11-27 16:28:52 +00:00
|
|
|
text = history[history_pos];
|
2007-11-18 12:20:17 +00:00
|
|
|
}
|
2007-11-27 16:28:52 +00:00
|
|
|
input = text;
|
2007-11-18 12:20:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var set_color = func(r, g, b) {
|
|
|
|
return { text: { color: { red: r, green: g, blue: b } }};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var print_prop = func(n) {
|
2008-04-26 13:27:27 +00:00
|
|
|
print(n.getPath(), " = ", debug.string(n.getValue()), " ", debug.attributes(n));
|
2007-11-18 12:20:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var search = func(n, s) {
|
|
|
|
if (find(s, n.getPath()) >= 0)
|
|
|
|
print_prop(n);
|
2009-05-06 20:37:40 +00:00
|
|
|
elsif (n.getType() != "NONE" and find(s, "" ~ n.getValue()) >= 0)
|
2008-12-08 14:03:18 +00:00
|
|
|
print_prop(n);
|
2007-11-18 12:20:17 +00:00
|
|
|
foreach (var c; n.getChildren())
|
|
|
|
search(c, s);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-05-19 17:31:57 +00:00
|
|
|
_setlistener("/sim/signals/nasal-dir-initialized", func {
|
|
|
|
foreach (var p; props.globals.getNode("/sim/gui/prop-key-handler/history", 1).getChildren("entry"))
|
|
|
|
append(history, p.getValue());
|
2008-12-09 00:20:29 +00:00
|
|
|
var max = props.globals.initNode("/sim/gui/prop-key-handler/history-max-size", 30).getValue();
|
2008-06-08 11:14:57 +00:00
|
|
|
if (size(history) > max)
|
|
|
|
history = subvec(history, size(history) - max);
|
2008-05-19 17:31:57 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
_setlistener("/sim/signals/exit", func {
|
2008-12-09 00:20:29 +00:00
|
|
|
var max = props.globals.initNode("/sim/gui/prop-key-handler/history-max-size", 30).getValue();
|
2008-05-19 17:31:57 +00:00
|
|
|
if (size(history) > max)
|
|
|
|
history = subvec(history, size(history) - max);
|
|
|
|
forindex (var i; history) {
|
|
|
|
var p = props.globals.getNode("/sim/gui/prop-key-handler/history", 1).getChild("entry", i, 1);
|
|
|
|
p.setValue(history[i]);
|
|
|
|
p.setAttribute("userarchive", 1);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|