You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
576 lines
14 KiB
576 lines
14 KiB
#include "meeting.h"
|
|
|
|
#include <tqmessagebox.h>
|
|
#include <tqpushbutton.h>
|
|
|
|
#include <tdelocale.h>
|
|
#include <tdemessagebox.h>
|
|
|
|
#include "defines.h"
|
|
#include "mp_option.h"
|
|
|
|
#define LIST_INTERVAL 0
|
|
|
|
|
|
NetMeeting::NetMeeting(const cId &_id, Socket *socket,
|
|
MPOptionWidget *option,
|
|
bool _server, TQWidget *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 */
|
|
TQVBoxLayout *top = new TQVBoxLayout(plainPage(), spacingHint());
|
|
top->setResizeMode(TQLayout::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 TQLabel(i18n("Waiting for clients"), plainPage());
|
|
labWait->setAlignment(AlignCenter);
|
|
top->addWidget(labWait);
|
|
|
|
// options widget
|
|
// if (ow) top->addWidget(ow); #### FIXME
|
|
|
|
// status bar
|
|
status = new TQStatusBar(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, TQ_SIGNAL(textChanged(const TQString &)),
|
|
TQ_SLOT(textChanged(const TQString &)));
|
|
else message(i18n("A new client has just arrived (#%1)")
|
|
.arg(wl->size()+1));
|
|
pl->setData(pld.ed);
|
|
connect(pl, TQ_SIGNAL(typeChanged(MeetingCheckBox::Type)),
|
|
TQ_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 TQString &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 TQString &str)
|
|
{
|
|
status->message(str, 3000);
|
|
}
|
|
|
|
/** ServerNetMeeting *********************************************************/
|
|
ServerNetMeeting::ServerNetMeeting(const cId &id,
|
|
const RemoteHostData &r, MPOptionWidget *option,
|
|
TQPtrList<RemoteHostData> &arhd, TQWidget *parent, const char * name)
|
|
: NetMeeting(id, r.socket, option, TRUE, parent, name), rhd(arhd)
|
|
{
|
|
connect(sm[0]->notifier(), TQ_SIGNAL(activated(int)), TQ_SLOT(newHost(int)));
|
|
players.append(Accepted); // server
|
|
|
|
// set server line
|
|
ExtData ed(r.bds, "", MeetingCheckBox::Ready);
|
|
spl->setData(ed);
|
|
connect(spl, TQ_SIGNAL(textChanged(const TQString &)),
|
|
TQ_SLOT(textChanged(const TQString &)));
|
|
|
|
// options signal
|
|
if (ow) connect(ow, TQ_SIGNAL(changed()), TQ_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 TQString &type)
|
|
{
|
|
Q_ASSERT( i!=0 );
|
|
disconnectHost(i, i18n("%1 client #%2: disconnect it").arg(type).arg(i));
|
|
}
|
|
|
|
void ServerNetMeeting::disconnectHost(uint i, const TQString &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(), TQ_SIGNAL(activated(int)),
|
|
TQ_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 (TQString)
|
|
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 TQString &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 << (TQ_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,
|
|
TQWidget *parent, const char * name)
|
|
: NetMeeting(id, rhd.socket, option, FALSE, parent, name), bds(rhd.bds)
|
|
{
|
|
connect(sm[0]->notifier(), TQ_SIGNAL(activated(int)),
|
|
TQ_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 TQString &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 TQString &text)
|
|
{
|
|
// text changed : send to server (Mod_Text flag + TQString)
|
|
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)
|
|
TQ_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"
|