From d6a96a47a3964b9c0b1ea2413601d70d0d327413 Mon Sep 17 00:00:00 2001 From: Manuel Nickschas Date: Sun, 5 Nov 2006 16:01:54 +0000 Subject: [PATCH 1/1] After quite a while, we have another big SVN update now. Most of the GUI code has been reorganized, and we have a working buffer management now. Also, we switched the main window to the new layout, which is probably here to stay; of course it will be enhanced quite a bit in the future :) Backlogs will be created and displayed whenever the GUI reconnects to the core. We limit the shown lines to 100 to not use too much time for rendering. This will be configurable in the future. There is probably a lot more changes that happened over the past few days which I can't remember, so you'll have to just see for yourself. --- CMakeLists.txt | 8 +- Quassel.kdevelop.filelist | 22 ++ core/CMakeLists.txt | 4 +- core/core.cpp | 135 +++++--- core/core.h | 27 +- core/coreproxy.cpp | 15 +- core/coreproxy.h | 7 +- core/server.cpp | 657 ++++++++++++++++++++++++++++++++++++++ core/server.h | 161 ++++++++++ gui/CMakeLists.txt | 12 +- gui/buffer.cpp | 311 ++++++++++++++++++ gui/buffer.h | 118 +++++++ gui/coreconnectdlg.h | 2 +- gui/guiproxy.cpp | 5 +- gui/guiproxy.h | 5 +- gui/mainwin.cpp | 251 +++++++++++++-- gui/mainwin.h | 39 ++- gui/networkview.cpp | 91 ++++++ gui/networkview.h | 79 +++++ gui/ui/bufferwidget.ui | 271 ++++++++++++++++ gui/ui/networkview.ui | 52 +++ gui/ui/networkwidget.ui | 29 -- main/global.h | 2 +- main/main_core.cpp | 1 - main/main_gui.cpp | 33 ++ main/main_mono.cpp | 8 +- main/message.cpp | 62 +++- main/message.h | 26 +- main/proxy_common.h | 3 +- main/util.cpp | 4 +- network/CMakeLists.txt | 4 +- network/buffer.cpp | 5 +- network/buffer.h | 8 +- network/server.cpp | 4 + network/server.h | 4 +- 35 files changed, 2307 insertions(+), 158 deletions(-) create mode 100644 core/server.cpp create mode 100644 core/server.h create mode 100644 gui/buffer.cpp create mode 100644 gui/buffer.h create mode 100644 gui/networkview.cpp create mode 100644 gui/networkview.h create mode 100644 gui/ui/bufferwidget.ui create mode 100644 gui/ui/networkview.ui delete mode 100644 gui/ui/networkwidget.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index 589e657d..d08682e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ SET(quassel_mono_SRCS main/main_mono.cpp) SET(quassel_core_SRCS main/main_core.cpp) SET(quassel_gui_SRCS main/main_gui.cpp ${common_SRCS}) SET(quassel_RCCS images/icons.qrc) -SET(quassel_DIRS main gui core network) +SET(quassel_DIRS main gui core) # Build correct absolute paths for subdirs to include SET(SDIRS "") @@ -55,11 +55,9 @@ INCLUDE(${QT_USE_FILE}) ADD_SUBDIRECTORY(main) IF(BUILD_CORE) ADD_SUBDIRECTORY(core) - ADD_SUBDIRECTORY(network) ENDIF(BUILD_CORE) IF(BUILD_MONO AND NOT BUILD_CORE) ADD_SUBDIRECTORY(core) - ADD_SUBDIRECTORY(network) ENDIF(BUILD_MONO AND NOT BUILD_CORE) QT4_ADD_RESOURCES(_RCCS ${quassel_RCCS}) @@ -67,7 +65,7 @@ QT4_ADD_RESOURCES(_RCCS ${quassel_RCCS}) IF(BUILD_CORE) ADD_DEFINITIONS(-DBUILD_CORE) ADD_EXECUTABLE(quasselcore ${quassel_core_SRCS} ${_RCCS}) - TARGET_LINK_LIBRARIES(quasselcore core network main ${QT_LIBRARIES}) + TARGET_LINK_LIBRARIES(quasselcore core main ${QT_LIBRARIES}) ENDIF(BUILD_CORE) IF(BUILD_GUI OR BUILD_MONO) # OK, now we need QtGui! @@ -81,7 +79,7 @@ IF(BUILD_GUI OR BUILD_MONO) # OK, now we need QtGui! IF(BUILD_MONO) ADD_DEFINITIONS(-DBUILD_MONO) ADD_EXECUTABLE(quassel ${quassel_mono_SRCS} ${_RCCS}) - TARGET_LINK_LIBRARIES(quassel gui core network main ${QT_LIBRARIES}) + TARGET_LINK_LIBRARIES(quassel gui core main ${QT_LIBRARIES}) ENDIF(BUILD_MONO) IF(BUILD_GUI) diff --git a/Quassel.kdevelop.filelist b/Quassel.kdevelop.filelist index 0080ef1d..5cc32b4e 100644 --- a/Quassel.kdevelop.filelist +++ b/Quassel.kdevelop.filelist @@ -48,3 +48,25 @@ network/buffer.h gui/channelwidgetinput.h gui/channelwidgetinput.cpp plugins/plugin.h +gui/ui/channelwidget.ui +gui/ui/coreconnectdlg.ui +gui/ui/identitiesdlg.ui +gui/ui/identitieseditdlg.ui +gui/ui/ircwidget.ui +gui/ui/mainwin.ui +gui/ui/networkeditdlg.ui +gui/ui/networkwidget.ui +gui/ui/nickeditdlg.ui +gui/ui/servereditdlg.ui +gui/ui/serverlistdlg.ui +gui/ui/settingsdlg.ui +gui/ui/networkview.ui +gui/networkview.cpp +gui/networkview.h +gui/buffer.cpp +gui/buffer.h +main/message.cpp +main/message.h +gui/ui/bufferwidget.ui +core/server.cpp +core/server.h diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index bde1fb02..484f994c 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,6 +1,6 @@ -SET(core_SRCS core.cpp coreproxy.cpp) +SET(core_SRCS core.cpp coreproxy.cpp server.cpp) SET(core_HDRS ) -SET(core_MOCS core.h coreproxy.h) +SET(core_MOCS core.h coreproxy.h server.h) QT4_WRAP_CPP(_MOC ${core_MOCS}) ADD_LIBRARY(core ${_MOC} ${core_SRCS} ${core_HDRS}) diff --git a/core/core.cpp b/core/core.cpp index f9f4f23a..b93c96e8 100644 --- a/core/core.cpp +++ b/core/core.cpp @@ -28,9 +28,10 @@ Core::Core() { if(core) qFatal("Trying to instantiate more than one Core object!"); + connect(coreProxy, SIGNAL(requestServerStates()), this, SIGNAL(serverStateRequested())); connect(coreProxy, SIGNAL(gsRequestConnect(QStringList)), this, SLOT(connectToIrc(QStringList))); connect(coreProxy, SIGNAL(gsUserInput(QString, QString, QString)), this, SIGNAL(msgFromGUI(QString, QString, QString))); - connect(this, SIGNAL(displayMsg(QString, QString, Message)), coreProxy, SLOT(csDisplayMsg(QString, QString, Message))); + connect(this, SIGNAL(displayMsg(QString, Message)), coreProxy, SLOT(csDisplayMsg(QString, Message))); connect(this, SIGNAL(displayStatusMsg(QString, QString)), coreProxy, SLOT(csDisplayStatusMsg(QString, QString))); // Read global settings from config file @@ -49,6 +50,19 @@ Core::Core() { } +Core::~Core() { + //foreach(Server *s, servers) { + // delete s; + //} + foreach(QDataStream *s, logStreams) { + delete s; + } + foreach(QFile *f, logFiles) { + if(f->isOpen()) f->close(); + delete f; + } +} + void Core::globalDataUpdated(QString key) { QVariant data = global->getData(key); QSettings s; @@ -61,10 +75,12 @@ void Core::connectToIrc(QStringList networks) { } else { Server *server = new Server(net); + connect(this, SIGNAL(serverStateRequested()), server, SLOT(sendState())); connect(this, SIGNAL(connectToIrc(QString)), server, SLOT(connectToIrc(QString))); connect(this, SIGNAL(disconnectFromIrc(QString)), server, SLOT(disconnectFromIrc(QString))); connect(this, SIGNAL(msgFromGUI(QString, QString, QString)), server, SLOT(userInput(QString, QString, QString))); - connect(server, SIGNAL(displayMsg(QString, Message)), this, SLOT(recvMessageFromServer(QString, Message))); + connect(server, SIGNAL(serverState(QString, VarMap)), coreProxy, SLOT(csServerState(QString, VarMap))); + connect(server, SIGNAL(displayMsg(Message)), this, SLOT(recvMessageFromServer(Message))); connect(server, SIGNAL(displayStatusMsg(QString)), this, SLOT(recvStatusMsgFromServer(QString))); connect(server, SIGNAL(modeSet(QString, QString, QString)), coreProxy, SLOT(csModeSet(QString, QString, QString))); connect(server, SIGNAL(topicSet(QString, QString, QString)), coreProxy, SLOT(csTopicSet(QString, QString, QString))); @@ -75,6 +91,8 @@ void Core::connectToIrc(QStringList networks) { connect(server, SIGNAL(nickUpdated(QString, QString, VarMap)), coreProxy, SLOT(csNickUpdated(QString, QString, VarMap))); connect(server, SIGNAL(ownNickSet(QString, QString)), coreProxy, SLOT(csOwnNickSet(QString, QString))); // add error handling + connect(server, SIGNAL(connected(QString)), coreProxy, SLOT(csServerConnected(QString))); + connect(server, SIGNAL(disconnected(QString)), this, SLOT(serverDisconnected(QString))); server->start(); servers[net] = server; @@ -83,13 +101,19 @@ void Core::connectToIrc(QStringList networks) { } } +void Core::serverDisconnected(QString net) { + delete servers[net]; + servers.remove(net); + coreProxy->csServerDisconnected(net); +} + // ALL messages coming pass through these functions before going to the GUI. // So this is the perfect place for storing the backlog and log stuff. -void Core::recvMessageFromServer(QString buf, Message msg) { +void Core::recvMessageFromServer(Message msg) { Server *s = qobject_cast(sender()); Q_ASSERT(s); - logMessage(msg); - emit displayMsg(s->getNetwork(), buf, msg); + logMessage(s->getNetwork(), msg); + emit displayMsg(s->getNetwork(), msg); } void Core::recvStatusMsgFromServer(QString msg) { @@ -115,35 +139,44 @@ void Core::initBackLog() { // backLogEnabled = false; // return; //} - QStringList logs = backLogDir.entryList(QStringList("quassel-backlog-*.bin"), QDir::Files|QDir::Readable, QDir::Name); - foreach(QString name, logs) { - QFile f(backLogDir.absolutePath() + "/" + name); - if(!f.open(QIODevice::ReadOnly)) { - qWarning(QString("Could not open \"%1\" for reading!").arg(f.fileName()).toAscii()); + QStringList networks = backLogDir.entryList(QDir::Dirs|QDir::NoDotAndDotDot|QDir::Readable, QDir::Name); + foreach(QString net, networks) { + QDir dir(backLogDir.absolutePath() + "/" + net); + if(!dir.exists()) { + qWarning(QString("Could not change to directory \"%1\"!").arg(dir.absolutePath()).toAscii()); continue; } - QDataStream in(&f); - in.setVersion(QDataStream::Qt_4_2); - QByteArray verstring; quint8 vernum; in >> verstring >> vernum; - if(verstring != BACKLOG_STRING) { - qWarning(QString("\"%1\" is not a Quassel backlog file!").arg(f.fileName()).toAscii()); - f.close(); continue; - } - if(vernum != BACKLOG_FORMAT) { - qWarning(QString("\"%1\": Version mismatch!").arg(f.fileName()).toAscii()); - f.close(); continue; - } - qDebug() << "Reading backlog from" << f.fileName(); - currentLogFileDate = QDate::fromString(f.fileName(), QString("'%1/quassel-backlog-'yyyy-MM-dd'.bin'").arg(backLogDir.absolutePath())); - if(!currentLogFileDate.isValid()) { - qWarning(QString("\"%1\" has an invalid file name!").arg(f.fileName()).toAscii()); + QStringList logs = dir.entryList(QStringList("quassel-backlog-*.bin"), QDir::Files|QDir::Readable, QDir::Name); + foreach(QString name, logs) { + QFile f(dir.absolutePath() + "/" + name); + if(!f.open(QIODevice::ReadOnly)) { + qWarning(QString("Could not open \"%1\" for reading!").arg(f.fileName()).toAscii()); + continue; + } + QDataStream in(&f); + in.setVersion(QDataStream::Qt_4_2); + QByteArray verstring; quint8 vernum; in >> verstring >> vernum; + if(verstring != BACKLOG_STRING) { + qWarning(QString("\"%1\" is not a Quassel backlog file!").arg(f.fileName()).toAscii()); + f.close(); continue; + } + if(vernum != BACKLOG_FORMAT) { + qWarning(QString("\"%1\": Version mismatch!").arg(f.fileName()).toAscii()); + f.close(); continue; + } + qDebug() << "Reading backlog from" << f.fileName(); + logFileDates[net] = QDate::fromString(f.fileName(), + QString("'%1/quassel-backlog-'yyyy-MM-dd'.bin'").arg(dir.absolutePath())); + if(!logFileDates[net].isValid()) { + qWarning(QString("\"%1\" has an invalid file name!").arg(f.fileName()).toAscii()); + } + while(!in.atEnd()) { + Message m; + in >> m; + backLog[net].append(m); + } + f.close(); } - while(!in.atEnd()) { - Message m; - in >> m; - backLog.append(m); - } - f.close(); } backLogEnabled = true; } @@ -153,22 +186,38 @@ void Core::initBackLog() { * The file header is the string defined by BACKLOG_STRING, followed by a quint8 specifying the format * version (BACKLOG_FORMAT). The rest is simply serialized Message objects. */ -void Core::logMessage(Message msg) { - backLog.append(msg); - if(!currentLogFileDate.isValid() || currentLogFileDate < QDate::currentDate()) { - if(currentLogFile.isOpen()) currentLogFile.close(); - currentLogFileDate = QDate::currentDate(); +void Core::logMessage(QString net, Message msg) { + backLog[net].append(msg); + if(!logFileDirs.contains(net)) { + QDir dir(backLogDir.absolutePath() + "/" + net); + if(!dir.exists()) { + qWarning(QString("Creating backlog directory \"%1\"...").arg(dir.absolutePath()).toAscii()); + if(!dir.mkpath(dir.absolutePath())) { + qWarning(QString("Could not create backlog directory!").toAscii()); + return; + } + } + logFileDirs[net] = dir; + Q_ASSERT(!logFiles.contains(net) && !logStreams.contains(net)); + if(!logFiles.contains(net)) logFiles[net] = new QFile(); + if(!logStreams.contains(net)) logStreams[net] = new QDataStream(); + } + if(!logFileDates[net].isValid() || logFileDates[net] < QDate::currentDate()) { + if(logFiles[net]->isOpen()) logFiles[net]->close(); + logFileDates[net] = QDate::currentDate(); } - if(!currentLogFile.isOpen()) { - currentLogFile.setFileName(backLogDir.absolutePath() + "/" + currentLogFileDate.toString("'quassel-backlog-'yyyy-MM-dd'.bin'")); - if(!currentLogFile.open(QIODevice::WriteOnly|QIODevice::Append)) { - qWarning(QString("Could not open \"%1\" for writing: %2").arg(currentLogFile.fileName()).arg(currentLogFile.errorString()).toAscii()); + if(!logFiles[net]->isOpen()) { + logFiles[net]->setFileName(QString("%1/%2").arg(logFileDirs[net].absolutePath()) + .arg(logFileDates[net].toString("'quassel-backlog-'yyyy-MM-dd'.bin'"))); + if(!logFiles[net]->open(QIODevice::WriteOnly|QIODevice::Append|QIODevice::Unbuffered)) { + qWarning(QString("Could not open \"%1\" for writing: %2") + .arg(logFiles[net]->fileName()).arg(logFiles[net]->errorString()).toAscii()); return; } - logStream.setDevice(¤tLogFile); logStream.setVersion(QDataStream::Qt_4_2); - if(!currentLogFile.size()) logStream << BACKLOG_STRING << (quint8)BACKLOG_FORMAT; + logStreams[net]->setDevice(logFiles[net]); logStreams[net]->setVersion(QDataStream::Qt_4_2); + if(!logFiles[net]->size()) *logStreams[net] << BACKLOG_STRING << (quint8)BACKLOG_FORMAT; } - logStream << msg; + *logStreams[net] << msg; } Core *core = 0; diff --git a/core/core.h b/core/core.h index f5532c3b..b2a31253 100644 --- a/core/core.h +++ b/core/core.h @@ -33,35 +33,42 @@ class Core : public QObject { public: Core(); - //~Core(); + ~Core(); + QHash > getBackLog() { return backLog; }; public slots: void connectToIrc(QStringList); signals: void msgFromGUI(QString network, QString channel, QString message); - void displayMsg(QString network, QString channel, Message message); + void displayMsg(QString network, Message message); void displayStatusMsg(QString, QString); void connectToIrc(QString net); void disconnectFromIrc(QString net); + void serverStateRequested(); private slots: + //void serverStatesRequested(); void globalDataUpdated(QString); void recvStatusMsgFromServer(QString msg); - void recvMessageFromServer(QString buffer, Message msg); + void recvMessageFromServer(Message msg); + void serverDisconnected(QString net); private: - QHash servers; - QList backLog; - bool backLogEnabled; QDir backLogDir; - QFile currentLogFile; - QDataStream logStream; - QDate currentLogFileDate; + bool backLogEnabled; + QHash servers; + QHash > backLog; + //QHash netIdx; + QHash logFiles; + QHash logStreams; + QHash logFileDates; + QHash logFileDirs; + //uint getNetIdx(QString net); void initBackLog(); - void logMessage(Message); + void logMessage(QString, Message); }; diff --git a/core/coreproxy.cpp b/core/coreproxy.cpp index 91380470..11fdf95b 100644 --- a/core/coreproxy.cpp +++ b/core/coreproxy.cpp @@ -23,9 +23,12 @@ #include "coreproxy.h" #include "global.h" #include "util.h" +#include "core.h" CoreProxy::CoreProxy() { if(coreProxy) qFatal("Trying to instantiate more than one CoreProxy object!"); + coreProxy = this; + core = new Core(); connect(global, SIGNAL(dataPutLocally(QString)), this, SLOT(updateGlobalData(QString))); connect(&server, SIGNAL(newConnection()), this, SLOT(incomingConnection())); @@ -85,11 +88,19 @@ void CoreProxy::processClientInit(QTcpSocket *socket, const QVariant &v) { coreData[key] = global->getData(key); } reply["CoreData"] = coreData; - //QVariant payload = QByteArray(1000000, 'a'); - //reply["payload"] = payload; + VarMap bl; + QHash > log = core->getBackLog(); + foreach(QString net, log.keys()) { + QByteArray buf; + QDataStream out(&buf, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_2); + foreach(Message msg, log[net]) { out << msg; } + bl[net] = buf; + } + reply["CoreBackLog"] = bl; QList sigdata; sigdata.append(CS_CORE_STATE); sigdata.append(QVariant(reply)); sigdata.append(QVariant()); sigdata.append(QVariant()); writeDataToDevice(socket, QVariant(sigdata)); + emit requestServerStates(); } void CoreProxy::processClientUpdate(QTcpSocket *socket, QString key, QVariant data) { diff --git a/core/coreproxy.h b/core/coreproxy.h index 4f6185aa..c8850a35 100644 --- a/core/coreproxy.h +++ b/core/coreproxy.h @@ -41,7 +41,10 @@ class CoreProxy : public QObject { public slots: inline void csUpdateGlobalData(QString key, QVariant data) { send(CS_UPDATE_GLOBAL_DATA, key, data); } - inline void csDisplayMsg(QString net, QString buf, Message msg) { send(CS_DISPLAY_MSG, net, buf, QVariant::fromValue(msg)); } + inline void csServerConnected(QString net) { send(CS_SERVER_CONNECTED, net); } + inline void csServerDisconnected(QString net) { send(CS_SERVER_DISCONNECTED, net); } + inline void csServerState(QString net, VarMap data) { send(CS_SERVER_STATE, net, data); } + inline void csDisplayMsg(QString net, Message msg) { send(CS_DISPLAY_MSG, net, QVariant::fromValue(msg)); } inline void csDisplayStatusMsg(QString net, QString msg) { send(CS_DISPLAY_STATUS_MSG, net, msg); } inline void csModeSet(QString net, QString target, QString mode) { send(CS_MODE_SET, net, target, mode); } inline void csTopicSet(QString net, QString buf, QString topic) { send(CS_TOPIC_SET, net, buf, topic); } @@ -57,6 +60,8 @@ class CoreProxy : public QObject { void gsUserInput(QString, QString, QString); void gsRequestConnect(QStringList networks); + void requestServerStates(); + private: void send(CoreSignal, QVariant arg1 = QVariant(), QVariant arg2 = QVariant(), QVariant arg3 = QVariant()); void recv(GUISignal, QVariant arg1 = QVariant(), QVariant arg2 = QVariant(), QVariant arg3 = QVariant()); diff --git a/core/server.cpp b/core/server.cpp new file mode 100644 index 00000000..eeb46934 --- /dev/null +++ b/core/server.cpp @@ -0,0 +1,657 @@ +/*************************************************************************** + * Copyright (C) 2005/06 by The Quassel Team * + * devel@quassel-irc.org * + * * + * 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "util.h" +#include "global.h" +#include "server.h" +#include "message.h" + +#include +#include + +Server::Server(QString net) : network(net) { + +} + +Server::~Server() { + +} + +void Server::run() { + connect(&socket, SIGNAL(connected()), this, SLOT(socketConnected())); + connect(&socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); + connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState))); + connect(&socket, SIGNAL(readyRead()), this, SLOT(socketHasData())); + + exec(); +} + +void Server::sendState() { + VarMap s; + VarMap n, t; + foreach(QString key, nicks.keys()) { n[key] = nicks[key]; } + foreach(QString key, topics.keys()) { t[key] = topics[key];} + s["Nicks"] = n; + s["Topics"] = t; + s["OwnNick"] = ownNick; + s["ServerSupports"] = serverSupports; + emit serverState(network, s); +} + +void Server::connectToIrc(QString net) { + if(net != network) return; // not me! + networkSettings = global->getData("Networks").toMap()[net].toMap(); + identity = global->getData("Identities").toMap()[networkSettings["Identity"].toString()].toMap(); + QList servers = networkSettings["Servers"].toList(); + QString host = servers[0].toMap()["Address"].toString(); + quint16 port = servers[0].toMap()["Port"].toUInt(); + displayStatusMsg(QString("Connecting to %1:%2...").arg(host).arg(port)); + socket.connectToHost(host, port); +} + +void Server::disconnectFromIrc(QString net) { + if(net != network) return; // not me! + socket.disconnectFromHost(); +} + +void Server::socketHasData() { + while(socket.canReadLine()) { + QString s = socket.readLine().trimmed(); + qDebug() << "Read" << s; + emit recvRawServerMsg(s); + //Message *msg = Message::createFromServerString(this, s); + handleServerMsg(s); + } +} + +void Server::socketError( QAbstractSocket::SocketError err ) { + //qDebug() << "Socket Error!"; + //emit error(err); +} + +void Server::socketConnected( ) { + emit connected(network); + putRawLine(QString("NICK :%1").arg(identity["NickList"].toStringList()[0])); + putRawLine(QString("USER %1 8 * :%2").arg(identity["Ident"].toString()).arg(identity["RealName"].toString())); +} + +void Server::socketDisconnected( ) { + //qDebug() << "Socket disconnected!"; + emit disconnected(network); + topics.clear(); + nicks.clear(); +} + +void Server::socketStateChanged(QAbstractSocket::SocketState state) { + //qDebug() << "Socket state changed: " << state; +} + +QString Server::updateNickFromMask(QString mask) { + QString user = userFromMask(mask); + QString host = hostFromMask(mask); + QString nick = nickFromMask(mask); + if(nicks.contains(nick) && !user.isEmpty() && !host.isEmpty()) { + VarMap n = nicks[nick]; + if(n["User"].toString() != user || n["Host"].toString() != host) { + if(!n["User"].toString().isEmpty() || !n["Host"].toString().isEmpty()) + qWarning(QString("Strange: Hostmask for nick %1 has changed!").arg(nick).toAscii()); + n["User"] = user; n["Host"] = host; + nicks[nick] = n; + emit nickUpdated(network, nick, n); + } + } + return nick; +} + +void Server::userInput(QString net, QString buf, QString msg) { + if(net != network) return; // not me! + //msg = msg.trimmed(); // remove whitespace from start and end + if(msg.isEmpty()) return; + if(!msg.startsWith('/')) { + msg = QString("/SAY ") + msg; + } + handleUserInput(buf, msg); +} + +void Server::putRawLine(QString s) { + qDebug() << "SentRaw: " << s; + s += "\r\n"; + socket.write(s.toAscii()); +} + +void Server::putCmd(QString cmd, QStringList params, QString prefix) { + QString m; + if(!prefix.isEmpty()) m += ":" + prefix + " "; + m += cmd.toUpper(); + for(int i = 0; i < params.size() - 1; i++) { + m += " " + params[i]; + } + if(!params.isEmpty()) m += " :" + params.last(); + qDebug() << "Sent: " << m; + m += "\r\n"; + socket.write(m.toAscii()); +} + +/** Handle a raw message string sent by the server. We try to find a suitable handler, otherwise we call a default handler. */ +void Server::handleServerMsg(QString msg) { + try { + if(msg.isEmpty()) { + qWarning() << "Received empty string from server!"; + return; + } + // OK, first we split the raw message into its various parts... + QString prefix; + QString cmd; + QStringList params; + if(msg[0] == ':') { + msg.remove(0,1); + prefix = msg.section(' ', 0, 0); + msg = msg.section(' ', 1); + } + cmd = msg.section(' ', 0, 0).toUpper(); + msg = msg.section(' ', 1); + QString left, trailing; + // RPL_ISUPPORT (005) can contain colons, so don't treat it like the rest of the commands + if(cmd.toUInt() == 5) { + left = msg.remove(QString(":are supported by this server")); + } else { + left = msg.section(':', 0, 0); + trailing = msg.section(':', 1); + } + if(!left.isEmpty()) { + params << left.split(' ', QString::SkipEmptyParts); + } + if(!trailing.isEmpty()) { + params << trailing; + } + // numeric replies usually have our own nick as first param. Remove this! + // BTW, this behavior is not in the RFC. + uint num = cmd.toUInt(); + if(num > 1 && params.count() > 0) { // 001 sets our nick, so we shouldn't remove anything + if(params[0] == ownNick) params.removeFirst(); + else qWarning((QString("First param NOT nick: %1:%2 %3").arg(prefix).arg(cmd).arg(params.join(" "))).toAscii()); + } + // Now we try to find a handler for this message. BTW, I do love the Trolltech guys ;-) + QString hname = cmd.toLower(); + hname[0] = hname[0].toUpper(); + hname = "handleServer" + hname; + if(!QMetaObject::invokeMethod(this, hname.toAscii(), Q_ARG(QString, prefix), Q_ARG(QStringList, params))) { + // Ok. Default handler it is. + defaultServerHandler(cmd, prefix, params); + } + } catch(Exception e) { + emit displayMsg(Message::error("", e.msg())); + } +} + +void Server::defaultServerHandler(QString cmd, QString prefix, QStringList params) { + uint num = cmd.toUInt(); + if(num) { + // A lot of server messages don't really need their own handler because they don't do much. + // Catch and handle these here. + switch(num) { + // Welcome, status, info messages. Just display these. + case 2: case 3: case 4: case 5: case 251: case 252: case 253: case 254: case 255: case 372: case 375: + emit displayMsg(Message::server("", params.join(" "), prefix)); + break; + // Server error messages without param, just display them + case 409: case 411: case 412: case 422: case 424: case 431: case 445: case 446: case 451: case 462: + case 463: case 464: case 465: case 466: case 472: case 481: case 483: case 485: case 491: case 501: case 502: + emit displayMsg(Message::error("", params.join(" "), prefix)); + break; + // Server error messages, display them in red. First param will be appended. + case 401: case 402: case 403: case 404: case 406: case 408: case 415: case 421: case 432: case 442: + { QString p = params.takeFirst(); + emit displayMsg(Message::error("", params.join(" ") + " " + p, prefix)); + break; + } + // Server error messages which will be displayed with a colon between the first param and the rest + case 413: case 414: case 423: case 433: case 436: case 441: case 444: case 461: + case 467: case 471: case 473: case 474: case 475: case 476: case 477: case 478: case 482: + { QString p = params.takeFirst(); + emit displayMsg(Message::error("", p + ": " + params.join(" "))); + break; + } + // Ignore these commands. + case 366: case 376: + break; + + // Everything else will be marked in red, so we can add them somewhere. + default: + emit displayMsg(Message::error("", cmd + " " + params.join(" "), prefix)); + } + //qDebug() << prefix <<":"< 2) msg = QString("%1 %2").arg(msg).arg(params[2]); + emit displayMsg(Message::kick(params[0], msg, prefix)); + if(chans.count() > 0) { + n["Channels"] = chans; + nicks[nick] = n; + emit nickUpdated(network, nick, n); + } else { + nicks.remove(nick); + emit nickRemoved(network, nick); + } + if(nick == ownNick) { + topics.remove(params[0]); + } +} + +void Server::handleServerMode(QString prefix, QStringList params) { + if(isChannelName(params[0])) { + // TODO only channel-user modes supported by now + QString prefixes = serverSupports["PrefixModes"].toString(); + QString modes = params[1]; + int p = 2; + int m = 0; + bool add = true; + while(m < modes.length()) { + if(modes[m] == '+') { add = true; m++; continue; } + if(modes[m] == '-') { add = false; m++; continue; } + if(prefixes.contains(modes[m])) { // it's a user channel mode + Q_ASSERT(params.count() > m); + QString nick = params[p++]; + if(nicks.contains(nick)) { // sometimes, a server might try to set a MODE on a nick that is no longer there + VarMap n = nicks[nick]; VarMap clist = n["Channels"].toMap(); VarMap chan = clist[params[0]].toMap(); + QString mstr = chan["Mode"].toString(); + add ? mstr += modes[m] : mstr.remove(modes[m]); + chan["Mode"] = mstr; clist[params[0]] = chan; n["Channels"] = clist; nicks[nick] = n; + emit nickUpdated(network, nick, n); + } + m++; + } else { + // TODO add more modes + m++; + } + } + emit displayMsg(Message::mode(params[0], params.join(" "), prefix)); + } else { + //Q_ASSERT(nicks.contains(params[0])); + //VarMap n = nicks[params[0]].toMap(); + //QString mode = n["Mode"].toString(); + emit displayMsg(Message::mode("", params.join(" "))); + } +} + +void Server::handleServerNick(QString prefix, QStringList params) { + QString oldnick = updateNickFromMask(prefix); + QString newnick = params[0]; + VarMap v = nicks.take(oldnick); + nicks[newnick] = v; + VarMap chans = v["Channels"].toMap(); + foreach(QString c, chans.keys()) { + if(oldnick != ownNick) { emit displayMsg(Message::nick(c, newnick, prefix)); } + else { emit displayMsg(Message::nick(c, newnick, newnick)); } + } + emit nickRenamed(network, oldnick, newnick); + if(oldnick == ownNick) { + ownNick = newnick; + emit ownNickSet(network, newnick); + } +} + +void Server::handleServerNotice(QString prefix, QStringList params) { + //Message msg(Message::Notice, params[1], prefix); + if(prefix == currentServer) emit displayMsg(Message::server("", params[1], prefix)); + else emit displayMsg(Message::notice("", params[1], prefix)); +} + +void Server::handleServerPart(QString prefix, QStringList params) { + QString nick = updateNickFromMask(prefix); + Q_ASSERT(nicks.contains(nick)); + VarMap n = nicks[nick]; + VarMap chans = n["Channels"].toMap(); + Q_ASSERT(chans.contains(params[0])); + chans.remove(params[0]); + QString msg; + if(params.count() > 1) msg = params[1]; + emit displayMsg(Message::part(params[0], msg, prefix)); + if(chans.count() > 0) { + n["Channels"] = chans; + nicks[nick] = n; + emit nickUpdated(network, nick, n); + } else { + nicks.remove(nick); + emit nickRemoved(network, nick); + } + if(nick == ownNick) { + Q_ASSERT(topics.contains(params[0])); + topics.remove(params[0]); + } +} + +void Server::handleServerPing(QString prefix, QStringList params) { + putCmd("PONG", params); +} + +void Server::handleServerPrivmsg(QString prefix, QStringList params) { + updateNickFromMask(prefix); + if(params.count()<2) emit displayMsg(Message::plain(params[0], "", prefix)); + else emit displayMsg(Message::plain(params[0], params[1], prefix)); +} + +void Server::handleServerQuit(QString prefix, QStringList params) { + QString nick = updateNickFromMask(prefix); + Q_ASSERT(nicks.contains(nick)); + VarMap chans = nicks[nick]["Channels"].toMap(); + foreach(QString c, chans.keys()) { + emit displayMsg(Message::quit(c, params[0], prefix)); + } + nicks.remove(nick); + emit nickRemoved(network, nick); +} + +void Server::handleServerTopic(QString prefix, QStringList params) { + QString nick = updateNickFromMask(prefix); + Q_ASSERT(nicks.contains(nick)); + topics[params[0]] = params[1]; + emit topicSet(network, params[0], params[1]); + emit displayMsg(Message::server(params[0], tr("%1 has changed topic for %2 to: \"%3\"").arg(nick).arg(params[0]).arg(params[1]))); +} + +/* RPL_WELCOME */ +void Server::handleServer001(QString prefix, QStringList params) { + currentServer = prefix; + ownNick = params[0]; + VarMap n; + n["Channels"] = VarMap(); + nicks[ownNick] = n; + emit ownNickSet(network, ownNick); + emit nickAdded(network, ownNick, VarMap()); + emit displayMsg(Message::server("", params[1], prefix)); +} + +/* RPL_ISUPPORT */ +void Server::handleServer005(QString prefix, QStringList params) { + foreach(QString p, params) { + QString key = p.section("=", 0, 0); + QString val = p.section("=", 1); + serverSupports[key] = val; + // handle some special cases + if(key == "PREFIX") { + VarMap foo; QString modes, prefixes; + Q_ASSERT(val.contains(')') && val.startsWith('(')); + int m = 1, p; + for(p = 2; p < val.length(); p++) if(val[p] == ')') break; + p++; + for(; val[m] != ')'; m++, p++) { + Q_ASSERT(p < val.length()); + foo[QString(val[m])] = QString(val[p]); + modes += val[m]; prefixes += val[p]; + } + serverSupports["PrefixModes"] = modes; serverSupports["Prefixes"] = prefixes; + serverSupports["ModePrefixMap"] = foo; + } + } +} + + +/* RPL_NOTOPIC */ +void Server::handleServer331(QString prefix, QStringList params) { + topics[params[0]] = ""; + emit topicSet(network, params[0], ""); + emit displayMsg(Message::server(params[0], tr("No topic is set for %1.").arg(params[0]))); +} + +/* RPL_TOPIC */ +void Server::handleServer332(QString prefix, QStringList params) { + topics[params[0]] = params[1]; + emit topicSet(network, params[0], params[1]); + emit displayMsg(Message::server(params[0], tr("Topic for %1 is \"%2\"").arg(params[0]).arg(params[1]))); +} + +/* Topic set by... */ +void Server::handleServer333(QString prefix, QStringList params) { + emit displayMsg(Message::server(params[0], + tr("Topic set by %1 on %2").arg(params[1]).arg(QDateTime::fromTime_t(params[2].toUInt()).toString()))); +} + +/* RPL_NAMREPLY */ +void Server::handleServer353(QString prefix, QStringList params) { + params.removeFirst(); // = or * + QString buf = params.takeFirst(); + QString prefixes = serverSupports["Prefixes"].toString(); + foreach(QString nick, params[0].split(' ')) { + QString mode = "", pfx = ""; + if(prefixes.contains(nick[0])) { + pfx = nick[0]; + for(int i = 0;; i++) + if(prefixes[i] == nick[0]) { mode = serverSupports["PrefixModes"].toString()[i]; break; } + nick.remove(0,1); + } + VarMap c; c["Mode"] = mode; c["Prefix"] = pfx; + if(nicks.contains(nick)) { + VarMap n = nicks[nick]; + VarMap chans = n["Channels"].toMap(); + chans[buf] = c; + n["Channels"] = chans; + nicks[nick] = n; + emit nickUpdated(network, nick, n); + } else { + VarMap n; VarMap c; VarMap chans; + c["Mode"] = mode; + chans[buf] = c; + n["Channels"] = chans; + nicks[nick] = n; + emit nickAdded(network, nick, n); + } + } +} +/***********************************************************************************/ + +/* Exception classes for message handling */ +Server::ParseError::ParseError(QString cmd, QString prefix, QStringList params) { + _msg = QString("Command Parse Error: ") + cmd + params.join(" "); + +} + +Server::UnknownCmdError::UnknownCmdError(QString cmd, QString prefix, QStringList params) { + _msg = QString("Unknown Command: ") + cmd; + +} diff --git a/core/server.h b/core/server.h new file mode 100644 index 00000000..ba333d39 --- /dev/null +++ b/core/server.h @@ -0,0 +1,161 @@ +/*************************************************************************** + * Copyright (C) 2005/06 by The Quassel Team * + * devel@quassel-irc.org * + * * + * 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef _SERVER_H_ +#define _SERVER_H_ + +#include +#include +#include + +#include "global.h" +#include "message.h" + +#define DEFAULT_PORT 6667 + + +/*! + * This is a server object, managing a single connection to an IRC server, handling the associated channels and so on. + * We have this running in its own thread mainly to not block other server objects or the core if something goes wrong, + * e.g. if some scripts starts running wild... + */ + +class Server : public QThread { + Q_OBJECT + + public: + Server(QString network); + ~Server(); + + // serverState state(); + bool isConnected() { return socket.state() == QAbstractSocket::ConnectedState; } + QString getNetwork() { return network; } + + public slots: + // void setServerOptions(); + void sendState(); + void connectToIrc(QString net); + void disconnectFromIrc(QString net); + void userInput(QString net, QString buffer, QString msg); + + void putRawLine(QString input); + void putCmd(QString cmd, QStringList params, QString prefix = 0); + + //void exitThread(); + + signals: + void serverState(QString net, VarMap data); + void recvRawServerMsg(QString); + void displayStatusMsg(QString); + void displayMsg(Message msg); + void connected(QString network); + void disconnected(QString network); + + void nickAdded(QString network, QString nick, VarMap props); + void nickRenamed(QString network, QString oldnick, QString newnick); + void nickRemoved(QString network, QString nick); + void nickUpdated(QString network, QString nick, VarMap props); + void modeSet(QString network, QString target, QString mode); + void topicSet(QString network, QString buffer, QString topic); + void setNicks(QString network, QString buffer, QStringList nicks); + void ownNickSet(QString network, QString newNick); + + + private slots: + void run(); + void socketHasData(); + void socketError(QAbstractSocket::SocketError); + void socketConnected(); + void socketDisconnected(); + void socketStateChanged(QAbstractSocket::SocketState); + + /* Message Handlers */ + + /* void handleUser(QString, QString); */ + void handleUserAway(QString, QString); + void handleUserDeop(QString, QString); + void handleUserDevoice(QString, QString); + void handleUserInvite(QString, QString); + void handleUserJoin(QString, QString); + void handleUserKick(QString, QString); + void handleUserList(QString, QString); + void handleUserMode(QString, QString); + void handleUserMsg(QString, QString); + void handleUserNick(QString, QString); + void handleUserOp(QString, QString); + void handleUserPart(QString, QString); + void handleUserQuit(QString, QString); + void handleUserQuote(QString, QString); + void handleUserSay(QString, QString); + void handleUserTopic(QString, QString); + void handleUserVoice(QString, QString); + + /* void handleServer(QString, QStringList); */ + void handleServerJoin(QString, QStringList); + void handleServerKick(QString, QStringList); + void handleServerMode(QString, QStringList); + void handleServerNick(QString, QStringList); + void handleServerNotice(QString, QStringList); + void handleServerPart(QString, QStringList); + void handleServerPing(QString, QStringList); + void handleServerPrivmsg(QString, QStringList); + void handleServerQuit(QString, QStringList); + void handleServerTopic(QString, QStringList); + + void handleServer001(QString, QStringList); // RPL_WELCOME + void handleServer005(QString, QStringList); // RPL_ISUPPORT + void handleServer331(QString, QStringList); // RPL_NOTOPIC + void handleServer332(QString, QStringList); // RPL_TOPIC + void handleServer333(QString, QStringList); // Topic set by... + void handleServer353(QString, QStringList); // RPL_NAMREPLY + + void defaultServerHandler(QString cmd, QString prefix, QStringList params); + void defaultUserHandler(QString buf, QString cmd, QString msg); + + private: + QString network; + QTcpSocket socket; + //QHash buffers; + + QString ownNick; + QString currentServer; + VarMap networkSettings; + VarMap identity; + QHash nicks; // stores all known nicks for the server + QHash topics; // stores topics for each buffer + VarMap serverSupports; // stores results from RPL_ISUPPORT + + void handleServerMsg(QString rawMsg); + void handleUserInput(QString buffer, QString usrMsg); + + QString updateNickFromMask(QString mask); + + class ParseError : public Exception { + public: + ParseError(QString cmd, QString prefix, QStringList params); + }; + + class UnknownCmdError : public Exception { + public: + UnknownCmdError(QString cmd, QString prefix, QStringList params); + }; +}; + +#endif diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 4d6297e3..8bc8b4bf 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -1,9 +1,11 @@ -SET(gui_SRCS channelwidget.cpp channelwidgetinput.cpp mainwin.cpp serverlist.cpp - identities.cpp coreconnectdlg.cpp guiproxy.cpp) +SET(gui_SRCS channelwidgetinput.cpp mainwin.cpp serverlist.cpp buffer.cpp + identities.cpp coreconnectdlg.cpp guiproxy.cpp networkview.cpp) SET(gui_HDRS ) -SET(gui_MOCS channelwidget.h channelwidgetinput.h mainwin.h serverlist.h identities.h coreconnectdlg.h guiproxy.h) -SET(gui_UICS channelwidget.ui identitiesdlg.ui identitieseditdlg.ui networkeditdlg.ui mainwin.ui - nickeditdlg.ui serverlistdlg.ui servereditdlg.ui coreconnectdlg.ui ircwidget.ui) +SET(gui_MOCS channelwidgetinput.h mainwin.h serverlist.h identities.h coreconnectdlg.h + guiproxy.h networkview.h buffer.h) +SET(gui_UICS identitiesdlg.ui identitieseditdlg.ui networkeditdlg.ui mainwin.ui + nickeditdlg.ui serverlistdlg.ui servereditdlg.ui coreconnectdlg.ui ircwidget.ui + networkview.ui bufferwidget.ui) # This prepends ui/ to the UIC names, so we don't have to do this ourselves. FOREACH(ui ${gui_UICS}) diff --git a/gui/buffer.cpp b/gui/buffer.cpp new file mode 100644 index 00000000..2eeda622 --- /dev/null +++ b/gui/buffer.cpp @@ -0,0 +1,311 @@ +/*************************************************************************** + * Copyright (C) 2005/06 by The Quassel Team * + * devel@quassel-irc.org * + * * + * 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "buffer.h" +#include "util.h" + +Buffer::Buffer(QString netname, QString bufname) { + networkName = netname; + bufferName = bufname; + + widget = 0; + active = false; +} + +Buffer::~Buffer() { + delete widget; +} + +void Buffer::setActive(bool a) { + if(a != active) { + active = a; + if(widget) widget->setActive(a); + } +} + +void Buffer::displayMsg(Message msg) { + contents.append(msg); + if(widget) widget->displayMsg(msg); +} + +void Buffer::userInput(QString msg) { + emit userInput(networkName, bufferName, msg); +} + +void Buffer::scrollToEnd() { + if(!widget) return; + widget->scrollToEnd(); +} + +QWidget * Buffer::showWidget(QWidget *parent) { + if(widget) return qobject_cast(widget); + widget = new BufferWidget(networkName, bufferName, isActive(), ownNick, contents, parent); + widget->setTopic(topic); + widget->updateNickList(nicks); + //widget->renderContents(); + widget->scrollToEnd(); + connect(widget, SIGNAL(userInput(QString)), this, SLOT(userInput(QString))); + return qobject_cast(widget); +} + +void Buffer::hideWidget() { + delete widget; + widget = 0; +} + +QWidget * Buffer::getWidget() { + return qobject_cast(widget); +} + +void Buffer::setTopic(QString t) { + topic = t; + if(widget) widget->setTopic(t); +} + +void Buffer::addNick(QString nick, VarMap props) { + if(nick == ownNick) setActive(true); + nicks[nick] = props; + if(widget) widget->updateNickList(nicks); +} + +void Buffer::updateNick(QString nick, VarMap props) { + nicks[nick] = props; + if(widget) widget->updateNickList(nicks); +} + +void Buffer::renameNick(QString oldnick, QString newnick) { + QVariant v = nicks.take(oldnick); + nicks[newnick] = v; + if(widget) widget->updateNickList(nicks); +} + +void Buffer::removeNick(QString nick) { + if(nick == ownNick) setActive(false); + nicks.remove(nick); + if(widget) widget->updateNickList(nicks); +} + +void Buffer::setOwnNick(QString nick) { + ownNick = nick; + if(widget) widget->setOwnNick(nick); +} + +/****************************************************************************************/ + +BufferWidget::BufferWidget(QString netname, QString bufname, bool act, QString own, QList cont, QWidget *parent) : QWidget(parent) { + ui.setupUi(this); + networkName = netname; + bufferName = bufname; + active = act; + contents = cont; + ui.ownNick->clear(); + ui.ownNick->addItem(own); + if(bufname.isEmpty()) { + // Server Buffer + ui.nickTree->hide(); + ui.topicEdit->hide(); + ui.chanSettingsButton->hide(); + } + connect(ui.nickTree, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SLOT(itemExpansionChanged(QTreeWidgetItem*))); + connect(ui.nickTree, SIGNAL(itemCollapsed(QTreeWidgetItem *)), this, SLOT(itemExpansionChanged(QTreeWidgetItem*))); + connect(ui.inputEdit, SIGNAL(returnPressed()), this, SLOT(enterPressed())); + + ui.chatWidget->setFocusProxy(ui.inputEdit); + + opsExpanded = voicedExpanded = usersExpanded = true; + + // Define standard colors + stdCol = "black"; + inactiveCol = "grey"; + noticeCol = "darkblue"; + serverCol = "darkblue"; + errorCol = "red"; + joinCol = "green"; + quitCol = "firebrick"; + partCol = "firebrick"; + kickCol = "firebrick"; + nickCol = "magenta"; + + int i = contents.count() - 100; + if(i < 0) i = 0; + for(int j = 0; j < i; j++) contents.removeAt(0); + renderContents(); + updateTitle(); + show(); +} + +void BufferWidget::updateTitle() { + QString title = QString("%1 in %2 [%3]: %4").arg(ui.ownNick->currentText()).arg(bufferName).arg(networkName).arg(ui.topicEdit->text()); + setWindowTitle(title); +} + +void BufferWidget::enterPressed() { + QStringList lines = ui.inputEdit->text().split('\n', QString::SkipEmptyParts); + foreach(QString msg, lines) { + if(msg.isEmpty()) continue; + emit userInput(msg); + } + ui.inputEdit->clear(); +} + +void BufferWidget::setActive(bool act) { + if(act != active) { + active = act; + renderContents(); + } +} + +void BufferWidget::renderContents() { + QString html; + for(int i = 0; i < contents.count(); i++) { + html += htmlFromMsg(contents[i]); + } + ui.chatWidget->clear(); + ui.chatWidget->setHtml(html); + scrollToEnd(); +} + +void BufferWidget::scrollToEnd() { + QScrollBar *sb = ui.chatWidget->verticalScrollBar(); + sb->setValue(sb->maximum()); + qDebug() << bufferName << "scrolled" << sb->value() << sb->maximum(); +} + +QString BufferWidget::htmlFromMsg(Message msg) { + QString s, n; + QString c = stdCol; + QString user = userFromMask(msg.sender); + QString host = hostFromMask(msg.sender); + QString nick = nickFromMask(msg.sender); + switch(msg.type) { + case Message::Plain: + c = stdCol; n = QString("<%1>").arg(nick); s = msg.text; + break; + case Message::Server: + c = serverCol; s = msg.text; + break; + case Message::Error: + c = errorCol; s = msg.text; + break; + case Message::Join: + c = joinCol; + s = QString(tr("--> %1 (%2@%3) has joined %4")).arg(nick).arg(user).arg(host).arg(bufferName); + break; + case Message::Part: + c = partCol; + s = QString(tr("<-- %1 (%2@%3) has left %4")).arg(nick).arg(user).arg(host).arg(bufferName); + if(!msg.text.isEmpty()) s = QString("%1 (%2)").arg(s).arg(msg.text); + break; + case Message::Kick: + { c = kickCol; + QString victim = msg.text.section(" ", 0, 0); + if(victim == ui.ownNick->currentText()) victim = tr("you"); + QString kickmsg = msg.text.section(" ", 1); + s = QString(tr("--> %1 has kicked %2 from %3")).arg(nick).arg(victim).arg(bufferName); + if(!kickmsg.isEmpty()) s = QString("%1 (%2)").arg(s).arg(kickmsg); + } + break; + case Message::Quit: + c = quitCol; + s = QString(tr("<-- %1 (%2@%3) has quit")).arg(nick).arg(user).arg(host); + if(!msg.text.isEmpty()) s = QString("%1 (%2)").arg(s).arg(msg.text); + break; + case Message::Nick: + c = nickCol; + if(nick == msg.text) s = QString(tr("<-> You are now known as %1")).arg(msg.text); + else s = QString(tr("<-> %1 is now known as %2")).arg(nick).arg(msg.text); + break; + case Message::Mode: + c = serverCol; + if(nick.isEmpty()) s = tr("*** User mode: %1").arg(msg.text); + else s = tr("*** Mode %1 by %2").arg(msg.text).arg(nick); + break; + default: + c = stdCol; n = QString("[%1]").arg(msg.sender); s = msg.text; + break; + } + if(!active) c = inactiveCol; + s.replace('&', "&"); s.replace('<', "<"); s.replace('>', ">"); + QString html = QString("" + "") + .arg(msg.timeStamp.toLocalTime().toString("hh:mm:ss")).arg("darkblue"); + if(!n.isEmpty()) + html += QString("") + .arg(n).arg("royalblue"); + html += QString("""
[%1]
%1
%1
").arg(s).arg(c); + return html; +} + +void BufferWidget::displayMsg(Message msg) { + contents.append(msg); + ui.chatWidget->append(htmlFromMsg(msg)); +} + +void BufferWidget::setOwnNick(QString nick) { + ui.ownNick->clear(); + ui.ownNick->addItem(nick); + updateTitle(); +} + +void BufferWidget::setTopic(QString topic) { + ui.topicEdit->setText(topic); + updateTitle(); +} + +void BufferWidget::updateNickList(VarMap nicks) { + ui.nickTree->clear(); + if(nicks.count() != 1) ui.nickTree->setHeaderLabel(tr("%1 Users").arg(nicks.count())); + else ui.nickTree->setHeaderLabel(tr("1 User")); + QTreeWidgetItem *ops = new QTreeWidgetItem(); + QTreeWidgetItem *voiced = new QTreeWidgetItem(); + QTreeWidgetItem *users = new QTreeWidgetItem(); + // To sort case-insensitive, we have to put all nicks in a map which is sorted by (lowercase) key... + QMap sorted; + foreach(QString n, nicks.keys()) { sorted[n.toLower()] = n; } + foreach(QString n, sorted.keys()) { + QString nick = sorted[n]; + QString mode = nicks[nick].toMap()["Channels"].toMap()[bufferName].toMap()["Mode"].toString(); + if(mode.contains('o')) { new QTreeWidgetItem(ops, QStringList(QString("@%1").arg(nick))); } + else if(mode.contains('v')) { new QTreeWidgetItem(voiced, QStringList(QString("+%1").arg(nick))); } + else new QTreeWidgetItem(users, QStringList(nick)); + } + if(ops->childCount()) { + ops->setText(0, tr("%1 Operators").arg(ops->childCount())); + ui.nickTree->addTopLevelItem(ops); + ops->setExpanded(opsExpanded); + } else delete ops; + if(voiced->childCount()) { + voiced->setText(0, tr("%1 Voiced").arg(voiced->childCount())); + ui.nickTree->addTopLevelItem(voiced); + voiced->setExpanded(voicedExpanded); + } else delete voiced; + if(users->childCount()) { + users->setText(0, tr("%1 Users").arg(users->childCount())); + ui.nickTree->addTopLevelItem(users); + users->setExpanded(usersExpanded); + } else delete users; +} + +void BufferWidget::itemExpansionChanged(QTreeWidgetItem *item) { + if(item->child(0)->text(0).startsWith('@')) opsExpanded = item->isExpanded(); + else if(item->child(0)->text(0).startsWith('+')) voicedExpanded = item->isExpanded(); + else usersExpanded = item->isExpanded(); +} + diff --git a/gui/buffer.h b/gui/buffer.h new file mode 100644 index 00000000..5df19aa7 --- /dev/null +++ b/gui/buffer.h @@ -0,0 +1,118 @@ +/*************************************************************************** + * Copyright (C) 2005/06 by The Quassel Team * + * devel@quassel-irc.org * + * * + * 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef _BUFFER_H_ +#define _BUFFER_H_ + +#include +#include + +#include "ui_bufferwidget.h" + +#include "global.h" +#include "message.h" + +class BufferWidget; + +class Buffer : public QObject { + Q_OBJECT + + public: + Buffer(QString network, QString buffer); + ~Buffer(); + + bool isActive() { return active; } + public: + QWidget *getWidget(); + + signals: + void userInput(QString, QString, QString); + void nickListChanged(QStringList); + + public slots: + void setActive(bool active = true); + void displayMsg(Message); + //void recvStatusMsg(QString msg); + void setTopic(QString); + //void setNicks(QStringList); + void addNick(QString nick, VarMap props); + void renameNick(QString oldnick, QString newnick); + void removeNick(QString nick); + void updateNick(QString nick, VarMap props); + void setOwnNick(QString nick); + + QWidget * showWidget(QWidget *parent = 0); + void hideWidget(); + + void scrollToEnd(); + + private slots: + void userInput(QString); + + private: + bool active; + BufferWidget *widget; + VarMap nicks; + QString topic; + QString ownNick; + QString networkName, bufferName; + + QList contents; +}; + +class BufferWidget : public QWidget { + Q_OBJECT + + public: + BufferWidget(QString netname, QString bufname, bool active, QString ownNick, QList contents, QWidget *parent = 0); + + void setActive(bool act = true); + signals: + void userInput(QString); + + public slots: + void displayMsg(Message); + void updateNickList(VarMap nicks); + void setOwnNick(QString ownNick); + void setTopic(QString topic); + void renderContents(); + void scrollToEnd(); + + private slots: + void enterPressed(); + void itemExpansionChanged(QTreeWidgetItem *); + void updateTitle(); + + private: + Ui::BufferWidget ui; + bool active; + QList contents; + + QString stdCol, errorCol, noticeCol, joinCol, quitCol, partCol, kickCol, serverCol, nickCol, inactiveCol; + QString CSS; + QString networkName; + QString bufferName; + + bool opsExpanded, voicedExpanded, usersExpanded; + + QString htmlFromMsg(Message); +}; + +#endif diff --git a/gui/coreconnectdlg.h b/gui/coreconnectdlg.h index e0e85226..b3a82f4c 100644 --- a/gui/coreconnectdlg.h +++ b/gui/coreconnectdlg.h @@ -22,7 +22,7 @@ #define _CORECONNECTDLG_H #include "coreconnectdlg.h" -#include "ui_coreconnectdlg.h" +#include "gui/ui_coreconnectdlg.h" class CoreConnectDlg: public QDialog { Q_OBJECT diff --git a/gui/guiproxy.cpp b/gui/guiproxy.cpp index 6dd5ea37..3657623d 100644 --- a/gui/guiproxy.cpp +++ b/gui/guiproxy.cpp @@ -28,9 +28,12 @@ void GUIProxy::recv(CoreSignal sig, QVariant arg1, QVariant arg2, QVariant arg3) //qDebug() << "[GUI] Received signal:" << sig <()); break; + case CS_DISPLAY_MSG: emit csDisplayMsg(arg1.toString(), arg2.value()); break; case CS_DISPLAY_STATUS_MSG: emit csDisplayStatusMsg(arg1.toString(), arg2.toString()); break; case CS_MODE_SET: emit csModeSet(arg1.toString(), arg2.toString(), arg3.toString()); break; case CS_TOPIC_SET: emit csTopicSet(arg1.toString(), arg2.toString(), arg3.toString()); break; diff --git a/gui/guiproxy.h b/gui/guiproxy.h index 92ac0d27..c8ac87e6 100644 --- a/gui/guiproxy.h +++ b/gui/guiproxy.h @@ -49,7 +49,10 @@ class GUIProxy : public QObject { signals: void csCoreState(QVariant); - void csDisplayMsg(QString, QString, Message); + void csServerState(QString, QVariant); + void csServerConnected(QString); + void csServerDisconnected(QString); + void csDisplayMsg(QString, Message); void csDisplayStatusMsg(QString, QString); void csUpdateGlobalData(QString key, QVariant data); void csGlobalDataChanged(QString key); diff --git a/gui/mainwin.cpp b/gui/mainwin.cpp index 8a21a9d3..6ea4091c 100644 --- a/gui/mainwin.cpp +++ b/gui/mainwin.cpp @@ -22,14 +22,18 @@ #include #include "global.h" +#include "message.h" +#include "guiproxy.h" #include "mainwin.h" -#include "channelwidget.h" +#include "buffer.h" +#include "networkview.h" #include "serverlist.h" #include "coreconnectdlg.h" MainWin::MainWin() : QMainWindow() { ui.setupUi(this); + widget = 0; setWindowTitle("Quassel IRC"); setWindowIcon(QIcon(":/qirc-icon.png")); @@ -41,24 +45,68 @@ MainWin::MainWin() : QMainWindow() { move(s.value("MainWinPos", QPoint(50, 50)).toPoint()); s.endGroup(); - //workspace = new QWorkspace(this); - //setCentralWidget(workspace); + workspace = new QWorkspace(this); + setCentralWidget(workspace); statusBar()->showMessage(tr("Waiting for core...")); - setEnabled(false); + + netView = new NetworkView("", this); + netView->setAllowedAreas(Qt::RightDockWidgetArea|Qt::LeftDockWidgetArea); + addDockWidget(Qt::LeftDockWidgetArea, netView); + connect(netView, SIGNAL(bufferSelected(QString, QString)), this, SLOT(showBuffer(QString, QString))); + connect(this, SIGNAL(bufferSelected(QString, QString)), netView, SLOT(selectBuffer(QString, QString))); + + connect(guiProxy, SIGNAL(csServerState(QString, QVariant)), this, SLOT(recvNetworkState(QString, QVariant))); + connect(guiProxy, SIGNAL(csServerConnected(QString)), this, SLOT(networkConnected(QString))); + connect(guiProxy, SIGNAL(csServerDisconnected(QString)), this, SLOT(networkDisconnected(QString))); + connect(guiProxy, SIGNAL(csDisplayMsg(QString, Message)), this, SLOT(recvMessage(QString, Message))); + connect(guiProxy, SIGNAL(csDisplayStatusMsg(QString, QString)), this, SLOT(recvStatusMsg(QString, QString))); + connect(guiProxy, SIGNAL(csTopicSet(QString, QString, QString)), this, SLOT(setTopic(QString, QString, QString))); + connect(guiProxy, SIGNAL(csSetNicks(QString, QString, QStringList)), this, SLOT(setNicks(QString, QString, QStringList))); + connect(guiProxy, SIGNAL(csNickAdded(QString, QString, VarMap)), this, SLOT(addNick(QString, QString, VarMap))); + connect(guiProxy, SIGNAL(csNickRemoved(QString, QString)), this, SLOT(removeNick(QString, QString))); + connect(guiProxy, SIGNAL(csNickRenamed(QString, QString, QString)), this, SLOT(renameNick(QString, QString, QString))); + connect(guiProxy, SIGNAL(csNickUpdated(QString, QString, VarMap)), this, SLOT(updateNick(QString, QString, VarMap))); + connect(guiProxy, SIGNAL(csOwnNickSet(QString, QString)), this, SLOT(setOwnNick(QString, QString))); + connect(this, SIGNAL(sendInput( QString, QString, QString )), guiProxy, SLOT(gsUserInput(QString, QString, QString))); + show(); syncToCore(); - setEnabled(true); + statusBar()->showMessage(tr("Ready.")); + + buffersUpdated(); + serverListDlg = new ServerListDlg(this); serverListDlg->setVisible(serverListDlg->showOnStartup()); setupMenus(); - //identitiesAct = settingsMenu->addAction(QIcon(":/default/identity.png"), tr("&Identities..."), serverListDlg, SLOT(editIdentities())); - //showServerList(); - IrcWidget *cw = new IrcWidget(this); - setCentralWidget(cw); - //workspace->addWindow(cw); - //cw->showMaximized(); - statusBar()->showMessage(tr("Ready.")); - cw->setFocus(); + + // replay backlog + foreach(QString net, coreBackLog.keys()) { + while(coreBackLog[net].count()) { + recvMessage(net, coreBackLog[net].takeFirst()); + } + } + /* + foreach(QString key, buffers.keys()) { + foreach(Buffer *b, buffers[key].values()) { + QWidget *widget = b->showWidget(this); + workspace->addWindow(widget); + widget->show(); + } + } + */ + s.beginGroup("Buffers"); + QString net = s.value("CurrentNetwork", "").toString(); + QString buf = s.value("CurrentBuffer", "").toString(); + s.endGroup(); + if(!net.isEmpty()) { + if(buffers.contains(net)) { + if(buffers[net].contains(buf)) { + showBuffer(net, buf); + } else { + showBuffer(net, ""); + } + } + } } void MainWin::setupMenus() { @@ -66,27 +114,6 @@ void MainWin::setupMenus() { connect(ui.actionEditIdentities, SIGNAL(activated()), serverListDlg, SLOT(editIdentities())); } -void MainWin::syncToCore() { - if(global->getData("CoreReady").toBool()) return; - // ok, apparently we are running as standalone GUI - coreConnectDlg = new CoreConnectDlg(this); - if(coreConnectDlg->exec() != QDialog::Accepted) { - //qApp->quit(); - exit(1); - } - VarMap state = coreConnectDlg->getCoreState().toMap()["CoreData"].toMap(); - delete coreConnectDlg; - QString key; - foreach(key, state.keys()) { - global->updateData(key, state[key]); - } - if(!global->getData("CoreReady").toBool()) { - QMessageBox::critical(this, tr("Fatal Error"), tr("Could not synchronize with Quassel Core!
Quassel GUI will be aborted."), QMessageBox::Abort); - //qApp->quit(); - exit(1); - } -} - void MainWin::showServerList() { // if(!serverListDlg) { // serverListDlg = new ServerListDlg(this); @@ -102,8 +129,164 @@ void MainWin::closeEvent(QCloseEvent *event) s.setValue("MainWinSize", size()); s.setValue("MainWinPos", pos()); s.endGroup(); + s.beginGroup("Buffers"); + s.setValue("CurrentNetwork", currentNetwork); + s.setValue("CurrentBuffer", currentBuffer); + s.endGroup(); event->accept(); //} else { //event->ignore(); //} } + +void MainWin::showBuffer(QString net, QString buf) { + currentBuffer = buf; currentNetwork = net; + Buffer *b = getBuffer(net, buf); + QWidget *old = widget; + widget = b->showWidget(this); + if(widget == old) return; + workspace->addWindow(widget); + widget->showMaximized(); + if(old) { old->close(); old->setParent(this); } + workspace->setActiveWindow(widget); + //widget->setFocus(); + //workspace->setFocus(); + //widget->activateWindow(); + widget->setFocus(Qt::MouseFocusReason); + focusNextChild(); + //workspace->tile(); + emit bufferSelected(net, buf); +} + +void MainWin::networkConnected(QString net) { + connected[net] = true; + Buffer *b = getBuffer(net, ""); + b->setActive(true); + b->displayMsg(Message::server("", tr("Connected."))); + buffersUpdated(); +} + +void MainWin::networkDisconnected(QString net) { + //getBuffer(net, "")->setActive(false); + foreach(QString buf, buffers[net].keys()) { + Buffer *b = getBuffer(net, buf); + b->displayMsg(Message::server(buf, tr("Server disconnected."))); + b->setActive(false); + + } + connected[net] = false; +} + +Buffer * MainWin::getBuffer(QString net, QString buf) { + if(!buffers[net].contains(buf)) { + Buffer *b = new Buffer(net, buf); + b->setOwnNick(ownNick[net]); + connect(b, SIGNAL(userInput(QString, QString, QString)), this, SLOT(userInput(QString, QString, QString))); + buffers[net][buf] = b; + buffersUpdated(); + } + return buffers[net][buf]; +} + +void MainWin::buffersUpdated() { + netView->buffersUpdated(buffers); +} + +void MainWin::recvNetworkState(QString net, QVariant state) { + connected[net] = true; + setOwnNick(net, state.toMap()["OwnNick"].toString()); + getBuffer(net, "")->setActive(true); + VarMap t = state.toMap()["Topics"].toMap(); + VarMap n = state.toMap()["Nicks"].toMap(); + foreach(QString buf, t.keys()) { + getBuffer(net, buf)->setActive(true); + setTopic(net, buf, t[buf].toString()); + } + foreach(QString nick, n.keys()) { + addNick(net, nick, n[nick].toMap()); + } + buffersUpdated(); +} + +void MainWin::recvMessage(QString net, Message msg) { + Buffer *b = getBuffer(net, msg.target); + b->displayMsg(msg); +} + +void MainWin::recvStatusMsg(QString net, QString msg) { + recvMessage(net, Message::server("", QString("[STATUS] %1").arg(msg))); + +} + +void MainWin::userInput(QString net, QString buf, QString msg) { + emit sendInput(net, buf, msg); +} + +void MainWin::setTopic(QString net, QString buf, QString topic) { + if(!connected[net]) return; + Buffer *b = getBuffer(net, buf); + b->setTopic(topic); + buffersUpdated(); + //if(!b->isActive()) { + // b->setActive(true); + // buffersUpdated(); + //} +} + +void MainWin::setNicks(QString net, QString buf, QStringList nicks) { + Q_ASSERT(false); +} + +void MainWin::addNick(QString net, QString nick, VarMap props) { + if(!connected[net]) return; + nicks[net][nick] = props; + VarMap chans = props["Channels"].toMap(); + QStringList c = chans.keys(); + foreach(QString bufname, c) { + getBuffer(net, bufname)->addNick(nick, props); + } + buffersUpdated(); +} + +void MainWin::renameNick(QString net, QString oldnick, QString newnick) { + if(!connected[net]) return; + QStringList chans = nicks[net][oldnick]["Channels"].toMap().keys(); + foreach(QString c, chans) { + getBuffer(net, c)->renameNick(oldnick, newnick); + } + nicks[net][newnick] = nicks[net].take(oldnick); +} + +void MainWin::updateNick(QString net, QString nick, VarMap props) { + if(!connected[net]) return; + QStringList oldchans = nicks[net][nick]["Channels"].toMap().keys(); + QStringList newchans = props["Channels"].toMap().keys(); + foreach(QString c, newchans) { + if(oldchans.contains(c)) getBuffer(net, c)->updateNick(nick, props); + else getBuffer(net, c)->addNick(nick, props); + } + foreach(QString c, oldchans) { + if(!newchans.contains(c)) getBuffer(net, c)->removeNick(nick); + } + nicks[net][nick] = props; + buffersUpdated(); +} + +void MainWin::removeNick(QString net, QString nick) { + if(!connected[net]) return; + VarMap chans = nicks[net][nick]["Channels"].toMap(); + foreach(QString bufname, chans.keys()) { + getBuffer(net, bufname)->removeNick(nick); + } + nicks[net].remove(nick); + buffersUpdated(); +} + +void MainWin::setOwnNick(QString net, QString nick) { + if(!connected[net]) return; + ownNick[net] = nick; + foreach(Buffer *b, buffers[net].values()) { + b->setOwnNick(nick); + } +} + diff --git a/gui/mainwin.h b/gui/mainwin.h index a6d1e1ec..d8996953 100644 --- a/gui/mainwin.h +++ b/gui/mainwin.h @@ -24,8 +24,13 @@ #include #include "gui/ui_mainwin.h" +#include "global.h" +#include "message.h" + class ServerListDlg; class CoreConnectDlg; +class NetworkView; +class Buffer; class MainWin : public QMainWindow { Q_OBJECT @@ -36,19 +41,51 @@ class MainWin : public QMainWindow { protected: void closeEvent(QCloseEvent *event); + signals: + void sendInput(QString network, QString buffer, QString message); + void bufferSelected(QString net, QString buffer); + private slots: + void userInput(QString, QString, QString); + void networkConnected(QString); + void networkDisconnected(QString); + void recvNetworkState(QString, QVariant); + void recvMessage(QString network, Message message); + void recvStatusMsg(QString network, QString message); + void setTopic(QString, QString, QString); + void setNicks(QString, QString, QStringList); + void addNick(QString net, QString nick, VarMap props); + void removeNick(QString net, QString nick); + void renameNick(QString net, QString oldnick, QString newnick); + void updateNick(QString net, QString nick, VarMap props); + void setOwnNick(QString net, QString nick); + void showServerList(); + void showBuffer(QString net, QString buf); + private: Ui::MainWin ui; void setupMenus(); - void syncToCore(); + void syncToCore(); // implemented in main_mono.cpp or main_gui.cpp + Buffer * getBuffer(QString net, QString buf); + void buffersUpdated(); QWorkspace *workspace; + QWidget *widget; ServerListDlg *serverListDlg; CoreConnectDlg *coreConnectDlg; + + QString currentNetwork, currentBuffer; + QHash > buffers; + QHash > nicks; + QHash connected; + QHash ownNick; + QHash > coreBackLog; + + NetworkView *netView; }; #endif diff --git a/gui/networkview.cpp b/gui/networkview.cpp new file mode 100644 index 00000000..be08b3b5 --- /dev/null +++ b/gui/networkview.cpp @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2005/06 by The Quassel Team * + * devel@quassel-irc.org * + * * + * 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "global.h" +#include "networkview.h" + +NetworkViewWidget::NetworkViewWidget(QWidget *parent) : QWidget(parent) { + ui.setupUi(this); + +} + +QSize NetworkViewWidget::sizeHint() const { + return QSize(150,100); + +} + +/**************************************************************************/ + +NetworkView::NetworkView(QString net, QWidget *parent) : network(net), QDockWidget(parent) { + if(!net.isEmpty()) setWindowTitle(net); + else setWindowTitle(tr("All Buffers")); + setWidget(new NetworkViewWidget(this)); + tree = qobject_cast(widget())->tree(); + tree->setSortingEnabled(false); + connect(tree, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this, SLOT(itemClicked(QTreeWidgetItem*))); +} + +void NetworkView::buffersUpdated(BufferHash buffers) { + tree->clear(); items.clear(); + if(network.isEmpty()) { + tree->setHeaderLabel(tr("Networks")); + foreach(QString net, buffers.keys()) { + QTreeWidgetItem *netItem = new QTreeWidgetItem(QStringList(net)); + foreach(QString buf, buffers[net].keys()) { + Buffer *b = buffers[net][buf]; + QStringList label; + if(buf.isEmpty()) label << tr("Status"); + else label << buf; + QTreeWidgetItem *item = new QTreeWidgetItem(netItem, label); + items[net][buf] = item; + VarMap d; + d["Network"] = net; + d["Buffer"] = buf; + item->setData(0, Qt::UserRole, d); + if(!b->isActive()) { + item->setForeground(0, QColor("grey")); + } + } + VarMap d; + d["Network"] = net; d["Buffer"] = ""; + netItem->setData(0, Qt::UserRole, d); + netItem->setFlags(netItem->flags() & ~Qt::ItemIsSelectable); + tree->addTopLevelItem(netItem); + netItem->setExpanded(true); + } + if(items[currentNetwork][currentBuffer]) items[currentNetwork][currentBuffer]->setSelected(true); + } +} + +void NetworkView::itemClicked(QTreeWidgetItem *item) { + if(network.isEmpty()) { + VarMap d = item->data(0, Qt::UserRole).toMap(); + QString net = d["Network"].toString(); QString buf = d["Buffer"].toString(); + if(!net.isEmpty()) { + emit bufferSelected(net, buf); + currentNetwork = net; currentBuffer = buf; + } + } +} + +void NetworkView::selectBuffer(QString net, QString buf) { + if(items[net][buf]) items[net][buf]->setSelected(true); + currentNetwork = net; currentBuffer = buf; +} diff --git a/gui/networkview.h b/gui/networkview.h new file mode 100644 index 00000000..ac870773 --- /dev/null +++ b/gui/networkview.h @@ -0,0 +1,79 @@ +/*************************************************************************** + * Copyright (C) 2005/06 by The Quassel Team * + * devel@quassel-irc.org * + * * + * 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef _NETWORKVIEW_H_ +#define _NETWORKVIEW_H_ + +#include +#include "ui_networkview.h" +#include "buffer.h" + +typedef QHash > BufferHash; + +class NetworkViewWidget : public QWidget { + Q_OBJECT + + public: + NetworkViewWidget(QWidget *parent = 0); + QTreeWidget *tree() { return ui.tree; } + + virtual QSize sizeHint () const; + + public slots: + + + signals: + void bufferSelected(QString net, QString buf); + + private slots: + + + private: + Ui::NetworkViewWidget ui; + +}; + +class NetworkView : public QDockWidget { + Q_OBJECT + + public: + NetworkView(QString network, QWidget *parent = 0); + + public slots: + void buffersUpdated(BufferHash); + void selectBuffer(QString net, QString buf); + + signals: + void bufferSelected(QString net, QString buf); + + private slots: + void itemClicked(QTreeWidgetItem *item); + + private: + QString network; + QString currentNetwork, currentBuffer; + QHash > buffers; + QHash > items; + QTreeWidget *tree; + +}; + + +#endif diff --git a/gui/ui/bufferwidget.ui b/gui/ui/bufferwidget.ui new file mode 100644 index 00000000..b3bab0d7 --- /dev/null +++ b/gui/ui/bufferwidget.ui @@ -0,0 +1,271 @@ + + BufferWidget + + + + 0 + 0 + 713 + 519 + + + + + 3 + 3 + 0 + 0 + + + + + 200 + 100 + + + + Qt::DefaultContextMenu + + + YourNickname #quassel Network: The Topic + + + + 1 + + + 6 + + + + + 0 + + + 6 + + + + + Qt::ClickFocus + + + true + + + + + + + Qt::NonModal + + + + 0 + 0 + 0 + 0 + + + + Qt::LeftToRight + + + ... + + + + + + + + + + 7 + 5 + 0 + 0 + + + + 1 + + + Qt::Horizontal + + + + + 7 + 5 + 4 + 0 + + + + + Bitstream Vera Sans Mono + 8 + 50 + false + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Bitstream Vera Sans Mono'; font-size:8pt; font-weight:400; font-style:normal; text-decoration:none;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Nimbus Mono L';"></p></body></html> + + + + + + 5 + 5 + 1 + 0 + + + + Qt::LeftToRight + + + true + + + Qt::ElideRight + + + true + + + false + + + false + + + 1 + + + + 32 Users + + + + + @ Operators + + + + New Sub Item + + + + + New Item + + + + + New Item + + + + + + 4 Voiced + + + + New Item + + + + + New Item + + + + + New Item + + + + + + 19 Users + + + + New Sub Item + + + + + New Item + + + + + New Item + + + + + New Item + + + + + + + + + + 0 + + + 6 + + + + + QComboBox::AdjustToContents + + + + + + + + 3 + 0 + 0 + 0 + + + + + + + + + + + ChannelWidgetInput + QLineEdit +
channelwidgetinput.h
+
+
+ + inputEdit + ownNick + nickTree + topicEdit + chanSettingsButton + chatWidget + + + +
diff --git a/gui/ui/networkview.ui b/gui/ui/networkview.ui new file mode 100644 index 00000000..a30e8681 --- /dev/null +++ b/gui/ui/networkview.ui @@ -0,0 +1,52 @@ + + NetworkViewWidget + + + + 0 + 0 + 151 + 294 + + + + + 5 + 1 + 0 + 0 + + + + Form + + + + 9 + + + 6 + + + + + + 5 + 1 + 0 + 0 + + + + + 16777215 + 16777215 + + + + + + + + + diff --git a/gui/ui/networkwidget.ui b/gui/ui/networkwidget.ui deleted file mode 100644 index 65f20287..00000000 --- a/gui/ui/networkwidget.ui +++ /dev/null @@ -1,29 +0,0 @@ - - NetworkWidget - - - - 0 - 0 - 236 - 358 - - - - Form - - - - 9 - - - 6 - - - - - - - - - diff --git a/main/global.h b/main/global.h index 1faf4d18..1c366611 100644 --- a/main/global.h +++ b/main/global.h @@ -24,7 +24,7 @@ /** The protocol version we use fo the communication between core and GUI */ #define GUI_PROTOCOL 1 -#define BACKLOG_FORMAT 1 +#define BACKLOG_FORMAT 2 #define BACKLOG_STRING "QuasselIRC Backlog File" class Global; diff --git a/main/main_core.cpp b/main/main_core.cpp index b7f0a8e6..607aada7 100644 --- a/main/main_core.cpp +++ b/main/main_core.cpp @@ -40,7 +40,6 @@ int main(int argc, char **argv) { global = new Global(); coreProxy = new CoreProxy(); - core = new Core(); //Logger *logger = new Logger(); //Quassel::setLogger(logger); diff --git a/main/main_gui.cpp b/main/main_gui.cpp index 7dc2d53f..b6e15b09 100644 --- a/main/main_gui.cpp +++ b/main/main_gui.cpp @@ -20,10 +20,12 @@ #include +#include #include #include "global.h" #include "guiproxy.h" +#include "coreconnectdlg.h" #include "util.h" #include "mainwin.h" @@ -46,6 +48,37 @@ int main(int argc, char **argv) { delete global; } +void MainWin::syncToCore() { + Q_ASSERT(!global->getData("CoreReady").toBool()); + // ok, we are running as standalone GUI + coreConnectDlg = new CoreConnectDlg(this); + if(coreConnectDlg->exec() != QDialog::Accepted) { + //qApp->quit(); + exit(1); + } + VarMap state = coreConnectDlg->getCoreState().toMap(); + delete coreConnectDlg; + VarMap data = state["CoreData"].toMap(); + QString key; + foreach(key, data.keys()) { + global->updateData(key, data[key]); + } + if(!global->getData("CoreReady").toBool()) { + QMessageBox::critical(this, tr("Fatal Error"), tr("Could not synchronize with Quassel Core!
Quassel GUI will be aborted."), QMessageBox::Abort); + //qApp->quit(); + exit(1); + } + foreach(QString net, state["CoreBackLog"].toMap().keys()) { + QByteArray logbuf = state["CoreBackLog"].toMap()[net].toByteArray(); + QDataStream in(&logbuf, QIODevice::ReadOnly); in.setVersion(QDataStream::Qt_4_2); + while(!in.atEnd()) { + Message msg; in >> msg; + coreBackLog[net].append(msg); + } + qDebug() << net << coreBackLog[net].count(); + } +} + GUIProxy::GUIProxy() { if(guiProxy) qFatal("Trying to instantiate more than one CoreProxy object!"); diff --git a/main/main_mono.cpp b/main/main_mono.cpp index 7700b27f..9384294a 100644 --- a/main/main_mono.cpp +++ b/main/main_mono.cpp @@ -41,7 +41,6 @@ int main(int argc, char **argv) { global = new Global(); guiProxy = new GUIProxy(); coreProxy = new CoreProxy(); - core = new Core(); MainWin mainWin; mainWin.show(); @@ -53,6 +52,13 @@ int main(int argc, char **argv) { return exitCode; } +void MainWin::syncToCore() { + Q_ASSERT(global->getData("CoreReady").toBool()); + coreBackLog = core->getBackLog(); + // NOTE: We don't need to request server states, because in the monolithic version there can't be + // any servers connected at this stage... +} + void CoreProxy::sendToGUI(CoreSignal sig, QVariant arg1, QVariant arg2, QVariant arg3) { guiProxy->recv(sig, arg1, arg2, arg3); } diff --git a/main/message.cpp b/main/message.cpp index e19e74c5..160c8288 100644 --- a/main/message.cpp +++ b/main/message.cpp @@ -21,20 +21,74 @@ #include "message.h" #include +Message Message::plain(QString _target, QString _text, QString _sender, Flags _flags) { + return Message(_target, Plain, _text, _sender, _flags); +} + +Message Message::notice(QString _target, QString _text, QString _sender, Flags _flags) { + return Message(_target, Notice, _text, _sender, _flags); +} + +Message Message::action(QString _target, QString _text, QString _sender, Flags _flags) { + return Message(_target, Action, _text, _sender, _flags); +} + +Message Message::kick(QString _target, QString _text, QString _sender, Flags _flags) { + return Message(_target, Kick, _text, _sender, _flags); +} + +Message Message::join(QString _target, QString _text, QString _sender, Flags _flags) { + return Message(_target, Join, _text, _sender, _flags); +} + +Message Message::part(QString _target, QString _text, QString _sender, Flags _flags) { + return Message(_target, Part, _text, _sender, _flags); +} + +Message Message::nick(QString _target, QString _text, QString _sender, Flags _flags) { + return Message(_target, Nick, _text, _sender, _flags); +} + +Message Message::mode(QString _target, QString _text, QString _sender, Flags _flags) { + return Message(_target, Mode, _text, _sender, _flags); +} + +Message Message::quit(QString _target, QString _text, QString _sender, Flags _flags) { + return Message(_target, Quit, _text, _sender, _flags); +} + +Message Message::kill(QString _target, QString _text, QString _sender, Flags _flags) { + return Message(_target, Kill, _text, _sender, _flags); +} + +Message Message::server(QString _target, QString _text, QString _sender, Flags _flags) { + return Message(_target, Server, _text, _sender, _flags); +} + +Message Message::info(QString _target, QString _text, QString _sender, Flags _flags) { + return Message(_target, Info, _text, _sender, _flags); +} + +Message Message::error(QString _target, QString _text, QString _sender, Flags _flags) { + return Message(_target, Error, _text, _sender, _flags); +} + QDataStream &operator<<(QDataStream &out, const Message &msg) { - out << (quint32)msg.timeStamp.toTime_t() << (quint8)msg.type << (quint8)msg.flags << msg.sender.toUtf8() << msg.msg.toUtf8(); + out << (quint32)msg.timeStamp.toTime_t() << (quint8)msg.type << (quint8)msg.flags + << msg.target.toUtf8() << msg.sender.toUtf8() << msg.text.toUtf8(); return out; } QDataStream &operator>>(QDataStream &in, Message &msg) { quint8 t, f; quint32 ts; - QByteArray s, m; - in >> ts >> t >> f >> s >> m; + QByteArray s, m, targ; + in >> ts >> t >> f >> targ >> s >> m; msg.type = (Message::Type)t; msg.flags = (Message::Flags)f; msg.timeStamp = QDateTime::fromTime_t(ts); + msg.target = QString::fromUtf8(targ); msg.sender = QString::fromUtf8(s); - msg.msg = QString::fromUtf8(m); + msg.text = QString::fromUtf8(m); return in; } diff --git a/main/message.h b/main/message.h index 80b2b8b0..711d7ad4 100644 --- a/main/message.h +++ b/main/message.h @@ -26,23 +26,37 @@ struct Message { /** The different types a message can have for display */ - enum Type { Msg, Notice, Action, Nick, Mode, Join, Part, Quit, Kick, Kill, Server, Info, Error }; + enum Type { Plain, Notice, Action, Nick, Mode, Join, Part, Quit, Kick, Kill, Server, Info, Error }; enum Flags { None = 0, Self = 1, Highlight = 2 }; Type type; Flags flags; + QString target; QString sender; - QString msg; + QString text; QDateTime timeStamp; - Message(Type _type = Msg, QString _msg = "", QString _sender = "", Flags _flags = None) - : msg(_msg), sender(_sender), type(_type), flags(_flags) { timeStamp = QDateTime::currentDateTime().toUTC(); }; - + Message(QString _target = "", Type _type = Plain, QString _text = "", QString _sender = "", Flags _flags = None) + : target(_target), text(_text), sender(_sender), type(_type), flags(_flags) { timeStamp = QDateTime::currentDateTime().toUTC(); } + + static Message plain(QString _target, QString _text, QString _sender = "", Flags _flags = None); + static Message notice(QString _target, QString _text, QString _sender = "", Flags _flags = None); + static Message action(QString _target, QString _text, QString _sender = "", Flags _flags = None); + static Message nick(QString _target, QString _text, QString _sender = "", Flags _flags = None); + static Message mode(QString _target, QString _text, QString _sender = "", Flags _flags = None); + static Message join(QString _target, QString _text, QString _sender = "", Flags _flags = None); + static Message part(QString _target, QString _text, QString _sender = "", Flags _flags = None); + static Message quit(QString _target, QString _text, QString _sender = "", Flags _flags = None); + static Message kick(QString _target, QString _text, QString _sender = "", Flags _flags = None); + static Message kill(QString _target, QString _text, QString _sender = "", Flags _flags = None); + static Message server(QString _target, QString _text, QString _sender = "", Flags _flags = None); + static Message info(QString _target, QString _text, QString _sender = "", Flags _flags = None); + static Message error(QString _target, QString _text, QString _sender = "", Flags _flags = None); }; QDataStream &operator<<(QDataStream &out, const Message &msg); QDataStream &operator>>(QDataStream &in, Message &msg); -Q_DECLARE_METATYPE(Message) +Q_DECLARE_METATYPE(Message); #endif diff --git a/main/proxy_common.h b/main/proxy_common.h index e06a9ac1..5fefd563 100644 --- a/main/proxy_common.h +++ b/main/proxy_common.h @@ -25,7 +25,8 @@ enum GUISignal { GS_CLIENT_INIT, GS_USER_INPUT, GS_REQUEST_CONNECT, GS_UPDATE_GL }; -enum CoreSignal { CS_CORE_STATE, CS_DISPLAY_MSG, CS_DISPLAY_STATUS_MSG, CS_UPDATE_GLOBAL_DATA, +enum CoreSignal { CS_CORE_STATE, CS_SERVER_CONNECTED, CS_SERVER_DISCONNECTED, CS_SERVER_STATE, + CS_DISPLAY_MSG, CS_DISPLAY_STATUS_MSG, CS_UPDATE_GLOBAL_DATA, CS_MODE_SET, CS_TOPIC_SET, CS_SET_NICKS, CS_NICK_ADDED, CS_NICK_REMOVED, CS_NICK_RENAMED, CS_NICK_UPDATED, CS_OWN_NICK_SET, diff --git a/main/util.cpp b/main/util.cpp index e4c6478d..b8652532 100644 --- a/main/util.cpp +++ b/main/util.cpp @@ -45,7 +45,7 @@ bool isChannelName(QString str) { void writeDataToDevice(QIODevice *dev, const QVariant &item) { QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); - out.setVersion(QDataStream::Qt_4_1); + out.setVersion(QDataStream::Qt_4_2); out << (quint32)0 << item; out.device()->seek(0); out << (quint32)(block.size() - sizeof(quint32)); @@ -54,7 +54,7 @@ void writeDataToDevice(QIODevice *dev, const QVariant &item) { bool readDataFromDevice(QIODevice *dev, quint32 &blockSize, QVariant &item) { QDataStream in(dev); - in.setVersion(QDataStream::Qt_4_1); + in.setVersion(QDataStream::Qt_4_2); if(blockSize == 0) { if(dev->bytesAvailable() < (int)sizeof(quint32)) return false; diff --git a/network/CMakeLists.txt b/network/CMakeLists.txt index 0ce6c67d..693ecedb 100644 --- a/network/CMakeLists.txt +++ b/network/CMakeLists.txt @@ -1,6 +1,6 @@ -SET(network_SRCS server.cpp buffer.cpp) +SET(network_SRCS server.cpp) SET(network_HDRS) -SET(network_MOCS server.h buffer.h) +SET(network_MOCS server.h) QT4_WRAP_CPP(_MOC ${network_MOCS}) ADD_LIBRARY(network ${_MOC} ${network_SRCS} ${network_HDRS}) diff --git a/network/buffer.cpp b/network/buffer.cpp index 61cd6b3d..d0b500a3 100644 --- a/network/buffer.cpp +++ b/network/buffer.cpp @@ -18,8 +18,10 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -#include "buffer.h" +/* OBSOLETE */ +#include "buffer.h" +/* Buffer::Buffer(QString n) { _name = n; @@ -27,3 +29,4 @@ Buffer::Buffer(QString n) { } +*/ \ No newline at end of file diff --git a/network/buffer.h b/network/buffer.h index cb77fff0..8429cfc3 100644 --- a/network/buffer.h +++ b/network/buffer.h @@ -18,12 +18,14 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -#ifndef _BUFFER_H_ -#define _BUFFER_H_ +/* THIS CODE IS OBSOLETE, PENDING REMOVAL */ + +#ifndef _BUFFER_OLD_H_ +#define _BUFFER_OLD_H_ #include -class Buffer : public QObject { +class Buffer_old : public QObject { Q_OBJECT public: diff --git a/network/server.cpp b/network/server.cpp index c27117e8..ffc90548 100644 --- a/network/server.cpp +++ b/network/server.cpp @@ -18,6 +18,10 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ +/* OBSOLETE CODE, MOVED TO core/ */ + +#error "obsolete code" + #include "util.h" #include "global.h" #include "server.h" diff --git a/network/server.h b/network/server.h index 03e33aab..f0f3a855 100644 --- a/network/server.h +++ b/network/server.h @@ -18,6 +18,8 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ +#error "obsolete code" + #ifndef _SERVER_H_ #define _SERVER_H_ @@ -63,7 +65,7 @@ class Server : public QThread { signals: void recvRawServerMsg(QString); void displayStatusMsg(QString); - void displayMsg(QString buffer, Message msg); + void displayMsg(Message msg); void disconnected(); void nickAdded(QString network, QString nick, VarMap props); -- 2.20.1