diff --git a/cockatrice/cockatrice.pro b/cockatrice/cockatrice.pro index e24a899d..c3cc92a7 100644 --- a/cockatrice/cockatrice.pro +++ b/cockatrice/cockatrice.pro @@ -44,7 +44,8 @@ HEADERS += src/counter.h \ src/window_sets.h \ src/abstractgraphicsitem.h \ src/dlg_settings.h \ - src/phasestoolbar.h + src/phasestoolbar.h \ + src/chatwidget.h SOURCES += src/counter.cpp \ src/gameselector.cpp \ src/dlg_creategame.cpp \ @@ -82,5 +83,6 @@ SOURCES += src/counter.cpp \ src/window_sets.cpp \ src/abstractgraphicsitem.cpp \ src/dlg_settings.cpp \ - src/phasestoolbar.cpp + src/phasestoolbar.cpp \ + src/chatwidget.cpp TRANSLATIONS += translations/cockatrice_de.ts translations/cockatrice_en.ts diff --git a/cockatrice/src/cardinfowidget.cpp b/cockatrice/src/cardinfowidget.cpp index 6ebe620b..4d82c656 100644 --- a/cockatrice/src/cardinfowidget.cpp +++ b/cockatrice/src/cardinfowidget.cpp @@ -62,6 +62,7 @@ CardInfoWidget::CardInfoWidget(CardDatabase *_db, QWidget *parent) retranslateUi(); setFrameStyle(QFrame::Panel | QFrame::Raised); + textLabel->setFixedHeight(130); setFixedSize(sizeHint()); } diff --git a/cockatrice/src/chatwidget.cpp b/cockatrice/src/chatwidget.cpp new file mode 100644 index 00000000..d42e7ef1 --- /dev/null +++ b/cockatrice/src/chatwidget.cpp @@ -0,0 +1,250 @@ +#include +#include "chatwidget.h" +#include "client.h" + +ChannelWidget::ChannelWidget(Client *_client, const QString &_name, bool readOnly, bool _virtualChannel, QWidget *parent) + : QWidget(parent), client(_client), name(_name), virtualChannel(_virtualChannel) +{ + playerList = new QListWidget; + playerList->setFixedWidth(100); + + textEdit = new QTextEdit; + textEdit->setReadOnly(true); + if (!readOnly) { + sayEdit = new QLineEdit; + connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(sendMessage())); + } + + QVBoxLayout *vbox = new QVBoxLayout; + vbox->addWidget(textEdit); + if (!readOnly) + vbox->addWidget(sayEdit); + + QHBoxLayout *hbox = new QHBoxLayout; + hbox->addLayout(vbox); + hbox->addWidget(playerList); + + setLayout(hbox); +} + +ChannelWidget::~ChannelWidget() +{ + if (!virtualChannel) + client->chatLeaveChannel(name); +} + +void ChannelWidget::sendMessage() +{ + if (sayEdit->text().isEmpty()) + return; + client->chatSay(name, sayEdit->text()); + sayEdit->clear(); +} + +void ChannelWidget::joinEvent(const QString &playerName) +{ + textEdit->append(tr("%1 has joined the channel.").arg(playerName)); + playerList->addItem(playerName); +} + +void ChannelWidget::listPlayersEvent(const QString &playerName) +{ + playerList->addItem(playerName); +} + +void ChannelWidget::leaveEvent(const QString &playerName) +{ + textEdit->append(tr("%1 has left the channel.").arg(playerName)); + for (int i = 0; i < playerList->count(); ++i) + if (playerList->item(i)->text() == playerName) { + delete playerList->takeItem(i); + break; + } +} + +void ChannelWidget::sayEvent(const QString &playerName, const QString &s) +{ + textEdit->append(QString("%1: %2").arg(playerName).arg(s)); +} + +void ChannelWidget::serverMessageEvent(const QString &s) +{ + textEdit->append(QString("%1").arg(s)); +} + +ChatWidget::ChatWidget(Client *_client, QWidget *parent) + : QWidget(parent), client(_client) +{ + channelList = new QTreeWidget; + channelList->setRootIsDecorated(false); + channelList->setFixedWidth(200); + + joinButton = new QPushButton; + connect(joinButton, SIGNAL(clicked()), this, SLOT(joinClicked())); + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addStretch(); + buttonLayout->addWidget(joinButton); + QVBoxLayout *leftLayout = new QVBoxLayout; + leftLayout->addWidget(channelList); + leftLayout->addLayout(buttonLayout); + + tab = new QTabWidget; + + QHBoxLayout *hbox = new QHBoxLayout; + hbox->addLayout(leftLayout); + hbox->addWidget(tab, 1); + + retranslateUi(); + setLayout(hbox); +} + +void ChatWidget::retranslateUi() +{ + joinButton->setText(tr("Joi&n")); + + QTreeWidgetItem *header = channelList->headerItem(); + Q_ASSERT(header != 0); + header->setText(0, tr("Channel")); + header->setText(1, tr("Players")); + header->setTextAlignment(1, Qt::AlignRight); +} + +void ChatWidget::enableChat() +{ + connect(client, SIGNAL(chatEvent(const ChatEventData &)), this, SLOT(chatEvent(const ChatEventData &))); + client->chatListChannels(); + show(); +} + +void ChatWidget::disableChat() +{ + disconnect(client, 0, this, 0); + while (tab->count()) { + ChannelWidget *cw = qobject_cast(tab->widget(0)); + tab->removeTab(0); + delete cw; + } + channelList->clear(); + hide(); +} + +void ChatWidget::chatEvent(const ChatEventData &data) +{ + const QStringList &msg = data.getEventData(); + switch (data.getEventType()) { + case eventChatListChannels: { + if (msg.size() != 4) + break; + for (int i = 0; i < channelList->topLevelItemCount(); ++i) { + QTreeWidgetItem *twi = channelList->topLevelItem(i); + if (twi->text(0) == msg[0]) { + twi->setToolTip(0, msg[1]); + twi->setText(1, msg[2]); + return; + } + } + QTreeWidgetItem *twi = new QTreeWidgetItem(QStringList() << msg[0] << msg[2]); + twi->setTextAlignment(1, Qt::AlignRight); + twi->setToolTip(0, msg[1]); + channelList->addTopLevelItem(twi); + channelList->resizeColumnToContents(0); + channelList->resizeColumnToContents(1); + if (msg[3] == "1") + joinChannel(msg[0]); + break; + } + case eventChatJoinChannel: { + if (msg.size() != 2) + break; + ChannelWidget *w = getChannel(msg[0]); + if (!w) + break; + w->joinEvent(msg[1]); + break; + } + case eventChatListPlayers: { + if (msg.size() != 2) + break; + ChannelWidget *w = getChannel(msg[0]); + if (!w) + break; + w->listPlayersEvent(msg[1]); + break; + } + case eventChatLeaveChannel: { + if (msg.size() != 2) + break; + ChannelWidget *w = getChannel(msg[0]); + if (!w) + break; + w->leaveEvent(msg[1]); + break; + } + case eventChatSay: { + if (msg.size() != 3) + break; + ChannelWidget *w = getChannel(msg[0]); + if (!w) + break; + w->sayEvent(msg[1], msg[2]); + break; + } + case eventChatServerMessage: { + if (msg.size() != 2) + break; + ChannelWidget *w; + if (msg[0].isEmpty()) { + w = getChannel("Server"); + if (!w) { + w = new ChannelWidget(client, "Server", true, true); + tab->addTab(w, "Server"); + } + } else + w = getChannel(msg[0]); + w->serverMessageEvent(msg[1]); + break; + } + default: { + } + } +} + +void ChatWidget::joinChannel(const QString &channelName) +{ + PendingCommand *pc = client->chatJoinChannel(channelName); + pc->setExtraData(channelName); + connect(pc, SIGNAL(finished(ServerResponse)), this, SLOT(joinFinished(ServerResponse))); +} + +void ChatWidget::joinClicked() +{ + QTreeWidgetItem *twi = channelList->currentItem(); + if (!twi) + return; + QString channelName = twi->text(0); + if (getChannel(channelName)) + return; + + joinChannel(channelName); +} + +void ChatWidget::joinFinished(ServerResponse resp) +{ + if (resp != RespOk) + return; + + PendingCommand *pc = qobject_cast(sender()); + QString channelName = pc->getExtraData(); + ChannelWidget *cw = new ChannelWidget(client, channelName); + tab->addTab(cw, channelName); +} + +ChannelWidget *ChatWidget::getChannel(const QString &name) +{ + for (int i = 0; i < tab->count(); ++i) { + ChannelWidget *cw = qobject_cast(tab->widget(i)); + if (cw->getName() == name) + return cw; + } + return 0; +} diff --git a/cockatrice/src/chatwidget.h b/cockatrice/src/chatwidget.h new file mode 100644 index 00000000..c4075f6f --- /dev/null +++ b/cockatrice/src/chatwidget.h @@ -0,0 +1,59 @@ +#ifndef CHATWIDGET_H +#define CHATWIDGET_H + +#include +#include "servereventdata.h" +#include "client.h" + +class QListWidget; +class QTextEdit; +class QLineEdit; +class QTreeWidget; +class QTabWidget; +class QPushButton; + +class ChannelWidget : public QWidget { + Q_OBJECT +private: + QListWidget *playerList; + QTextEdit *textEdit; + QLineEdit *sayEdit; + Client *client; + QString name; + bool virtualChannel; +private slots: + void sendMessage(); +public: + ChannelWidget(Client *_client, const QString &_name, bool readOnly = false, bool _virtualChannel = false, QWidget *parent = 0); + ~ChannelWidget(); + const QString &getName() const { return name; } + + void joinEvent(const QString &playerName); + void listPlayersEvent(const QString &playerName); + void leaveEvent(const QString &playerName); + void sayEvent(const QString &playerName, const QString &s); + void serverMessageEvent(const QString &s); +}; + +class ChatWidget : public QWidget { + Q_OBJECT +private: + QTreeWidget *channelList; + QPushButton *joinButton; + QTabWidget *tab; + Client *client; + + ChannelWidget *getChannel(const QString &name); + void joinChannel(const QString &channelName); +private slots: + void chatEvent(const ChatEventData &data); + void joinClicked(); + void joinFinished(ServerResponse resp); +public: + ChatWidget(Client *_client, QWidget *parent = 0); + void retranslateUi(); + void enableChat(); + void disableChat(); +}; + +#endif diff --git a/cockatrice/src/client.cpp b/cockatrice/src/client.cpp index 1e05f3f7..cd8407c3 100644 --- a/cockatrice/src/client.cpp +++ b/cockatrice/src/client.cpp @@ -110,6 +110,8 @@ void Client::readLine() emit playerIdReceived(id, data[1]); } else emit gameEvent(event); + } else if (prefix == "chat") { + emit chatEvent(ChatEventData(line)); } else if (prefix == "resp") { if (values.size() != 2) { // XXX @@ -231,6 +233,26 @@ void Client::ping() cmd("ping"); } +PendingCommand *Client::chatListChannels() +{ + return cmd("chat_list_channels"); +} + +PendingCommand *Client::chatJoinChannel(const QString &name) +{ + return cmd(QString("chat_join_channel|%1").arg(name)); +} + +PendingCommand *Client::chatLeaveChannel(const QString &name) +{ + return cmd(QString("chat_leave_channel|%1").arg(name)); +} + +PendingCommand *Client::chatSay(const QString &channel, const QString &s) +{ + return cmd(QString("chat_say|%1|%2").arg(channel).arg(s)); +} + PendingCommand *Client::listGames() { return cmd("list_games"); diff --git a/cockatrice/src/client.h b/cockatrice/src/client.h index 8939fd38..9aac7c76 100644 --- a/cockatrice/src/client.h +++ b/cockatrice/src/client.h @@ -33,6 +33,7 @@ private: QString cmd; int msgid; int time; + QString extraData; signals: void finished(ServerResponse resp); void timeout(); @@ -42,6 +43,8 @@ public slots: public: int getMsgId() const { return msgid; } QString getCmd() const { return cmd; } + const QString &getExtraData() const { return extraData; } + void setExtraData(const QString &_extraData) { extraData = _extraData; } PendingCommand(const QString &_cmd, int _msgid, QObject *parent = 0); }; @@ -57,6 +60,7 @@ signals: void responseReceived(int msgid, ServerResponse resp); void playerIdReceived(int id, QString name); void gameEvent(const ServerEventData &msg); + void chatEvent(const ChatEventData &msg); void serverTimeout(); void logSocketError(const QString &errorString); void serverError(ServerResponse resp); @@ -90,6 +94,10 @@ public: void connectToServer(const QString &hostname, unsigned int port, const QString &_playerName, const QString &_password); void disconnectFromServer(); public slots: + PendingCommand *chatListChannels(); + PendingCommand *chatJoinChannel(const QString &name); + PendingCommand *chatLeaveChannel(const QString &name); + PendingCommand *chatSay(const QString &name, const QString &s); PendingCommand *listGames(); PendingCommand *listPlayers(); PendingCommand *createGame(const QString &description, const QString &password, unsigned int maxPlayers); diff --git a/cockatrice/src/game.cpp b/cockatrice/src/game.cpp index 12ba6aee..f3638983 100644 --- a/cockatrice/src/game.cpp +++ b/cockatrice/src/game.cpp @@ -300,9 +300,12 @@ void Game::gameEvent(const ServerEventData &msg) p->gameEvent(msg); break; } - case eventInvalid: + case eventInvalid: { qDebug("Unhandled global event"); } + default: { + } + } } } diff --git a/cockatrice/src/gameselector.cpp b/cockatrice/src/gameselector.cpp index 02891695..65c5c94e 100644 --- a/cockatrice/src/gameselector.cpp +++ b/cockatrice/src/gameselector.cpp @@ -9,8 +9,8 @@ GameSelector::GameSelector(Client *_client, QWidget *parent) gameListModel = new GamesModel(this); gameListView->setModel(gameListModel); - createButton = new QPushButton(tr("C&reate")); - joinButton = new QPushButton(tr("&Join")); + createButton = new QPushButton; + joinButton = new QPushButton; QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addStretch(); buttonLayout->addWidget(createButton); @@ -20,6 +20,7 @@ GameSelector::GameSelector(Client *_client, QWidget *parent) mainLayout->addWidget(gameListView); mainLayout->addLayout(buttonLayout); + retranslateUi(); setLayout(mainLayout); setMinimumWidth(gameListView->columnWidth(0) * gameListModel->columnCount()); @@ -27,18 +28,13 @@ GameSelector::GameSelector(Client *_client, QWidget *parent) connect(createButton, SIGNAL(clicked()), this, SLOT(actCreate())); connect(joinButton, SIGNAL(clicked()), this, SLOT(actJoin())); - - connect(client, SIGNAL(gameListEvent(ServerGame *)), gameListModel, SLOT(updateGameList(ServerGame *))); - connect(client, SIGNAL(statusChanged(ProtocolStatus)), this, SLOT(statusChanged(ProtocolStatus))); - - client->listGames(); } void GameSelector::actCreate() { DlgCreateGame dlg(client, this); if (dlg.exec()) - deleteLater(); + disableGameList(); } void GameSelector::actRefresh() @@ -46,19 +42,13 @@ void GameSelector::actRefresh() client->listGames(); } -void GameSelector::statusChanged(ProtocolStatus status) -{ - if (status == StatusDisconnected) - deleteLater(); -} - void GameSelector::checkResponse(ServerResponse response) { createButton->setEnabled(true); joinButton->setEnabled(true); if (response == RespOk) - deleteLater(); + disableGameList(); else { QMessageBox::critical(this, tr("Error"), tr("XXX")); return; @@ -84,3 +74,23 @@ void GameSelector::actJoin() createButton->setEnabled(false); joinButton->setEnabled(false); } + +void GameSelector::enableGameList() +{ + connect(client, SIGNAL(gameListEvent(ServerGame *)), gameListModel, SLOT(updateGameList(ServerGame *))); + client->listGames(); + show(); +} + +void GameSelector::disableGameList() +{ + disconnect(client, 0, this, 0); + hide(); + gameListModel->cleanList(); +} + +void GameSelector::retranslateUi() +{ + createButton->setText(tr("C&reate")); + joinButton->setText(tr("&Join")); +} \ No newline at end of file diff --git a/cockatrice/src/gameselector.h b/cockatrice/src/gameselector.h index 2d4b447c..fe3ac8cf 100644 --- a/cockatrice/src/gameselector.h +++ b/cockatrice/src/gameselector.h @@ -13,12 +13,14 @@ class GameSelector : public QWidget { Q_OBJECT public: GameSelector(Client *_client, QWidget *parent = 0); + void enableGameList(); + void disableGameList(); + void retranslateUi(); private slots: void actCreate(); void actRefresh(); void actJoin(); void checkResponse(ServerResponse response); - void statusChanged(ProtocolStatus status); private: Client *client; diff --git a/cockatrice/src/gamesmodel.cpp b/cockatrice/src/gamesmodel.cpp index cf542c44..848b6ce0 100644 --- a/cockatrice/src/gamesmodel.cpp +++ b/cockatrice/src/gamesmodel.cpp @@ -69,8 +69,10 @@ void GamesModel::updateGameList(ServerGame *game) void GamesModel::cleanList() { + beginRemoveRows(QModelIndex(), 0, gameList.size() - 1); QListIterator i(gameList); while (i.hasNext()) delete i.next(); gameList.clear(); + endRemoveRows(); } diff --git a/cockatrice/src/gamesmodel.h b/cockatrice/src/gamesmodel.h index 545939ba..ef352fb9 100644 --- a/cockatrice/src/gamesmodel.h +++ b/cockatrice/src/gamesmodel.h @@ -16,11 +16,11 @@ public: QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; ServerGame *getGame(int row); + void cleanList(); public slots: void updateGameList(ServerGame *game); private: QList gameList; - void cleanList(); }; #endif diff --git a/cockatrice/src/player.cpp b/cockatrice/src/player.cpp index 22c66602..36b039d0 100644 --- a/cockatrice/src/player.cpp +++ b/cockatrice/src/player.cpp @@ -64,6 +64,8 @@ Player::Player(const QString &_name, int _id, QPointF _base, bool _local, CardDa libraryMenu->addAction(aDrawCard); libraryMenu->addAction(aDrawCards); libraryMenu->addSeparator(); + libraryMenu->addAction(aShuffle); + libraryMenu->addSeparator(); libraryMenu->addAction(aViewLibrary); libraryMenu->addAction(aViewTopCards); zones.findZone("deck")->setMenu(libraryMenu, aDrawCard); diff --git a/cockatrice/src/servereventdata.cpp b/cockatrice/src/servereventdata.cpp index cf7ba9f4..324e90fe 100644 --- a/cockatrice/src/servereventdata.cpp +++ b/cockatrice/src/servereventdata.cpp @@ -3,49 +3,58 @@ // Message structure for server events: // {"private","public"}|PlayerId|PlayerName|EventType|EventData -const int event_count = 21; -const event_string event_strings[event_count] = { - {eventPlayerId, "player_id"}, - {eventSay, "say"}, - {eventName, "name"}, - {eventJoin, "join"}, - {eventLeave, "leave"}, - {eventReadyStart, "ready_start"}, - {eventSetupZones, "setup_zones"}, - {eventGameStart, "game_start"}, - {eventShuffle, "shuffle"}, - {eventRollDice, "roll_dice"}, - {eventDraw, "draw"}, - {eventMoveCard, "move_card"}, - {eventCreateToken, "create_token"}, - {eventSetCardAttr, "set_card_attr"}, - {eventAddCounter, "add_counter"}, - {eventSetCounter, "set_counter"}, - {eventDelCounter, "del_counter"}, - {eventSetActivePlayer, "set_active_player"}, - {eventSetActivePhase, "set_active_phase"}, - {eventDumpZone, "dump_zone"}, - {eventStopDumpZone, "stop_dump_zone"} -}; +QHash ServerEventData::eventHash; ServerEventData::ServerEventData(const QString &line) { - QStringList values = line.split("|"); + if (eventHash.isEmpty()) { + eventHash.insert("player_id", eventPlayerId); + eventHash.insert("say", eventSay); + eventHash.insert("name", eventName); + eventHash.insert("join", eventJoin); + eventHash.insert("leave", eventLeave); + eventHash.insert("ready_start", eventReadyStart); + eventHash.insert("setup_zones", eventSetupZones); + eventHash.insert("game_start", eventGameStart); + eventHash.insert("shuffle", eventShuffle); + eventHash.insert("roll_dice", eventRollDice); + eventHash.insert("draw", eventDraw); + eventHash.insert("move_card", eventMoveCard); + eventHash.insert("create_token", eventCreateToken); + eventHash.insert("set_card_attr", eventSetCardAttr); + eventHash.insert("add_counter", eventAddCounter); + eventHash.insert("set_counter", eventSetCounter); + eventHash.insert("del_counter", eventDelCounter); + eventHash.insert("set_active_player", eventSetActivePlayer); + eventHash.insert("set_active_phase", eventSetActivePhase); + eventHash.insert("dump_zone", eventDumpZone); + eventHash.insert("stop_dump_zone", eventStopDumpZone); + } + + QStringList values = line.split('|'); IsPublic = !values.takeFirst().compare("public"); PlayerId = values.takeFirst().toInt(); PlayerName = values.takeFirst(); - - QString type = values.takeFirst(); - bool found = false; - for (int i = 0; i < event_count; i++) - if (!type.compare(event_strings[i].str)) { - EventType = event_strings[i].type; - found = true; - break; - } - if (!found) - EventType = eventInvalid; - + EventType = eventHash.value(values.takeFirst(), eventInvalid); EventData = values; } + +QHash ChatEventData::eventHash; + +ChatEventData::ChatEventData(const QString &line) +{ + if (eventHash.isEmpty()) { + eventHash.insert("list_channels", eventChatListChannels); + eventHash.insert("join_channel", eventChatJoinChannel); + eventHash.insert("list_players", eventChatListPlayers); + eventHash.insert("leave_channel", eventChatLeaveChannel); + eventHash.insert("say", eventChatSay); + eventHash.insert("server_message", eventChatServerMessage); + } + + QStringList values = line.split('|'); + values.removeFirst(); + eventType = eventHash.value(values.takeFirst(), eventChatInvalid); + eventData = values; +} diff --git a/cockatrice/src/servereventdata.h b/cockatrice/src/servereventdata.h index f471fc22..9f809fcf 100644 --- a/cockatrice/src/servereventdata.h +++ b/cockatrice/src/servereventdata.h @@ -2,6 +2,7 @@ #define SERVEREVENTDATA_H #include +#include enum ServerEventType { eventInvalid, @@ -28,16 +29,10 @@ enum ServerEventType { eventStopDumpZone }; -struct event_string { - ServerEventType type; - char *str; -}; - -extern const int event_count; -extern const event_string event_strings[]; - class ServerEventData { private: + static QHash eventHash; + bool IsPublic; int PlayerId; QString PlayerName; @@ -47,9 +42,31 @@ public: ServerEventData(const QString &line); bool getPublic() const { return IsPublic; } int getPlayerId() const { return PlayerId; } - QString getPlayerName() const { return PlayerName; } + const QString &getPlayerName() const { return PlayerName; } ServerEventType getEventType() const { return EventType; } - QStringList getEventData() const { return EventData; } + const QStringList &getEventData() const { return EventData; } +}; + +enum ChatEventType { + eventChatInvalid, + eventChatListChannels, + eventChatJoinChannel, + eventChatListPlayers, + eventChatLeaveChannel, + eventChatSay, + eventChatServerMessage +}; + +class ChatEventData { +private: + static QHash eventHash; + + ChatEventType eventType; + QStringList eventData; +public: + ChatEventData(const QString &line); + ChatEventType getEventType() const { return eventType; } + const QStringList &getEventData() const { return eventData; } }; #endif diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp index fc7696d2..b9d76deb 100644 --- a/cockatrice/src/window_main.cpp +++ b/cockatrice/src/window_main.cpp @@ -35,6 +35,7 @@ #include "zoneviewzone.h" #include "zoneviewwidget.h" #include "zoneviewlayout.h" +#include "chatwidget.h" void MainWindow::hoverCard(QString name) { @@ -69,7 +70,9 @@ void MainWindow::statusChanged(ProtocolStatus _status) aRestartGame->setEnabled(false); aLeaveGame->setEnabled(false); phasesToolbar->setActivePhase(-1); - phasesToolbar->setEnabled(false); + phasesToolbar->hide(); + gameSelector->disableGameList(); + chatWidget->disableChat(); emit logDisconnected(); break; case StatusLoggingIn: @@ -84,14 +87,17 @@ void MainWindow::statusChanged(ProtocolStatus _status) aRestartGame->setEnabled(false); aLeaveGame->setEnabled(false); phasesToolbar->setActivePhase(-1); - phasesToolbar->setEnabled(false); + phasesToolbar->hide(); - GameSelector *gameSelector = new GameSelector(client); - viewLayout->insertWidget(0, gameSelector); + view->hide(); + gameSelector->enableGameList(); + chatWidget->enableChat(); break; } case StatusPlaying: - phasesToolbar->setEnabled(true); + chatWidget->disableChat(); + phasesToolbar->show(); + view->show(); break; default: break; @@ -211,6 +217,8 @@ void MainWindow::retranslateUi() sayLabel->setText(tr("&Say:")); cardInfo->retranslateUi(); + chatWidget->retranslateUi(); + gameSelector->retranslateUi(); } void MainWindow::createActions() @@ -272,6 +280,7 @@ MainWindow::MainWindow(QTranslator *_translator, QWidget *parent) scene = new QGraphicsScene(0, 0, 1096, 1160, this); view = new GameView(scene); + view->hide(); // view->setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers))); @@ -285,6 +294,12 @@ MainWindow::MainWindow(QTranslator *_translator, QWidget *parent) sayLabel = new QLabel; sayEdit = new QLineEdit; sayLabel->setBuddy(sayEdit); + + client = new Client(this); + gameSelector = new GameSelector(client); + gameSelector->hide(); + chatWidget = new ChatWidget(client); + chatWidget->hide(); QHBoxLayout *hLayout = new QHBoxLayout; hLayout->addWidget(sayLabel); @@ -296,10 +311,12 @@ MainWindow::MainWindow(QTranslator *_translator, QWidget *parent) verticalLayout->addLayout(hLayout); viewLayout = new QVBoxLayout; + viewLayout->addWidget(gameSelector); + viewLayout->addWidget(chatWidget); viewLayout->addWidget(view); phasesToolbar = new PhasesToolbar; - phasesToolbar->setEnabled(false); + phasesToolbar->hide(); QHBoxLayout *mainLayout = new QHBoxLayout; mainLayout->addWidget(phasesToolbar); @@ -312,7 +329,6 @@ MainWindow::MainWindow(QTranslator *_translator, QWidget *parent) connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(actSay())); - client = new Client(this); connect(client, SIGNAL(serverTimeout()), this, SLOT(serverTimeout())); connect(client, SIGNAL(statusChanged(ProtocolStatus)), this, SLOT(statusChanged(ProtocolStatus))); connect(client, SIGNAL(playerIdReceived(int, QString)), this, SLOT(playerIdReceived(int, QString))); @@ -334,6 +350,8 @@ MainWindow::MainWindow(QTranslator *_translator, QWidget *parent) void MainWindow::closeEvent(QCloseEvent */*event*/) { delete game; + chatWidget->disableChat(); + gameSelector->disableGameList(); } void MainWindow::changeEvent(QEvent *event) diff --git a/cockatrice/src/window_main.h b/cockatrice/src/window_main.h index 460ca2bc..39f821e5 100644 --- a/cockatrice/src/window_main.h +++ b/cockatrice/src/window_main.h @@ -40,6 +40,8 @@ class ServerZoneCard; class ZoneViewLayout; class ZoneViewWidget; class PhasesToolbar; +class GameSelector; +class ChatWidget; class MainWindow : public QMainWindow { Q_OBJECT @@ -80,6 +82,8 @@ private: QLabel *sayLabel; QLineEdit *sayEdit; PhasesToolbar *phasesToolbar; + GameSelector *gameSelector; + ChatWidget *chatWidget; Client *client; QGraphicsScene *scene; diff --git a/cockatrice/translations/cockatrice_de.ts b/cockatrice/translations/cockatrice_de.ts index 8d0dc78e..834020f2 100644 --- a/cockatrice/translations/cockatrice_de.ts +++ b/cockatrice/translations/cockatrice_de.ts @@ -1,6 +1,6 @@ - - + + @@ -55,22 +55,22 @@ CardInfoWidget - + Name: Name: - + Mana cost: Manakosten: - + Card type: Kartentyp: - + P / T: S/W: @@ -83,6 +83,41 @@ Das Kartenhintergrundbild konnte nicht geladen werden. + + ChannelWidget + + + %1 has joined the channel. + %1 hat den Raum betreten. + + + + %1 has left the channel. + %1 hat den Raum verlassen. + + + + ChatWidget + + + Joi&n + Teil&nehmen + + + + Channel + Raum + + + Description + Beschreibung + + + + Players + Spielerzahl + + DeckList @@ -185,6 +220,7 @@ Spiel erstellen + Error Fehler @@ -316,32 +352,26 @@ Ctrl+L - &Shuffle Mi&schen - Ctrl+S Ctrl+S - &Draw a card Karte &ziehen - Ctrl+D Ctrl+D - D&raw cards... Ka&rten ziehen... - Ctrl+E Ctrl+E @@ -494,47 +524,46 @@ F10 - + Set life Setze Leben - + New life total: Neues Leben insgesammt: - + Roll dice Würfeln - + Number of sides: Anzahl der Seiten: - Draw cards Karten ziehen - + Number: Anzahl: - + Create token Token erstellen - + Name: Name: - + Set counters Setze Zählmarke @@ -542,32 +571,32 @@ GameSelector - + C&reate Spiel e&rstellen - + &Join &Teilnehmen - + Error Fehler - + XXX XXX - + Join game Spiel beitreten - + Password: Passwort: @@ -613,6 +642,9 @@ GeneralSettingsPage + + + Choose path Pfad auswählen @@ -653,6 +685,7 @@ Pfad zur Kartendatenbank: + English Deutsch @@ -661,97 +694,97 @@ MainWindow - + Error Fehler - + Server timeout Server Zeitüberschreitung - + &Connect... &Verbinden... - + &Disconnect Verbindung &trennen - + &Restart game... Spiel neu sta&rten... - + F2 F2 - + &Leave game Spiel ver&lassen - + &Deck editor &Deck-Editor - + &Full screen &Vollbild - + Ctrl+F Ctrl+F - + &Settings... &Einstellungen... - + &Exit &Beenden - + Close most recent zone view Letzte Zonenansicht schließen - + Esc Esc - + &Game Spi&el - + &Actions &Aktionen - + &Card &Karte - + &Say: &Sagen: - + Cockatrice Cockatrice @@ -1208,27 +1241,27 @@ Bib&liothek - + &Graveyard &Friedhof - + &Removed cards Entfe&rnte Karten - + &Sideboard &Sideboard - + View top cards of library Zeige die obersten Karten der Bibliothek - + Number of cards: Anzahl der Karten: @@ -1263,12 +1296,12 @@ Ctrl+S - + Draw cards Karten ziehen - + Number: Anzahl: @@ -1286,12 +1319,12 @@ Sideboard - + Cockatrice decks (*.cod) Cockatrice Decks (*.cod) - + Plain text decks (*.dec *.mwDeck) Text Decks (*.dec *.mwDeck) diff --git a/cockatrice/translations/cockatrice_en.ts b/cockatrice/translations/cockatrice_en.ts index 7d86c9a1..c021d286 100644 --- a/cockatrice/translations/cockatrice_en.ts +++ b/cockatrice/translations/cockatrice_en.ts @@ -1,5 +1,6 @@ - + + CardDatabaseModel @@ -31,26 +32,57 @@ CardInfoWidget - + Name: - + Mana cost: - + Card type: - + P / T: + + ChannelWidget + + + %1 has joined the channel. + + + + + %1 has left the channel. + + + + + ChatWidget + + + Joi&n + + + + + Channel + + + + + Players + + + DeckList @@ -153,6 +185,7 @@ + Error @@ -397,42 +430,42 @@ - + Set life - + New life total: - + Roll dice - + Number of sides: - + Number: - + Create token - + Name: - + Set counters @@ -440,32 +473,32 @@ GameSelector - + C&reate - + &Join - + Error - + XXX - + Join game - + Password: @@ -511,6 +544,9 @@ GeneralSettingsPage + + + Choose path @@ -551,6 +587,7 @@ + English English @@ -559,97 +596,97 @@ MainWindow - + Error - + Server timeout - + &Connect... - + &Disconnect - + &Restart game... - + F2 - + &Leave game - + &Deck editor - + &Full screen - + Ctrl+F - + &Settings... - + &Exit - + Close most recent zone view - + Esc - + &Game - + &Actions - + &Card - + &Say: - + Cockatrice @@ -1086,27 +1123,27 @@ - + &Graveyard - + &Removed cards - + &Sideboard - + View top cards of library - + Number of cards: @@ -1141,12 +1178,12 @@ - + Draw cards - + Number: @@ -1164,12 +1201,12 @@ - + Cockatrice decks (*.cod) - + Plain text decks (*.dec *.mwDeck) diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example index 632c525d..5a36553c 100644 --- a/servatrice/servatrice.ini.example +++ b/servatrice/servatrice.ini.example @@ -7,3 +7,14 @@ hostname=localhost database=servatrice user=servatrice password=foobar + +[messages] +login="Example line 1", "Example line 2" + +[chatchannels] +size=1 +1\name="General Chat" +1\description="Discuss anything here." +1\autojoin=true +1\joinmessage="This is the general chat channel.", "This message is only here to show that channels can have a join message." + diff --git a/servatrice/servatrice.pro b/servatrice/servatrice.pro index 626dc610..120d7a42 100755 --- a/servatrice/servatrice.pro +++ b/servatrice/servatrice.pro @@ -9,7 +9,7 @@ INCLUDEPATH += . src MOC_DIR = build OBJECTS_DIR = build -# CONFIG += qt debug +CONFIG += qt debug QT += network sql QT -= gui @@ -21,7 +21,8 @@ HEADERS += src/server.h src/servergame.h src/serversocket.h \ src/counter.h \ src/abstractrng.h \ src/rng_qt.h \ - src/returnmessage.h + src/returnmessage.h \ + src/chatchannel.h SOURCES += src/main.cpp \ src/server.cpp \ src/servergame.cpp \ @@ -30,4 +31,5 @@ SOURCES += src/main.cpp \ src/card.cpp \ src/counter.cpp \ src/rng_qt.cpp \ - src/returnmessage.cpp + src/returnmessage.cpp \ + src/chatchannel.cpp diff --git a/servatrice/src/chatchannel.cpp b/servatrice/src/chatchannel.cpp new file mode 100644 index 00000000..75d8dad6 --- /dev/null +++ b/servatrice/src/chatchannel.cpp @@ -0,0 +1,46 @@ +#include "chatchannel.h" +#include "serversocket.h" + +ChatChannel::ChatChannel(const QString &_name, const QString &_description, bool _autoJoin, const QStringList &_joinMessage) + : name(_name), description(_description), autoJoin(_autoJoin), joinMessage(_joinMessage) +{ +} + +void ChatChannel::addPlayer(ServerSocket *player) +{ + QString str = QString("chat|join_channel|%1|%2").arg(name).arg(player->getPlayerName()); + for (int i = 0; i < size(); ++i) + at(i)->msg(str); + + append(player); + + for (int i = 0; i < size(); ++i) + player->msg(QString("chat|list_players|%1|%2").arg(name).arg(at(i)->getPlayerName())); + for (int i = 0; i < joinMessage.size(); ++i) + player->msg(QString("chat|server_message|%1|%2").arg(name).arg(joinMessage[i])); + + emit channelInfoChanged(); +} + +void ChatChannel::removePlayer(ServerSocket *player) +{ + QString str = QString("chat|leave_channel|%1|%2").arg(name).arg(player->getPlayerName()); + + removeAt(indexOf(player)); + for (int i = 0; i < size(); ++i) + at(i)->msg(str); + + emit channelInfoChanged(); +} + +void ChatChannel::say(ServerSocket *player, const QString &s) +{ + QString str = QString("chat|say|%1|%2|%3").arg(name).arg(player->getPlayerName()).arg(s); + for (int i = 0; i < size(); ++i) + at(i)->msg(str); +} + +QString ChatChannel::getChannelListLine() const +{ + return QString("chat|list_channels|%1|%2|%3|%4").arg(name).arg(description).arg(size()).arg(autoJoin ? 1 : 0); +} diff --git a/servatrice/src/chatchannel.h b/servatrice/src/chatchannel.h new file mode 100644 index 00000000..c7ca5b76 --- /dev/null +++ b/servatrice/src/chatchannel.h @@ -0,0 +1,30 @@ +#ifndef CHATCHANNEL_H +#define CHATCHANNEL_H + +#include +#include +#include + +class ServerSocket; + +class ChatChannel : public QObject, public QList { + Q_OBJECT +signals: + void channelInfoChanged(); +private: + QString name; + QString description; + bool autoJoin; + QStringList joinMessage; +public: + ChatChannel(const QString &_name, const QString &_description, bool _autoJoin, const QStringList &_joinMessage); + QString getName() const { return name; } + QString getDescription() const { return description; } + bool getAutoJoin() const { return autoJoin; } + void addPlayer(ServerSocket *player); + void removePlayer(ServerSocket *player); + void say(ServerSocket *player, const QString &s); + QString getChannelListLine() const; +}; + +#endif diff --git a/servatrice/src/returnmessage.cpp b/servatrice/src/returnmessage.cpp index 210f2644..14d1eca3 100644 --- a/servatrice/src/returnmessage.cpp +++ b/servatrice/src/returnmessage.cpp @@ -10,6 +10,7 @@ bool ReturnMessage::send(ReturnCode code) switch (code) { case ReturnNothing: return true; case ReturnOk: returnCodeString = "ok"; break; + case ReturnNameNotFound: returnCodeString = "name_not_found"; break; case ReturnLoginNeeded: returnCodeString = "login_needed"; break; case ReturnSyntaxError: returnCodeString = "syntax"; break; case ReturnContextError: returnCodeString = "context"; break; diff --git a/servatrice/src/returnmessage.h b/servatrice/src/returnmessage.h index 37837bca..58a464a1 100644 --- a/servatrice/src/returnmessage.h +++ b/servatrice/src/returnmessage.h @@ -9,7 +9,7 @@ private: unsigned int msg_id; QString cmd; public: - enum ReturnCode { ReturnNothing, ReturnOk, ReturnLoginNeeded, ReturnSyntaxError, ReturnContextError, ReturnPasswordWrong }; + enum ReturnCode { ReturnNothing, ReturnOk, ReturnNameNotFound, ReturnLoginNeeded, ReturnSyntaxError, ReturnContextError, ReturnPasswordWrong }; ReturnMessage(QObject *parent = 0) : QObject(parent), msg_id(0) { } unsigned int getMsgId() const { return msg_id; } void setMsgId(unsigned int _msg_id) { msg_id = _msg_id; } diff --git a/servatrice/src/server.cpp b/servatrice/src/server.cpp index b8aa8c9a..990503cd 100644 --- a/servatrice/src/server.cpp +++ b/servatrice/src/server.cpp @@ -22,11 +22,12 @@ #include "serversocket.h" #include "counter.h" #include "rng_qt.h" +#include "chatchannel.h" #include #include Server::Server(QObject *parent) - : QTcpServer(parent), nextGameId(0) + : QTcpServer(parent), nextGameId(0) { rng = new RNG_Qt(this); @@ -35,6 +36,21 @@ Server::Server(QObject *parent) QString dbType = settings->value("database/type").toString(); if (dbType == "mysql") openDatabase(); + + int size = settings->beginReadArray("chatchannels"); + for (int i = 0; i < size; ++i) { + settings->setArrayIndex(i); + chatChannelList << new ChatChannel(settings->value("name").toString(), + settings->value("description").toString(), + settings->value("autojoin").toBool(), + settings->value("joinmessage").toStringList()); + } + settings->endArray(); + + for (int i = 0; i < chatChannelList.size(); ++i) + connect(chatChannelList[i], SIGNAL(channelInfoChanged()), this, SLOT(broadcastChannelUpdate())); + + loginMessage = settings->value("messages/login").toStringList(); } Server::~Server() @@ -159,6 +175,14 @@ void Server::broadcastGameListUpdate(ServerGame *game) players[i]->msg(line); } +void Server::broadcastChannelUpdate() +{ + QString line = qobject_cast(sender())->getChannelListLine(); + for (int i = 0; i < players.size(); ++i) + if (players[i]->getAcceptsChatChannelListChanges()) + players[i]->msg(line); +} + void Server::addClientToGame(int gameId, ServerSocket *client) { ServerGame *game = getGame(gameId); diff --git a/servatrice/src/server.h b/servatrice/src/server.h index 467f62e9..bccdb292 100644 --- a/servatrice/src/server.h +++ b/servatrice/src/server.h @@ -21,12 +21,14 @@ #define SERVER_H #include +#include class ServerGame; class ServerSocket; class QSqlDatabase; class QSettings; class AbstractRNG; +class ChatChannel; enum AuthenticationResult { PasswordWrong = 0, PasswordRight = 1, UnknownUser = 2 }; @@ -37,6 +39,7 @@ private slots: void addGame(const QString description, const QString password, const int maxPlayers, ServerSocket *creator); void addClientToGame(int gameId, ServerSocket *client); void gameClosing(); + void broadcastChannelUpdate(); public: Server(QObject *parent = 0); ~Server(); @@ -45,15 +48,19 @@ public: bool checkGamePassword(int gameId, const QString &password); AuthenticationResult checkUserPassword(const QString &user, const QString &password); QList listOpenGames(); + QList getChatChannelList() { return chatChannelList; } ServerGame *getGame(int gameId); AbstractRNG *getRNG() const { return rng; } void broadcastGameListUpdate(ServerGame *game); void removePlayer(ServerSocket *player); + const QStringList &getLoginMessage() const { return loginMessage; } private: void incomingConnection(int SocketId); QList games; QList players; + QList chatChannelList; int nextGameId; + QStringList loginMessage; AbstractRNG *rng; }; diff --git a/servatrice/src/serversocket.cpp b/servatrice/src/serversocket.cpp index d16157e0..c4a07a4b 100644 --- a/servatrice/src/serversocket.cpp +++ b/servatrice/src/serversocket.cpp @@ -28,6 +28,7 @@ #include "counter.h" #include "card.h" #include "abstractrng.h" +#include "chatchannel.h" ServerSocket::ServerSocket(Server *_server, QObject *parent) : QTcpSocket(parent), server(_server), game(0), PlayerStatus(StatusNormal), authState(PasswordWrong), acceptsGameListChanges(false) @@ -47,6 +48,8 @@ ServerSocket::~ServerSocket() server->removePlayer(this); if (game) game->removePlayer(this); + for (int i = 0; i < chatChannels.size(); ++i) + chatChannels[i]->removePlayer(this); } int ServerSocket::newCardId() @@ -166,6 +169,11 @@ const ServerSocket::CommandProperties ServerSocket::commandList[ServerSocket::nu {"ping", false, false, false, QList(), &ServerSocket::cmdPing}, {"login", false, false, false, QList() << QVariant::String << QVariant::String, &ServerSocket::cmdLogin}, + {"chat_list_channels", true, false, false, QList(), &ServerSocket::cmdChatListChannels}, + {"chat_join_channel", true, false, false, QList() << QVariant::String, &ServerSocket::cmdChatJoinChannel}, + {"chat_leave_channel", true, false, false, QList() << QVariant::String, &ServerSocket::cmdChatLeaveChannel}, + {"chat_say", true, false, false, QList() << QVariant::String + << QVariant::String, &ServerSocket::cmdChatSay}, {"list_games", true, false, false, QList(), &ServerSocket::cmdListGames}, {"create_game", true, false, false, QList() << QVariant::String << QVariant::String @@ -228,9 +236,65 @@ ReturnMessage::ReturnCode ServerSocket::cmdLogin(const QList ¶ms) return ReturnMessage::ReturnPasswordWrong; playerName = params[0].toString(); + remsg->send(ReturnMessage::ReturnOk); + + QStringList loginMessage = server->getLoginMessage(); + for (int i = 0; i < loginMessage.size(); ++i) + msg("chat|server_message||" + loginMessage[i]); + + return ReturnMessage::ReturnNothing; +} + +ReturnMessage::ReturnCode ServerSocket::cmdChatListChannels(const QList &/*params*/) +{ + QList chatChannelList = server->getChatChannelList(); + for (int i = 0; i < chatChannelList.size(); ++i) + msg(chatChannelList[i]->getChannelListLine()); + + acceptsChatChannelListChanges = true; return ReturnMessage::ReturnOk; } +ReturnMessage::ReturnCode ServerSocket::cmdChatJoinChannel(const QList ¶ms) +{ + for (int i = 0; i < chatChannels.size(); ++i) + if (chatChannels[i]->getName() == params[0]) + return ReturnMessage::ReturnContextError; + + QList allChannels = server->getChatChannelList(); + for (int i = 0; i < allChannels.size(); ++i) + if (allChannels[i]->getName() == params[0]) { + remsg->send(ReturnMessage::ReturnOk); + allChannels[i]->addPlayer(this); + chatChannels << allChannels[i]; + return ReturnMessage::ReturnNothing; + } + return ReturnMessage::ReturnNameNotFound; +} + +ReturnMessage::ReturnCode ServerSocket::cmdChatLeaveChannel(const QList ¶ms) +{ + for (int i = 0; i < chatChannels.size(); ++i) { + ChatChannel *c = chatChannels[i]; + if (c->getName() == params[0]) { + chatChannels.removeAt(i); + c->removePlayer(this); + return ReturnMessage::ReturnOk; + } + } + return ReturnMessage::ReturnNameNotFound; +} + +ReturnMessage::ReturnCode ServerSocket::cmdChatSay(const QList ¶ms) +{ + for (int i = 0; i < chatChannels.size(); ++i) + if (chatChannels[i]->getName() == params[0]) { + chatChannels[i]->say(this, params[1].toString()); + return ReturnMessage::ReturnOk; + } + return ReturnMessage::ReturnNameNotFound; +} + ReturnMessage::ReturnCode ServerSocket::cmdListGames(const QList &/*params*/) { QList gameList = server->listOpenGames(); @@ -248,6 +312,7 @@ ReturnMessage::ReturnCode ServerSocket::cmdCreateGame(const QList &par QString password = params[1].toString(); int maxPlayers = params[2].toInt(); acceptsGameListChanges = false; + acceptsChatChannelListChanges = false; leaveGame(); emit createGame(description, password, maxPlayers, this); return ReturnMessage::ReturnOk; @@ -260,6 +325,7 @@ ReturnMessage::ReturnCode ServerSocket::cmdJoinGame(const QList ¶m if (!server->checkGamePassword(gameId, password)) return ReturnMessage::ReturnPasswordWrong; acceptsGameListChanges = false; + acceptsChatChannelListChanges = false; leaveGame(); emit joinGame(gameId, this); return ReturnMessage::ReturnOk; diff --git a/servatrice/src/serversocket.h b/servatrice/src/serversocket.h index 1b7b1874..08b1404b 100644 --- a/servatrice/src/serversocket.h +++ b/servatrice/src/serversocket.h @@ -55,11 +55,15 @@ private: QList paramTypes; CommandHandler handler; }; - static const int numberCommands = 27; + static const int numberCommands = 31; static const CommandProperties commandList[numberCommands]; ReturnMessage::ReturnCode cmdPing(const QList ¶ms); ReturnMessage::ReturnCode cmdLogin(const QList ¶ms); + ReturnMessage::ReturnCode cmdChatListChannels(const QList ¶ms); + ReturnMessage::ReturnCode cmdChatJoinChannel(const QList ¶ms); + ReturnMessage::ReturnCode cmdChatLeaveChannel(const QList ¶ms); + ReturnMessage::ReturnCode cmdChatSay(const QList ¶ms); ReturnMessage::ReturnCode cmdListGames(const QList ¶ms); ReturnMessage::ReturnCode cmdCreateGame(const QList ¶ms); ReturnMessage::ReturnCode cmdJoinGame(const QList ¶ms); @@ -88,6 +92,7 @@ private: Server *server; ServerGame *game; + QList chatChannels; QList DeckList; QList SideboardList; QList zones; @@ -105,6 +110,7 @@ private: ReturnMessage *remsg; AuthenticationResult authState; bool acceptsGameListChanges; + bool acceptsChatChannelListChanges; public: ServerSocket(Server *_server, QObject *parent = 0); ~ServerSocket(); @@ -117,6 +123,7 @@ public: void setPlayerId(int _id) { playerId = _id; } QString getPlayerName() const { return playerName; } bool getAcceptsGameListChanges() const { return acceptsGameListChanges; } + bool getAcceptsChatChannelListChanges() const { return acceptsChatChannelListChanges; } QStringList listCounters() const; QStringList listZones() const; void setupZones();