From 0c6454ca05cb7117455d538994630c877604d7bd Mon Sep 17 00:00:00 2001 From: Basile Burg Date: Mon, 12 Jan 2015 06:19:25 +0100 Subject: [PATCH] drw2 --- resman/build/tool.coedit | 3 + resman/library/resman.d | 6 +- resman/tool/e7F.d | 53 ++++++++ resman/tool/item.d | 159 ++++++++++++++++++++++++ resman/tool/resman.d | 239 +++++++++++++++++++++++++------------ resman/tool/resman.resdata | 25 ++++ resman/tool/resman.resman | 2 +- resman/tool/utils.d | 64 ++++++++++ resman/tool/z85.d | 140 ++++++++++++++++++++++ src/ce_editor.pas | 1 - src/ce_libman.pas | 6 +- src/ce_projconf.lfm | 6 +- src/ce_projconf.pas | 48 ++++---- src/ce_resman.lfm | 8 +- src/ce_resman.pas | 173 +++++++++++++++------------ src/ce_tools.pas | 2 +- src/ce_toolseditor.pas | 59 ++++----- 17 files changed, 775 insertions(+), 219 deletions(-) create mode 100644 resman/tool/e7F.d create mode 100644 resman/tool/item.d create mode 100644 resman/tool/resman.resdata create mode 100644 resman/tool/z85.d diff --git a/resman/build/tool.coedit b/resman/build/tool.coedit index 229b0ee3..18268b0a 100644 --- a/resman/build/tool.coedit +++ b/resman/build/tool.coedit @@ -18,6 +18,9 @@ object CurrentProject: TCEProject Sources.Strings = ( '..\tool\resman.d' '..\tool\utils.d' + '..\tool\e7F.d' + '..\tool\z85.d' + '..\tool\item.d' ) ConfigurationIndex = 0 end diff --git a/resman/library/resman.d b/resman/library/resman.d index ba35a554..7077786f 100644 --- a/resman/library/resman.d +++ b/resman/library/resman.d @@ -3,6 +3,8 @@ */ module resman; +import std.path, std.conv, std.base64, std.digest.crc; + /** * Activate the resman mechanism. If the resources related to a * module have been generated using the Coedit *Resman* widget @@ -17,7 +19,7 @@ module resman; mixin template activateResman() { mixin("private import resman;"); - enum f = (__FILE__.stripExtension.stripPath) ~ ".res"; + enum f = (__FILE__.stripExtension.baseName) ~ ".res"; mixin(import(f)); } @@ -32,7 +34,7 @@ public enum ResFormat { * * Params: * identifiers = the array which holds the resource identifiers, - * always named *residententifiers*. + * always named *TBA*. * identifier = the identifier to find. * * Return: diff --git a/resman/tool/e7F.d b/resman/tool/e7F.d new file mode 100644 index 00000000..accd487b --- /dev/null +++ b/resman/tool/e7F.d @@ -0,0 +1,53 @@ +module e7F; + +private const t = true; +private const f = false; + +/// Returns true if a white has to be encoded. +private bool[14] isWhite7F = [ + t, t, t, f, f, f, t, f, f, t, t, f, f, t +]; + +/** + * "7F" binary-to-text encoder. + * Kind of "percent encoding" except that the prefix is the character 0x7F. + * non-ASCII chars and "whites" are prefixed and represented in base 16. + */ +char[] encode_7F(ubyte[] input) +{ + import utils : ubyte2Hex; + char[] result; + foreach(b; input){ + //if ((b < 14 && isWhite7F[b]) || b > 0x7E) + if (b < 32 || b > 0x7E || b == '"' || b == '\\') + result ~= (cast(char) 0x7F) ~ ubyte2Hex(b); + else + result ~= cast(char)b; + } + return result; +} + +/** + * "7F" text-to-binary decoder. + */ +ubyte[] decode_7F(in char[] input) +{ + ubyte[] result; + size_t i; + while(i < input.length) + { + char c = input[i]; + assert(c <= 0x7F); + if (c == 0x7F) + { + assert(i+2 < input.length); + char[2] digits = input[i+1 .. i+3]; + import std.conv : to; + result ~= to!ubyte(digits[], 16); + i += 2; + } + else result ~= c; + ++i; + } + return result; +} diff --git a/resman/tool/item.d b/resman/tool/item.d new file mode 100644 index 00000000..e2c10774 --- /dev/null +++ b/resman/tool/item.d @@ -0,0 +1,159 @@ +module item; + +import core.exception; + +import std.file, std.path; +import std.string: format; + +import utils; + +enum ResFormat {bytes, utf8, base16, base64, z85, e7F} + +alias ResourceItems = ResourceItem * []; + +/** + * Resource item. + * The resource properties and the encoded form are generated in the constructor. + */ +struct ResourceItem{ + private: + static const resFileMsg = "resource file '%s' "; + ResFormat _resFormat; + string _resIdentifier; + string _resMetaData; + char[] _resTxtData; + ubyte[] _resRawData; + uint _initialSum; + uint _encodedSum; + + bool encodeDispatcher(){ + final switch(this._resFormat){ + case ResFormat.bytes: + return encodeUtf8(); // should add to a ubyte[][] import(filename) + case ResFormat.utf8: + return encodeUtf8(); + case ResFormat.base16: + return encodeb16(); + case ResFormat.base64: + return encodeb64(); + case ResFormat.z85: + return encodez85(); + case ResFormat.e7F: + return encodee7F(); + } + } + + /// encodes _resRawData to an UTF8 string + bool encodeUtf8(){ + scope(failure) return false; + _resTxtData = (cast(char[])_resRawData).dup; + return true; + } + + /// encodes _resRawData to a hex string + bool encodeb16(){ + scope(failure) return false; + foreach(b; _resRawData) + this._resTxtData ~= ubyte2Hex(b); + assert(_resTxtData.length == _resRawData.length * 2, + "b16 representation length mismatches"); + return true; + } + + /// encodes _resRawData to a base64 string + bool encodeb64(){ + import std.base64: Base64; + scope(failure) return false; + _resTxtData = Base64.encode(_resRawData); + return true; + } + + /// encodes _resRawData to a Z85 string + bool encodez85(){ + import z85: Z85_encode; + scope(failure) return false; + _resTxtData = Z85_encode(_resRawData); + assert(_resTxtData.length == _resRawData.length * 5 / 4, + "z85 representation length mismatches"); + return true; + } + + /// encodes _resRawData to an e7F string + bool encodee7F() + { + import e7F: encode_7F; + scope(failure) return false; + _resTxtData = encode_7F(_resRawData); + return true; + } + + public: + + /** + * Creates and encodes a new resource item. + * + * Params: + * + * resFile = the file whose content is to encode. + * resEnc = the encoding format. + * resIdent = optional string used as identifier. When not set, the filename is used. + * resMeta = optional string used as metadata. + * + * Examples: + * -------- + * auto item = new Item("\folder\strings_en.txt", ResEncoding.e7F, "string_english"); + * auto item = new Item("\folder\strings_fr.txt", ResEncoding.e7F, "string_frecnh"); + * -------- + */ + this(string resFile, ResFormat resFormat, string resIdent = "", string resMeta = "") + { + this._resFormat = resFormat; + + // load the raw content + if (!resFile.exists) + throw new Exception(format(resFileMsg ~ "does not exist", resFile)); + else + _resRawData = cast(ubyte[])std.file.read(resFile); + if (!_resRawData.length) + throw new Exception(format(resFileMsg ~ "is empty", resFile)); + + import std.digest.crc: CRC32; + CRC32 ihash; + ihash.put(_resRawData); + _initialSum = crc322uint(ihash.finish); + + // sets the resource identifier to the res filename if param is empty + this._resIdentifier = resIdent; + if (this._resIdentifier == "") + this._resIdentifier = resFile.baseName.stripExtension; + + this._resMetaData = resMeta; + + if (!encodeDispatcher) + throw new Exception(format(resFileMsg ~ "encoding failed", resFile)); + this._resRawData.length = 0; + CRC32 ehash; + ehash.put(cast(ubyte[])_resTxtData); + _encodedSum = crc322uint(ehash.finish); + + writeMessage(true, format("encoded resource file '%s'", resFile)); + } + + /// returns the resource encoded as a string. + char[] resText(){return _resTxtData;} + + /// returns the resource identifier. + string resIdentifier(){return _resIdentifier;} + + /// returns the resource metadata. + string resMetaData(){return _resMetaData;} + + /// returns the resource encoding kind. + ResFormat resFormat(){return _resFormat;} + + /// returns the signature of the original data. + uint initialSum(){return _initialSum;} + + /// returns the signature of the encoded data. + uint encodedSum(){return _encodedSum;} +} diff --git a/resman/tool/resman.d b/resman/tool/resman.d index 09268262..6b0fad54 100644 --- a/resman/tool/resman.d +++ b/resman/tool/resman.d @@ -1,14 +1,20 @@ module resman; import std.stdio, std.getopt, std.path; -import std.json, std.file, std.conv; +import std.json, std.file, std.conv, std.string; + +import utils, item; enum ResType {aFile, aFolder} -enum ResFormat {bytes, utf8, base16, base64} - -struct ResourceItem{} -alias ResourceItems = ResourceItem * []; +/** + * Resman main procedure. + * Params: + * args = the options and a list of file. + * - -v or --verbose: detailed messages for each operation. + * - -b or --basepath: a path used to solve relative resource filenames. + * - -files: a list of files produced by the Resman widget. + */ void main(string[] args) { string[] files; @@ -29,24 +35,57 @@ void main(string[] args) json2Items(f, items); Items2Module(f, items); } - readln; + //readln; } + +static string fmt_json_err = "invalid JSON format: %s, in file '%s'"; +static string fmt_file_skp = "invalid JSON file: %s, '%s' is skipped"; +static string fmt_item_skp = "invalid JSON item: %s, '%s' is skipped"; +static string fmt_item_rep = "invalid JSON value: %s, has been set to '%s'"; +/** + * Transforms the items contained in the JSON file fname to an array of + * ResourceItem. + * Params: + * fname = a valid filename. + * items = container for the ResourceItem found in the file. + */ void json2Items(string fname, out ResourceItems items) { - if (!fname.exists) return; // + verbose, msg + if (!fname.exists) { + writeMessage(true, format(fmt_file_skp, "it does not exists", fname)); + return; + } + size_t size = cast(size_t) getSize(fname); - if (size == 0) return; // + verbose, msg + if (size == 0) { + writeMessage(true, format(fmt_file_skp, "empty file", fname)); + return; + } auto json_string = cast(string) std.file.read(fname, size); JSONValue root = parseJSON(json_string); - if (root.type != JSON_TYPE.OBJECT) return; // invalid format - JSONValue * itms = ("items" in root.object); - if (itms == null) return; // invalid format - if (itms.type != JSON_TYPE.ARRAY) return; // invalid format - foreach(itm; itms.array) + if (root.type != JSON_TYPE.OBJECT) { + writeMessage(true, format(fmt_json_err, "first value must be of type OBJECT", fname)); + return; + } + + JSONValue * itms = ("resources" in root.object); + if (itms == null) { + writeMessage(true, format(fmt_json_err, "the member 'resources' is missing", fname)); + return; + } + if (itms.type != JSON_TYPE.ARRAY) { + writeMessage(true, format(fmt_json_err, "'resources' must be of type ARRAY", fname)); + return; + } + + foreach(itm; itms.array) { - if (itm.type != JSON_TYPE.OBJECT) continue; // invalid format + if (itm.type != JSON_TYPE.OBJECT) { + writeMessage(true, format(fmt_item_skp, "an item must be of type OBJECT", itm.toString)); + continue; + } JSONValue * itm_tpe = ("resourceType" in itm.object); JSONValue * itm_nme = ("name" in itm.object); @@ -54,89 +93,131 @@ void json2Items(string fname, out ResourceItems items) JSONValue * itm_fmt = ("format" in itm.object); JSONValue * itm_mdt = ("metadata" in itm.object); - if (itm_tpe == null) continue; // invalid format - if (itm_nme == null) continue; // invalid format - if (itm_idt == null) continue; // invalid format - if (itm_fmt == null) continue; // invalid format - if (itm_mdt == null) continue; // invalid format + if (itm_tpe == null || itm_nme == null || itm_idt == null || + itm_fmt == null || itm_mdt == null) + { + writeMessage(true, format(fmt_item_skp, "an item must contain all the properties", + itm.toString)); + continue; + } - if (itm_tpe.type != JSON_TYPE.STRING) continue; // invalid format - if (itm_nme.type != JSON_TYPE.STRING) continue; // invalid format - if (itm_idt.type != JSON_TYPE.STRING) continue; // invalid format - if (itm_fmt.type != JSON_TYPE.STRING) continue; // invalid format - if (itm_mdt.type != JSON_TYPE.STRING) continue; // invalid format + if (itm_tpe.type != JSON_TYPE.STRING || itm_nme.type != JSON_TYPE.STRING || + itm_idt.type != JSON_TYPE.STRING || itm_fmt.type != JSON_TYPE.STRING || + itm_mdt.type != JSON_TYPE.STRING) + { + writeMessage(true, format(fmt_item_skp, "an item value must be of type STRING", + itm.toString)); + continue; + } string[] nme_vs; string nme_v = itm_nme.str; string idt_v = itm_idt.str; string mdt_v = itm_mdt.str; - ResType tpe_v = to!ResType(itm_tpe.str); - ResFormat fmt_v = to!ResFormat(itm_fmt.str); + ResType tpe_v; + ResFormat fmt_v; - if (!nme_v.exists) continue; // path or filename must exists + if (!nme_v.exists) { + writeMessage(true, format(fmt_item_skp, "the item filename or path is invalid", nme_v)); + continue; + } + + try tpe_v = to!ResType(itm_tpe.str); + catch (Exception e) { + if (isDir(nme_v)) tpe_v = ResType.aFolder; + else tpe_v = ResType.aFile; + writeMessage(true, format(fmt_item_rep, "resourceType", to!string(tpe_v))); + } + + try fmt_v = to!ResFormat(itm_fmt.str); + catch (Exception e) { + fmt_v = ResFormat.bytes; + writeMessage(true, format(fmt_item_rep, "format", to!string(fmt_v))); + } + + if (idt_v == "" && tpe_v == ResType.aFile) + writeMessage(true, format(fmt_item_rep, "identifier", "the striped filename")); + + if (idt_v != "" && tpe_v == ResType.aFolder) + writeMessage(true, format(fmt_item_rep, + "identifier", "the striped filename of each entry in the direrctory")); if (nme_v.isDir) - foreach(e; dirEntries(nme_v, SpanMode.shallow)) - nme_vs ~= e; + { + foreach(e; dirEntries(nme_v, SpanMode.shallow)) nme_vs ~= e; + idt_v = ""; + } else nme_vs ~= nme_v; foreach(n; nme_vs) { - // creates item for the file n - } + items ~= new ResourceItem(n, fmt_v, idt_v, mdt_v); + } } - - - - - - - - void json_print(ref JSONValue value) - { - - writeln("-------------"); - JSON_TYPE tp = value.type; - final switch(tp){ - case JSON_TYPE.ARRAY: - foreach(v; value.array) - json_print(v); - break; - case JSON_TYPE.FALSE: - writeln(value); - break; - case JSON_TYPE.FLOAT: - writeln(value); - break; - case JSON_TYPE.INTEGER: - writeln(value); - break; - case JSON_TYPE.NULL: - break; - case JSON_TYPE.OBJECT: - writeln(value); - writeln(("identifier" in value.object) != null); - foreach(v; value.object) - json_print(v); - break; - case JSON_TYPE.STRING: - writeln(value); - break; - case JSON_TYPE.TRUE: - writeln(value); - break; - case JSON_TYPE.UINTEGER: - writeln(value); - break; - } - } - - json_print(root); } -void Items2Module(string fname, ref ResourceItems items) +/** + * Writes the ResourceItems *items* as valid D code D in a file whose content + * will be imported by the library template activateResman(). The file written + * in this method has the same name as the JSON file analyzed before, but with + * another extension. + * Params: + * fname = the name of the JSON file parsed previously. + * items = an array of *ResourceItem* + */ +void Items2Module(string fname, ref ResourceItems resItems) { + string outputFname = fname.stripExtension ~ ".resdata"; + if (outputFname.exists) std.file.remove(outputFname); + + // writes the resource representations to the module + writeMessage(true, "writing the resources data as text..."); + outputFname.append("\r\n\r\n"); + outputFname.append("private static const resource_txt = ["); + foreach (i; 0 .. resItems.length -1) + outputFname.append(format("\r\n\t" ~ "\"" ~ "%s" ~ "\"" ~ ",", resItems[i].resText)); + outputFname.append(format("\r\n\t" ~ "\"" ~ "%s" ~ "\"" ~ "\r\n];", resItems[$-1].resText)); + + // writes the resources identifiers to the module + writeMessage(true, "writing the resources identifiers..."); + outputFname.append("\r\n\r\n"); + outputFname.append("private static const resource_idt = ["); + foreach (i; 0 .. resItems.length -1) + outputFname.append(format("\r\n\t" ~ "\"" ~ "%s" ~ "\"" ~ ",", resItems[i].resIdentifier)); + outputFname.append(format("\r\n\t" ~ "\"" ~ "%s" ~ "\"" ~ "\r\n];", resItems[$-1].resIdentifier)); + + // writes the resources metadata to the module + writeMessage(true, "writing the resources metadata..."); + outputFname.append("\r\n\r\n"); + outputFname.append("private static const resource_mdt = ["); + foreach (i; 0 .. resItems.length -1) + outputFname.append(format("\r\n\t" ~ "\"" ~ "%s" ~ "\"" ~ ",", resItems[i].resMetaData)); + outputFname.append(format("\r\n\t" ~ "\"" ~ "%s" ~ "\"" ~ "\r\n];", resItems[$-1].resMetaData)); + + // writes the resources encoder kind to the module + writeMessage(true, "writing the resources kind..."); + outputFname.append("\r\n\r\n"); + outputFname.append("private static const resource_enc = ["); + foreach (i; 0 .. resItems.length -1) + outputFname.append(format("\r\n\t%s.%s,", ResFormat.stringof, resItems[i].resFormat)); + outputFname.append(format("\r\n\t%s.%s \r\n];", ResFormat.stringof, resItems[$-1].resFormat)); + + // writes the initial sums to the module + writeMessage(true, "writing the resources initial sum..."); + outputFname.append("\r\n\r\n"); + outputFname.append("private static const uint[] resource_sumi = ["); + foreach (i; 0 .. resItems.length -1) + outputFname.append(format("\r\n\t" ~ "%.d" ~ ",", resItems[i].initialSum)); + outputFname.append(format("\r\n\t" ~ "%.d" ~ "\r\n];", resItems[$-1].initialSum)); + + // writes the encoded sums to the module + writeMessage(true, "writing the resources encoded sum..."); + outputFname.append("\r\n\r\n"); + outputFname.append("private static const uint[] resource_sume = ["); + foreach (i; 0 .. resItems.length -1) + outputFname.append(format("\r\n\t" ~ "%.d" ~ ",", resItems[i].encodedSum)); + outputFname.append(format("\r\n\t" ~ "%.d" ~ "\r\n];", resItems[$-1].encodedSum)); } diff --git a/resman/tool/resman.resdata b/resman/tool/resman.resdata new file mode 100644 index 00000000..f08e7f51 --- /dev/null +++ b/resman/tool/resman.resdata @@ -0,0 +1,25 @@ + + +private static const resource_txt = [ + "Ibl@q4gj0X0000dnK#lE0000g0000g2Ngne03y?jve{oc8fwqHBw)9&BA.elwDk2bz^)o2nO})GwM08Fwo&gpwJP})0gltCk#-9.v/loaklAiZ1W}dTCKCJ[Vl@Jrk{W*62E1j]sj&ZBXJOZK%nI$(+Nn}2@!g:f!ZYlnRLUWOG$b5Q4exCS4*fxWk5*#:Z.AI&3ZS*$c(K:zXiK818w", "metadata" : "", "name" : "", "resourceType" : "aFile" }] } \ No newline at end of file +{ "Name" : "", "Tag" : 0, "resources" : [{ "format" : "z85", "identifier" : "image1", "metadata" : "bla|abl", "name" : "C:\\Dev\\dproj\\Resource.d\\res\\res_img1.png", "resourceType" : "aFile" }] } \ No newline at end of file diff --git a/resman/tool/utils.d b/resman/tool/utils.d index 4d05c1e4..58b03cc3 100644 --- a/resman/tool/utils.d +++ b/resman/tool/utils.d @@ -1 +1,65 @@ module utils; + + +private static const hexDigits = "0123456789ABCDEF"; + +/** + * Returns the hexadecimal representation of a ubyte. + * "to!string(int, radix)" is not used because it strips the leading 0 off. + */ +public char[2] ubyte2Hex(ubyte aUbyte) +{ + char[2] result; + result[1] = hexDigits[(aUbyte & 0x0F)]; + result[0] = hexDigits[(aUbyte & 0xF0) >> 4]; + return result; +} + +/** + * Converts a CRC32 to a BigEndian uint. + * It grants a resource module to be cross-plateform. + */ +public uint crc322uint(in ubyte[4] aCrc32) +{ + uint result; + ubyte* ptr = cast(ubyte*) &result; + version(BigEndian) + foreach(i; 0..4) * (ptr + i) = aCrc32[i]; + else + foreach(i; 0..4) * (ptr + i) = aCrc32[3-i]; + return result; +} + +/** + * splits the encoded resource representation in multiple lines. + * Problem: not UTF-8 aware (does not always split on a code point) + */ +string splitConstString(char[] input, size_t columns = 80) +{ + size_t lo, hi; + string result; + auto lines = input.length / columns; + foreach(i; 0 .. lines){ + lo = i * columns; + hi = lo + columns; + result ~= "\t\"" ~ input[lo .. hi] ~ "\" ~\r\n"; + } + result ~= "\t" ~ "\"" ~ input[hi .. $-1] ~ "\""; + return result; +} + +/** + * Adds a log message if verbose. + */ +void writeMessage(bool verbose, lazy string aMessage){ + import std.stdio : writefln; + if (!verbose) return; + writefln("%s", aMessage); +} + +/** + * Self-centered init. + */ +void reset(T)(ref T aValue){ + aValue = aValue.init; +} diff --git a/resman/tool/z85.d b/resman/tool/z85.d new file mode 100644 index 00000000..c4b50bfa --- /dev/null +++ b/resman/tool/z85.d @@ -0,0 +1,140 @@ +module z85; + +// -------------------------------------------------------------------------- +// Z85 is Copyright (c) 2010-2013 iMatix Corporation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// -------------------------------------------------------------------------- + +import std.stdio, std.conv; + +/// Maps base 256 to base 85 +static string encoder = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#"; + +/** + * Encodes a byte array as a string whose charset is defined by encoder. + * + * Modified version of the reference implementation of Z85_encode. + * The tailling value is integrated to the input data before encoding: + * - the tail is added as an ubyte[tail_size] + * - the tail size is indicated in the first slot of an additional ubyte[4]. + * Finally the encoded text contains up to 7 * 5 / 4 chars in more from the original z85 form. + * + * This modified version is destructive for the input data. + */ +char[] Z85_encode(ref ubyte[] input) +in +{ + assert(input.length); +} +body +{ + // appends the tail things + ubyte[] tail; + ubyte added = 4 - (input.length % 4); + tail.length = added + 4; + tail[added] = added; + input ~= tail; + assert(input.length % 4 == 0); + + // reference implementation + size_t encoded_size = input.length * 5 / 4; + char[] encoded; + encoded.length = encoded_size; + uint char_nbr; + uint byte_nbr; + uint value; + while (byte_nbr < input.length) + { + value = value * 256 + input [byte_nbr++]; + if ((byte_nbr & 3) == 0) + { + uint divisor = 85 * 85 * 85 * 85; + while (divisor) + { + encoded[char_nbr++] = encoder[value / divisor % 85]; + divisor /= 85; + } + value = 0; + } + } + assert (char_nbr == encoded_size); + return encoded; +} + +/// Maps base 85 to base 256 +static immutable ubyte[96] z85_decoder = [ + 0x00, 0x44, 0x00, 0x54, 0x53, 0x52, 0x48, 0x00, + 0x4B, 0x4C, 0x46, 0x41, 0x00, 0x3F, 0x3E, 0x45, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x40, 0x00, 0x49, 0x42, 0x4A, 0x47, + 0x51, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, + 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, + 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, + 0x3B, 0x3C, 0x3D, 0x4D, 0x00, 0x4E, 0x43, 0x00, + 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, + 0x21, 0x22, 0x23, 0x4F, 0x00, 0x50, 0x00, 0x00 +]; + +/** + * Decodes a string as a byte array. + * + * Modified version of the reference implementation of Z85_decode. + * It automatically handles the tail added to grant the 4/5 i/o ratio, + * as described in Z85_endcode() + */ +ubyte[] Z85_decode(in char[] input) +in +{ + assert(input.length % 5 == 0); +} +body +{ + // reference implementation + size_t decoded_size = input.length * 4 / 5; + ubyte[] decoded; + decoded.length = decoded_size; + uint byte_nbr; + uint char_nbr; + uint value; + while (char_nbr < input.length) + { + value = value * 85 + z85_decoder [cast(ubyte) input[char_nbr++] - 32]; + if (char_nbr % 5 == 0) + { + uint divisor = 256 * 256 * 256; + while (divisor) + { + decoded[byte_nbr++] = value / divisor % 256; + divisor /= 256; + } + value = 0; + } + } + assert (byte_nbr == decoded_size); + + // removes the tail things. + ubyte added = decoded[$-4]; + decoded = decoded[0..$- (4 + added)]; + + return decoded; +} diff --git a/src/ce_editor.pas b/src/ce_editor.pas index 22ae3e09..622e728f 100644 --- a/src/ce_editor.pas +++ b/src/ce_editor.pas @@ -125,7 +125,6 @@ end; {$REGION ICEMultiDocObserver ---------------------------------------------------} procedure TCEEditorWidget.docNew(aDoc: TCESynMemo); begin - fDoc := aDoc; end; procedure TCEEditorWidget.docClosing(aDoc: TCESynMemo); diff --git a/src/ce_libman.pas b/src/ce_libman.pas index fb7257d6..7dd06813 100644 --- a/src/ce_libman.pas +++ b/src/ce_libman.pas @@ -78,12 +78,12 @@ begin end; procedure TLibraryManager.addDubPackages; -var - path: string; +//var + //path: string; begin if not exeInSysPath('dub' + exeExt) then exit; // - path := getUserDocPath + 'dub' + DirectorySeparator + 'packages'; + //path := getUserDocPath + 'dub' + DirectorySeparator + 'packages'; { get folders foreach folder in folders diff --git a/src/ce_projconf.lfm b/src/ce_projconf.lfm index 06652b49..2796c91c 100644 --- a/src/ce_projconf.lfm +++ b/src/ce_projconf.lfm @@ -137,7 +137,7 @@ inherited CEProjectConfigurationWidget: TCEProjectConfigurationWidget Width = 5 AutoSnap = False end - object Grid: TTIPropertyGrid + object inspector: TTIPropertyGrid Left = 165 Height = 239 Top = 0 @@ -147,8 +147,8 @@ inherited CEProjectConfigurationWidget: TCEProjectConfigurationWidget Filter = [tkInteger, tkChar, tkEnumeration, tkFloat, tkSet, tkMethod, tkSString, tkLString, tkAString, tkWString, tkVariant, tkArray, tkRecord, tkInterface, tkClass, tkWChar, tkBool, tkInt64, tkQWord, tkDynArray, tkInterfaceRaw, tkProcVar, tkUString, tkUChar, tkHelper] Indent = 16 NameFont.Color = clWindowText - OnEditorFilter = GridEditorFilter - OnModified = GridModified + OnEditorFilter = inspectorEditorFilter + OnModified = inspectorModified PreferredSplitterX = 145 SplitterX = 145 ValueFont.Color = clGreen diff --git a/src/ce_projconf.pas b/src/ce_projconf.pas index 9c321d7a..6d5f14a5 100644 --- a/src/ce_projconf.pas +++ b/src/ce_projconf.pas @@ -24,14 +24,14 @@ type btnDelConf: TSpeedButton; btnCloneConf: TSpeedButton; Splitter1: TSplitter; - Grid: TTIPropertyGrid; + inspector: TTIPropertyGrid; Tree: TTreeView; procedure btnAddConfClick(Sender: TObject); procedure btnDelConfClick(Sender: TObject); procedure btnCloneCurrClick(Sender: TObject); procedure btnSyncEditClick(Sender: TObject); - procedure GridEditorFilter(Sender: TObject; aEditor: TPropertyEditor;var aShow: boolean); - procedure GridModified(Sender: TObject); + procedure inspectorEditorFilter(Sender: TObject; aEditor: TPropertyEditor;var aShow: boolean); + procedure inspectorModified(Sender: TObject); procedure selConfChange(Sender: TObject); procedure TreeChange(Sender: TObject; Node: TTreeNode); procedure GridFilter(Sender: TObject; aEditor: TPropertyEditor;var aShow: boolean); @@ -80,7 +80,7 @@ begin png.Free; end; Tree.Selected := Tree.Items.GetLastNode; - Grid.OnEditorFilter := @GridFilter; + inspector.OnEditorFilter := @GridFilter; // EntitiesConnector.addObserver(self); end; @@ -105,8 +105,8 @@ procedure TCEProjectConfigurationWidget.projClosing(aProject: TCEProject); begin if fProj <> aProject then exit; - Grid.TIObject := nil; - Grid.ItemIndex := -1; + inspector.TIObject := nil; + inspector.ItemIndex := -1; self.selConf.Clear; syncroMode := false; fProj := nil; @@ -148,10 +148,10 @@ end; procedure TCEProjectConfigurationWidget.TreeChange(Sender: TObject; Node: TTreeNode); begin - Grid.TIObject := getGridTarget; + inspector.TIObject := getGridTarget; end; -procedure TCEProjectConfigurationWidget.GridEditorFilter(Sender: TObject; +procedure TCEProjectConfigurationWidget.inspectorEditorFilter(Sender: TObject; aEditor: TPropertyEditor; var aShow: boolean); begin if aEditor.ClassType = TCollectionPropertyEditor then aShow := false; @@ -184,7 +184,7 @@ begin fSyncroPropValue := Value; end; -procedure TCEProjectConfigurationWidget.GridModified(Sender: TObject); +procedure TCEProjectConfigurationWidget.inspectorModified(Sender: TObject); var propstr: string; src_list, trg_list: rttiutils.TPropInfoList; @@ -195,14 +195,14 @@ var begin if fProj = nil then exit; if not fSyncroMode then exit; - if Grid.TIObject = nil then exit; - if Grid.ItemIndex = -1 then exit; + if inspector.TIObject = nil then exit; + if inspector.ItemIndex = -1 then exit; // storage := nil; src_prop:= nil; trg_prop:= nil; trg_obj := nil; - propstr := Grid.PropertyPath(Grid.ItemIndex); + propstr := inspector.PropertyPath(inspector.ItemIndex); storage := rttiutils.TPropsStorage.Create; storage.OnReadString := @syncroSetPropAsString; storage.OnWriteString := @syncroGetPropAsString; @@ -218,23 +218,23 @@ begin // skip current config if i = fProj.ConfigurationIndex then continue; // find target persistent - if Grid.TIObject = fProj.currentConfiguration.messagesOptions then + if inspector.TIObject = fProj.currentConfiguration.messagesOptions then trg_obj := fProj.configuration[i].messagesOptions else - if Grid.TIObject = fProj.currentConfiguration.debugingOptions then + if inspector.TIObject = fProj.currentConfiguration.debugingOptions then trg_obj := fProj.configuration[i].debugingOptions else - if Grid.TIObject = fProj.currentConfiguration.documentationOptions then + if inspector.TIObject = fProj.currentConfiguration.documentationOptions then trg_obj := fProj.configuration[i].documentationOptions else - if Grid.TIObject = fProj.currentConfiguration.outputOptions then + if inspector.TIObject = fProj.currentConfiguration.outputOptions then trg_obj := fProj.configuration[i].outputOptions else - if Grid.TIObject = fProj.currentConfiguration.otherOptions then + if inspector.TIObject = fProj.currentConfiguration.otherOptions then trg_obj := fProj.configuration[i].otherOptions else - if Grid.TIObject = fProj.currentConfiguration.pathsOptions then + if inspector.TIObject = fProj.currentConfiguration.pathsOptions then trg_obj := fProj.configuration[i].pathsOptions else - if Grid.TIObject = fProj.currentConfiguration.preBuildProcess then + if inspector.TIObject = fProj.currentConfiguration.preBuildProcess then trg_obj := fProj.configuration[i].preBuildProcess else - if Grid.TIObject = fProj.currentConfiguration.postBuildProcess then + if inspector.TIObject = fProj.currentConfiguration.postBuildProcess then trg_obj := fProj.configuration[i].postBuildProcess else - if Grid.TIObject = fProj.currentConfiguration.runOptions then + if inspector.TIObject = fProj.currentConfiguration.runOptions then trg_obj := fProj.configuration[i].runOptions else continue; // find target property @@ -279,8 +279,8 @@ begin if fProj.OptionsCollection.Count = 1 then exit; // beginUpdateByEvent; - Grid.TIObject := nil; - Grid.Clear; + inspector.TIObject := nil; + inspector.Clear; Invalidate; fProj.OptionsCollection.Delete(selConf.ItemIndex); fProj.ConfigurationIndex := 0; @@ -363,7 +363,7 @@ begin selConf.Items.Add(fProj.configuration[i].name); selConf.ItemIndex := fProj.ConfigurationIndex; - Grid.TIObject := getGridTarget; + inspector.TIObject := getGridTarget; end; {$ENDREGION --------------------------------------------------------------------} diff --git a/src/ce_resman.lfm b/src/ce_resman.lfm index 5bb907f9..3d06af60 100644 --- a/src/ce_resman.lfm +++ b/src/ce_resman.lfm @@ -63,14 +63,14 @@ inherited CEResmanWidget: TCEResmanWidget ClientHeight = 295 ClientWidth = 424 TabOrder = 1 - object lstItems: TListBox + object lstRes: TListBox Left = 0 Height = 295 Top = 0 Width = 160 Align = alLeft ItemHeight = 0 - OnSelectionChange = lstItemsSelectionChange + OnSelectionChange = lstResSelectionChange TabOrder = 0 end object Splitter1: TSplitter @@ -80,7 +80,7 @@ inherited CEResmanWidget: TCEResmanWidget Width = 5 AutoSnap = False end - object propsEd: TTIPropertyGrid + object inspector: TTIPropertyGrid Left = 165 Height = 295 Top = 0 @@ -90,7 +90,7 @@ inherited CEResmanWidget: TCEResmanWidget Filter = [tkInteger, tkChar, tkEnumeration, tkFloat, tkSet, tkMethod, tkSString, tkLString, tkAString, tkWString, tkVariant, tkArray, tkRecord, tkInterface, tkClass, tkObject, tkWChar, tkBool, tkInt64, tkQWord, tkDynArray, tkInterfaceRaw, tkProcVar, tkUString, tkUChar, tkHelper] Indent = 16 NameFont.Color = clWindowText - OnModified = propsEdModified + OnModified = inspectorModified ValueFont.Color = clMaroon end end diff --git a/src/ce_resman.pas b/src/ce_resman.pas index a4c04f84..ba7ae063 100644 --- a/src/ce_resman.pas +++ b/src/ce_resman.pas @@ -13,7 +13,7 @@ type TResourceType = (aFile, aFolder); - TResourceFormat = (bytes, utf8, base16, base64); + TResourceFormat = (bytes, utf8, base16, base64, z85, e7F); TResourceItem = class(TCollectionItem) private @@ -34,43 +34,47 @@ type * Represents a resource script. The resource script use the * JSON format for a better compatibility with the tool. *) - TResourceItems = class(TWritableComponent) + TResources = class(TWritableComponent) private - fItems: TCollection; - procedure setItems(aValue: TCollection); + fResources: TCollection; + procedure setResources(aValue: TCollection); + function getResource(index: Integer): TResourceItem; published - property items: TCollection read fItems write setItems; + property resources: TCollection read fResources write setResources; public constructor create(aOwner: TComponent); override; destructor destroy; override; // overides the component streaming methods to use JSON instead of lfm procedure saveToFile(const aFilename: string); override; procedure loadFromFile(const aFilename: string); override; + property resource[index: Integer]: TResourceItem read getResource; default; end; { TCEResmanWidget } TCEResmanWidget = class(TCEWidget, ICEProjectObserver, ICEMultiDocObserver) BtnAddItem: TBitBtn; btnRemItem: TBitBtn; - lstItems: TListBox; + lstRes: TListBox; Panel1: TPanel; Panel2: TPanel; - propsEd: TTIPropertyGrid; + inspector: TTIPropertyGrid; Splitter1: TSplitter; procedure BtnAddItemClick(Sender: TObject); procedure btnRemItemClick(Sender: TObject); - procedure lstItemsSelectionChange(Sender: TObject; User: boolean); - procedure propsEdModified(Sender: TObject); + procedure lstResSelectionChange(Sender: TObject; User: boolean); + procedure inspectorModified(Sender: TObject); private fProj: TCEProject; fDoc: TCESynMemo; - fResourceItems: TResourceItems; + fResourceItems: TResources; + fLogMessager: TCELogMessageSubject; // try to load the json resource script for the current doc procedure loadDocResJson; // try to save the json resource script for the current doc procedure saveDocResJson; - procedure refreshItemList; - procedure updateIdentifierList; + procedure clearInspector; + procedure rebuildResList; + procedure updateResList; procedure genResources; // procedure projNew(aProject: TCEProject); @@ -85,25 +89,31 @@ type procedure docClosing(aDoc: TCESynMemo); public constructor create(aOwner: TComponent); override; + destructor destroy; override; end; implementation {$R *.lfm} -{$REGION TResourceItems --------------------------------------------------------} -constructor TResourceItems.Create(aOwner: TCOmponent); +{$REGION TResources ------------------------------------------------------------} +constructor TResources.Create(aOwner: TCOmponent); begin inherited; - fItems := TCollection.Create(TResourceItem); + fResources := TCollection.Create(TResourceItem); end; -destructor TResourceItems.destroy; +destructor TResources.destroy; begin - fItems.Free; + fResources.Free; inherited; end; -procedure TResourceItems.saveToFile(const aFilename: string); +function TResources.getResource(index: Integer): TResourceItem; +begin + result := TResourceItem(fResources.Items[index]); +end; + +procedure TResources.saveToFile(const aFilename: string); var json_streamer: TJSONStreamer; json_string: TJSONStringType; @@ -129,7 +139,7 @@ begin afterSave; end; -procedure TResourceItems.loadFromFile(const aFilename: string); +procedure TResources.loadFromFile(const aFilename: string); var json_destreamer: TJSONDeStreamer; json_string: TJSONStringType; @@ -152,18 +162,20 @@ begin afterLoad; end; -procedure TResourceItems.setItems(aValue: TCollection); +procedure TResources.setResources(aValue: TCollection); begin - fItems.Assign(aValue); + fResources.Assign(aValue); end; {$ENDREGION} +{$REGION Standard Comp/Obj------------------------------------------------------} constructor TCEResmanWidget.create(aOwner: TComponent); var png: TPortableNetworkGraphic; begin inherited; - fResourceItems := TResourceItems.create(self); + fLogMessager := TCELogMessageSubject.create; + fResourceItems := TResources.create(self); // png := TPortableNetworkGraphic.Create; try @@ -176,6 +188,13 @@ begin end; end; +destructor TCEResmanWidget.destroy; +begin + fLogMessager.Free; + inherited; +end; +{$ENDREGION} + {$REGION ICEProjectObserver ----------------------------------------------------} procedure TCEResmanWidget.projNew(aProject: TCEProject); begin @@ -193,10 +212,10 @@ procedure TCEResmanWidget.projClosing(aProject: TCEProject); begin if fProj <> aProject then exit; fProj := nil; - propsEd.TIObject := nil; - propsEd.ItemIndex := -1; - fResourceItems.Items.Clear; - refreshItemList; + inspector.TIObject := nil; + inspector.ItemIndex := -1; + fResourceItems.resources.Clear; + rebuildResList; end; procedure TCEResmanWidget.projFocused(aProject: TCEProject); @@ -229,7 +248,7 @@ begin // saveDocResJson; fDoc := nil; - refreshItemList; + rebuildResList; end; procedure TCEResmanWidget.docFocused(aDoc: TCESynMemo); @@ -240,20 +259,20 @@ end; {$ENDREGION} {$REGION Resources things -----------------------------------------------------} -procedure TCEResmanWidget.lstItemsSelectionChange(Sender: TObject; User: boolean); +procedure TCEResmanWidget.lstResSelectionChange(Sender: TObject; User: boolean); begin - if lstItems.Count = 0 then exit; - if lstItems.ItemIndex = -1 then exit; + if lstRes.Count = 0 then exit; + if lstRes.ItemIndex = -1 then exit; // - propsEd.TIObject := TPersistent(lstItems.Items.Objects[lstItems.ItemIndex]); + inspector.TIObject := fResourceItems[lstRes.ItemIndex]; end; -procedure TCEResmanWidget.propsEdModified(Sender: TObject); +procedure TCEResmanWidget.inspectorModified(Sender: TObject); begin - if propsEd.ItemIndex = -1 then + if inspector.ItemIndex = -1 then exit; - if propsEd.Rows[propsEd.ItemIndex].Name = 'identifier' then - updateIdentifierList; + if inspector.Rows[inspector.ItemIndex].Name = 'identifier' then + updateResList; saveDocResJson; end; @@ -261,20 +280,20 @@ procedure TCEResmanWidget.BtnAddItemClick(Sender: TObject); var item: TResourceItem; begin - item := TResourceItem(fResourceItems.items.Add); + item := TResourceItem(fResourceItems.resources.Add); item.identifier := format('' ,[item.ID]); - refreshItemList; + rebuildResList; saveDocResJson; end; procedure TCEResmanWidget.btnRemItemClick(Sender: TObject); begin - if lstItems.ItemIndex = -1 then + if lstRes.ItemIndex = -1 then exit; - propsEd.TIObject := nil; - propsEd.ItemIndex := -1; - fResourceItems.items.Delete(lstItems.ItemIndex); - refreshItemList; + inspector.TIObject := nil; + inspector.ItemIndex := -1; + fResourceItems.resources.Delete(lstRes.ItemIndex); + rebuildResList; saveDocResJson; end; @@ -287,12 +306,12 @@ begin if not fProj.isProjectSource(fDoc.fileName) then exit; // fname := stripFileExt(fDoc.fileName) + '.resman'; - propsEd.TIObject := nil; - propsEd.ItemIndex := -1; - fResourceItems.Items.Clear; + inspector.TIObject := nil; + inspector.ItemIndex := -1; + fResourceItems.resources.Clear; if fileExists(fname) then fResourceItems.loadFromFile(fname); - refreshItemList; + rebuildResList; end; procedure TCEResmanWidget.saveDocResJson; @@ -304,7 +323,7 @@ begin if not fProj.isProjectSource(fDoc.fileName) then exit; // fname := stripFileExt(fDoc.fileName) + '.resman'; - if fResourceItems.Items.Count = 0 then exit; + if fResourceItems.resources.Count = 0 then exit; // fResourceItems.saveToFile(fname); end; @@ -312,17 +331,19 @@ end; procedure TCEResmanWidget.genResources; var proc: TProcess; - fname: string; + str: TStringList; + fname, msg: string; i: Integer; begin if fProj = nil then exit; if not exeInSysPath('resman' + exeExt) then exit; // - proc := Tprocess.Create(nil); + proc := TProcess.Create(nil); + str := TStringList.Create; try proc.Executable:= 'resman' + exeExt; - //proc.Options := [poUsePipes, poStderrToOutPut]; - //proc.ShowWindow := swoHIDE; + proc.Options := [poUsePipes, poStderrToOutPut]; + proc.ShowWindow := swoHIDE; proc.Parameters.Add('-v'); for i := 0 to fProj.Sources.Count-1 do begin @@ -331,41 +352,45 @@ begin if not FileExists(fname) then continue; proc.Parameters.Add(fname); end; + msg := 'generating the resources...'; + subjLmFromString(fLogMessager, msg, fProj, amcProj, amkInf); proc.Execute; - while proc.Running do; - // output to project message... + while proc.Running do begin + processOutputToStrings(proc, str); + for msg in str do + subjLmFromString(fLogMessager, msg, fProj, amcProj, amkAuto); + end; finally proc.Free; + str.Free; end; end; -procedure TCEResmanWidget.refreshItemList; -var - i: Integer; - item: TResourceItem; +procedure TCEResmanWidget.clearInspector; begin - propsEd.TIObject := nil; - propsEd.ItemIndex := -1; - lstItems.Items.Clear; - for i:= 0 to fResourceItems.items.Count-1 do - begin - item := TResourceItem(fResourceItems.items.Items[i]); - lstItems.Items.AddObject(item.identifier, item); - end; - if lstItems.Count > 0 then - lstItems.ItemIndex := 0; + inspector.TIObject := nil; + inspector.ItemIndex := -1; end; -procedure TCEResmanWidget.updateIdentifierList; +procedure TCEResmanWidget.rebuildResList; var i: Integer; - item: TResourceItem; begin - for i:= 0 to fResourceItems.items.Count-1 do - begin - item := TResourceItem(fResourceItems.items.Items[i]); - lstItems.Items.Strings[i] := item.identifier; - end; + clearInspector; + lstRes.Items.Clear; + // + for i:= 0 to fResourceItems.resources.Count-1 do + lstRes.AddItem(fResourceItems[i].identifier, nil); + if lstRes.Count > 0 then + lstRes.ItemIndex := 0; +end; + +procedure TCEResmanWidget.updateResList; +var + i: Integer; +begin + for i:= 0 to fResourceItems.resources.Count-1 do + lstRes.Items.Strings[i] := fResourceItems[i].identifier; end; {$ENDREGION} diff --git a/src/ce_tools.pas b/src/ce_tools.pas index 3dcc52c7..afa7c9c3 100644 --- a/src/ce_tools.pas +++ b/src/ce_tools.pas @@ -63,7 +63,7 @@ type function addTool: TCEToolItem; procedure executeTool(aTool: TCEToolItem); overload; procedure executeTool(aToolIndex: Integer); overload; - property tool[index: integer]: TCEToolItem read getTool; + property tool[index: integer]: TCEToolItem read getTool; default; end; const diff --git a/src/ce_toolseditor.pas b/src/ce_toolseditor.pas index b79cee5f..fb32a0da 100644 --- a/src/ce_toolseditor.pas +++ b/src/ce_toolseditor.pas @@ -32,8 +32,9 @@ type procedure propsEdModified(Sender: TObject); private procedure executeSelectedTool; - procedure DataToGui; - procedure updateNames; + procedure clearInspector; + procedure rebuildToolList; + procedure updateToolList; public constructor create(aOwner: TComponent); override; end; @@ -61,29 +62,34 @@ begin finally png.free; end; - DataToGui; + rebuildToolList; end; -procedure TCEToolsEditorWidget.updateNames; +procedure TCEToolsEditorWidget.clearInspector; +begin + propsEd.TIObject := nil; + propsEd.ItemIndex := -1; +end; + +procedure TCEToolsEditorWidget.rebuildToolList; +var + i: integer; +begin + clearInspector; + lstTools.Clear; + // + for i := 0 to CustomTools.tools.Count-1 do + lstTools.AddItem(CustomTools[i].toolAlias, nil); + if lstTools.Count > 0 then + lstTools.ItemIndex := 0; +end; + +procedure TCEToolsEditorWidget.updateToolList; var i: Integer; begin for i := 0 to CustomTools.tools.Count-1 do - lstTools.Items.Strings[i] := CustomTools.tool[i].toolAlias; -end; - -procedure TCEToolsEditorWidget.DataToGui; -var - i: integer; -begin - propsEd.TIObject := nil; - propsEd.ItemIndex := -1; - lstTools.Clear; - // - for i := 0 to CustomTools.tools.Count-1 do - lstTools.AddItem(CustomTools.tool[i].toolAlias, nil); - if lstTools.Count > 0 then - lstTools.ItemIndex := 0; + lstTools.Items.Strings[i] := CustomTools[i].toolAlias; end; procedure TCEToolsEditorWidget.lstToolsSelectionChange(Sender: TObject; @@ -91,7 +97,7 @@ procedure TCEToolsEditorWidget.lstToolsSelectionChange(Sender: TObject; begin if lstTools.ItemIndex = -1 then exit; - propsEd.TIObject := CustomTools.tool[lstTools.ItemIndex]; + propsEd.TIObject := CustomTools[lstTools.ItemIndex]; end; procedure TCEToolsEditorWidget.propsEdModified(Sender: TObject); @@ -99,23 +105,22 @@ begin if propsEd.ItemIndex = -1 then exit; if propsEd.Rows[propsEd.ItemIndex].Name = 'toolAlias' then - updateNames; + updateToolList; end; procedure TCEToolsEditorWidget.BtnAddToolClick(Sender: TObject); begin CustomTools.addTool; - DataToGui; + rebuildToolList; end; procedure TCEToolsEditorWidget.btnRemToolClick(Sender: TObject); begin if lstTools.ItemIndex = -1 then exit; - propsEd.TIObject := nil; - propsEd.ItemIndex := -1; + clearInspector; CustomTools.tools.Delete(lstTools.ItemIndex); - DataToGui; + rebuildToolList; end; procedure TCEToolsEditorWidget.btnMoveUpClick(Sender: TObject); @@ -125,7 +130,7 @@ begin // CustomTools.tools.Exchange(lstTools.ItemIndex, lstTools.ItemIndex - 1); lstTools.ItemIndex := lstTools.ItemIndex - 1; - updateNames; + updateToolList; end; procedure TCEToolsEditorWidget.btnMoveDownClick(Sender: TObject); @@ -135,7 +140,7 @@ begin // CustomTools.tools.Exchange(lstTools.ItemIndex, lstTools.ItemIndex + 1); lstTools.ItemIndex := lstTools.ItemIndex + 1; - updateNames; + updateToolList; end; procedure TCEToolsEditorWidget.executeSelectedTool;