diff --git a/Aircraft/ufo/ufo.nas b/Aircraft/ufo/ufo.nas index 5281bc951..6f8d30af3 100644 --- a/Aircraft/ufo/ufo.nas +++ b/Aircraft/ufo/ufo.nas @@ -31,7 +31,7 @@ var FT2M = 0.3048; var M2FT = 3.28083989501312335958; var normdeg = geo.normdeg; -var printf = func(_...) { print(call(sprintf, _)) } +var printf = func { print(call(sprintf, arg)) } var init_prop = func(prop, value) { @@ -632,15 +632,15 @@ var file_selector = nil; var file_select_model = func { if (file_selector == nil) { file_selector = gui.FileSelector.new(fsel_callback, - "Select *.ac or *.xml model file", - "Load Model", getprop("/sim/fg-root")); + "Select 3D model file", "Load Model", + ["*.ac", "*.xml"], getprop("/sim/fg-root")); } file_selector.open(); } var fsel_callback = func { var model = cmdarg().getValue(); - var root = io.fixpath(getprop("/sim/fg-root")) ~ "/"; + var root = string.fixpath(getprop("/sim/fg-root")) ~ "/"; if (substr(model, 0, size(root)) == root) model = substr(model, size(root)); diff --git a/Nasal/gui.nas b/Nasal/gui.nas index 2e2e48ec3..a305946e5 100644 --- a/Nasal/gui.nas +++ b/Nasal/gui.nas @@ -240,11 +240,12 @@ Dialog = { ## # FileSelector class (derived from Dialog class). # -# SYNOPSIS: FileSelector.new(, , <button> [, <dir> [, <file> [, <dotfiles>]]]) +# SYNOPSIS: FileSelector.new(<callback>, <title>, <button> [, <pattern> [, <dir> [, <file> [, <dotfiles>]]]]) # # callback ... callback function that gets return value as cmdarg().getValue() # title ... dialog title # button ... button text (should say "Save", "Load", etc. and not just "OK") +# pattern ... array with shell pattern or nil (which is equivalent to "*") # dir ... starting dir ($FG_ROOT if unset) # file ... pre-selected default file name # dotfiles ... flag that decids whether UNIX dotfiles should be shown (1) or not (0) @@ -252,7 +253,13 @@ Dialog = { # EXAMPLE: # # var report = func { print("file ", cmdarg().getValue(), " selected") } -# var selector = gui.FileSelector.new(report, "Save Flight", "Save", "/tmp", "flight.sav"); +# var selector = gui.FileSelector.new( +# report, # callback function +# "Save Flight", # dialot title +# "Save", # button text +# ["*.sav", "*.xml"], # pattern for displayed files +# "/tmp", # start dir +# "flight.sav"); # default file name # selector.open(); # # selector.close(); @@ -260,7 +267,7 @@ Dialog = { # selector.open(); # var FileSelector = { - new : func(callback, title, button, dir = "", file = "", dotfiles = 0) { + new : func(callback, title, button, pattern = nil, dir = "", file = "", dotfiles = 0) { var name = "file-select-"; var data = props.globals.getNode("/sim/gui/dialogs/", 1); var i = nil; @@ -277,6 +284,7 @@ var FileSelector = { m.set_directory(dir); m.set_file(file); m.set_dotfiles(dotfiles); + m.set_pattern(pattern); m.cblistener = setlistener(data.getNode("path", 1), callback); return m; }, @@ -286,6 +294,12 @@ var FileSelector = { set_directory : func(dir) { me.data.getNode("directory", 1).setValue(dir) }, set_file : func(file) { me.data.getNode("selection", 1).setValue(file) }, set_dotfiles : func(dot) { me.data.getNode("dotfiles", 1).setBoolValue(dot) }, + set_pattern : func(pattern) { + me.data.removeChildren("pattern"); + if (pattern != nil) + forindex (var i; pattern) + me.data.getChild("pattern", i, 1).setValue(pattern[i]); + }, del : func { me.close(); delete(me.instance, me.name); diff --git a/Nasal/io.nas b/Nasal/io.nas index 6a50152ea..32607c161 100644 --- a/Nasal/io.nas +++ b/Nasal/io.nas @@ -26,27 +26,3 @@ var ifmts = {dir:4, reg:8, lnk:10, sock:12, fifo:1, blk:6, chr:2}; foreach(fmt; keys(ifmts)) caller(0)[0]["is" ~ fmt] = _gen_ifmt_test(ifmts[fmt]); -# Removes superfluous slashes, emtpy and "." elements, expands -# all ".." elements, and turns all backslashes into slashes. -# The result will start with a slash if it started with a slash -# or backslash, it will end without slash. Should be applied on -# absolute paths, otherwise ".." elements might be resolved wrongly. -var fixpath = func(path) { - var d = ""; - for(var i = 0; i < size(path); i += 1) { - if(path[i] == `\\`) d ~= "/"; - else d ~= chr(path[i]); - } - var prefix = path[0] == `/` ? "/" : ""; - var stack = []; - foreach(var e; split("/", d)) { - if(e == "." or e == "") continue; - elsif(e == "..") pop(stack); - else append(stack, e); - } - if(!size(stack)) return "/"; - path = stack[0]; - foreach(var s; subvec(stack, 1)) path ~= "/" ~ s; - return prefix ~ path; -} - diff --git a/Nasal/screen.nas b/Nasal/screen.nas index c52520300..65ac35d17 100644 --- a/Nasal/screen.nas +++ b/Nasal/screen.nas @@ -30,32 +30,6 @@ # a message falls off; if 0 then don't scroll at all # -var isspace = func(c) { c == ` ` or c == `\t` or c == `\n` or c == `\r` } - - -## -# trim spaces at the left (lr < 0), at the right (lr > 0), or both (lr = 0) -# -var trim = func(s, lr = 0) { - var l = 0; - if (lr <= 0) { - for (; l < size(s); l += 1) { - if (!isspace(s[l])) { - break; - } - } - } - var r = size(s) - 1; - if (lr >= 0) { - for (; r >= 0; r -= 1) { - if (!isspace(s[r])) { - break; - } - } - } - return r < l ? "" : substr(s, l, r - l + 1); -} - ## # convert string for output; replaces tabs by spaces, and skips @@ -86,7 +60,7 @@ var sanitize = func(s) { var dialog_id = 0; var theme_font = nil; -window = { +var window = { new : func(x = nil, y = nil, maxlines = 10, autoscroll = 10) { var m = { parents : [window] }; # @@ -118,8 +92,8 @@ window = { if (g == nil) { g = me.fg[1] } if (b == nil) { b = me.fg[2] } if (a == nil) { a = me.fg[3] } - foreach (var line; split("\n", trim(msg))) { - line = sanitize(trim(line)); + foreach (var line; split("\n", string.trim(msg))) { + line = sanitize(string.trim(line)); append(me.lines, [line, r, g, b, a]); if (size(me.lines) > me.maxlines) { me.lines = subvec(me.lines, 1); @@ -227,7 +201,7 @@ _setlistener("/sim/signals/nasal-dir-initialized", func { ############################################################################## -msg_repeat = func { +var msg_repeat = func { if (getprop("/sim/tutorials/running")) { var last = getprop("/sim/tutorials/last-message"); if (last == nil) { diff --git a/Nasal/string.nas b/Nasal/string.nas new file mode 100644 index 000000000..dc2390db4 --- /dev/null +++ b/Nasal/string.nas @@ -0,0 +1,173 @@ +var isupper = func(c) { c >= `A` and c <= `Z` } +var islower = func(c) { c >= `a` and c <= `z` } +var toupper = func(c) { islower(c) ? c + `A` - `a` : c } +var tolower = func(c) { isupper(c) ? c + `a` - `A` : c } + +var isletter = func(c) { isupper(c) or islower(c) } +var isdigit = func(c) { c >= `0` and c <= `9` } +var isalnum = func(c) { isletter(c) or isdigit(c) } +var isspace = func(c) { c == ` ` or c == `\t` or c == `\n` or c == `\r` } + + +## +# trim spaces at the left (lr < 0), at the right (lr > 0), or both (lr = 0) +# +var trim = func(s, lr = 0) { + var l = 0; + if (lr <= 0) + for (; l < size(s); l += 1) + if (!isspace(s[l])) + break; + var r = size(s) - 1; + if (lr >= 0) + for (; r >= 0; r -= 1) + if (!isspace(s[r])) + break; + return r < l ? "" : substr(s, l, r - l + 1); +} + + +## +# return string converted to lower case letters +# +var lc = func(str) { + var s = ""; + for (var i = 0; i < size(str); i += 1) + s ~= chr(tolower(str[i])); + return s; +} + + +## +# return string converted to upper case letters +# +var uc = func(str) { + var s = ""; + for (var i = 0; i < size(str); i += 1) + s ~= chr(toupper(str[i])); + return s; +} + + +## +# case insensitive string compare and match functions +# (not very effective -- converting the array to be sorted +# first is faster) +# +var icmp = func(a, b) cmp(lc(a), lc(b)); +var imatch = func(a, b) match(lc(a), lc(b)); + + +## +# check if string <str> matches shell style pattern <patt> +# +# Rules: +# ? stands for any single character +# * stands for zero or any number of arbitrary characters +# \ escapes the next character and makes it stand for itself; that is: +# \? stands for a question mark (not "any single character") +# [] stands for a group of characters: +# [abc] stands for letters a or b or c +# [^abc] stands for any letter but any of a, b, and c +# [1-4] stands for digits 1 to 4 (1, 2, 3, 4) +# [1-4-] stands for digits 1 to 4 (1, 2, 3, 4), and the minus +# [-1-4] same as above +# [1-3-6] stands for digits 1 to 3, minus, and 6 +# [1-3-6-9] stands for digits 1 to 3, minus, and 6 to 9 +# [][] stands for the closing bracket and the opening bracket +# [^^] stands for all characters but the caret symbol +# +# Example: +# string.match(file, "*[0-9].xml"); ... true if string ends with digit followed by ".xml" +# +var match = func(str, patt) { + var s = 0; + for (var p = 0; p < size(patt) and s < size(str); ) { + if (patt[p] == `\\`) { + if ((p += 1) >= size(patt)) + return !size(str); # pattern ends with backslash + + } elsif (patt[p] == `?`) { + s += 1; + p += 1; + continue; + + } elsif (patt[p] == `*`) { + for (; p < size(patt); p += 1) + if (patt[p] != `*`) + break; + if (p >= size(patt)) + return 1; + + for (; s < size(str); s += 1) + if (match(substr(str, s), substr(patt, p))) + return 1; + continue; + + } elsif (patt[p] == `[`) { + setsize(var x = [], 256); + var invert = 0; + if ((p += 1) < size(patt) and patt[p] == `^`) { + p += 1; + invert = 1; + } + for (var i = 0; p < size(patt); p += 1) { + if (patt[p] == `]` and i) + break; + x[patt[p]] = 1; + i += 1; + + if (p + 2 < patt[p] and patt[p + 1] == `-` and patt[p + 2] != `]`) { + var from = patt[p]; + var to = patt[p += 2]; + for (var c = from; c <= to; c += 1) + x[c] = 1; + } + } + if (invert ? !!x[str[s]] : !x[str[s]]) + return 0; + s += 1; + p += 1; + continue; + } + + if (str[s] != patt[p]) + return 0; + s += 1; + p += 1; + } + return s == size(str) and p == size(patt); +} + + +## +# Removes superfluous slashes, emtpy and "." elements, expands +# all ".." elements, and turns all backslashes into slashes. +# The result will start with a slash if it started with a slash +# or backslash, it will end without slash. Should be applied on +# absolute property or file paths, otherwise ".." elements might +# be resolved wrongly. +# +var fixpath = func(path) { + var d = ""; + for (var i = 0; i < size(path); i += 1) + d ~= path[i] == `\\` ? "/" : chr(path[i]); + + var prefix = d[0] == `/` ? "/" : ""; + var stack = []; + foreach (var e; split("/", d)) { + if (e == "." or e == "") + continue; + elsif (e == "..") + pop(stack); + else + append(stack, e); + } + if (!size(stack)) + return "/"; + path = stack[0]; + foreach (var s; subvec(stack, 1)) + path ~= "/" ~ s; + return prefix ~ path; +} + diff --git a/gui/dialogs/file-select.xml b/gui/dialogs/file-select.xml index 21d5db79b..f96265920 100644 --- a/gui/dialogs/file-select.xml +++ b/gui/dialogs/file-select.xml @@ -113,6 +113,7 @@ var self = cmdarg(); var list = self.getNode("list"); + # cloning var dlgname = self.getNode("name").getValue(); self.getNode("input/property").setValue("/sim/gui/dialogs/" ~ dlgname ~ "/directory"); self.getNode("list/property").setValue("/sim/gui/dialogs/" ~ dlgname ~ "/selection"); @@ -132,11 +133,15 @@ var kbdshift = props.globals.getNode("/devices/status/keyboard/shift", 1); var kbdalt = props.globals.getNode("/devices/status/keyboard/alt", 1); var current = { dir : "", file : "" }; + var pattern = []; + foreach (var p; dlg.getChildren("pattern")) + append(pattern, p.getValue()); - var isletter = func(c) { c >= `A` and `Z` >= c or c >= `a` and `z` >= c } - - var squeeze = func(s, n) { - return n >= size(s) ? s : "... " ~ substr(s, size(s) - n); + var matches = func(s) { + foreach (var p; pattern) + if (string.match(s, p)) + return 1; + return 0; } var update = func(d) { @@ -161,12 +166,12 @@ continue; var stat = io.stat(d ~ "/" ~ e); - if (stat == nil) # dead link, no permission + if (stat == nil) # dead link continue; if (io.isdir(stat[2])) append(dirs, e ~ "/"); - else + elsif (!size(pattern) or matches(e)) append(files, e); } @@ -204,7 +209,7 @@ gui.dialog_update(dlgname, "file-input"); } if (new != nil) { - var p = io.fixpath(new); + var p = string.fixpath(new); if (update(p)) current.dir = p; selection.setValue(""); @@ -216,7 +221,7 @@ } var dir_input = func { - var p = io.fixpath(dir.getValue()); + var p = string.fixpath(dir.getValue()); if (update(p)) current.dir = p; gui.dialog_update(dlgname, "list"); @@ -229,7 +234,7 @@ var ok = func { dir_input(); file_input(); - var p = io.fixpath(current.dir ~ "/" ~ current.file); + var p = string.fixpath(current.dir ~ "/" ~ current.file); var stat = io.stat(p); if (stat == nil) return; @@ -251,7 +256,7 @@ current.dir = (var d = dir.getValue()) != nil and d != "" ? d : getprop("/sim/fg-current"); current.file = (var d = file.getValue()) != nil and d != "" ? d : ""; gui.dialog_update(dlgname, "file-input"); ## dir-input ? - update(io.fixpath(current.dir)); + update(string.fixpath(current.dir)); dir.setValue(current.dir); </open> </nasal> diff --git a/gui/dialogs/nasal-console.xml b/gui/dialogs/nasal-console.xml index 7c195158a..98b14a0a0 100644 --- a/gui/dialogs/nasal-console.xml +++ b/gui/dialogs/nasal-console.xml @@ -192,7 +192,7 @@ if (active) { # false in help mode dlg.getNode("active").setIntValue(active); if (!init) - dlg.getChild("code", active).setValue(screen.trim(edit.getValue())); + dlg.getChild("code", active).setValue(string.trim(edit.getValue())); } if (kbdctrl.getValue()) { execute(dlg.getChild("code", which));