From f8276536686dfc3f2bfa113e5d4ad917de255608 Mon Sep 17 00:00:00 2001 From: mfranz Date: Fri, 22 Jun 2007 14:13:30 +0000 Subject: [PATCH] - create new Nasal module string.nas with simple shell style pattern matching algorithm (needed for the file selector and useful for other purposes, like assembling lists of livery or screenshot files etc.) - io.nas: move fixpath to string.nas (it's not only useful for file paths but can also be used for property paths) - screen.nas: move trim to string.nas (used by screen.nas and nasal-console.xml) - gui.nas: add pattern matching to FileSelctor - ufo.nas: use patterm matching (only *.ac and *.xml files shall be listed) --- Aircraft/ufo/ufo.nas | 8 +- Nasal/gui.nas | 20 +++- Nasal/io.nas | 24 ----- Nasal/screen.nas | 34 +------ Nasal/string.nas | 173 ++++++++++++++++++++++++++++++++++ gui/dialogs/file-select.xml | 25 +++-- gui/dialogs/nasal-console.xml | 2 +- 7 files changed, 214 insertions(+), 72 deletions(-) create mode 100644 Nasal/string.nas 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));