Phi: nicer scroll animation for METAR widget
[fg:fgdata.git] / Nasal / multikey.nas
1 var translate = { 356: '<', 357: '^', 358: '>', 359: '_' };
2 var listener = nil;
3 var dialog = nil;
4 var data = nil;
5 var cmd = nil;
6 var menu = 0;
7
8
9 var start = func {
10         cmd = "";
11         dialog = Dialog.new();
12         handle_key(8);
13         listener = setlistener("/devices/status/keyboard/event", func(event) {
14                 var key = event.getNode("key");
15                 if (!event.getNode("pressed").getValue()) {
16                         if (cmd == "" and key.getValue() == `;`)  # FIXME hack around kbd bug
17                                 key.setValue(`:`);
18                         return;
19                 }
20                 if (handle_key(key.getValue()))
21                         key.setValue(-1);
22         });
23 }
24
25
26 var stop = func {
27         removelistener(listener);
28         listener = nil;
29         dialog.del();
30 }
31
32
33 var handle_key = func(key) {
34         var mode = 0;
35         if (key == 27) {
36                 stop();
37                 return 1;
38         } elsif (key == 8) {
39                 cmd = substr(cmd, 0, size(cmd) - 1);
40         } elsif (key == `\t`) {
41                 menu = !menu;
42         } elsif (key == `\n` or key == `\r`) {
43                 mode = 2;
44         } elsif (contains(translate, key)) {
45                 cmd ~= translate[key];
46         } elsif (!string.isprint(key)) {
47                 return 0;
48         } else {
49                 cmd ~= chr(key);
50         }
51
52         var options = [];
53         var bindings = [];
54         var desc = __multikey._ = nil;
55         if ((var node = find_entry(cmd, data, __multikey.arg = [])) != nil) {
56                 desc = node.getNode("desc", 1).getValue() or "";
57                 desc = call(sprintf, [desc] ~ __multikey.arg);
58                 bindings = node.getChildren("binding");
59                 if (node.getNode("no-exit") != nil) {
60                         cmd = substr(cmd, 0, size(cmd) - 1);
61                         mode = 1;
62                 } elsif (node.getNode("exit") != nil) {
63                         mode = 2;
64                 }
65                 if (menu)
66                         foreach (var c; node.getChildren("key"))
67                                 if (size(c.getChildren("binding")) or size(c.getChildren("key")))
68                                         append(options, c);
69         }
70
71         if (mode and size(bindings)) {
72                 foreach (var b; bindings)
73                         props.runBinding(b, "__multikey");
74                 if (mode == 2)
75                         stop();
76         }
77         if (mode < 2)
78                 dialog.update(cmd, __multikey._ or desc, options);
79         return 1;
80 }
81
82
83 var Dialog = {
84         new : func {
85                 var m = { parents: [Dialog] };
86                 m.name = "multikey";
87                 m.prop = props.Node.new({ "dialog-name": m.name });
88                 m.isopen = 0;
89                 m.firstrun = 1;
90                 return m;
91         },
92         del : func {
93                 me.isopen and fgcommand("dialog-close", me.prop);
94                 me.isopen = 0;
95         },
96         update : func(cmd, title, options) {
97                 var dlg = gui.Widget.new();
98                 dlg.set("name", me.name);
99                 dlg.set("y", -80);
100                 dlg.set("layout", "vbox");
101                 dlg.set("default-padding", 2);
102
103                 # title/description
104                 var t = dlg.addChild("text");
105                 if (!size(cmd)) {
106                         t.set("label", "  Command Mode  ");
107                         t.setColor(1, 0.7, 0);
108                 } elsif (title) {
109                         t.set("label", "  " ~ title ~ "  ");
110                         t.setColor(0.7, 1, 0.7);
111                 } else {
112                         t.set("label", "  <unknown>  ");
113                         t.setColor(1, 0.4, 0.4);
114                 }
115
116                 # typed command
117                 var t = dlg.addChild("text");
118                 if (me.firstrun) {
119                         me.firstrun = 0;
120                         cmd = "  Use <Tab> to toggle options!  ";
121                         t.setColor(0.5, 0.5, 0.5);
122                 }
123                 t.set("label", cmd);
124
125                 # option menu
126                 if (var numopt = size(options)) {
127                         dlg.addChild("hrule");
128                         var g = dlg.addChild("group");
129                         g.set("layout", "table");
130                         g.set("default-padding", 2);
131                         var numrows = numopt / (1 + (numopt > 15) + (numopt > 30));
132                         forindex (var i; options) {
133                                 var col = 3 * int(i / numrows);
134                                 var row = math.mod(i, numrows);
135
136                                 var desc = (options[i].getNode("desc", 1).getValue() or "") ~ "  ";
137                                 var name = "  " ~ options[i].getNode("name", 1).getValue();
138                                 name = string.replace(name, "%%", "%");
139
140                                 var o = g.addChild("text");
141                                 o.set("label", name);
142                                 o.set("row", row);
143                                 o.set("col", col);
144                                 var o = g.addChild("text");
145                                 o.set("label", " ... ");
146                                 o.set("row", row);
147                                 o.set("col", col + 1);
148                                 var o = g.addChild("text");
149                                 o.set("label", desc);
150                                 o.set("row", row);
151                                 o.set("col", col + 2);
152                                 o.set("halign", "left");
153                         }
154                 }
155                 me.del();
156                 fgcommand("dialog-new", dlg.prop());
157                 fgcommand("dialog-show", me.prop);
158                 me.isopen = 1;
159         },
160 };
161
162
163 var help = func {
164         var colorize = func(str) {
165                 var s = "";
166                 for (var i = 0; i < size(str); i += 1) {
167                         var c = str[i];
168                         if (c == `<` or c == `>` or c == `^` or c == `_`) {
169                                 s ~= string.color("35", chr(c));
170                         } elsif (c == `%`) {
171                                 if ((i += 1) < size(str) and str[i] == `%`) {
172                                         s ~= '%';
173                                         continue;
174                                 }
175                                 var f = '%';
176                                 for (; i < size(str) and (c = str[i]) != nil and string.isdigit(c); i += 1)
177                                         f ~= chr(c);
178                                 if (c == `d`)
179                                         s ~= string.color("31", f ~ 'd');
180                                 elsif (c == `u`)
181                                         s ~= string.color("32", f ~ 'u');
182                                 elsif (c == `f`)
183                                         s ~= string.color("36", f ~ 'f');
184                                 elsif (c == `s`)
185                                         s ~= string.color("34", f ~ 's');
186                         } else {
187                                 s ~= chr(c);
188                         }
189                 }
190                 return s;
191         }
192
193         var list = [];
194         var read_list = func(data) {
195                 foreach (var c; data.children)
196                         read_list(c);
197                 if (size(data.format))
198                         append(list, [data.format, data.node]);
199         }
200         read_list(data);
201
202         var (curr, title) = (0, "");
203         foreach (var k; sort(list, func(a, b) cmp(a[0], b[0]))) {
204                 var bndg = k[1].getChildren("binding");
205                 var desc = k[1].getNode("desc", 1).getValue() or "??";
206                 if (size(k[0]) == 1 or k[0][0] == `%`)
207                         title = desc;
208                 if (!size(bndg) or size(bndg) == 1 and bndg[0].getNode("command", 1).getValue() == "null")
209                         continue;
210                 if (string.isalnum(k[0][0]) and k[0][0] != curr) {
211                         curr = k[0][0];
212                         var line = "---------------------------------------------------";
213                         print(string.color("33", sprintf("\n-- %s %s", title, substr(line, size(title) + 2))));
214                 }
215                 if (k[1].getNode("no-exit") != nil)
216                         desc ~= string.color("32", "  +");
217                 elsif (k[1].getNode("exit") != nil)
218                         desc ~= string.color("31", "  $");
219                 printf("%s\t%s", colorize(k[0]), desc);
220         }
221         print(string.color("33", "\n-- Legend -------------------------------------------"));
222         printf("\t%s ... unsigned number", colorize("%u"));
223         printf("\t%s ... signed number", colorize("%d"));
224         printf("\t%s ... floating point number", colorize("%f"));
225         printf("\t%s ... string", colorize("%s"));
226         printf("\t%s  ... < or cursor left", colorize("<"));
227         printf("\t%s  ... > or cursor right", colorize(">"));
228         printf("\t%s  ... ^ or cursor up", colorize("^"));
229         printf("\t%s  ... _ or cursor down", colorize("_"));
230         printf("\t%s  ... repeatable action", string.color("32", "+"));
231         printf("\t%s  ... immediate action", string.color("31", "$"));
232 }
233
234
235 var find_entry = func(str, data, result) {
236         foreach (var c; data.children)
237                 if ((var n = find_entry(str, c, result)) != nil)
238                         return n;
239         if (string.scanf(str, data.format, var res = [])) {
240                 foreach (var r; res)
241                         append(result, r);
242                 return data.node;
243         }
244         return nil;
245 }
246
247
248 var init = func {
249         globals["__multikey"] = { _: };
250         var tree = props.globals.getNode("/input/keyboard/multikey", 1);
251
252         foreach (var n; tree.getChildren("nasal")) {
253                 n.getNode("module", 1).setValue("__multikey");
254                 fgcommand("nasal", n);
255         }
256
257         var scan = func(tree, format = "") {
258                 var d = [];
259                 foreach (var key; tree.getChildren("key"))
260                         foreach (var name; key.getChildren("name"))
261                                 if ((var n = name.getValue()) != nil)
262                                         append(d, { format: format ~ n, node: key,
263                                                         children: scan(key, format ~ n) });
264                 return d;
265         }
266
267         data = { format: "", node: tree, children: scan(tree) };
268 }
269
270
271 _setlistener("/sim/signals/nasal-dir-initialized", init);
272
273