diff options
Diffstat (limited to 'libksirtet/lib/meeting.cpp')
-rw-r--r-- | libksirtet/lib/meeting.cpp | 575 |
1 files changed, 575 insertions, 0 deletions
diff --git a/libksirtet/lib/meeting.cpp b/libksirtet/lib/meeting.cpp new file mode 100644 index 00000000..2cb6e285 --- /dev/null +++ b/libksirtet/lib/meeting.cpp @@ -0,0 +1,575 @@ +#include "meeting.h" + +#include <qmessagebox.h> +#include <qpushbutton.h> + +#include <klocale.h> +#include <kmessagebox.h> + +#include "defines.h" +#include "mp_option.h" + +#define LIST_INTERVAL 0 + + +NetMeeting::NetMeeting(const cId &_id, Socket *socket, + MPOptionWidget *option, + bool _server, QWidget *parent, const char * name) +: KDialogBase(Plain, i18n("Network Meeting"), + (_server ? Ok|Cancel|Help : Cancel|Help), + (_server ? Ok : Cancel), parent, name), + server(_server), ow(option), id(_id), socketRemoved(FALSE) +{ + sm.append(socket, SocketManager::ReadWrite); + sm[0]->notifier()->setEnabled(TRUE); + +/* top layout */ + QVBoxLayout *top = new QVBoxLayout(plainPage(), spacingHint()); + top->setResizeMode(QLayout::Fixed); + + // server line + spl = new MeetingLine(server, server, true, plainPage()); + top->addWidget(spl); + + // widget list + wl = new WidgetList<MeetingLine>(LIST_INTERVAL, plainPage()); + wl->hide(); + top->addWidget(wl); + + labWait = new QLabel(i18n("Waiting for clients"), plainPage()); + labWait->setAlignment(AlignCenter); + top->addWidget(labWait); + + // options widget +// if (ow) top->addWidget(ow); #### FIXME + + // status bar + status = new QStatusBar(plainPage()); + status->setSizeGripEnabled(false); + top->addWidget(status); + + // buttons + enableButtonSeparator(TRUE); + if (server) { + setButtonOK(i18n("Start Game")); + enableButtonOK(FALSE); + } + setButtonCancel(server ? i18n("Abort") : i18n("Quit")); + enableButton(Help, FALSE); +} + +NetMeeting::~NetMeeting() +{} + +void NetMeeting::appendLine(const MeetingLineData &pld, bool server) +{ + MeetingLine *pl; + pl = new MeetingLine(pld.own, server, false, wl); + if (pld.own) connect(pl, SIGNAL(textChanged(const QString &)), + SLOT(textChanged(const QString &))); + else message(i18n("A new client has just arrived (#%1)") + .arg(wl->size()+1)); + pl->setData(pld.ed); + connect(pl, SIGNAL(typeChanged(MeetingCheckBox::Type)), + SLOT(typeChanged(MeetingCheckBox::Type))); + wl->append(pl); + waiting(); +} + +void NetMeeting::removeLine(uint i) +{ + wl->remove(i); + waiting(); +} + +void NetMeeting::waiting() +{ + if ( wl->size() ) { + labWait->hide(); + wl->show(); + } else { + labWait->show(); + wl->hide(); + } + if (server) enableButtonOK(ready()); +} + +void NetMeeting::setType(const TypeInfo &ti) +{ + if ( ti.i==0 ) spl->setType(ti.type); // in fact should not append + else { + wl->widget(ti.i-1)->setType(ti.type); + if (server) enableButtonOK(ready()); + } +} + +void NetMeeting::setText(const TextInfo &ti) +{ + if ( ti.i==0 ) spl->setText(ti.text); + else wl->widget(ti.i-1)->setText(ti.text); +} + +bool NetMeeting::ready() const +{ + int nbReady = 0; + for(uint k=0; k<wl->size(); k++) { + switch ( wl->widget(k)->type() ) { + case MeetingCheckBox::Ready : nbReady++; break; + case MeetingCheckBox::NotReady : return FALSE; + default : break; + } + } + return ( nbReady!=0 ); +} + +void NetMeeting::cleanReject(const QString &str) +{ + sm.clean(); // remove the sockets immediately to avoid possible further mess + if ( !str.isEmpty() ) + KMessageBox::information(this, str, caption()); + KDialogBase::reject(); +} + +#define WRITE(i) if ( !sm[i]->write() ) { writeError(i); return; } +#define CHECK_READ(i) if ( !sm[i]->readingStream().readOk() ) { dataError(i); return; } + +// Read incoming data +void NetMeeting::readNotifier(int fd) +{ + int i = sm.find(fd); + Q_ASSERT( i!=-1 ); + switch ( sm[i]->read() ) { + case -1: readError(i); break; + case 0: brokeError(i); break; + default: readData(i); + } +} + +void NetMeeting::readData(uint i) +{ + // get message type + MeetingMsgFlag mt; + sm[i]->readingStream() >> mt; + CHECK_READ(i); + switch (mt) { + case EndFlag: endFlag(i); break; + case NewFlag: newFlag(i); break; + case Mod_TextFlag: modTextFlag(i); break; + case Mod_TypeFlag: modTypeFlag(i); break; + case IdFlag: idFlag(i); break; + case DelFlag: delFlag(i); break; + case Mod_OptFlag: modOptFlag(i); break; + case PlayFlag: playFlag(i); break; + default: dataError(i); + } + + if (socketRemoved) socketRemoved = FALSE; + else if ( !sm[i]->readingStream().atEnd() ) + readData(i); // more pending data +} + +void NetMeeting::readError(uint i) + { netError(i, i18n("Error reading data from")); } +void NetMeeting::dataError(uint i) + { netError(i, i18n("Unknown data from")); } +void NetMeeting::writeError(uint i) + { netError(i, i18n("Error writing to")); } +void NetMeeting::brokeError(uint i) + { netError(i, i18n("Link broken or empty data from")); } + +bool NetMeeting::checkState(uint i, PlayerState s) +{ + bool ok = ( players[i]==s ); + if (!ok) dataError(i); + return ok; +} + +bool NetMeeting::checkAndSetState(uint i, PlayerState os, PlayerState ns) +{ + bool ok = checkState(i, os); + if (ok) players[i] = ns; + return ok; +} + +void NetMeeting::reject() +{ + // send an End flag + sm.commonWritingStream() << EndFlag; + writeToAll(); + + cleanReject(); +} + +void NetMeeting::accept() +{ + KDialogBase::accept(); +} + +void NetMeeting::message(const QString &str) +{ + status->message(str, 3000); +} + +/** ServerNetMeeting *********************************************************/ +ServerNetMeeting::ServerNetMeeting(const cId &id, + const RemoteHostData &r, MPOptionWidget *option, + QPtrList<RemoteHostData> &arhd, QWidget *parent, const char * name) +: NetMeeting(id, r.socket, option, TRUE, parent, name), rhd(arhd) +{ + connect(sm[0]->notifier(), SIGNAL(activated(int)), SLOT(newHost(int))); + players.append(Accepted); // server + + // set server line + ExtData ed(r.bds, "", MeetingCheckBox::Ready); + spl->setData(ed); + connect(spl, SIGNAL(textChanged(const QString &)), + SLOT(textChanged(const QString &))); + + // options signal + if (ow) connect(ow, SIGNAL(changed()), SLOT(optionsChanged())); +} + +void ServerNetMeeting::writeToAll(uint i) +{ + for (uint k=1; k<sm.size(); k++) { + if ( k==i ) continue; + if ( !sm.writeCommon(k) ) writeError(k); + } + sm.commonWritingStream().clear(); +} + +void ServerNetMeeting::netError(uint i, const QString &type) +{ + Q_ASSERT( i!=0 ); + disconnectHost(i, i18n("%1 client #%2: disconnect it").arg(type).arg(i)); +} + +void ServerNetMeeting::disconnectHost(uint i, const QString &str) +{ + sm.remove(i, true); + socketRemoved = TRUE; + if ( players[i]==Accepted ) { + removeLine(i-1); + + // Send a Del message to all (other) clients + sm.commonWritingStream() << DelFlag << i; + writeToAll(); + } + players.remove(players.at(i)); + message(str); +} + +void ServerNetMeeting::newHost(int) +{ + KExtendedSocket *s; + int res = sm[0]->accept(s); + if ( res!=0 ) { + message(i18n("Failed to accept incoming client:\n%1") + .arg(socketError(s))); + return; + } + players.append(NewPlayer); + Socket *socket = new Socket(s, true); + uint i = sm.append(socket, SocketManager::ReadWrite); + connect(sm[i]->notifier(), SIGNAL(activated(int)), + SLOT(readNotifier(int))); + sm[i]->notifier()->setEnabled(TRUE); +} + +void ServerNetMeeting::idFlag(uint i) +{ + bool b = checkAndSetState(i, NewPlayer, IdChecked); + Q_ASSERT(b); + + // get client id + cId clientId; + sm[i]->readingStream() >> clientId; + CHECK_READ(i); + + // compare id + id.check(clientId); + + // send result to client + Stream &s = sm[i]->writingStream(); + s << IdFlag << id; + WRITE(i); + + // if not accepted : remove socket and player from list + if ( !id.accepted() ) + disconnectHost(i, i18n("Client rejected for incompatible ID")); +} + +void ServerNetMeeting::endFlag(uint i) +{ + disconnectHost(i, i18n("Client #%1 has left").arg(i)); +} + +void ServerNetMeeting::newFlag(uint i) +{ + checkAndSetState(i, IdChecked, Accepted); + + // get line infos from new client (GameData struct) + MeetingLineData pld; + sm[i]->readingStream() >> pld.ed.bds; + CHECK_READ(i); + + // complete the MeetingLineData struct with initial values + pld.own = FALSE; // client line + pld.ed.type = MeetingCheckBox::NotReady; // not ready by default + pld.ed.text = ""; // empty line to begin with + appendLine(pld, TRUE); + + // send to the new client already present lines including its own + // (New flag + MeetingLineData struct) + spl->data(pld.ed); + sm[i]->writingStream() << NewFlag << pld.ed; + for(uint k=1; k<sm.size(); k++) { + wl->widget(k-1)->data(pld.ed); + pld.own = ( k==i ); + sm[i]->writingStream() << NewFlag << pld; + } + WRITE(i); + + // send to all other clients the new line (New flag + MeetingLineData struct) + wl->widget(i-1)->data(pld.ed); + pld.own = FALSE; + sm.commonWritingStream() << NewFlag << pld; + writeToAll(i); +} + +void ServerNetMeeting::modTextFlag(uint i) +{ + checkState(i-1, Accepted); + + // the client i has just sent a new text (QString) + TextInfo ti; + sm[i]->readingStream() >> ti.text; + CHECK_READ(i); + ti.i = i; + setText(ti); + + // send it to all other clients (Mod_Text flag + TextInfo struct) + sm.commonWritingStream() << Mod_TextFlag << ti; + writeToAll(i); +} + +void ServerNetMeeting::modTypeFlag(uint i) +{ + checkState(i-1, Accepted); + + // a client has just sent a new TCB type (TCB type) + TypeInfo ti; + sm[i]->readingStream() >> ti.type; + CHECK_READ(i); + ti.i = i; + setType(ti); + + // send it to all other clients (Mod_Type flag + TypeInfo struct) + sm.commonWritingStream() << Mod_TypeFlag << ti; + writeToAll(i); +} + +void ServerNetMeeting::textChanged(const QString &text) +{ + // server line text changed : send to every clients (Mod_Text flag + TextInfo struct) + TextInfo ti; ti.i = 0; ti.text = text; + sm.commonWritingStream() << Mod_TextFlag << ti; + writeToAll(); +} + +void ServerNetMeeting::typeChanged(MeetingCheckBox::Type type) +{ + Q_ASSERT( sender()!=spl ); // server TCB not modifiable + // the server has changed a client TCB + + // find the changed TCB index + TypeInfo ty; + ty.type = type; + for (ty.i=0; ty.i<wl->size(); ty.i++) + if ( sender()==wl->widget(ty.i) ) break; + ty.i++; + + // TCB change : send to every clients (Mod_Type flag + TypeInfo struct) + sm.commonWritingStream() << Mod_TypeFlag << ty; + writeToAll(); + if (server) enableButtonOK(ready()); +} + +void ServerNetMeeting::accept() +{ + Q_ASSERT( ready() && rhd.count()==0 ); + + // stop receiving data from clients (will be buffered by OS) + for (uint k=0; k<sm.size(); k++) disconnect(sm[k]->notifier()); + sm.remove(0, true); + + // check which client will play and fill RemoteHostData array + ExtData ed; + bool willPlay; + for (uint k=1; k<players.count(); k++) { + willPlay = FALSE; + + if ( players[k]==Accepted ) { // client with lines + wl->widget(k-1)->data(ed); + if ( ed.type==MeetingCheckBox::Ready ) { + willPlay = TRUE; + RemoteHostData *r = new RemoteHostData; + r->socket = sm[0]; + r->bds = ed.bds; + rhd.append(r); + } + + // send play message to client (Play flag + // + bool [accepted/rejected]) + sm[0]->writingStream() << PlayFlag << (Q_UINT8)willPlay; + // if write failed and the client is not playing : silently + // put it aside ... + if ( !sm[0]->write() && willPlay ) { + cleanReject(i18n("Unable to write to client #%1 at game " + "beginning.")); + return; + } + } + + sm[0]->notifier()->setEnabled(false); + sm.remove(0, !willPlay); + } + + NetMeeting::accept(); +} + +void ServerNetMeeting::optionsChanged() +{ + sm.commonWritingStream() << Mod_OptFlag; + ow->dataOut( sm.commonWritingStream() ); + writeToAll(); +} + +/** ClientNetMeeting *********************************************************/ +ClientNetMeeting::ClientNetMeeting(const cId &id, + const RemoteHostData &rhd, MPOptionWidget *option, + QWidget *parent, const char * name) +: NetMeeting(id, rhd.socket, option, FALSE, parent, name), bds(rhd.bds) +{ + connect(sm[0]->notifier(), SIGNAL(activated(int)), + SLOT(readNotifier(int))); + players.append(NewPlayer); // server player + + // Send id to server (Id flag + Id struct) + sm.commonWritingStream() << IdFlag << id; + writeToAll(); // what happens if there is a message box appearing before exec() call ?? +} + +void ClientNetMeeting::netError(uint, const QString &str) +{ + cleanReject(i18n("%1 server: aborting connection.").arg(str)); +} + +void ClientNetMeeting::writeToAll(uint) +{ + if ( !sm.writeCommon(0) ) writeError(0); + sm.commonWritingStream().clear(); +} + +void ClientNetMeeting::idFlag(uint) +{ + checkAndSetState(0, NewPlayer, IdChecked); + + // read Id result (Id flag + Id struct) + cId serverId; + sm[0]->readingStream() >> serverId; + CHECK_READ(0); + + // check result + if ( !serverId.accepted() ) cleanReject(serverId.errorMessage(id)); + else { + // send client info (New flag + GameData struct) + sm.commonWritingStream() << NewFlag << bds; + writeToAll(); + } +} + +void ClientNetMeeting::newFlag(uint) +{ + if ( players[0]==IdChecked ) { + ExtData ed; + sm[0]->readingStream() >> ed; + spl->setData(ed); + players[0] = Accepted; + } else { + MeetingLineData pld; + sm[0]->readingStream() >> pld; + appendLine(pld, FALSE); + } + CHECK_READ(0); +} + +void ClientNetMeeting::modTextFlag(uint) +{ + // receive new text from server (TextInfo struct) + TextInfo ti; + sm[0]->readingStream() >> ti; + CHECK_READ(0); + setText(ti); +} + +void ClientNetMeeting::modTypeFlag(uint) +{ + // receive new type from server (TypeInfo struct) + TypeInfo ti; + sm[0]->readingStream() >> ti; + CHECK_READ(0); + setType(ti); +} + +void ClientNetMeeting::delFlag(uint) +{ + // receive client number (uint) + uint k; + sm[0]->readingStream() >> k; + CHECK_READ(0); + removeLine(k-1); + message(i18n("Client %1 has left").arg(k)); +} + +void ClientNetMeeting::textChanged(const QString &text) +{ + // text changed : send to server (Mod_Text flag + QString) + sm.commonWritingStream() << Mod_TextFlag << text; + writeToAll(); +} + +void ClientNetMeeting::typeChanged(MeetingCheckBox::Type type) +{ + // type changed : send to server (Mod_Type flag + TCB) + sm.commonWritingStream() << Mod_TypeFlag << type; + writeToAll(); +} + +void ClientNetMeeting::playFlag(uint) +{ + // receive accept or reject (bool) + Q_UINT8 i; + sm[0]->readingStream() >> i; + CHECK_READ(0); + sm[0]->notifier()->setEnabled(false); + sm.remove(0, i==0); + socketRemoved = true; + if (i) accept(); + else cleanReject(i18n("The game has begun without you\n" + "(You have been excluded by the server).")); +} + +void ClientNetMeeting::modOptFlag(uint) +{ + // read new option data + ow->dataIn( sm[0]->readingStream() ); + CHECK_READ(0); +} + +void ClientNetMeeting::endFlag(uint) +{ + // abort from server + cleanReject(i18n("The server has aborted the game.")); +} +#include "meeting.moc" |