1
0
Fork 0

- 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)
This commit is contained in:
mfranz 2007-06-22 14:13:30 +00:00
parent eea4465eea
commit f827653668
7 changed files with 214 additions and 72 deletions

View file

@ -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));

View file

@ -240,11 +240,12 @@ Dialog = {
##
# FileSelector class (derived from Dialog class).
#
# SYNOPSIS: FileSelector.new(<callback>, <title>, <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);

View file

@ -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;
}

View file

@ -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) {

173
Nasal/string.nas Normal file
View file

@ -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;
}

View file

@ -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>

View file

@ -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));