2
* wbmanager.cpp - Whiteboard manager
3
* Copyright (C) 2006 Joonas Govenius
6
* pepmanager.cpp - Classes for PEP
7
* Copyright (C) 2006 Remko Troncon
9
* This program is free software; you can redistribute it and/or
10
* modify it under the terms of the GNU General Public License
11
* as published by the Free Software Foundation; either version 2
12
* of the License, or (at your option) any later version.
14
* This program is distributed in the hope that it will be useful,
15
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
* GNU General Public License for more details.
19
* You should have received a copy of the GNU General Public License
20
* along with this library; if not, write to the Free Software
21
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25
#include "wbmanager.h"
31
// -----------------------------------------------------------------------------
33
WbManager::WbManager(Client* client, PsiAccount* pa) : client_(client) {
34
wbHash_ = QTime::currentTime().toString("z").toInt();
36
// Supported SVG features
37
supportedFeatures_.append("http://www.w3.org/TR/SVG11/feature#CoreAttribute");
38
supportedFeatures_.append("http://www.w3.org/TR/SVG11/feature#BasicStructure");
39
supportedFeatures_.append("http://www.w3.org/TR/SVG11/feature#BasicPaintAttribute");
40
supportedFeatures_.append("http://www.w3.org/TR/SVG11/feature#Shape");
41
supportedFeatures_.append("http://www.w3.org/TR/SVG11/feature#BasicText");
42
supportedFeatures_.append("http://www.w3.org/TR/SVG11/feature#Image");
45
connect(client_, SIGNAL(messageReceived(const Message &)), SLOT(messageReceived(const Message &)));
46
connect(client_, SIGNAL(groupChatLeft(const Jid &)), SLOT(groupChatLeft(const Jid &)));
47
connect(client_, SIGNAL(groupChatJoined(const Jid &)), SLOT(groupChatJoined(const Jid &)));
49
negotiationTimer_.setSingleShot(true);
50
negotiationTimer_.setInterval(120000);
51
connect(&negotiationTimer_, SIGNAL(timeout()), SLOT(negotiationTimeout()));
54
void WbManager::openWhiteboard(const Jid &target, const Jid &ownJid, bool groupChat) {
55
// See if we have a session for the JID
56
WbDlg* w = findWbDlg(target);
57
// else negotiate a new session and return null
59
startNegotiation(target, ownJid, groupChat);
64
void WbManager::messageReceived(const Message &message) {
65
// only process messages that contain a <wb/> with a nonempty 'session' attribute and that are addressed to this particular account
66
if(!message.whiteboard().attribute("session").isEmpty()) {
67
// skip messages from self
68
if(ownJids_.contains(message.from().full())) {
72
bool recordSessionId = false;
73
// Don't process delayed messages (chat history) but remember the session id
74
if(!message.spooled()) {
75
// qDebug() << (QString("<wb/> to session %1 from %2").arg(message.whiteboard().attribute("session")).arg(message.from().full()).toAscii());
77
// Check if the <wb/> contains a <protocol/>
78
QDomNodeList children = message.whiteboard().childNodes();
79
for(uint i=0; i < children.length(); i++) {
80
if(children.item(i).nodeName() == "protocol") {
81
w = negotiateSession(message);
84
// try finding a matching dialog for the session if new one not negotiated
86
w = findWbDlg(message.whiteboard().attribute("session"));
88
// only pass the message to existing sessions, not to newly negotiated ones.
89
w->incomingWbElement(message.whiteboard(), message.from());
91
recordSessionId = true;
94
// PsiPopup::PopupType popupType = PsiPopup::AlertNone;
95
// bool doPopup = false;
96
pa_->playSound(option.onevent[eChat2]);
97
// if(!w->isActiveWindow() || option.alertOpenChats) {
98
// popupType = PsiPopup::AlertChat;
99
// if(option.popupChats) {
100
// if(!option.noUnlistedPopup && message.type() != "groupchat") {
101
// // don't popup wb's from unlisted contacts
102
// if(!pa_->find(message.from()))
108
if(option.raiseChatWindow) {
111
// if ((popupType == PsiPopup::AlertChat && option.ppChat) /*&& makeSTATUS(status()) != STATUS_DND*/) {
112
// PsiPopup *popup = new PsiPopup(popupType, pa_);
113
// popup->setData(j, r, u, e);
115
// #if defined(Q_WS_MAC) && defined(HAVE_GROWL)
116
// PsiGrowlNotifier::instance()->popup(this, popupType, j, r, u, e);
120
recordSessionId = true;
121
if(recordSessionId) {
122
// check if a record of the session exists
123
bool alreadyRecorded = false;
124
foreach(detectedSession d, detectedSessions_) {
125
if(d.session == message.whiteboard().attribute("session")) {
126
alreadyRecorded = true;
130
if(!alreadyRecorded) {
131
// store a record of a detected session
132
detectedSession detected;
133
detected.session = message.whiteboard().attribute("session");
134
if(message.type() == "groupchat")
135
detected.jid = message.from().bare();
137
detected.jid = message.from();
138
detected.time = QTime::currentTime();
139
detectedSessions_.append(detected);
145
void WbManager::removeSession(const QString &session)
148
for(int i = 0; i < dialogs_.size(); i++) {
149
// does the session match?
150
if(dialogs_.at(i)->session() == session) {
151
d = dialogs_.takeAt(i);
154
QDomElement wb = doc.createElementNS("http://jabber.org/protocol/svgwb", "wb");
155
wb.setAttribute("session", session);
156
QDomElement protocol = doc.createElement("protocol");
157
protocol.appendChild(doc.createElement("left-session"));
158
wb.appendChild(protocol);
159
sendMessage(wb, d->target(), d->groupChat());
164
if(negotiations_.contains(session)) {
165
sendAbortNegotiation(session, negotiations_[session]->peer, d->groupChat());
166
negotiations_.remove(session);
168
// FIXME: Delete the dialog
169
d->setAttribute(Qt::WA_DeleteOnClose);
176
WbDlg* WbManager::negotiateSession(const Message &message) {
177
QString session = message.whiteboard().attribute("session");
178
if(session.isEmpty())
181
if(option.ignoreNonRoster && message.type() != "groupchat") {
182
// Ignore the message if contact not in roster
183
if(!pa_->find(message.from())) {
184
qDebug("Whiteboard invitation received from contact that is not in roster.");
190
QDomElement wb = doc.createElementNS("http://jabber.org/protocol/svgwb", "wb");
191
wb.setAttribute("session", session);
192
QDomElement protocol = doc.createElement("protocol");
193
wb.appendChild(protocol);
195
// Find or create a negotiation process
197
WbNegotiation* negotiation = negotiations_[session];
199
// Only accept further negotiation messages from the source we are already negotiationing with or if we've requested connection to a groupchat session
200
if(!negotiation->peer.compare(message.from()) && !(negotiation->groupChat && negotiation->peer.resource().isEmpty())) {
202
// if(negotiation->groupChat) {
203
// // If it is a history offer, turn it down
204
// for(uint i = 0; i < message.whiteboard().childNodes().length(); i++) {
205
// if(message.whiteboard().childNodes().at(i).nodeName() == "protocol") {
206
// for(uint j = 0; j < message.whiteboard().childNodes().at(i).childNodes().length(); j++) {
207
// if(message.whiteboard().childNodes().at(i).childNodes().at(j).nodeName() == "history-offer") {
208
sendAbortNegotiation(session, message.from(), true);
218
negotiation = new WbNegotiation;
219
negotiation->dialog = findWbDlg(session);
220
negotiation->peer = message.from();
221
if(negotiation->dialog) {
223
negotiation->role = WbNegotiation::Participant;
224
negotiation->state = WbNegotiation::Finished;
225
negotiation->target = negotiation->dialog->target();
226
negotiation->groupChat = negotiation->dialog->groupChat();
227
negotiation->ownJid = negotiation->dialog->ownJid();
229
if(message.type() == "groupchat") {
230
negotiation->groupChat = true;
231
// If we're being invited from a groupchat,
232
// ownJid is determined based on the "bare" part of ownJids_
233
foreach(QString j, ownJids_) {
234
if(message.from().bare() == j.left(j.indexOf("/"))) {
235
negotiation->ownJid = j;
239
// Also, the target is just the "bare" JID in a groupchat
240
negotiation->target = message.from().bare();
242
negotiation->groupChat = false;
243
negotiation->target = negotiation->peer;
245
if(negotiation->ownJid.isEmpty())
246
negotiation->ownJid = message.to();
247
negotiation->role = WbNegotiation::Joiner;
248
negotiation->state = WbNegotiation::NotStarted;
251
negotiationTimer_.start();
253
negotiations_[session] = negotiation;
256
// Process the children of the <wb/>
258
for(int i = 0; i < message.whiteboard().childNodes().count(); i++) {
259
n = message.whiteboard().childNodes().at(i);
264
if(n.nodeName() == "protocol") {
266
for(int j = 0; j < n.childNodes().count(); j++) {
267
m = n.childNodes().at(j);
269
// protocol = protocol.cloneNode(false).toElement();
270
while(protocol.hasChildNodes())
271
protocol.removeChild(protocol.firstChild());
272
// Check if <protocol/> contains <left-session/>
273
if(m.nodeName() == "left-session") {
274
if(negotiation->state == WbNegotiation::Finished) {
275
// Only end session if it's a 1-to-1 session
276
if(!negotiation->groupChat) {
277
negotiation->dialog->peerLeftSession();
280
} else if(m.nodeName() == "abort-negotiation") {
281
if(negotiation->role == WbNegotiation::Participant && negotiation->state < WbNegotiation::HistoryOffered && negotiation->state != WbNegotiation::DocumentBegan) {
282
// Abort, as in delete session, if still establishing it and not trying to create a new groupchat session
283
if(!(negotiation->groupChat && negotiation->peer.resource().isEmpty()))
284
negotiation->state = WbNegotiation::Aborted;
286
// Just remove the "negotation" but keep the dialog if sending history was cancelled
287
negotiation->state = WbNegotiation::Finished;
289
} else if(m.nodeName() == "invitation" && negotiation->state == WbNegotiation::NotStarted) {
290
// qDebug("4.1234567");
292
// Reject invitations from a source that we already have a session to
293
if(findWbDlg(negotiation->target))
296
// Check if the proposed features are supported
297
for(uint k = 0; k < m.childNodes().length(); k++) {
298
if(m.childNodes().at(k).nodeName() == "feature") {
299
if(!supportedFeatures_.contains(m.childNodes().at(k).toElement().text()))
305
protocol.appendChild(doc.createElement("accept-invitation"));
306
negotiation->state = WbNegotiation::InvitationAccepted;
308
protocol.appendChild(doc.createElement("abort-negotiation"));
309
negotiation->state = WbNegotiation::Aborted;
311
} else if(m.nodeName() == "connect-request" && negotiation->state == WbNegotiation::Finished) {
312
negotiation->state = WbNegotiation::HistoryOffered;
313
protocol.appendChild(doc.createElement("history-offer"));
315
} else if(m.nodeName() == "history-offer" && negotiation->state == WbNegotiation::ConnectionRequested) {
317
negotiation->state = WbNegotiation::HistoryAccepted;
318
negotiation->peer = message.from();
319
protocol.appendChild(doc.createElement("accept-history"));
320
} else if(m.nodeName() == "document-begin" && (negotiation->state == WbNegotiation::HistoryAccepted || negotiation->state == WbNegotiation::InvitationAccepted)) {
321
if(!negotiation->dialog) {
322
// qDebug() << QString("%1 %2 %3 %4").arg(negotiation->target.full()).arg(session).arg(negotiation->ownJid.full());
323
negotiation->dialog = createWbDlg(negotiation->target, session, negotiation->ownJid, negotiation->groupChat);
325
// negotiation->dialog->setAllowEdits(false);
326
if(negotiation->dialog) {
327
negotiation->dialog->setImporting(true);
328
negotiation->state = WbNegotiation::DocumentBegan;
330
negotiation->state = WbNegotiation::Aborted;
331
} else if(m.nodeName() == "document-end" && negotiation->state == WbNegotiation::DocumentBegan) {
332
negotiation->state = WbNegotiation::Finished;
333
for(int k = 0; k < m.childNodes().count(); k++) {
334
if(m.childNodes().at(k).nodeName() == "last-wb") {
335
QDomElement last = m.childNodes().at(k).toElement();
336
negotiation->dialog->eraseQueueUntil(last.attribute("sender"), last.attribute("hash"));
340
negotiation->dialog->setImporting(false);
341
// negotiation->dialog->setAllowEdits(true);
342
} else if ((m.nodeName() == "accept-history" && negotiation->state == WbNegotiation::HistoryOffered) || (m.nodeName() == "accept-invitation" && negotiation->state == WbNegotiation::InvitationSent)) {
344
if(!negotiation->dialog) {
345
negotiation->dialog = createWbDlg(negotiation->target, session, negotiation->ownJid, negotiation->groupChat);
346
if(!negotiation->dialog) {
347
sendAbortNegotiation(session, negotiation->peer, negotiation->groupChat);
351
negotiation->dialog->setQueueing(true);
352
// create the wb element with the history of the whiteboard
353
QDomElement history = doc.createElementNS("http://jabber.org/protocol/svgwb", "wb");
354
history.setAttribute("session", session);
355
QDomElement documentBegin = doc.createElement("protocol");
356
documentBegin.appendChild(doc.createElement("document-begin"));
357
QDomElement documentEnd = doc.createElement("protocol");
358
documentEnd.appendChild(doc.createElement("document-end"));
359
QDomElement lastWb = doc.createElement("last-wb");
360
lastWb.setAttribute("sender", negotiation->dialog->lastWb()["sender"]);
361
lastWb.setAttribute("hash", negotiation->dialog->lastWb()["hash"]);
362
documentEnd.appendChild(lastWb);
364
// append <document-begin/>
365
history.appendChild(documentBegin);
366
// append <new/> and <configure/>s or each element
367
foreach(WbItem* item, negotiation->dialog->snapshot()) {
369
QString oldParent = item->parentWbItem();
370
QDomNode insertReference = history.lastChild();
371
QDomElement newElement = item->svg();
372
QDomElement configure;
374
// do each of the undos and simultaneously create <configure/>'s that represent the edits
375
for(int k = item->undos.size() - 1; k >= 0; k--) {
377
// Note: don't remove the undos
378
EditUndo u = item->undos.at(k);
379
// only create one <configure/> per version
380
if(version != u.version) {
382
// append the <configure/> for previous version
383
if(configure.hasChildNodes()) {
385
if(insertReference.isNull()) {
387
history.insertBefore(configure.cloneNode(), insertReference);
389
history.insertAfter(configure.cloneNode(), insertReference);
391
// create the <configure/> for the older version
393
configure = doc.createElement("configure");
394
configure.setAttribute("target", item->id());
395
configure.setAttribute("version", version);
397
if(u.type == Edit::AttributeEdit) {
400
QDomElement attributeEdit = doc.createElement("attribute");
401
attributeEdit.setAttribute("name", u.attribute);
402
attributeEdit.appendChild(doc.createTextNode(newElement.attribute(u.attribute)));
403
configure.insertBefore(attributeEdit, QDomNode());
405
if(u.oldValue.isNull()) {
407
newElement.removeAttribute(u.attribute);
409
newElement.setAttribute(u.attribute, u.oldValue);
410
} else if(u.type == Edit::ContentEdit) {
412
// Create the edit and do the undo
413
QDomElement contentEdit = doc.createElement("content");
414
while(newElement.hasChildNodes()) {
416
contentEdit.appendChild(newElement.firstChild());
417
newElement.removeChild(newElement.firstChild());
419
configure.insertBefore(contentEdit, QDomNode());
420
for(uint j=0; j < u.oldContent.length(); j++) {
422
newElement.appendChild(u.oldContent.at(j));
424
} else if(u.type == Edit::ParentEdit) {
427
QDomElement parentEdit = doc.createElement("parent");
428
parentEdit.appendChild(doc.createTextNode(oldParent));
429
configure.insertBefore(parentEdit, QDomNode());
431
oldParent = u.oldParent;
435
// Append the last <configure/>
436
if(configure.hasChildNodes()) {
438
if(insertReference.isNull()) {
439
history.insertBefore(configure.cloneNode(), insertReference);
441
history.insertAfter(configure.cloneNode(), insertReference);
444
// Create and insert the <new/> element before the <configure/>s created above.
445
if(item->id() != "root") {
446
QDomElement newEdit = doc.createElement("new");
447
newEdit.setAttribute("id", item->id());
448
newEdit.setAttribute("parent", oldParent);
449
newEdit.setAttribute("index", item->index());
450
newEdit.appendChild(newElement);
451
if(insertReference.isNull()) {
453
history.insertBefore(newEdit, insertReference);
455
history.insertAfter(newEdit, insertReference);
459
// append <protocol><documend-end/><last-edit/></protocol>
460
history.appendChild(documentEnd);
462
sendMessage(history, negotiation->peer, negotiation->groupChat);
463
negotiation->state = WbNegotiation::Finished;
464
negotiation->dialog->setQueueing(false);
465
} /*else if(m.nodeName() == "decline-invitation" && negotiation->state == WbNegotiation::ConnectionRequested) {
467
negotiation->state = WbNegotiation::Aborted;
469
if(protocol.hasChildNodes()) {
470
sendMessage(wb.cloneNode().toElement(), negotiation->peer, negotiation->groupChat);
473
} else if((negotiation->state == WbNegotiation::DocumentBegan || negotiation->state == WbNegotiation::Finished) && negotiation->dialog) {
474
// If state after <document-begin/>,
475
// pass the edits to the dialog
476
wb.removeChild(protocol);
478
negotiation->dialog->incomingWbElement(wb, negotiation->peer);
480
wb.appendChild(protocol);
483
// Delete erroneous attempts and finished negotiations
485
if(negotiation->state == WbNegotiation::NotStarted) {
486
negotiations_.remove(session);
488
} else if(negotiation->state == WbNegotiation::Finished) {
490
if(negotiation->dialog) {
491
w = negotiation->dialog;
492
if(!dialogs_.contains(negotiation->dialog))
493
dialogs_.append(negotiation->dialog);
495
negotiations_.remove(session);
498
} else if(negotiation->state == WbNegotiation::Aborted) {
499
if(negotiation->dialog)
500
negotiation->dialog->endSession();
501
negotiations_.remove(session);
509
void WbManager::startNegotiation(const Jid &target, const Jid &ownJid, bool groupChat, QList<QString> features) {
510
// generate a session identifier
512
// Check if there's detected activity of a session to the specified jid
513
// TODO: Make a custom dialog.
514
QList<QString> potentialSessions;
515
// Remove old detected sessions
516
removeDetectedSession(QString());
517
// potentialSessions.append(tr("New session"));
518
// potentialSessions.append(tr("Manual..."));
519
foreach(detectedSession detected, detectedSessions_) {
520
if(detected.jid.compare(target))
521
potentialSessions.append(detected.session);
524
session = QInputDialog::getItem(0, tr("Specify Session"),tr("If you wish to join and existing session,\nchoose from one of the recently detected sessions\nor type in your own. Else leave the field empty."), potentialSessions, 0, true, &ok);
527
// else, generate an arbitrary session
528
// TODO: this could conflict with other clients starting a session at the same time though it's extremely unlikely
529
bool existing = true;
530
if(session.isEmpty()) {
533
session = QTime::currentTime().toString("msz");
534
} while (findWbDlg(session));
536
// Prepare the list of features
537
if(!features.size()) {
538
// If none is specified, try all supported features;
539
features = supportedFeatures_;
540
} else foreach(QString f, features) {
541
// Check that all features are actually supported
542
if(!supportedFeatures_.contains(f))
543
features.removeAll(f);
545
// Prepare the connect request
547
QDomElement wb = doc.createElementNS("http://jabber.org/protocol/svgwb", "wb");
548
wb.setAttribute("session", session);
549
QDomElement protocol = doc.createElement("protocol");
552
request = doc.createElement("connect-request");
554
request = doc.createElement("invitation");
555
QDomElement feature = doc.createElement("feature");
556
foreach(QString f, features) {
557
feature = feature.cloneNode(false).toElement();
558
feature.appendChild(doc.createTextNode(f));
559
request.appendChild(feature);
562
protocol.appendChild(request);
563
wb.appendChild(protocol);
564
// Create the negotiation object
565
WbNegotiation* negotiation = new WbNegotiation;
567
negotiation->role = WbNegotiation::Participant;
568
negotiation->state = WbNegotiation::ConnectionRequested;
570
negotiation->role = WbNegotiation::Joiner;
571
negotiation->state = WbNegotiation::InvitationSent;
573
negotiation->target = target;
574
negotiation->peer = target;
575
negotiation->ownJid = ownJid;
576
negotiation->groupChat = groupChat;
577
negotiation->dialog = 0;
578
negotiations_[session] = negotiation;
580
sendMessage(wb, target, groupChat);
582
// Reset the timeout for negotiations
583
negotiationTimer_.start();
588
void WbManager::negotiationTimeout() {
589
WbNegotiation* negotiation;
590
foreach(QString session, negotiations_.keys()){
591
negotiation = negotiations_.take(session);
592
if(negotiation->role == WbNegotiation::Participant && negotiation->state < WbNegotiation::HistoryOffered && negotiation->state != WbNegotiation::DocumentBegan) {
593
if(negotiation->dialog)
594
negotiation->dialog->endSession();
595
sendAbortNegotiation(session, negotiation->peer, negotiation->groupChat);
601
// #include <QTextStream>
602
void WbManager::sendMessage(QDomElement wb, const Jid & receiver, bool groupChat) {
605
// QTextStream s(&debug);
607
// qDebug() << (debug.toAscii());
609
// // Split large wb elements to smaller ones if possible
611
// QTextStream stream(&debug);
612
// wb.save(stream, 0);
613
// QList<QDomElement> wbs;
614
// while(str > 6000) {
616
// wb.save(stream, 0);
619
WbDlg* dialog = qobject_cast<WbDlg*>(sender());
621
dialog->setLastWb(dialog->ownJid().full(), QString("%1").arg(wbHash_));
622
// Add a unique hash to each sent wb element
623
wb.setAttribute("hash", wbHash_);
626
if(groupChat && receiver.resource().isEmpty())
627
m.setType("groupchat");
628
if(client_->isActive())
629
client_->sendMessage(m);
631
//TODO: queue the message
635
WbDlg* WbManager::findWbDlg(const Jid &jid) {
636
// find if a dialog for the jid already exists
637
foreach(WbDlg* w, dialogs_) {
638
// does the jid match?
639
if(w->target().compare(jid)) {
646
WbDlg* WbManager::findWbDlg(const QString &session) {
647
// find if a dialog for the session already exists
648
foreach(WbDlg* w, dialogs_) {
649
// does the session match?
650
if(w->session() == session)
656
WbDlg* WbManager::createWbDlg(const Jid &target, QString session, const Jid &ownJid, bool groupChat) {
657
if(session.isEmpty() || !target.isValid())
659
if(!ownJids_.contains(ownJid.full()))
660
ownJids_.append(ownJid.full());
662
WbDlg* w = new WbDlg(target, session, ownJid, groupChat, pa_);
663
// connect the signals
664
connect(w, SIGNAL(newWbElement(QDomElement, Jid, bool)), SLOT(sendMessage(const QDomElement &, const Jid &, bool)));
665
connect(w, SIGNAL(sessionEnded(QString)), SLOT(removeSession(const QString &)));
666
removeDetectedSession(session);
668
// Note: the dialog should be added to dialogs_ once negotiation is finished
671
void WbManager::sendAbortNegotiation(QString session, const Jid &peer, bool groupChat) {
672
QDomDocument doc = QDomDocument();
673
QDomElement wb = doc.createElementNS("http://jabber.org/protocol/svgwb", "wb");
674
wb.setAttribute("session", session);
675
QDomElement protocol = doc.createElement("protocol");
676
protocol.appendChild(doc.createElement("abort-negotiation"));
677
wb.appendChild(protocol);
678
sendMessage(wb, peer, groupChat);
681
void WbManager::removeDetectedSession(const QString &session) {
682
for(int i = 0; i < detectedSessions_.size(); i++) {
683
// Remove the specified session from the list
684
if(detectedSessions_.at(i).session == session)
685
detectedSessions_.removeAt(i);
686
else if(detectedSessions_.at(i).time.secsTo(QTime::currentTime()) > 1800)
687
// Remove detected session that are old
688
detectedSessions_.removeAt(i);
692
void WbManager::groupChatLeft(const Jid &jid) {
693
for(int i = 0; i < ownJids_.size(); i++) {
694
if(jid.bare() == ownJids_.at(i).left(ownJids_.at(i).indexOf("/")))
695
ownJids_.removeAt(i);
697
WbDlg* w = findWbDlg(jid);
702
void WbManager::groupChatJoined(const Jid &ownJid) {
703
if(!ownJids_.contains(ownJid.full()))
704
ownJids_.append(ownJid.full());