diff --git a/dlangui-msvc.sln b/dlangui-msvc.sln index 5238ee35..1412240a 100644 --- a/dlangui-msvc.sln +++ b/dlangui-msvc.sln @@ -49,6 +49,11 @@ Project("{002A2DE9-8BB6-484D-9802-7E4AD4084715}") = "dwindow", "..\dwindow\dwind EndProject Project("{002A2DE9-8BB6-484D-9802-7E4AD4084715}") = "dmdbug", "examples\dmdbug\dmdbug.visualdproj", "{6FD15ECD-35AF-457D-9327-DD8C2A0EADF1}" EndProject +Project("{002A2DE9-8BB6-484D-9802-7E4AD4084715}") = "ircclient", "examples\ircclient\ircclient.visualdproj", "{20722E6B-CA27-467F-8BB8-07F80106B478}" + ProjectSection(ProjectDependencies) = postProject + {52A2ABB9-2CF7-4D5F-AE8C-75B21F8585A5} = {52A2ABB9-2CF7-4D5F-AE8C-75B21F8585A5} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -182,6 +187,16 @@ Global {6FD15ECD-35AF-457D-9327-DD8C2A0EADF1}.Unittest|Win32.Build.0 = Release|Win32 {6FD15ECD-35AF-457D-9327-DD8C2A0EADF1}.Unittest|x64.ActiveCfg = Release|Win32 {6FD15ECD-35AF-457D-9327-DD8C2A0EADF1}.Unittest|x64.Build.0 = Release|Win32 + {20722E6B-CA27-467F-8BB8-07F80106B478}.Debug|Win32.ActiveCfg = Debug|Win32 + {20722E6B-CA27-467F-8BB8-07F80106B478}.Debug|Win32.Build.0 = Debug|Win32 + {20722E6B-CA27-467F-8BB8-07F80106B478}.Debug|x64.ActiveCfg = Debug|Win32 + {20722E6B-CA27-467F-8BB8-07F80106B478}.Release|Win32.ActiveCfg = Release|Win32 + {20722E6B-CA27-467F-8BB8-07F80106B478}.Release|Win32.Build.0 = Release|Win32 + {20722E6B-CA27-467F-8BB8-07F80106B478}.Release|x64.ActiveCfg = Release|Win32 + {20722E6B-CA27-467F-8BB8-07F80106B478}.Unittest|Win32.ActiveCfg = Release|Win32 + {20722E6B-CA27-467F-8BB8-07F80106B478}.Unittest|Win32.Build.0 = Release|Win32 + {20722E6B-CA27-467F-8BB8-07F80106B478}.Unittest|x64.ActiveCfg = Release|Win32 + {20722E6B-CA27-467F-8BB8-07F80106B478}.Unittest|x64.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dlangui-msvc.visualdproj b/dlangui-msvc.visualdproj index c88cfe91..cccdbf5d 100644 --- a/dlangui-msvc.visualdproj +++ b/dlangui-msvc.visualdproj @@ -735,6 +735,7 @@ + @@ -747,6 +748,7 @@ + diff --git a/examples/ircclient/dub.json b/examples/ircclient/dub.json new file mode 100644 index 00000000..fb2decd3 --- /dev/null +++ b/examples/ircclient/dub.json @@ -0,0 +1,21 @@ +{ + "name": "ircclient", + "description": "dlangui library example IRC Client", + "homepage": "https://github.com/buggins/dlangui", + "license": "Boost", + "authors": ["Vadim Lopatin"], + + "stringImportPaths": ["views", "views/res", "views/res/i18n", "views/res/mdpi"], + + "targetPath": "bin", + "targetName": "ircclient", + "targetType": "executable", + + "sourceFiles-windows": ["$PACKAGE_DIR/src/win_app.def"], + + "versions": ["EmbedStandardResources"], + + "dependencies": { + "dlangui": {"path": "../../"} + } +} diff --git a/examples/ircclient/ircclient.visualdproj b/examples/ircclient/ircclient.visualdproj new file mode 100644 index 00000000..3b97a2fa --- /dev/null +++ b/examples/ircclient/ircclient.visualdproj @@ -0,0 +1,215 @@ + + {20722E6B-CA27-467F-8BB8-07F80106B478} + + 0 + 0 + 0 + 2 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 3 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 2 + 0 + 0 + 0 + 0 + 0 + $(CC) -c + 1 + $(DMDInstallDir)windows\bin\dmd.exe + $(SolutionDir)/src $(SolutionDir)/3rdparty $(SolutionDir)/deps/DerelictGL3/source $(SolutionDir)/deps/DerelictUtil/source $(SolutionDir)/deps/DerelictFT/source $(SolutionDir)/deps/DerelictSDL2/source + views views/res views/res/i18n views/res/mdpi views/res/hdpi + $(ConfigurationName) + $(OutDir) + + + 0 + + + + + 0 + + + 1 + $(IntDir)\$(TargetName).json + 0 + + 0 + USE_OPENGL EmbedStandardResources ForceLogs + 0 + 0 + 0 + + + + 0 + + 1 + $(VisualDInstallDir)cv2pdb\cv2pdb.exe + 0 + 0 + 0 + + + + + + + + $(OutDir)\$(ProjectName).exe + 1 + 2 + 0 + + + + *.obj;*.cmd;*.build;*.json;*.dep + + + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 2.043 + 0 + 0 + 0 + 0 + 0 + $(CC) -c + 1 + $(DMDInstallDir)windows\bin\dmd.exe + + + $(ConfigurationName) + $(OutDir) + + + 0 + + + + + 0 + + + 1 + $(IntDir)\$(TargetName).json + 0 + + 0 + + 0 + 0 + 0 + + + + 0 + + 0 + $(VisualDInstallDir)cv2pdb\cv2pdb.exe + 0 + 0 + 0 + + + + + + + + $(OutDir)\$(ProjectName).exe + 1 + 1 + 0 + + + + *.obj;*.cmd;*.build;*.json;*.dep + + + + + + + + + + diff --git a/examples/ircclient/src/ircclient/net/client.d b/examples/ircclient/src/ircclient/net/client.d new file mode 100644 index 00000000..1c06386b --- /dev/null +++ b/examples/ircclient/src/ircclient/net/client.d @@ -0,0 +1,274 @@ +module ircclient.net.client; + +public import dlangui.core.asyncsocket; +import dlangui.core.logger; +import std.string : empty, format; +import std.conv : to; + +interface IRCClientCallback { + void onIRCConnect(IRCClient client); + void onIRCDisconnect(IRCClient client); + void onIRCMessage(IRCClient client, IRCMessage message); + void onIRCPing(IRCClient client, string message); + void onIRCPrivmsg(IRCClient client, IRCAddress source, string target, string message); + void onIRCNotice(IRCClient client, IRCAddress source, string target, string message); +} + +enum IRCCommand : int { + UNKNOWN, + USER = 1000, + PRIVMSG, // :source PRIVMSG :Message + NOTICE, // :source NOTICE :Message + NICK, + PING, // PING :message + PONG, // PONG :message + QUIT, // :source QUIT :reason + JOIN, // :source JOIN :#channel + PART, // :source PART #channel :reason + MODE, // +} + +IRCCommand findCommandId(string s) { + if (s.empty) + return IRCCommand.UNKNOWN; + if (s[0] >= '0' && s[0] <= '9') { + // parse numeric command ID + int n = 0; + foreach(ch; s) + if (ch >= '0' && ch <= '9') + n = n * 10 + (ch - '0'); + return cast(IRCCommand)n; + } + + switch (s) with(IRCCommand) { + case "USER": return USER; + case "PRIVMSG": return PRIVMSG; + case "NICK": return NICK; + case "QUIT": return QUIT; + case "PING": return PING; + case "PONG": return PONG; + case "JOIN": return JOIN; + case "PART": return PART; + case "NOTICE": return NOTICE; + default: + return UNKNOWN; + } +} + +/// IRC message +class IRCMessage { + /// full message text + string msg; + /// optional first parameter of message, starting with : -- e.g. ":holmes.freenode.net" + string source; // source of message + IRCAddress sourceAddress; // parsed source + string command; // command text + IRCCommand commandId = IRCCommand.UNKNOWN; // command id + string[] params; // all parameters after command + string message; // optional message parameter, w/o starting : + string target; // for some command types - message target + /// parse message text + bool parse(string s) { + msg = s; + if (s.empty) + return false; + if (s[0] == ':') { + // parse source + source = parseDelimitedParameter(s); + if (source.length < 2) + return false; + source = source[1 .. $]; + sourceAddress = new IRCAddress(source); + } + command = parseDelimitedParameter(s); + if (command.empty) + return false; + commandId = findCommandId(command); + while (!s.empty) { + if (s[0] == ':') { + params ~= s; + message = s[1 .. $]; + break; + } else { + params ~= parseDelimitedParameter(s); + } + } + switch(commandId) with (IRCCommand) { + case PRIVMSG: + case NOTICE: + case JOIN: + case PART: + if (params.length > 0) + target = params[0]; + break; + default: + break; + } + return true; + } +} + +class IRCAddress { + string full; + string host; + string channel; + string nick; + string username; + this(string s) { + full = s; + string s1 = parseDelimitedParameter(s, '!'); + if (!s.empty) { + // VadimLopatin!~Buggins@149.62.27.44 + nick = s1; + username = s; + } else { + host = s1; + } + } + @property string longName() { + if (!nick.empty) { + return nick ~ " (" ~ username ~ ")"; + } else { + return full; + } + } +} + +/// IRC Client connection implementation +class IRCClient : AsyncSocketCallback { +protected: + AsyncSocket _socket; + IRCClientCallback _callback; + char[] _readbuf; + string _host; + ushort _port; + string _nick; + void onDataReceived(AsyncSocket socket, ubyte[] data) { + _readbuf ~= cast(char[])data; + // split by lines + int start = 0; + for (int i = 0; i + 1 < _readbuf.length; i++) { + if (_readbuf[i] == '\r' && _readbuf[i + 1] == '\n') { + if (i > start) + onMessageText(_readbuf[start .. i].dup); + start = i + 2; + } + } + if (start < _readbuf.length) { + // has unfinished text + _readbuf = _readbuf[start .. $].dup; + } else { + // end of buffer + _readbuf.length = 0; + } + } + void onMessageText(string msgText) { + IRCMessage msg = new IRCMessage(); + if (msg.parse(msgText)) { + onMessage(msg); + } else { + Log.e("cannot parse IRC message " ~ msgText); + } + } + void onMessage(IRCMessage msg) { + Log.d("MSG: " ~ msg.msg); + switch (msg.commandId) with (IRCCommand) { + case PING: + _callback.onIRCPing(this, msg.message); + break; + case PRIVMSG: + _callback.onIRCPrivmsg(this, msg.sourceAddress, msg.target, msg.message); + break; + case NOTICE: + _callback.onIRCNotice(this, msg.sourceAddress, msg.target, msg.message); + break; + default: + _callback.onIRCMessage(this, msg); + break; + } + } + void onConnect(AsyncSocket socket) { + Log.e("onConnect"); + _readbuf.length = 0; + _callback.onIRCConnect(this); + } + void onDisconnect(AsyncSocket socket) { + Log.e("onDisconnect"); + _readbuf.length = 0; + _callback.onIRCDisconnect(this); + } + void onError(AsyncSocket socket, SocketError error, string msg) { + } +public: + this() { + } + ~this() { + if (_socket) + destroy(_socket); + } + @property string host() { return _host; } + @property ushort port() { return _port; } + @property string hostPort() { return "%s:%d".format(_host, _port); } + /// set socket to use + @property void socket(AsyncSocket sock) { + _socket = sock; + } + @property void callback(IRCClientCallback callback) { + _callback = callback; + } + void connect(string host, ushort port) { + _host = host; + _port = port; + _socket.connect(host, port); + } + void sendMessage(string msg) { + Log.d("CMD: " ~ msg); + _socket.send(cast(ubyte[])(msg ~ "\r\n")); + } + void pong(string msg) { + sendMessage("PONG :" ~ msg); + } + void privMsg(string destination, string message) { + sendMessage("PRIVMSG " ~ destination ~ " :" ~ message); + _callback.onIRCPrivmsg(this, new IRCAddress(_nick ~ "!~username@host"), destination, message); + } + void disconnect() { + _socket.disconnect(); + } + @property string nick() { + return _nick; + } + @property void nick(string nickName) { + if (_nick.empty) + _nick = nickName; + sendMessage("NICK " ~ nickName); + } + void join(string channel) { + sendMessage("JOIN " ~ channel); + } + void part(string channel, string message) { + sendMessage("PART " ~ channel ~ (message.empty ? "" : ":" ~ message)); + } + + void quit(string message) { + sendMessage("QUIT " ~ (message.empty ? "" : ":" ~ message)); + } +} + +/// utility function to get first space delimited parameter from string +string parseDelimitedParameter(ref string s, char delimiter = ' ') { + string res; + int i = 0; + // parse source + for (; i < s.length; i++) { + if (s[i] == delimiter) + break; + } + if (i > 0) { + res = s[0 .. i]; + } + i++; + s = i < s.length ? s[i .. $] : null; + return res; +} + diff --git a/examples/ircclient/src/main.d b/examples/ircclient/src/main.d new file mode 100644 index 00000000..f712739b --- /dev/null +++ b/examples/ircclient/src/main.d @@ -0,0 +1,343 @@ +module dmledit; + +import dlangui; +import dlangui.dialogs.filedlg; +import dlangui.dialogs.dialog; +import dlangui.dml.dmlhighlight; +import std.array : replaceFirst; +import ircclient.net.client; +import std.string : startsWith, indexOf; + +mixin APP_ENTRY_POINT; + +// action codes +enum IDEActions : int { + //ProjectOpen = 1010000, + FileNew = 1010000, + FileOpen, + FileSave, + FileSaveAs, + FileSaveAll, + FileClose, + FileExit, + EditPreferences, + DebugStart, + HelpAbout, +} + +// actions +const Action ACTION_FILE_NEW = new Action(IDEActions.FileNew, "MENU_FILE_NEW"c, "document-new", KeyCode.KEY_N, KeyFlag.Control); +const Action ACTION_FILE_SAVE = (new Action(IDEActions.FileSave, "MENU_FILE_SAVE"c, "document-save", KeyCode.KEY_S, KeyFlag.Control)).disableByDefault(); +const Action ACTION_FILE_SAVE_AS = (new Action(IDEActions.FileSaveAs, "MENU_FILE_SAVE_AS"c)).disableByDefault(); +const Action ACTION_FILE_OPEN = new Action(IDEActions.FileOpen, "MENU_FILE_OPEN"c, "document-open", KeyCode.KEY_O, KeyFlag.Control); +const Action ACTION_FILE_EXIT = new Action(IDEActions.FileExit, "MENU_FILE_EXIT"c, "document-close"c, KeyCode.KEY_X, KeyFlag.Alt); +const Action ACTION_EDIT_COPY = (new Action(EditorActions.Copy, "MENU_EDIT_COPY"c, "edit-copy"c, KeyCode.KEY_C, KeyFlag.Control)).addAccelerator(KeyCode.INS, KeyFlag.Control).disableByDefault(); +const Action ACTION_EDIT_PASTE = (new Action(EditorActions.Paste, "MENU_EDIT_PASTE"c, "edit-paste"c, KeyCode.KEY_V, KeyFlag.Control)).addAccelerator(KeyCode.INS, KeyFlag.Shift).disableByDefault(); +const Action ACTION_EDIT_CUT = (new Action(EditorActions.Cut, "MENU_EDIT_CUT"c, "edit-cut"c, KeyCode.KEY_X, KeyFlag.Control)).addAccelerator(KeyCode.DEL, KeyFlag.Shift).disableByDefault(); +const Action ACTION_EDIT_UNDO = (new Action(EditorActions.Undo, "MENU_EDIT_UNDO"c, "edit-undo"c, KeyCode.KEY_Z, KeyFlag.Control)).disableByDefault(); +const Action ACTION_EDIT_REDO = (new Action(EditorActions.Redo, "MENU_EDIT_REDO"c, "edit-redo"c, KeyCode.KEY_Y, KeyFlag.Control)).addAccelerator(KeyCode.KEY_Z, KeyFlag.Control|KeyFlag.Shift).disableByDefault(); +const Action ACTION_EDIT_INDENT = (new Action(EditorActions.Indent, "MENU_EDIT_INDENT"c, "edit-indent"c, KeyCode.TAB, 0)).addAccelerator(KeyCode.KEY_BRACKETCLOSE, KeyFlag.Control).disableByDefault(); +const Action ACTION_EDIT_UNINDENT = (new Action(EditorActions.Unindent, "MENU_EDIT_UNINDENT"c, "edit-unindent", KeyCode.TAB, KeyFlag.Shift)).addAccelerator(KeyCode.KEY_BRACKETOPEN, KeyFlag.Control).disableByDefault(); +const Action ACTION_EDIT_TOGGLE_LINE_COMMENT = (new Action(EditorActions.ToggleLineComment, "MENU_EDIT_TOGGLE_LINE_COMMENT"c, null, KeyCode.KEY_DIVIDE, KeyFlag.Control)).disableByDefault(); +const Action ACTION_EDIT_TOGGLE_BLOCK_COMMENT = (new Action(EditorActions.ToggleBlockComment, "MENU_EDIT_TOGGLE_BLOCK_COMMENT"c, null, KeyCode.KEY_DIVIDE, KeyFlag.Control|KeyFlag.Shift)).disableByDefault(); +const Action ACTION_EDIT_PREFERENCES = (new Action(IDEActions.EditPreferences, "MENU_EDIT_PREFERENCES"c, null)).disableByDefault(); +const Action ACTION_DEBUG_START = new Action(IDEActions.DebugStart, "MENU_DEBUG_UPDATE_PREVIEW"c, "debug-run"c, KeyCode.F5, 0); +const Action ACTION_HELP_ABOUT = new Action(IDEActions.HelpAbout, "MENU_HELP_ABOUT"c); + +class IRCFrame : AppFrame, IRCClientCallback { + + MenuItem mainMenuItems; + IRCClient _client; + + ~this() { + if (_client) + destroy(_client); + } + + override protected void initialize() { + _appName = "DlangUI_IRCClient"; + super.initialize(); + } + + /// create main menu + override protected MainMenu createMainMenu() { + mainMenuItems = new MenuItem(); + MenuItem fileItem = new MenuItem(new Action(1, "MENU_FILE")); + fileItem.add(//ACTION_FILE_NEW, ACTION_FILE_OPEN, + ACTION_FILE_EXIT); + mainMenuItems.add(fileItem); + //MenuItem editItem = new MenuItem(new Action(2, "MENU_EDIT")); + //editItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, + // ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO, + // ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT, ACTION_DEBUG_START); + // + //editItem.add(ACTION_EDIT_PREFERENCES); + //mainMenuItems.add(editItem); + MainMenu mainMenu = new MainMenu(mainMenuItems); + return mainMenu; + } + + + /// create app toolbars + override protected ToolBarHost createToolbars() { + ToolBarHost res = new ToolBarHost(); + ToolBar tb; + tb = res.getOrAddToolbar("Standard"); + tb.addButtons(//ACTION_FILE_NEW, ACTION_FILE_OPEN, ACTION_FILE_SAVE, ACTION_SEPARATOR, + ACTION_DEBUG_START); + + //tb = res.getOrAddToolbar("Edit"); + //tb.addButtons(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_SEPARATOR, + // ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT); + return res; + } + + bool onCanClose() { + // todo + return true; + } + + /// override to handle specific actions + override bool handleAction(const Action a) { + if (a) { + switch (a.id) { + case IDEActions.FileExit: + if (onCanClose()) + window.close(); + return true; + case IDEActions.HelpAbout: + window.showMessageBox(UIString("About DlangUI IRC Client"d), + UIString("DLangUI IRC Client\n(C) Vadim Lopatin, 2015\nhttp://github.com/buggins/dlangui\nSimple IRC client"d)); + return true; + case IDEActions.EditPreferences: + //showPreferences(); + return true; + case IDEActions.DebugStart: + connect(); + return true; + default: + return super.handleAction(a); + } + } + return false; + } + + /// override to handle specific actions state (e.g. change enabled state for supported actions) + override bool handleActionStateRequest(const Action a) { + switch (a.id) { + case IDEActions.HelpAbout: + case IDEActions.FileNew: + case IDEActions.FileSave: + case IDEActions.FileOpen: + case IDEActions.DebugStart: + case IDEActions.EditPreferences: + a.state = ACTION_STATE_ENABLED; + return true; + default: + return super.handleActionStateRequest(a); + } + } + + TabWidget _tabs; + /// create app body widget + override protected Widget createBody() { + _tabs = new TabWidget("TABS"); + _tabs.layoutWidth = FILL_PARENT; + _tabs.layoutHeight = FILL_PARENT; + //tabs.addTab(new IRCWindow("sample"), "Sample"d); + statusLine.setStatusText(toUTF32("Not Connected")); + return _tabs; + } + + void connect() { + if (!_client) { + _client = new IRCClient(); + AsyncSocket connection = window.createAsyncSocket(_client); + _client.socket = connection; + _client.callback = this; + } + _client.connect("irc.freenode.net", 6667); + } + + IRCWindow getOrCreateWindowFor(string party) { + string winId = party; + IRCWindow w = cast(IRCWindow)_tabs.tabBody(winId); + if (!w) { + w = new IRCWindow(winId, _client); + _tabs.addTab(w, toUTF32(winId)); + } + return w; + } + + void onIRCConnect(IRCClient client) { + IRCWindow w = getOrCreateWindowFor(client.hostPort); + w.addLine("connected to " ~ client.hostPort); + client.sendMessage("USER username 0 * :Real name"); + client.nick("dlangui_irc"); + client.join("#clienttest"); + statusLine.setStatusText(toUTF32("Connected to " ~ client.hostPort)); + } + + void onIRCDisconnect(IRCClient client) { + IRCWindow w = getOrCreateWindowFor(client.hostPort); + w.addLine("disconnected from " ~ client.hostPort); + statusLine.setStatusText(toUTF32("Disconnected")); + } + + void onIRCPing(IRCClient client, string message) { + IRCWindow w = getOrCreateWindowFor(client.hostPort); + w.addLine("PING " ~ message); + client.pong(message); + } + + void onIRCPrivmsg(IRCClient client, IRCAddress source, string target, string message) { + string wid = target.startsWith("#") ? target : client.hostPort; + if (target == client.nick) + wid = source.nick; + else if (source.nick == client.nick) + wid = target; + IRCWindow w = getOrCreateWindowFor(wid); + w.addLine("<" ~ (!source.nick.empty ? source.nick : source.full) ~ "> " ~ message); + } + + void onIRCNotice(IRCClient client, IRCAddress source, string target, string message) { + IRCWindow w = getOrCreateWindowFor(target.startsWith("#") ? target : client.hostPort); + w.addLine("-" ~ source.full ~ "- " ~ message); + } + + void onIRCMessage(IRCClient client, IRCMessage message) { + IRCWindow w = getOrCreateWindowFor(client.hostPort); + if (message.commandId < 1000) { + // custom server messages + w.addLine(message.message); + return; + } + if (message.commandId == IRCCommand.JOIN || message.commandId == IRCCommand.PART) { + if (message.sourceAddress && !message.sourceAddress.nick.empty && message.target.startsWith("#")) { + w = getOrCreateWindowFor(message.target); + if (message.commandId == IRCCommand.JOIN) { + w.addLine("* " ~ message.sourceAddress.longName ~ " has joined " ~ message.target); + } else { + w.addLine("* " ~ message.sourceAddress.longName ~ " has left " ~ message.target ~ (message.message.empty ? "" : ("(Reason: " ~ message.message ~ ")"))); + } + } + return; + } + w.addLine(message.msg); + } +} + +enum IRCWindowKind { + Server, + Channel, + Private +} + +class IRCWindow : VerticalLayout, EditorActionHandler { + LogWidget _editBox; + StringListWidget _listBox; + EditLine _editLine; + IRCClient _client; + IRCWindowKind _kind; + this(string ID, IRCClient client) { + super(ID); + _client = client; + layoutWidth = FILL_PARENT; + layoutHeight = FILL_PARENT; + HorizontalLayout hlayout = new HorizontalLayout(); + hlayout.layoutWidth = FILL_PARENT; + hlayout.layoutHeight = FILL_PARENT; + _editBox = new LogWidget(); + _editBox.layoutWidth = FILL_PARENT; + _editBox.layoutHeight = FILL_PARENT; + hlayout.addChild(_editBox); + if (ID.startsWith("#")) { + _listBox = new StringListWidget(); + _listBox.layoutHeight = FILL_PARENT; + _listBox.layoutWidth = WRAP_CONTENT; + _listBox.minWidth = 100; + _listBox.maxWidth = 200; + _listBox.orientation = Orientation.Vertical; + _listBox.items = ["Nick1"d, "Nick2"d]; + hlayout.addChild(new ResizerWidget(null, Orientation.Horizontal)); + hlayout.addChild(_listBox); + _kind = IRCWindowKind.Channel; + } else { + if (id.indexOf(':') >= 0) + _kind = IRCWindowKind.Server; + else + _kind = IRCWindowKind.Private; + } + addChild(hlayout); + _editLine = new EditLine(); + addChild(_editLine); + _editLine.editorAction = this; + } + void addLine(string s) { + _editBox.appendText(toUTF32(s ~ "\n")); + if (visible) + window.update(); + } + bool onEditorAction(const Action action) { + if (!_editLine.text.empty) { + string s = toUTF8(_editLine.text); + _editLine.text = ""d; + if (s.startsWith("/")) { + Log.d("Custom command: " ~ s); + // command + string cmd = parseDelimitedParameter(s); + + if (cmd == "/quit") { + _client.quit(param); + return; + } + + string param = parseDelimitedParameter(s); + if (cmd == "/nick" && !param.empty) { + _client.nick(param); + } else if (cmd == "/join" && param.startsWith("#")) { + _client.join(param); + } else if (cmd == "/part" && param.startsWith("#")) { + _client.part(param, s); + } else if (cmd == "/msg" && !param.empty && !s.empty) { + _client.privMsg(param, s); + } else { + Log.d("Unknown command: " ~ cmd); + } + } else { + // message + if (_kind != IRCWindowKind.Server) { + _client.privMsg(id, s); + } + } + } + return true; + } +} + +/// entry point for dlangui based application +extern (C) int UIAppMain(string[] args) { + + // embed non-standard resources listed in views/resources.list into executable + embeddedResourceList.addResources(embedResourcesFromList!("resources.list")()); + + /// set font gamma (1.0 is neutral, < 1.0 makes glyphs lighter, >1.0 makes glyphs bolder) + FontManager.fontGamma = 0.8; + FontManager.hintingMode = HintingMode.Normal; + + // create window + Window window = Platform.instance.createWindow("DlangUI IRC Client"d, null, WindowFlag.Resizable, 700, 470); + + // create some widget to show in window + window.windowIcon = drawableCache.getImage("dlangui-logo1"); + + + // create some widget to show in window + window.mainWidget = new IRCFrame(); + + // show window + window.show(); + + // run message loop + return Platform.instance.enterMessageLoop(); +} diff --git a/examples/ircclient/src/win_app.def b/examples/ircclient/src/win_app.def new file mode 100644 index 00000000..40b796c7 --- /dev/null +++ b/examples/ircclient/src/win_app.def @@ -0,0 +1,2 @@ +EXETYPE NT +SUBSYSTEM WINDOWS diff --git a/examples/ircclient/views/res/hdpi/document-close.png b/examples/ircclient/views/res/hdpi/document-close.png new file mode 100644 index 00000000..0bcfe981 Binary files /dev/null and b/examples/ircclient/views/res/hdpi/document-close.png differ diff --git a/examples/ircclient/views/res/hdpi/document-open-recent.png b/examples/ircclient/views/res/hdpi/document-open-recent.png new file mode 100644 index 00000000..6a10e6ef Binary files /dev/null and b/examples/ircclient/views/res/hdpi/document-open-recent.png differ diff --git a/examples/ircclient/views/res/hdpi/document-open.png b/examples/ircclient/views/res/hdpi/document-open.png new file mode 100644 index 00000000..8ba54411 Binary files /dev/null and b/examples/ircclient/views/res/hdpi/document-open.png differ diff --git a/examples/ircclient/views/res/hdpi/document-save-as.png b/examples/ircclient/views/res/hdpi/document-save-as.png new file mode 100644 index 00000000..9695a564 Binary files /dev/null and b/examples/ircclient/views/res/hdpi/document-save-as.png differ diff --git a/examples/ircclient/views/res/hdpi/document-save.png b/examples/ircclient/views/res/hdpi/document-save.png new file mode 100644 index 00000000..7fa489c0 Binary files /dev/null and b/examples/ircclient/views/res/hdpi/document-save.png differ diff --git a/examples/ircclient/views/res/hdpi/edit-clear.png b/examples/ircclient/views/res/hdpi/edit-clear.png new file mode 100644 index 00000000..631ed445 Binary files /dev/null and b/examples/ircclient/views/res/hdpi/edit-clear.png differ diff --git a/examples/ircclient/views/res/hdpi/edit-copy.png b/examples/ircclient/views/res/hdpi/edit-copy.png new file mode 100644 index 00000000..477e83a6 Binary files /dev/null and b/examples/ircclient/views/res/hdpi/edit-copy.png differ diff --git a/examples/ircclient/views/res/hdpi/edit-cut.png b/examples/ircclient/views/res/hdpi/edit-cut.png new file mode 100644 index 00000000..07323280 Binary files /dev/null and b/examples/ircclient/views/res/hdpi/edit-cut.png differ diff --git a/examples/ircclient/views/res/hdpi/edit-delete.png b/examples/ircclient/views/res/hdpi/edit-delete.png new file mode 100644 index 00000000..cc6d2af8 Binary files /dev/null and b/examples/ircclient/views/res/hdpi/edit-delete.png differ diff --git a/examples/ircclient/views/res/hdpi/edit-paste.png b/examples/ircclient/views/res/hdpi/edit-paste.png new file mode 100644 index 00000000..6788b022 Binary files /dev/null and b/examples/ircclient/views/res/hdpi/edit-paste.png differ diff --git a/examples/ircclient/views/res/hdpi/edit-redo.png b/examples/ircclient/views/res/hdpi/edit-redo.png new file mode 100644 index 00000000..d759f136 Binary files /dev/null and b/examples/ircclient/views/res/hdpi/edit-redo.png differ diff --git a/examples/ircclient/views/res/hdpi/edit-undo.png b/examples/ircclient/views/res/hdpi/edit-undo.png new file mode 100644 index 00000000..c893a1a6 Binary files /dev/null and b/examples/ircclient/views/res/hdpi/edit-undo.png differ diff --git a/examples/ircclient/views/res/i18n/en.ini b/examples/ircclient/views/res/i18n/en.ini new file mode 100644 index 00000000..576671ef --- /dev/null +++ b/examples/ircclient/views/res/i18n/en.ini @@ -0,0 +1,40 @@ +EXIT=Exit +MENU_FILE=&File +MENU_FILE_NEW=&New +MENU_FILE_OPEN=&Open +MENU_FILE_OPEN_RECENT=Open recent +MENU_FILE_SAVE=&Save +MENU_FILE_EXIT=E&xit +MENU_EDIT=&Edit +MENU_EDIT_COPY=&Copy +MENU_EDIT_PASTE=&Paste +MENU_EDIT_CUT=Cu&t +MENU_EDIT_UNDO=&Undo +MENU_EDIT_REDO=&Redo +MENU_EDIT_INDENT=Indent block +MENU_EDIT_UNINDENT=Unindent block +MENU_EDIT_TOGGLE_LINE_COMMENT=Toggle line comment +MENU_EDIT_TOGGLE_BLOCK_COMMENT=Toggle block comment +MENU_EDIT_PREFERENCES=&Preferences +MENU_VIEW=&View +MENU_VIEW_LANGUAGE=Interface &Language +MENU_VIEW_LANGUAGE_EN=English +MENU_VIEW_LANGUAGE_RU=Русский +MENU_VIEW_THEME=&Theme +MENU_VIEW_THEME_DEFAULT=Default +MENU_VIEW_THEME_DARK=Dark +MENU_VIEW_THEME_CUSTOM1=Custom 1 +MENU_WINDOW=&Window +MENU_WINDOW_PREFERENCES=&Preferences +MENU_HELP=&Help +MENU_HELP_VIEW_HELP=&View help +MENU_HELP_ABOUT=&About +MENU_DEBUG_UPDATE_PREVIEW=Update Preview + +TAB_LONG_LIST=Long list +TAB_BUTTONS=Buttons +TAB_ANIMATION=Animation +TAB_TABLE_LAYOUT=Table layout +TAB_EDITORS=Editors +TAB_CANVAS=Canvas + diff --git a/examples/ircclient/views/res/i18n/ru.ini b/examples/ircclient/views/res/i18n/ru.ini new file mode 100644 index 00000000..ab52d3d0 --- /dev/null +++ b/examples/ircclient/views/res/i18n/ru.ini @@ -0,0 +1,34 @@ +EXIT=Выход +MENU_FILE=&Файл +MENU_FILE_OPEN=&Открыть +MENU_FILE_OPEN_RECENT=Открыть из последних +MENU_FILE_SAVE=&Сохранить +MENU_FILE_EXIT=Вы&ход +MENU_EDIT=&Правка +MENU_EDIT_COPY=&Копировать +MENU_EDIT_PASTE=&Вставить +MENU_EDIT_CUT=Вырезать +MENU_EDIT_UNDO=&Отмена +MENU_EDIT_REDO=&Повторить +MENU_EDIT_PREFERENCES=&Настройки +MENU_VIEW=&Вид +MENU_VIEW_LANGUAGE=&Язык интерфейса +MENU_VIEW_LANGUAGE_EN=English +MENU_VIEW_LANGUAGE_RU=Русский +MENU_VIEW_THEME=&Тема +MENU_VIEW_THEME_DEFAULT=Стандартная +MENU_VIEW_THEME_DARK=Тёмная +MENU_VIEW_THEME_CUSTOM1=Пример 1 +MENU_WINDOW=&Окно +MENU_WINDOW_PREFERENCES=&Настройки +MENU_HELP=&Справка +MENU_HELP_VIEW_HELP=&Просмотр справки +MENU_HELP_ABOUT=&О программе + +TAB_LONG_LIST=Длинный список +TAB_BUTTONS=Кнопки +TAB_ANIMATION=Анимация +TAB_TABLE_LAYOUT=Табличный layout +TAB_EDITORS=Редакторы + + diff --git a/examples/ircclient/views/res/mdpi/cr3_logo.png b/examples/ircclient/views/res/mdpi/cr3_logo.png new file mode 100644 index 00000000..6e0d94ea Binary files /dev/null and b/examples/ircclient/views/res/mdpi/cr3_logo.png differ diff --git a/examples/ircclient/views/res/mdpi/debug-run.png b/examples/ircclient/views/res/mdpi/debug-run.png new file mode 100644 index 00000000..eee3e905 Binary files /dev/null and b/examples/ircclient/views/res/mdpi/debug-run.png differ diff --git a/examples/ircclient/views/res/mdpi/document-close.png b/examples/ircclient/views/res/mdpi/document-close.png new file mode 100644 index 00000000..411031eb Binary files /dev/null and b/examples/ircclient/views/res/mdpi/document-close.png differ diff --git a/examples/ircclient/views/res/mdpi/document-new.png b/examples/ircclient/views/res/mdpi/document-new.png new file mode 100644 index 00000000..a956a809 Binary files /dev/null and b/examples/ircclient/views/res/mdpi/document-new.png differ diff --git a/examples/ircclient/views/res/mdpi/document-open-recent.png b/examples/ircclient/views/res/mdpi/document-open-recent.png new file mode 100644 index 00000000..15847ce4 Binary files /dev/null and b/examples/ircclient/views/res/mdpi/document-open-recent.png differ diff --git a/examples/ircclient/views/res/mdpi/document-open.png b/examples/ircclient/views/res/mdpi/document-open.png new file mode 100644 index 00000000..17076a31 Binary files /dev/null and b/examples/ircclient/views/res/mdpi/document-open.png differ diff --git a/examples/ircclient/views/res/mdpi/document-properties.png b/examples/ircclient/views/res/mdpi/document-properties.png new file mode 100644 index 00000000..12c6b448 Binary files /dev/null and b/examples/ircclient/views/res/mdpi/document-properties.png differ diff --git a/examples/ircclient/views/res/mdpi/document-save-as.png b/examples/ircclient/views/res/mdpi/document-save-as.png new file mode 100644 index 00000000..41c52aaa Binary files /dev/null and b/examples/ircclient/views/res/mdpi/document-save-as.png differ diff --git a/examples/ircclient/views/res/mdpi/document-save.png b/examples/ircclient/views/res/mdpi/document-save.png new file mode 100644 index 00000000..3f7fd63c Binary files /dev/null and b/examples/ircclient/views/res/mdpi/document-save.png differ diff --git a/examples/ircclient/views/res/mdpi/edit-copy.png b/examples/ircclient/views/res/mdpi/edit-copy.png new file mode 100644 index 00000000..54eaf777 Binary files /dev/null and b/examples/ircclient/views/res/mdpi/edit-copy.png differ diff --git a/examples/ircclient/views/res/mdpi/edit-cut.png b/examples/ircclient/views/res/mdpi/edit-cut.png new file mode 100644 index 00000000..f4a55e3d Binary files /dev/null and b/examples/ircclient/views/res/mdpi/edit-cut.png differ diff --git a/examples/ircclient/views/res/mdpi/edit-indent.png b/examples/ircclient/views/res/mdpi/edit-indent.png new file mode 100644 index 00000000..ffd0730d Binary files /dev/null and b/examples/ircclient/views/res/mdpi/edit-indent.png differ diff --git a/examples/ircclient/views/res/mdpi/edit-paste.png b/examples/ircclient/views/res/mdpi/edit-paste.png new file mode 100644 index 00000000..3f71b1c7 Binary files /dev/null and b/examples/ircclient/views/res/mdpi/edit-paste.png differ diff --git a/examples/ircclient/views/res/mdpi/edit-redo.png b/examples/ircclient/views/res/mdpi/edit-redo.png new file mode 100644 index 00000000..c03bba0c Binary files /dev/null and b/examples/ircclient/views/res/mdpi/edit-redo.png differ diff --git a/examples/ircclient/views/res/mdpi/edit-undo.png b/examples/ircclient/views/res/mdpi/edit-undo.png new file mode 100644 index 00000000..f1239421 Binary files /dev/null and b/examples/ircclient/views/res/mdpi/edit-undo.png differ diff --git a/examples/ircclient/views/res/mdpi/edit-unindent.png b/examples/ircclient/views/res/mdpi/edit-unindent.png new file mode 100644 index 00000000..1e6f2cdb Binary files /dev/null and b/examples/ircclient/views/res/mdpi/edit-unindent.png differ diff --git a/examples/ircclient/views/res/mdpi/text-dml.png b/examples/ircclient/views/res/mdpi/text-dml.png new file mode 100644 index 00000000..7a97a453 Binary files /dev/null and b/examples/ircclient/views/res/mdpi/text-dml.png differ diff --git a/examples/ircclient/views/res/mdpi/tx_fabric.jpg b/examples/ircclient/views/res/mdpi/tx_fabric.jpg new file mode 100644 index 00000000..6c33894a Binary files /dev/null and b/examples/ircclient/views/res/mdpi/tx_fabric.jpg differ diff --git a/examples/ircclient/views/resources.list b/examples/ircclient/views/resources.list new file mode 100644 index 00000000..d8a1b540 --- /dev/null +++ b/examples/ircclient/views/resources.list @@ -0,0 +1,20 @@ +res/i18n/en.ini +res/i18n/ru.ini +res/mdpi/cr3_logo.png +res/mdpi/debug-run.png +res/mdpi/document-close.png +res/mdpi/document-new.png +res/mdpi/document-open-recent.png +res/mdpi/document-open.png +res/mdpi/document-properties.png +res/mdpi/document-save-as.png +res/mdpi/document-save.png +res/mdpi/edit-copy.png +res/mdpi/edit-cut.png +res/mdpi/edit-indent.png +res/mdpi/edit-paste.png +res/mdpi/edit-redo.png +res/mdpi/edit-undo.png +res/mdpi/edit-unindent.png +res/mdpi/text-dml.png +res/mdpi/tx_fabric.jpg diff --git a/src/dlangui/core/asyncsocket.d b/src/dlangui/core/asyncsocket.d new file mode 100644 index 00000000..953b0775 --- /dev/null +++ b/src/dlangui/core/asyncsocket.d @@ -0,0 +1,195 @@ +module dlangui.core.asyncsocket; + +import std.socket; +import core.thread; +import dlangui.core.queue; +import dlangui.core.logger; + +/// Socket state +enum SocketState { + Disconnected, + Connecting, + Connected +} + +/// Asynchronous socket interface +interface AsyncSocket { + @property SocketState state(); + void connect(string host, ushort port); + void disconnect(); + void send(ubyte[] data); +} + +/// Socket error code +enum SocketError { + ConnectError, + WriteError, + NotConnected, + AlreadyConnected, +} + +/// Callback interface for using by AsyncSocket implementations +interface AsyncSocketCallback { + void onDataReceived(AsyncSocket socket, ubyte[] data); + void onConnect(AsyncSocket socket); + void onDisconnect(AsyncSocket socket); + void onError(AsyncSocket socket, SocketError error, string msg); +} + +/// proxy for AsyncConnectionHandler - to call in GUI thread +class AsyncSocketCallbackProxy : AsyncSocketCallback { +private: + AsyncSocketCallback _handler; + void delegate(void delegate() runnable) _executor; +public: + this(AsyncSocketCallback handler, void delegate(void delegate() runnable) executor) { + _executor = executor; + _handler = handler; + } + void onDataReceived(AsyncSocket socket, ubyte[] data) { + _executor(delegate() { + _handler.onDataReceived(socket, data); + }); + } + void onConnect(AsyncSocket socket) { + _executor(delegate() { + _handler.onConnect(socket); + }); + } + void onDisconnect(AsyncSocket socket) { + _executor(delegate() { + _handler.onDisconnect(socket); + }); + } + void onError(AsyncSocket socket, SocketError error, string msg) { + _executor(delegate() { + _handler.onError(socket, error, msg); + }); + } +} + +/// Asynchrous socket which uses separate thread for operation +class AsyncClientConnection : Thread, AsyncSocket { +protected: + Socket _sock; + SocketSet _readSet; + SocketSet _writeSet; + SocketSet _errorSet; + RunnableQueue _queue; + AsyncSocketCallback _callback; + SocketState _state = SocketState.Disconnected; + void threadProc() { + ubyte[] readBuf = new ubyte[65536]; + Log.d("entering ClientConnection thread proc"); + for(;;) { + if (_queue.closed) + break; + Runnable task; + if (_queue.get(task, _sock ? 10 : 1000)) { + if (_queue.closed) + break; + task(); + } + if (_sock) { + _readSet.reset(); + _writeSet.reset(); + _errorSet.reset(); + _readSet.add(_sock); + _writeSet.add(_sock); + _errorSet.add(_sock); + if (Socket.select(_readSet, _writeSet, _errorSet, dur!"msecs"(10)) > 0) { + if (_writeSet.isSet(_sock)) { + if (_state == SocketState.Connecting) { + _state = SocketState.Connected; + _callback.onConnect(this); + } + } + if (_readSet.isSet(_sock)) { + long bytesRead = _sock.receive(readBuf); + if (bytesRead > 0) { + _callback.onDataReceived(this, readBuf[0 .. cast(int)bytesRead].dup); + } + } + if (_errorSet.isSet(_sock)) { + doDisconnect(); + } + } + } + } + doDisconnect(); + Log.d("exiting ClientConnection thread proc"); + } + void doDisconnect() { + if (_sock) { + _sock.shutdown(SocketShutdown.BOTH); + _sock.close(); + destroy(_sock); + _sock = null; + if (_state != SocketState.Disconnected) { + _state = SocketState.Disconnected; + _callback.onDisconnect(this); + } + } + } +public: + this(AsyncSocketCallback cb) { + super(&threadProc); + _callback = cb; + _queue = new RunnableQueue(); + start(); + } + ~this() { + _queue.close(); + join(); + } + @property SocketState state() { + return _state; + } + void connect(string host, ushort port) { + _queue.put(delegate() { + if (_state == SocketState.Connecting) { + _callback.onError(this, SocketError.NotConnected, "socket is already connecting"); + return; + } + if (_state == SocketState.Connected) { + _callback.onError(this, SocketError.NotConnected, "socket is already connected"); + return; + } + doDisconnect(); + _sock = new TcpSocket(); + _sock.blocking = false; + _readSet = new SocketSet(); + _writeSet = new SocketSet(); + _errorSet = new SocketSet(); + _state = SocketState.Connecting; + _sock.connect(new InternetAddress(host, port)); + }); + } + void disconnect() { + _queue.put(delegate() { + if (!_sock) + return; + doDisconnect(); + }); + } + void send(ubyte[] data) { + _queue.put(delegate() { + if (!_sock) { + _callback.onError(this, SocketError.NotConnected, "socket is not connected"); + return; + } + for (;;) { + long bytesSent = _sock.send(data); + if (bytesSent == Socket.ERROR) { + _callback.onError(this, SocketError.WriteError, "error while writing to connection"); + return; + } else { + //Log.d("Bytes sent:" ~ to!string(bytesSent)); + if (bytesSent >= data.length) + return; + data = data[cast(int)bytesSent .. $]; + } + } + }); + } +} diff --git a/src/dlangui/core/queue.d b/src/dlangui/core/queue.d new file mode 100644 index 00000000..b3bd9f3b --- /dev/null +++ b/src/dlangui/core/queue.d @@ -0,0 +1,144 @@ +module dlangui.core.queue; + +import core.sync.condition; +import core.sync.mutex; + +class BlockingQueue(T) { + + private Mutex _mutex; + private Condition _condition; + private T[] _buffer; + private int _readPos; + private int _writePos; + private shared bool _closed; + + this() { + _mutex = new Mutex(); + _condition = new Condition(_mutex); + _readPos = 0; + _writePos = 0; + } + + ~this() { + close(); + if (_condition) { + destroy(_condition); + _condition = null; + } + if (_mutex) { + destroy(_mutex); + _mutex = null; + } + } + + void close() { + if (_mutex && !_closed) { + synchronized(_mutex) { + _closed = true; + if (_condition !is null) + _condition.notifyAll(); + } + } else { + _closed = true; + } + } + + /// returns true if queue is closed + @property bool closed() { + return _closed; + } + + private void move() { + if (_readPos > 1024 && _readPos > _buffer.length * 3 / 4) { + // move buffer data + for (int i = 0; _readPos + i < _writePos; i++) + _buffer[i] = _buffer[_readPos + i]; + _writePos -= _readPos; + _readPos = 0; + } + } + + private void append(ref T item) { + if (_writePos >= _buffer.length) { + move(); + _buffer.length = _buffer.length == 0 ? 64 : _buffer.length * 2; + } + _buffer[_writePos++] = item; + } + + void put(T item) { + if (_closed) + return; + synchronized(_mutex) { + if (_closed) + return; + append(item); + _condition.notifyAll(); + } + } + + void put(T[] items) { + if (_closed) + return; + synchronized(_mutex) { + if (_closed) + return; + foreach(ref item; items) { + append(item); + } + _condition.notifyAll(); + } + } + + bool get(ref T value, int timeoutMillis = 0) { + if (_closed) + return false; + synchronized(_mutex) { + if (_closed) + return false; + if (_readPos < _writePos) { + value = _buffer[_readPos++]; + return true; + } + try { + if (timeoutMillis <= 0) + _condition.wait(); // no timeout + else if (!_condition.wait(dur!"msecs"(timeoutMillis))) + return false; // timeout + } catch (Exception e) { + // ignore + } + if (_readPos < _writePos) { + value = _buffer[_readPos++]; + return true; + } + } + return false; + } + + bool getAll(ref T[] values, int timeoutMillis) { + if (_closed) + return false; + synchronized(_mutex) { + if (_closed) + return false; + values.length = 0; + while (_readPos < _writePos) + values ~= _buffer[_readPos++]; + if (values.length > 0) + return true; + if (timeoutMillis <= 0) + _condition.wait(); // no timeout + else if (!_condition.wait(dur!"msecs"(timeoutMillis))) + return false; // timeout + while (_readPos < _writePos) + values ~= _buffer[_readPos++]; + if (values.length > 0) + return true; + } + return false; + } +} + +alias Runnable = void delegate(); +alias RunnableQueue = BlockingQueue!Runnable; diff --git a/src/dlangui/platforms/common/platform.d b/src/dlangui/platforms/common/platform.d index 2fbd6135..6f2c1f41 100644 --- a/src/dlangui/platforms/common/platform.d +++ b/src/dlangui/platforms/common/platform.d @@ -26,6 +26,7 @@ import dlangui.widgets.widget; import dlangui.widgets.popup; import dlangui.graphics.drawbuf; import dlangui.core.stdaction; +import dlangui.core.asyncsocket; private import dlangui.graphics.gldrawbuf; private import std.algorithm; @@ -814,6 +815,12 @@ class Window : CustomEventTarget { postEvent(event); } + /// Creates async socket + AsyncSocket createAsyncSocket(AsyncSocketCallback callback) { + AsyncClientConnection conn = new AsyncClientConnection(new AsyncSocketCallbackProxy(callback, &executeInUiThread)); + return conn; + } + /// remove event from queue by unique id if not yet dispatched (this method can be used from background thread) void cancelEvent(uint uniqueId) { CustomEvent ev = _eventList.get(uniqueId);