From 70706ff642683d03ff091cab25d984ec7d9612de Mon Sep 17 00:00:00 2001 From: Manuel Nickschas Date: Tue, 30 Oct 2007 23:17:59 +0000 Subject: [PATCH] Adding work-in-progress for the new QGraphicsScene-based chatview. Disabled for now, because it will require a lot of work and time I don't have now. So we'll live with the old chatwidget (that does not display selections anymore) until QuasselTopia is released... --- Quassel.kdevelop.filelist | 8 + src/client/buffer.h | 4 +- src/client/quasselui.h | 2 +- src/common/global.h | 2 + src/common/message.cpp | 18 +- src/common/message.h | 8 +- src/core/sqlitestorage.cpp | 6 +- src/qtopia/chatline.cpp | 12 +- src/qtopia/chatline.h | 8 +- src/qtui/bufferwidget.cpp | 27 ++- src/qtui/bufferwidget.h | 28 +-- src/qtui/chatitem.cpp | 119 +++++++++++ src/qtui/chatitem.h | 59 ++++++ src/qtui/chatline-old.cpp | 373 ++++++++++++++++++++++++++++++++++ src/qtui/chatline-old.h | 109 ++++++++++ src/qtui/chatline.cpp | 387 +++++++----------------------------- src/qtui/chatline.h | 96 +++------ src/qtui/chatscene.cpp | 69 +++++++ src/qtui/chatscene.h | 54 +++++ src/qtui/chatview.cpp | 87 ++++++++ src/qtui/chatview.h | 58 ++++++ src/qtui/chatwidget.cpp | 4 +- src/qtui/mainwin.cpp | 2 +- src/qtui/qtui.pri | 12 +- src/qtui/ui/bufferwidget.ui | 6 +- src/uisupport/uistyle.cpp | 4 +- src/uisupport/uistyle.h | 4 +- templates/cpp | 2 +- templates/h | 2 +- 29 files changed, 1116 insertions(+), 454 deletions(-) create mode 100644 src/qtui/chatitem.cpp create mode 100644 src/qtui/chatitem.h create mode 100644 src/qtui/chatline-old.cpp create mode 100644 src/qtui/chatline-old.h create mode 100644 src/qtui/chatscene.cpp create mode 100644 src/qtui/chatscene.h create mode 100644 src/qtui/chatview.cpp create mode 100644 src/qtui/chatview.h diff --git a/Quassel.kdevelop.filelist b/Quassel.kdevelop.filelist index f534cf85..f9798c02 100644 --- a/Quassel.kdevelop.filelist +++ b/Quassel.kdevelop.filelist @@ -469,8 +469,16 @@ src/qtui/bufferwidget.cpp src/qtui/bufferwidget.h src/qtui/channelwidgetinput.cpp src/qtui/channelwidgetinput.h +src/qtui/chatitem.cpp +src/qtui/chatitem.h +src/qtui/chatline-old.cpp +src/qtui/chatline-old.h src/qtui/chatline.cpp src/qtui/chatline.h +src/qtui/chatscene.cpp +src/qtui/chatscene.h +src/qtui/chatview.cpp +src/qtui/chatview.h src/qtui/chatwidget.cpp src/qtui/chatwidget.h src/qtui/coreconnectdlg.cpp diff --git a/src/client/buffer.h b/src/client/buffer.h index 781a996a..e862d5af 100644 --- a/src/client/buffer.h +++ b/src/client/buffer.h @@ -61,8 +61,8 @@ public: BufferInfo bufferInfo() const; void updateBufferInfo(BufferInfo bufferid); - uint uid() const; - uint networkId() const; + BufferId uid() const; + NetworkId networkId() const; QString networkName() const; QString name() const; diff --git a/src/client/quasselui.h b/src/client/quasselui.h index 327c37f6..c4280ba9 100644 --- a/src/client/quasselui.h +++ b/src/client/quasselui.h @@ -32,7 +32,7 @@ class AbstractUiMsg { virtual QString text() const = 0; virtual MsgId msgId() const = 0; virtual BufferInfo bufferInfo() const = 0; - virtual QDateTime timeStamp() const = 0; + virtual QDateTime timestamp() const = 0; }; diff --git a/src/common/global.h b/src/common/global.h index fd285ca0..6da6103c 100644 --- a/src/common/global.h +++ b/src/common/global.h @@ -38,6 +38,8 @@ typedef uint UserId; typedef uint MsgId; +typedef uint BufferId; +typedef uint NetworkId; namespace Global { enum RunMode { Monolithic, ClientOnly, CoreOnly }; diff --git a/src/common/message.cpp b/src/common/message.cpp index 46ae4849..fc4dfccb 100644 --- a/src/common/message.cpp +++ b/src/common/message.cpp @@ -26,11 +26,11 @@ Message::Message(BufferInfo __buffer, Type __type, QString __text, QString __sender, quint8 __flags) : _buffer(__buffer), _text(__text), _sender(__sender), _type(__type), _flags(__flags) { - _timeStamp = QDateTime::currentDateTime().toUTC(); + _timestamp = QDateTime::currentDateTime().toUTC(); } Message::Message(QDateTime __ts, BufferInfo __buffer, Type __type, QString __text, QString __sender, quint8 __flags) - : _timeStamp(__ts), _buffer(__buffer), _text(__text), _sender(__sender), _type(__type), _flags(__flags) { + : _timestamp(__ts), _buffer(__buffer), _text(__text), _sender(__sender), _type(__type), _flags(__flags) { } @@ -62,8 +62,8 @@ quint8 Message::flags() const { return _flags; } -QDateTime Message::timeStamp() const { - return _timeStamp; +QDateTime Message::timestamp() const { + return _timestamp; } QString Message::mircToInternal(QString mirc) { @@ -118,7 +118,7 @@ void Message::format() { QString networkName = buffer().network(); QString bufferName = buffer().buffer(); - _formattedTimeStamp = tr("%DT[%1]").arg(timeStamp().toLocalTime().toString("hh:mm:ss")); + _formattedTimestamp = tr("%DT[%1]").arg(timestamp().toLocalTime().toString("hh:mm:ss")); QString s, t; switch(type()) { @@ -169,9 +169,9 @@ void Message::format() { _formattedText = t; } -QString Message::formattedTimeStamp() { +QString Message::formattedTimestamp() { format(); - return _formattedTimeStamp; + return _formattedTimestamp; } QString Message::formattedSender() { @@ -193,7 +193,7 @@ QString Message::formattedToHtml(const QString &f) { */ QDataStream &operator<<(QDataStream &out, const Message &msg) { - out << (quint32)msg.timeStamp().toTime_t() << (quint8)msg.type() << (quint8)msg.flags() + out << (quint32)msg.timestamp().toTime_t() << (quint8)msg.type() << (quint8)msg.flags() << msg.buffer() << msg.sender().toUtf8() << msg.text().toUtf8(); return out; } @@ -207,7 +207,7 @@ QDataStream &operator>>(QDataStream &in, Message &msg) { msg._type = (Message::Type)t; msg._flags = (quint8)f; msg._buffer = buf; - msg._timeStamp = QDateTime::fromTime_t(ts); + msg._timestamp = QDateTime::fromTime_t(ts); msg._sender = QString::fromUtf8(s); msg._text = QString::fromUtf8(m); return in; diff --git a/src/common/message.h b/src/common/message.h index 68047db5..43742839 100644 --- a/src/common/message.h +++ b/src/common/message.h @@ -48,9 +48,9 @@ class Message { QString sender() const; Type type() const; quint8 flags() const; - QDateTime timeStamp() const; + QDateTime timestamp() const; - QString formattedTimeStamp(); + QString formattedTimestamp(); QString formattedSender(); QString formattedText(); @@ -59,7 +59,7 @@ class Message { void format(); private: - QDateTime _timeStamp; + QDateTime _timestamp; MsgId _msgId; BufferInfo _buffer; QString _text; @@ -67,7 +67,7 @@ class Message { Type _type; quint8 _flags; - QString _formattedTimeStamp, _formattedSender, _formattedText; // cache + QString _formattedTimestamp, _formattedSender, _formattedText; // cache /** Convert mIRC control codes to our own */ QString mircToInternal(QString); diff --git a/src/core/sqlitestorage.cpp b/src/core/sqlitestorage.cpp index 4e22c079..f148db94 100644 --- a/src/core/sqlitestorage.cpp +++ b/src/core/sqlitestorage.cpp @@ -356,7 +356,7 @@ QList SqliteStorage::requestBuffers(UserId user, QDateTime since) { } MsgId SqliteStorage::logMessage(Message msg) { - logMessageQuery->bindValue(":time", msg.timeStamp().toTime_t()); + logMessageQuery->bindValue(":time", msg.timestamp().toTime_t()); logMessageQuery->bindValue(":bufferid", msg.buffer().uid()); logMessageQuery->bindValue(":type", msg.type()); logMessageQuery->bindValue(":flags", msg.flags()); @@ -376,7 +376,7 @@ MsgId SqliteStorage::logMessage(Message msg) { } } - getLastMessageIdQuery->bindValue(":time", msg.timeStamp().toTime_t()); + getLastMessageIdQuery->bindValue(":time", msg.timestamp().toTime_t()); getLastMessageIdQuery->bindValue(":bufferid", msg.buffer().uid()); getLastMessageIdQuery->bindValue(":type", msg.type()); getLastMessageIdQuery->bindValue(":sender", msg.sender()); @@ -385,7 +385,7 @@ MsgId SqliteStorage::logMessage(Message msg) { if(getLastMessageIdQuery->first()) { return getLastMessageIdQuery->value(0).toUInt(); } else { // somethin went wrong... :( - qDebug() << getLastMessageIdQuery->lastQuery() << "time/bufferid/type/sender:" << msg.timeStamp().toTime_t() << msg.buffer().uid() << msg.type() << msg.sender(); + qDebug() << getLastMessageIdQuery->lastQuery() << "time/bufferid/type/sender:" << msg.timestamp().toTime_t() << msg.buffer().uid() << msg.type() << msg.sender(); Q_ASSERT(false); return 0; } diff --git a/src/qtopia/chatline.cpp b/src/qtopia/chatline.cpp index 531fe280..52f31385 100644 --- a/src/qtopia/chatline.cpp +++ b/src/qtopia/chatline.cpp @@ -25,13 +25,13 @@ ChatLine::ChatLine(Message msg) { _text = msg.text(); // FIXME _sender = msg.sender(); - _timeStamp = msg.timeStamp(); + _timestamp = msg.timestamp(); _msgId = msg.msgId(); _bufferInfo = msg.buffer(); _htmlSender = formattedToHtml(msg.formattedSender()); _htmlText = formattedToHtml(msg.formattedText()); - _htmlTimeStamp = formattedToHtml(msg.formattedTimeStamp()); + _htmlTimestamp = formattedToHtml(msg.formattedTimestamp()); } QString ChatLine::sender() const { @@ -50,8 +50,8 @@ BufferInfo ChatLine::bufferInfo() const { return _bufferInfo; } -QDateTime ChatLine::timeStamp() const { - return _timeStamp; +QDateTime ChatLine::timestamp() const { + return _timestamp; } QString ChatLine::htmlSender() const { @@ -62,8 +62,8 @@ QString ChatLine::htmlText() const { return _htmlText; } -QString ChatLine::htmlTimeStamp() const { - return _htmlTimeStamp; +QString ChatLine::htmlTimestamp() const { + return _htmlTimestamp; } diff --git a/src/qtopia/chatline.h b/src/qtopia/chatline.h index c03f21a8..0477da37 100644 --- a/src/qtopia/chatline.h +++ b/src/qtopia/chatline.h @@ -31,17 +31,17 @@ class ChatLine : public AbstractUiMsg { QString text() const; MsgId msgId() const; BufferInfo bufferInfo() const; - QDateTime timeStamp() const; + QDateTime timestamp() const; QString htmlSender() const; QString htmlText() const; - QString htmlTimeStamp() const; + QString htmlTimestamp() const; private: - QString _sender, _text, _htmlSender, _htmlText, _htmlTimeStamp; + QString _sender, _text, _htmlSender, _htmlText, _htmlTimestamp; MsgId _msgId; BufferInfo _bufferInfo; - QDateTime _timeStamp; + QDateTime _timestamp; QString formattedToHtml(const QString &); diff --git a/src/qtui/bufferwidget.cpp b/src/qtui/bufferwidget.cpp index 5448221b..9bb2cab7 100644 --- a/src/qtui/bufferwidget.cpp +++ b/src/qtui/bufferwidget.cpp @@ -20,7 +20,7 @@ #include "bufferwidget.h" #include "buffer.h" -#include "chatline.h" +#include "chatline-old.h" #include "chatwidget.h" #include "settings.h" @@ -30,18 +30,40 @@ BufferWidget::BufferWidget(QWidget *parent) : QWidget(parent) { } void BufferWidget::init() { + } BufferWidget::~BufferWidget() { + } void BufferWidget::setBuffer(Buffer *buf) { + /* + ChatView *chatView; + if(_chatViews.contains(buf->uid())) { + chatView = _chatViews[buf->uid()]; + } else { + chatView = new ChatView(buf, this); + ui.stackedWidget->addWidget(chatView); + _chatViews[buf->uid()] = chatView; + } + ui.stackedWidget->setCurrentWidget(chatView); + disconnect(this, SIGNAL(userInput(QString)), 0, 0); + connect(this, SIGNAL(userInput(QString)), buf, SLOT(processUserInput(QString))); + //chatView->setFocusProxy(ui.inputEdit); + ui.inputEdit->setFocus(); + ui.ownNick->clear(); // TODO add nick history +} + */ + + // ui.ownNick->addItem(state->ownNick); + ChatWidget *chatWidget; if(_chatWidgets.contains(buf->uid())) { chatWidget = _chatWidgets[buf->uid()]; } else { chatWidget = new ChatWidget(this); - chatWidget->init(networkName, bufferName); + chatWidget->init(buf->networkName(), buf->name()); QList lines; QList msgs = buf->contents(); foreach(AbstractUiMsg *msg, msgs) { @@ -63,6 +85,7 @@ void BufferWidget::setBuffer(Buffer *buf) { } void BufferWidget::saveState() { + } QSize BufferWidget::sizeHint() const { diff --git a/src/qtui/bufferwidget.h b/src/qtui/bufferwidget.h index c3c7aa35..79786dbc 100644 --- a/src/qtui/bufferwidget.h +++ b/src/qtui/bufferwidget.h @@ -23,18 +23,17 @@ #include "ui_bufferwidget.h" +#include "chatview.h" #include "global.h" class Buffer; -struct BufferState; +class ChatView; class ChatWidget; class LayoutThread; -//!\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. - */ +//! Displays the contents of a Buffer. +/** +*/ class BufferWidget : public QWidget { Q_OBJECT @@ -48,23 +47,24 @@ public: signals: void userInput(QString msg); void aboutToClose(); - + public slots: void setBuffer(Buffer *); void saveState(); - + private slots: void enterPressed(); void setActive(bool act = true); - - + + private: Ui::BufferWidget ui; - QHash _chatWidgets; + //QHash _chatViews; + QHash _chatWidgets; bool active; - - QString networkName; - QString bufferName; + + QString _networkName; + QString _bufferName; }; diff --git a/src/qtui/chatitem.cpp b/src/qtui/chatitem.cpp new file mode 100644 index 00000000..08d9f11d --- /dev/null +++ b/src/qtui/chatitem.cpp @@ -0,0 +1,119 @@ +/*************************************************************************** + * Copyright (C) 2005-07 by the Quassel IRC 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 +#include +#include +#include + +#include + +#include "chatitem.h" + +ChatItem::ChatItem(QGraphicsItem *parent) : QGraphicsItem(parent) { + _width = 0; + //if(_wrapMode == WordWrap) { + // setFlags(QGraphicsItem::ItemClipsToShape, true); + //} +} + +ChatItem::~ChatItem() { + +} + +void ChatItem::setWidth(int w) { + _width = w; + layout(); +} + +void ChatItem::setTextOption(const QTextOption &option) { + _textOption = option; + layout(); +} + +QTextOption ChatItem::textOption() const { + return _textOption; +} + +QString ChatItem::text() const { + return _layout.text(); +} + +void ChatItem::setText(const UiStyle::StyledText &text) { + _layout.setText(text.text); + _layout.setAdditionalFormats(text.formats); + layout(); +} + +void ChatItem::layout() { + if(!_layout.additionalFormats().count()) return; // no text set + if(_width <= 0) return; + prepareGeometryChange(); + QFontMetrics metrics(_layout.additionalFormats()[0].format.font()); + int leading = metrics.leading(); + int height = 0; + _layout.setTextOption(textOption()); + _layout.beginLayout(); + while(1) { + QTextLine line = _layout.createLine(); + if(!line.isValid()) break; + line.setLineWidth(_width); + if(textOption().wrapMode() != QTextOption::NoWrap && line.naturalTextWidth() > _width) { + // word did not fit, we need to wrap it in the middle + // this is a workaround for Qt failing to handle WrapAtWordBoundaryOrAnywhere correctly + QTextOption::WrapMode mode = textOption().wrapMode(); + textOption().setWrapMode(QTextOption::WrapAnywhere); + _layout.setTextOption(textOption()); + line.setLineWidth(_width); + textOption().setWrapMode(mode); + _layout.setTextOption(textOption()); + } + height += leading; + line.setPosition(QPoint(0, height)); + height += line.height(); + } + _layout.endLayout(); + update(); +} + +QRectF ChatItem::boundingRect() const { + return _layout.boundingRect(); +} + +void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { + Q_UNUSED(option); Q_UNUSED(widget); + _layout.draw(painter, QPointF(0, 0)); + +} + +/* +void ChatItem::mouseMoveEvent ( QGraphicsSceneMouseEvent * event ) { + qDebug() << (void*)this << "moving" << event->pos(); + if(event->pos().y() < 0) { + QTextCursor cursor(document()); + //cursor.insertText("foo"); + //cursor.select(QTextCursor::Document); + event->ignore(); + } else QGraphicsTextItem::mouseMoveEvent(event); +} +*/ + + + diff --git a/src/qtui/chatitem.h b/src/qtui/chatitem.h new file mode 100644 index 00000000..4ffb9442 --- /dev/null +++ b/src/qtui/chatitem.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * Copyright (C) 2005-07 by the Quassel IRC 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 _CHATITEM_H_ +#define _CHATITEM_H_ + +#include +#include +#include + +#include "uistyle.h" + +class QGraphicsSceneMouseEvent; + +class ChatItem : public QGraphicsItem { + + public: + ChatItem(QGraphicsItem *parent = 0); + virtual ~ChatItem(); + + virtual QRectF boundingRect() const; + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + QString text() const; + void setText(const UiStyle::StyledText &text); + + QTextOption textOption() const; + void setTextOption(const QTextOption &option); + + void setWidth(int width); + virtual void layout(); + + protected: + //void mouseMoveEvent ( QGraphicsSceneMouseEvent * event ); + + private: + int _width; + QTextLayout _layout; + QTextOption _textOption; +}; + +#endif diff --git a/src/qtui/chatline-old.cpp b/src/qtui/chatline-old.cpp new file mode 100644 index 00000000..32d46560 --- /dev/null +++ b/src/qtui/chatline-old.cpp @@ -0,0 +1,373 @@ +/*************************************************************************** + * Copyright (C) 2005-07 by The Quassel IRC Development 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 "chatline-old.h" +#include "qtui.h" + +//!\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) { + hght = 0; + //networkName = m.buffer.network(); + //bufferName = m.buffer.buffer(); + msg = m; + selectionMode = None; + formatMsg(msg); +} + +ChatLine::~ChatLine() { + +} + +void ChatLine::formatMsg(Message msg) { + QTextOption tsOption, senderOption, textOption; + styledTimeStamp = QtUi::style()->styleString(msg.formattedTimestamp()); + styledSender = QtUi::style()->styleString(msg.formattedSender()); + styledText = QtUi::style()->styleString(msg.formattedText()); + precomputeLine(); +} + +// This function is almost obsolete, since with the new style engine, we already get a list of formats... +// We don't know yet if we keep this implementation of ChatLine, so I won't bother making this actually nice. +// Also, the additional format is ignored for now, which means that you won't see a selection... +// FIXME TODO +QList ChatLine::calcFormatRanges(const UiStyle::StyledText &fs, QTextLayout::FormatRange additional) { + QList ranges; + + foreach(QTextLayout::FormatRange f, fs.formats) { + FormatRange range; + range.start = f.start; + range.length = f.length; + range.format = f.format; + QFontMetrics metrics(range.format.font()); + range.height = metrics.lineSpacing(); + ranges.append(range); + } + /* + QList formats = fs.formats; + formats.append(additional); + int cur = -1; + FormatRange range, lastrange; + for(int i = 0; i < fs.text.length(); i++) { + QTextCharFormat format; + foreach(QTextLayout::FormatRange f, formats) { + if(i >= f.start && i < f.start + f.length) format.merge(f.format); + } + if(cur < 0) { + range.start = 0; range.length = 1; range.format= format; + cur = 0; + } else { + if(format == range.format) range.length++; + else { + QFontMetrics metrics(range.format.font()); + range.height = metrics.lineSpacing(); + ranges.append(range); + range.start = i; range.length = 1; range.format = format; + cur++; + } + } + } + if(cur >= 0) { + QFontMetrics metrics(range.format.font()); + range.height = metrics.lineSpacing(); + ranges.append(range); + } + */ + return ranges; +} + +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: + tsFormat = calcFormatRanges(styledTimeStamp); + senderFormat = calcFormatRanges(styledSender); + textFormat = calcFormatRanges(styledText); + 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); + textFormat = calcFormatRanges(styledText, textSel); + foreach(FormatRange fr, textFormat); + break; + case Full: + tsSel.format.setForeground(pal.brush(QPalette::HighlightedText)); + tsSel.format.setBackground(pal.brush(QPalette::Highlight)); + tsSel.start = 0; tsSel.length = styledTimeStamp.text.length(); + tsFormat = calcFormatRanges(styledTimeStamp, tsSel); + senderSel.format.setForeground(pal.brush(QPalette::HighlightedText)); + senderSel.format.setBackground(pal.brush(QPalette::Highlight)); + senderSel.start = 0; senderSel.length = styledSender.text.length(); + senderFormat = calcFormatRanges(styledSender, senderSel); + textSel.format.setForeground(pal.brush(QPalette::HighlightedText)); + textSel.format.setBackground(pal.brush(QPalette::Highlight)); + textSel.start = 0; textSel.length = styledText.text.length(); + textFormat = calcFormatRanges(styledText, textSel); + break; + } +} + +uint ChatLine::msgId() const { + return msg.buffer().uid(); +} + +BufferInfo ChatLine::bufferInfo() const { + return msg.buffer(); +} + +QDateTime ChatLine::timestamp() const { + return msg.timestamp(); +} + +QString ChatLine::sender() const { + return styledSender.text; +} + +QString ChatLine::text() const { + return styledText.text; +} + +bool ChatLine::isUrl(int c) const { + if(c < 0 || c >= charUrlIdx.count()) return false;; + return charUrlIdx[c] >= 0; +} + +QUrl ChatLine::getUrl(int c) const { + if(c < 0 || c >= charUrlIdx.count()) return QUrl(); + int i = charUrlIdx[c]; + if(i >= 0) return styledText.urls[i].url; + else return QUrl(); +} + +//!\brief Return the cursor position for the given coordinate pos. +/** + * \param pos The position relative to the ChatLine + * \return The cursor position, [or -3 for invalid,] or -2 for timestamp, or -1 for sender + */ +int ChatLine::posToCursor(QPointF pos) { + if(pos.x() < tsWidth + (int)QtUi::style()->sepTsSender()/2) return -2; + qreal textStart = tsWidth + QtUi::style()->sepTsSender() + senderWidth + QtUi::style()->sepSenderText(); + if(pos.x() < textStart) return -1; + int x = (int)(pos.x() - textStart); + for(int l = lineLayouts.count() - 1; l >=0; l--) { + LineLayout line = lineLayouts[l]; + if(pos.y() >= line.y) { + int offset = charPos[line.start]; x += offset; + for(int i = line.start + line.length - 1; i >= line.start; i--) { + if((charPos[i] + charPos[i+1])/2 <= x) return i+1; // FIXME: Optimize this! + } + return line.start; + } + } + return 0; +} + +void ChatLine::precomputeLine() { + tsFormat = calcFormatRanges(styledTimeStamp); + senderFormat = calcFormatRanges(styledSender); + textFormat = calcFormatRanges(styledText); + + minHeight = 0; + foreach(FormatRange fr, tsFormat) minHeight = qMax(minHeight, fr.height); + foreach(FormatRange fr, senderFormat) minHeight = qMax(minHeight, fr.height); + + words.clear(); + charPos.resize(styledText.text.length() + 1); + charHeights.resize(styledText.text.length()); + charUrlIdx.fill(-1, styledText.text.length()); + for(int i = 0; i < styledText.urls.count(); i++) { + QtUiStyle::UrlInfo url = styledText.urls[i]; + for(int j = url.start; j < url.end; j++) charUrlIdx[j] = i; + } + if(!textFormat.count()) return; + int idx = 0; int cnt = 0; int w = 0; + QFontMetrics metrics(textFormat[0].format.font()); + Word wr; + wr.start = -1; wr.trailing = -1; + for(int i = 0; i < styledText.text.length(); ) { + charPos[i] = w; charHeights[i] = textFormat[idx].height; + w += metrics.charWidth(styledText.text, i); + if(!styledText.text[i].isSpace()) { + if(wr.trailing >= 0) { + // new word after space + words.append(wr); + wr.start = -1; + } + if(wr.start < 0) { + wr.start = i; wr.length = 1; wr.trailing = -1; wr.height = textFormat[idx].height; + } else { + wr.length++; wr.height = qMax(wr.height, textFormat[idx].height); + } + } else { + if(wr.start < 0) { + wr.start = i; wr.length = 0; wr.trailing = 1; wr.height = 0; + } else { + wr.trailing++; + } + } + if(++i < styledText.text.length() && ++cnt >= textFormat[idx].length) { + cnt = 0; idx++; + Q_ASSERT(idx < textFormat.count()); + metrics = QFontMetrics(textFormat[idx].format.font()); + } + } + charPos[styledText.text.length()] = w; + if(wr.start >= 0) words.append(wr); +} + +qreal ChatLine::layout(qreal tsw, qreal senderw, qreal textw) { + tsWidth = tsw; senderWidth = senderw; textWidth = textw; + if(textw <= 0) return minHeight; + lineLayouts.clear(); LineLayout line; + int h = 0; + int offset = 0; int numWords = 0; + line.y = 0; + line.start = 0; + line.height = minHeight; // first line needs room for ts and sender + for(uint i = 0; i < (uint)words.count(); i++) { + int lastpos = charPos[words[i].start + words[i].length]; // We use charPos[lastchar + 1], 'coz last char needs to fit + if(lastpos - offset <= textw) { + line.height = qMax(line.height, words[i].height); + line.length = words[i].start + words[i].length - line.start; + numWords++; + } else { + // we need to wrap! + if(numWords > 0) { + // ok, we had some words before, so store the layout and start a new line + h += line.height; + line.length = words[i-1].start + words[i-1].length - line.start; + lineLayouts.append(line); + line.y += line.height; + line.start = words[i].start; + line.height = words[i].height; + offset = charPos[words[i].start]; + } + numWords = 1; + // check if the word fits into the current line + if(lastpos - offset <= textw) { + line.length = words[i].length; + } else { + // we need to break a word in the middle + int border = (int)textw + offset; // save some additions + line.start = words[i].start; + line.length = 1; + line.height = charHeights[line.start]; + int j = line.start + 1; + for(int l = 1; l < words[i].length; j++, l++) { + if(charPos[j+1] < border) { + line.length++; + line.height = qMax(line.height, charHeights[j]); + continue; + } else { + h += line.height; + lineLayouts.append(line); + line.y += line.height; + line.start = j; + line.height = charHeights[j]; + line.length = 1; + offset = charPos[j]; + border = (int)textw + offset; + } + } + } + } + } + h += line.height; + if(numWords > 0) { + lineLayouts.append(line); + } + hght = h; + return hght; +} + +//!\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 + QtUi::style()->sepTsSender() + senderWidth + QtUi::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); + */ + //p->setClipRect(QRectF(pos, QSizeF(tsWidth, 15))); + //p->drawRect(QRectF(pos, QSizeF(tsWidth, minHeight))); + p->setBackgroundMode(Qt::OpaqueMode); + QPointF tp = pos; + QRectF rect(pos, QSizeF(tsWidth, minHeight)); + QRectF brect; + foreach(FormatRange fr, tsFormat) { + p->setFont(fr.format.font()); + p->setPen(QPen(fr.format.foreground(), 0)); p->setBackground(fr.format.background()); + p->drawText(rect, Qt::AlignLeft|Qt::TextSingleLine, styledTimeStamp.text.mid(fr.start, fr.length), &brect); + rect.setLeft(brect.right()); + } + rect = QRectF(pos + QPointF(tsWidth + QtUi::style()->sepTsSender(), 0), QSizeF(senderWidth, minHeight)); + for(int i = senderFormat.count() - 1; i >= 0; i--) { + FormatRange fr = senderFormat[i]; + p->setFont(fr.format.font()); p->setPen(QPen(fr.format.foreground(), 0)); p->setBackground(fr.format.background()); + p->drawText(rect, Qt::AlignRight|Qt::TextSingleLine, styledSender.text.mid(fr.start, fr.length), &brect); + rect.setRight(brect.left()); + } + QPointF tpos = pos + QPointF(tsWidth + QtUi::style()->sepTsSender() + senderWidth + QtUi::style()->sepSenderText(), 0); + qreal h = 0; int l = 0; + rect = QRectF(tpos + QPointF(0, h), QSizeF(textWidth, lineLayouts[l].height)); + int offset = 0; + foreach(FormatRange fr, textFormat) { + if(l >= lineLayouts.count()) break; + p->setFont(fr.format.font()); p->setPen(QPen(fr.format.foreground(), 0)); p->setBackground(fr.format.background()); + int start, end, frend, llend; + do { + frend = fr.start + fr.length; + if(frend <= lineLayouts[l].start) break; + llend = lineLayouts[l].start + lineLayouts[l].length; + start = qMax(fr.start, lineLayouts[l].start); end = qMin(frend, llend); + rect.setLeft(tpos.x() + charPos[start] - offset); + p->drawText(rect, Qt::AlignLeft|Qt::TextSingleLine, styledText.text.mid(start, end - start), &brect); + if(llend <= end) { + h += lineLayouts[l].height; + l++; + if(l < lineLayouts.count()) { + rect = QRectF(tpos + QPointF(0, h), QSizeF(textWidth, lineLayouts[l].height)); + offset = charPos[lineLayouts[l].start]; + } + } + } while(end < frend && l < lineLayouts.count()); + } +} diff --git a/src/qtui/chatline-old.h b/src/qtui/chatline-old.h new file mode 100644 index 00000000..5b5bffc7 --- /dev/null +++ b/src/qtui/chatline-old.h @@ -0,0 +1,109 @@ +/*************************************************************************** + * Copyright (C) 2005-07 by The Quassel IRC Development 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 _CHATLINE_H_ +#define _CHATLINE_H_ + +#include + +#include "util.h" +#include "uistyle.h" +#include "quasselui.h" + +//FIXME: chatline doku +//!\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, public AbstractUiMsg { + Q_OBJECT + + public: + ChatLine(Message message); + virtual ~ChatLine(); + + qreal layout(qreal tsWidth, qreal nickWidth, qreal textWidth); + qreal height() const { 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 timestamp() const; + QString sender() const; + QString text() const; + MsgId msgId() const; + BufferInfo bufferInfo() const; + + bool isUrl(int pos) const; + QUrl getUrl(int pos) const; + + public slots: + + private: + qreal hght; + Message msg; + qreal tsWidth, senderWidth, textWidth; + UiStyle::StyledText styledTimeStamp, styledSender, styledText; + + struct FormatRange { + int start; + int length; + int height; + QTextCharFormat format; + }; + struct Word { + int start; + int length; + int trailing; + int height; + }; + struct LineLayout { + int y; + int height; + int start; + int length; + }; + QVector charPos; + QVector charWidths; + QVector charHeights; + QVector charUrlIdx; + QList tsFormat, senderFormat, textFormat; + QList words; + QList lineLayouts; + int minHeight; + + SelectionMode selectionMode; + int selectionStart, selectionEnd; + void formatMsg(Message); + void precomputeLine(); + QList calcFormatRanges(const UiStyle::StyledText &, QTextLayout::FormatRange additional = QTextLayout::FormatRange()); +}; + +#endif diff --git a/src/qtui/chatline.cpp b/src/qtui/chatline.cpp index f3b4a357..7121c210 100644 --- a/src/qtui/chatline.cpp +++ b/src/qtui/chatline.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-07 by The Quassel IRC Development Team * + * Copyright (C) 2005-07 by the Quassel IRC Team * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -18,356 +18,103 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ +#include +#include +#include + +#include "bufferinfo.h" +#include "chatitem.h" #include "chatline.h" #include "qtui.h" -//!\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) { - hght = 0; - //networkName = m.buffer.network(); - //bufferName = m.buffer.buffer(); - msg = m; - selectionMode = None; - formatMsg(msg); -} - -ChatLine::~ChatLine() { - -} - -void ChatLine::formatMsg(Message msg) { - QTextOption tsOption, senderOption, textOption; - styledTimeStamp = QtUi::style()->styleString(msg.formattedTimeStamp()); - styledSender = QtUi::style()->styleString(msg.formattedSender()); - styledText = QtUi::style()->styleString(msg.formattedText()); - precomputeLine(); -} - -// This function is almost obsolete, since with the new style engine, we already get a list of formats... -// We don't know yet if we keep this implementation of ChatLine, so I won't bother making this actually nice. -// Also, the additional format is ignored for now, which means that you won't see a selection... -// FIXME TODO -QList ChatLine::calcFormatRanges(const UiStyle::StyledString &fs, QTextLayout::FormatRange additional) { - QList ranges; +ChatLine::ChatLine(Message msg) : QGraphicsItem(), AbstractUiMsg() { + _styledTimestamp = QtUi::style()->styleString(msg.formattedTimestamp()); + _styledSender = QtUi::style()->styleString(msg.formattedSender()); + _styledText = QtUi::style()->styleString(msg.formattedText()); + _msgId = msg.msgId(); + _timestamp = msg.timestamp(); - foreach(QTextLayout::FormatRange f, fs.formats) { - FormatRange range; - range.start = f.start; - range.length = f.length; - range.format = f.format; - QFontMetrics metrics(range.format.font()); - range.height = metrics.lineSpacing(); - ranges.append(range); - } - /* - QList formats = fs.formats; - formats.append(additional); - int cur = -1; - FormatRange range, lastrange; - for(int i = 0; i < fs.text.length(); i++) { - QTextCharFormat format; - foreach(QTextLayout::FormatRange f, formats) { - if(i >= f.start && i < f.start + f.length) format.merge(f.format); - } - if(cur < 0) { - range.start = 0; range.length = 1; range.format= format; - cur = 0; - } else { - if(format == range.format) range.length++; - else { - QFontMetrics metrics(range.format.font()); - range.height = metrics.lineSpacing(); - ranges.append(range); - range.start = i; range.length = 1; range.format = format; - cur++; - } - } - } - if(cur >= 0) { - QFontMetrics metrics(range.format.font()); - range.height = metrics.lineSpacing(); - ranges.append(range); - } - */ - return ranges; -} + _tsColWidth = _senderColWidth = _textColWidth = 0; + QTextOption option; + option.setWrapMode(QTextOption::NoWrap); + _tsItem = new ChatItem(this); + _tsItem->setTextOption(option); + _tsItem->setText(_styledTimestamp); -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: - tsFormat = calcFormatRanges(styledTimeStamp); - senderFormat = calcFormatRanges(styledSender); - textFormat = calcFormatRanges(styledText); - 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); - textFormat = calcFormatRanges(styledText, textSel); - foreach(FormatRange fr, textFormat); - break; - case Full: - tsSel.format.setForeground(pal.brush(QPalette::HighlightedText)); - tsSel.format.setBackground(pal.brush(QPalette::Highlight)); - tsSel.start = 0; tsSel.length = styledTimeStamp.text.length(); - tsFormat = calcFormatRanges(styledTimeStamp, tsSel); - senderSel.format.setForeground(pal.brush(QPalette::HighlightedText)); - senderSel.format.setBackground(pal.brush(QPalette::Highlight)); - senderSel.start = 0; senderSel.length = styledSender.text.length(); - senderFormat = calcFormatRanges(styledSender, senderSel); - textSel.format.setForeground(pal.brush(QPalette::HighlightedText)); - textSel.format.setBackground(pal.brush(QPalette::Highlight)); - textSel.start = 0; textSel.length = styledText.text.length(); - textFormat = calcFormatRanges(styledText, textSel); - break; - } -} + option.setAlignment(Qt::AlignRight); + _senderItem = new ChatItem(this); + _senderItem->setTextOption(option); + _senderItem->setText(_styledSender); -uint ChatLine::msgId() const { - return msg.buffer().uid(); -} + option.setAlignment(Qt::AlignLeft); + option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + _textItem = new ChatItem(this); + _textItem->setTextOption(option); + _textItem->setText(_styledText); -BufferInfo ChatLine::bufferInfo() const { - return msg.buffer(); } -QDateTime ChatLine::timeStamp() const { - return msg.timeStamp(); +ChatLine::~ChatLine() { + } QString ChatLine::sender() const { - return styledSender.text; + return QString(); } QString ChatLine::text() const { - return styledText.text; + return QString(); } -bool ChatLine::isUrl(int c) const { - if(c < 0 || c >= charUrlIdx.count()) return false;; - return charUrlIdx[c] >= 0; +MsgId ChatLine::msgId() const { + return 0; } -QUrl ChatLine::getUrl(int c) const { - if(c < 0 || c >= charUrlIdx.count()) return QUrl(); - int i = charUrlIdx[c]; - if(i >= 0) return styledText.urls[i].url; - else return QUrl(); +BufferInfo ChatLine::bufferInfo() const { + Q_ASSERT(false); // do we actually need this function??? + return BufferInfo(); } -//!\brief Return the cursor position for the given coordinate pos. -/** - * \param pos The position relative to the ChatLine - * \return The cursor position, [or -3 for invalid,] or -2 for timestamp, or -1 for sender - */ -int ChatLine::posToCursor(QPointF pos) { - if(pos.x() < tsWidth + (int)QtUi::style()->sepTsSender()/2) return -2; - qreal textStart = tsWidth + QtUi::style()->sepTsSender() + senderWidth + QtUi::style()->sepSenderText(); - if(pos.x() < textStart) return -1; - int x = (int)(pos.x() - textStart); - for(int l = lineLayouts.count() - 1; l >=0; l--) { - LineLayout line = lineLayouts[l]; - if(pos.y() >= line.y) { - int offset = charPos[line.start]; x += offset; - for(int i = line.start + line.length - 1; i >= line.start; i--) { - if((charPos[i] + charPos[i+1])/2 <= x) return i+1; // FIXME: Optimize this! - } - return line.start; - } - } - return 0; +QDateTime ChatLine::timestamp() const { + return QDateTime(); } -void ChatLine::precomputeLine() { - tsFormat = calcFormatRanges(styledTimeStamp); - senderFormat = calcFormatRanges(styledSender); - textFormat = calcFormatRanges(styledText); +QRectF ChatLine::boundingRect () const { + return childrenBoundingRect(); +} - minHeight = 0; - foreach(FormatRange fr, tsFormat) minHeight = qMax(minHeight, fr.height); - foreach(FormatRange fr, senderFormat) minHeight = qMax(minHeight, fr.height); +void ChatLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { - words.clear(); - charPos.resize(styledText.text.length() + 1); - charHeights.resize(styledText.text.length()); - charUrlIdx.fill(-1, styledText.text.length()); - for(int i = 0; i < styledText.urls.count(); i++) { - QtUiStyle::UrlInfo url = styledText.urls[i]; - for(int j = url.start; j < url.end; j++) charUrlIdx[j] = i; - } - if(!textFormat.count()) return; - int idx = 0; int cnt = 0; int w = 0; - QFontMetrics metrics(textFormat[0].format.font()); - Word wr; - wr.start = -1; wr.trailing = -1; - for(int i = 0; i < styledText.text.length(); ) { - charPos[i] = w; charHeights[i] = textFormat[idx].height; - w += metrics.charWidth(styledText.text, i); - if(!styledText.text[i].isSpace()) { - if(wr.trailing >= 0) { - // new word after space - words.append(wr); - wr.start = -1; - } - if(wr.start < 0) { - wr.start = i; wr.length = 1; wr.trailing = -1; wr.height = textFormat[idx].height; - } else { - wr.length++; wr.height = qMax(wr.height, textFormat[idx].height); - } - } else { - if(wr.start < 0) { - wr.start = i; wr.length = 0; wr.trailing = 1; wr.height = 0; - } else { - wr.trailing++; - } - } - if(++i < styledText.text.length() && ++cnt >= textFormat[idx].length) { - cnt = 0; idx++; - Q_ASSERT(idx < textFormat.count()); - metrics = QFontMetrics(textFormat[idx].format.font()); - } - } - charPos[styledText.text.length()] = w; - if(wr.start >= 0) words.append(wr); } -qreal ChatLine::layout(qreal tsw, qreal senderw, qreal textw) { - tsWidth = tsw; senderWidth = senderw; textWidth = textw; - if(textw <= 0) return minHeight; - lineLayouts.clear(); LineLayout line; - int h = 0; - int offset = 0; int numWords = 0; - line.y = 0; - line.start = 0; - line.height = minHeight; // first line needs room for ts and sender - for(uint i = 0; i < (uint)words.count(); i++) { - int lastpos = charPos[words[i].start + words[i].length]; // We use charPos[lastchar + 1], 'coz last char needs to fit - if(lastpos - offset <= textw) { - line.height = qMax(line.height, words[i].height); - line.length = words[i].start + words[i].length - line.start; - numWords++; - } else { - // we need to wrap! - if(numWords > 0) { - // ok, we had some words before, so store the layout and start a new line - h += line.height; - line.length = words[i-1].start + words[i-1].length - line.start; - lineLayouts.append(line); - line.y += line.height; - line.start = words[i].start; - line.height = words[i].height; - offset = charPos[words[i].start]; - } - numWords = 1; - // check if the word fits into the current line - if(lastpos - offset <= textw) { - line.length = words[i].length; - } else { - // we need to break a word in the middle - int border = (int)textw + offset; // save some additions - line.start = words[i].start; - line.length = 1; - line.height = charHeights[line.start]; - int j = line.start + 1; - for(int l = 1; l < words[i].length; j++, l++) { - if(charPos[j+1] < border) { - line.length++; - line.height = qMax(line.height, charHeights[j]); - continue; - } else { - h += line.height; - lineLayouts.append(line); - line.y += line.height; - line.start = j; - line.height = charHeights[j]; - line.length = 1; - offset = charPos[j]; - border = (int)textw + offset; - } - } - } - } +void ChatLine::setColumnWidths(int tsColWidth, int senderColWidth, int textColWidth) { + if(tsColWidth >= 0) { + _tsColWidth = tsColWidth; + _tsItem->setWidth(tsColWidth); + } + if(senderColWidth >= 0) { + _senderColWidth = senderColWidth; + _senderItem->setWidth(senderColWidth); } - h += line.height; - if(numWords > 0) { - lineLayouts.append(line); + if(textColWidth >= 0) { + _textColWidth = textColWidth; + _textItem->setWidth(textColWidth); } - hght = h; - return hght; + layout(); } -//!\brief Draw ChatLine on the given QPainter at the given position. -void ChatLine::draw(QPainter *p, const QPointF &pos) { - QPalette pal = QApplication::palette(); +void ChatLine::layout() { + prepareGeometryChange(); + _tsItem->setPos(QPointF(0, 0)); + _senderItem->setPos(QPointF(_tsColWidth + QtUi::style()->sepTsSender(), 0)); + _textItem->setPos(QPointF(_tsColWidth + QtUi::style()->sepTsSender() + _senderColWidth + QtUi::style()->sepSenderText(), 0)); +} - if(selectionMode == Full) { - p->setPen(Qt::NoPen); - p->setBrush(pal.brush(QPalette::Highlight)); - p->drawRect(QRectF(pos, QSizeF(tsWidth + QtUi::style()->sepTsSender() + senderWidth + QtUi::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); - */ - //p->setClipRect(QRectF(pos, QSizeF(tsWidth, 15))); - //p->drawRect(QRectF(pos, QSizeF(tsWidth, minHeight))); - p->setBackgroundMode(Qt::OpaqueMode); - QPointF tp = pos; - QRectF rect(pos, QSizeF(tsWidth, minHeight)); - QRectF brect; - foreach(FormatRange fr, tsFormat) { - p->setFont(fr.format.font()); - p->setPen(QPen(fr.format.foreground(), 0)); p->setBackground(fr.format.background()); - p->drawText(rect, Qt::AlignLeft|Qt::TextSingleLine, styledTimeStamp.text.mid(fr.start, fr.length), &brect); - rect.setLeft(brect.right()); - } - rect = QRectF(pos + QPointF(tsWidth + QtUi::style()->sepTsSender(), 0), QSizeF(senderWidth, minHeight)); - for(int i = senderFormat.count() - 1; i >= 0; i--) { - FormatRange fr = senderFormat[i]; - p->setFont(fr.format.font()); p->setPen(QPen(fr.format.foreground(), 0)); p->setBackground(fr.format.background()); - p->drawText(rect, Qt::AlignRight|Qt::TextSingleLine, styledSender.text.mid(fr.start, fr.length), &brect); - rect.setRight(brect.left()); - } - QPointF tpos = pos + QPointF(tsWidth + QtUi::style()->sepTsSender() + senderWidth + QtUi::style()->sepSenderText(), 0); - qreal h = 0; int l = 0; - rect = QRectF(tpos + QPointF(0, h), QSizeF(textWidth, lineLayouts[l].height)); - int offset = 0; - foreach(FormatRange fr, textFormat) { - if(l >= lineLayouts.count()) break; - p->setFont(fr.format.font()); p->setPen(QPen(fr.format.foreground(), 0)); p->setBackground(fr.format.background()); - int start, end, frend, llend; - do { - frend = fr.start + fr.length; - if(frend <= lineLayouts[l].start) break; - llend = lineLayouts[l].start + lineLayouts[l].length; - start = qMax(fr.start, lineLayouts[l].start); end = qMin(frend, llend); - rect.setLeft(tpos.x() + charPos[start] - offset); - p->drawText(rect, Qt::AlignLeft|Qt::TextSingleLine, styledText.text.mid(start, end - start), &brect); - if(llend <= end) { - h += lineLayouts[l].height; - l++; - if(l < lineLayouts.count()) { - rect = QRectF(tpos + QPointF(0, h), QSizeF(textWidth, lineLayouts[l].height)); - offset = charPos[lineLayouts[l].start]; - } - } - } while(end < frend && l < lineLayouts.count()); - } +bool ChatLine::sceneEvent ( QEvent * event ) { + qDebug() <<(void*)this<< "receiving event"; + event->ignore(); + return false; } + + diff --git a/src/qtui/chatline.h b/src/qtui/chatline.h index be84f24e..cd3e4ae4 100644 --- a/src/qtui/chatline.h +++ b/src/qtui/chatline.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-07 by The Quassel IRC Development Team * + * Copyright (C) 2005-07 by the Quassel IRC Team * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -21,89 +21,43 @@ #ifndef _CHATLINE_H_ #define _CHATLINE_H_ -#include +#include -#include "util.h" -#include "uistyle.h" +#include "message.h" #include "quasselui.h" +#include "uistyle.h" + +class ChatItem; -//FIXME: chatline doku -//!\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, public AbstractUiMsg { - Q_OBJECT +class ChatLine : public QGraphicsItem, public AbstractUiMsg { public: - ChatLine(Message message); + ChatLine(Message); virtual ~ChatLine(); + virtual QString sender() const; + virtual QString text() const; + virtual MsgId msgId() const; + virtual BufferInfo bufferInfo() const; + virtual QDateTime timestamp() const; - qreal layout(qreal tsWidth, qreal nickWidth, qreal textWidth); - qreal height() const { return hght; } - int posToCursor(QPointF pos); - void draw(QPainter *p, const QPointF &pos); + virtual QRectF boundingRect () const; + virtual void paint (QPainter * painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + void layout(); - enum SelectionMode { None, Partial, Full }; - void setSelection(SelectionMode, int start = 0, int end = 0); + void setColumnWidths(int tsColWidth, int senderColWidth, int textColWidth); - QDateTime timeStamp() const; - QString sender() const; - QString text() const; - MsgId msgId() const; - BufferInfo bufferInfo() const; + void myMousePressEvent ( QGraphicsSceneMouseEvent * event ) { qDebug() << "press"; mousePressEvent(event); } - bool isUrl(int pos) const; - QUrl getUrl(int pos) const; - - public slots: + protected: + bool sceneEvent ( QEvent * event ); private: - qreal hght; - Message msg; - qreal tsWidth, senderWidth, textWidth; - UiStyle::StyledString styledTimeStamp, styledSender, styledText; - - struct FormatRange { - int start; - int length; - int height; - QTextCharFormat format; - }; - struct Word { - int start; - int length; - int trailing; - int height; - }; - struct LineLayout { - int y; - int height; - int start; - int length; - }; - QVector charPos; - QVector charWidths; - QVector charHeights; - QVector charUrlIdx; - QList tsFormat, senderFormat, textFormat; - QList words; - QList lineLayouts; - int minHeight; + UiStyle::StyledText _styledTimestamp, _styledText, _styledSender; + QDateTime _timestamp; + MsgId _msgId; - SelectionMode selectionMode; - int selectionStart, selectionEnd; - void formatMsg(Message); - void precomputeLine(); - QList calcFormatRanges(const UiStyle::StyledString &, QTextLayout::FormatRange additional = QTextLayout::FormatRange()); + ChatItem *_tsItem, *_senderItem, *_textItem; + int _tsColWidth, _senderColWidth, _textColWidth; }; #endif diff --git a/src/qtui/chatscene.cpp b/src/qtui/chatscene.cpp new file mode 100644 index 00000000..89145403 --- /dev/null +++ b/src/qtui/chatscene.cpp @@ -0,0 +1,69 @@ +/*************************************************************************** + * Copyright (C) 2005-07 by the Quassel IRC 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 + +#include "buffer.h" +#include "chatitem.h" +#include "chatline.h" +#include "chatscene.h" +#include "quasselui.h" + +ChatScene::ChatScene(Buffer *buf, QObject *parent) : QGraphicsScene(parent) { + _buffer = buf; + + foreach(AbstractUiMsg *msg, buf->contents()) { + appendMsg(msg); + } + connect(buf, SIGNAL(msgAppended(AbstractUiMsg *)), this, SLOT(appendMsg(AbstractUiMsg *))); + connect(buf, SIGNAL(msgPrepended(AbstractUiMsg *)), this, SLOT(prependMsg(AbstractUiMsg *))); +} + +ChatScene::~ChatScene() { + + +} + +void ChatScene::appendMsg(AbstractUiMsg * msg) { + ChatLine *line = dynamic_cast(msg); + Q_ASSERT(line); + _lines.append(line); + addItem(line); + line->setPos(0, _lines.count() * 30); + line->setColumnWidths(80, 80, 400); +} + +void ChatScene::prependMsg(AbstractUiMsg * msg) { + ChatLine *line = dynamic_cast(msg); + Q_ASSERT(line); qDebug() << "prepending"; + _lines.prepend(line); + addItem(line); + line->setPos(0, _lines.count() * 30); +} + +void ChatScene::mousePressEvent ( QGraphicsSceneMouseEvent * mouseEvent ) { + qDebug() << "recv" << mouseEvent->scenePos(); + ChatLine *line = static_cast(itemAt(mouseEvent->scenePos())); + ChatItem *item = static_cast(itemAt(mouseEvent->scenePos())); + qDebug() << (void*)line << (void*)item; + if(line) { + line->myMousePressEvent(mouseEvent); + } else QGraphicsScene::mousePressEvent(mouseEvent); +} diff --git a/src/qtui/chatscene.h b/src/qtui/chatscene.h new file mode 100644 index 00000000..0963afac --- /dev/null +++ b/src/qtui/chatscene.h @@ -0,0 +1,54 @@ +/*************************************************************************** + * Copyright (C) 2005-07 by the Quassel IRC 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 _CHATSCENE_H_ +#define _CHATSCENE_H_ + +#include + +class AbstractUiMsg; +class Buffer; +class ChatLine; +class QGraphicsSceneMouseEvent; + +class ChatScene : public QGraphicsScene { + Q_OBJECT + + public: + ChatScene(Buffer *buffer, QObject *parent); + virtual ~ChatScene(); + + Buffer *buffer() const; + + public slots: + + protected slots: + void appendMsg(AbstractUiMsg *msg); + void prependMsg(AbstractUiMsg *msg); + + void mousePressEvent ( QGraphicsSceneMouseEvent * mouseEvent ); + + private: + Buffer *_buffer; + QList _lines; + +}; + +#endif diff --git a/src/qtui/chatview.cpp b/src/qtui/chatview.cpp new file mode 100644 index 00000000..ab555071 --- /dev/null +++ b/src/qtui/chatview.cpp @@ -0,0 +1,87 @@ +/*************************************************************************** + * Copyright (C) 2005-07 by the Quassel IRC 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 + +#include "buffer.h" +#include "chatline.h" +#include "chatscene.h" +#include "chatview.h" +#include "quasselui.h" + +ChatView::ChatView(Buffer *buf, QWidget *parent) : QGraphicsView(parent) { + _scene = new ChatScene(buf, this); + setScene(_scene); + + QGraphicsTextItem *item = scene()->addText(buf->name()); + +} + + +ChatView::~ChatView() { + +} + + +ChatScene *ChatView::scene() const { + return _scene; +} + +/* +void ChatView::clear() +{ +} + +void ChatView::prependMsg(AbstractUiMsg *msg) { + ChatLine *line = dynamic_cast(msg); + Q_ASSERT(line); + prependChatLine(line); +} + +void ChatView::prependChatLine(ChatLine *line) { + qDebug() << "prepending"; +} + +void ChatView::prependChatLines(QList clist) { + +} + +void ChatView::appendMsg(AbstractUiMsg *msg) { + ChatLine *line = dynamic_cast(msg); + Q_ASSERT(line); + appendChatLine(line); +} + +void ChatView::appendChatLine(ChatLine *line) { + qDebug() << "appending"; +} + + +void ChatView::appendChatLines(QList list) { + foreach(ChatLine *line, list) { + + } +} + +void ChatView::setContents(QList list) { + qDebug() << "setting" << list.count(); + appendChatLines(list); +} +*/ diff --git a/src/qtui/chatview.h b/src/qtui/chatview.h new file mode 100644 index 00000000..b2341f39 --- /dev/null +++ b/src/qtui/chatview.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * Copyright (C) 2005-07 by the Quassel IRC 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 _CHATVIEW_H_ +#define _CHATVIEW_H_ + +#include + +class AbstractUiMsg; +class Buffer; +class ChatLine; +class ChatScene; + +class ChatView : public QGraphicsView { + Q_OBJECT + + public: + ChatView(Buffer *, QWidget *parent = 0); + ~ChatView(); + + ChatScene *scene() const; + + public slots: +/* + void clear(); + + void prependMsg(AbstractUiMsg *); + void appendMsg(AbstractUiMsg *); + + void prependChatLine(ChatLine *); + void appendChatLine(ChatLine *); + void prependChatLines(QList); + void appendChatLines(QList); + + void setContents(QList); +*/ + private: + ChatScene *_scene; +}; + +#endif diff --git a/src/qtui/chatwidget.cpp b/src/qtui/chatwidget.cpp index 68d63da3..004ca93d 100644 --- a/src/qtui/chatwidget.cpp +++ b/src/qtui/chatwidget.cpp @@ -20,7 +20,7 @@ #include "util.h" #include "chatwidget.h" -#include "chatline.h" +#include "chatline-old.h" #include "qtui.h" @@ -534,7 +534,7 @@ QString ChatWidget::selectionToString() { if(selectionMode == LinesSelected) { QString result; for(int l = selectionStart; l <= selectionEnd; l++) { - result += QString("[%1] %2 %3\n").arg(lines[l]->timeStamp().toLocalTime().toString("hh:mm:ss")) + result += QString("[%1] %2 %3\n").arg(lines[l]->timestamp().toLocalTime().toString("hh:mm:ss")) .arg(lines[l]->sender()).arg(lines[l]->text()); } return result; diff --git a/src/qtui/mainwin.cpp b/src/qtui/mainwin.cpp index 6f62adf7..4bd2c96f 100644 --- a/src/qtui/mainwin.cpp +++ b/src/qtui/mainwin.cpp @@ -21,7 +21,7 @@ #include "mainwin.h" #include "bufferview.h" -#include "chatline.h" +#include "chatline-old.h" #include "client.h" #include "coreconnectdlg.h" #include "serverlist.h" diff --git a/src/qtui/qtui.pri b/src/qtui/qtui.pri index 714a8c0c..73cfc07b 100644 --- a/src/qtui/qtui.pri +++ b/src/qtui/qtui.pri @@ -1,12 +1,12 @@ -DEPMOD = uisupport common client contrib/qxt +DEPMOD = uisupport common client QT_MOD = core gui network -SRCS += bufferview.cpp bufferviewfilter.cpp bufferwidget.cpp channelwidgetinput.cpp chatline.cpp \ - chatwidget.cpp coreconnectdlg.cpp \ - guisettings.cpp identities.cpp mainwin.cpp qtui.cpp qtuistyle.cpp serverlist.cpp settingsdlg.cpp tabcompleter.cpp topicwidget.cpp +SRCS += bufferview.cpp bufferviewfilter.cpp bufferwidget.cpp channelwidgetinput.cpp chatline-old.cpp \ + chatwidget.cpp coreconnectdlg.cpp \ + guisettings.cpp identities.cpp mainwin.cpp qtui.cpp qtuistyle.cpp serverlist.cpp settingsdlg.cpp tabcompleter.cpp topicwidget.cpp -HDRS += bufferview.h bufferviewfilter.h bufferwidget.h channelwidgetinput.h chatline.h chatwidget.h coreconnectdlg.h \ - guisettings.h identities.h mainwin.h qtui.h qtuistyle.h serverlist.h settingsdlg.h settingspage.h tabcompleter.h topicwidget.h +HDRS += bufferview.h bufferviewfilter.h bufferwidget.h channelwidgetinput.h chatline-old.h chatwidget.h \ + coreconnectdlg.h guisettings.h identities.h mainwin.h qtui.h qtuistyle.h serverlist.h settingsdlg.h settingspage.h tabcompleter.h topicwidget.h FORMNAMES = identitiesdlg.ui identitieseditdlg.ui networkeditdlg.ui mainwin.ui nickeditdlg.ui serverlistdlg.ui \ diff --git a/src/qtui/ui/bufferwidget.ui b/src/qtui/ui/bufferwidget.ui index 68c8950b..f62031e0 100644 --- a/src/qtui/ui/bufferwidget.ui +++ b/src/qtui/ui/bufferwidget.ui @@ -105,9 +105,9 @@ <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Trebuchet MS'; font-size:14pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:11pt;"><span style=" font-size:89pt;">Yarrrrr!</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:49pt;"><span style=" font-size:22pt;">Avast, ye scurvy dogs!</span></p></body></html> +</style></head><body style=" font-family:'Trebuchet MS'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:11pt;"><span style=" font-size:89pt; color:#483d8b;">Quassel IRC</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:49pt;"><span style=" font-size:22pt;">Think less. Chat more.</span></p></body></html> true diff --git a/src/uisupport/uistyle.cpp b/src/uisupport/uistyle.cpp index 3fc53bd7..e14208b4 100644 --- a/src/uisupport/uistyle.cpp +++ b/src/uisupport/uistyle.cpp @@ -104,8 +104,8 @@ QString UiStyle::formatCode(FormatType ftype) const { return _formatCodes.key(ftype); } -UiStyle::StyledString UiStyle::styleString(QString s) { - StyledString result; +UiStyle::StyledText UiStyle::styleString(QString s) { + StyledText result; QList fmtList; fmtList.append(None); QTextLayout::FormatRange curFmtRng; diff --git a/src/uisupport/uistyle.h b/src/uisupport/uistyle.h index 0ddc1b81..f09cdbc4 100644 --- a/src/uisupport/uistyle.h +++ b/src/uisupport/uistyle.h @@ -51,13 +51,13 @@ class UiStyle { QUrl url; }; - struct StyledString { + struct StyledText { QString text; QList formats; QList urls; }; - StyledString styleString(QString); + StyledText styleString(QString); void setFormat(FormatType, QTextCharFormat); QTextCharFormat format(FormatType) const; diff --git a/templates/cpp b/templates/cpp index 9151cb79..82c47e0f 100644 --- a/templates/cpp +++ b/templates/cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-07 by The Quassel IRC Development Team * + * Copyright (C) 2005-07 by the Quassel IRC 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 2a25675a..0beb1981 100644 --- a/templates/h +++ b/templates/h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-07 by The Quassel IRC Development Team * + * Copyright (C) 2005-07 by the Quassel IRC Team * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * -- 2.20.1