diff --git a/dlanguilib.visualdproj b/dlanguilib.visualdproj index e77e75b8..c53bae16 100644 --- a/dlanguilib.visualdproj +++ b/dlanguilib.visualdproj @@ -300,6 +300,8 @@ + + diff --git a/examples/example1/main.d b/examples/example1/main.d index 1ab079e2..8f2901d6 100644 --- a/examples/example1/main.d +++ b/examples/example1/main.d @@ -31,13 +31,17 @@ extern (C) int UIAppMain(string[] args) { // setup resource dir version (Windows) { string resourceDir = exePath() ~ "..\\res\\"; + string i18nDir = exePath() ~ "..\\res\\i18n\\"; } else { string resourceDir = exePath() ~ "../../res/"; + string i18nDir = exePath() ~ "../res/i18n/"; } string[] imageDirs = [ resourceDir ]; drawableCache.resourcePaths = imageDirs; + i18n.resourceDir = i18nDir; + i18n.load("ru.ini", "en.ini"); // create window Window window = Platform.instance().createWindow("My Window", null); @@ -46,7 +50,7 @@ extern (C) int UIAppMain(string[] args) { LinearLayout layout = new LinearLayout(); layout.addChild((new TextWidget()).textColor(0x00802000).text("Text widget 0")); layout.addChild((new TextWidget()).textColor(0x40FF4000).text("Text widget")); - layout.addChild((new Button("BTN1")).text("Button1")); //.textColor(0x40FF4000) + layout.addChild((new Button("BTN1")).textResource("EXIT")); //.textColor(0x40FF4000) diff --git a/examples/example1/res/i18n/en.ini b/examples/example1/res/i18n/en.ini new file mode 100644 index 00000000..d60ac061 --- /dev/null +++ b/examples/example1/res/i18n/en.ini @@ -0,0 +1 @@ +EXIT=Exit diff --git a/examples/example1/res/i18n/ru.ini b/examples/example1/res/i18n/ru.ini new file mode 100644 index 00000000..fc1e0776 --- /dev/null +++ b/examples/example1/res/i18n/ru.ini @@ -0,0 +1 @@ +EXIT=Выход diff --git a/src/dlangui/all.d b/src/dlangui/all.d index d401f6ea..aed7e822 100644 --- a/src/dlangui/all.d +++ b/src/dlangui/all.d @@ -9,3 +9,4 @@ public import dlangui.widgets.controls; public import dlangui.widgets.layouts; public import dlangui.widgets.lists; public import dlangui.graphics.fonts; +public import dlangui.core.i18n; diff --git a/src/dlangui/core/i18n.d b/src/dlangui/core/i18n.d new file mode 100644 index 00000000..fa9b2e01 --- /dev/null +++ b/src/dlangui/core/i18n.d @@ -0,0 +1,166 @@ +module dlangui.core.i18n; + +import dlangui.core.logger; +import std.utf; + +/// container for UI string - either raw value or string resource ID +struct UIString { + /// if not null, use it, otherwise lookup by id + dstring _value; + /// id to find value in translator + string _id; + @property string id() const { return _id; } + @property void id(string ID) { + _id = ID; + _value = null; + } + /// get value (either raw or translated by id) + @property dstring value() const { + if (_value !is null) + return _value; + if (_id is null) + return null; + // translate ID to dstring + return i18n.get(_id); + } + /// set raw value + @property void value(dstring newValue) { + _value = newValue; + } + /// assign raw value + ref UIString opAssign(dstring rawValue) { + _value = rawValue; + _id = null; + return this; + } + /// assign ID + ref UIString opAssign(string ID) { + _id = ID; + _value = null; + return this; + } + /// default conversion to dstring + alias value this; +} + +public __gshared UIStringTranslator i18n = new UIStringTranslator(); +//static shared this() { +// i18n = new UIStringTranslator(); +//} + +class UIStringTranslator { + private UIStringList _main; + private UIStringList _fallback; + private string _resourceDir; + /// get i18n resource directory + @property string resourceDir() { return _resourceDir; } + /// set i18n resource directory + @property void resourceDir(string dir) { _resourceDir = dir; } + + /// convert resource path - зкуpend resource dir if necessary + string convertResourcePath(string filename) { + if (filename is null) + return null; + bool hasPathDelimiters = false; + foreach(char ch; filename) + if (ch == '/' || ch == '\\') + hasPathDelimiters = true; + if (!hasPathDelimiters && _resourceDir !is null) + return _resourceDir ~ filename; + return filename; + } + + this() { + _main = new UIStringList(); + _fallback = new UIStringList(); + } + /// load translation file(s) + bool load(string mainFilename, string fallbackFilename = null) { + _main.clear(); + _fallback.clear(); + bool res = _main.load(convertResourcePath(mainFilename)); + if (fallbackFilename !is null) { + res = _fallback.load(convertResourcePath(fallbackFilename)) || res; + } + return res; + } + /// translate string ID to string (returns "UNTRANSLATED: id" for missing values) + dstring get(string id) { + if (id is null) + return null; + dstring s = _main.get(id); + if (s !is null) + return s; + s = _fallback.get(id); + if (s !is null) + return s; + return "UNTRANSLATED: "d ~ toUTF32(id); + } +} + +/// UI string translator +class UIStringList { + private dstring[string] _map; + /// remove all items + void clear() { + _map.clear(); + } + /// set item value + void set(string id, dstring value) { + _map[id] = value; + } + /// get item value, null if translation is not found for id + dstring get(string id) const { + if (id in _map) + return _map[id]; + return null; + } + /// load strings from stream + bool load(std.stream.InputStream stream) { + clear(); + dlangui.core.linestream.LineStream lines = dlangui.core.linestream.LineStream.create(stream, ""); + int count = 0; + for (;;) { + dchar[] s = lines.readLine(); + if (s is null) + break; + int eqpos = -1; + int firstNonspace = -1; + int lastNonspace = -1; + for (int i = 0; i < s.length; i++) + if (s[i] == '=') { + eqpos = i; + break; + } else if (s[i] != ' ' && s[i] != '\t') { + if (firstNonspace == -1) + firstNonspace = i; + lastNonspace = i; + } + if (eqpos > 0 && firstNonspace != -1) { + string id = toUTF8(s[firstNonspace .. lastNonspace + 1]); + dstring value = s[eqpos + 1 .. $].dup; + set(id, value); + count++; + } + } + return count > 0; + } + /// load strings from file (utf8, id=value lines) + bool load(string filename) { + import std.stream; + import std.file; + try { + Log.d("Loading string resources from file ", filename); + if (!exists(filename) && isFile(filename)) { + Log.e("File does not exist: ", filename); + return false; + } + std.stream.File f = new std.stream.File(filename); + scope(exit) { f.close(); } + return load(f); + } catch (StreamFileException e) { + Log.e("Cannot read string resources from file ", filename); + } + return false; + } +} diff --git a/src/dlangui/core/linestream.d b/src/dlangui/core/linestream.d new file mode 100644 index 00000000..58c8cd54 --- /dev/null +++ b/src/dlangui/core/linestream.d @@ -0,0 +1,584 @@ +module dlangui.core.linestream; + +import std.stream; +import std.stdio; +import std.conv; + +class LineStream { + public enum EncodingType { + ASCII, + UTF8, + UTF16BE, + UTF16LE, + UTF32BE, + UTF32LE + }; + + InputStream _stream; + string _filename; + ubyte[] _buf; // stream reading buffer + uint _pos; // reading position of stream buffer + uint _len; // number of bytes in stream buffer + bool _streamEof; // true if input stream is in EOF state + uint _line; // current line number + + uint _textPos; // start of text line in text buffer + uint _textLen; // position of last filled char in text buffer + 1 + dchar[] _textBuf; // text buffer + bool _eof; // end of file, no more lines + + @property string filename() { return _filename; } + @property uint line() { return _line; } + @property EncodingType encoding() { return _encoding; } + @property int errorCode() { return _errorCode; } + @property string errorMessage() { return _errorMessage; } + @property int errorLine() { return _errorLine; } + @property int errorPos() { return _errorPos; } + + immutable EncodingType _encoding; + + int _errorCode; + string _errorMessage; + uint _errorLine; + uint _errorPos; + + protected this(InputStream stream, string filename, EncodingType encoding, ubyte[] buf, uint offset, uint len) { + _filename = filename; + _stream = stream; + _encoding = encoding; + _buf = buf; + _len = len; + _pos = offset; + _streamEof = _stream.eof; + } + + // returns slice of bytes available in buffer + uint readBytes() { + uint bytesLeft = _len - _pos; + if (_streamEof || bytesLeft > QUARTER_BYTE_BUFFER_SIZE) + return bytesLeft; + if (_pos > 0) { + for (uint i = 0; i < bytesLeft; i++) + _buf[i] = _buf[i + _pos]; + _len = bytesLeft; + _pos = 0; + } + uint bytesRead = cast(uint)_stream.read(_buf[_len .. BYTE_BUFFER_SIZE]); + _len += bytesRead; + _streamEof = _stream.eof; + return _len - _pos; //_buf[_pos .. _len]; + } + + // when bytes consumed from byte buffer, call this method to update position + void consumedBytes(uint count) { + _pos += count; + } + + // reserve text buffer for specified number of characters, and return pointer to first free character in buffer + dchar * reserveTextBuf(uint len) { + // create new text buffer if necessary + if (_textBuf == null) { + if (len < TEXT_BUFFER_SIZE) + len = TEXT_BUFFER_SIZE; + _textBuf = new dchar[len]; + return _textBuf.ptr; + } + uint spaceLeft = cast(uint)_textBuf.length - _textLen; + if (spaceLeft >= len) + return _textBuf.ptr + _textLen; + // move text to beginning of buffer, if necessary + if (_textPos > _textBuf.length / 2) { + uint charCount = _textLen - _textPos; + dchar * p = _textBuf.ptr; + for (uint i = 0; i < charCount; i++) + p[i] = p[i + _textPos]; + _textLen = charCount; + _textPos = 0; + } + // resize buffer if necessary + if (_textLen + len > _textBuf.length) { + // resize buffer + uint newsize = cast(uint)_textBuf.length * 2; + if (newsize < _textLen + len) + newsize = _textLen + len; + _textBuf.length = newsize; + } + return _textBuf.ptr + _textLen; + } + + void appendedText(uint len) { + //writeln("appended ", len, " chars of text"); //:", _textBuf[_textLen .. _textLen + len]); + _textLen += len; + } + + void setError(int code, string message, uint errorLine, uint errorPos) { + _errorCode = code; + _errorMessage = message; + _errorLine = errorLine; + _errorPos = errorPos; + } + + // override to decode text + abstract uint decodeText(); + + immutable static uint LINE_POSITION_UNDEFINED = uint.max; + public dchar[] readLine() { + if (_errorCode != 0) { + //writeln("error ", _errorCode, ": ", _errorMessage, " in line ", _errorLine); + return null; // error detected + } + if (_eof) { + //writeln("EOF found"); + return null; + } + _line++; + uint p = 0; + uint eol = LINE_POSITION_UNDEFINED; + uint eof = LINE_POSITION_UNDEFINED; + uint lastchar = LINE_POSITION_UNDEFINED; + do { + if (_errorCode != 0) { + //writeln("error ", _errorCode, ": ", _errorMessage, " in line ", _errorLine); + return null; // error detected + } + uint charsLeft = _textLen - _textPos; + if (p >= charsLeft) { + uint decodedChars = decodeText(); + if (_errorCode != 0) { + return null; // error detected + } + charsLeft = _textLen - _textPos; + if (decodedChars == 0) { + eol = charsLeft; + eof = charsLeft; + lastchar = charsLeft; + break; + } + } + for (; p < charsLeft; p++) { + dchar ch = _textBuf[_textPos + p]; + if (ch == 0x0D) { + lastchar = p; + if (p == charsLeft - 1) { + // need one more char to check if it's 0D0A or just 0D eol + //writeln("read one more char for 0D0A detection"); + decodeText(); + if (_errorCode != 0) { + return null; // error detected + } + charsLeft = _textLen - _textPos; + } + dchar ch2 = (p < charsLeft - 1) ? _textBuf[_textPos + p + 1] : 0; + if (ch2 == 0x0A) + eol = p + 2; + else + eol = p + 1; + break; + } else if (ch == 0x0A || ch == 0x2028 || ch == 0x2029) { + // single char eoln + lastchar = p; + eol = p + 1; + break; + } else if (ch == 0 || ch == 0x001A) { + // eof + //writeln("EOF char found"); + lastchar = p; + eol = eof = p + 1; + break; + } + } + } while (eol == LINE_POSITION_UNDEFINED); + uint lineStart = _textPos; + uint lineEnd = _textPos + lastchar; + _textPos += eol; // consume text + if (eof != LINE_POSITION_UNDEFINED) { + _eof = true; + //writeln("Setting eof flag. lastchar=", lastchar, ", p=", p, ", lineStart=", lineStart); + if (lineStart >= lineEnd) { + //writeln("lineStart >= lineEnd -- treat as eof"); + return null; // eof + } + } + // return slice with decoded line + return _textBuf[lineStart .. lineEnd]; + } + + immutable static int TEXT_BUFFER_SIZE = 1024; + immutable static int BYTE_BUFFER_SIZE = 512; + immutable static int QUARTER_BYTE_BUFFER_SIZE = BYTE_BUFFER_SIZE / 4; + + // factory for string parser + public static LineStream create(string code, string filename = "") { + uint len = cast(uint)code.length; + ubyte[] data = new ubyte[len + 3]; + for (uint i = 0; i < len; i++) + data[i + 3] = code[i]; + // BOM for UTF8 + data[0] = 0xEF; + data[1] = 0xBB; + data[2] = 0xBF; + MemoryStream stream = new MemoryStream(data); + return create(stream, filename); + } + + // factory + public static LineStream create(InputStream stream, string filename) { + ubyte[] buf = new ubyte[BYTE_BUFFER_SIZE]; + buf[0] = buf[1] = buf[2] = buf[3] = 0; + if (!stream.isOpen) + return null; + uint len = cast(uint)stream.read(buf); + if (buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) { + return new Utf8LineStream(stream, filename, buf, len); + } else if (buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0xFE && buf[3] == 0xFF) { + return new Utf32beLineStream(stream, filename, buf, len); + } else if (buf[0] == 0xFF && buf[1] == 0xFE && buf[2] == 0x00 && buf[3] == 0x00) { + return new Utf32leLineStream(stream, filename, buf, len); + } else if (buf[0] == 0xFE && buf[1] == 0xFF) { + return new Utf16beLineStream(stream, filename, buf, len); + } else if (buf[0] == 0xFF && buf[1] == 0xFE) { + return new Utf16leLineStream(stream, filename, buf, len); + } else { + return new AsciiLineStream(stream, filename, buf, len); + } + } + + protected bool invalidCharFlag; + protected void invalidCharError() { + uint pos = _textLen - _textPos + 1; + setError(1, "Invalid character in line " ~ to!string(_line) ~ ":" ~ to!string(pos), _line, pos); + } +} + + + +class AsciiLineStream : LineStream { + this(InputStream stream, string filename, ubyte[] buf, uint len) { + super(stream, filename, EncodingType.ASCII, buf, 0, len); + } + override uint decodeText() { + if (invalidCharFlag) { + invalidCharError(); + return 0; + } + uint bytesAvailable = readBytes(); + ubyte * bytes = _buf.ptr + _pos; + if (bytesAvailable == 0) + return 0; // nothing to decode + uint len = bytesAvailable; + ubyte* b = bytes; + dchar* text = reserveTextBuf(len); + uint i = 0; + for (; i < len; i++) { + ubyte ch = b[i]; + if (ch & 0x80) { + // invalid character + invalidCharFlag = true; + break; + } + text[i] = ch; + } + consumedBytes(i); + appendedText(i); + return len; + } + +} + +class Utf8LineStream : LineStream { + this(InputStream stream, string filename, ubyte[] buf, uint len) { + super(stream, filename, EncodingType.UTF8, buf, 3, len); + } + override uint decodeText() { + if (invalidCharFlag) { + invalidCharError(); + return 0; + } + uint bytesAvailable = readBytes(); + ubyte * bytes = _buf.ptr + _pos; + if (bytesAvailable == 0) + return 0; // nothing to decode + uint len = bytesAvailable; + uint chars = 0; + ubyte* b = bytes; + dchar* text = reserveTextBuf(len); + uint i = 0; + for (; i < len; i++) { + uint ch = 0; + uint ch0 = b[i]; + uint bleft = len - i; + uint bread = 0; + if (!(ch0 & 0x80)) { + // 0x00..0x7F single byte + ch = ch0; + bread = 1; + } if ((ch0 & 0xE0) == 0xC0) { + // two bytes 110xxxxx 10xxxxxx + if (bleft < 2) + break; + uint ch1 = b[i + 1]; + if ((ch1 & 0xC0) != 0x80) { + invalidCharFlag = true; + break; + } + ch = ((ch0 & 0x1F) << 6) | ((ch1 & 0x3F)); + bread = 2; + } if ((ch0 & 0xF0) == 0xE0) { + // three bytes 1110xxxx 10xxxxxx 10xxxxxx + if (bleft < 3) + break; + uint ch1 = b[i + 1]; + uint ch2 = b[i + 2]; + if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80) { + invalidCharFlag = true; + break; + } + ch = ((ch0 & 0x0F) << 12) | ((ch1 & 0x1F) << 6) | ((ch2 & 0x3F)); + bread = 3; + } if ((ch0 & 0xF8) == 0xF0) { + // four bytes 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + if (bleft < 4) + break; + uint ch1 = b[i + 1]; + uint ch2 = b[i + 2]; + uint ch3 = b[i + 3]; + if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80 || (ch3 & 0xC0) != 0x80) { + invalidCharFlag = true; + break; + } + ch = ((ch0 & 0x07) << 18) | ((ch1 & 0x3F) << 12) | ((ch2 & 0x3F) << 6) | ((ch3 & 0x3F)); + bread = 4; + } if ((ch0 & 0xFC) == 0xF8) { + // five bytes 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + if (bleft < 5) + break; + uint ch1 = b[i + 1]; + uint ch2 = b[i + 2]; + uint ch3 = b[i + 3]; + uint ch4 = b[i + 4]; + if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80 || (ch3 & 0xC0) != 0x80 || (ch4 & 0xC0) != 0x80) { + invalidCharFlag = true; + break; + } + ch = ((ch0 & 0x03) << 24) | ((ch1 & 0x3F) << 18) | ((ch2 & 0x3F) << 12) | ((ch3 & 0x3F) << 6) | ((ch4 & 0x3F)); + bread = 5; + } if ((ch0 & 0xFE) == 0xFC) { + // six bytes 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + if (bleft < 6) + break; + uint ch1 = b[i + 1]; + uint ch2 = b[i + 2]; + uint ch3 = b[i + 3]; + uint ch4 = b[i + 4]; + uint ch5 = b[i + 5]; + if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80 || (ch3 & 0xC0) != 0x80 || (ch4 & 0xC0) != 0x80 || (ch5 & 0xC0) != 0x80) { + invalidCharFlag = true; + break; + } + ch = ((ch0 & 0x01) << 30) | ((ch1 & 0x3F) << 24) | ((ch2 & 0x3F) << 18) | ((ch3 & 0x3F) << 12) | ((ch4 & 0x3F) << 6) | ((ch5 & 0x3F)); + bread = 5; + } + if ((ch >= 0xd800 && ch < 0xe000) || (ch > 0x10FFFF)) { + invalidCharFlag = true; + break; + } + if (ch < 0x10000) { + text[chars++] = ch; + } else { + uint lo = ch & 0x3FF; + uint hi = ch >> 10; + text[chars++] = (0xd800 | hi); + text[chars++] = (0xdc00 | lo); + } + i += bread - 1; + } + consumedBytes(i); + appendedText(chars); + uint bleft = len - i; + if (_streamEof && bleft > 0) + invalidCharFlag = true; // incomplete character at end of stream + return chars; + } +} + +class Utf16beLineStream : LineStream { + this(InputStream stream, string filename, ubyte[] buf, uint len) { + super(stream, filename, EncodingType.UTF16BE, buf, 2, len); + } + override uint decodeText() { + if (invalidCharFlag) { + invalidCharError(); + return 0; + } + uint bytesAvailable = readBytes(); + ubyte * bytes = _buf.ptr + _pos; + if (bytesAvailable == 0) + return 0; // nothing to decode + uint len = bytesAvailable; + uint chars = 0; + ubyte* b = bytes; + dchar* text = reserveTextBuf(len / 2 + 1); + uint i = 0; + for (; i < len - 1; i += 2) { + uint ch0 = b[i]; + uint ch1 = b[i + 1]; + uint ch = (ch0 << 8) | ch1; + // TODO: check special cases + text[chars++] = ch; + } + consumedBytes(i); + appendedText(chars); + uint bleft = len - i; + if (_streamEof && bleft > 0) + invalidCharFlag = true; // incomplete character at end of stream + return chars; + } +} + +class Utf16leLineStream : LineStream { + this(InputStream stream, string filename, ubyte[] buf, uint len) { + super(stream, filename, EncodingType.UTF16LE, buf, 2, len); + } + override uint decodeText() { + if (invalidCharFlag) { + invalidCharError(); + return 0; + } + uint bytesAvailable = readBytes(); + ubyte * bytes = _buf.ptr + _pos; + if (bytesAvailable == 0) + return 0; // nothing to decode + uint len = bytesAvailable; + uint chars = 0; + ubyte* b = bytes; + dchar* text = reserveTextBuf(len / 2 + 1); + uint i = 0; + for (; i < len - 1; i += 2) { + uint ch0 = b[i]; + uint ch1 = b[i + 1]; + uint ch = (ch1 << 8) | ch0; + // TODO: check special cases + text[chars++] = ch; + } + consumedBytes(i); + appendedText(chars); + uint bleft = len - i; + if (_streamEof && bleft > 0) + invalidCharFlag = true; // incomplete character at end of stream + return chars; + } +} + +class Utf32beLineStream : LineStream { + this(InputStream stream, string filename, ubyte[] buf, uint len) { + super(stream, filename, EncodingType.UTF32BE, buf, 4, len); + } + override uint decodeText() { + if (invalidCharFlag) { + invalidCharError(); + return 0; + } + uint bytesAvailable = readBytes(); + ubyte * bytes = _buf.ptr + _pos; + if (bytesAvailable == 0) + return 0; // nothing to decode + uint len = bytesAvailable; + uint chars = 0; + ubyte* b = bytes; + dchar* text = reserveTextBuf(len / 2 + 1); + uint i = 0; + for (; i < len - 3; i += 4) { + uint ch0 = b[i]; + uint ch1 = b[i + 1]; + uint ch2 = b[i + 2]; + uint ch3 = b[i + 3]; + uint ch = (ch0 << 24) | (ch1 << 16) | (ch2 << 8) | ch3; + if ((ch >= 0xd800 && ch < 0xe000) || (ch > 0x10FFFF)) { + invalidCharFlag = true; + break; + } + text[chars++] = ch; + } + consumedBytes(i); + appendedText(chars); + uint bleft = len - i; + if (_streamEof && bleft > 0) + invalidCharFlag = true; // incomplete character at end of stream + return chars; + } +} + +class Utf32leLineStream : LineStream { + this(InputStream stream, string filename, ubyte[] buf, uint len) { + super(stream, filename, EncodingType.UTF32LE, buf, 4, len); + } + override uint decodeText() { + if (invalidCharFlag) { + invalidCharError(); + return 0; + } + uint bytesAvailable = readBytes(); + ubyte * bytes = _buf.ptr + _pos; + if (bytesAvailable == 0) + return 0; // nothing to decode + uint len = bytesAvailable; + uint chars = 0; + ubyte* b = bytes; + dchar* text = reserveTextBuf(len / 2 + 1); + uint i = 0; + for (; i < len - 3; i += 4) { + uint ch3 = b[i]; + uint ch2 = b[i + 1]; + uint ch1 = b[i + 2]; + uint ch0 = b[i + 3]; + uint ch = (ch0 << 24) | (ch1 << 16) | (ch2 << 8) | ch3; + if ((ch >= 0xd800 && ch < 0xe000) || (ch > 0x10FFFF)) { + invalidCharFlag = true; + break; + } + text[chars++] = ch; + } + consumedBytes(i); + appendedText(chars); + uint bleft = len - i; + if (_streamEof && bleft > 0) + invalidCharFlag = true; // incomplete character at end of stream + return chars; + } +} + + +unittest { + static if (false) { + import std.stdio; + import std.conv; + import std.utf; + //string fname = "C:\\projects\\d\\ddc\\ddclexer\\src\\ddc\\lexer\\LineStream.d"; + //string fname = "/home/lve/src/d/ddc/ddclexer/" ~ __FILE__; //"/home/lve/src/d/ddc/ddclexer/src/ddc/lexer/Lexer.d"; + //string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf8.d"; + //string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf16be.d"; + //string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf16le.d"; + //string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf32be.d"; + string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf32le.d"; + writeln("opening file"); + std.stream.File f = new std.stream.File(fname); + scope(exit) { f.close(); } + try { + LineStream lines = LineStream.create(f, fname); + for (;;) { + dchar[] s = lines.readLine(); + if (s is null) + break; + writeln("line " ~ to!string(lines.line()) ~ ":" ~ toUTF8(s)); + } + if (lines.errorCode != 0) { + writeln("Error ", lines.errorCode, " ", lines.errorMessage, " -- at line ", lines.errorLine, " position ", lines.errorPos); + } else { + writeln("EOF reached"); + } + } catch (Exception e) { + writeln("Exception " ~ e.toString); + } + } +} +// LAST LINE diff --git a/src/dlangui/widgets/controls.d b/src/dlangui/widgets/controls.d index 403badc2..51ee98b6 100644 --- a/src/dlangui/widgets/controls.d +++ b/src/dlangui/widgets/controls.d @@ -10,7 +10,7 @@ class TextWidget : Widget { super(ID); styleId = "TEXT"; } - protected dstring _text; + protected UIString _text; /// get widget text override @property dstring text() { return _text; } /// set text to show @@ -19,6 +19,12 @@ class TextWidget : Widget { requestLayout(); return this; } + /// set text resource ID to show + @property Widget textResource(string s) { + _text = s; + requestLayout(); + return this; + } override void measure(int parentWidth, int parentHeight) { FontRef font = font(); @@ -122,9 +128,10 @@ class ImageButton : ImageWidget { } class Button : Widget { - protected dstring _text; + protected UIString _text; override @property dstring text() { return _text; } override @property Widget text(dstring s) { _text = s; requestLayout(); return this; } + @property Widget textResource(string s) { _text = s; requestLayout(); return this; } this(string ID = null) { super(ID); styleId = "BUTTON"; @@ -567,3 +574,10 @@ class ScrollBar : AbstractSlider, OnClickHandler { _indicator.onDraw(buf); } } + +class TabItem { +} + +class TabWidget { + +} diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index 5ac3a980..df8f679b 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -6,6 +6,7 @@ public import dlangui.widgets.styles; public import dlangui.graphics.drawbuf; public import dlangui.graphics.images; public import dlangui.graphics.fonts; +public import dlangui.core.i18n; public import std.signals;