From 12369966345e76e3bba92f64c84c940ef4d8d916 Mon Sep 17 00:00:00 2001 From: Manuel Nickschas Date: Mon, 12 Feb 2007 18:14:53 +0000 Subject: [PATCH] Merged changes from branch "sput" r50:55 back into trunk. This includes my work of the past few months, most notably a pretty complete rework of the buffer management and much other GUI code, as well as our new ChatWidget which is now functional enough to be used. Probably most of the GUI code now looks quite different. There have also been many cleanups, the obsolete network/ directory is being removed, and I have started to document some things. --- CMakeLists.txt | 4 +- Doxyfile | 28 +- Quassel.kdevelop.filelist | 4 + .../builtin_cmds.obsolete.cpp | 13 +- .../paulk-mainwindow.ui | 0 gui/CMakeLists.txt | 12 +- gui/buffer.cpp | 156 ++--- gui/buffer.h | 28 +- gui/channelwidget.cpp | 335 ---------- gui/channelwidget.h | 108 --- gui/chatwidget.cpp | 577 ++++++++++++++++ gui/chatwidget.h | 182 +++++ gui/mainwin.cpp | 28 +- gui/mainwin.h | 8 + network/buffer.cpp => gui/settings.cpp | 14 +- .../builtin_handlers.cpp => gui/settings.h | 25 +- gui/style.cpp | 256 ++++++++ network/buffer.h => gui/style.h | 35 +- gui/ui/bufferwidget.ui | 39 +- gui/ui/mainwin.ui | 36 +- gui/ui/settingsdlg.ui | 96 ++- main/main_gui.cpp | 3 + main/main_mono.cpp | 3 + network/CMakeLists.txt | 6 - network/cmdcodes.h | 39 -- network/server.cpp | 619 ------------------ network/server.h | 158 ----- plugins/plugin.h | 110 ++++ templates/cpp | 2 +- templates/h | 2 +- 30 files changed, 1372 insertions(+), 1554 deletions(-) rename network/builtin_cmds.cpp => dev-notes/builtin_cmds.obsolete.cpp (95%) rename gui/mainwindow.ui => dev-notes/paulk-mainwindow.ui (100%) delete mode 100644 gui/channelwidget.cpp delete mode 100644 gui/channelwidget.h create mode 100644 gui/chatwidget.cpp create mode 100644 gui/chatwidget.h rename network/buffer.cpp => gui/settings.cpp (89%) rename network/builtin_handlers.cpp => gui/settings.h (76%) create mode 100644 gui/style.cpp rename network/buffer.h => gui/style.h (74%) delete mode 100644 network/CMakeLists.txt delete mode 100644 network/cmdcodes.h delete mode 100644 network/server.cpp delete mode 100644 network/server.h create mode 100644 plugins/plugin.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d08682e1..9f800771 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,8 @@ IF(NOT BUILD_MONO AND NOT BUILD_CORE AND NOT BUILD_GUI) MESSAGE(FATAL_ERROR "\nYou have not selected which parts of Quassel I should build. Aborting.\nRun 'cmake -DBUILD=', where contains one or more of 'core', 'gui' or 'monolithic', or 'all' to build everything.\n") ENDIF(NOT BUILD_MONO AND NOT BUILD_CORE AND NOT BUILD_GUI) +SET(CMAKE_BUILD_TYPE Debug) + # Define files SET(quassel_mono_SRCS main/main_mono.cpp) SET(quassel_core_SRCS main/main_core.cpp) @@ -36,7 +38,7 @@ SET(SDIRS "") FOREACH(dir ${quassel_DIRS}) SET(SDIRS ${SDIRS} "${CMAKE_CURRENT_SOURCE_DIR}/${dir}") ENDFOREACH(dir) -INCLUDE_DIRECTORIES(${SDIRS}) +INCLUDE_DIRECTORIES(${SDIRS} plugins) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) # We need Qt4 support. diff --git a/Doxyfile b/Doxyfile index 127f3f55..0272fd85 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.4.1-KDevelop +# Doxyfile 1.5.1-KDevelop #--------------------------------------------------------------------------- # Project related configuration options @@ -22,21 +22,23 @@ ABBREVIATE_BRIEF = "The $name class" \ a \ an \ the -ALWAYS_DETAILED_SEC = YES +ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = YES FULL_PATH_NAMES = NO STRIP_FROM_PATH = / -STRIP_FROM_INC_PATH = +STRIP_FROM_INC_PATH = /home/sputnick/devel/local-quassel/ SHORT_NAMES = YES JAVADOC_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO DETAILS_AT_TOP = YES INHERIT_DOCS = YES -DISTRIBUTE_GROUP_DOC = NO +SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 8 ALIASES = OPTIMIZE_OUTPUT_FOR_C = NO OPTIMIZE_OUTPUT_JAVA = NO +BUILTIN_STL_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = YES SUBGROUPING = YES #--------------------------------------------------------------------------- # Build related configuration options @@ -80,7 +82,10 @@ WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- -INPUT = /home/sputnick/shared/Development/quassel/ +INPUT = gui \ + main \ + core \ + plugins FILE_PATTERNS = *.c \ *.cc \ *.cxx \ @@ -137,7 +142,7 @@ EXAMPLE_RECURSIVE = NO IMAGE_PATH = INPUT_FILTER = FILTER_PATTERNS = -FILTER_SOURCE_FILES = NO +FILTER_SOURCE_FILES = YES #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- @@ -146,6 +151,8 @@ INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES REFERENCED_BY_RELATION = YES REFERENCES_RELATION = YES +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index @@ -226,11 +233,11 @@ PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- -ENABLE_PREPROCESSING = NO -MACRO_EXPANSION = NO +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = NO -SEARCH_INCLUDES = NO -INCLUDE_PATH = +SEARCH_INCLUDES = YES +INCLUDE_PATH = /usr/include/qt4 INCLUDE_FILE_PATTERNS = PREDEFINED = EXPAND_AS_DEFINED = @@ -257,6 +264,7 @@ TEMPLATE_RELATIONS = NO INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES CALL_GRAPH = NO +CALLER_GRAPH = NO GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES DOT_IMAGE_FORMAT = png diff --git a/Quassel.kdevelop.filelist b/Quassel.kdevelop.filelist index 5cc32b4e..be3867fc 100644 --- a/Quassel.kdevelop.filelist +++ b/Quassel.kdevelop.filelist @@ -70,3 +70,7 @@ main/message.h gui/ui/bufferwidget.ui core/server.cpp core/server.h +gui/chatwidget.cpp +gui/chatwidget.h +gui/style.cpp +gui/style.h diff --git a/network/builtin_cmds.cpp b/dev-notes/builtin_cmds.obsolete.cpp similarity index 95% rename from network/builtin_cmds.cpp rename to dev-notes/builtin_cmds.obsolete.cpp index 59274692..5a9acf68 100644 --- a/network/builtin_cmds.cpp +++ b/dev-notes/builtin_cmds.obsolete.cpp @@ -18,11 +18,12 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -/* THIS CODE IS OBSOLETE! */ +#error THIS CODE IS OBSOLETE! -#include -//#include "message.h" -#include "cmdcodes.h" +/* We are keeping this for further reference only. + * This method of defining server commands sucked, but at least we have a quite complete list + * of commands here. + */ /** This macro marks strings as translateable for Qt's linguist tools */ #define _(str) QT_TR_NOOP(str) @@ -32,10 +33,6 @@ * Named commands have a negative enum value. */ -/** \NOTE: Function handlers _must_ be global functions or static methods! */ - -/** Set handler addresses to 0 to use the default (server) handler. */ - BuiltinCmd builtins[] = { { CMD_ADMIN, "admin", _("Get information about the administrator of a server."), _("[server]"), _("server: Server"), 0, 0 }, diff --git a/gui/mainwindow.ui b/dev-notes/paulk-mainwindow.ui similarity index 100% rename from gui/mainwindow.ui rename to dev-notes/paulk-mainwindow.ui diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 8bc8b4bf..a9b27aa3 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -1,11 +1,11 @@ -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 channelwidgetinput.h mainwin.h serverlist.h identities.h coreconnectdlg.h - guiproxy.h networkview.h buffer.h) +SET(gui_SRCS chatwidget.cpp channelwidgetinput.cpp mainwin.cpp serverlist.cpp buffer.cpp + identities.cpp coreconnectdlg.cpp guiproxy.cpp networkview.cpp style.cpp settings.cpp) +SET(gui_HDRS style.h) +SET(gui_MOCS chatwidget.h channelwidgetinput.h mainwin.h serverlist.h identities.h coreconnectdlg.h + guiproxy.h networkview.h buffer.h settings.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) + networkview.ui bufferwidget.ui settingsdlg.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 index 3892ee95..751da803 100644 --- a/gui/buffer.cpp +++ b/gui/buffer.cpp @@ -20,17 +20,21 @@ #include "buffer.h" #include "util.h" +#include "chatwidget.h" Buffer::Buffer(QString netname, QString bufname) { networkName = netname; bufferName = bufname; widget = 0; + chatWidget = 0; + contentsWidget = 0; active = false; } Buffer::~Buffer() { delete widget; + delete chatWidget; } void Buffer::setActive(bool a) { @@ -49,21 +53,30 @@ void Buffer::userInput(QString msg) { emit userInput(networkName, bufferName, msg); } +/* FIXME do we need this? */ void Buffer::scrollToEnd() { if(!widget) return; - widget->scrollToEnd(); + //widget->scrollToEnd(); } QWidget * Buffer::showWidget(QWidget *parent) { + if(widget) { - widget->scrollToEnd(); return qobject_cast(widget); } - widget = new BufferWidget(networkName, bufferName, isActive(), ownNick, contents, parent); + + if(!contentsWidget) { + contentsWidget = new ChatWidgetContents(networkName, bufferName, 0); + contentsWidget->hide(); + /* FIXME do we need this? */ + for(int i = 0; i < contents.count(); i++) { + contentsWidget->appendMsg(contents[i]); + } + } + contentsWidget->hide(); + widget = new BufferWidget(networkName, bufferName, isActive(), ownNick, contentsWidget, this, parent); widget->setTopic(topic); widget->updateNickList(nicks); - //widget->renderContents(); - //widget->scrollToEnd(); connect(widget, SIGNAL(userInput(QString)), this, SLOT(userInput(QString))); return qobject_cast(widget); } @@ -73,6 +86,10 @@ void Buffer::hideWidget() { widget = 0; } +void Buffer::deleteWidget() { + widget = 0; +} + QWidget * Buffer::getWidget() { return qobject_cast(widget); } @@ -112,12 +129,19 @@ void Buffer::setOwnNick(QString nick) { /****************************************************************************************/ -BufferWidget::BufferWidget(QString netname, QString bufname, bool act, QString own, QList cont, QWidget *parent) : QWidget(parent) { + + +/****************************************************************************************/ + +BufferWidget::BufferWidget(QString netname, QString bufname, bool act, QString own, ChatWidgetContents *contents, Buffer *pBuf, QWidget *parent) : QWidget(parent) { ui.setupUi(this); networkName = netname; bufferName = bufname; active = act; - contents = cont; + parentBuffer = pBuf; + + ui.chatWidget->init(netname, bufname, contents); + ui.ownNick->clear(); ui.ownNick->addItem(own); if(bufname.isEmpty()) { @@ -130,30 +154,15 @@ BufferWidget::BufferWidget(QString netname, QString bufname, bool act, QString o connect(ui.nickTree, SIGNAL(itemCollapsed(QTreeWidgetItem *)), this, SLOT(itemExpansionChanged(QTreeWidgetItem*))); connect(ui.inputEdit, SIGNAL(returnPressed()), this, SLOT(enterPressed())); - ui.chatWidget->setFocusProxy(ui.inputEdit); - ui.chatWidget->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - 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); - show(); - renderContents(); + ui.chatWidget->setFocusProxy(ui.inputEdit); updateTitle(); - //show(); +} + +BufferWidget::~BufferWidget() { + ui.chatWidget->takeWidget(); /* remove ownership so the chatwidget contents does not get destroyed */ + parentBuffer->deleteWidget(); /* make sure the parent buffer knows we are gone */ } void BufferWidget::updateTitle() { @@ -173,100 +182,13 @@ void BufferWidget::enterPressed() { void BufferWidget::setActive(bool act) { if(act != active) { active = act; - renderContents(); + //renderContents(); //scrollToEnd(); } } -void BufferWidget::renderContents() { - QString html; - //html = ""; - for(int i = 0; i < contents.count(); i++) { - html += htmlFromMsg(contents[i]); - } - //ui.chatWidget->clear(); - hide(); - ui.chatWidget->setHtml(html); show(); - //ui.chatWidget->insertHtml("
"); // <-- bug that would not reset the scrollbar sizes... - 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)); + ui.chatWidget->appendMsg(msg); } void BufferWidget::setOwnNick(QString nick) { diff --git a/gui/buffer.h b/gui/buffer.h index 5df19aa7..4ef1112f 100644 --- a/gui/buffer.h +++ b/gui/buffer.h @@ -29,8 +29,15 @@ #include "global.h" #include "message.h" +class ChatWidget; +class ChatWidgetContents; class BufferWidget; +//!\brief Encapsulates the contents of a single channel, query or server status context. +/** A Buffer maintains a list of existing nicks and their status. New messages can be appended using + * displayMsg(). A buffer displays its contents by way of a BufferWidget, which can be shown + * (and created on demand) by calling showWidget(). + */ class Buffer : public QObject { Q_OBJECT @@ -60,6 +67,7 @@ class Buffer : public QObject { QWidget * showWidget(QWidget *parent = 0); void hideWidget(); + void deleteWidget(); void scrollToEnd(); @@ -69,6 +77,8 @@ class Buffer : public QObject { private: bool active; BufferWidget *widget; + ChatWidget *chatWidget; + ChatWidgetContents *contentsWidget; VarMap nicks; QString topic; QString ownNick; @@ -77,23 +87,30 @@ class Buffer : public QObject { QList contents; }; +//!\brief Displays the contents of a Buffer. +/** A BufferWidget usually includes a topic line, a nicklist, the chat itself, and an input line. + * For server buffers or queries, there is of course no nicklist. + * The contents of the chat is rendered by a ChatWidget. + */ class BufferWidget : public QWidget { Q_OBJECT public: - BufferWidget(QString netname, QString bufname, bool active, QString ownNick, QList contents, QWidget *parent = 0); + BufferWidget(QString netname, QString bufname, bool active, QString ownNick, ChatWidgetContents *contents, Buffer *parentBuffer, QWidget *parent = 0); + ~BufferWidget(); void setActive(bool act = true); + signals: void userInput(QString); + protected: + 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(); @@ -102,17 +119,14 @@ class BufferWidget : public QWidget { private: Ui::BufferWidget ui; + Buffer *parentBuffer; 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/channelwidget.cpp b/gui/channelwidget.cpp deleted file mode 100644 index 8aeb5926..00000000 --- a/gui/channelwidget.cpp +++ /dev/null @@ -1,335 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2005 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 "channelwidget.h" -#include "guiproxy.h" -#include "global.h" -#include "util.h" - -#include -#include - -ChannelWidget::ChannelWidget(QString netname, QString bufname, QString own, QWidget *parent) : QWidget(parent) { - ui.setupUi(this); - _networkName = netname; - _bufferName = bufname; - 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())); - connect(this, SIGNAL(nickListChanged(QStringList)), ui.inputEdit, SLOT(updateNickList(QStringList))); - ui.inputEdit->setFocus(); - - opsExpanded = voicedExpanded = usersExpanded = true; - - // Define standard colors - stdCol = "black"; - noticeCol = "darkblue"; - serverCol = "darkblue"; - errorCol = "red"; - joinCol = "green"; - quitCol = "firebrick"; - partCol = "firebrick"; - kickCol = "firebrick"; - nickCol = "magenta"; - - completer = 0; -} - -void ChannelWidget::enterPressed() { - QStringList lines = ui.inputEdit->text().split('\n', QString::SkipEmptyParts); - foreach(QString msg, lines) { - if(msg.isEmpty()) continue; - emit sendInput(networkName(), bufferName(), msg); - } - ui.inputEdit->clear(); -} - -void ChannelWidget::recvMessage(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::Msg: - c = stdCol; n = QString("<%1>").arg(nick); s = msg.msg; - break; - case Message::Server: - c = serverCol; s = msg.msg; - break; - case Message::Error: - c = errorCol; s = msg.msg; - 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.msg.isEmpty()) s = QString("%1 (%2)").arg(s).arg(msg.msg); - break; - case Message::Kick: - { c = kickCol; - QString victim = msg.msg.section(" ", 0, 0); - if(victim == ui.ownNick->currentText()) victim = tr("you"); - QString kickmsg = msg.msg.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.msg.isEmpty()) s = QString("%1 (%2)").arg(s).arg(msg.msg); - break; - case Message::Nick: - c = nickCol; - if(nick == msg.msg) s = QString(tr("<-> You are now known as %1")).arg(msg.msg); - else s = QString(tr("<-> %1 is now known as %2")).arg(nick).arg(msg.msg); - break; - case Message::Mode: - c = serverCol; - if(nick.isEmpty()) s = tr("*** User mode: %1").arg(msg.msg); - else s = tr("*** Mode %1 by %2").arg(msg.msg).arg(nick); - break; - default: - c = stdCol; n = QString("[%1]").arg(msg.sender); s = msg.msg; - break; - } - 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); - ui.chatWidget->append(html); // qDebug() << html; - //ui.chatWidget->append(QString("
%1
%2
 %3
") - //.arg(msg.timeStamp.toLocalTime().toString("hh:mm:ss")).arg(nick).arg(s)); - ui.chatWidget->ensureCursorVisible(); -} - -void ChannelWidget::recvStatusMsg(QString msg) { - ui.chatWidget->insertPlainText(QString("[STATUS] %1").arg(msg)); - ui.chatWidget->ensureCursorVisible(); -} - -void ChannelWidget::setTopic(QString topic) { - ui.topicEdit->setText(topic); -} - -void ChannelWidget::setNicks(QStringList nicks) { - - -} - -void ChannelWidget::addNick(QString nick, VarMap props) { - nicks[nick] = props; - updateNickList(); - if(completer) delete completer; - completer = new QCompleter(nicks.keys()); - ui.inputEdit->setCompleter(completer); -} - -void ChannelWidget::updateNick(QString nick, VarMap props) { - nicks[nick] = props; - updateNickList(); -} - -void ChannelWidget::renameNick(QString oldnick, QString newnick) { - QVariant v = nicks.take(oldnick); - nicks[newnick] = v; - updateNickList(); -} - -void ChannelWidget::removeNick(QString nick) { - nicks.remove(nick); - updateNickList(); -} - -void ChannelWidget::setOwnNick(QString nick) { - ui.ownNick->clear(); - ui.ownNick->addItem(nick); -} - -void ChannelWidget::updateNickList() { - 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 ChannelWidget::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(); -} - -/**********************************************************************************************/ - - -IrcWidget::IrcWidget(QWidget *parent) : QWidget(parent) { - ui.setupUi(this); - ui.tabWidget->removeTab(0); - - connect(guiProxy, SIGNAL(csDisplayMsg(QString, QString, Message)), this, SLOT(recvMessage(QString, 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))); -} - -ChannelWidget * IrcWidget::getBuffer(QString net, QString buf) { - QString key = net + buf; - if(!buffers.contains(key)) { - ChannelWidget *cw = new ChannelWidget(net, buf, ownNick[net]); - connect(cw, SIGNAL(sendInput(QString, QString, QString)), this, SLOT(userInput(QString, QString, QString))); - ui.tabWidget->addTab(cw, net+buf); - ui.tabWidget->setCurrentWidget(cw); - cw->setFocus(); - buffers[key] = cw; - } - return buffers[key]; -} - - -void IrcWidget::recvMessage(QString net, QString buf, Message msg) { - ChannelWidget *cw = getBuffer(net, buf); - cw->recvMessage(msg); -} - -void IrcWidget::recvStatusMsg(QString net, QString msg) { - recvMessage(net, "", Message(Message::Server, QString("[STATUS] %1").arg(msg))); - -} - -void IrcWidget::userInput(QString net, QString buf, QString msg) { - emit sendInput(net, buf, msg); -} - -void IrcWidget::setTopic(QString net, QString buf, QString topic) { - ChannelWidget *cw = getBuffer(net, buf); - cw->setTopic(topic); -} - -void IrcWidget::setNicks(QString net, QString buf, QStringList nicks) { - ChannelWidget *cw = getBuffer(net, buf); - cw->setNicks(nicks); -} - -void IrcWidget::addNick(QString net, QString nick, VarMap props) { - VarMap netnicks = nicks[net].toMap(); - netnicks[nick] = props; - nicks[net] = netnicks; - VarMap chans = props["Channels"].toMap(); - QStringList c = chans.keys(); - foreach(QString bufname, c) { - getBuffer(net, bufname)->addNick(nick, props); - } -} - -void IrcWidget::renameNick(QString net, QString oldnick, QString newnick) { - VarMap netnicks = nicks[net].toMap(); - qDebug() << "renNICK:"<renameNick(oldnick, newnick); - } - QVariant v = netnicks.take(oldnick); - netnicks[newnick] = v; - nicks[net] = netnicks; -} - -void IrcWidget::updateNick(QString net, QString nick, VarMap props) { - QStringList oldchans = nicks[net].toMap()[nick].toMap()["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); - } - VarMap netnicks = nicks[net].toMap(); - netnicks[nick] = props; - nicks[net] = netnicks; -} - -void IrcWidget::removeNick(QString net, QString nick) { - VarMap chans = nicks[net].toMap()[nick].toMap()["Channels"].toMap(); - foreach(QString bufname, chans.keys()) { - getBuffer(net, bufname)->removeNick(nick); - } - VarMap netnicks = nicks[net].toMap(); - netnicks.remove(nick); - nicks[net] = netnicks; -} - -void IrcWidget::setOwnNick(QString net, QString nick) { - ownNick[net] = nick; - foreach(ChannelWidget *cw, buffers.values()) { - if(cw->networkName() == net) cw->setOwnNick(nick); - } -} - diff --git a/gui/channelwidget.h b/gui/channelwidget.h deleted file mode 100644 index 936cf5f4..00000000 --- a/gui/channelwidget.h +++ /dev/null @@ -1,108 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2005 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 _CHANNELWIDGET_H_ -#define _CHANNELWIDGET_H_ - -#include "ui_channelwidget.h" -#include "ui_ircwidget.h" - -#include "global.h" -#include "message.h" - -class ChannelWidget : public QWidget { - Q_OBJECT - - public: - ChannelWidget(QString netname, QString bufname, QString ownNick, QWidget *parent = 0); - - QString bufferName() { return _bufferName; } - QString networkName() { return _networkName; } - - signals: - void sendInput(QString, QString, QString); - void nickListChanged(QStringList); - - public slots: - void recvMessage(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); - - - private slots: - void enterPressed(); - void updateNickList(); - - void itemExpansionChanged(QTreeWidgetItem *); - - private: - Ui::ChannelWidget ui; - - QString stdCol, errorCol, noticeCol, joinCol, quitCol, partCol, kickCol, serverCol, nickCol; - QString CSS; - QString _networkName; - QString _bufferName; - VarMap nicks; - - QCompleter *completer; - - bool opsExpanded, voicedExpanded, usersExpanded; -}; - -/** Temporary widget for displaying a set of ChannelWidgets. */ -class IrcWidget : public QWidget { - Q_OBJECT - - public: - IrcWidget(QWidget *parent = 0); - - public slots: - void recvMessage(QString network, QString buffer, 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); - - signals: - void sendInput(QString network, QString buffer, QString message); - - private slots: - void userInput(QString, QString, QString); - - private: - Ui::IrcWidget ui; - QHash buffers; - VarMap nicks; - QHash ownNick; - - ChannelWidget * getBuffer(QString net, QString buf); -}; - -#endif diff --git a/gui/chatwidget.cpp b/gui/chatwidget.cpp new file mode 100644 index 00000000..03a9d1f6 --- /dev/null +++ b/gui/chatwidget.cpp @@ -0,0 +1,577 @@ +/*************************************************************************** + * Copyright (C) 2005-07 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 "style.h" +#include "chatwidget.h" +#include +#include + +ChatWidget::ChatWidget(QWidget *parent) : QScrollArea(parent) { + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + setAlignment(Qt::AlignLeft | Qt::AlignTop); + +} + +void ChatWidget::init(QString netname, QString bufname, ChatWidgetContents *contentsWidget) { + networkName = netname; + bufferName = bufname; + //setAlignment(Qt::AlignBottom); + contents = contentsWidget; + setWidget(contents); + //setWidgetResizable(true); + //contents->setWidth(contents->sizeHint().width()); + contents->setFocusProxy(this); + //contents->show(); + //setAlignment(Qt::AlignBottom); +} + +ChatWidget::~ChatWidget() { + +} + +void ChatWidget::clear() { + //contents->clear(); +} + +void ChatWidget::appendMsg(Message msg) { + contents->appendMsg(msg); + //qDebug() << "appending" << msg.text; + +} + +void ChatWidget::resizeEvent(QResizeEvent *event) { + //qDebug() << bufferName << isVisible() << event->size(); + contents->setWidth(event->size().width()); + //setAlignment(Qt::AlignBottom); + QScrollArea::resizeEvent(event); +} + +/*************************************************************************************/ + +ChatWidgetContents::ChatWidgetContents(QString net, QString buf, QWidget *parent) : QWidget(parent) { + networkName = net; + bufferName = buf; + layoutTimer = new QTimer(this); + layoutTimer->setSingleShot(true); + connect(layoutTimer, SIGNAL(timeout()), this, SLOT(triggerLayout())); + + setBackgroundRole(QPalette::Base); + setFont(QFont("Fixed")); + + ycoords.append(0); + tsWidth = 90; + senderWidth = 100; + //textWidth = 400; + computePositions(); + //setFixedWidth((int)(tsWidth + senderWidth + textWidth + 20)); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setMouseTracking(true); + mouseMode = Normal; + selectionMode = NoSelection; + + doLayout = false; +} + +ChatWidgetContents::~ChatWidgetContents() { + delete layoutTimer; + foreach(ChatLine *l, lines) { + delete l; + } +} + +QSize ChatWidgetContents::sizeHint() const { + //qDebug() << size(); + return size(); +} + +void ChatWidgetContents::paintEvent(QPaintEvent *event) { + QPainter painter(this); + qreal top = event->rect().top(); + qreal bot = top + event->rect().height(); + int idx = yToLineIdx(top); + if(idx < 0) return; + for(int i = idx; i < lines.count() ; i++) { + lines[i]->draw(&painter, QPointF(0, ycoords[i])); + if(ycoords[i+1] > bot) return; + } +} + +void ChatWidgetContents::appendMsg(Message msg) { + ChatLine *line = new ChatLine(msg, networkName, bufferName); + qreal h = line->layout(tsWidth, senderWidth, textWidth); + ycoords.append(h + ycoords[ycoords.count() - 1]); + setFixedHeight((int)ycoords[ycoords.count()-1]); + lines.append(line); + update(); + return; + +} + +void ChatWidgetContents::clear() { + + +} + +//!\brief Computes the different x position vars for given tsWidth and senderWidth. +void ChatWidgetContents::computePositions() { + senderX = tsWidth + Style::sepTsSender(); + textX = senderX + senderWidth + Style::sepSenderText(); + tsGrabPos = tsWidth + (int)Style::sepTsSender()/2; + senderGrabPos = senderX + senderWidth + (int)Style::sepSenderText()/2; + textWidth = size().width() - textX; +} + +void ChatWidgetContents::setWidth(qreal w) { + textWidth = (int)w - (Style::sepTsSender() + Style::sepSenderText()) - tsWidth - senderWidth; + setFixedWidth((int)w); + layout(); +} + +//!\brief Trigger layout (used by layoutTimer only). +/** This method is triggered by the layoutTimer. Layout the widget if it has been postponed earlier. + */ +void ChatWidgetContents::triggerLayout() { + layout(true); +} + +//!\brief Layout the widget. +/** The contents of the widget is re-layouted completely. Since this could take a while if the widget + * is huge, we don't want to trigger the layout procedure too often (causing layout calls to pile up). + * We use a timer that ensures layouting is only done if the last one has finished. + */ +void ChatWidgetContents::layout(bool timer) { + if(layoutTimer->isActive()) { + // Layouting too fast. We set a flag though, so that a final layout is done when the timer runs out. + doLayout = true; + return; + } + if(timer && !doLayout) return; // Only check doLayout if we have been triggered by the timer! + qreal y = 0; + for(int i = 0; i < lines.count(); i++) { + qreal h = lines[i]->layout(tsWidth, senderWidth, textWidth); + ycoords[i+1] = h + ycoords[i]; + } + setFixedHeight((int)ycoords[ycoords.count()-1]); + update(); + doLayout = false; // Clear previous layout requests + layoutTimer->start(50); // Minimum time until we start the next layout +} + +int ChatWidgetContents::yToLineIdx(qreal y) { + if(y >= ycoords[ycoords.count()-1]) ycoords.count()-1; + if(ycoords.count() <= 1) return 0; + int uidx = 0; + int oidx = ycoords.count() - 1; + int idx; + while(1) { + if(uidx == oidx - 1) return uidx; + idx = (uidx + oidx) / 2; + if(ycoords[idx] > y) oidx = idx; + else uidx = idx; + } +} + +void ChatWidgetContents::mousePressEvent(QMouseEvent *event) { + if(event->button() == Qt::LeftButton) { + dragStartPos = event->pos(); + dragStartMode = Normal; + switch(mouseMode) { + case Normal: + if(mousePos == OverTsSep) { + dragStartMode = DragTsSep; + setCursor(Qt::ClosedHandCursor); + } else if(mousePos == OverTextSep) { + dragStartMode = DragTextSep; + setCursor(Qt::ClosedHandCursor); + } else { + dragStartLine = yToLineIdx(event->pos().y()); + dragStartCursor = lines[dragStartLine]->posToCursor(QPointF(event->pos().x(), event->pos().y()-ycoords[dragStartLine])); + } + mouseMode = Pressed; + break; + } + } +} + +void ChatWidgetContents::mouseReleaseEvent(QMouseEvent *event) { + if(event->button() == Qt::LeftButton) { + dragStartPos = QPoint(); + if(mousePos == OverTsSep || mousePos == OverTextSep) setCursor(Qt::OpenHandCursor); + else setCursor(Qt::ArrowCursor); + + switch(mouseMode) { + case Pressed: + mouseMode = Normal; + clearSelection(); + break; + case MarkText: + mouseMode = Normal; + selectionMode = TextSelected; + selectionLine = dragStartLine; + selectionStart = qMin(dragStartCursor, curCursor); + selectionEnd = qMax(dragStartCursor, curCursor); + // TODO Make X11SelectionMode configurable! + QApplication::clipboard()->setText(selectionToString()); + break; + case MarkLines: + mouseMode = Normal; + selectionMode = LinesSelected; + selectionStart = qMin(dragStartLine, curLine); + selectionEnd = qMax(dragStartLine, curLine); + // TODO Make X11SelectionMode configurable! + QApplication::clipboard()->setText(selectionToString()); + break; + default: + mouseMode = Normal; + } + } +} + +//!\brief React to mouse movements over the ChatWidget. +/** This is called by Qt whenever the mouse moves. Here we have to do most of the mouse handling, + * such as changing column widths, marking text or initiating drag & drop. + */ +void ChatWidgetContents::mouseMoveEvent(QMouseEvent *event) { + // Set some basic properties of the current position + int x = event->pos().x(); + int y = event->pos().y(); + MousePos oldpos = mousePos; + if(x >= tsGrabPos - 3 && x <= tsGrabPos + 3) mousePos = OverTsSep; + else if(x >= senderGrabPos - 3 && x <= senderGrabPos + 3) mousePos = OverTextSep; + else mousePos = None; + + // Pass 1: Do whatever we can before switching mouse mode (if at all). + switch(mouseMode) { + // No special mode. Set mouse cursor if appropriate. + case Normal: + if(oldpos != mousePos) { + if(mousePos == OverTsSep || mousePos == OverTextSep) setCursor(Qt::OpenHandCursor); + else setCursor(Qt::ArrowCursor); + } + break; + // Left button pressed. Might initiate marking or drag & drop if we moved past the drag distance. + case Pressed: + if(!dragStartPos.isNull() && (dragStartPos - event->pos()).manhattanLength() >= QApplication::startDragDistance()) { + // Moving a column separator? + if(dragStartMode == DragTsSep) mouseMode = DragTsSep; + else if(dragStartMode == DragTextSep) mouseMode = DragTextSep; + // Nope. Check if we are over a selection to start drag & drop. + else if(dragStartMode == Normal) { + bool dragdrop = false; + if(selectionMode == TextSelected) { + int l = yToLineIdx(y); + if(selectionLine == l) { + int p = lines[l]->posToCursor(QPointF(x, y - ycoords[l])); + if(p >= selectionStart && p <= selectionEnd) dragdrop = true; + } + } else if(selectionMode == LinesSelected) { + int l = yToLineIdx(y); + if(l >= selectionStart && l <= selectionEnd) dragdrop = true; + } + // Ok, so just start drag & drop if appropriate. + if(dragdrop) { + QDrag *drag = new QDrag(this); + QMimeData *mimeData = new QMimeData; + mimeData->setText(selectionToString()); + drag->setMimeData(mimeData); + drag->start(); + mouseMode = Normal; + // Otherwise, clear the selection and start text marking! + } else { + clearSelection(); + if(dragStartCursor < 0) { mouseMode = MarkLines; curLine = -1; } + else mouseMode = MarkText; + } + } + } + break; + case DragTsSep: + break; + case DragTextSep: + break; + } + // Pass 2: Some mouse modes need work after being set... + if(mouseMode == DragTsSep && x < size().width() - Style::sepSenderText() - senderWidth - 10) { + // Drag first column separator + int foo = Style::sepTsSender()/2; + tsWidth = qMax(x, foo) - foo; + computePositions(); + layout(); + } else if(mouseMode == DragTextSep && x < size().width() - 10) { + // Drag second column separator + int foo = tsWidth + Style::sepTsSender() + Style::sepSenderText()/2; + senderWidth = qMax(x, foo) - foo; + computePositions(); + layout(); + } else if(mouseMode == MarkText) { + // Change currently marked text + curLine = yToLineIdx(y); + int c = lines[curLine]->posToCursor(QPointF(x, y - ycoords[curLine])); + if(curLine == dragStartLine && c >= 0) { + if(c != curCursor) { + curCursor = c; + lines[curLine]->setSelection(ChatLine::Partial, dragStartCursor, c); + update(); + } + } else { + mouseMode = MarkLines; + selectionStart = qMin(curLine, dragStartLine); selectionEnd = qMax(curLine, dragStartLine); + for(int i = selectionStart; i <= selectionEnd; i++) lines[i]->setSelection(ChatLine::Full); + update(); + } + } else if(mouseMode == MarkLines) { + // Line marking + int l = yToLineIdx(y); + if(l != curLine) { + selectionStart = qMin(l, dragStartLine); selectionEnd = qMax(l, dragStartLine); + if(curLine >= 0 && curLine < selectionStart) { + for(int i = curLine; i < selectionStart; i++) lines[i]->setSelection(ChatLine::None); + } else if(curLine > selectionEnd) { + for(int i = selectionEnd+1; i <= curLine; i++) lines[i]->setSelection(ChatLine::None); + } else if(selectionStart < curLine && l < curLine) { + for(int i = selectionStart; i < curLine; i++) lines[i]->setSelection(ChatLine::Full); + } else if(curLine < selectionEnd && l > curLine) { + for(int i = curLine+1; i <= selectionEnd; i++) lines[i]->setSelection(ChatLine::Full); + } + curLine = l; + update(); + } + } +} + +//!\brief Clear current text selection. +void ChatWidgetContents::clearSelection() { + if(selectionMode == TextSelected) { + lines[selectionLine]->setSelection(ChatLine::None); + } else if(selectionMode == LinesSelected) { + for(int i = selectionStart; i <= selectionEnd; i++) { + lines[i]->setSelection(ChatLine::None); + } + } + selectionMode = NoSelection; + update(); +} + +//!\brief Convert current selection to human-readable string. +QString ChatWidgetContents::selectionToString() { + //TODO Make selection format configurable! + if(selectionMode == NoSelection) return ""; + if(selectionMode == LinesSelected) { + QString result; + for(int l = selectionStart; l <= selectionEnd; l++) { + result += QString("[%1] %2 %3\n").arg(lines[l]->getTimeStamp().toLocalTime().toString("hh:mm:ss")) + .arg(lines[l]->getSender()).arg(lines[l]->getText()); + } + return result; + } + // selectionMode == TextSelected + return lines[selectionLine]->getText().mid(selectionStart, selectionEnd - selectionStart); +} + +/************************************************************************************/ + +//!\brief Construct a ChatLine object from a message. +/** + * \param m The message to be layouted and rendered + * \param net The network name + * \param buf The buffer name + */ +ChatLine::ChatLine(Message m, QString net, QString buf) : QObject() { + hght = 0; + networkName = net; + bufferName = buf; + msg = m; + selectionMode = None; + formatMsg(msg); + +} + +ChatLine::~ChatLine() { + +} + +void ChatLine::formatMsg(Message msg) { + QString user = userFromMask(msg.sender); + QString host = hostFromMask(msg.sender); + QString nick = nickFromMask(msg.sender); + QString text = Style::mircToInternal(msg.text); + + QString c = tr("%DT[%1]").arg(msg.timeStamp.toLocalTime().toString("hh:mm:ss")); + QString s, t; + switch(msg.type) { + case Message::Plain: + s = tr("%DS<%1>").arg(nick); t = tr("%D0%1").arg(text); break; + case Message::Server: + s = tr("%Ds*"); t = tr("%Ds%1").arg(text); break; + case Message::Error: + s = tr("%De*"); t = tr("%De%1").arg(text); break; + case Message::Join: + s = tr("%Dj-->"); t = tr("%Dj%DN%1%DN %DH(%2@%3)%DH has joined %DC%4%DC").arg(nick, user, host, bufferName); break; + case Message::Part: + s = tr("%Dp<--"); t = tr("%Dp%DN%1%DN %DH(%2@%3)%DH has left %DC%4%DC").arg(nick, user, host, bufferName); + if(!text.isEmpty()) t = QString("%1 (%2)").arg(t).arg(text); + break; + case Message::Quit: + s = tr("%Dq<--"); t = tr("%Dq%DN%1%DN %DH(%2@%3)%DH has quit").arg(nick, user, host); + if(!text.isEmpty()) t = QString("%1 (%2)").arg(t).arg(text); + break; + case Message::Kick: + { s = tr("%Dk<-*"); + QString victim = text.section(" ", 0, 0); + //if(victim == ui.ownNick->currentText()) victim = tr("you"); + QString kickmsg = text.section(" ", 1); + t = tr("%Dk%DN%1%DN has kicked %DN%2%DN from %DC%3%DC").arg(nick).arg(victim).arg(bufferName); + if(!kickmsg.isEmpty()) t = QString("%1 (%2)").arg(t).arg(kickmsg); + } + break; + case Message::Nick: + s = tr("%Dr<->"); + if(nick == msg.text) t = tr("You are now known as %DN%1%DN").arg(msg.text); + else t = tr("%DN%1%DN is now known as %DN%2%DN").arg(nick, msg.text); + break; + case Message::Mode: + s = tr("%Dm***"); + if(nick.isEmpty()) t = tr("User mode: %DM%1%DM").arg(msg.text); + else t = tr("Mode %DM%1%DM by %DN%2%DN").arg(msg.text, nick); + break; + default: + s = tr("%De%1").arg(msg.sender); + t = tr("%De[%1]").arg(msg.text); + } + QTextOption tsOption, senderOption, textOption; + tsFormatted = Style::internalToFormatted(c); + senderFormatted = Style::internalToFormatted(s); + textFormatted = Style::internalToFormatted(t); + tsLayout.setText(tsFormatted.text); tsLayout.setAdditionalFormats(tsFormatted.formats); + tsOption.setWrapMode(QTextOption::NoWrap); + tsLayout.setTextOption(tsOption); + senderLayout.setText(senderFormatted.text); senderLayout.setAdditionalFormats(senderFormatted.formats); + senderOption.setAlignment(Qt::AlignRight); senderOption.setWrapMode(QTextOption::ManualWrap); + senderLayout.setTextOption(senderOption); + textLayout.setText(textFormatted.text); textLayout.setAdditionalFormats(textFormatted.formats); + textOption.setWrapMode(QTextOption::WrapAnywhere); // seems to do what we want, apparently + textLayout.setTextOption(textOption); +} + +//!\brief Return the cursor position for the given coordinate pos. +/** + * \param pos The position relative to the ChatLine + * \return The cursor position, or -2 for timestamp, or -1 for sender + */ +int ChatLine::posToCursor(QPointF pos) { + if(pos.x() < tsWidth + (int)Style::sepTsSender()/2) return -2; + qreal textStart = tsWidth + Style::sepTsSender() + senderWidth + Style::sepSenderText(); + if(pos.x() < textStart) return -1; + for(int l = textLayout.lineCount() - 1; l >=0; l--) { + QTextLine line = textLayout.lineAt(l); + if(pos.y() >= line.position().y()) { + int p = line.xToCursor(pos.x() - textStart, QTextLine::CursorOnCharacter); + return p; + } + } +} + +void ChatLine::setSelection(SelectionMode mode, int start, int end) { + selectionMode = mode; + tsFormat.clear(); senderFormat.clear(); textFormat.clear(); + QPalette pal = QApplication::palette(); + QTextLayout::FormatRange tsSel, senderSel, textSel; + switch (mode) { + case None: + break; + case Partial: + selectionStart = qMin(start, end); selectionEnd = qMax(start, end); + textSel.format.setForeground(pal.brush(QPalette::HighlightedText)); + textSel.format.setBackground(pal.brush(QPalette::Highlight)); + textSel.start = selectionStart; + textSel.length = selectionEnd - selectionStart; + textFormat.append(textSel); + break; + case Full: + tsSel.format.setForeground(pal.brush(QPalette::HighlightedText)); + tsSel.start = 0; tsSel.length = tsLayout.text().length(); tsFormat.append(tsSel); + senderSel.format.setForeground(pal.brush(QPalette::HighlightedText)); + senderSel.start = 0; senderSel.length = senderLayout.text().length(); senderFormat.append(senderSel); + textSel.format.setForeground(pal.brush(QPalette::HighlightedText)); + textSel.start = 0; textSel.length = textLayout.text().length(); textFormat.append(textSel); + break; + } +} + +QDateTime ChatLine::getTimeStamp() { + return msg.timeStamp; +} + +QString ChatLine::getSender() { + return senderLayout.text(); +} + +QString ChatLine::getText() { + return textLayout.text(); +} + +qreal ChatLine::layout(qreal tsw, qreal senderw, qreal textw) { + tsWidth = tsw; senderWidth = senderw; textWidth = textw; + QTextLine tl; + tsLayout.beginLayout(); + tl = tsLayout.createLine(); + tl.setLineWidth(tsWidth); + tl.setPosition(QPointF(0, 0)); + tsLayout.endLayout(); + + senderLayout.beginLayout(); + tl = senderLayout.createLine(); + tl.setLineWidth(senderWidth); + tl.setPosition(QPointF(0, 0)); + senderLayout.endLayout(); + + qreal h = 0; + textLayout.beginLayout(); + while(1) { + tl = textLayout.createLine(); + if(!tl.isValid()) break; + tl.setLineWidth(textWidth); + tl.setPosition(QPointF(0, h)); + h += tl.height(); + } + textLayout.endLayout(); + hght = h; + return h; +} + +//!\brief Draw ChatLine on the given QPainter at the given position. +void ChatLine::draw(QPainter *p, const QPointF &pos) { + QPalette pal = QApplication::palette(); + if(selectionMode == Full) { + p->setPen(Qt::NoPen); + p->setBrush(pal.brush(QPalette::Highlight)); + p->drawRect(QRectF(pos, QSizeF(tsWidth + Style::sepTsSender() + senderWidth + Style::sepSenderText() + textWidth, height()))); + } else if(selectionMode == Partial) { + + } + p->setClipRect(QRectF(pos, QSizeF(tsWidth, height()))); + tsLayout.draw(p, pos, tsFormat); + p->setClipRect(QRectF(pos + QPointF(tsWidth + Style::sepTsSender(), 0), QSizeF(senderWidth, height()))); + senderLayout.draw(p, pos + QPointF(tsWidth + Style::sepTsSender(), 0), senderFormat); + p->setClipping(false); + textLayout.draw(p, pos + QPointF(tsWidth + Style::sepTsSender() + senderWidth + Style::sepSenderText(), 0), textFormat); +} diff --git a/gui/chatwidget.h b/gui/chatwidget.h new file mode 100644 index 00000000..93ee1173 --- /dev/null +++ b/gui/chatwidget.h @@ -0,0 +1,182 @@ +/*************************************************************************** + * 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 _CHATWIDGET_H_ +#define _CHATWIDGET_H_ + +#include "style.h" +#include "message.h" +#include + +class ChatWidgetContents; + +//!\brief Scroll area showing part of the chat messages for a given buffer. +/** The contents of the scroll area, i.e. a widget of type ChatWidgetContents, + * needs to be provided by calling init(). We don't create this widget ourselves, because + * while a ChatWidget will be destroyed and recreated quite often (for example when switching + * buffers), there ususally is no need to re-render its content every time (which can be time-consuming). + * Before a ChatWidget is destroyed, it gives up its ownership of its contents, referring responsibility + * back to where it came from. + * + * Because we use this as a custom widget in Qt Designer, we cannot use a constructor that takes custom + * parameters. Instead, it is mandatory to call init() before using this widget. + */ +class ChatWidget : public QScrollArea { + Q_OBJECT + + public: + ChatWidget(QWidget *parent = 0); + ~ChatWidget(); + void init(QString net, QString buf, ChatWidgetContents *contents); + + public slots: + void clear(); + void appendMsg(Message); + + protected: + void resizeEvent(QResizeEvent *event); + + private: + QString networkName, bufferName; + ChatWidgetContents *contents; +}; + +class ChatLine; + +//!\brief Renders the complete contents of a Buffer. +/** Usually, this widget is used within a scroll + * area. Because Qt's rich-text rendering engine is much too slow for our purposes, we do everything + * except for the actual glyph painting ourselves. While this makes managing text quite cumbersome + * (we cannot, for example, use any of Qt's text editing, selecting or layouting features), it is + * also orders of magnitudes faster than any of the usual methods. + */ +class ChatWidgetContents : public QWidget { + Q_OBJECT + + public: + ChatWidgetContents(QString net, QString buf, QWidget *parent = 0); + ~ChatWidgetContents(); + //int heightForWidth(int w) const; + virtual QSize sizeHint() const; + + public slots: + void clear(); + void appendMsg(Message); + void setWidth(qreal); + + protected: + virtual void paintEvent(QPaintEvent *event); + + protected slots: + virtual void mousePressEvent(QMouseEvent *event); + virtual void mouseReleaseEvent(QMouseEvent *event); + virtual void mouseMoveEvent(QMouseEvent *event); + + private slots: + void layout(bool timer = false); + void triggerLayout(); + + private: + enum SelectionMode { NoSelection, TextSelected, LinesSelected }; + enum MouseMode { Normal, Pressed, DragTsSep, DragTextSep, MarkText, MarkLines }; + enum MousePos { None, OverTsSep, OverTextSep, OverUrl }; + MouseMode mouseMode; + MousePos mousePos; + QPoint dragStartPos; + MouseMode dragStartMode; + int dragStartLine; + int dragStartCursor; + int curCursor; + int curLine; + SelectionMode selectionMode; + int selectionStart, selectionEnd, selectionLine; + QString networkName, bufferName; + QTimer *layoutTimer; + bool doLayout; + + QList lines; + QList ycoords; + + int senderX; + int textX; + int tsWidth; + int senderWidth; + int textWidth; + int tsGrabPos; ///< X-Position for changing the timestamp width + int senderGrabPos; + void computePositions(); + + qreal width; + qreal height; + qreal y; + + int yToLineIdx(qreal y); + void clearSelection(); + QString selectionToString(); +}; + +//!\brief Containing the layout and providing the rendering of a single message. +/** A ChatLine takes a Message object, + * formats it (by turning the various message types into a human-readable form and afterwards pumping it through + * our Style engine), and stores it as a number of QTextLayouts representing the three fields of a chat line + * (timestamp, sender and text). These layouts already include any rendering information such as font, + * color, or selected characters. By calling layout(), they can be quickly layouted to fit a given set of field widths. + * Afterwards, they can quickly be painted whenever necessary. + * + * By separating the complex and slow task of interpreting and formatting Message objects (which happens exactly once + * per message) from the actual layouting and painting, we gain a lot of speed compared to the standard Qt rendering + * functions. + */ +class ChatLine : public QObject { + Q_OBJECT + + public: + ChatLine(Message message, QString networkName, QString bufferName); + ~ChatLine(); + + qreal layout(qreal tsWidth, qreal nickWidth, qreal textWidth); + qreal height() { return hght; } + int posToCursor(QPointF pos); + void draw(QPainter *p, const QPointF &pos); + + enum SelectionMode { None, Partial, Full }; + void setSelection(SelectionMode, int start = 0, int end = 0); + QDateTime getTimeStamp(); + QString getSender(); + QString getText(); + private: + qreal hght; + Message msg; + QString networkName, bufferName; + //QString ts, nick, text; + qreal tsWidth, senderWidth, textWidth; + QTextLayout tsLayout; + QTextLayout senderLayout; + QTextLayout textLayout; + QVector tsFormat, senderFormat, textFormat; + Style::StringFormats tsFormatted, senderFormatted, textFormatted; + + SelectionMode selectionMode; + int selectionStart, selectionEnd; + void formatMsg(Message); +}; + + +#endif diff --git a/gui/mainwin.cpp b/gui/mainwin.cpp index dd1fcb9f..e9fe9e45 100644 --- a/gui/mainwin.cpp +++ b/gui/mainwin.cpp @@ -30,6 +30,7 @@ #include "networkview.h" #include "serverlist.h" #include "coreconnectdlg.h" +#include "settings.h" MainWin::MainWin() : QMainWindow() { ui.setupUi(this); @@ -45,8 +46,8 @@ 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...")); netView = new NetworkView("", this); @@ -77,6 +78,8 @@ MainWin::MainWin() : QMainWindow() { serverListDlg = new ServerListDlg(this); serverListDlg->setVisible(serverListDlg->showOnStartup()); + settingsDlg = new SettingsDlg(this); + settingsDlg->setVisible(false); setupMenus(); // replay backlog @@ -112,6 +115,8 @@ MainWin::MainWin() : QMainWindow() { void MainWin::setupMenus() { connect(ui.actionNetworkList, SIGNAL(triggered()), this, SLOT(showServerList())); connect(ui.actionEditIdentities, SIGNAL(triggered()), serverListDlg, SLOT(editIdentities())); + connect(ui.actionSettingsDlg, SIGNAL(triggered()), this, SLOT(showSettingsDlg())); + ui.actionSettingsDlg->setEnabled(false); } void MainWin::showServerList() { @@ -121,6 +126,10 @@ void MainWin::showServerList() { serverListDlg->show(); } +void MainWin::showSettingsDlg() { + settingsDlg->show(); +} + void MainWin::closeEvent(QCloseEvent *event) { //if (userReallyWantsToQuit()) { @@ -145,13 +154,14 @@ void MainWin::showBuffer(QString net, QString 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->addWindow(widget); + //widget->show(); + setCentralWidget(widget); + widget->show(); + //workspace->setActiveWindow(widget); + widget->setFocus(); //workspace->setFocus(); - //widget->activateWindow(); + widget->activateWindow(); widget->setFocus(Qt::MouseFocusReason); focusNextChild(); //workspace->tile(); @@ -172,7 +182,7 @@ void MainWin::networkDisconnected(QString net) { Buffer *b = getBuffer(net, buf); b->displayMsg(Message::server(buf, tr("Server disconnected."))); b->setActive(false); - + } connected[net] = false; } diff --git a/gui/mainwin.h b/gui/mainwin.h index d8996953..2d2516a0 100644 --- a/gui/mainwin.h +++ b/gui/mainwin.h @@ -31,7 +31,13 @@ class ServerListDlg; class CoreConnectDlg; class NetworkView; class Buffer; +class SettingsDlg; +//!\brief The main window and central object of Quassel GUI. +/** In addition to displaying the main window including standard stuff like a menubar, + * dockwidgets and of course the chat window, this class also stores all data it + * receives from the core, and it maintains a list of all known nicks. + */ class MainWin : public QMainWindow { Q_OBJECT @@ -61,6 +67,7 @@ class MainWin : public QMainWindow { void setOwnNick(QString net, QString nick); void showServerList(); + void showSettingsDlg(); void showBuffer(QString net, QString buf); @@ -77,6 +84,7 @@ class MainWin : public QMainWindow { ServerListDlg *serverListDlg; CoreConnectDlg *coreConnectDlg; + SettingsDlg *settingsDlg; QString currentNetwork, currentBuffer; QHash > buffers; diff --git a/network/buffer.cpp b/gui/settings.cpp similarity index 89% rename from network/buffer.cpp rename to gui/settings.cpp index d0b500a3..a4efaeaf 100644 --- a/network/buffer.cpp +++ b/gui/settings.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005/06 by The Quassel Team * + * Copyright (C) 2005-07 by The Quassel Team * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -18,15 +18,9 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -/* OBSOLETE */ - -#include "buffer.h" -/* -Buffer::Buffer(QString n) { - _name = n; - +#include "settings.h" +SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { + ui.setupUi(this); } - -*/ \ No newline at end of file diff --git a/network/builtin_handlers.cpp b/gui/settings.h similarity index 76% rename from network/builtin_handlers.cpp rename to gui/settings.h index 86a41fc0..ed9be0ed 100644 --- a/network/builtin_handlers.cpp +++ b/gui/settings.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005/06 by The Quassel Team * + * Copyright (C) 2005-07 by The Quassel Team * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -18,5 +18,26 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -#include "message.h" +#ifndef _SETTINGS_H_ +#define _SETTINGS_H_ +#include +#include "ui_settingsdlg.h" + +#include "plugin.h" + +class SettingsDlg : public QDialog { + Q_OBJECT + public: + SettingsDlg(QWidget *parent = 0); + void registerSettingsPage(SettingsInterface *); + void unregisterSettingsPage(SettingsInterface *); + + + private: + Ui::SettingsDlg ui; + + +}; + +#endif diff --git a/gui/style.cpp b/gui/style.cpp new file mode 100644 index 00000000..8837872e --- /dev/null +++ b/gui/style.cpp @@ -0,0 +1,256 @@ +/*************************************************************************** + * Copyright (C) 2005-07 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 "style.h" + +void Style::init() { + // Colors (mIRC standard) + colors["00"] = QColor("white"); + colors["01"] = QColor("black"); + colors["02"] = QColor("navy"); + colors["03"] = QColor("green"); + colors["04"] = QColor("red"); + colors["05"] = QColor("maroon"); + colors["06"] = QColor("purple"); + colors["07"] = QColor("orange"); + colors["08"] = QColor("yellow"); + colors["09"] = QColor("lime"); + colors["10"] = QColor("teal"); + colors["11"] = QColor("aqua"); + colors["12"] = QColor("royalblue"); + colors["13"] = QColor("fuchsia"); + colors["14"] = QColor("grey"); + colors["15"] = QColor("silver"); + + QTextCharFormat def; + //def.setFont(QFont("Lucida Mono")); + formats["default"] = def; + + // %B - 0x02 - bold + QTextCharFormat bold; + bold.setFontWeight(QFont::Bold); + formats["%B"] = bold; + + // %O - 0x0f - plain + formats["%O"] = QTextCharFormat(); + + // %R - 0x12 - reverse + // -- - 0x16 - reverse + // (no format) + + // %S - 0x1d - italic + QTextCharFormat italic; + italic.setFontItalic(true); + formats["%S"] = italic; + + // %U - 0x1f - underline + QTextCharFormat underline; + underline.setFontUnderline(true); + formats["%U"] = underline; + + // %C - 0x03 - mIRC colors + for(uint i = 0; i < 16; i++) { + QString idx = QString("%1").arg(i, (int)2, (int)10, (QChar)'0'); + QString fg = QString("%C%1").arg(idx); + QString bg = QString("%C,%1").arg(idx); + QTextCharFormat fgf; fgf.setForeground(QBrush(colors[idx])); formats[fg] = fgf; + QTextCharFormat bgf; bgf.setBackground(QBrush(colors[idx])); formats[bg] = bgf; + } + + // Internal formats - %D + // %D0 - plain msg + QTextCharFormat plainMsg; + plainMsg.setForeground(QBrush("black")); + formats["%D0"] = plainMsg; + // %Dn - notice + QTextCharFormat notice; + notice.setForeground(QBrush("navy")); + formats["%Dn"] = notice; + // %Ds - server msg + QTextCharFormat server; + server.setForeground(QBrush("navy")); + formats["%Ds"] = server; + // %De - error msg + QTextCharFormat error; + error.setForeground(QBrush("red")); + formats["%De"] = error; + // %Dj - join + QTextCharFormat join; + join.setForeground(QBrush("green")); + formats["%Dj"] = join; + // %Dp - part + QTextCharFormat part; + part.setForeground(QBrush("firebrick")); + formats["%Dp"] = part; + // %Dq - quit + QTextCharFormat quit; + quit.setForeground(QBrush("firebrick")); + formats["%Dq"] = quit; + // %Dk - kick + QTextCharFormat kick; + kick.setForeground(QBrush("firebrick")); + formats["%Dk"] = kick; + // %Dr - nick rename + QTextCharFormat nren; + nren.setForeground(QBrush("magenta")); + formats["%Dr"] = nren; + // %Dm - mode change + QTextCharFormat mode; + mode.setForeground(QBrush("steelblue")); + formats["%Dm"] = mode; + + // %DT - timestamp + QTextCharFormat ts; + ts.setForeground(QBrush("grey")); + formats["%DT"] = ts; + // %DS - sender + QTextCharFormat sender; + sender.setAnchor(true); + sender.setForeground(QBrush("navy")); + formats["%DS"] = sender; + // %DN - nickname + QTextCharFormat nick; + nick.setAnchor(true); + nick.setFontWeight(QFont::Bold); + formats["%DN"] = nick; + // %DH - hostmask + QTextCharFormat hostmask; + hostmask.setFontItalic(true); + formats["%DH"] = hostmask; + // %DC - channame + QTextCharFormat channel; + channel.setAnchor(true); + channel.setFontWeight(QFont::Bold); + formats["%DC"] = channel; + // %DM - modeflags + QTextCharFormat flags; + flags.setFontWeight(QFont::Bold); + formats["%DM"] = flags; + +} + +QString Style::mircToInternal(QString mirc) { + mirc.replace('%', "%%"); // escape % just to be sure + mirc.replace('\x02', "%B"); + mirc.replace('\x03', "%C"); + mirc.replace('\x0f', "%O"); + mirc.replace('\x12', "%R"); + mirc.replace('\x16', "%R"); + mirc.replace('\x1d', "%S"); + mirc.replace('\x1f', "%U"); + return mirc; +} + +/** Returns a string stripped of format codes, and a list of FormatRange objects + * describing the formats of the string. + * \param s string in internal format (% style format codes) + */ +Style::StringFormats Style::internalToFormatted(QString s) { + QHash toggles; + QString p; + StringFormats sf; + QTextLayout::FormatRange rng; + rng.format = formats["default"]; rng.start = 0; rng.length = -1; sf.formats.append(rng); + toggles["default"] = sf.formats.count() - 1; + int i, j; + for(i = 0, j = 0; i < s.length(); i++) { + if(s[i] != '%') { p += s[i]; j++; continue; } + i++; + if(s[i] == '%') { p += '%'; j++; continue; } + else if(s[i] == 'C') { + if(!s[i+1].isDigit() && s[i+1] != ',') { + if(toggles.contains("bg")) { + sf.formats[toggles["bg"]].length = j - sf.formats[toggles["bg"]].start; + toggles.remove("bg"); + } + } + if(s[i+1].isDigit() || s[i+1] != ',') { + if(toggles.contains("fg")) { + sf.formats[toggles["fg"]].length = j - sf.formats[toggles["fg"]].start; + toggles.remove("fg"); + } + if(s[i+1].isDigit()) { + QString n(s[++i]); + if(s[i+1].isDigit()) n += s[++i]; + int num = n.toInt() & 0xf; + n = QString("%C%1").arg(num, (int)2, (int)10, (QChar)'0'); + //qDebug() << n << formats[n].foreground(); + QTextLayout::FormatRange range; + range.format = formats[n]; range.start = j; range.length = -1; sf.formats.append(range); + toggles["fg"] = sf.formats.count() - 1; + } + } + if(s[i+1] == ',') { + if(toggles.contains("bg")) { + sf.formats[toggles["bg"]].length = j - sf.formats[toggles["bg"]].start; + toggles.remove("bg"); + } + i++; + if(s[i+1].isDigit()) { + QString n(s[++i]); + if(s[i+1].isDigit()) n += s[++i]; + int num = n.toInt() & 0xf; + n = QString("%C,%1").arg(num, (int)2, (int)10, (QChar)'0'); + QTextLayout::FormatRange range; + range.format = formats[n]; range.start = j; range.length = -1; + sf.formats.append(range); + toggles["bg"] = sf.formats.count() - 1; + } + } + } else if(s[i] == 'O') { + foreach(QString key, toggles.keys()) { + sf.formats[toggles[key]].length = j - sf.formats[toggles[key]].start; + toggles.remove(key); + } + + } else if(s[i] == 'R') { + // TODO implement reverse formatting + + } else { + // all others are toggles + QString key = "%"; key += s[i]; + if(s[i] == 'D') key += s[i+1]; + if(formats.contains(key)) { + if(s[i] == 'D') i++; + if(toggles.contains(key)) { + sf.formats[toggles[key]].length = j - sf.formats[toggles[key]].start; + toggles.remove(key); + } else { + QTextLayout::FormatRange range; + range.format = formats[key]; range.start = j; range.length = -1; + sf.formats.append(range); + toggles[key] = sf.formats.count() -1; + } + } else { + // unknown format + p += '%'; p += s[i]; j+=2; + } + } + } + foreach(int idx, toggles.values()) { + sf.formats[idx].length = j - sf.formats[idx].start; + } + sf.text = p; + return sf; +} + +QHash Style::formats; +QHash Style::colors; + diff --git a/network/buffer.h b/gui/style.h similarity index 74% rename from network/buffer.h rename to gui/style.h index 8429cfc3..19c3be69 100644 --- a/network/buffer.h +++ b/gui/style.h @@ -18,36 +18,33 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -/* THIS CODE IS OBSOLETE, PENDING REMOVAL */ - -#ifndef _BUFFER_OLD_H_ -#define _BUFFER_OLD_H_ +#ifndef _STYLE_H_ +#define _STYLE_H_ #include +#include -class Buffer_old : public QObject { - Q_OBJECT +class Style { public: - Buffer(QString name); + static void init(); - QString name() { return _name; } - QString topic() { return _topic; } + struct StringFormats { + QString text; + QList formats; + }; - public slots: - //void setNicks(QStringList nicks); - //void addNick(QString nick); - //void removeNick(QString nick); + static QString mircToInternal(QString); + //static QString internalToMirc(QString); + static StringFormats internalToFormatted(QString); + static int sepTsSender() { return 10; } + static int sepSenderText() { return 10; } - signals: private: - QString _name; - QString _topic; - QStringList nicks; + static QHash formats; + static QHash colors; }; - - #endif diff --git a/gui/ui/bufferwidget.ui b/gui/ui/bufferwidget.ui index b3bab0d7..854d4918 100644 --- a/gui/ui/bufferwidget.ui +++ b/gui/ui/bufferwidget.ui @@ -31,7 +31,7 @@ - 1 + 9 6 @@ -79,43 +79,18 @@ - - - 7 - 5 - 0 - 0 - - - - 1 - Qt::Horizontal - + - 7 + 5 5 - 4 + 6 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> - @@ -252,6 +227,11 @@ p, li { white-space: pre-wrap; } + + ChatWidget + QWidget +
chatwidget.h
+
ChannelWidgetInput QLineEdit @@ -264,7 +244,6 @@ p, li { white-space: pre-wrap; } nickTree topicEdit chanSettingsButton - chatWidget diff --git a/gui/ui/mainwin.ui b/gui/ui/mainwin.ui index 152f3bd4..32f9fa84 100644 --- a/gui/ui/mainwin.ui +++ b/gui/ui/mainwin.ui @@ -19,19 +19,9 @@ 0 0 800 - 29 + 32 - - - Views - - - - - Help - - Connection @@ -48,15 +38,29 @@ + + + Views + + Settings + + + + + + + + Help + - + @@ -118,6 +122,14 @@ Edit Identities... + + + Configure Quassel... + + + F7 + + diff --git a/gui/ui/settingsdlg.ui b/gui/ui/settingsdlg.ui index dd7af107..771012a2 100644 --- a/gui/ui/settingsdlg.ui +++ b/gui/ui/settingsdlg.ui @@ -1,18 +1,18 @@ - SettingsDialog - + SettingsDlg + 0 0 - 700 - 577 + 848 + 514 - Settings + Dialog - + 9 @@ -20,44 +20,7 @@ 6 - - - - 4 - 7 - 1 - 0 - - - - - Settings - - - - - General - - - - - Appearance - - - - - Connection - - - - - Plugins - - - - - - + 0 @@ -65,31 +28,52 @@ 6 - + 5 - 5 - 4 + 7 + 0 0 - - + + + Settings + + - - - Qt::Horizontal + + + + 3 + 5 + 0 + 0 + - - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults + + QFrame::StyledPanel + + + QFrame::Raised + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + @@ -97,7 +81,7 @@ buttonBox accepted() - SettingsDialog + SettingsDlg accept() @@ -113,7 +97,7 @@ buttonBox rejected() - SettingsDialog + SettingsDlg reject() diff --git a/main/main_gui.cpp b/main/main_gui.cpp index b6e15b09..3ee9f9f2 100644 --- a/main/main_gui.cpp +++ b/main/main_gui.cpp @@ -23,6 +23,7 @@ #include #include +#include "style.h" #include "global.h" #include "guiproxy.h" #include "coreconnectdlg.h" @@ -42,6 +43,8 @@ int main(int argc, char **argv) { global = new Global(); guiProxy = new GUIProxy(); + Style::init(); + MainWin mainWin; int exitCode = app.exec(); delete guiProxy; diff --git a/main/main_mono.cpp b/main/main_mono.cpp index 9384294a..268f3c1a 100644 --- a/main/main_mono.cpp +++ b/main/main_mono.cpp @@ -23,6 +23,7 @@ #include #include "core.h" +#include "style.h" #include "global.h" #include "guiproxy.h" #include "coreproxy.h" @@ -42,6 +43,8 @@ int main(int argc, char **argv) { guiProxy = new GUIProxy(); coreProxy = new CoreProxy(); + Style::init(); + MainWin mainWin; mainWin.show(); int exitCode = app.exec(); diff --git a/network/CMakeLists.txt b/network/CMakeLists.txt deleted file mode 100644 index 693ecedb..00000000 --- a/network/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -SET(network_SRCS server.cpp) -SET(network_HDRS) -SET(network_MOCS server.h) - -QT4_WRAP_CPP(_MOC ${network_MOCS}) -ADD_LIBRARY(network ${_MOC} ${network_SRCS} ${network_HDRS}) diff --git a/network/cmdcodes.h b/network/cmdcodes.h deleted file mode 100644 index 7f9625f3..00000000 --- a/network/cmdcodes.h +++ /dev/null @@ -1,39 +0,0 @@ -/*************************************************************************** - * 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. * - ***************************************************************************/ - -/* THIS CODE IS OBSOLETE! */ - -#ifndef _CMDCODES_H_ -#define _CMDCODES_H_ - -/** Contains numeric codes for the named commands. These are _negative_ in the message object. */ -enum CmdCodes { - CMD_ADMIN = 1, CMD_AME, CMD_AMSG, CMD_AWAY, CMD_BAN, CMD_CTCP, CMD_CYCLE, CMD_DEHALFOP, CMD_DEOP, CMD_DEVOICE, - CMD_DIE, CMD_ERROR, CMD_HALFOP, CMD_INFO, CMD_INVITE, CMD_ISON, CMD_JOIN, CMD_KICK, CMD_KICKBAN, CMD_KILL, CMD_LINKS, - CMD_LIST, CMD_LUSERS, CMD_ME, CMD_MODE, CMD_MOTD, CMD_MSG, CMD_NAMES, CMD_NICK, CMD_NOTICE, CMD_OP, CMD_OPER, - CMD_PART, CMD_PING, CMD_PONG, CMD_PRIVMSG, CMD_QUERY, CMD_QUIT, CMD_QUOTE, CMD_REHASH, CMD_RESTART, CMD_SERVICE, - CMD_SERVLIST, CMD_SQUERY, CMD_SQUIT, CMD_STATS, CMD_SUMMON, CMD_TIME, CMD_TOPIC, CMD_TRACE, CMD_UNBAN, CMD_USERHOST, - CMD_USERS, CMD_VERSION, CMD_VOICE, CMD_WALLOPS, CMD_WHO, CMD_WHOIS, CMD_WHOWAS, - CMD_USERDEFINED -}; - - - -#endif diff --git a/network/server.cpp b/network/server.cpp deleted file mode 100644 index ffc90548..00000000 --- a/network/server.cpp +++ /dev/null @@ -1,619 +0,0 @@ -/*************************************************************************** - * 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. * - ***************************************************************************/ - -/* OBSOLETE CODE, MOVED TO core/ */ - -#error "obsolete code" - -#include "util.h" -#include "global.h" -#include "server.h" -#include "cmdcodes.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::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( ) { - 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(); -} - -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].toMap(); - 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] == currentNick) 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(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(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(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(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(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(Message::Error, cmd + " " + params.join(" "), prefix)); - } - //qDebug() << prefix <<":"< 2) msg = QString("%1 %2").arg(msg).arg(params[2]); - emit displayMsg(params[0], Message(Message::Kick, 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); - } -} - -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 && nicks.contains(params[p])); - QString nick = params[p++]; - VarMap n = nicks[nick].toMap(); 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(params[0], Message(Message::Mode, params.join(" "), prefix)); - } else { - //Q_ASSERT(nicks.contains(params[0])); - //VarMap n = nicks[params[0]].toMap(); - //QString mode = n["Mode"].toString(); - emit displayMsg("", Message(Message::Mode, params.join(" "))); - } -} - -void Server::handleServerNick(QString prefix, QStringList params) { - QString oldnick = updateNickFromMask(prefix); - QString newnick = params[0]; - QVariant v = nicks.take(oldnick); - nicks[newnick] = v; - VarMap chans = v.toMap()["Channels"].toMap(); - foreach(QString c, chans.keys()) { - if(oldnick != currentNick) { emit displayMsg(c, Message(Message::Nick, newnick, prefix)); } - else { emit displayMsg(c, Message(Message::Nick, newnick, newnick)); } - } - emit nickRenamed(network, oldnick, newnick); - if(oldnick == currentNick) { - currentNick = 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(Message::Server, params[1], prefix)); - else emit displayMsg("", Message(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].toMap(); - 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(params[0], Message(Message::Part, 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); - } -} - -void Server::handleServerPing(QString prefix, QStringList params) { - putCmd("PONG", params); -} - -void Server::handleServerPrivmsg(QString prefix, QStringList params) { - updateNickFromMask(prefix); - emit displayMsg(params[0], Message(Message::Msg, params[1], prefix)); - -} - -void Server::handleServerQuit(QString prefix, QStringList params) { - QString nick = updateNickFromMask(prefix); - Q_ASSERT(nicks.contains(nick)); - VarMap chans = nicks[nick].toMap()["Channels"].toMap(); - foreach(QString c, chans.keys()) { - emit displayMsg(c, Message(Message::Quit, params[0], prefix)); - } - nicks.remove(nick); - emit nickRemoved(network, nick); -} - -/* RPL_WELCOME */ -void Server::handleServer001(QString prefix, QStringList params) { - currentServer = prefix; - currentNick = params[0]; - VarMap n; - n["Channels"] = VarMap(); - nicks[currentNick] = n; - emit ownNickSet(network, currentNick); - emit nickAdded(network, currentNick, VarMap()); - emit displayMsg("", Message(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) { - emit topicSet(network, params[0], ""); - emit displayMsg(params[0], Message(Message::Server, tr("No topic is set for %1.").arg(params[0]))); -} - -/* RPL_TOPIC */ -void Server::handleServer332(QString prefix, QStringList params) { - emit topicSet(network, params[0], params[1]); - emit displayMsg(params[0], Message(Message::Server, tr("Topic for %1 is \"%2\"").arg(params[0]).arg(params[1]))); -} - -/* Topic set by... */ -void Server::handleServer333(QString prefix, QStringList params) { - emit displayMsg(params[0], Message(Message::Server, - 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].toMap(); - 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/network/server.h b/network/server.h deleted file mode 100644 index f0f3a855..00000000 --- a/network/server.h +++ /dev/null @@ -1,158 +0,0 @@ -/*************************************************************************** - * 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. * - ***************************************************************************/ - -#error "obsolete code" - -#ifndef _SERVER_H_ -#define _SERVER_H_ - -#include -#include -#include - -#include "global.h" -#include "buffer.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 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 recvRawServerMsg(QString); - void displayStatusMsg(QString); - void displayMsg(Message msg); - void disconnected(); - - 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 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 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 currentNick; - QString currentServer; - VarMap networkSettings; - VarMap identity; - VarMap nicks; // stores all known nicks for the server - 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/plugins/plugin.h b/plugins/plugin.h new file mode 100644 index 00000000..5fa70524 --- /dev/null +++ b/plugins/plugin.h @@ -0,0 +1,110 @@ +/*************************************************************************** + * 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 _PLUGIN_H_ +#define _PLUGIN_H_ + +// TODO disable GUI-related stuff for core-only compile + +#include +#include + +/** \file plugin.h + * Header that needs to be included by all plugins and defines the interfaces a plugin + * can implement. + */ + +/// The base class for the generic plugin interfaces. +class PluginInterface { + + +}; + + +/// All GUI plugins need to implement this interface. +class GuiPluginInterface : public PluginInterface { + + +}; + +Q_DECLARE_INTERFACE(GuiPluginInterface, + "eu.quassel.plugins.GuiPluginInterface/1.0"); + +/// All core plugins need to implement this interface. +class CorePluginInterface: public PluginInterface { + + + +}; + +Q_DECLARE_INTERFACE(CorePluginInterface, + "eu.quassel.plugins.CorePluginInterface/1.0"); + +/** Plugins implementing this interface can provide a settings widget that will be shown in + * the application's settings dialog. + * This is also used by built-in settings dialogs. + */ +class SettingsInterface { + public: + virtual QWidget *settingsWidget(QWidget *parent = 0) = 0; + +}; + +Q_DECLARE_INTERFACE(SettingsInterface, + "eu.quassel.plugins.SettingsInterface/1.0"); + +/** Plugins implementing this interface will be provided with the raw text the users enters. + * The output they generate is in turn treated like generic user input. Note that the order in + * which plugins are called is not defined. + */ +class UserInputFilterInterface { + + + +}; + +Q_DECLARE_INTERFACE(UserInputFilterInterface, + "eu.quassel.plugins.UserInputFilterInterface/1.0"); + +/** Plugins implementing this interface receive and can process all messages coming from the core. + */ +class CoreMessageFilterInterface { + + + +}; + +Q_DECLARE_INTERFACE(CoreMessageFilterInterface, + "eu.quassel.plugins.CoreMessageFilterInterface/1.0"); + +/** Plugins implementing this interface receive core messages for a single buffer only. + * Any ChatWidgetPlugin has to provide a widget it outputs these messages to, to be used + * in a ChannelWidget. + */ +class ChatWidgetInterface { + + + +}; + +Q_DECLARE_INTERFACE(ChatWidgetInterface, + "eu.quassel.plugins.ChatWidgetInterface/1.0"); + +#endif diff --git a/templates/cpp b/templates/cpp index e204ef82..53f70707 100644 --- a/templates/cpp +++ b/templates/cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005/06 by The Quassel Team * + * Copyright (C) 2005-07 by The Quassel Team * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * diff --git a/templates/h b/templates/h index 5c252274..21a1438d 100644 --- a/templates/h +++ b/templates/h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005/06 by The Quassel Team * + * Copyright (C) 2005-07 by The Quassel Team * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * -- 2.20.1