diff options
Diffstat (limited to 'kbackgammon/engines/fibs')
-rw-r--r-- | kbackgammon/engines/fibs/Makefile.am | 9 | ||||
-rw-r--r-- | kbackgammon/engines/fibs/clip.h | 39 | ||||
-rw-r--r-- | kbackgammon/engines/fibs/kbgfibs.cpp | 2314 | ||||
-rw-r--r-- | kbackgammon/engines/fibs/kbgfibs.h | 479 | ||||
-rw-r--r-- | kbackgammon/engines/fibs/kbgfibschat.cpp | 828 | ||||
-rw-r--r-- | kbackgammon/engines/fibs/kbgfibschat.h | 273 | ||||
-rw-r--r-- | kbackgammon/engines/fibs/kbginvite.cpp | 185 | ||||
-rw-r--r-- | kbackgammon/engines/fibs/kbginvite.h | 113 | ||||
-rw-r--r-- | kbackgammon/engines/fibs/kplayerlist.cpp | 902 | ||||
-rw-r--r-- | kbackgammon/engines/fibs/kplayerlist.h | 298 |
10 files changed, 5440 insertions, 0 deletions
diff --git a/kbackgammon/engines/fibs/Makefile.am b/kbackgammon/engines/fibs/Makefile.am new file mode 100644 index 00000000..e32522de --- /dev/null +++ b/kbackgammon/engines/fibs/Makefile.am @@ -0,0 +1,9 @@ +noinst_LTLIBRARIES = libkbgfibs.la + +libkbgfibs_la_SOURCES = kbgfibs.cpp kplayerlist.cpp kbginvite.cpp kbgfibschat.cpp + +INCLUDES= -I$(top_srcdir)/kbackgammon/engines -I$(top_srcdir)/libkdegames \ + -I$(top_srcdir)/kbackgammon $(all_includes) + +METASOURCES = AUTO + diff --git a/kbackgammon/engines/fibs/clip.h b/kbackgammon/engines/fibs/clip.h new file mode 100644 index 00000000..e016eb5b --- /dev/null +++ b/kbackgammon/engines/fibs/clip.h @@ -0,0 +1,39 @@ +/* + + This file defines constants of the "CLIent Protocol" of FIBS. + It comes directly from Marvin and I guess it is copyrighted + by him. If you have questions regarding this file, try to + visit + + http://fibs.demon.co.uk/clip.html + +*/ + +#ifndef KFIBS_CLIP_H +#define KFIBS_CLIP_H + + +#define CLIP_VERSION 1008 + +#define CLIP_WELCOME 1 +#define CLIP_OWN_INFO 2 +#define CLIP_MOTD_BEGIN 3 +#define CLIP_MOTD_END 4 +#define CLIP_WHO_INFO 5 +#define CLIP_WHO_END 6 +#define CLIP_LOGIN 7 +#define CLIP_LOGOUT 8 +#define CLIP_MESSAGE 9 +#define CLIP_MESSAGE_DELIVERED 10 +#define CLIP_MESSAGE_SAVED 11 +#define CLIP_SAYS 12 +#define CLIP_SHOUTS 13 +#define CLIP_WHISPERS 14 +#define CLIP_KIBITZES 15 +#define CLIP_YOU_SAY 16 +#define CLIP_YOU_SHOUT 17 +#define CLIP_YOU_WHISPER 18 +#define CLIP_YOU_KIBITZ 19 + + +#endif // KFIBS_CLIP_H diff --git a/kbackgammon/engines/fibs/kbgfibs.cpp b/kbackgammon/engines/fibs/kbgfibs.cpp new file mode 100644 index 00000000..06fdaec7 --- /dev/null +++ b/kbackgammon/engines/fibs/kbgfibs.cpp @@ -0,0 +1,2314 @@ +/* Yo Emacs, this -*- C++ -*- + + Copyright (C) 1999-2001 Jens Hoefkens + jens@hoefkens.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + $Id$ + +*/ + +/* + + TODO: popup dialog for accept/reject and join ?? + clear the chat history? + game over, clear the caption? + need show saved + need buddy list + need wait for player,... + +*/ + +#include "kbgfibs.h" +#include "kbgfibs.moc" + +#include <kapplication.h> +#include <kconfig.h> +#include <qtimer.h> +#include <qcheckbox.h> +#include <qlayout.h> +#include <qstring.h> +#include <qsocket.h> +#include <qpopupmenu.h> +#include <qgroupbox.h> +#include <qbuttongroup.h> +#include <qradiobutton.h> +#include <kmainwindow.h> +#include <klineeditdlg.h> +#include <kmessagebox.h> +#include <qdatetime.h> +#include <qwhatsthis.h> +#include <kaudioplayer.h> +#include <kstandarddirs.h> +#include <qvbox.h> +#include <kiconloader.h> +#include <ktabctl.h> +#include <kpassdlg.h> +#include <qcstring.h> +#include <knotifyclient.h> +#include <kaction.h> + + +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <iostream> +#include "kbgboard.h" +#include "kbgstatus.h" + +#include "clip.h" +#include "version.h" + + +void KBgEngineFIBS::start() +{ + // FIXME: open the child windows here and not in the constructor +} + +// == configuration handling =================================================== + +/* + * Restore settings and ask children to do the same + */ +void KBgEngineFIBS::readConfig() +{ + KConfig *config = kapp->config(); + config->setGroup("fibs engine"); + + // history variables + lastAway = config->readEntry("away_hist", ""); + + // various options + showMsg = config->readBoolEntry("pers_msg", false); + whoisInvite = config->readBoolEntry("whois_invite", false); + + // connection information + infoFIBS[FIBSHost] = config->readEntry("server", "fibs.com"); + infoFIBS[FIBSPort] = config->readEntry("port", "4321"); + infoFIBS[FIBSUser] = config->readEntry("user", ""); + infoFIBS[FIBSPswd] = config->readEntry("password", ""); + + // automatic messages + useAutoMsg[MsgBeg] = config->readBoolEntry("auto-beg", false); + useAutoMsg[MsgLos] = config->readBoolEntry("auto-los", false); + useAutoMsg[MsgWin] = config->readBoolEntry("auto-win", false); + + autoMsg[MsgBeg] = config->readEntry("msg-beg", ""); + autoMsg[MsgLos] = config->readEntry("msg-los", ""); + autoMsg[MsgWin] = config->readEntry("msg-win", ""); + + // ask the children to read their config options + playerlist->readConfig(); + chatWindow->readConfig(); +} + +/* + * Save the engine specific settings and tell all clients + */ +void KBgEngineFIBS::saveConfig() +{ + KConfig *config = kapp->config(); + config->setGroup("fibs engine"); + + // history variables + config->writeEntry("away_hist", lastAway); + + // various options + config->writeEntry("pers_msg", showMsg); + config->writeEntry("whois_invite", whoisInvite); + + // connection information + config->writeEntry("server", infoFIBS[FIBSHost]); + config->writeEntry("port", infoFIBS[FIBSPort]); + config->writeEntry("user", infoFIBS[FIBSUser]); + config->writeEntry("password", infoFIBS[FIBSPswd]); + + // automatic messages + config->writeEntry("auto-beg", useAutoMsg[MsgBeg]); + config->writeEntry("auto-los", useAutoMsg[MsgLos]); + config->writeEntry("auto-win", useAutoMsg[MsgWin]); + + config->writeEntry("msg-beg", autoMsg[MsgBeg]); + config->writeEntry("msg-los", autoMsg[MsgLos]); + config->writeEntry("msg-win", autoMsg[MsgWin]); + + // ask the children to read their config options + playerlist->saveConfig(); + chatWindow->saveConfig(); +} + +void KBgEngineFIBS::setupDefault() +{ + + cbp->setChecked(false); + cbi->setChecked(false); + + lec[FIBSHost]->setText("fibs.com"); + lec[FIBSPort]->setText("4321"); + + lec[FIBSUser]->clear(); + lec[FIBSPswd]->clear(); + + + chatWindow->setupDefault(); + playerlist->setupDefault(); +} + +void KBgEngineFIBS::setupCancel() +{ + chatWindow->setupCancel(); + playerlist->setupCancel(); +} + +/* + * Called when the setup dialog is positively closed + */ +void KBgEngineFIBS::setupOk() +{ + // various options + showMsg = cbp->isChecked(); + whoisInvite = cbi->isChecked(); + + // connection information + for (int i = 0; i < NumFIBS; i++) + infoFIBS[i] = lec[i]->text(); + + // automatic messages + for (int i = 0; i < NumMsg; i++) { + useAutoMsg[i] = cbm[i]->isChecked(); + autoMsg[i] = lem[i]->text(); + } + + chatWindow->setupOk(); + playerlist->setupOk(); + + // save settings + saveConfig(); +} + +/* + * Puts the FIBS specific setup into the dialog nb + */ +void KBgEngineFIBS::getSetupPages(KDialogBase *nb) +{ + /* + * Main Widget + */ + QVBox *vbp = nb->addVBoxPage(i18n("FIBS Engine"), i18n("Here you can configure the FIBS backgammon engine"), + kapp->iconLoader()->loadIcon(PROG_NAME "_engine", KIcon::Desktop)); + + /* + * Get a multi page work space + */ + KTabCtl *tc = new KTabCtl(vbp, "fibs tabs"); + + /* + * FIBS, local options + */ + QWidget *w = new QWidget(tc); + QGridLayout *gl = new QGridLayout(w, 3, 1, nb->spacingHint()); + + /* + * Group boxes + */ + QGroupBox *gbo = new QGroupBox(i18n("Options"), w); + QGroupBox *gbm = new QGroupBox(i18n("Automatic Messages"), w); + + gl->addWidget(gbo, 0, 0); + gl->addWidget(gbm, 1, 0); + + /* + * Options + */ + cbp = new QCheckBox(i18n("Show copy of personal messages in main window"), gbo); + cbi = new QCheckBox(i18n("Automatically request player info on invitation"), gbo); + + QWhatsThis::add(cbp, i18n("Usually, all messages sent directly to you by other players " + "are displayed only in the chat window. Check this box if you " + "would like to get a copy of these messages in the main window.")); + QWhatsThis::add(cbi, i18n("Check this box if you would like to receive information on " + "players that invite you to games.")); + + cbp->setChecked(showMsg); + cbi->setChecked(whoisInvite); + + gl = new QGridLayout(gbo, 2, 1, 20); + gl->addWidget(cbp, 0, 0); + gl->addWidget(cbi, 1, 0); + + /* + * Automatic messages + */ + gl = new QGridLayout(gbm, NumMsg, 2, 20); + + cbm[MsgBeg] = new QCheckBox(i18n("Start match:"), gbm); + cbm[MsgWin] = new QCheckBox(i18n("Win match:"), gbm); + cbm[MsgLos] = new QCheckBox(i18n("Lose match:"), gbm); + + QWhatsThis::add(cbm[MsgBeg], i18n("If you want to send a standard greeting to your " + "opponent whenever you start a new match, check " + "this box and write the message into the entry " + "field.")); + QWhatsThis::add(cbm[MsgWin], i18n("If you want to send a standard message to your " + "opponent whenever you won a match, check this box " + "and write the message into the entry field.")); + QWhatsThis::add(cbm[MsgLos], i18n("If you want to send a standard message to your " + "opponent whenever you lost a match, check this box " + "and write the message into the entry field.")); + + for (int i = 0; i < NumMsg; i++) { + lem[i] = new QLineEdit(autoMsg[i], gbm); + gl->addWidget(cbm[i], i, 0); + gl->addWidget(lem[i], i, 1); + connect(cbm[i], SIGNAL(toggled(bool)), lem[i], SLOT(setEnabled(bool))); + cbm[i]->setChecked(useAutoMsg[i]); + lem[i]->setEnabled(useAutoMsg[i]); + QWhatsThis::add(lem[i], QWhatsThis::textFor(cbm[i])); + } + + /* + * Put the page into the notebook + */ + gl->activate(); + tc->addTab(w, i18n("&Local")); + + + /* + * FIBS, connection setup + */ + w = new QWidget(tc); + gl = new QGridLayout(w, 3, 1, nb->spacingHint()); + + QGroupBox *gbc = new QGroupBox(i18n("Server"), w); + QGroupBox *gbk = new QGroupBox(i18n("Other"), w); + + gl->addWidget(gbc, 0, 0); + gl->addWidget(gbk, 1, 0); + + /* + * Server box + */ + gl = new QGridLayout(gbc, 4, 2, 20); + + QLabel *lbc[NumFIBS]; + + lbc[FIBSHost] = new QLabel(i18n("Server name:"), gbc); + lbc[FIBSPort] = new QLabel(i18n("Server port:"), gbc); + lbc[FIBSUser] = new QLabel(i18n("User name:"), gbc); + lbc[FIBSPswd] = new QLabel(i18n("Password:"), gbc); + + for (int i = 0; i < NumFIBS; i++) { + lec[i] = new QLineEdit(infoFIBS[i], gbc); + gl->addWidget(lbc[i], i, 0); + gl->addWidget(lec[i], i, 1); + } + lec[FIBSPswd]->setEchoMode(QLineEdit::Password); + + QWhatsThis::add(lec[FIBSHost], i18n("Enter here the host name of FIBS. With almost " + "absolute certainty this should be \"fibs.com\". " + "If you leave this blank, you will be asked again " + "at connection time.")); + QWhatsThis::add(lec[FIBSPort], i18n("Enter here the port number of FIBS. With almost " + "absolute certainty this should be \"4321\". " + "If you leave this blank, you will be asked again " + "at connection time.")); + QWhatsThis::add(lec[FIBSUser], i18n("Enter your login on FIBS here. If you do not have a " + "login yet, you should first create an account using " + "the corresponding menu entry. If you leave this blank, " + "you will be asked again at connection time.")); + QWhatsThis::add(lec[FIBSPswd], i18n("Enter your password on FIBS here. If you do not have a " + "login yet, you should first create an account using " + "the corresponding menu entry. If you leave this blank, " + "you will be asked again at connection time. The password " + "will not be visible.")); + + /* + * Connection keepalive + */ + cbk = new QCheckBox(i18n("Keep connections alive"), gbk); + + QWhatsThis::add(cbk, i18n("Usually, FIBS drops the connection after one hour of inactivity. When " + "you check this box, %1 will try to keep the connection alive, even " + "if you are not actually playing or chatting. Use this with caution " + "if you do not have flat-rate Internet access.").arg(PROG_NAME)); + + cbk->setChecked(keepalive); + + gl = new QGridLayout(gbk, 1, 1, nb->spacingHint()); + gl->addWidget(cbk, 0, 0); + + /* + * Done with the page, put it in + */ + gl->activate(); + tc->addTab(w, i18n("&Connection")); + + /* + * Ask children for settings + */ + chatWindow->getSetupPages(tc, nb->spacingHint()); + playerlist->getSetupPages(tc, nb->spacingHint()); + + /* + * TODO: future extensions + */ + w = new QWidget(tc); + tc->addTab(w, i18n("&Buddy List")); +} + + +// == functions related to the invitation menu ================================= + +/* + * Remove a player from the invitation list in the join menu + */ +void KBgEngineFIBS::cancelJoin(const QString &info) +{ + QRegExp patt = QRegExp("^" + info + " "); + + for (int i = 0; i <= numJoin; i++) { + if (actJoin[i]->text().contains(patt)) { + // move all entries starting at i+1 up by one... + for (int j = i; j < numJoin; j++) + actJoin[j]->setText(actJoin[j+1]->text()); + actJoin[numJoin--]->unplug(joinMenu); + break; + } + } +} + +/* + * Parse the information in info for the purposes of the invitation + * submenu + */ +void KBgEngineFIBS::changeJoin(const QString &info) +{ + char name_p[100], name_o[100]; + float rate; + int expi; + + /* + * Extract the name of the player, her opponent, rating and experience. + * It is okay to use latin1(), since the string is coming from FIBS. + */ + sscanf(info.latin1(), "%99s %99s %*s %*s %*s %f %i %*s %*s %*s %*s %*s", + name_p, name_o, &rate, &expi); + + QString name = name_p; + QString oppo = name_o; + + QString rate_s; rate_s.setNum(rate); + QString expi_s; expi_s.setNum(expi); + + QRegExp patt = QRegExp("^" + name + " "); + + /* + * We have essentially two lists of names to check against: the ones + * that have invited us and are not yet in the menu and the ones that + * are already in the menu. + */ + + if (numJoin > -1 && oppo != "-") + cancelJoin(name); + + for (QStringList::Iterator it = invitations.begin(); it != invitations.end(); ++it) { + + if ((*it).contains(patt)) { + + QString text, menu; + + if ((*it).contains(QRegExp(" r$"))) { + menu = i18n("R means resume", "%1 (R)").arg(name); + text = i18n("%1 (experience %2, rating %3) wants to resume a saved match with you. " + "If you want to play, use the corresponding menu entry to join (or type " + "'join %4').").arg(name).arg(expi_s).arg(rate_s).arg(name); + KNotifyClient::event("invitation", i18n("%1 wants to resume a saved match with you"). + arg(name)); + } else if ((*it).contains(QRegExp(" u$"))) { + menu = i18n("U means unlimited", "%1 (U)").arg(name); + text = i18n("%1 (experience %2, rating %3) wants to play an unlimited match with you. " + "If you want to play, use the corresponding menu entry to join (or type " + "'join %4').").arg(name).arg(expi_s).arg(rate_s).arg(name); + KNotifyClient::event("invitation", i18n("%1 has invited you to an unlimited match"). + arg(name)); + } else { + QString len = (*it).right((*it).length() - name.length() - 1); + menu = i18n("If the format of the (U) and (R) strings is changed, it should also be changed here", + "%1 (%2)").arg(name).arg(len); + text = i18n("%1 (experience %2, rating %3) wants to play a %4 point match with you. " + "If you want to play, use the corresponding menu entry to join (or type " + "'join %5').").arg(name).arg(expi_s).arg(rate_s).arg(len).arg(name); + KNotifyClient::event("invitation", i18n("%1 has invited you for a %2 point match"). + arg(name).arg(len)); + } + emit serverString("rawwho " + name); // this avoids a race + if (whoisInvite) { + emit serverString("whois " + name); + emit infoText("<font color=\"red\">" + text + "</font>"); + } else + emit infoText("<font color=\"red\">" + text + "</font><br>"); + + for (int i = 0; i <=numJoin; i++) + actJoin[i]->unplug(joinMenu); + + if (++numJoin > 7) numJoin = 7; + + for (int i = numJoin; i > 0; i--) + actJoin[i]->setText(actJoin[i-1]->text()); + + actJoin[0]->setText(menu); + + for (int i = 0; i <= numJoin; i++) + actJoin[i]->plug(joinMenu); + + invitations.remove(it); + break; + } + } + + /* + * If there are entries in the menu, enable it + */ + menu->setItemEnabled(joinMenuID, numJoin > -1); +} + + +// == various slots and functions ============================================== + +/* + * Keep the connection alive. + */ +void KBgEngineFIBS::keepAlive() +{ + emit serverString("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); +} + +/* + * Several bookkeeping operations that have to be done at the + * end of every game. Some of these may or may not be necessary + * at a particular time, but they don't hurt either. + */ +void KBgEngineFIBS::endGame() +{ + playing = false; + + emit serverString("rawwho " + infoFIBS[FIBSUser]); + + actConti->setEnabled(false); + actLeave->setEnabled(false); + + actAccept->setEnabled(false); + actReject->setEnabled(false); + + emit allowCommand(Load, false); + emit allowCommand(Undo, false); + emit allowCommand(Done, false); + emit allowCommand(Cube, false); + emit allowCommand(Roll, false); +} + +/* + * Toggle visibility of the player list + */ +void KBgEngineFIBS::showList() +{ + playerlist->isVisible() ? playerlist->hide() : playerlist->show(); +} + +/* + * Toggle visibility of the chat window + */ +void KBgEngineFIBS::showChat() +{ + chatWindow->isVisible() ? chatWindow->hide() : chatWindow->show(); +} + +/* + * Process the last move coming from the board + */ +void KBgEngineFIBS::handleMove(QString *s) +{ + lastMove = *s; + QString t = lastMove.left(1); + int moves = t.toInt(); + + emit allowCommand(Done, moves == toMove); + emit allowCommand(Undo, moves > 0); + + /* + * Allow undo and possibly start the commit timer + */ + redoPossible &= ((moves < toMove) && (undoCounter > 0)); + emit allowCommand(Redo, redoPossible); + if (moves == toMove && cl >= 0) { + emit allowMoving(false); + ct->start(cl, true); + } +} + +/* + * Done with the move + */ +void KBgEngineFIBS::done() +{ + // prevent the timer from expiring again + ct->stop(); + + // no more moves + emit allowMoving(false); + + // no more commands until it's our turn + emit allowCommand(Load, false); + emit allowCommand(Undo, false); + emit allowCommand(Done, false); + emit allowCommand(Cube, false); + emit allowCommand(Roll, false); + + // Transform the string to FIBS cormat + lastMove.replace(0, 2, "move "); + lastMove.replace(pat[PlsChar], "-"); + + // sent it to the server + emit serverString(lastMove); +} + +/* + * Undo the last move + */ +void KBgEngineFIBS::undo() +{ + ct->stop(); + + redoPossible = true; + ++undoCounter; + + emit allowMoving(true); + emit allowCommand(Done, false); + emit allowCommand(Redo, true); + emit undoMove(); +} + +/* + * Redo the last undone move + */ +void KBgEngineFIBS::redo() +{ + --undoCounter; + emit redoMove(); +} + +/* + * Double the cube - coming from the board + */ +void KBgEngineFIBS::doubleCube(const int w) +{ + if (playing && w == US) cube(); +} + +/* + * Roll the dice - coming from the board + */ +void KBgEngineFIBS::rollDice(const int w) +{ + if (playing && w == US) roll(); +} + +/* + * This engine passes all commands unmodified to the server + */ +void KBgEngineFIBS::handleCommand(QString const &cmd) +{ + emit serverString(cmd); +} + +/* + * If we have a connection, we don't quit right away + */ +bool KBgEngineFIBS::queryClose() +{ + if (connection->state() == QSocket::Idle) + return true; + + switch (KMessageBox::warningYesNoCancel((QWidget *)parent(),i18n("Still connected. Log out first?"),QString::null,i18n("Log Out"), i18n("Stay Connected"))) { + case KMessageBox::Yes : + disconnectFIBS(); + return true; + case KMessageBox::No : + return true; + default: // cancel + return false; + } +} + +/* + * If we have a connection, we don't quit right away + */ +bool KBgEngineFIBS::queryExit() +{ + if( kapp->sessionSaving()) + return true; + if (connection->state() != QSocket::Idle) + disconnectFIBS(); + return true; +} + +/* + * This displays a copy of personal messages in the main window. + * Normally, these only get displayed in the chat window. + */ +void KBgEngineFIBS::personalMessage(const QString &msg) +{ + if (showMsg) + emit infoText(msg); +} + + +// == slots and functions for FIBS commands ==================================== + +/* + * Accept the offer + */ +void KBgEngineFIBS::accept() +{ + actAccept->setEnabled(false); + actReject->setEnabled(false); + + emit serverString("accept"); +} + +/* + * Reject the offer + */ +void KBgEngineFIBS::reject() +{ + actAccept->setEnabled(false); + actReject->setEnabled(false); + + emit serverString("reject"); +} + +/* + * Continue a multi game match + */ +void KBgEngineFIBS::match_conti() +{ + actConti->setEnabled(false); + actLeave->setEnabled(false); + + emit serverString("join"); +} + +/* + * Leave a multi game match + */ +void KBgEngineFIBS::match_leave() +{ + actConti->setEnabled(false); + actLeave->setEnabled(false); + + emit serverString("leave"); +} + +/* + * Go away from the server for a little while. Offer the last know away + * message as a default to the user. + */ +void KBgEngineFIBS::away() +{ + bool ret; + QString msg = KLineEditDlg::getText(i18n("Please type the message that should be displayed to other\n" + "users while you are away."), + lastAway, &ret, (QWidget *)parent()); + if (ret) { + lastAway = msg; + emit serverString("away " + msg); + actAway->setEnabled(false); + } +} + +/* + * Toggle being ready for games + */ +void KBgEngineFIBS::toggle_ready() +{ + emit serverString("toggle ready"); +} + +/* + * Toggle the use of greedy bearoffs + */ +void KBgEngineFIBS::toggle_greedy() +{ + emit serverString("toggle greedy"); +} + +/* + * Toggle whether we will be asked to double/roll or not + */ +void KBgEngineFIBS::toggle_double() +{ + emit serverString("toggle double"); +} + +/* + * Toggle whether we want to see details on rating computations + */ +void KBgEngineFIBS::toggle_ratings() +{ + emit serverString("toggle ratings"); +} + +/* + * Come back after being away. + */ +void KBgEngineFIBS::back() +{ + emit serverString("back"); +} + +/* + * Double the cube + */ +void KBgEngineFIBS::cube() +{ + emit serverString("double"); +} + +/* + * Roll the dice + */ +void KBgEngineFIBS::roll() +{ + emit serverString("roll"); +} + +/* + * Reload the board + */ +void KBgEngineFIBS::load() +{ + emit serverString("board"); +} + +/* + * Handle the menu short cuts for joining. This is not as pretty as it + * could or should be, but it works and is easy to understand. + */ +void KBgEngineFIBS::join(const QString &msg) +{ + emit serverString("join " + msg.left(msg.find('('))); +} +void KBgEngineFIBS::join_0() { join(actJoin[0]->text()); } +void KBgEngineFIBS::join_1() { join(actJoin[1]->text()); } +void KBgEngineFIBS::join_2() { join(actJoin[2]->text()); } +void KBgEngineFIBS::join_3() { join(actJoin[3]->text()); } +void KBgEngineFIBS::join_4() { join(actJoin[4]->text()); } +void KBgEngineFIBS::join_5() { join(actJoin[5]->text()); } +void KBgEngineFIBS::join_6() { join(actJoin[6]->text()); } +void KBgEngineFIBS::join_7() { join(actJoin[7]->text()); } + + +// == invitation handling ====================================================== + +/* + * Show the invitation dialog and set the name to player + */ +void KBgEngineFIBS::inviteDialog() +{ + fibsRequestInvitation(""); +} + +/* + * Show the invitation dialog and set the name to player + */ +void KBgEngineFIBS::fibsRequestInvitation(const QString &player) +{ + if (!invitationDlg) { + QString p = player; + invitationDlg = new KBgInvite("invite"); + connect(invitationDlg, SIGNAL(inviteCommand(const QString &)), this, SLOT(handleCommand(const QString &))); + connect(invitationDlg, SIGNAL(dialogDone()), this, SLOT(invitationDone())); + } + invitationDlg->setPlayer(player); + invitationDlg->show(); +} + +/* + * Finish off the invitation dialog + */ +void KBgEngineFIBS::invitationDone() +{ + delete invitationDlg; + invitationDlg = 0; +} + + +// == connection handling ====================================================== + +/* + * Establish a connection to the server and log in if the parameter login + * is true. + */ +void KBgEngineFIBS::connectFIBS() +{ + /* + * Make sure the connection parameter are properly set. + */ + if (!queryConnection(false)) + return; + + conAction->setEnabled(false); + newAction->setEnabled(false); + disAction->setEnabled(false); + + /* + * Connect + */ + emit infoText(i18n("Looking up %1").arg(infoFIBS[FIBSHost])); + connection->connectToHost(infoFIBS[FIBSHost], infoFIBS[FIBSPort].toUShort()); + + return; +} + +/* + * Hostname has been resolved. + */ +void KBgEngineFIBS::hostFound() +{ + emit infoText(i18n("Connecting to %1").arg(infoFIBS[FIBSHost])); +} + +/* + * An error has occurred. Reset and inform the user. + */ +void KBgEngineFIBS::connError(int f) +{ + switch (f) { + case QSocket::ErrConnectionRefused: + emit infoText(i18n("Error, connection has been refused")); + break; + case QSocket::ErrHostNotFound: + emit infoText(i18n("Error, nonexistent host or name server down.")); + break; + case QSocket::ErrSocketRead: + emit infoText(i18n("Error, reading data from socket")); + break; + } + connectionClosed(); + return; +} + +void KBgEngineFIBS::readData() +{ + QString line; + while(connection->canReadLine()) { + line = connection->readLine(); + if (line.length() > 2) { + line.truncate(line.length()-2); + handleServerData(line); + } + } +} + +/* + * Transmit the string s to the server + */ +void KBgEngineFIBS::sendData(const QString &s) +{ + connection->writeBlock((s+"\r\n").latin1(),2+s.length()); +} + +/* + * Connection has been established. Log in and update the menus & actions. + */ +void KBgEngineFIBS::connected() +{ + conAction->setEnabled(false); + newAction->setEnabled(false); + disAction->setEnabled(true); + + menu->setItemEnabled( cmdMenuID, true); + menu->setItemEnabled(respMenuID, true); + menu->setItemEnabled(optsMenuID, true); + + /* + * Initialize the rx state machine + */ + rxStatus = RxConnect; + rxCollect = ""; + + /* + * Depending on whether somebody else wants to handle the login or not + */ + if (login) { + + /* + * Make sure the player list is empty when the whole list comes + * right after login + */ + playerlist->clear(); + + /* + * Login, using the autologin feature of FIBS, before we even receive anything. + */ + QString entry; + entry.setNum(CLIP_VERSION); + emit serverString(QString("login ") + PROG_NAME + "-" + PROG_VERSION + " " + entry + " " + + infoFIBS[FIBSUser] + " " + infoFIBS[FIBSPswd]); + + } else { + + emit serverString("guest"); + login = true; + + } + + /* + * Some visual feedback and done + */ + emit infoText(i18n("Connected") + "<br>"); +} + +/* + * Create a new account on FIBS. Obviously, this will also create + * a connection. The actual login is handled in the message parsing + * state machine. + */ +void KBgEngineFIBS::newAccount() +{ + if (!queryConnection(true)) + return; + + rxStatus = RxNewLogin; + rxCollect = ""; + login = false; + connectFIBS(); +} + +/* + * Send a disconnection request to the server. The server will disconnect + * and we will receive a connectionClosed() signal. + */ +void KBgEngineFIBS::disconnectFIBS() +{ + // send two lines in case we are stuck in the login phase + emit serverString("quit"); + emit serverString("quit"); +} + +/* + * Connection to the server is closed for some (unknown) reason. Delete + * the connection object and get the actions into a proper state. + */ +void KBgEngineFIBS::connectionClosed() +{ + /* + * Read remaining input + */ + readData(); + + /* + * Flush whatever is left in the rxBuffer and send a note + */ + emit infoText(rxCollect + "<br><hr>"); + emit infoText(i18n("Disconnected.") + "<br>"); + + conAction->setEnabled(true); + newAction->setEnabled(true); + disAction->setEnabled(false); + + menu->setItemEnabled(joinMenuID, false); + menu->setItemEnabled( cmdMenuID, false); + menu->setItemEnabled(respMenuID, false); + menu->setItemEnabled(optsMenuID, false); +} + +/* + * To establish a connection, we need to query the server name, the port + * number, the login and the password. + */ +bool KBgEngineFIBS::queryConnection(const bool newlogin) +{ + QString text, msg; + bool first, ret = true; + + /* + * query the connection parameter + */ + if (newlogin || infoFIBS[FIBSHost].isEmpty()) { + + msg = KLineEditDlg::getText(i18n("Enter the name of the server you want to connect to.\n" + "This should almost always be \"fibs.com\"."), + infoFIBS[FIBSHost], &ret, (QWidget *)parent()); + + if (ret) + infoFIBS[FIBSHost] = msg; + else + return false; + + } + if (newlogin || infoFIBS[FIBSPort].isEmpty()) { + + msg = KLineEditDlg::getText(i18n("Enter the port number on the server. " + "It should almost always be \"4321\"."), + infoFIBS[FIBSPort], &ret, (QWidget *)parent()); + + if (ret) + infoFIBS[FIBSPort] = msg; + else + return false; + } + if (newlogin || infoFIBS[FIBSUser].isEmpty()) { + + if (newlogin) + + text = i18n("Enter the login you would like to use on the server %1. The login may not\n" + "contain spaces or colons. If the login you choose is not available, you'll later be\n" + "given the opportunity to pick another one.\n\n").arg(infoFIBS[FIBSHost]); + + else + + text = i18n("Enter your login on the server %1. If you don't have a login, you\n" + "should create one using the corresponding menu option.\n\n").arg(infoFIBS[FIBSHost]); + + + first = true; + do { + msg = (KLineEditDlg::getText(text, infoFIBS[FIBSUser], &ret, + (QWidget *)parent())).stripWhiteSpace(); + if (first) { + text += i18n("The login may not contain spaces or colons!"); + first = false; + } + + } while (ret && (msg.isEmpty() || msg.contains(' ') || msg.contains(':'))); + + if (ret) + infoFIBS[FIBSUser] = msg; + else + return false; + } + if (newlogin || infoFIBS[FIBSPswd].isEmpty()) { + + if (newlogin) + + text = i18n("Enter the password you would like to use with the login %1\n" + "on the server %2. It may not contain colons.\n\n"). + arg(infoFIBS[FIBSUser]).arg(infoFIBS[FIBSHost]); + + else + + text = i18n("Enter the password for the login %1 on the server %2.\n\n"). + arg(infoFIBS[FIBSUser]).arg(infoFIBS[FIBSHost]); + + first = true; + do { + QCString password; + if (newlogin) + ret = (KPasswordDialog::getNewPassword(password, text) == KPasswordDialog::Accepted); + else + ret = (KPasswordDialog::getPassword(password, text) == KPasswordDialog::Accepted); + + password.stripWhiteSpace(); + msg = password; + + if (first) { + text += i18n("The password may not contain colons or spaces!"); + first = false; + } + + } while (ret && (msg.isEmpty() || msg.contains(' ') || msg.contains(':'))); + + if (ret) + infoFIBS[FIBSPswd] = msg; + else + return false; + } + + /* + * Made it here, all parameters acquired + */ + return true; +} + + +// == message parsing ========================================================== + +/* + * Pattern setup - rather long and boring + */ +void KBgEngineFIBS::initPattern() +{ + QString pattern; + + /* + * Initialize the search pattern array + */ + pat[Welcome] = QRegExp(pattern.sprintf("^%d ", CLIP_WELCOME)); + pat[OwnInfo] = QRegExp(pattern.sprintf("^%d ", CLIP_OWN_INFO)); + pat[WhoInfo] = QRegExp(pattern.sprintf("^%d ", CLIP_WHO_INFO)); + pat[WhoEnde] = QRegExp(pattern.sprintf("^%d$", CLIP_WHO_END)); + pat[MotdBeg] = QRegExp(pattern.sprintf("^%d" , CLIP_MOTD_BEGIN)); + pat[MotdEnd] = QRegExp(pattern.sprintf("^%d" , CLIP_MOTD_END)); + pat[MsgPers] = QRegExp(pattern.sprintf("^%d ", CLIP_MESSAGE)); + pat[MsgDeli] = QRegExp(pattern.sprintf("^%d ", CLIP_MESSAGE_DELIVERED)); + pat[MsgSave] = QRegExp(pattern.sprintf("^%d ", CLIP_MESSAGE_SAVED)); + pat[ChatSay] = QRegExp(pattern.sprintf("^%d ", CLIP_SAYS)); + pat[ChatSht] = QRegExp(pattern.sprintf("^%d ", CLIP_SHOUTS)); + pat[ChatWis] = QRegExp(pattern.sprintf("^%d ", CLIP_WHISPERS)); + pat[ChatKib] = QRegExp(pattern.sprintf("^%d ", CLIP_KIBITZES)); + pat[SelfSay] = QRegExp(pattern.sprintf("^%d ", CLIP_YOU_SAY)); + pat[SelfSht] = QRegExp(pattern.sprintf("^%d ", CLIP_YOU_SHOUT)); + pat[SelfWis] = QRegExp(pattern.sprintf("^%d ", CLIP_YOU_WHISPER)); + pat[SelfKib] = QRegExp(pattern.sprintf("^%d ", CLIP_YOU_KIBITZ)); + pat[UserLin] = QRegExp(pattern.sprintf("^%d ", CLIP_LOGIN)); + pat[UserLot] = QRegExp(pattern.sprintf("^%d ", CLIP_LOGOUT)); + + pat[NoLogin] = QRegExp("\\*\\* Unknown command: 'login'"); + pat[BegRate] = QRegExp("^rating calculation:$"); + pat[EndRate] = QRegExp("^change for "); + pat[HTML_lt] = QRegExp("<"); + pat[HTML_gt] = QRegExp(">"); + pat[BoardSY] = QRegExp("^Value of 'boardstyle' set to 3"); + pat[BoardSN] = QRegExp("^Value of 'boardstyle' set to [^3]"); + pat[WhoisBG] = QRegExp("^Information about "); + pat[WhoisE1] = QRegExp("^ No email address\\.$"); + pat[WhoisE2] = QRegExp("^ Email address: "); + pat[SelfSlf] = QRegExp("^You say to yourself:"); + pat[Goodbye] = QRegExp("^ Goodbye\\."); + pat[GameSav] = QRegExp("The game was saved\\.$"); + pat[RawBord] = QRegExp("^board:"); + pat[YouTurn] = QRegExp("^It's your turn\\. Please roll or double"); + pat[PlsMove] = QRegExp("^Please move [1-6]+ pie"); + pat[EndWtch] = QRegExp("^You stop watching "); + pat[BegWtch] = QRegExp("^You're now watching "); + pat[BegGame] = QRegExp("^Starting a new game with "); + pat[Reload1] = QRegExp("^You are now playing with "); + pat[Reload2] = QRegExp(" has joined you. Your running match was loaded\\.$"); + pat[OneWave] = QRegExp(" waves goodbye.$"); + pat[TwoWave] = QRegExp(" waves goodbye again.$"); + pat[YouWave] = QRegExp("^You wave goodbye.$"); + pat[GameBG1] = QRegExp("start a [0-9]+ point match"); + pat[GameBG2] = QRegExp("start an unlimited match"); + pat[GameRE1] = QRegExp("are resuming their [0-9]+-point match"); + pat[GameRE2] = QRegExp("are resuming their unlimited match"); + pat[GameEnd] = QRegExp("point match against"); + pat[TabChar] = QRegExp("\\t"); + pat[PlsChar] = QRegExp("\\+"); + pat[Invite0] = QRegExp(" wants to play a [0-9]+ point match with you\\.$"); + pat[Invite1] = QRegExp("^.+ wants to play a "); + pat[Invite2] = QRegExp(" wants to resume a saved match with you\\.$"); + pat[Invite3] = QRegExp(" wants to play an unlimited match with you\\.$"); + pat[TypJoin] = QRegExp("^Type 'join "); + pat[OneName] = QRegExp("^ONE USERNAME PER PERSON ONLY!!!"); + pat[YouAway] = QRegExp("^You're away. Please type 'back'"); + pat[YouBack] = QRegExp("^Welcome back\\.$"); + pat[YouMove] = QRegExp("^It's your turn to move\\."); + pat[YouRoll] = QRegExp("^It's your turn to roll or double\\."); + pat[TwoStar] = QRegExp("^\\*\\* "); + pat[OthrNam] = QRegExp("^\\*\\* Please use another name\\. "); + pat[BoxHori] = QRegExp("^ *\\+-*\\+ *$"); + pat[BoxVer1] = QRegExp("^ *\\|"); + pat[BoxVer2] = QRegExp("\\| *$"); + pat[YourNam] = QRegExp("Your name will be "); + pat[GivePwd] = QRegExp("Please give your password:"); + pat[RetypeP] = QRegExp("Please retype your password:"); + pat[HelpTxt] = QRegExp("^NAME$"); + pat[MatchB1] = QRegExp(" has joined you for a [0-9]+ point match\\.$"); + pat[MatchB2] = QRegExp(" has joined you for an unlimited match\\.$"); + pat[EndLose] = QRegExp(" wins the [0-9]+ point match [0-9]+-[0-9]+"); + pat[EndVict] = QRegExp(" win the [0-9]+ point match [0-9]+-[0-9]+"); + pat[RejAcpt] = QRegExp("Type 'accept' or 'reject'\\.$"); + pat[YouAcpt] = QRegExp("^You accept the double\\. The cube shows [0-9]+\\."); + + pat[KeepAlv] = QRegExp("^\\*\\* Unknown command: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"); + pat[RatingY] = QRegExp("You'll see how the rating changes are calculated\\.$"); + pat[RatingN] = QRegExp("You won't see how the rating changes are calculated\\.$"); + + // FIXME same problem as in previous line + // mpgnu accepts the double.5 arthur_tn - gnu 1 0 1243.32 365 6 983722411 adsl-61-168-141.bna.bellsouth.net - - + + // FIXME: <PLAYER> can't move. -- needs board reload... + + /* + + opponent matchlength score (your points first) + **gnu 1 0 - 0 + *blah 1 0 - 0 + kraut 1 0 - 0 + + logged in and ready ** + logged in * + otherwise " " + + */ + + pat[ConLeav] = QRegExp("^Type 'join' if you want to play the next game, type 'leave' if you don't\\.$"); + pat[GreedyY] = QRegExp("^\\*\\* Will use automatic greedy bearoffs\\."); + pat[GreedyN] = QRegExp("^\\*\\* Won't use automatic greedy bearoffs\\."); + pat[BegBlnd] = QRegExp("^\\*\\* You blind "); + pat[EndBlnd] = QRegExp("^\\*\\* You unblind "); + pat[MatchB3] = QRegExp("^\\*\\* You are now playing a [0-9]+ point match with "); + pat[MatchB4] = QRegExp("^\\*\\* You are now playing an unlimited match with "); + pat[RejCont] = QRegExp("^You reject\\. The game continues\\."); + pat[AcptWin] = QRegExp("^You accept and win "); + pat[YouGive] = QRegExp("^You give up\\."); + pat[DoubleY] = QRegExp("^\\*\\* You will be asked if you want to double\\."); + pat[DoubleN] = QRegExp("^\\*\\* You won't be asked if you want to double\\."); +} + +/* + * Parse an incoming line and notify all interested parties - first match + * decides. + */ +void KBgEngineFIBS::handleServerData(QString &line) +{ + QString rawline = line; // contains the line before it is HTML'fied + + /* + * Fix-up any HTML-like tags in the line + */ + line.replace(pat[HTML_lt], "<"); + line.replace(pat[HTML_gt], ">"); + + /* + * FIBS sometimes sends tabs, where it should send 8 spaces... + */ + line.replace(pat[TabChar], " "); + + switch (rxStatus) { + + case RxConnect: + handleMessageConnect(line, rawline); + break; + + case RxMotd: + handleMessageMotd(line); + return; + + case RxWhois: + handleMessageWhois(line); + break; + + case RxRating: + handleMessageRating(line); + break; + + case RxNewLogin: + handleMessageNewLogin(line); + break; + + case RxIgnore: + /* + * Ignore _ALL_ incoming strings - this is needed during the + * login phase, when the message box is open. + */ + break; + + case RxGoodbye: + /* + * Receive the logout sequence. The string will be flushed by the + * disconnectFIBS() callback + */ + rxCollect += QString("<font color=\"blue\"><pre>") + line + "</pre></font><br>"; + break; + + case RxNormal: + handleMessageNormal(line, rawline); + break; + + default: + /* + * This is a serious problem - latin1() is fine since the line comes from FIBS. + */ + std::cerr << "PROBLEM in KBgEngineFIBS::handleServerData: " << line.latin1() << std::endl; + } +} + +/* + * Handle messages during the RxWhois state + */ +void KBgEngineFIBS::handleMessageWhois(const QString &line) +{ + rxCollect += "<br> " + line; + if (line.contains(pat[WhoisE1]) || line.contains(pat[WhoisE2])) { + rxStatus = RxNormal; + emit infoText("<font color=\"darkgreen\">" + rxCollect + "<br></font>"); + } +} + +/* + * Handle messages during the RxRating state + */ +void KBgEngineFIBS::handleMessageRating(const QString &line) +{ + rxCollect += "<br>" + line; + if (line.contains(pat[EndRate]) && ++rxCount == 2) { + emit infoText("<font color=\"blue\">" + rxCollect + "<br></font>"); + rxStatus = RxNormal; + } +} + +/* + * Handle messages during the RxMotd state + */ +void KBgEngineFIBS::handleMessageMotd(const QString &line) +{ + if (line.contains(pat[MotdEnd])) { + rxStatus = RxNormal; + emit infoText("<font color=\"blue\"><pre>" + rxCollect + "</pre></font>"); + /* + * just to be on the safe side, we set the value of boardstyle. + * we do it here, since this is reasonably late in the login + * procedure + */ + emit serverString("set boardstyle 3"); + } else { + QString tline = line; + tline.replace(pat[BoxHori], "<br><hr>"); + tline.replace(pat[BoxVer1], ""); + tline.replace(pat[BoxVer2], ""); + rxCollect += "<br>" + tline; + } +} + +/* + * Handle messages during the RxConnect state + */ +void KBgEngineFIBS::handleMessageConnect(const QString &line, const QString &rawline) +{ + /* + * Two possibilities: either we are logged in or we sent bad password/login + */ + if (line.contains("login:")) { + /* + * This can only happen if the password/login is wrong. + */ + if (rxCollect.isEmpty()) { + rxStatus = RxIgnore; + int ret = KMessageBox::warningContinueCancel + ((QWidget *)parent(), i18n("There was a problem with " + "your login and password. " + "You can reenter\n" + "your login and password and " + "try to reconnect."), + i18n("Wrong Login/Password"), + i18n("Reconnect")); + if (ret == KMessageBox::Continue) { + infoFIBS[FIBSUser] = ""; + infoFIBS[FIBSPswd] = ""; + login = true; + connectFIBS(); // will reset the rxStatus + } else { + rxStatus = RxConnect; + emit serverString(""); + emit serverString(""); + } + return; + } + emit infoText("<hr><pre>" + rxCollect + "</pre><br>"); + rxCollect = ""; + return; + } + + /* + * Ok, we are logged in! Now receive personal information. These + * are completely useless but what the heck. + */ + if (line.contains(pat[Welcome])) { + char p[3][256]; + time_t tmp; + // Using latin1() is okay, since the string comes from FIBS. + int words = sscanf (line.latin1(), "%255s%255s%li%255s", p[0], p[1], &tmp, p[2]); + if (words >= 4) { + QDateTime d; d.setTime_t(tmp); + QString text = i18n("%1, last logged in from %2 at %3.").arg(p[1]).arg(p[2]).arg(d.toString()); + emit infoText("<hr><br>" + text); + playerlist->setName(p[1]); + } + return; + } + + /* + * Initial parsing of user options and making sure that settings needed + * by us are at the correct value. We use and ignore values according + * to the following list: + * + * p[ 0] - CLIP_OWN_INFO + * p[ 1] - name -- IGNORE + * OptAllowPip + * n[ 0] - autoboard -- IGNORE + * OptAutoDouble + * OptAutoMove + * n[ 1] - away -- IGNORE + * n[ 2] - bell -- IGNORE + * OptCrawford + * n[ 3] - double -- IGNORE + * n[ 4] - expierience -- IGNORE + * OptGreedy + * n[ 6] - moreboards -- IGNORE and set to YES + * OptMoves + * n[ 8] - notify -- IGNORE and set to YES + * rating - rating -- IGNORE + * OptRatings + * OptReady + * n[10] - redoubles -- IGNORE + * n[11] - report -- IGNORE and set to YES + * OptSilent + * p[3] - timezone + * + */ + if (line.contains(pat[OwnInfo])) { + + rxStatus = RxNormal; + + int fibsOptions[NumFIBSOpt]; + + char p[3][256]; + int n[12]; + double rating; + + // Using latin1() is okay, since the string comes from FIBS. + int words = sscanf (line.latin1(), "%255s%255s%i%i%i%i%i%i%i%i%i%i%i%i%i%lf%i%i%i%i%i%255s", + p[0], p[1], + &fibsOptions[OptAllowPip], + &n[0], + &fibsOptions[OptDouble], + &fibsOptions[OptAutoMove], // equivalent to OptDouble, can be ignored + &n[1], &n[2], + &fibsOptions[OptCrawford], + &n[3], &n[4], + &fibsOptions[OptGreedy], + &n[6], + &fibsOptions[OptMoves], + &n[8], + &rating, + &fibsOptions[OptRatings], + &fibsOptions[OptReady], + &n[10], &n[11], + &fibsOptions[OptSilent], + p[2]); + + if (words >= 22 && n[6] != 1) { + /* + * need to get boards after new dice have arrived + */ + emit infoText("<font color=\"red\">" + i18n("The moreboards toggle has been set.") + "</font>"); + emit serverString("toggle moreboards"); + } + if (words >= 22 && n[8] != 1) { + /* + * need to know who logs out + */ + emit infoText("<font color=\"red\">" + i18n("The notify toggle has been set.") + "</font>"); + emit serverString("toggle notify"); + } + if (words >= 22 && n[11] != 1) { + /* + * want to know who starts playing games + */ + emit infoText("<font color=\"red\">" + i18n("The report toggle has been set.") + "</font>"); + emit serverString("toggle report"); + } + + /* + * Set the correct toggles in the options menu + */ + fibsOpt[OptReady]->setChecked(fibsOptions[OptReady]); + fibsOpt[OptDouble]->setChecked(!fibsOptions[OptDouble]); + fibsOpt[OptRatings]->setChecked(fibsOptions[OptRatings]); + + return; + } + + /* + * The beginning of a new login procedure starts starts here + */ + if (line.contains(pat[OneName])) { + rxStatus = RxNewLogin; + emit infoText(QString("<font color=\"red\">") + rxCollect + "</font>"); + rxCollect = ""; + QString tmp = rawline; + handleServerData(tmp); + return; + } + + /* + * Still in the middle of the login sequence, still collecting information + */ + rxCollect += "<br>" + line; +} + +/* + * Handle messages during the RxNewLogin state + */ +void KBgEngineFIBS::handleMessageNewLogin(const QString &line) +{ + /* + * Request the new login + */ + if (line.contains(pat[OneName])) { + emit serverString(QString("name ") + infoFIBS[FIBSUser]); + return; + } + /* + * Ooops, user name already exists + */ + if (line.contains(pat[OthrNam])) { + QString text = i18n("The selected login is alreay in use! Please select another one."); + bool ret, first = true; + QString msg; + + do { + msg = (KLineEditDlg::getText(text, infoFIBS[FIBSUser], &ret, + (QWidget *)parent())).stripWhiteSpace(); + if (first) { + text += i18n("\n\nThe login may not contain spaces or colons!"); + first = false; + } + } while (msg.contains(' ') || msg.contains(':')); + + if (ret) { + infoFIBS[FIBSUser] = msg; + emit serverString("name " + msg); + } else + emit serverString("bye"); + + return; + } + /* + * first time we send the password + */ + if (line.contains(pat[YourNam])) { + emit serverString(infoFIBS[FIBSPswd]); + return; + } + /* + * second time we send the password + */ + if (line.contains(pat[GivePwd])) { + emit serverString(infoFIBS[FIBSPswd]); + return; + } + /* + * at this point we are done creating the account + */ + if (line.contains(pat[RetypeP])) { + + QString text = i18n("Your account has been created. Your new login is <u>%1</u>. To fully activate " + "this account, I will now close the connection. Once you reconnect, you can start " + "playing backgammon on FIBS.").arg(infoFIBS[FIBSUser]); + emit infoText("<br><hr><font color=\"blue\">" + text + "</font><br><hr>"); + emit serverString("bye"); + rxStatus = RxNormal; + rxCollect = ""; + return; + } + return; +} + +/* + * Handle all normal messages - during the RxNormal state + */ +void KBgEngineFIBS::handleMessageNormal(QString &line, QString &rawline) +{ + + // - ignored ---------------------------------------------------------------------- + + /* + * For now, the waves are ignored. They should probably go into + * the chat window -- but only optional + */ + if (line.contains(pat[OneWave]) || line.contains(pat[TwoWave]) || line.contains(pat[YouWave])) { + + return; + } + + /* + * These messages used to go into the games window. If KBackgammon + * ever gets a games window, they should be in there. For now, they + * are ignored. + */ + else if (line.contains(pat[GameBG1]) || line.contains(pat[GameBG2]) || line.contains(pat[GameRE1]) || + line.contains(pat[GameRE2]) || line.contains(pat[GameEnd])) { + + return; + } + + /* + * Artefact caused by the login test procedure utilized. + */ + else if (line.contains(pat[NoLogin])) { + + return; + } + + /* + * Connection keep-alive response + */ + else if (line.contains(pat[KeepAlv])) { + + return; + } + + // -------------------------------------------------------------------------------- + + /* + * Chat and personal messages - note that the chat window sends these messages + * back to us so we can display them if the user wants that. + */ + else if (line.contains(pat[ChatSay]) || line.contains(pat[ChatSht]) || line.contains(pat[ChatWis]) || + line.contains(pat[ChatKib]) || line.contains(pat[SelfSay]) || line.contains(pat[SelfSht]) || + line.contains(pat[SelfWis]) || line.contains(pat[SelfKib]) || line.contains(pat[SelfSlf]) || + line.contains(pat[MsgPers]) || line.contains(pat[MsgDeli]) || line.contains(pat[MsgSave])) { + + emit chatMessage(line); + return; + } + + // -------------------------------------------------------------------------------- + + /* + * Beginning of games. In all these cases we are playing and not watching. + */ + else if (line.contains(pat[MatchB1]) || line.contains(pat[MatchB2])) { + + if (useAutoMsg[MsgBeg] && !autoMsg[MsgBeg].stripWhiteSpace().isEmpty()) + emit serverString("kibitz " + autoMsg[MsgBeg]); + } + else if (line.contains(pat[MatchB3]) || line.contains(pat[MatchB4])) { + + if (useAutoMsg[MsgBeg] && !autoMsg[MsgBeg].stripWhiteSpace().isEmpty()) + emit serverString("kibitz " + autoMsg[MsgBeg]); + line = "<font color=\"red\">" + line + "</font>"; + } + + // -------------------------------------------------------------------------------- + + /* + * The help should be handled separately. A fairly complete implementation of a + * help parsing can be found in KFibs. + */ + else if (line.contains(pat[HelpTxt])) { + + // do nothing + } + + // -------------------------------------------------------------------------------- + + /* + * Simple cases without the need for many comments... + */ + else if (line.contains(pat[RawBord])) { + + /* + * Save the board string and create a new game state + */ + KBgStatus *st = new KBgStatus(currBoard = rawline); + + /* + * Save important state data and stop the timeout + */ + ct->stop(); + undoCounter = 0; + + pname[US ] = st->player(US); + pname[THEM] = st->player(THEM); + + playing = (QString("You") == pname[US]); + + toMove = st->moves(); + + /* + * Update the caption string + */ + if (st->turn() < 0) + caption = i18n("%1 (%2) vs. %3 (%4) - game over").arg(pname[US]). + arg(st->points(US)).arg(pname[THEM]).arg(st->points(THEM)); + else if (st->length() < 0) + caption = i18n("%1 (%2) vs. %3 (%4) - unlimited match").arg(pname[US]). + arg(st->points(US)).arg(pname[THEM]).arg(st->points(THEM)); + else + caption = i18n("%1 (%2) vs. %3 (%4) - %5 point match").arg(pname[US]). + arg(st->points(US)).arg(pname[THEM]).arg(st->points(THEM)). + arg(st->length()); + + emit statText(caption); + + /* + * Emit information and drop the state object + */ + emit allowMoving(playing && (st->turn() == US)); + emit newState(*st); + + delete st; + + /* + * Set the actions correctly + */ + emit allowCommand(Load, true ); + emit allowCommand(Undo, false); + emit allowCommand(Redo, false); + emit allowCommand(Done, false); + + return; + } + else if (line.contains(pat[PlsMove]) || line.contains(pat[YouMove])) { + + KNotifyClient::event("move", i18n("Please make your move")); + + } + + // -------------------------------------------------------------------------------- + + /* + * Being away and coming back + */ + else if (line.contains(pat[YouAway])) { + + emit changePlayerStatus(infoFIBS[FIBSUser], KFibsPlayerList::Away, true); + actBack->setEnabled(true); + line += "<br><pre> </pre>" + i18n("(or use the corresponding menu entry to join the match)"); + } + else if (line.contains(pat[YouBack])) { + + emit changePlayerStatus(infoFIBS[FIBSUser], KFibsPlayerList::Away, false); + actBack->setEnabled(false); + actAway->setEnabled(true); + } + + // -------------------------------------------------------------------------------- + + /* + * Catch the response of the user responding to double or resign + */ + else if (line.contains(pat[YouGive]) || line.contains(pat[RejCont]) || line.contains(pat[AcptWin])) { + + actAccept->setEnabled(false); + actReject->setEnabled(false); + } + + // -------------------------------------------------------------------------------- + + /* + * Catch the responses to newly set toggles + */ + else if (line.contains(pat[GreedyY]) || line.contains(pat[GreedyN])) { + + fibsOpt[OptGreedy]->setChecked(line.contains(pat[GreedyY])); + line = "<font color=\"red\">" + line + "</font>"; + } + else if (line.contains(pat[DoubleY]) || line.contains(pat[DoubleN])) { + + fibsOpt[OptDouble]->setChecked(line.contains(pat[DoubleY])); + line = "<font color=\"red\">" + line + "</font>"; + } + + else if (line.contains(pat[RatingY]) || line.contains(pat[RatingN])) { + + fibsOpt[OptRatings]->setChecked(line.contains(pat[RatingY])); + line = "<font color=\"red\">" + line + "</font>"; + } + + // -------------------------------------------------------------------------------- + + /* + * It's our turn to roll or double + */ + else if (line.contains(pat[YouTurn]) || line.contains(pat[YouRoll])) { + + emit allowCommand(Cube, playing); + emit allowCommand(Roll, playing); + + emit statText(caption); // force a pip count recomputation by the board + + KNotifyClient::event("roll or double", i18n("It's your turn to roll the dice or double the cube")); + } + + // -------------------------------------------------------------------------------- + + /* + * Got an invitation for a match + */ + else if (line.contains(pat[Invite0]) || line.contains(pat[Invite2]) || line.contains(pat[Invite3])) { + + rxCollect = rawline.left(rawline.find(' ')); + emit serverString("rawwho " + rxCollect); + + if (line.contains(pat[Invite0])) { + rawline.replace(pat[Invite1], ""); + rawline = rxCollect + " "+ rawline.left(rawline.find(' ')); + } else if (line.contains(pat[Invite2])) { + rawline = rxCollect + " r"; + } else if (line.contains(pat[Invite3])) { + invitations += rxCollect + " u"; + } + invitations += rawline; + return; // will be printed once the rawwho is received + } + + // - rx status changes ------------------------------------------------------------ + + else if (line.contains(pat[WhoisBG])) { + rxStatus = RxWhois; + rxCollect = QString("<br><u>") + line + "</u>"; + return; + } + else if (line.contains(pat[MotdBeg])) { + rxStatus = RxMotd; + rxCollect = ""; + return; + } + else if (line.contains(pat[BegRate])) { + rxStatus = RxRating; + rxCount = 0; + rxCollect = "<br>" + line; + return; + } + else if (line.contains(pat[Goodbye])) { + rxStatus = RxGoodbye; + rxCollect = "<br><hr><br>"; + handleServerData(rawline); // danger: recursion! + return; + } + + // -------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------- + + /* + * Continue a mutli game match? We have to either leave or continue + */ + else if (line.contains(pat[ConLeav])) { + actConti->setEnabled(true); + actLeave->setEnabled(true); + line.append("<br><pre> </pre>" + i18n("(or use the corresponding menu " + "entry to leave or continue the match)")); + } + /* + * Beginning and end of user updates + */ + else if (line.contains(pat[WhoInfo])) { + rawline.replace(pat[WhoInfo], ""); + if (rawline.contains(QRegExp("^" + infoFIBS[FIBSUser] + " "))) { + int ready; + // Using latin1() is fine, since the string is coming from FIBS. + sscanf(rawline.latin1(), "%*s %*s %*s %i %*s %*s %*s %*s %*s %*s %*s %*s", &ready); + fibsOpt[OptReady]->setChecked(ready); + } + emit fibsWhoInfo(rawline); + return; + } + else if (line.contains(pat[WhoEnde])) { + emit fibsWhoEnd(); + return; + } + /* + * This message is ignored. The instruction is given elsewhere (and slightly + * delayed in the flow of time). + */ + if (line.contains(pat[TypJoin])) { + return; + } + /* + * Watching other players + */ + else if (line.contains(pat[BegWtch])) { + emit allowCommand(Load, true); + rawline.replace(pat[BegWtch], ""); + rawline.truncate(rawline.length()-1); + emit fibsStartNewGame(rawline); + load(); + } + else if (line.contains(pat[EndWtch])) { + emit gameOver(); + } + /* + * Blinding of players, the actual blind is handled by + * the player list + */ + else if (line.contains(pat[BegBlnd])) { + rawline.replace(pat[BegBlnd], ""); + rawline.truncate(rawline.length()-1); + emit changePlayerStatus(rawline, KFibsPlayerList::Blind, true); + line = "<font color=\"red\">" + line + "</font>"; + } + else if (line.contains(pat[EndBlnd])) { + rawline.replace(pat[EndBlnd], ""); + rawline.truncate(rawline.length()-1); + emit changePlayerStatus(rawline, KFibsPlayerList::Blind, false); + line = "<font color=\"red\">" + line + "</font>"; + } + /* + * Starting or reloading games or matches + */ + else if (line.contains(pat[BegGame])) { + rawline.replace(pat[BegGame], ""); + rawline.truncate(rawline.length()-1); + emit fibsStartNewGame(rawline); + fibsOpt[OptDouble]->setChecked(true); + fibsOpt[OptGreedy]->setChecked(false); + actConti->setEnabled(false); + actLeave->setEnabled(false); + } + else if (line.contains(pat[Reload1])) { + rawline.replace(pat[Reload1], ""); + rawline = rawline.left(rawline.find(' ')); + rawline.truncate(rawline.length()-1); + emit fibsStartNewGame(rawline); + fibsOpt[OptDouble]->setChecked(true); + fibsOpt[OptGreedy]->setChecked(false); + actConti->setEnabled(false); + actLeave->setEnabled(false); + load(); + } + else if (line.contains(pat[Reload2])) { + rawline.replace(pat[Reload2], ""); + emit fibsStartNewGame(rawline); + fibsOpt[OptDouble]->setChecked(true); + fibsOpt[OptGreedy]->setChecked(false); + actConti->setEnabled(false); + actLeave->setEnabled(false); + load(); + } + /* + * Opponent offered resignation or the cube. We have to accept + * or reject the offer. + */ + else if (line.contains(pat[RejAcpt])) { + actAccept->setEnabled(true); + actReject->setEnabled(true); + line += "<br><pre> </pre>" + i18n("(or use the corresponding menu " + "entry to accept or reject the offer)"); + } + /* + * This is strange: FIBS seems to not send a newline at the + * end of this pattern. So we work around that. + */ + else if (line.contains(pat[YouAcpt])) { + actAccept->setEnabled(false); + actReject->setEnabled(false); + rawline.replace(pat[YouAcpt], ""); + line.truncate(line.length()-rawline.length()); + if (!rawline.stripWhiteSpace().isEmpty()) { + handleServerData(rawline); + } + } + /* + * Ending of games + */ + else if (line.contains(pat[EndLose])) { + if (playing) { + KNotifyClient::event("game over l", i18n("Sorry, you lost the game.")); + if (useAutoMsg[MsgLos] && !autoMsg[MsgLos].stripWhiteSpace().isEmpty()) + emit serverString(QString("tell ") + pname[THEM] + " " + autoMsg[MsgLos]); + } + emit gameOver(); + } + else if (line.contains(pat[EndVict])) { + if (playing) { + KNotifyClient::event("game over w", i18n("Congratulations, you won the game!")); + if (useAutoMsg[MsgWin] && !autoMsg[MsgWin].stripWhiteSpace().isEmpty()) + emit serverString(QString("tell ") + pname[THEM] + " " + autoMsg[MsgWin]); + } + emit gameOver(); + } + else if (line.contains(pat[GameSav])) { + emit gameOver(); + } + /* + * User logs out. This has to be signalled to the player + * list. Get the true user names by working on the rawline. + */ + else if (line.contains(pat[UserLot])) { + rawline.replace(pat[UserLot], ""); + emit fibsLogout(rawline.left(rawline.find(' '))); + return; + } + /* + * Emit the name of the newly logged in user. + */ + else if (line.contains(pat[UserLin])) { + rawline.replace(pat[UserLin], ""); + emit fibsLogin(rawline.left(rawline.find(' '))); + return; + } + /* + * Special attention has to be paid to the proper setting of + * the 'boardstyle' variable, since we will not be able to display + * the board properly without it. + */ + else if (line.contains(pat[BoardSY])) { + // ignored + return; + } + else if (line.contains(pat[BoardSN])) { + emit serverString("set boardstyle 3"); + emit infoText(QString("<font color=\"red\"><br>") + + i18n("You should never set the 'boardstyle' variable " + "by hand! It is vital for proper functioning of " + "this program that it remains set to 3. It has " + "been reset for you.") + + "<br></font>"); + return; + } + /* + * This is the final fall through: if the line started with ** and + * hasn't been processed, make it red, since it is a server resp. + * to something we just did. + */ + else if (line.contains(pat[TwoStar])) { + line = "<font color=\"red\">" + line + "</font>"; + } + + // -------------------------------------------------------------------------------- + + /* + * Print whatever part of the line made it here + */ + emit infoText(line); +} + +// EOF + + +// == constructor, destructor and setup ======================================== + +/* + * Constructor + */ +KBgEngineFIBS::KBgEngineFIBS(QWidget *parent, QString *name, QPopupMenu *pmenu) + : KBgEngine(parent, name, pmenu) +{ + /* + * No connection, not playing, ready for login + */ + connection = new QSocket(parent, "fibs connection"); + playing = false; + login = true; + + connect(connection, SIGNAL(hostFound()), this, SLOT(hostFound())); + connect(connection, SIGNAL(connected()), this, SLOT(connected())); + connect(connection, SIGNAL(error(int)), this, SLOT(connError(int))); + connect(connection, SIGNAL(connectionClosed()), this, SLOT(connectionClosed())); + connect(connection, SIGNAL(delayedCloseFinished()), this, SLOT(connectionClosed())); + connect(connection, SIGNAL(readyRead()), this, SLOT(readData())); + + connect(this, SIGNAL(serverString(const QString &)), this, SLOT(sendData(const QString &))); + + /* + * No invitation dialog + */ + invitationDlg = 0; + + connect(this, SIGNAL(fibsWhoInfo(const QString &)), this, SLOT(changeJoin(const QString &))); + connect(this, SIGNAL(fibsLogout (const QString &)), this, SLOT(cancelJoin(const QString &))); + connect(this, SIGNAL(gameOver()), this, SLOT(endGame())); + + /* + * Creating, initializing and connecting the player list + */ + playerlist = new KFibsPlayerList(0, "fibs player list"); + + connect(this, SIGNAL(fibsWhoInfo(const QString &)), playerlist, SLOT(changePlayer(const QString &))); + connect(this, SIGNAL(fibsLogout (const QString &)), playerlist, SLOT(deletePlayer(const QString &))); + connect(this, SIGNAL(fibsWhoEnd()), playerlist, SLOT(stopUpdate())); + connect(this, SIGNAL(fibsConnectionClosed()), playerlist, SLOT(stopUpdate())); + connect(this, SIGNAL(changePlayerStatus(const QString &, int, bool)), + playerlist, SLOT(changePlayerStatus(const QString &, int, bool))); + connect(playerlist, SIGNAL(fibsCommand(const QString &)), this, SLOT(handleCommand(const QString &))); + connect(playerlist, SIGNAL(fibsInvite(const QString &)), this, SLOT(fibsRequestInvitation(const QString &))); + + /* + * Create, initialize and connect the chat window + */ + chatWindow = new KBgChat(0, "chat window"); + + connect(this, SIGNAL(chatMessage(const QString &)), chatWindow, SLOT(handleData(const QString &))); + connect(this, SIGNAL(fibsStartNewGame(const QString &)), chatWindow, SLOT(startGame(const QString &))); + connect(this, SIGNAL(gameOver()), chatWindow, SLOT(endGame())); + connect(this, SIGNAL(fibsLogout (const QString &)), chatWindow, SLOT(deletePlayer(const QString &))); + connect(chatWindow, SIGNAL(fibsCommand(const QString &)), this, SLOT(handleCommand(const QString &))); + connect(chatWindow, SIGNAL(fibsRequestInvitation(const QString &)), this, SLOT(fibsRequestInvitation(const QString &))); + connect(chatWindow, SIGNAL(personalMessage(const QString &)), this, SLOT(personalMessage(const QString &))); + connect(playerlist, SIGNAL(fibsTalk(const QString &)), chatWindow, SLOT(fibsTalk(const QString &))); + + /* + * Creating, initializing and connecting the menu + * ---------------------------------------------- + */ + respMenu = new QPopupMenu(); + joinMenu = new QPopupMenu(); + cmdMenu = new QPopupMenu(); + optsMenu = new QPopupMenu(); + + /* + * Initialize the FIBS submenu - this is also put in the play menu + */ + conAction = new KAction(i18n("&Connect"), 0, this, SLOT( connectFIBS()), this); + newAction = new KAction(i18n("New Account"), 0, this, SLOT( newAccount()), this); + disAction = new KAction(i18n("&Disconnect"), 0, this, SLOT(disconnectFIBS()), this); + + conAction->setEnabled(true ); conAction->plug(menu); + disAction->setEnabled(false); disAction->plug(menu); + newAction->setEnabled(true ); newAction->plug(menu); + + menu->insertSeparator(); + + (invAction = new KAction(i18n("&Invite..."), 0, this, SLOT(inviteDialog()), this))->plug(menu); + + /* + * Create and fill the response menu. This is for all these: type this or + * that messages from FIBS. + */ + cmdMenuID = menu->insertItem(i18n("&Commands"), cmdMenu); { + + (actAway = new KAction(i18n("Away"), 0, this, SLOT(away()), this))->plug(cmdMenu); + (actBack = new KAction(i18n("Back"), 0, this, SLOT(back()), this))->plug(cmdMenu); + + actAway->setEnabled(true); + actBack->setEnabled(false); + } + + /* + * Create the server side options. This is preliminary and needs more work. + * The available options are skewed, since they refelect the needs of the + * author. Contact jens@hoefkens.com if your favorite option is missing. + */ + optsMenuID = menu->insertItem(i18n("&Options"), optsMenu); { + + for (int i = 0; i < NumFIBSOpt; i++) + fibsOpt[i] = 0; + + fibsOpt[OptReady] = new KToggleAction(i18n("Ready to Play"), + 0, this, SLOT(toggle_ready()), this); + fibsOpt[OptRatings] = new KToggleAction(i18n("Show Rating Computations"), + 0, this, SLOT(toggle_ratings()), this); + fibsOpt[OptRatings]->setCheckedState(i18n("Hide Rating Computations")); + fibsOpt[OptGreedy] = new KToggleAction(i18n("Greedy Bearoffs"), + 0, this, SLOT(toggle_greedy()), this); + fibsOpt[OptDouble] = new KToggleAction(i18n("Ask for Doubles"), + 0, this, SLOT(toggle_double()), this); + + for (int i = 0; i < NumFIBSOpt; i++) + if (fibsOpt[i]) + fibsOpt[i]->plug(optsMenu); + + } + + /* + * Create and fill the response menu. This is for all these: type this or + * that messages from FIBS. + */ + respMenuID = menu->insertItem(i18n("&Response"), respMenu); { + + (actAccept = new KAction(i18n("Accept"), 0, this, SLOT(accept()), this))->plug(respMenu); + (actReject = new KAction(i18n("Reject"), 0, this, SLOT(reject()), this))->plug(respMenu); + + actAccept->setEnabled(false); + actReject->setEnabled(false); + + respMenu->insertSeparator(); + + (actConti = new KAction(i18n("Join"), 0, this, SLOT(match_conti()), this))->plug(respMenu); + (actLeave = new KAction(i18n("Leave"), 0, this, SLOT(match_leave()), this))->plug(respMenu); + + actConti->setEnabled(false); + actLeave->setEnabled(false); + } + + /* + * Create the join menu and do not fill it (this happens at first + * action setup. + */ + joinMenuID = menu->insertItem(i18n("&Join"), joinMenu); { + numJoin = -1; + + actJoin[0] = new KAction("", 0, this, SLOT(join_0()), this); + actJoin[1] = new KAction("", 0, this, SLOT(join_1()), this); + actJoin[2] = new KAction("", 0, this, SLOT(join_2()), this); + actJoin[3] = new KAction("", 0, this, SLOT(join_3()), this); + actJoin[4] = new KAction("", 0, this, SLOT(join_4()), this); + actJoin[5] = new KAction("", 0, this, SLOT(join_5()), this); + actJoin[6] = new KAction("", 0, this, SLOT(join_6()), this); + actJoin[7] = new KAction("", 0, this, SLOT(join_7()), this); + } + + menu->setItemEnabled(joinMenuID, false); + menu->setItemEnabled( cmdMenuID, false); + menu->setItemEnabled(respMenuID, false); + menu->setItemEnabled(optsMenuID, false); + + /* + * Continue with the FIBS menu + */ + menu->insertSeparator(); + + (listAct = new KToggleAction(i18n("&Player List"), 0, this, SLOT(showList()), this))->plug(menu); + (chatAct = new KToggleAction(i18n("&Chat"), 0, this, SLOT(showChat()), this))->plug(menu); + + connect(playerlist, SIGNAL(windowVisible(bool)), listAct, SLOT(setChecked(bool))); + connect(chatWindow, SIGNAL(windowVisible(bool)), chatAct, SLOT(setChecked(bool))); + + /* + * Create message IDs. This sets up a lot of regular expressions. + */ + initPattern(); + + /* + * Restore old settings + */ + readConfig(); + + // FIXME: open the child windows in start() and not here + + /* + * Update the menu actions + */ + listAct->setChecked(playerlist->isVisible()); + chatAct->setChecked(chatWindow->isVisible()); + + /* + * Initialize the keepalive timer FIXME: make this a setting + */ + keepalive = true; + + // FIXME: move the start to connect... + + keepaliveTimer = new QTimer(this); + connect(keepaliveTimer, SIGNAL(timeout()), this, SLOT(keepAlive())); + keepaliveTimer->start(1200000); +} + +/* + * Destructor deletes child objects if necessary + */ +KBgEngineFIBS::~KBgEngineFIBS() +{ + delete joinMenu; + delete respMenu; + delete cmdMenu; + delete optsMenu; + + delete connection; + delete invitationDlg; + + delete playerlist; + delete chatWindow; +} + + diff --git a/kbackgammon/engines/fibs/kbgfibs.h b/kbackgammon/engines/fibs/kbgfibs.h new file mode 100644 index 00000000..1c14e0f3 --- /dev/null +++ b/kbackgammon/engines/fibs/kbgfibs.h @@ -0,0 +1,479 @@ +/* Yo Emacs, this -*- C++ -*- + + Copyright (C) 1999-2001 Jens Hoefkens + jens@hoefkens.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + $Id$ + +*/ + + +#ifndef __KBGFIBS_H +#define __KBGFIBS_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <generic/kbgengine.h> + +#include "kplayerlist.h" +#include "kbgfibschat.h" +#include "kbginvite.h" // TODO + +#include <qstring.h> +#include <qstringlist.h> +#include <qregexp.h> + +#include <klocale.h> + +class QTimer; +class QSocket; +class QPopupMenu; +class QCheckBox; + +class KAction; +class KToggleAction; + +/** + * + * Special backgammon engine for games on the First Internet Backgammon Server + * + * @short The FIBS backgammon engine + * @author Jens Hoefkens <jens@hoefkens.com> + * + */ +class KBgEngineFIBS : public KBgEngine +{ + Q_OBJECT + +public: + + /** + * Constructor + */ + KBgEngineFIBS(QWidget *parent = 0, QString *name = 0, QPopupMenu *pmenu = 0); + + /** + * Destructor + */ + virtual ~KBgEngineFIBS(); + + /** + * Fills the engine-specific page into the notebook + */ + virtual void getSetupPages(KDialogBase *nb); + + virtual void setupOk(); + virtual void setupDefault(); + virtual void setupCancel(); + + /* + * Check with the engine if we can quit. This may require user + * interaction. + */ + virtual bool queryClose(); + + /** + * About to be closed. Let the engine exit properly. + */ + virtual bool queryExit(); + + virtual void start(); + + +public slots: + + /** + * Read and save user settings to the config file + */ + virtual void readConfig(); + virtual void saveConfig(); + + /** + * Roll dice for the player w + */ + virtual void rollDice(const int w); + + /** + * Double the cube of player w + */ + virtual void doubleCube(const int w); + + /** + * A move has been made on the board - see the board class + * for the format of the string s + */ + virtual void handleMove(QString *s); + + /** + * Undo the last move + */ + virtual void undo(); + + /** + * Redo the last move + */ + virtual void redo(); + + /** + * Commit a move + */ + virtual void done(); + + + // ########################################################################### + // + // + // + // TODO TODO TODO TODO TODO TODO TODO + // + // + // + // ########################################################################### + + /* + * Process the string cmd + */ + void handleCommand(const QString &cmd); + + void fibsRequestInvitation(const QString &player); + + void personalMessage(const QString &msg); + + + + /* + * Local configuration handling + */ + + void keepAlive(); + +signals: + + void serverString(const QString &s); + + void fibsWhoInfo(const QString &line); + void fibsWhoEnd(); + void fibsLogout(const QString &p); + void fibsLogin(const QString &p); + + void fibsConnectionClosed(); + + void changePlayerStatus(const QString &, int, bool); + + void chatMessage(const QString &msg); + + void fibsStartNewGame(const QString &msg); + void gameOver(); + +protected slots: + + void invitationDone(); + void inviteDialog(); + void showList(); + void showChat(); + + void endGame(); + +private: + + QTimer *keepaliveTimer; + + QString pname[2]; + + QString currBoard, caption; + + //KBgStatus *currBoard + //KBgFIBSBoard *boardHandler; + + QStringList invitations; + + /* + * special menu entries + */ + int respMenuID, cmdMenuID, joinMenuID, optsMenuID; + QPopupMenu *respMenu, *cmdMenu, *joinMenu, *optsMenu; + + /* + * child windows + */ + KFibsPlayerList *playerlist; + KBgChat *chatWindow; + KBgInvite *invitationDlg; + + /* + * Other stuff + */ + QString lastMove; + int toMove; + + QString lastAway; + bool playing; + bool redoPossible; + int undoCounter; + + KAction *conAction, *disAction, *newAction, *invAction; + + KAction *actAccept, *actReject, *actConti, *actLeave, *actAway, *actBack; + + KToggleAction *chatAct, *listAct; + + + // ########################################################################### + // + // + // + // DONE DONE DONE DONE DONE DONE DONE + // + // + // + // ########################################################################### + +private: + + /** + * Actions for responding to invitations. numJoin is he current + * number of active actions. The max. number of pending invitations + * is eight and this is hardcoded in a lot of places (not the least + * of which are the slots join_N(). + */ + KAction *actJoin[8]; + int numJoin; + +protected slots: + + /** + * Handle rawwho information for the purposes of the invitation + * submenu and the join entries + */ + void changeJoin(const QString &info); + + /** + * A player will be removed from the menu of pending invitations + * if necessary. + */ + void cancelJoin(const QString &info); + + /** + * We have up to 8 names in the join menu. They are the + * players that invited us to play games. Each action + * has its own slot and all slots call the common backend + * join(). + */ + void join(const QString &msg); + + void join_0(); + void join_1(); + void join_2(); + void join_3(); + void join_4(); + void join_5(); + void join_6(); + void join_7(); + + /** + * Simple slots that toggle FIBS server-side settings. The + * names of the functions reflect the name of the toggle on + * FIBS. + */ + void toggle_greedy(); + void toggle_ready(); + void toggle_double(); + void toggle_ratings(); + +private: + + /** + * Toggle actions for the FIBS server-side settings + */ + enum FIBSOpt {OptReady, OptGreedy, OptDouble, + OptAllowPip, OptAutoMove, OptCrawford, OptSilent, OptRatings, OptMoves, NumFIBSOpt}; + KToggleAction *fibsOpt[NumFIBSOpt]; + +public slots: + + /* + * Connection handling + * ------------------- + */ + + // initiate asynchronous connection establishment + void connectFIBS(); + + // take the connection down + void disconnectFIBS(); + + // create a new account and connect + void newAccount(); + + // called when the connection is down + void connectionClosed(); + + // the hostname has been resolved + void hostFound(); + + // a connection error occurred + void connError(int f); + + // connection has been established + void connected(); + + // data can be read from the socket + void readData(); + + // send the string s to the server + void sendData(const QString &s); + +protected: + + // get the connection parameters + bool queryConnection(const bool newlogin); + +private: + + // actual connection object + QSocket *connection; + + // flag if we have login information or new account + bool login; + +protected slots: + + /* + * FIBS command slots + * ------------------ + */ + + // go away and leave a message + void away(); + + // come back after being away + void back(); + + // roll dice + virtual void roll(); + + // double the cube + virtual void cube(); + + // reload the board to the last known sane state + virtual void load(); + + // accept an offer + void accept(); + + // reject an offer + void reject(); + + // continue a multi game match + void match_conti(); + + // leave a multi game match + void match_leave(); + +protected slots: + + /* + * All strings received from the server are given to handleServerData() for + * identification and processing. It implements a limited state machine to + * handle the incoming data correctly. The whole function could probably be + * made more efficient, but it is not time critical (and it appears to be + * easier to understand this way). + */ + void handleServerData(QString &line); + +protected: + + enum RxStatus {RxIgnore, RxConnect, RxWhois, RxMotd, RxRating, + RxNewLogin, RxGoodbye, RxNormal}; + + int rxStatus, rxCount; + + QString rxCollect; + + /* + * The following functions handle the individual states + * of the handleServerData() state machine, + */ + void handleMessageWhois(const QString &line); + void handleMessageRating(const QString &line); + void handleMessageMotd(const QString &line); + void handleMessageNewLogin(const QString &line); + void handleMessageConnect(const QString &line, const QString &rawline); + void handleMessageNormal(QString &line, QString &rawline); + + /* + * The next enumeration and the array of regular expressions is needed for the + * message identification in handleServerData(). + */ + enum Pattern {Welcome, OwnInfo, NoLogin, BegRate, EndRate, HTML_lt, HTML_gt, + BoardSY, BoardSN, WhoisBG, WhoisE1, WhoisE2, WhoEnde, WhoInfo, + MotdBeg, MotdEnd, MsgPers, MsgDeli, MsgSave, ChatSay, ChatSht, + ChatWis, ChatKib, SelfSay, SelfSlf, SelfSht, SelfWis, SelfKib, + UserLin, UserLot, Goodbye, GameSav, RawBord, YouTurn, PlsMove, + BegWtch, EndWtch, BegBlnd, EndBlnd, BegGame, OneWave, TwoWave, + YouWave, Reload1, Reload2, GameBG1, GameBG2, GameRE1, GameRE2, + GameEnd, EndLose, EndVict, MatchB1, MatchB2, MatchB3, MatchB4, + RejAcpt, YouAway, YouAcpt, HelpTxt, Invite0, Invite1, Invite2, + Invite3, ConLeav, TabChar, PlsChar, OneName, TypJoin, YouBack, + YouMove, YouRoll, TwoStar, BoxHori, BoxVer1, BoxVer2, OthrNam, + YourNam, GivePwd, RetypeP, GreedyY, GreedyN, RejCont, AcptWin, + YouGive, DoubleY, DoubleN, KeepAlv, RatingY, RatingN, + NumPattern}; + + QRegExp pat[NumPattern]; + + /* + * This function is simply filling the pat[] array with the proper values. + */ + void initPattern(); + +private: + + /* + * Local setup and config variables + * ================================ + */ + + /* + * Various options + */ + bool showMsg, whoisInvite; + QCheckBox *cbp, *cbi; + + QCheckBox *cbk; + bool keepalive; + + /* + * Connection setup + */ + enum FIBSInfo {FIBSHost, FIBSPort, FIBSUser, FIBSPswd, NumFIBS}; + QString infoFIBS[NumFIBS]; + QLineEdit *lec[NumFIBS]; + + /* + * Auto messages + */ + enum AutoMessages {MsgBeg, MsgLos, MsgWin, NumMsg}; + QLineEdit *lem[NumMsg]; + QCheckBox *cbm[NumMsg]; + bool useAutoMsg[NumMsg]; + QString autoMsg[NumMsg]; +}; + +#endif // __KBGFIBS_H diff --git a/kbackgammon/engines/fibs/kbgfibschat.cpp b/kbackgammon/engines/fibs/kbgfibschat.cpp new file mode 100644 index 00000000..45ba2bb7 --- /dev/null +++ b/kbackgammon/engines/fibs/kbgfibschat.cpp @@ -0,0 +1,828 @@ + +/* Yo Emacs, this -*- C++ -*- + + Copyright (C) 1999-2001 Jens Hoefkens + jens@hoefkens.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + $Id$ + +*/ + + +#include "kbgfibschat.h" +#include "kbgfibschat.moc" + +#include <qstring.h> + +#include <qlayout.h> +#include <qlabel.h> +#include <qpopupmenu.h> +#include <qregexp.h> +#include <qfont.h> +#include <qwhatsthis.h> +#include <qdatetime.h> +#include <qclipboard.h> +#include <qsimplerichtext.h> +#include <qregion.h> +#include <qpalette.h> +#include <qpainter.h> +#include <qpoint.h> +#include <qlistbox.h> +#include <qiconset.h> +#include <qstringlist.h> +#include <qdict.h> + +#include <klocale.h> +#include <kconfig.h> +#include <kapplication.h> +#include <kdebug.h> +#include <kstdaction.h> +#include <ktabctl.h> +#include <kaction.h> +#include <kiconloader.h> + +#include "clip.h" +#include "version.h" + + +/* + * Private utility class that might become more generally useful in + * the future. Basically, it implements rich text QListBox items. + */ +class KLBT : public QListBoxText +{ + +public: + + /* + * Constructor + */ + KLBT(QWidget *parent, const QString &text = QString::null, const QString &player = QString::null) + : QListBoxText(text) + { + w = parent; + n = new QString(player); + t = new QSimpleRichText(text, w->font()); + + // FIXME: this is not yet perfect + t->setWidth(w->width()-20); + } + + /* + * Destructor + */ + virtual ~KLBT() + { + delete t; + delete n; + } + + /* + * Overloaded required members returning height + */ + virtual int height(const QListBox *) const + { + return (1+t->height()); + } + + /* + * Overloaded required members returning width + */ + virtual int width(const QListBox *) const + { + return t->width(); + } + + /* + * The context menu needs the name of the player. It's easier + * than extracting it from the text. + */ + QString player() const + { + return *n; + } + +protected: + + /* + * Required overloaded member to paint the text on the painter p. + */ + virtual void paint(QPainter *p) + { + t->draw(p, 1, 1, QRegion(p->viewport()), w->colorGroup()); + } + +private: + + QSimpleRichText *t; + QWidget *w; + QString *n; + +}; + + +class KBgChatPrivate +{ +public: + + /* + * Name of the users + */ + QString mName[2]; + + /* + * Hold and assemble info text + */ + QString mText; + + /* + * Numbers of the private action list. + */ + enum Privact {Inquire, InviteD, Invite1, Invite2, Invite3, Invite4, + Invite5, Invite6, Invite7, InviteR, InviteU, Silent, + Talk, Gag, Ungag, Cleargag, Copy, Clear, Close, MaxAction}; + + /* + * Available actions + */ + KAction *mAct[MaxAction]; + + /* + * Context menu and invitation menu + */ + QPopupMenu *mChat, *mInvt; + + /* + * list of users we do not want to hear shouting + */ + QStringList mGag; + + /* + * Listbox needed by the setup dialog + */ + QListBox *mLb; + + /* + * Internal ID to name mapping + */ + QDict<int> *mName2ID; + +}; + + +// == constructor, destructor ================================================== + +/* + * Constructor of the chat window. + */ +KBgChat::KBgChat(QWidget *parent, const char *name) + : KChat(parent, false) +{ + d = new KBgChatPrivate(); + KActionCollection* actions = new KActionCollection(this); + + d->mName[0] = QString::null; + d->mChat = 0; + d->mInvt = new QPopupMenu(); + + setAutoAddMessages(false); // we get an echo from FIBS + setFromNickname(i18n("%1 user").arg(PROG_NAME)); + + if (!addSendingEntry(i18n("Kibitz to watchers and players"), CLIP_YOU_KIBITZ)) + kdDebug(10500) << "adding kibitz" << endl; + if (!addSendingEntry(i18n("Whisper to watchers only"), CLIP_YOU_WHISPER)) + kdDebug(10500) << "adding whisper" << endl; + + connect(this, SIGNAL(rightButtonClicked(QListBoxItem *, const QPoint &)), + this, SLOT(contextMenu(QListBoxItem *, const QPoint &))); + connect(this, SIGNAL(signalSendMessage(int, const QString &)), + this, SLOT(handleCommand(int, const QString &))); + + d->mName2ID = new QDict<int>(17, true); + d->mName2ID->setAutoDelete(true); + + /* + * some eye candy :) + */ + setIcon(kapp->miniIcon()); + setCaption(i18n("Chat Window")); + + QWhatsThis::add(this, i18n("This is the chat window.\n\n" + "The text in this window is colored depending on whether " + "it is directed at you personally, shouted to the general " + "FIBS population, has been said by you, or is of general " + "interest. If you select the name of a player, the context " + "contains entries specifically geared towards that player.")); + /* + * Define set of available actions + */ + d->mAct[KBgChatPrivate::Inquire] = new KAction(i18n("Info On"), + QIconSet(kapp->iconLoader()->loadIcon( + "help.xpm", KIcon::Small)), + 0, this, SLOT(slotInquire()), actions); + d->mAct[KBgChatPrivate::Talk] = new KAction(i18n("Talk To"), + QIconSet(kapp->iconLoader()->loadIcon( + PROG_NAME "-chat.png", KIcon::Small)), + 0, this, SLOT(slotTalk()), actions); + + d->mAct[KBgChatPrivate::InviteD] = new KAction(i18n("Use Dialog"), 0, this, + SLOT(slotInviteD()), actions); + d->mAct[KBgChatPrivate::Invite1] = new KAction(i18n("1 Point Match"), 0, this, + SLOT(slotInvite1()), actions); + d->mAct[KBgChatPrivate::Invite2] = new KAction(i18n("2 Point Match"), 0, this, + SLOT(slotInvite2()), actions); + d->mAct[KBgChatPrivate::Invite3] = new KAction(i18n("3 Point Match"), 0, this, + SLOT(slotInvite3()), actions); + d->mAct[KBgChatPrivate::Invite4] = new KAction(i18n("4 Point Match"), 0, this, + SLOT(slotInvite4()), actions); + d->mAct[KBgChatPrivate::Invite5] = new KAction(i18n("5 Point Match"), 0, this, + SLOT(slotInvite5()), actions); + d->mAct[KBgChatPrivate::Invite6] = new KAction(i18n("6 Point Match"), 0, this, + SLOT(slotInvite6()), actions); + d->mAct[KBgChatPrivate::Invite7] = new KAction(i18n("7 Point Match"), 0, this, + SLOT(slotInvite7()), actions); + d->mAct[KBgChatPrivate::InviteU] = new KAction(i18n("Unlimited"), 0, this, + SLOT(slotInviteU()), actions); + d->mAct[KBgChatPrivate::InviteR] = new KAction(i18n("Resume"), 0, this, + SLOT(slotInviteR()), actions); + + d->mAct[KBgChatPrivate::InviteD]->plug(d->mInvt); + + d->mInvt->insertSeparator(); + + d->mAct[KBgChatPrivate::Invite1]->plug(d->mInvt); + d->mAct[KBgChatPrivate::Invite2]->plug(d->mInvt); + d->mAct[KBgChatPrivate::Invite3]->plug(d->mInvt); + d->mAct[KBgChatPrivate::Invite4]->plug(d->mInvt); + d->mAct[KBgChatPrivate::Invite5]->plug(d->mInvt); + d->mAct[KBgChatPrivate::Invite6]->plug(d->mInvt); + d->mAct[KBgChatPrivate::Invite7]->plug(d->mInvt); + + d->mInvt->insertSeparator(); + + d->mAct[KBgChatPrivate::InviteU]->plug(d->mInvt); + d->mAct[KBgChatPrivate::InviteR]->plug(d->mInvt); + + d->mAct[KBgChatPrivate::Gag] = new KAction(i18n("Gag"), 0, this, SLOT(slotGag()), actions); + d->mAct[KBgChatPrivate::Ungag] = new KAction(i18n("Ungag"), 0, this, SLOT(slotUngag()), actions); + d->mAct[KBgChatPrivate::Cleargag] = new KAction(i18n("Clear Gag List"), 0, this, SLOT(slotCleargag()), actions); + d->mAct[KBgChatPrivate::Copy] = KStdAction::copy(this, SLOT(slotCopy()), actions); + d->mAct[KBgChatPrivate::Clear] = new KAction(i18n("Clear"), 0, this, SLOT(slotClear()), actions); + d->mAct[KBgChatPrivate::Close] = KStdAction::close(this, SLOT(hide()), actions); + d->mAct[KBgChatPrivate::Silent] = new KToggleAction(i18n("Silent"), 0, this, SLOT(slotSilent()), actions); +} + + +/* + * Destructor + */ +KBgChat::~KBgChat() +{ + delete d->mName2ID; + delete d->mChat; // save to delete NULL pointers + delete d->mInvt; + delete d; +} + + +// == configuration handling =================================================== + +/* + * Restore the previously stored settings + */ +void KBgChat::readConfig() +{ + KConfig* config = kapp->config(); + config->setGroup("chat window"); + + QPoint pos(10, 10); + + pos = config->readPointEntry("ori", &pos); + setGeometry(pos.x(), pos.y(), config->readNumEntry("wdt",460), config->readNumEntry("hgt",200)); + + config->readBoolEntry("vis", false) ? show() : hide(); + + ((KToggleAction *)d->mAct[KBgChatPrivate::Silent])->setChecked(config->readBoolEntry("sil", false)); + + d->mGag = config->readListEntry("gag"); +} + +/* + * Save the current settings to disk + */ +void KBgChat::saveConfig() +{ + KConfig* config = kapp->config(); + config->setGroup("chat window"); + + config->writeEntry("ori", pos()); + config->writeEntry("hgt", height()); + config->writeEntry("wdt", width()); + config->writeEntry("vis", isVisible()); + + config->writeEntry("sil", ((KToggleAction *)d->mAct[KBgChatPrivate::Silent])->isChecked()); + + config->writeEntry("gag", d->mGag); +} + + +/* + * Setup dialog page of the player list - allow the user to select the + * columns to show + * + * FIXME: need to be able to set font here KChatBase::setBothFont(const QFont& font) + */ +void KBgChat::getSetupPages(KTabCtl *nb, int space) +{ + /* + * Main Widget + * =========== + */ + QWidget *w = new QWidget(nb); + QGridLayout *gl = new QGridLayout(w, 2, 1, space); + + d->mLb = new QListBox(w); + d->mLb->setMultiSelection(true); + + d->mLb->insertStringList(d->mGag); + + QLabel *info = new QLabel(w); + info->setText(i18n("Select users to be removed from the gag list.")); + + QWhatsThis::add(w, i18n("Select all the users you want " + "to remove from the gag list " + "and then click OK. Afterwards " + "you will again hear what they shout.")); + + gl->addWidget(d->mLb, 0, 0); + gl->addWidget(info, 1, 0); + + /* + * put in the page + * =============== + */ + gl->activate(); + w->adjustSize(); + w->setMinimumSize(w->size()); + nb->addTab(w, i18n("&Gag List")); +} + +/* + * Remove all the selected entries from the gag list + */ +void KBgChat::setupOk() +{ + for (uint i = 0; i < d->mLb->count(); ++i) { + if (d->mLb->isSelected(i)) + d->mGag.remove(d->mLb->text(i)); + } + d->mLb->clear(); + d->mLb->insertStringList(d->mGag); +} + +/* + * Don't do anything + */ +void KBgChat::setupCancel() +{ + // empty +} + +/* + * By default, all players stay in the gag list + */ +void KBgChat::setupDefault() +{ + d->mLb->clearSelection(); +} + + +// == various slots and functions ============================================== + +/* + * Overloaded member to create a QListBoxItem for the chat window. + */ +QListBoxItem* KBgChat::layoutMessage(const QString& fromName, const QString& text) +{ + QListBoxText* message = new KLBT(this, text, fromName); + return message; +} + +/* + * Catch hide events, so the engine's menu can be update. + */ +void KBgChat::showEvent(QShowEvent *e) +{ + QFrame::showEvent(e); + emit windowVisible(true); +} + +/* + * Catch hide events, so the engine's menu can be update. + */ +void KBgChat::hideEvent(QHideEvent *e) +{ + emit windowVisible(false); + QFrame::hideEvent(e); +} + +/* + * At the beginning of a game, add the name to the list and switch to + * kibitz mode. + */ +void KBgChat::startGame(const QString &name) +{ + int *id = d->mName2ID->find(d->mName[1] = name); + if (!id) { + id = new int(nextId()); + d->mName2ID->insert(name, id); + addSendingEntry(i18n("Talk to %1").arg(name), *id); + } + setSendingEntry(CLIP_YOU_KIBITZ); +} + +/* + * At the end of a game, we switch to talk mode. + */ +void KBgChat::endGame() +{ + int *id = d->mName2ID->find(d->mName[1]); + if (id) + setSendingEntry(*id); + else + setSendingEntry(SendToAll); +} + +/* + * Set the chat window ready to talk to name + */ +void KBgChat::fibsTalk(const QString &name) +{ + int *id = d->mName2ID->find(name); + if (!id) { + id = new int(nextId()); + d->mName2ID->insert(name, id); + addSendingEntry(i18n("Talk to %1").arg(name), *id); + } + setSendingEntry(*id); +} + +/* + * Remove the player from the combo box when he/she logs out. + */ +void KBgChat::deletePlayer(const QString &name) +{ + int *id = d->mName2ID->find(name); + if (id) { + removeSendingEntry(*id); + d->mName2ID->remove(name); + } +} + +/* + * Take action when the user presses return in the line edit control. + */ +void KBgChat::handleCommand(int id, const QString& msg) +{ + int realID = sendingEntry(); + + switch (realID) { + case SendToAll: + emit fibsCommand("shout " + msg); + break; + case CLIP_YOU_KIBITZ: + emit fibsCommand("kibitz " + msg); + break; + case CLIP_YOU_WHISPER: + emit fibsCommand("whisper " + msg); + break; + default: + QDictIterator<int> it(*d->mName2ID); + while (it.current()) { + if (*it.current() == realID) { + emit fibsCommand("tell " + it.currentKey() + " " + msg); + return; + } + ++it; + } + kdDebug(10500) << "unrecognized ID in KBgChat::handleCommand" << endl; + } +} + + +// == handle strings from the server =========================================== + +/* + * A message from the server that should be handled by us. If necessary, + * we replace the CLIP number by a string and put the line into the window. + * + * This function emits the string in rich text format with the signal + * personalMessage - again: the string contains rich text! + */ +void KBgChat::handleData(const QString &msg) +{ + QString clip = msg.left(msg.find(' ')), user, cMsg = msg; + QDateTime date; + + bool flag = false; + int cmd = clip.toInt(&flag); + + if (flag) { + cMsg.replace(0, cMsg.find(' ')+1, ""); + + user = cMsg.left(cMsg.find(' ')); + + switch (cmd) { + case CLIP_SAYS: + if (!d->mGag.contains(user)) { + cMsg = i18n("<u>%1 tells you:</u> %2").arg(user).arg(cMsg.replace(QRegExp("^" + user), "")); + cMsg = "<font color=\"red\">" + cMsg + "</font>"; + emit personalMessage(cMsg); + } else + cMsg = ""; + break; + + case CLIP_SHOUTS: + if ((!((KToggleAction *)d->mAct[KBgChatPrivate::Silent])->isChecked()) && (!d->mGag.contains(user))) { + cMsg = i18n("<u>%1 shouts:</u> %2").arg(user).arg(cMsg.replace(QRegExp("^" + user), "")); + cMsg = "<font color=\"black\">" + cMsg + "</font>"; + } else + cMsg = ""; + break; + + case CLIP_WHISPERS: + if (!d->mGag.contains(user)) { + cMsg = i18n("<u>%1 whispers:</u> %2").arg(user).arg(cMsg.replace(QRegExp("^" + user), "")); + cMsg = "<font color=\"red\">" + cMsg + "</font>"; + emit personalMessage(cMsg); + } else + cMsg = ""; + break; + + case CLIP_KIBITZES: + if (!d->mGag.contains(user)) { + cMsg = i18n("<u>%1 kibitzes:</u> %2").arg(user).arg(cMsg.replace(QRegExp("^" + user), "")); + cMsg = "<font color=\"red\">" + cMsg + "</font>"; + emit personalMessage(cMsg); + } else + cMsg = ""; + break; + + case CLIP_YOU_SAY: + cMsg = i18n("<u>You tell %1:</u> %2").arg(user).arg(cMsg.replace(QRegExp("^" + user), "")); + cMsg = "<font color=\"darkgreen\">" + cMsg + "</font>"; + emit personalMessage(cMsg); + user = QString::null; + break; + + case CLIP_YOU_SHOUT: + cMsg = i18n("<u>You shout:</u> %1").arg(cMsg); + cMsg = "<font color=\"darkgreen\">" + cMsg + "</font>"; + emit personalMessage(cMsg); + user = QString::null; + break; + + case CLIP_YOU_WHISPER: + cMsg = i18n("<u>You whisper:</u> %1").arg(cMsg); + cMsg = "<font color=\"darkgreen\">" + cMsg + "</font>"; + emit personalMessage(cMsg); + user = QString::null; + break; + + case CLIP_YOU_KIBITZ: + cMsg = i18n("<u>You kibitz:</u> %1").arg(cMsg); + cMsg = "<font color=\"darkgreen\">" + cMsg + "</font>"; + emit personalMessage(cMsg); + user = QString::null; + break; + + case CLIP_MESSAGE: + user = cMsg.left(cMsg.find(' ')+1); + cMsg.remove(0, cMsg.find(' ')+1); + date.setTime_t(cMsg.left(cMsg.find(' ')+1).toUInt()); + cMsg.remove(0, cMsg.find(' ')); + cMsg = i18n("<u>User %1 left a message at %2</u>: %3").arg(user).arg(date.toString()).arg(cMsg); + cMsg = "<font color=\"red\">" + cMsg + "</font>"; + emit personalMessage(cMsg); + user = QString::null; + break; + + case CLIP_MESSAGE_DELIVERED: + cMsg = i18n("Your message for %1 has been delivered.").arg(user); + cMsg = QString("<font color=\"darkgreen\">") + cMsg + "</font>"; + emit personalMessage(cMsg); + user = QString::null; + break; + + case CLIP_MESSAGE_SAVED: + cMsg = i18n("Your message for %1 has been saved.").arg(user); + cMsg = QString("<font color=\"darkgreen\">") + cMsg + "</font>"; + emit personalMessage(cMsg); + user = QString::null; + break; + + default: // ignore the message + return; + } + + } else { + + /* + * Special treatment for non-CLIP messages + */ + if (cMsg.contains(QRegExp("^You say to yourself: "))) { + cMsg.replace(QRegExp("^You say to yourself: "), + i18n("<u>You say to yourself:</u> ")); + } else { + kdDebug(user.isNull(), 10500) << "KBgChat::handleData unhandled message: " + << cMsg.latin1() << endl; + return; + } + } + + if (!cMsg.isEmpty()) + addMessage(user, cMsg); +} + + +// == context menu and related slots =========================================== + +/* + * RMB opens a context menu. + */ +void KBgChat::contextMenu(QListBoxItem *i, const QPoint &p) +{ + /* + * Even if i is non-null, user might still be QString::null + */ + d->mName[0] = (i == 0) ? QString::null : ((KLBT *)i)->player(); + d->mText = (i == 0) ? QString::null : ((KLBT *)i)->text(); + + /* + * Get a new context menu every time. Safe to delete the 0 + * pointer. + */ + delete d->mChat; d->mChat = new QPopupMenu(); + + /* + * Fill the context menu with actions + */ + if (!d->mName[0].isNull()) { + + d->mAct[KBgChatPrivate::Talk]->setText(i18n("Talk to %1").arg(d->mName[0])); + d->mAct[KBgChatPrivate::Talk]->plug(d->mChat); + + d->mAct[KBgChatPrivate::Inquire]->setText(i18n("Info on %1").arg(d->mName[0])); + d->mAct[KBgChatPrivate::Inquire]->plug(d->mChat); + + // invite menu is always the same + d->mChat->insertItem(i18n("Invite %1").arg(d->mName[0]), d->mInvt); + + d->mChat->insertSeparator(); + + if (d->mGag.contains(d->mName[0]) <= 0) { + d->mAct[KBgChatPrivate::Gag]->setText(i18n("Gag %1").arg(d->mName[0])); + d->mAct[KBgChatPrivate::Gag]->plug(d->mChat); + } else { + d->mAct[KBgChatPrivate::Ungag]->setText(i18n("Ungag %1").arg(d->mName[0])); + d->mAct[KBgChatPrivate::Ungag]->plug(d->mChat); + } + } + if (d->mGag.count() > 0) + d->mAct[KBgChatPrivate::Cleargag]->plug(d->mChat); + + if ((d->mGag.count() > 0) || (!d->mName[0].isNull())) + d->mChat->insertSeparator(); + + d->mAct[KBgChatPrivate::Silent]->plug(d->mChat); + + d->mChat->insertSeparator(); + + d->mAct[KBgChatPrivate::Copy ]->plug(d->mChat); + d->mAct[KBgChatPrivate::Clear]->plug(d->mChat); + d->mAct[KBgChatPrivate::Close]->plug(d->mChat); + + d->mChat->popup(p); +} + +/* + * Clear the gag list + */ +void KBgChat::slotCleargag() +{ + d->mGag.clear(); + + QString msg("<font color=\"blue\">"); + msg += i18n("The gag list is now empty."); + msg += "</font>"; + + addMessage(QString::null, msg); +} + +/* + * Gag the selected user + */ +void KBgChat::slotGag() +{ + d->mGag.append(d->mName[0]); + + QString msg("<font color=\"blue\">"); + msg += i18n("You won't hear what %1 says and shouts.").arg(d->mName[0]); + msg += "</font>"; + + addMessage(QString::null, msg); +} + +/* + * Simple interface to the actual talk slot + */ +void KBgChat::slotTalk() +{ + fibsTalk(d->mName[0]); +} + +/* + * Remove selected user from gag list + */ +void KBgChat::slotUngag() +{ + d->mGag.remove(d->mName[0]); + + QString msg("<font color=\"blue\">"); + msg += i18n("You will again hear what %1 says and shouts.").arg(d->mName[0]); + msg += "</font>"; + + addMessage(QString::null, msg); +} + +/* + * Get information on selected user + */ +void KBgChat::slotInquire() +{ + kdDebug(d->mName[0].isNull(), 10500) << "KBgChat::slotInquire: user == null" << endl; + emit fibsCommand("whois " + d->mName[0]); +} + +/* + * Block all shouts from the chat window + */ +void KBgChat::slotSilent() +{ + QString msg; + if (((KToggleAction *)d->mAct[KBgChatPrivate::Silent])->isChecked()) + msg = "<font color=\"blue\">" + i18n("You will not hear what people shout.") + "</font>"; + else + msg = "<font color=\"blue\">" + i18n("You will hear what people shout.") + "</font>"; + addMessage(QString::null, msg); +} + +/* + * Copy the selected line to the clipboard. Strip the additional HTML + * from the text before copying. + */ +void KBgChat::slotCopy() +{ + d->mText.replace(QRegExp("<u>"), ""); + d->mText.replace(QRegExp("</u>"), ""); + d->mText.replace(QRegExp("</font>"), ""); + d->mText.replace(QRegExp("^.*\">"), ""); + + kapp->clipboard()->setText(d->mText); +} + +/* + * Invite the selected player. + */ +void KBgChat::slotInviteD() +{ + kdDebug(d->mName[0].isNull(), 10500) << "KBgChat::slotInvite: user == null" << endl; + emit fibsRequestInvitation(d->mName[0]); +} +void KBgChat::slotInvite1() { emit fibsCommand("invite " + d->mName[0] + " 1"); } +void KBgChat::slotInvite2() { emit fibsCommand("invite " + d->mName[0] + " 2"); } +void KBgChat::slotInvite3() { emit fibsCommand("invite " + d->mName[0] + " 3"); } +void KBgChat::slotInvite4() { emit fibsCommand("invite " + d->mName[0] + " 4"); } +void KBgChat::slotInvite5() { emit fibsCommand("invite " + d->mName[0] + " 5"); } +void KBgChat::slotInvite6() { emit fibsCommand("invite " + d->mName[0] + " 6"); } +void KBgChat::slotInvite7() { emit fibsCommand("invite " + d->mName[0] + " 7"); } + +void KBgChat::slotInviteU() { emit fibsCommand("invite " + d->mName[0] + " unlimited"); } +void KBgChat::slotInviteR() { emit fibsCommand("invite " + d->mName[0]); } + + +// EOF diff --git a/kbackgammon/engines/fibs/kbgfibschat.h b/kbackgammon/engines/fibs/kbgfibschat.h new file mode 100644 index 00000000..c3a1d670 --- /dev/null +++ b/kbackgammon/engines/fibs/kbgfibschat.h @@ -0,0 +1,273 @@ +/* Yo Emacs, this -*- C++ -*- + + Copyright (C) 1999-2001 Jens Hoefkens + jens@hoefkens.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + $Id$ + +*/ + +#ifndef __KBGCHAT_H +#define __KBGCHAT_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <kchat.h> + +class QString; +class QPoint; +class QListBox; +class QListBoxItem; +class QPopupMenu; + +class KTabCtl; +class KAction; + +class KBgChatPrivate; + +/** + * Class of the FIBS Chat Windows + * + * This class inherits from KChat and represents a widget for a chat + * window. It has rich text entries and supports a powerful context + * menu. + * + * @short An extension of the KGame chat window for the FIBS engine + * @author Jens Hoefkens <jens@hoefkens.com> + * + */ +class KBgChat : public KChat +{ + Q_OBJECT + +public: + + /** + * Constructor + */ + KBgChat(QWidget *parent = 0, const char *name = 0); + + /** + * Destructor + */ + virtual ~KBgChat(); + +public slots: + + /** + * Catch the RMB signal to display a context menu at p. The + * menu shows entries specific to the selected item i. + */ + void contextMenu(QListBoxItem *i, const QPoint &p); + + /** + * Add chat window specific pages to the setup dialog + */ + void getSetupPages(KTabCtl *nb, int space); + + /** + * Save and apply the changes made in the setup dialog + */ + void setupOk(); + + /** + * Do not save any of the changes made in the setup dialog + */ + void setupCancel(); + + /** + * Load default values from the setup dialog + */ + void setupDefault(); + + /** + * Player name has logges out. Remove name from the chat + * window combo box if necessary. + */ + void deletePlayer(const QString &name); + + /** + * Process and append msg to the text. + */ + void handleData(const QString &msg); + + /** + * Restore previously saved setting or provides defaults + */ + void readConfig(); + + /** + * Save current settings + */ + void saveConfig(); + + /** + * Set the opponents name and select whisper + */ + void startGame(const QString &name); + + /** + * Game is over. We won (or not) and have been playing (or not) + */ + void endGame(); + + /** + * Start talking to name + */ + void fibsTalk(const QString &name); + +signals: + + /** + * Emits a string that can be sent to the server + */ + void fibsCommand(const QString &cmd); + + /** + * Request an invitation of player + */ + void fibsRequestInvitation(const QString &player); + + /** + * Text of a personal message + */ + void personalMessage(const QString &msg); + + /** + * Dialog is visible or not + */ + void windowVisible(bool v); + +protected: + + /** + * Catch show events, so the engine's menu can be updated. + */ + virtual void showEvent(QShowEvent *e); + + /** + * Catch hide events, so the engine's menu can be updated. + */ + virtual void hideEvent(QHideEvent *e); + + /** + * Create a custom ListBoxItem that contains a formated string + * for the chat window. + */ + virtual QListBoxItem* layoutMessage(const QString& fromName, const QString& text); + +protected slots: + + /** + * Invite the selected player using the dialog + */ + void slotInviteD(); + + /** + * Invite the selected player to resume a match + */ + void slotInviteR(); + + /** + * Invite the selected player to an unlimited match + */ + void slotInviteU(); + + /** + * Invite the selected player to a 1 point match + */ + void slotInvite1(); + + /** + * Invite the selected player to a 2 point match + */ + void slotInvite2(); + + /** + * Invite the selected player to a 3 point match + */ + void slotInvite3(); + + /** + * Invite the selected player to a 4 point match + */ + void slotInvite4(); + + /** + * Invite the selected player to a 5 point match + */ + void slotInvite5(); + + /** + * Invite the selected player to a 6 point match + */ + void slotInvite6(); + + /** + * Invite the selected player to a 7 point match + */ + void slotInvite7(); + + /** + * Request information on the selected player + */ + void slotInquire(); + + /** + * Copy the selected line to the clipboard + */ + void slotCopy(); + + /** + * Talk to the selected player + */ + void slotTalk(); + + /** + * Add the selected player to the gag list + */ + void slotGag(); + + /** + * Remove the selected player from the gag list + */ + void slotUngag(); + + /** + * Clear the gag list + */ + void slotCleargag(); + + /** + * Toggle everybody silent + */ + void slotSilent(); + + /** + * Slot for return pressed. Time to send the text to FIBS. + */ + void handleCommand(int id, const QString& msg); + +private: + + KBgChatPrivate *d; + +}; + +#endif // __KBGCHAT_H diff --git a/kbackgammon/engines/fibs/kbginvite.cpp b/kbackgammon/engines/fibs/kbginvite.cpp new file mode 100644 index 00000000..cb455f0a --- /dev/null +++ b/kbackgammon/engines/fibs/kbginvite.cpp @@ -0,0 +1,185 @@ +/* Yo Emacs, this -*- C++ -*- + + Copyright (C) 1999-2001 Jens Hoefkens + jens@hoefkens.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + $Id$ + +*/ + +#include "kbginvite.h" +#include "kbginvite.moc" + +#include <qlabel.h> +#include <qlayout.h> +#include <qframe.h> +#include <qspinbox.h> +#include <qstring.h> + +#include <klocale.h> +#include <klineedit.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +class KBgInvitePrivate { + +public: + + KLineEdit *mLe; + QSpinBox *mSb; + QPushButton *mInvite, *mResume, *mUnlimited, *mCancel, *mClose; + +}; + +/* + * Constructor is quite simple - most positioning is left to + * the toolkit. + */ +KBgInvite::KBgInvite(const char *name) + : KDialog(0, name, false) +{ + setCaption(i18n("Invite Players")); + + d = new KBgInvitePrivate(); + + QLabel *info = new QLabel(this); + + d->mLe = new KLineEdit(this, "invitation dialog"); + d->mSb = new QSpinBox(1, 999, 1, this, "spin box"); + + d->mInvite = new QPushButton(i18n("&Invite"), this); + d->mResume = new QPushButton(i18n("&Resume"), this); + d->mUnlimited = new QPushButton(i18n("&Unlimited"), this); + + d->mClose = new KPushButton(KStdGuiItem::close(), this); + d->mCancel = new KPushButton(KStdGuiItem::clear(), this); + + info->setText(i18n("Type the name of the player you want to invite in the first entry\n" + "field and select the desired match length in the spin box.")); + + QFrame *hLine = new QFrame(this); + hLine->setFrameStyle(QFrame::Sunken|QFrame::HLine); + + /* + * Set up layouts + */ + QBoxLayout *vbox = new QVBoxLayout(this); + + QBoxLayout *hbox_1 = new QHBoxLayout(vbox); + QBoxLayout *hbox_2 = new QHBoxLayout(vbox); + QBoxLayout *hbox_3 = new QHBoxLayout(vbox); + QBoxLayout *hbox_4 = new QHBoxLayout(vbox); + QBoxLayout *hbox_5 = new QHBoxLayout(vbox); + + hbox_1->addWidget(info); + + hbox_2->addWidget(d->mLe); + hbox_2->addWidget(d->mSb); + + hbox_3->addWidget(hLine); + + hbox_4->addWidget(d->mInvite); + hbox_4->addWidget(d->mResume); + hbox_4->addWidget(d->mUnlimited); + + hbox_5->addWidget(d->mClose); + hbox_5->addWidget(d->mCancel); + + /* + * Adjust widget sizes and resize the dialog + */ + KDialog::resizeLayout(this, marginHint(), spacingHint()); + setMinimumSize(childrenRect().size()); + vbox->activate(); + resize(minimumSize()); + + /* + * Set focus and default buttons + */ + d->mInvite->setDefault(true); + d->mInvite->setAutoDefault(true); + d->mLe->setFocus(); + + /* + * Connect the buttons + */ + connect(d->mUnlimited, SIGNAL(clicked()), SLOT(unlimitedClicked())); + connect(d->mResume, SIGNAL(clicked()), SLOT(resumeClicked())); + connect(d->mInvite, SIGNAL(clicked()), SLOT(inviteClicked())); + connect(d->mClose, SIGNAL(clicked()), SLOT(hide())); + connect(d->mCancel, SIGNAL(clicked()), SLOT(cancelClicked())); +} + +/* + * Destructor + */ +KBgInvite::~KBgInvite() +{ + delete d; +} + +/* + * After hiding, we tell our creator that we are ready to die. + */ +void KBgInvite::hide() +{ + emit dialogDone(); +} + +/* + * Set player name + */ +void KBgInvite::setPlayer(const QString &player) +{ + d->mLe->setText(player); +} + +/* + * Invitation with number + */ +void KBgInvite::inviteClicked() +{ + QString tmp; + emit inviteCommand(QString("invite ") + d->mLe->text() + " " + tmp.setNum(d->mSb->value())); +} + +/* + * Invitation for unlimited match + */ +void KBgInvite::unlimitedClicked() +{ + emit inviteCommand(QString("invite ") + d->mLe->text() + " unlimited"); +} + +/* + * Resume a game + */ +void KBgInvite::resumeClicked() +{ + emit inviteCommand(QString("invite ") + d->mLe->text()); +} + +/* + * Slot for Cancel. clear everything to default. + */ +void KBgInvite::cancelClicked() +{ + d->mSb->setValue(1); + d->mLe->clear(); +} + +// EOF diff --git a/kbackgammon/engines/fibs/kbginvite.h b/kbackgammon/engines/fibs/kbginvite.h new file mode 100644 index 00000000..992ee445 --- /dev/null +++ b/kbackgammon/engines/fibs/kbginvite.h @@ -0,0 +1,113 @@ +/* Yo Emacs, this -*- C++ -*- + + Copyright (C) 1999-2001 Jens Hoefkens + jens@hoefkens.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + $Id$ + +*/ + +#ifndef __KBGINVITE_H +#define __KBGINVITE_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <kdialog.h> + +class KBgInvitePrivate; + +/** + * + * This class implements a dialog for inviting players for games. It + * is quite simple (but follows the default style guide). The dialog + * offers specific numbers, unlimited and resume as invitation + * options. + * + * @short Simple dialog that allows to invite somebody on FIBS + * @author Jens Hoefkens <jens@hoefkens.com> + * + */ +class KBgInvite : public KDialog +{ + Q_OBJECT + +public: + + /** + * Constructor + */ + KBgInvite(const char *name = 0); + + /** + * Destructor + */ + virtual ~KBgInvite(); + +public slots: + + /** + * After hiding, we tell our creator that we are ready to die. + */ + virtual void hide(); + + /** + * Set the name of the player in the line editor + */ + void setPlayer(const QString &name); + +protected slots: + + /** + * Emits the FIBS invitation command if the Ok button was clicked. + */ + void inviteClicked(); + + /** + * Ask FIBS to resume a match + */ + void resumeClicked(); + + /** + * Ask FIBS for an unlimited match + */ + void unlimitedClicked(); + + /** + * Clear the entry field + */ + void cancelClicked(); + +signals: + + /** + * Emits the text of an invitation + */ + void inviteCommand(const QString &cmd); + + /** + * Delete the dialog after it is closed. + */ + void dialogDone(); + +private: + + KBgInvitePrivate *d; +}; + +#endif // __KBGINVITE_H diff --git a/kbackgammon/engines/fibs/kplayerlist.cpp b/kbackgammon/engines/fibs/kplayerlist.cpp new file mode 100644 index 00000000..102c354d --- /dev/null +++ b/kbackgammon/engines/fibs/kplayerlist.cpp @@ -0,0 +1,902 @@ +/* Yo Emacs, this -*- C++ -*- + + Copyright (C) 1999-2001 Jens Hoefkens + jens@hoefkens.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + $Id$ + +*/ + +#include "kplayerlist.moc" +#include "kplayerlist.h" + +#include <qlayout.h> +#include <qiconset.h> +#include <qgroupbox.h> +#include <qpopupmenu.h> +#include <qcheckbox.h> +#include <qlistview.h> +#include <qwhatsthis.h> +#include <qdatetime.h> +#include <qlabel.h> + +#include <kapplication.h> +#include <kconfig.h> +#include <kaction.h> +#include <kstdaction.h> +#include <ktabctl.h> +#include <kiconloader.h> +#include <kdebug.h> + +#include <string.h> +#include <stdio.h> + +#include "kbgfibs.h" +#include "version.h" + + +/* + * Simple container for information on columns of the list view. + * + * index : the internal index in the list + * width : width of the column in pixel + * show : whether the column is visible + * cb : check box for the setup dialog + */ +class KFibsPlayerListCI { + +public: + + int index, width; + bool show; + QCheckBox *cb; + QString key, name; + +}; + +/* + * Extension of the QListViewItem class that has a custom key function + * that can deal with the different items of the player list. + */ +class KFibsPlayerListLVI : public KListViewItem { + +public: + + /* + * Constructor + */ + KFibsPlayerListLVI(KFibsPlayerList *parent) : KListViewItem(parent) { _plist = parent; } + + /* + * Destructor + */ + virtual ~KFibsPlayerListLVI() {} + + /* + * Overloaded key function for sorting + */ + virtual QString key(int col, bool) const + { + int real_col = _plist->cIndex(col); + + QString s = text(col); + + switch (real_col) { + case KFibsPlayerList::Player: + case KFibsPlayerList::Opponent: + case KFibsPlayerList::Watches: + case KFibsPlayerList::Client: + case KFibsPlayerList::Email: + case KFibsPlayerList::Status: + case KFibsPlayerList::Host: + s = s.lower(); + break; + case KFibsPlayerList::Idle: + case KFibsPlayerList::Experience: + s.sprintf("%08d", s.toInt()); + break; + case KFibsPlayerList::Rating: + s.sprintf("%08d", (int)(1000*s.toDouble())); + break; + case KFibsPlayerList::Time: + s = s.lower(); + break; + default: + kdDebug(10500) << "KFibsPlayerListLVI::key(): illegal column" << endl; + break; + } + return s; + } + +private: + + KFibsPlayerList *_plist; + +}; + +/* + * Private data of the player list + */ +class KFibsPlayerListPrivate { + +public: + + /* + * Named constants for the popup menu actions + */ + enum MenuID {Info, Talk, Mail, InviteD, Invite1, Invite2, Invite3, Invite4, + Invite5, Invite6, Invite7, InviteR, InviteU, + Look, Watch, Unwatch, BlindAct, Update, Reload, Close, ActionEnd}; + + /* + * Various actions for the context menu + */ + KAction *mAct[ActionEnd]; + + /* + * All relevant information on the columns + */ + KFibsPlayerListCI *mCol[KFibsPlayerList::LVEnd]; + + /* + * Context menus for player related commands + */ + QPopupMenu *mPm[2]; + + /* + * ID of the invite menu in the context menu + */ + int mInID; + + /* + * Are we watching? + */ + bool mWatch; + + /* + * count similar clients - KFibs & kbackgammon + */ + int mCount[2]; + + /* + * Short abbreviations for Blind, Ready, and Away. + */ + QString mAbrv[KFibsPlayerList::MaxStatus]; + + /* + * Name of the last selected player - for internal purposes + */ + QString mUser; + + /* + * Our own name + */ + QString mName; + + /* + * Email address of the last selected player - for internal purposes + */ + QString mMail; + +}; + + +// == constructor, destructor and setup ======================================== + +/* + * Construct the playerlist and do some initial setup + */ +KFibsPlayerList::KFibsPlayerList(QWidget *parent, const char *name) + : KListView(parent, name) +{ + d = new KFibsPlayerListPrivate(); + KActionCollection* actions = new KActionCollection(this); + + /* + * Allocate the column information + */ + for (int i = 0; i < LVEnd; i++) + d->mCol[i] = new KFibsPlayerListCI(); + + /* + * Initialize variables + */ + d->mCol[Player]->name = i18n("Player"); + d->mCol[Opponent]->name = i18n("Opponent"); + d->mCol[Watches]->name = i18n("Watches"); + d->mCol[Status]->name = i18n("Status"); + d->mCol[Rating]->name = i18n("Rating"); + d->mCol[Experience]->name = i18n("Exp."); + d->mCol[Idle]->name = i18n("Idle"); + d->mCol[Time]->name = i18n("Time"); + d->mCol[Host]->name = i18n("Host name"); + d->mCol[Client]->name = i18n("Client"); + d->mCol[Email]->name = i18n("Email"); + + // These strings shouldn't be translated!! + d->mCol[Player]->key = "player"; + d->mCol[Opponent]->key = "opponent"; + d->mCol[Watches]->key = "watches"; + d->mCol[Status]->key = "status"; + d->mCol[Rating]->key = "rating"; + d->mCol[Experience]->key = "experience"; + d->mCol[Idle]->key = "idle"; + d->mCol[Time]->key = "time"; + d->mCol[Host]->key = "hostname"; + d->mCol[Client]->key = "client"; + d->mCol[Email]->key = "email"; + + d->mCount[0] = d->mCount[1] = 0; + + d->mAbrv[Blind] = i18n("abreviate blind", "B"); + d->mAbrv[Away ] = i18n("abreviate away", "A"); + d->mAbrv[Ready] = i18n("abreviate ready", "R"); + + d->mName = QString::null; + + d->mWatch = false; + + /* + * Get a sane caption, initialize some eye candy and read the + * configuration - needed for the column information. + */ + updateCaption(); + setIcon(kapp->miniIcon()); + QWhatsThis::add(this, i18n("This window contains the player list. It shows " + "all players that are currently logged into FIBS." + "Use the right mouse button to get a context " + "menu with helpful information and commands.")); + + readColumns(); + + /* + * Put the columns into the list view + */ + for (int i = 0; i < LVEnd; i++) { + if (d->mCol[i]->show) { + d->mCol[i]->index = addColumn(d->mCol[i]->name, d->mCol[i]->width); + if (i == Experience || i == Rating || i == Time || i == Idle) + setColumnAlignment(d->mCol[i]->index, AlignRight); + } else { + d->mCol[i]->index = -1; + } + } + setAllColumnsShowFocus(true); + + /* + * Create context menus + */ + d->mPm[0] = new QPopupMenu(); + d->mPm[1] = new QPopupMenu(); + + /* + * Create the whole set of actions + */ + d->mAct[KFibsPlayerListPrivate::Info] = new KAction(i18n("Info"), + QIconSet(kapp->iconLoader()->loadIcon + ("help.xpm", KIcon::Small)), + 0, this, SLOT(slotInfo()), actions); + d->mAct[KFibsPlayerListPrivate::Talk] = new KAction(i18n("Talk"), + QIconSet(kapp->iconLoader()->loadIcon + (PROG_NAME "-chat.png", KIcon::Small)), + 0, this, SLOT(slotTalk()), actions); + + d->mAct[KFibsPlayerListPrivate::Look] = new KAction(i18n("Look"), 0, this, SLOT(slotLook()), actions); + d->mAct[KFibsPlayerListPrivate::Watch] = new KAction(i18n("Watch"), 0, this, SLOT(slotWatch()), actions); + d->mAct[KFibsPlayerListPrivate::Unwatch] = new KAction(i18n("Unwatch"), 0, this, SLOT(slotUnwatch()),actions); + d->mAct[KFibsPlayerListPrivate::BlindAct] = new KAction(i18n("Blind"), 0, this, SLOT(slotBlind()), actions); + d->mAct[KFibsPlayerListPrivate::Update] = new KAction(i18n("Update"), 0, this, SLOT(slotUpdate()), actions); + + d->mAct[KFibsPlayerListPrivate::Reload] = KStdAction::redisplay(this, SLOT(slotReload()), actions); + d->mAct[KFibsPlayerListPrivate::Mail] = KStdAction::mail(this, SLOT(slotMail()), actions); + d->mAct[KFibsPlayerListPrivate::Close] = KStdAction::close(this, SLOT(hide()), actions); + + d->mAct[KFibsPlayerListPrivate::InviteD] = new KAction(i18n("Use Dialog"), 0, this, + SLOT(slotInviteD()), actions); + d->mAct[KFibsPlayerListPrivate::Invite1] = new KAction(i18n("1 Point Match"), 0, this, + SLOT(slotInvite1()), actions); + d->mAct[KFibsPlayerListPrivate::Invite2] = new KAction(i18n("2 Point Match"), 0, this, + SLOT(slotInvite2()), actions); + d->mAct[KFibsPlayerListPrivate::Invite3] = new KAction(i18n("3 Point Match"), 0, this, + SLOT(slotInvite3()), actions); + d->mAct[KFibsPlayerListPrivate::Invite4] = new KAction(i18n("4 Point Match"), 0, this, + SLOT(slotInvite4()), actions); + d->mAct[KFibsPlayerListPrivate::Invite5] = new KAction(i18n("5 Point Match"), 0, this, + SLOT(slotInvite5()), actions); + d->mAct[KFibsPlayerListPrivate::Invite6] = new KAction(i18n("6 Point Match"), 0, this, + SLOT(slotInvite6()), actions); + d->mAct[KFibsPlayerListPrivate::Invite7] = new KAction(i18n("7 Point Match"), 0, this, + SLOT(slotInvite7()), actions); + d->mAct[KFibsPlayerListPrivate::InviteU] = new KAction(i18n("Unlimited"), 0, this, + SLOT(slotInviteU()), actions); + d->mAct[KFibsPlayerListPrivate::InviteR] = new KAction(i18n("Resume"), 0, this, + SLOT(slotInviteR()), actions); + + /* + * Fill normal context menu + */ + d->mAct[KFibsPlayerListPrivate::Info]->plug(d->mPm[0]); + d->mAct[KFibsPlayerListPrivate::Talk]->plug(d->mPm[0]); + d->mAct[KFibsPlayerListPrivate::Mail]->plug(d->mPm[0]); + d->mPm[0]->insertSeparator(); + d->mInID = d->mPm[0]->insertItem(i18n("Invite"), d->mPm[1]); // save ID for later + d->mAct[KFibsPlayerListPrivate::Look ]->plug(d->mPm[0]); + d->mAct[KFibsPlayerListPrivate::Watch ]->plug(d->mPm[0]); + d->mAct[KFibsPlayerListPrivate::Unwatch ]->plug(d->mPm[0]); + d->mAct[KFibsPlayerListPrivate::BlindAct]->plug(d->mPm[0]); + d->mPm[0]->insertSeparator(); + d->mAct[KFibsPlayerListPrivate::Update]->plug(d->mPm[0]); + d->mAct[KFibsPlayerListPrivate::Reload]->plug(d->mPm[0]); + d->mPm[0]->insertSeparator(); + d->mAct[KFibsPlayerListPrivate::Close]->plug(d->mPm[0]); + + /* + * Fill the invitation menu + */ + d->mAct[KFibsPlayerListPrivate::InviteD]->plug(d->mPm[1]); + d->mPm[1]->insertSeparator(); + d->mAct[KFibsPlayerListPrivate::Invite1]->plug(d->mPm[1]); + d->mAct[KFibsPlayerListPrivate::Invite2]->plug(d->mPm[1]); + d->mAct[KFibsPlayerListPrivate::Invite3]->plug(d->mPm[1]); + d->mAct[KFibsPlayerListPrivate::Invite4]->plug(d->mPm[1]); + d->mAct[KFibsPlayerListPrivate::Invite5]->plug(d->mPm[1]); + d->mAct[KFibsPlayerListPrivate::Invite6]->plug(d->mPm[1]); + d->mAct[KFibsPlayerListPrivate::Invite7]->plug(d->mPm[1]); + d->mPm[1]->insertSeparator(); + d->mAct[KFibsPlayerListPrivate::InviteU]->plug(d->mPm[1]); + d->mAct[KFibsPlayerListPrivate::InviteR]->plug(d->mPm[1]); + + /* + * Right mouse button gets context menu, double click gets player info + */ + connect(this, SIGNAL(contextMenu(KListView *, QListViewItem *, const QPoint &)), + this, SLOT(showContextMenu(KListView *, QListViewItem *, const QPoint &))); + connect(this, SIGNAL(doubleClicked(QListViewItem *, const QPoint &, int)), + this, SLOT(getPlayerInfo(QListViewItem *, const QPoint &, int))); +} + +/* + * Destructor deletes members + */ +KFibsPlayerList::~KFibsPlayerList() +{ + for (int i = 0; i < LVEnd; i++) + delete d->mCol[i]; + delete d->mPm[0]; + delete d->mPm[1]; + delete d; +} + + +// == settings and config ====================================================== + +/* + * Called when the setup dialog is positively closed + */ +void KFibsPlayerList::setupOk() +{ + int i; + bool change = false; + + for (i = 1; i < LVEnd; i++) + change |= (d->mCol[i]->cb->isChecked() != d->mCol[i]->show); + + /* + * Only juggle with the columns if something changed + */ + if (change) { + + /* + * It's important to remove the columns in reverse order + */ + for (i = LVEnd-1; i > 0; i--) + if (d->mCol[i]->show) + removeColumn(d->mCol[i]->index); + + /* + * Now add all columns that are selected + */ + for (i = 1; i < LVEnd; i++) { + if ((d->mCol[i]->show = d->mCol[i]->cb->isChecked())) { + d->mCol[i]->index = addColumn(d->mCol[i]->name, d->mCol[i]->width); + if (i == Experience || i == Rating || i == Time || i == Idle) + setColumnAlignment(d->mCol[i]->index, AlignRight); + } else { + d->mCol[i]->index = -1; + } + } + + /* + * Reload the list + */ + slotReload(); + } + + /* + * store the new settings + */ + saveConfig(); + +} + +/* + * Setup dialog page of the player list - allow the user to select the + * columns to show + */ +void KFibsPlayerList::getSetupPages(KTabCtl *nb, int space) +{ + int i; + + /* + * Main Widget + */ + QWidget *w = new QWidget(nb); + QGridLayout *gl = new QGridLayout(w, 2, 1, space); + + /* + * Label + */ + QGroupBox *gbl = new QGroupBox(w); + gbl->setTitle(i18n("Column Selection")); + + gl->addWidget(gbl, 0, 0); + + /* + * Note that the first column (Player == 0) is always there + */ + QLabel *lb = new QLabel(i18n("Select all the columns that you would\n" + "like to be shown in the player list."), gbl); + + for (i = 1; i < LVEnd; i++) { + d->mCol[i]->cb = new QCheckBox(d->mCol[i]->name, gbl); + d->mCol[i]->cb->setChecked(d->mCol[i]->show); + } + + gl = new QGridLayout(gbl, LVEnd, 2, 20); + gl->addWidget(lb, 0, 0); + + // two column layout.... + for (i = 1; i < LVEnd/2; i++) { + gl->addWidget(d->mCol[2*i-1]->cb, i, 0); + gl->addWidget(d->mCol[2*i ]->cb, i, 1); + } + gl->addWidget(d->mCol[2*i-1]->cb, i, 0); + if (2*i < LVEnd) + gl->addWidget(d->mCol[2*i]->cb, i, 1); + + /* + * put in the page and connect + */ + nb->addTab(w, i18n("&Playerlist")); + + connect(nb, SIGNAL(applyButtonPressed()), this, SLOT(setupOk())); +} + +/* + * Nothing to cancel/undo + */ +void KFibsPlayerList::setupCancel() +{ + // do nothing +} + +/* + * By default all entries are checked + */ +void KFibsPlayerList::setupDefault() +{ + for (int i = 0; i < LVEnd; i++) + d->mCol[i]->cb->setChecked(true); +} + +/* + * Restore the columns + */ +void KFibsPlayerList::readColumns() +{ + KConfig* config = kapp->config(); + config->setGroup(name()); + + for (int i = 0; i < LVEnd; i++) { + d->mCol[i]->show = config->readBoolEntry("col-" + d->mCol[i]->key, true); + d->mCol[i]->width = config->readNumEntry("col-w-" + d->mCol[i]->key, -1); + } +} + +/* + * Restore the saved settings + */ +void KFibsPlayerList::readConfig() +{ + KConfig* config = kapp->config(); + config->setGroup(name()); + + QPoint pos, defpos(10, 10); + pos = config->readPointEntry("ori", &defpos); + setGeometry(pos.x(), pos.y(), config->readNumEntry("wdt",460), + config->readNumEntry("hgt",190)); + + (config->readBoolEntry("vis", false)) ? show() : hide(); + + readColumns(); +} + +/* + * Save current settings + */ +void KFibsPlayerList::saveConfig() +{ + KConfig* config = kapp->config(); + config->setGroup(name()); + + config->writeEntry("ori", pos()); + config->writeEntry("hgt", height()); + config->writeEntry("wdt", width()); + + config->writeEntry("vis", isVisible()); + + for (int i = 0; i < LVEnd; i++) { + config->writeEntry("col-" + d->mCol[i]->key, d->mCol[i]->show); + config->writeEntry("col-w-" + d->mCol[i]->key, + (d->mCol[i]->show) ? columnWidth(d->mCol[i]->index) : -1); + } +} + + +// == popup menu slots and functions =========================================== + +/* + * Save selected player, update the menu entries and show the popup menu + */ +void KFibsPlayerList::showContextMenu(KListView *, QListViewItem *i, const QPoint &p) +{ + /* + * Get the name of the selected player + */ + d->mUser = (i ? i->text(Player) : QString::null); + + d->mAct[KFibsPlayerListPrivate::Info ]->setText(i18n("Info on %1" ).arg(d->mUser)); + d->mAct[KFibsPlayerListPrivate::Talk ]->setText(i18n("Talk to %1" ).arg(d->mUser)); + d->mAct[KFibsPlayerListPrivate::Mail ]->setText(i18n("Email to %1").arg(d->mUser)); + d->mAct[KFibsPlayerListPrivate::Look ]->setText(i18n("Look at %1" ).arg(d->mUser)); + d->mAct[KFibsPlayerListPrivate::Watch ]->setText(i18n("Watch %1" ).arg(d->mUser)); + d->mAct[KFibsPlayerListPrivate::Update]->setText(i18n("Update %1" ).arg(d->mUser)); + + d->mAct[KFibsPlayerListPrivate::Info ]->setEnabled(i); + d->mAct[KFibsPlayerListPrivate::Talk ]->setEnabled(i); + d->mAct[KFibsPlayerListPrivate::Mail ]->setEnabled(i); + d->mAct[KFibsPlayerListPrivate::Look ]->setEnabled(i); + d->mAct[KFibsPlayerListPrivate::Watch ]->setEnabled(i); + d->mAct[KFibsPlayerListPrivate::Update ]->setEnabled(i); + d->mAct[KFibsPlayerListPrivate::BlindAct]->setEnabled(i); + + d->mAct[KFibsPlayerListPrivate::Unwatch]->setEnabled(d->mWatch); + + d->mPm[0]->setItemEnabled(d->mInID, i && d->mName != d->mUser); + d->mPm[0]->changeItem(d->mInID, i18n("Invite %1").arg(d->mUser)); + + d->mMail = (i && d->mCol[Email]->show ? i->text(d->mCol[Email]->index) : QString::null); + d->mAct[KFibsPlayerListPrivate::Mail]->setEnabled(!d->mMail.isEmpty()); + + if (i && d->mCol[Status]->show) + d->mAct[KFibsPlayerListPrivate::BlindAct]->setText + ((i->text(d->mCol[Status]->index).contains(d->mAbrv[Blind])) ? + i18n("Unblind %1").arg(d->mUser) : i18n("Blind %1").arg(d->mUser)); + else + d->mAct[KFibsPlayerListPrivate::BlindAct]->setText(i18n("Blind")); + + // show the menu + d->mPm[0]->popup(p); +} + +/* + * Reload the entire list + */ +void KFibsPlayerList::slotReload() +{ + emit fibsCommand("rawwho"); + clear(); +} + +/* + * Stop watching + */ +void KFibsPlayerList::slotUnwatch() +{ + emit fibsCommand("unwatch"); +} + +/* + * Blind/Unblind user + */ +void KFibsPlayerList::slotBlind() +{ + emit fibsCommand("blind " + d->mUser); +} + +/* + * Start talking to user + */ +void KFibsPlayerList::slotTalk() +{ + emit fibsTalk(d->mUser); +} + +/* + * Request information on user + */ +void KFibsPlayerList::slotInfo() +{ + emit fibsCommand("whois " + d->mUser); +} + +/* + * Look at user + */ +void KFibsPlayerList::slotLook() +{ + emit fibsCommand("look " + d->mUser); +} + +/* + * Send an email to player user at address email + */ +void KFibsPlayerList::slotMail() +{ + kapp->invokeMailer(d->mMail, QString::null); +} + +/* + * Request a new entry for user + */ +void KFibsPlayerList::slotUpdate() +{ + emit fibsCommand("rawwho " + d->mUser); +} + +/* + * Watch user and get an updated board + */ +void KFibsPlayerList::slotWatch() +{ + emit fibsCommand("watch " + d->mUser); + emit fibsCommand("board"); +} + +/* + * Request information about the selected user + */ +void KFibsPlayerList::getPlayerInfo(QListViewItem *i, const QPoint &, int col) +{ + int num = cIndex(col); + if (col < 0 || num < 0 || num > 2 || i->text(num).isEmpty()) + num = 0; + emit fibsCommand("whois " + i->text(num)); +} + +/* + * Invite the selected user. + */ +void KFibsPlayerList::slotInviteD() +{ + emit fibsInvite(d->mUser); +} +void KFibsPlayerList::slotInvite1() { emit fibsCommand("invite " + d->mUser + " 1"); } +void KFibsPlayerList::slotInvite2() { emit fibsCommand("invite " + d->mUser + " 2"); } +void KFibsPlayerList::slotInvite3() { emit fibsCommand("invite " + d->mUser + " 3"); } +void KFibsPlayerList::slotInvite4() { emit fibsCommand("invite " + d->mUser + " 4"); } +void KFibsPlayerList::slotInvite5() { emit fibsCommand("invite " + d->mUser + " 5"); } +void KFibsPlayerList::slotInvite6() { emit fibsCommand("invite " + d->mUser + " 6"); } +void KFibsPlayerList::slotInvite7() { emit fibsCommand("invite " + d->mUser + " 7"); } + +void KFibsPlayerList::slotInviteU() { emit fibsCommand("invite " + d->mUser + " unlimited"); } +void KFibsPlayerList::slotInviteR() { emit fibsCommand("invite " + d->mUser); } + + +// == inserting and updating the list ========================================== + +/* + * Add or change the entry of player with the corresponding string + * from the server - rawwho + */ +void KFibsPlayerList::changePlayer(const QString &line) +{ + char entry[LVEnd][100]; + char ready[2], away[2]; + QListViewItem *i; + QDateTime fromEpoch; + QString str_entry[LVEnd], tmp; + + entry[Status][0] = '\0'; + + // the line comes from FIBS and is 7 bit ASCII + sscanf(line.latin1(), "%99s %99s %99s %1s %1s %99s %99s %99s %99s %99s %99s %99s", entry[Player], entry[Opponent], + entry[Watches], ready, away, entry[Rating], entry[Experience], entry[Idle], entry[Time], + entry[Host], entry[Client], entry[Email]); + + // convert time + tmp = entry[Time]; + fromEpoch.setTime_t(tmp.toUInt()); + strcpy(entry[Time], fromEpoch.toString().latin1()); + + // clear empty strings and copy + for (int j = 0; j < LVEnd; j++) { + if ((str_entry[j] = entry[j]) == "-") + str_entry[j] = ""; + } + str_entry[Status].replace(Ready, 1, ready[0] == '0' ? "-" : d->mAbrv[Ready]); + str_entry[Status].replace(Away, 1, away [0] == '0' ? "-" : d->mAbrv[Away ]); + str_entry[Status].replace(Blind, 1, "-"); + + // disable drawing until the end of update + setUpdatesEnabled(false); + + // try to find the item in the list + QListViewItemIterator it(this); + for ( ; it.current(); ++it) { + if (it.current()->text(0) == str_entry[Player]) { + i = it.current(); + goto found; + } + } + + // getting here means we have to create a new entry + i = new KFibsPlayerListLVI(this); + + // count the KFibs and KBackgammon clients + if (str_entry[Client].contains("KFibs")) + d->mCount[0]++; + else if (str_entry[Client].contains(PROG_NAME)) + d->mCount[1]++; + + // new entry requires an update to the player count + updateCaption(); + + goto update; + + found: + + // getting here means the player is in the list - update private status + str_entry[Status].replace(Blind,1,i->text(Status).contains + (d->mAbrv[Blind]) ? d->mAbrv[Blind] : "-"); + + update: + + for (int j = 0; j < LVEnd; j++) { + if (d->mCol[j]->show) + i->setText(d->mCol[j]->index, str_entry[j]); + } + + // find out if we are watching somebody + if (d->mName == str_entry[Player]) + d->mWatch = !str_entry[Watches].isEmpty(); +} + +/* + * Remove player from the list + */ +void KFibsPlayerList::deletePlayer(const QString &player) +{ + QListViewItemIterator it(this); + for ( ; it.current(); ++it) { + if (it.current()->text(0) == player) { + if (it.current()->text(Client).contains(PROG_NAME)) + --d->mCount[1]; + else if (it.current()->text(Client).contains("KFibs")) + --d->mCount[0]; + delete it.current(); + updateCaption(); + return; + } + } +} + +/* + * Set/Unset the status stat in the corresponding column of the list + */ +void KFibsPlayerList::changePlayerStatus(const QString &player, int stat, bool flag) +{ + QListViewItem *i = 0; + + /* + * Find the correct line + */ + QListViewItemIterator it(this); + for ( ; it.current(); ++it) { + if (it.current()->text(Player) == player) { + i = it.current(); + break; + } + } + if (!i) return; + + /* + * Update the status flag + */ + i->setText(Status, i->text(Status).replace(stat, 1, (flag) ? d->mAbrv[stat] : "-")); +} + + +// == various slots and functions ============================================== + +/* + * Reverse column to index mapping. Return negative on error. + */ +int KFibsPlayerList::cIndex(int col) +{ + for (int i = 0; i < LVEnd; i++) + if (d->mCol[i]->index == col) + return i; + return -1; +} + +/* + * Catch hide events, so the engine's menu can be update. + */ +void KFibsPlayerList::showEvent(QShowEvent *e) +{ + KListView::showEvent(e); + emit windowVisible(true); +} + +/* + * Catch hide events, so the engine's menu can be update. + */ +void KFibsPlayerList::hideEvent(QHideEvent *e) +{ + emit windowVisible(false); + KListView::hideEvent(e); +} + +/* + * Called at the end of updates to re-enable the UI + */ +void KFibsPlayerList::stopUpdate() +{ + setUpdatesEnabled(true); + triggerUpdate(); +} + +/* + * Knowing our own name allows us to disable certain menu entries for + * ourselves. + */ +void KFibsPlayerList::setName(const QString &name) +{ + d->mName = name; +} + +/* + * Update the caption of the list by including the current client + * count + */ +void KFibsPlayerList::updateCaption() +{ + setCaption(i18n("Player List - %1 - %2/%3").arg(childCount()).arg(d->mCount[0]).arg(d->mCount[1])); +} + +/* + * Clear the list and reset the client counters + */ +void KFibsPlayerList::clear() +{ + d->mCount[0] = 0; + d->mCount[1] = 0; + QListView::clear(); +} + +// EOF diff --git a/kbackgammon/engines/fibs/kplayerlist.h b/kbackgammon/engines/fibs/kplayerlist.h new file mode 100644 index 00000000..701f9ace --- /dev/null +++ b/kbackgammon/engines/fibs/kplayerlist.h @@ -0,0 +1,298 @@ +/* Yo Emacs, this -*- C++ -*- + + Copyright (C) 1999-2001 Jens Hoefkens + jens@hoefkens.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + $Id$ + +*/ + +#ifndef __KPLAYERLIST_H +#define __KPLAYERLIST_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <klistview.h> + +class KTabCtl; +class KFibsPlayerListPrivate; + +/** + * + * A class that keeps track of players on the server. The server is flooding + * us with user information. At any given time we are able to have an current + * list of all loged-in players and their status. + * + * @short The FIBS backgammon engine player list + * @author Jens Hoefkens <jens@hoefkens.com> + * + */ +class KFibsPlayerList : public KListView +{ + Q_OBJECT + +public: + + /** + * Enumerate player status + */ + enum PStatus {Ready, Away, Blind, MaxStatus}; + + /** + * Enumerate the different columns of the list + */ + enum {Player, Opponent, Watches, Status, Rating, Experience, + Idle, Time, Host, Client, Email, LVEnd}; + + /** + * Constructor + */ + KFibsPlayerList(QWidget *parent = 0, const char *name = 0); + + /** + * Destructor + */ + virtual ~KFibsPlayerList(); + + /** + * Clear the list and reset the client counter + */ + virtual void clear(); + +public slots: + + /** + * Remove the player with the name given by the first word + */ + void deletePlayer(const QString &player); + + /** + * Change/Add the entry for the given player + */ + void changePlayer(const QString &line); + + /** + * Enables list redraws after an update + */ + void stopUpdate(); + + /** + * Read the UI settings from disk + */ + void readConfig(); + + /** + * Read the column info from disk + */ + void readColumns(); + + /** + * Restore settings from previously stored settings + */ + void saveConfig(); + + /** + * Change the status of a player + */ + void changePlayerStatus(const QString &player, int stat, bool flag); + + /** + * Fills the playerlist page into the notebook + */ + virtual void getSetupPages(KTabCtl *nb, int space); + + /** + * Save setting changes + */ + void setupOk(); + + /** + * Setup changes have been cancelled + */ + void setupCancel(); + + /** + * Set default settings + */ + void setupDefault(); + + /** + * Set our own name. This allows us to special case the context + * menu. + */ + void setName(const QString &name); + + /** + * Return the column index + */ + int cIndex(int col); + +protected: + + /** + * Catch show events, so the engine's menu can be update. + */ + virtual void showEvent(QShowEvent *e); + + /** + * Catch hide events, so the engine's menu can be update. + */ + virtual void hideEvent(QHideEvent *e); + +protected slots: + + /** + * Double click handler, requests information on a player + */ + void getPlayerInfo(QListViewItem *i, const QPoint &p, int col); + + /** + * Display a popup menu for the current player + */ + void showContextMenu(KListView *, QListViewItem *, const QPoint &); + + /** + * Reload the whole list + */ + void slotReload(); + + /** + * Upate the caption + */ + void updateCaption(); + + /** + * Watch user + */ + void slotWatch(); + + /** + * Update line of user + */ + void slotUpdate(); + + /** + * Request information on user + */ + void slotInfo(); + + /** + * Look at user + */ + void slotLook(); + + /** + * Send an email to user + */ + void slotMail(); + + /** + * Stop watching + */ + void slotUnwatch(); + + /** + * Blind user + */ + void slotBlind(); + + /** + * Talk to user + */ + void slotTalk(); + + /** + * Invite using the dialog + */ + void slotInviteD(); + + /** + * Invite to a 1 point match + */ + void slotInvite1(); + + /** + * Invite to a 2 point match + */ + void slotInvite2(); + + /** + * Invite to a 3 point match + */ + void slotInvite3(); + + /** + * Invite to a 4 point match + */ + void slotInvite4(); + + /** + * Invite to a 5 point match + */ + void slotInvite5(); + + /** + * Invite to a 6 point match + */ + void slotInvite6(); + + /** + * Invite to a 7 point match + */ + void slotInvite7(); + + /** + * Invite to resume a saved match + */ + void slotInviteR(); + + /** + * Invite to an unlimited match + */ + void slotInviteU(); + +signals: + + /** + * Send a command to the server + */ + void fibsCommand(const QString &); + + /** + * Initiate an invitation of a player + */ + void fibsInvite(const QString &); + + /** + * Request talking to player user + */ + void fibsTalk(const QString &); + + /** + * Allow the engine's menu to be updated + */ + void windowVisible(bool); + +private: + + KFibsPlayerListPrivate *d; + +}; + +#endif // __KPLAYERLIST_H |