- 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:
parent
eea4465eea
commit
f827653668
7 changed files with 214 additions and 72 deletions
|
@ -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));
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
24
Nasal/io.nas
24
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
173
Nasal/string.nas
Normal 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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in a new issue