summaryrefslogtreecommitdiff
Unidiff
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--noncore/net/opieirc/ircmessageparser.cpp33
-rw-r--r--noncore/net/opieirc/ircmessageparser.h3
-rw-r--r--noncore/net/opieirc/ircoutput.h3
-rw-r--r--noncore/net/opieirc/ircservertab.cpp12
4 files changed, 49 insertions, 2 deletions
diff --git a/noncore/net/opieirc/ircmessageparser.cpp b/noncore/net/opieirc/ircmessageparser.cpp
index 5c70753..d1b70a5 100644
--- a/noncore/net/opieirc/ircmessageparser.cpp
+++ b/noncore/net/opieirc/ircmessageparser.cpp
@@ -6,45 +6,48 @@
6IRCLiteralMessageParserStruct IRCMessageParser::literalParserProcTable[] = { 6IRCLiteralMessageParserStruct IRCMessageParser::literalParserProcTable[] = {
7 { "PING", FUNC(parseLiteralPing) }, 7 { "PING", FUNC(parseLiteralPing) },
8 { "NOTICE", FUNC(parseLiteralNotice) }, 8 { "NOTICE", FUNC(parseLiteralNotice) },
9 { "JOIN", FUNC(parseLiteralJoin) }, 9 { "JOIN", FUNC(parseLiteralJoin) },
10 { "PRIVMSG", FUNC(parseLiteralPrivMsg) }, 10 { "PRIVMSG", FUNC(parseLiteralPrivMsg) },
11 { "NICK", FUNC(parseLiteralNick) }, 11 { "NICK", FUNC(parseLiteralNick) },
12 { "PART", FUNC(parseLiteralPart) }, 12 { "PART", FUNC(parseLiteralPart) },
13 { "QUIT", FUNC(parseLiteralQuit) }, 13 { "QUIT", FUNC(parseLiteralQuit) },
14 { "ERROR", FUNC(parseLiteralError) }, 14 { "ERROR", FUNC(parseLiteralError) },
15 { "ERROR:", FUNC(parseLiteralError) }, 15 { "ERROR:", FUNC(parseLiteralError) },
16 { "MODE", FUNC(parseLiteralMode) }, 16 { "MODE", FUNC(parseLiteralMode) },
17 { "KICK", FUNC(parseLiteralKick) }, 17 { "KICK", FUNC(parseLiteralKick) },
18 { "TOPIC", FUNC(parseLiteralTopic) },
18 { 0 , 0 } 19 { 0 , 0 }
19}; 20};
20 21
21/* Lookup table for literal commands */ 22/* Lookup table for literal commands */
22IRCCTCPMessageParserStruct IRCMessageParser::ctcpParserProcTable[] = { 23IRCCTCPMessageParserStruct IRCMessageParser::ctcpParserProcTable[] = {
23 { "PING", FUNC(parseCTCPPing) }, 24 { "PING", FUNC(parseCTCPPing) },
24 { "VERSION", FUNC(parseCTCPVersion) }, 25 { "VERSION", FUNC(parseCTCPVersion) },
25 { "ACTION", FUNC(parseCTCPAction) }, 26 { "ACTION", FUNC(parseCTCPAction) },
26 { 0 , 0 } 27 { 0 , 0 }
27}; 28};
28 29
29/* Lookup table for numerical commands */ 30/* Lookup table for numerical commands */
30IRCNumericalMessageParserStruct IRCMessageParser::numericalParserProcTable[] = { 31IRCNumericalMessageParserStruct IRCMessageParser::numericalParserProcTable[] = {
31 { 1, FUNC(parseNumerical001) }, // RPL_WELCOME 32 { 1, FUNC(parseNumerical001) }, // RPL_WELCOME
32 { 2, FUNC(parseNumerical002) }, // RPL_YOURHOST 33 { 2, FUNC(parseNumerical002) }, // RPL_YOURHOST
33 { 3, FUNC(parseNumerical003) }, // RPL_CREATED 34 { 3, FUNC(parseNumerical003) }, // RPL_CREATED
34 { 4, FUNC(parseNumerical004) }, // RPL_MYINFO 35 { 4, FUNC(parseNumerical004) }, // RPL_MYINFO
35 { 5, FUNC(parseNumerical005) }, // RPL_BOUNCE, RPL_PROTOCTL 36 { 5, FUNC(parseNumerical005) }, // RPL_BOUNCE, RPL_PROTOCTL
36 { 251, FUNC(parseNumericalStats) }, // RPL_LUSERCLIENT 37 { 251, FUNC(parseNumericalStats) }, // RPL_LUSERCLIENT
37 { 254, FUNC(nullFunc)}, // RPL_LUSERCHANNELS 38 { 254, FUNC(nullFunc)}, // RPL_LUSERCHANNELS
38 { 255, FUNC(parseNumericalStats) }, // RPL_LUSERNAME 39 { 255, FUNC(parseNumericalStats) }, // RPL_LUSERNAME
40 { 332, FUNC(parseNumericalTopic) }, // RPL_TOPIC
41 { 333, FUNC(parseNumericalTopicWhoTime) }, // RPL_TOPICWHOTIME
39 { 353, FUNC(parseNumericalNames) }, // RPL_NAMREPLY 42 { 353, FUNC(parseNumericalNames) }, // RPL_NAMREPLY
40 { 366, FUNC(parseNumericalEndOfNames) }, // RPL_ENDOFNAMES 43 { 366, FUNC(parseNumericalEndOfNames) }, // RPL_ENDOFNAMES
41 { 375, FUNC(parseNumericalStats) }, // RPL_MOTDSTART 44 { 375, FUNC(parseNumericalStats) }, // RPL_MOTDSTART
42 { 372, FUNC(parseNumericalStats) }, // RPL_MOTD 45 { 372, FUNC(parseNumericalStats) }, // RPL_MOTD
43 { 376, FUNC(parseNumericalStats) }, // RPL_ENDOFMOTD 46 { 376, FUNC(parseNumericalStats) }, // RPL_ENDOFMOTD
44 { 377, FUNC(parseNumericalStats) }, // RPL_MOTD2 47 { 377, FUNC(parseNumericalStats) }, // RPL_MOTD2
45 { 378, FUNC(parseNumericalStats) }, // RPL_MOTD3 48 { 378, FUNC(parseNumericalStats) }, // RPL_MOTD3
46 { 401, FUNC(parseNumericalNoSuchNick) }, // ERR_NOSUCHNICK 49 { 401, FUNC(parseNumericalNoSuchNick) }, // ERR_NOSUCHNICK
47 { 406, FUNC(parseNumericalNoSuchNick) }, // ERR_WASNOSUCHNICK 50 { 406, FUNC(parseNumericalNoSuchNick) }, // ERR_WASNOSUCHNICK
48 { 412, FUNC(parseNumericalStats) }, // ERR_NOTEXTTOSEND 51 { 412, FUNC(parseNumericalStats) }, // ERR_NOTEXTTOSEND
49 { 433, FUNC(parseNumericalNicknameInUse) }, // ERR_NICKNAMEINUSE 52 { 433, FUNC(parseNumericalNicknameInUse) }, // ERR_NICKNAMEINUSE
50 { 0, 0 } 53 { 0, 0 }
@@ -182,25 +185,25 @@ void IRCMessageParser::parseLiteralPrivMsg(IRCMessage *message) {
182 if (channel) { 185 if (channel) {
183 IRCPerson mask(message->prefix()); 186 IRCPerson mask(message->prefix());
184 IRCChannelPerson *person = channel->getPerson(mask.nick()); 187 IRCChannelPerson *person = channel->getPerson(mask.nick());
185 if (person) { 188 if (person) {
186 IRCOutput output(OUTPUT_CHANPRIVMSG, message->param(1)); 189 IRCOutput output(OUTPUT_CHANPRIVMSG, message->param(1));
187 output.addParam(channel); 190 output.addParam(channel);
188 output.addParam(person); 191 output.addParam(person);
189 emit outputReady(output); 192 emit outputReady(output);
190 } else { 193 } else {
191 emit outputReady(IRCOutput(OUTPUT_ERROR, tr("Channel message with unknown sender"))); 194 emit outputReady(IRCOutput(OUTPUT_ERROR, tr("Channel message with unknown sender")));
192 } 195 }
193 } else { 196 } else {
194 emit outputReady(IRCOutput(OUTPUT_ERROR, tr("Channel message with unknown channel"))); 197 emit outputReady(IRCOutput(OUTPUT_ERROR, tr("Channel message with unknown channel ") + message->param(0)));
195 } 198 }
196 } else { 199 } else {
197 emit outputReady(IRCOutput(OUTPUT_ERROR, tr("Received PRIVMSG of unknown type"))); 200 emit outputReady(IRCOutput(OUTPUT_ERROR, tr("Received PRIVMSG of unknown type")));
198 } 201 }
199} 202}
200 203
201void IRCMessageParser::parseLiteralNick(IRCMessage *message) { 204void IRCMessageParser::parseLiteralNick(IRCMessage *message) {
202 IRCPerson mask(message->prefix()); 205 IRCPerson mask(message->prefix());
203 206
204 if (mask.nick() == m_session->m_server->nick()) { 207 if (mask.nick() == m_session->m_server->nick()) {
205 /* We are changing our nickname */ 208 /* We are changing our nickname */
206 m_session->m_server->setNick(message->param(0)); 209 m_session->m_server->setNick(message->param(0));
@@ -233,24 +236,36 @@ void IRCMessageParser::parseLiteralQuit(IRCMessage *message) {
233 delete chanperson; 236 delete chanperson;
234 } 237 }
235 m_session->removePerson(person); 238 m_session->removePerson(person);
236 IRCOutput output(OUTPUT_QUIT, mask.nick() + tr(" has quit ") + "(" + message->param(0) + ")"); 239 IRCOutput output(OUTPUT_QUIT, mask.nick() + tr(" has quit ") + "(" + message->param(0) + ")");
237 output.addParam(person); 240 output.addParam(person);
238 emit outputReady(output); 241 emit outputReady(output);
239 delete person; 242 delete person;
240 } else { 243 } else {
241 emit outputReady(IRCOutput(OUTPUT_ERROR, tr("Unknown person quit - desynchronized?"))); 244 emit outputReady(IRCOutput(OUTPUT_ERROR, tr("Unknown person quit - desynchronized?")));
242 } 245 }
243} 246}
244 247
248void IRCMessageParser::parseLiteralTopic(IRCMessage *message) {
249 IRCPerson mask(message->prefix());
250 IRCChannel *channel = m_session->getChannel(message->param(0));
251 if (channel) {
252 IRCOutput output(OUTPUT_TOPIC, mask.nick() + tr(" changed topic to ") + "\"" + message->param(1) + "\"");
253 output.addParam(channel);
254 emit outputReady(output);
255 } else {
256 emit outputReady(IRCOutput(OUTPUT_ERROR, tr("Unknown channel topic - desynchronized?")));
257 }
258}
259
245void IRCMessageParser::parseLiteralError(IRCMessage *message) { 260void IRCMessageParser::parseLiteralError(IRCMessage *message) {
246 emit outputReady(IRCOutput(OUTPUT_ERROR, message->allParameters())); 261 emit outputReady(IRCOutput(OUTPUT_ERROR, message->allParameters()));
247} 262}
248 263
249void IRCMessageParser::parseCTCPPing(IRCMessage *message) { 264void IRCMessageParser::parseCTCPPing(IRCMessage *message) {
250 IRCPerson mask(message->prefix()); 265 IRCPerson mask(message->prefix());
251 m_session->m_connection->sendCTCP(mask.nick(), "PING " + message->allParameters()); 266 m_session->m_connection->sendCTCP(mask.nick(), "PING " + message->allParameters());
252 emit outputReady(IRCOutput(OUTPUT_CTCP, tr("Received a CTCP PING from ")+mask.nick())); 267 emit outputReady(IRCOutput(OUTPUT_CTCP, tr("Received a CTCP PING from ")+mask.nick()));
253} 268}
254 269
255void IRCMessageParser::parseCTCPVersion(IRCMessage *message) { 270void IRCMessageParser::parseCTCPVersion(IRCMessage *message) {
256 IRCPerson mask(message->prefix()); 271 IRCPerson mask(message->prefix());
@@ -475,12 +490,28 @@ void IRCMessageParser::parseNumericalEndOfNames(IRCMessage *message) {
475 } 490 }
476} 491}
477 492
478 493
479void IRCMessageParser::parseNumericalNicknameInUse(IRCMessage *) { 494void IRCMessageParser::parseNumericalNicknameInUse(IRCMessage *) {
480 emit outputReady(IRCOutput(OUTPUT_ERROR, tr("Nickname is in use, please reconnect with a different nickname"))); 495 emit outputReady(IRCOutput(OUTPUT_ERROR, tr("Nickname is in use, please reconnect with a different nickname")));
481 m_session->endSession(); 496 m_session->endSession();
482} 497}
483 498
484void IRCMessageParser::parseNumericalNoSuchNick(IRCMessage *) { 499void IRCMessageParser::parseNumericalNoSuchNick(IRCMessage *) {
485 emit outputReady(IRCOutput(OUTPUT_ERROR, tr("No such nickname"))); 500 emit outputReady(IRCOutput(OUTPUT_ERROR, tr("No such nickname")));
486} 501}
502
503void IRCMessageParser::parseNumericalTopic(IRCMessage *message) {
504 IRCChannel *channel = m_session->getChannel(message->param(1));
505 if (channel) {
506 IRCOutput output(OUTPUT_TOPIC, tr("Topic for channel " + channel->channelname() + " is \"" + message->param(2) + "\""));
507 output.addParam(channel);
508 emit outputReady(output);
509 } else {
510 IRCOutput output(OUTPUT_TOPIC, tr("Topic for channel " + message->param(1) + " is \"" + message->param(2) + "\""));
511 output.addParam(0);
512 emit outputReady(output);
513 }
514}
515
516void IRCMessageParser::parseNumericalTopicWhoTime(IRCMessage *message) {
517}
diff --git a/noncore/net/opieirc/ircmessageparser.h b/noncore/net/opieirc/ircmessageparser.h
index f774047..c4dd96c 100644
--- a/noncore/net/opieirc/ircmessageparser.h
+++ b/noncore/net/opieirc/ircmessageparser.h
@@ -63,34 +63,37 @@ private:
63 /* Parser functions */ 63 /* Parser functions */
64 void nullFunc(IRCMessage *message); 64 void nullFunc(IRCMessage *message);
65 void parseLiteralPing(IRCMessage *message); 65 void parseLiteralPing(IRCMessage *message);
66 void parseLiteralNotice(IRCMessage *message); 66 void parseLiteralNotice(IRCMessage *message);
67 void parseLiteralJoin(IRCMessage *message); 67 void parseLiteralJoin(IRCMessage *message);
68 void parseLiteralPrivMsg(IRCMessage *message); 68 void parseLiteralPrivMsg(IRCMessage *message);
69 void parseLiteralNick(IRCMessage *message); 69 void parseLiteralNick(IRCMessage *message);
70 void parseLiteralPart(IRCMessage *message); 70 void parseLiteralPart(IRCMessage *message);
71 void parseLiteralQuit(IRCMessage *message); 71 void parseLiteralQuit(IRCMessage *message);
72 void parseLiteralError(IRCMessage *message); 72 void parseLiteralError(IRCMessage *message);
73 void parseLiteralMode(IRCMessage *message); 73 void parseLiteralMode(IRCMessage *message);
74 void parseLiteralKick(IRCMessage *message); 74 void parseLiteralKick(IRCMessage *message);
75 void parseLiteralTopic(IRCMessage *message);
75 void parseNumerical001(IRCMessage *message); 76 void parseNumerical001(IRCMessage *message);
76 void parseNumerical002(IRCMessage *message); 77 void parseNumerical002(IRCMessage *message);
77 void parseNumerical003(IRCMessage *message); 78 void parseNumerical003(IRCMessage *message);
78 void parseNumerical004(IRCMessage *message); 79 void parseNumerical004(IRCMessage *message);
79 void parseNumerical005(IRCMessage *message); 80 void parseNumerical005(IRCMessage *message);
80 void parseNumericalStats(IRCMessage *message); 81 void parseNumericalStats(IRCMessage *message);
81 void parseNumericalNames(IRCMessage *message); 82 void parseNumericalNames(IRCMessage *message);
82 void parseNumericalEndOfNames(IRCMessage *message); 83 void parseNumericalEndOfNames(IRCMessage *message);
83 void parseNumericalNicknameInUse(IRCMessage *message); 84 void parseNumericalNicknameInUse(IRCMessage *message);
84 void parseNumericalNoSuchNick(IRCMessage *message); 85 void parseNumericalNoSuchNick(IRCMessage *message);
86 void parseNumericalTopic(IRCMessage *message);
87 void parseNumericalTopicWhoTime(IRCMessage *message);
85 void parseCTCPPing(IRCMessage *message); 88 void parseCTCPPing(IRCMessage *message);
86 void parseCTCPVersion(IRCMessage *message); 89 void parseCTCPVersion(IRCMessage *message);
87 void parseCTCPAction(IRCMessage *message); 90 void parseCTCPAction(IRCMessage *message);
88protected: 91protected:
89 IRCSession *m_session; 92 IRCSession *m_session;
90 /* Parser tables */ 93 /* Parser tables */
91 static IRCLiteralMessageParserStruct literalParserProcTable[]; 94 static IRCLiteralMessageParserStruct literalParserProcTable[];
92 static IRCNumericalMessageParserStruct numericalParserProcTable[]; 95 static IRCNumericalMessageParserStruct numericalParserProcTable[];
93 static IRCCTCPMessageParserStruct ctcpParserProcTable[]; 96 static IRCCTCPMessageParserStruct ctcpParserProcTable[];
94}; 97};
95 98
96#endif /* __IRCMESSAGEPARSER_H */ 99#endif /* __IRCMESSAGEPARSER_H */
diff --git a/noncore/net/opieirc/ircoutput.h b/noncore/net/opieirc/ircoutput.h
index e8cc524..9c0b8bb 100644
--- a/noncore/net/opieirc/ircoutput.h
+++ b/noncore/net/opieirc/ircoutput.h
@@ -35,25 +35,26 @@ enum IRCOutputType {
35 OUTPUT_NICKCHANGE = 4, /* parameters : person (IRCPerson) */ 35 OUTPUT_NICKCHANGE = 4, /* parameters : person (IRCPerson) */
36 OUTPUT_SELFJOIN = 5, /* parameters : channel (IRCChannel) */ 36 OUTPUT_SELFJOIN = 5, /* parameters : channel (IRCChannel) */
37 OUTPUT_OTHERJOIN = 6, /* parameters : channel (IRCChannel), person (IRCChannelPerson) */ 37 OUTPUT_OTHERJOIN = 6, /* parameters : channel (IRCChannel), person (IRCChannelPerson) */
38 OUTPUT_SELFPART = 7, /* parameters : channel (IRCChannel) */ 38 OUTPUT_SELFPART = 7, /* parameters : channel (IRCChannel) */
39 OUTPUT_OTHERPART = 8, /* parameters : channel (IRCChannel), person (IRCChannelPerson) */ 39 OUTPUT_OTHERPART = 8, /* parameters : channel (IRCChannel), person (IRCChannelPerson) */
40 OUTPUT_QUIT = 9, /* parameters : person (IRCPerson) */ 40 OUTPUT_QUIT = 9, /* parameters : person (IRCPerson) */
41 OUTPUT_CONNCLOSE = 10, /* parameters : none */ 41 OUTPUT_CONNCLOSE = 10, /* parameters : none */
42 OUTPUT_CTCP = 11, /* parameters : none */ 42 OUTPUT_CTCP = 11, /* parameters : none */
43 OUTPUT_SELFKICK = 12, /* parameters : channel (IRCChannel) */ 43 OUTPUT_SELFKICK = 12, /* parameters : channel (IRCChannel) */
44 OUTPUT_OTHERKICK = 13, /* parameters : channel (IRCChannel) person (IRCChannelPerson) */ 44 OUTPUT_OTHERKICK = 13, /* parameters : channel (IRCChannel) person (IRCChannelPerson) */
45 OUTPUT_CHANACTION = 14, /* parameters : channel (IRCChannel) person (IRCChannelPerson) */ 45 OUTPUT_CHANACTION = 14, /* parameters : channel (IRCChannel) person (IRCChannelPerson) */
46 OUTPUT_QUERYACTION = 15, /* parameters : person (IRCPerson) */ 46 OUTPUT_QUERYACTION = 15, /* parameters : person (IRCPerson) */
47 OUTPUT_CHANPERSONMODE = 16 /* parameters : channel (IRCCHannel) person (IRCChannelPerson) */ 47 OUTPUT_CHANPERSONMODE = 16, /* parameters : channel (IRCCHannel) person (IRCChannelPerson) */
48 OUTPUT_TOPIC = 17 /* parameters : channel (IRCChannel) */
48}; 49};
49 50
50/* The IRCOutput class is used as a kind of message which is sent by the 51/* The IRCOutput class is used as a kind of message which is sent by the
51 IRC parser to inform the GUI of changes. This could for example be a 52 IRC parser to inform the GUI of changes. This could for example be a
52 channel message or a nickname change */ 53 channel message or a nickname change */
53 54
54class IRCOutput { 55class IRCOutput {
55public: 56public:
56 IRCOutput(IRCOutputType type, QString message); 57 IRCOutput(IRCOutputType type, QString message);
57 /* Used to add a parameter to this IRCOutput. Parameters are dependent 58 /* Used to add a parameter to this IRCOutput. Parameters are dependent
58 on which IRCOutputType we are using (see above) */ 59 on which IRCOutputType we are using (see above) */
59 void addParam(void *data); 60 void addParam(void *data);
diff --git a/noncore/net/opieirc/ircservertab.cpp b/noncore/net/opieirc/ircservertab.cpp
index 4be60ef..aea58a3 100644
--- a/noncore/net/opieirc/ircservertab.cpp
+++ b/noncore/net/opieirc/ircservertab.cpp
@@ -210,24 +210,36 @@ void IRCServerTab::display(IRCOutput output) {
210 case OUTPUT_SELFKICK: { 210 case OUTPUT_SELFKICK: {
211 appendText("<font color=\"" + m_errorColor + "\">" + output.htmlMessage() + "</font><br>"); 211 appendText("<font color=\"" + m_errorColor + "\">" + output.htmlMessage() + "</font><br>");
212 IRCChannelTab *channelTab = getTabForChannel((IRCChannel *)output.getParam(0)); 212 IRCChannelTab *channelTab = getTabForChannel((IRCChannel *)output.getParam(0));
213 if (channelTab) 213 if (channelTab)
214 m_mainWindow->killTab(channelTab); 214 m_mainWindow->killTab(channelTab);
215 } 215 }
216 break; 216 break;
217 case OUTPUT_CHANACTION: { 217 case OUTPUT_CHANACTION: {
218 IRCChannelTab *channelTab = getTabForChannel((IRCChannel *)output.getParam(0)); 218 IRCChannelTab *channelTab = getTabForChannel((IRCChannel *)output.getParam(0));
219 channelTab->appendText("<font color=\"" + m_otherColor + "\">"+output.htmlMessage()+"</font><br>"); 219 channelTab->appendText("<font color=\"" + m_otherColor + "\">"+output.htmlMessage()+"</font><br>");
220 } 220 }
221 break; 221 break;
222 case OUTPUT_TOPIC: {
223 IRCChannel *channel = (IRCChannel *) output.getParam(0);
224 if (channel) {
225 IRCChannelTab *channelTab = getTabForChannel(channel);
226 if (channelTab) {
227 channelTab->appendText("<font color=\"" + m_notificationColor + "\">"+output.htmlMessage()+"</font><br>");
228 return;
229 }
230 }
231 appendText("<font color=\"" + m_notificationColor + "\">"+output.htmlMessage()+"</font><br>");
232 }
233 break;
222 case OUTPUT_QUIT: { 234 case OUTPUT_QUIT: {
223 QString nick = ((IRCPerson *)output.getParam(0))->nick(); 235 QString nick = ((IRCPerson *)output.getParam(0))->nick();
224 QListIterator<IRCChannelTab> it(m_channelTabs); 236 QListIterator<IRCChannelTab> it(m_channelTabs);
225 for (; it.current(); ++it) { 237 for (; it.current(); ++it) {
226 if (it.current()->list()->hasPerson(nick)) { 238 if (it.current()->list()->hasPerson(nick)) {
227 it.current()->appendText("<font color=\"" + m_notificationColor + "\">"+output.htmlMessage()+"</font><br>"); 239 it.current()->appendText("<font color=\"" + m_notificationColor + "\">"+output.htmlMessage()+"</font><br>");
228 it.current()->list()->update(); 240 it.current()->list()->update();
229 } 241 }
230 } 242 }
231 } 243 }
232 break; 244 break;
233 case OUTPUT_OTHERJOIN: 245 case OUTPUT_OTHERJOIN: