diff --git a/examples/d3d/d3d-msvc.visualdproj b/examples/d3d/d3d-msvc.visualdproj
index 985d53d8..a80d15b0 100644
--- a/examples/d3d/d3d-msvc.visualdproj
+++ b/examples/d3d/d3d-msvc.visualdproj
@@ -409,6 +409,12 @@
*.obj;*.cmd;*.build;*.json;*.dep
+
+
+
+
+
+
diff --git a/examples/ircclient/src/ircclient/net/client.d b/examples/ircclient/src/ircclient/net/client.d
index 1c06386b..b9ec9d5c 100644
--- a/examples/ircclient/src/ircclient/net/client.d
+++ b/examples/ircclient/src/ircclient/net/client.d
@@ -2,8 +2,10 @@ module ircclient.net.client;
public import dlangui.core.asyncsocket;
import dlangui.core.logger;
-import std.string : empty, format;
+import dlangui.core.collections;
+import std.string : empty, format, startsWith;
import std.conv : to;
+import std.utf : toUTF32, toUTF8;
interface IRCClientCallback {
void onIRCConnect(IRCClient client);
@@ -16,6 +18,10 @@ interface IRCClientCallback {
enum IRCCommand : int {
UNKNOWN,
+ CHANNEL_TOPIC = 332,
+ CHANNEL_TOPIC_SET_BY = 333,
+ CHANNEL_NAMES_LIST = 353,
+ CHANNEL_NAMES_LIST_END = 366,
USER = 1000,
PRIVMSG, // :source PRIVMSG :Message
NOTICE, // :source NOTICE :Message
@@ -101,6 +107,16 @@ class IRCMessage {
if (params.length > 0)
target = params[0];
break;
+ case CHANNEL_TOPIC:
+ case CHANNEL_TOPIC_SET_BY:
+ case CHANNEL_NAMES_LIST_END:
+ if (params.length > 1)
+ target = params[1];
+ break;
+ case CHANNEL_NAMES_LIST:
+ if (params.length > 2 && params[1] == "=")
+ target = params[2];
+ break;
default:
break;
}
@@ -114,6 +130,8 @@ class IRCAddress {
string channel;
string nick;
string username;
+ this() {
+ }
this(string s) {
full = s;
string s1 = parseDelimitedParameter(s, '!');
@@ -134,6 +152,75 @@ class IRCAddress {
}
}
+class IRCUser {
+ string nick;
+ int flags;
+ this(string s) {
+ nick = s;
+ }
+}
+
+class UserList {
+ Collection!IRCUser _users;
+ @property int length() { return _users.length; }
+ @property IRCUser opIndex(int index) { return _users[index]; }
+ void fromList(string userList) {
+ _users.clear();
+ for(;;) {
+ string s = parseDelimitedParameter(userList);
+ if (s.empty)
+ break;
+ IRCUser u = new IRCUser(s);
+ _users.add(u);
+ }
+ }
+}
+
+class IRCChannel {
+ string name;
+ string topic;
+ string topicSetBy;
+ long topicSetWhen;
+ UserList users;
+ this(string name) {
+ this.name = name;
+ users = new UserList();
+ }
+ char[] userListBuffer;
+ void setUserList(string userList) {
+ users.fromList(userList);
+ }
+ @property dstring[] userNames() {
+ dstring[] res;
+ for(int i = 0; i < users.length; i++) {
+ res ~= toUTF32(users[i].nick);
+ }
+ return res;
+ }
+ void handleMessage(IRCMessage msg) {
+ switch (msg.commandId) with (IRCCommand) {
+ case CHANNEL_TOPIC:
+ topic = msg.message;
+ break;
+ case CHANNEL_TOPIC_SET_BY:
+ topicSetBy = msg.params.length == 3 ? msg.params[1] : null;
+ break;
+ case CHANNEL_NAMES_LIST:
+ if (userListBuffer.length > 0)
+ userListBuffer ~= " ";
+ userListBuffer ~= msg.message;
+ break;
+ case CHANNEL_NAMES_LIST_END:
+ setUserList(userListBuffer.dup);
+ Log.d("user list for " ~ name ~ " : " ~ userListBuffer);
+ userListBuffer = null;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
/// IRC Client connection implementation
class IRCClient : AsyncSocketCallback {
protected:
@@ -143,6 +230,7 @@ protected:
string _host;
ushort _port;
string _nick;
+ IRCChannel[string] _channels;
void onDataReceived(AsyncSocket socket, ubyte[] data) {
_readbuf ~= cast(char[])data;
// split by lines
@@ -182,6 +270,16 @@ protected:
case NOTICE:
_callback.onIRCNotice(this, msg.sourceAddress, msg.target, msg.message);
break;
+ case CHANNEL_TOPIC:
+ case CHANNEL_TOPIC_SET_BY:
+ case CHANNEL_NAMES_LIST:
+ case CHANNEL_NAMES_LIST_END:
+ if (msg.target.startsWith("#")) {
+ auto channel = channelByName(msg.target, true);
+ channel.handleMessage(msg);
+ _callback.onIRCMessage(this, msg);
+ }
+ break;
default:
_callback.onIRCMessage(this, msg);
break;
@@ -206,6 +304,23 @@ public:
if (_socket)
destroy(_socket);
}
+ IRCChannel removeChannel(string name) {
+ if (auto p = name in _channels) {
+ _channels.remove(name);
+ return *p;
+ }
+ return null;
+ }
+ IRCChannel channelByName(string name, bool createIfNotExist = false) {
+ if (auto p = name in _channels) {
+ return *p;
+ }
+ if (!createIfNotExist)
+ return null;
+ IRCChannel res = new IRCChannel(name);
+ _channels[name] = res;
+ return res;
+ }
@property string host() { return _host; }
@property ushort port() { return _port; }
@property string hostPort() { return "%s:%d".format(_host, _port); }
diff --git a/examples/ircclient/src/main.d b/examples/ircclient/src/main.d
index f712739b..aaa8694b 100644
--- a/examples/ircclient/src/main.d
+++ b/examples/ircclient/src/main.d
@@ -207,21 +207,32 @@ class IRCFrame : AppFrame, IRCClientCallback {
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 ~ ")")));
+ switch (message.commandId) with (IRCCommand) {
+ case JOIN:
+ case PART:
+ if (message.sourceAddress && !message.sourceAddress.nick.empty && message.target.startsWith("#")) {
+ w = getOrCreateWindowFor(message.target);
+ if (message.commandId == 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;
+ return;
+ case CHANNEL_NAMES_LIST_END:
+ if (message.target.startsWith("#")) {
+ w = getOrCreateWindowFor(message.target);
+ IRCChannel channel = _client.channelByName(message.target);
+ w.updateUserList(channel);
+ }
+ return;
+ default:
+ if (message.commandId < 1000) {
+ // custom server messages
+ w.addLine(message.message);
+ return;
+ }
+ break;
}
w.addLine(message.msg);
}
@@ -258,7 +269,7 @@ class IRCWindow : VerticalLayout, EditorActionHandler {
_listBox.minWidth = 100;
_listBox.maxWidth = 200;
_listBox.orientation = Orientation.Vertical;
- _listBox.items = ["Nick1"d, "Nick2"d];
+ //_listBox.items = ["Nick1"d, "Nick2"d];
hlayout.addChild(new ResizerWidget(null, Orientation.Horizontal));
hlayout.addChild(_listBox);
_kind = IRCWindowKind.Channel;
@@ -278,6 +289,10 @@ class IRCWindow : VerticalLayout, EditorActionHandler {
if (visible)
window.update();
}
+ void updateUserList(IRCChannel channel) {
+ _listBox.items = channel.userNames;
+ window.update();
+ }
bool onEditorAction(const Action action) {
if (!_editLine.text.empty) {
string s = toUTF8(_editLine.text);
@@ -288,8 +303,8 @@ class IRCWindow : VerticalLayout, EditorActionHandler {
string cmd = parseDelimitedParameter(s);
if (cmd == "/quit") {
- _client.quit(param);
- return;
+ _client.quit(s);
+ return true;
}
string param = parseDelimitedParameter(s);
@@ -303,6 +318,7 @@ class IRCWindow : VerticalLayout, EditorActionHandler {
_client.privMsg(param, s);
} else {
Log.d("Unknown command: " ~ cmd);
+ addLine("Supported commands: /nick /join /part /msg /quit");
}
} else {
// message