While playing around with various approaches to how to implement ChatView in QML, I think slowly
things are shaping up. We now have extended the QmlMessageModel to provide all info needed for rendering
a QmlChatLine in one data stracture, in order to avoid numerous calls to the model whenever a ChatLine
is instantiated.
Currently there's a lot of code duplication with the related non-QML classes, but this will either go away
once we decide to kill the QGV-based ChatView, or be unified into a common set of base classes if we
find out that both implementations are going to stay around.
import QtQuick 1.1
// import Qt.components 1.0
-import eu.quassel.qmlui 1.0
+import eu.quassel.qml 1.0
Rectangle {
- id: container
+ id: container
- ListView {
- id: chatView
- anchors.fill: parent
- model: msgModel
+ ListView {
+ id: chatView
- delegate: Component {
- ChatLine {
- chatLineData: chatLineDataRole
- }
- }
+ property int timestampWidth: 50
+ property int senderWidth: 80
+ property int contentsWidth: width - timestampWidth - senderWidth - 30;
+ property int columnSpacing: 10
- //interactive: false
- boundsBehavior: Flickable.StopAtBounds
+ anchors.fill: parent
+ model: msgModel
- property int timestampWidth: 50
- property int senderWidth: 80
- property int contentsWidth: width - timestampWidth - senderWidth - 30;
+ delegate: ChatLine {
+ id: chatLineDelegate
+
+ timestampWidth: chatView.timestampWidth
+ senderWidth: chatView.senderWidth
+ contentsWidth: chatView.contentsWidth
+ columnSpacing: chatView.columnSpacing
+ model: chatView.model
+ renderData: renderDataRole
- Connections {
- target: msgModel
- onRowsInserted: chatView.positionViewAtEnd();
- }
/*
- MouseArea {
- id: mouseArea
- anchors.fill: parent
- acceptedButtons: Qt.LeftButton
-
- onClicked: {
- console.log("clicked")
- parent.senderWidth = parent.senderWidth + 10
- }
- onPositionChanged: {
- console.log("changed" + mouseX + mouseY)
- }
+ MouseArea {
+ id: itemMouseArea
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton
+ hoverEnabled: true
- }
+ //onClicked: {
+ // console.log("clicked " + mouseX + " " + mouseY + " " + parent.text)
+ // parent.onClicked(mouseX, mouseY)
+ //}
+
+ //onPositionChanged: {
+ // console.log("changed " + mouseX + " " + mouseY + " " + parent.text)
+ //}
+
+ }
+*/
+/*
+ Connections {
+ target: itemMouseArea
+ onClicked: onClicked(itemMouseArea.mouseX, itemMouseArea.mouseY)
+ onPressed: onPressed(itemMouseArea.mouseX, itemMouseArea.mouseY)
+ onPositionChanged: onMousePositionChanged(itemMouseArea.mouseX, itemMouseArea.mouseY)
+ }
*/
+ }
+
+ interactive: true
+ boundsBehavior: Flickable.StopAtBounds
+
+ Connections {
+ target: msgModel
+ onRowsInserted: chatView.positionViewAtEnd();
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton
+ //hoverEnabled: true
+
+ onClicked: {
+ //parent.senderWidth = parent.senderWidth + 10
+ //chatView.currentItem.senderWidth = 40;
+ //var pos = mapToItem(chatView, mouseX, mouseY)
+ //console.log(pos.x + " " + pos.y + " " + chatView.contentY)
+ console.log("clicked " + mouseX + " " + mouseY + " " + chatView.indexAt(mouseX, mouseY + chatView.contentY))
+ console.log("item " + chatView.model.get(17).text)
+ }
+ onPositionChanged: {
+ console.log("changed " + mouseX + " " + mouseY)
+ }
+
+ }
+
- Rectangle {
- id: scrollbar
- anchors.right: chatView.right
- y: chatView.visibleArea.yPosition * chatView.height
- width: 10
- height: chatView.visibleArea.heightRatio * chatView.height
- color: "grey"
+ Rectangle {
+ id: scrollbar
+ anchors.right: chatView.right
+ y: chatView.visibleArea.yPosition * chatView.height
+ width: 10
+ height: chatView.visibleArea.heightRatio * chatView.height
+ color: "grey"
+ }
}
- }
}
#include "qmlchatline.h"
void QmlChatLine::registerTypes() {
- qRegisterMetaType<Data>("QmlChatLine::Data");
- qRegisterMetaTypeStreamOperators<Data>("QmlChatLine::Data");
- qmlRegisterType<QmlChatLine>("eu.quassel.qmlui", 1, 0, "ChatLine");
+ qRegisterMetaType<RenderData>("QmlChatLine::RenderData");
+ qRegisterMetaTypeStreamOperators<RenderData>("QmlChatLine::RenderData");
+ qmlRegisterType<QmlChatLine>("eu.quassel.qml", 1, 0, "ChatLine");
}
-QDataStream &operator<<(QDataStream &out, const QmlChatLine::Data &data) {
- out << data.timestamp.text << data.timestamp.formats
- << data.sender.text << data.sender.formats
- << data.contents.text << data.contents.formats;
+QDataStream &operator<<(QDataStream &out, const QmlChatLine::RenderData &data) {
+ for(int i = 0; i < (int)QmlChatLine::NumColumns; ++i) {
+ const QmlChatLine::RenderData::Column &col = data[static_cast<QmlChatLine::ColumnType>(i)];
+ out << col.text << col.formats;
+ }
return out;
}
-QDataStream &operator>>(QDataStream &in, QmlChatLine::Data &data) {
- in >> data.timestamp.text >> data.timestamp.formats
- >> data.sender.text >> data.sender.formats
- >> data.contents.text >> data.contents.formats;
+QDataStream &operator>>(QDataStream &in, QmlChatLine::RenderData &data) {
+ for(int i = 0; i < (int)QmlChatLine::NumColumns; ++i) {
+ QmlChatLine::RenderData::Column &col = data[static_cast<QmlChatLine::ColumnType>(i)];
+ in >> col.text >> col.formats;
+ }
return in;
}
-QmlChatLine::QmlChatLine(QDeclarativeItem *parent) : QDeclarativeItem(parent) {
+QmlChatLine::QmlChatLine(QDeclarativeItem *parent)
+ : QDeclarativeItem(parent),
+ _timestampWidth(0),
+ _senderWidth(0),
+ _contentsWidth(0),
+ _layout(0)
+{
setFlag(ItemHasNoContents, false);
setImplicitHeight(20);
- setImplicitWidth(100);
+ setImplicitWidth(1000);
+ connect(this, SIGNAL(columnWidthChanged(ColumnType)), SLOT(onColumnWidthChanged(ColumnType)));
}
QmlChatLine::~QmlChatLine() {
}
+void QmlChatLine::setTimestampWidth(qreal w) {
+ if(w != _timestampWidth) {
+ _timestampWidth = w;
+ emit timestampWidthChanged(w);
+ emit columnWidthChanged(TimestampColumn);
+ }
+}
+
+void QmlChatLine::setSenderWidth(qreal w) {
+ if(w != _senderWidth) {
+ _senderWidth = w;
+ emit senderWidthChanged(w);
+ emit columnWidthChanged(SenderColumn);
+ }
+}
+
+void QmlChatLine::setContentsWidth(qreal w) {
+ if(w != _contentsWidth) {
+ _contentsWidth = w;
+ emit contentsWidthChanged(w);
+ emit columnWidthChanged(ContentsColumn);
+ }
+}
+
+void QmlChatLine::setColumnSpacing(qreal s) {
+ if(s != _columnSpacing) {
+ _columnSpacing = s;
+ emit columnSpacingChanged(s);
+ }
+}
+
+QPointF QmlChatLine::columnPos(ColumnType colType) const {
+ switch(colType) {
+ case TimestampColumn:
+ return QPointF(0, 0);
+ case SenderColumn:
+ return QPointF(timestampWidth(), 0);
+ case ContentsColumn:
+ return QPointF(timestampWidth() + senderWidth(), 0);
+ default:
+ return QPointF();
+ }
+}
+
+qreal QmlChatLine::columnWidth(ColumnType colType) const {
+ switch(colType) {
+ case TimestampColumn:
+ return timestampWidth();
+ case SenderColumn:
+ return senderWidth();
+ case ContentsColumn:
+ return contentsWidth();
+ default:
+ return 0;
+ }
+}
+
+QRectF QmlChatLine::columnBoundingRect(ColumnType colType) const {
+ QRectF rect;
+ switch(colType) {
+ case TimestampColumn:
+ return QRectF(columnPos(TimestampColumn), QSizeF(timestampWidth() - columnSpacing(), implicitHeight()));
+ case SenderColumn:
+ return QRectF(columnPos(SenderColumn), QSizeF(senderWidth() - columnSpacing(), implicitHeight()));
+ case ContentsColumn:
+ return QRectF(columnPos(ContentsColumn), QSizeF(contentsWidth(), implicitHeight()));
+ default:
+ return QRectF();
+ }
+}
+
+void QmlChatLine::setRenderData(const RenderData &data) {
+ _data = data;
+ if(_layout) {
+ delete _layout;
+ _layout = 0;
+ }
+
+ //update();
+}
+
+QmlChatLine::ColumnLayout *QmlChatLine::layout() const {
+ if(!_layout) {
+ _layout = new ColumnLayout(this);
+ }
+ return _layout;
+}
+
void QmlChatLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
- painter->drawText(0, 0, data().contents.text);
+ Q_UNUSED(option)
+ Q_UNUSED(widget)
+ //painter->drawText(0, 0, renderData()[TimestampColumn].text);
+ //painter->drawText(timestampWidth() + columnSpacing(), 0, renderData()[SenderColumn].text);
+ //painter->drawText(timestampWidth() + senderWidth() + 2*columnSpacing(), 0, renderData()[ContentsColumn].text);
+ layout()->draw(painter);
+}
+
+void QmlChatLine::onColumnWidthChanged(ColumnType colType) {
+
+ //qDebug() << "changed width" << _timestampWidth << _senderWidth << _contentsWidth;
+ //setImplicitHeight(implicitHeight() + 5);
+
+ if(colType == ContentsColumn) {
+ layout()->prepare();
+ setImplicitHeight(layout()->height());
+ }
+
+ update();
+}
+/**************************************************************************************/
+
+QmlChatLine::ColumnLayout::ColumnLayout(const QmlChatLine *parent)
+ : _parent(parent)
+{
+
+}
+
+qreal QmlChatLine::ColumnLayout::height() const {
+ return chatLine()->contentsWidth()/20;
}
+void QmlChatLine::ColumnLayout::prepare() {
+}
+
+void QmlChatLine::ColumnLayout::draw(QPainter *p) {
+ p->drawText(chatLine()->boundingRect(), chatLine()->renderData()[ContentsColumn].text);
+ //p->drawText(chatLine()->timestampWidth() + chatLine()->columnSpacing(), 0, chatLine()->renderData()[SenderColumn].text);
+ //p->drawText(chatLine()->timestampWidth() + chatLine()->senderWidth() + 2*chatLine()->columnSpacing(), 0, chatLine()->renderData()[ContentsColumn].text);
+
+}
#include "uistyle.h"
+class QAbstractItemModel;
+
+#include <QAbstractItemModel>
+
class QmlChatLine : public QDeclarativeItem {
Q_OBJECT
- Q_PROPERTY(QVariant chatLineData READ chatLineData WRITE setChatLineData)
-
- //Q_PROPERTY(QVariant )
+ Q_PROPERTY(QObject *model READ modelPointer WRITE setModelPointer)
+ Q_PROPERTY(QmlChatLine::RenderData renderData READ renderData WRITE setRenderData)
+ Q_PROPERTY(qreal timestampWidth READ timestampWidth WRITE setTimestampWidth NOTIFY timestampWidthChanged)
+ Q_PROPERTY(qreal senderWidth READ senderWidth WRITE setSenderWidth NOTIFY senderWidthChanged)
+ Q_PROPERTY(qreal contentsWidth READ contentsWidth WRITE setContentsWidth NOTIFY contentsWidthChanged)
+ Q_PROPERTY(qreal columnSpacing READ columnSpacing WRITE setColumnSpacing NOTIFY columnSpacingChanged)
+ Q_PROPERTY(QVariant test READ test WRITE setTest)
public:
- //! Contains all data needed to render a QmlChatLine
- struct Data {
- struct ChatLineColumnData {
+ enum ColumnType {
+ TimestampColumn,
+ SenderColumn,
+ ContentsColumn,
+ NumColumns
+ };
+
+ //! Contains all model data needed to render a QmlChatLine
+ struct RenderData {
+ struct Column {
QString text;
UiStyle::FormatList formats;
+ QBrush background;
+ QBrush selectedBackground;
};
- ChatLineColumnData timestamp;
- ChatLineColumnData sender;
- ChatLineColumnData contents;
+ qint32 messageLabel;
+
+ Column &operator[](ColumnType col) {
+ return _data[col];
+ }
+
+ Column const &operator[](ColumnType col) const {
+ return _data[col];
+ }
+
+ RenderData() { messageLabel = 0; }
+
+ private:
+ Column _data[NumColumns];
};
+ class ColumnLayout;
+
QmlChatLine(QDeclarativeItem *parent = 0);
virtual ~QmlChatLine();
- inline Data data() const { return _data; }
- inline QVariant chatLineData() const { return QVariant::fromValue<Data>(_data); }
- inline void setChatLineData(const QVariant &data) { _data = data.value<Data>(); }
+ inline QAbstractItemModel *model() const { return _model; }
+ inline QObject *modelPointer() const { return _model; }
+ void setModelPointer(QObject *model) { _model = qobject_cast<QAbstractItemModel *>(model); }
+
+ inline RenderData renderData() const { return _data; }
+ void setRenderData(const RenderData &data);
+
+ ColumnLayout *layout() const;
+
+ inline qreal timestampWidth() const { return _timestampWidth; }
+ void setTimestampWidth(qreal w);
+ inline qreal senderWidth() const { return _senderWidth; }
+ void setSenderWidth(qreal w);
+ inline qreal contentsWidth() const { return _contentsWidth; }
+ void setContentsWidth(qreal w);
+ inline qreal columnSpacing() const { return _columnSpacing; }
+ void setColumnSpacing(qreal s);
+
+ inline QString text() const { return renderData()[ContentsColumn].text; }
+
+ void setTest(const QVariant &test) { _test = test; qDebug() << "set test" << test; }
+ QVariant test() const { return _test; }
+
+ QPointF columnPos(ColumnType colType) const;
+ qreal columnWidth(ColumnType colType) const;
+ QRectF columnBoundingRect(ColumnType colType) const;
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
static void registerTypes();
+signals:
+ void timestampWidthChanged(qreal);
+ void senderWidthChanged(qreal);
+ void contentsWidthChanged(qreal);
+ void columnWidthChanged(ColumnType column);
+ void columnSpacingChanged(qreal);
+
+public slots:
+ void onClicked(qreal mouseX, qreal mouseY) { qDebug() << "clicked" << mouseX << mouseY; }
+ void onPressed(qreal mouseX, qreal mouseY) { qDebug() << "pressed" << mouseX << mouseY; }
+ void onMousePositionChanged(qreal mouseX, qreal mouseY) { qDebug() << "moved" << mouseX << mouseY; }
+
+protected:
+
+protected slots:
+ void onColumnWidthChanged(ColumnType column);
+
private:
- Data _data;
+ QAbstractItemModel *_model;
+ RenderData _data;
+
+ qreal _timestampWidth, _senderWidth, _contentsWidth;
+ qreal _columnSpacing;
+
+ QVariant _test;
+
+ mutable ColumnLayout *_layout;
};
-QDataStream &operator<<(QDataStream &out, const QmlChatLine::Data &data);
-QDataStream &operator>>(QDataStream &in, QmlChatLine::Data &data);
+QDataStream &operator<<(QDataStream &out, const QmlChatLine::RenderData &data);
+QDataStream &operator>>(QDataStream &in, QmlChatLine::RenderData &data);
-Q_DECLARE_METATYPE(QmlChatLine::Data)
+Q_DECLARE_METATYPE(QmlChatLine::RenderData)
+
+class QmlChatLine::ColumnLayout {
+public:
+ explicit ColumnLayout(const QmlChatLine *parent);
+ virtual ~ColumnLayout() {}
+
+ inline const QmlChatLine *chatLine() const { return _parent; }
+
+ qreal height() const;
+ virtual void prepare();
+ virtual void draw(QPainter *p);
+
+private:
+ const QmlChatLine *_parent;
+};
#endif
QmlChatLine::registerTypes();
QHash<int, QByteArray> roles;
- roles[ChatLineDataRole] = "chatLineDataRole";
+ roles[RenderDataRole] = "renderDataRole";
setRoleNames(roles);
}
public:
enum QmlMessageModelRole {
- ChatLineDataRole = MessageModel::UserRole,
+ RenderDataRole = MessageModel::UserRole,
+ MsgLabelRole,
+ SelectedBackgroundRole,
UserRole
};
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
+#include "graphicalui.h"
#include "qmlchatline.h"
#include "qmlmessagemodel.h"
#include "qmlmessagemodelitem.h"
_styledMsg.setFlags(msg.flags() |= Message::ServerMsg);
}
+bool QmlMessageModelItem::setData(int column, const QVariant &value, int role) {
+ switch(role) {
+ case MessageModel::FlagsRole:
+ _styledMsg.setFlags((Message::Flags)value.toUInt());
+ return true;
+ default:
+ return MessageModelItem::setData(column, value, role);
+ }
+}
+
QVariant QmlMessageModelItem::data(int column, int role) const {
QVariant variant;
switch(role) {
- case QmlMessageModel::ChatLineDataRole: {
- QmlChatLine::Data data;
- data.timestamp.text = _styledMsg.decoratedTimestamp();
- data.timestamp.formats = UiStyle::FormatList() << qMakePair((quint16)0, (quint32)UiStyle::formatType(_styledMsg.type()) | UiStyle::Timestamp);
- data.sender.text = _styledMsg.decoratedSender();
- data.sender.formats = UiStyle::FormatList() << qMakePair((quint16)0, (quint32)UiStyle::formatType(_styledMsg.type()) | UiStyle::Sender);
- data.contents.text = _styledMsg.plainContents();
- data.contents.formats = _styledMsg.contentsFormatList();
- return QVariant::fromValue<QmlChatLine::Data>(data);
+ case QmlMessageModel::MsgLabelRole:
+ return messageLabel();
+
+ case QmlMessageModel::RenderDataRole: {
+ QmlChatLine::RenderData data;
+ data[QmlChatLine::TimestampColumn].text = _styledMsg.decoratedTimestamp();
+ data[QmlChatLine::TimestampColumn].formats = UiStyle::FormatList() << qMakePair((quint16)0, (quint32)UiStyle::formatType(_styledMsg.type()) | UiStyle::Timestamp);
+ data[QmlChatLine::TimestampColumn].background = backgroundBrush(UiStyle::Timestamp).value<QBrush>();
+ data[QmlChatLine::TimestampColumn].selectedBackground = backgroundBrush(UiStyle::Timestamp, true).value<QBrush>();
+
+ data[QmlChatLine::SenderColumn].text = _styledMsg.decoratedSender();
+ data[QmlChatLine::SenderColumn].formats = UiStyle::FormatList() << qMakePair((quint16)0, (quint32)UiStyle::formatType(_styledMsg.type()) | UiStyle::Sender);
+ data[QmlChatLine::SenderColumn].background = backgroundBrush(UiStyle::Sender).value<QBrush>();
+ data[QmlChatLine::SenderColumn].selectedBackground = backgroundBrush(UiStyle::Sender, true).value<QBrush>();
+
+ data[QmlChatLine::ContentsColumn].text = _styledMsg.plainContents();
+ data[QmlChatLine::ContentsColumn].formats = _styledMsg.contentsFormatList();
+ data[QmlChatLine::ContentsColumn].background = backgroundBrush(UiStyle::Contents).value<QBrush>();
+ data[QmlChatLine::ContentsColumn].selectedBackground = backgroundBrush(UiStyle::Contents, true).value<QBrush>();
+
+ return QVariant::fromValue<QmlChatLine::RenderData>(data);
}
+
default:
break;
}
+
+ MessageModel::ColumnType col = (MessageModel::ColumnType)column;
+ switch(col) {
+ case QmlMessageModel::TimestampColumn:
+ variant = timestampData(role);
+ break;
+ case QmlMessageModel::SenderColumn:
+ variant = senderData(role);
+ break;
+ case QmlMessageModel::ContentsColumn:
+ variant = contentsData(role);
+ break;
+ default:
+ break;
+ }
+
if(!variant.isValid())
return MessageModelItem::data(column, role);
return variant;
}
+
+QVariant QmlMessageModelItem::timestampData(int role) const {
+ switch(role) {
+ case QmlMessageModel::DisplayRole:
+ return _styledMsg.decoratedTimestamp();
+ case QmlMessageModel::EditRole:
+ return _styledMsg.timestamp();
+ case QmlMessageModel::BackgroundRole:
+ return backgroundBrush(UiStyle::Timestamp);
+ case QmlMessageModel::SelectedBackgroundRole:
+ return backgroundBrush(UiStyle::Timestamp, true);
+ case QmlMessageModel::FormatRole:
+ return QVariant::fromValue<UiStyle::FormatList>(UiStyle::FormatList()
+ << qMakePair((quint16)0, (quint32)UiStyle::formatType(_styledMsg.type()) | UiStyle::Timestamp));
+ }
+ return QVariant();
+}
+
+QVariant QmlMessageModelItem::senderData(int role) const {
+ switch(role) {
+ case QmlMessageModel::DisplayRole:
+ return _styledMsg.decoratedSender();
+ case QmlMessageModel::EditRole:
+ return _styledMsg.plainSender();
+ case QmlMessageModel::BackgroundRole:
+ return backgroundBrush(UiStyle::Sender);
+ case QmlMessageModel::SelectedBackgroundRole:
+ return backgroundBrush(UiStyle::Sender, true);
+ case QmlMessageModel::FormatRole:
+ return QVariant::fromValue<UiStyle::FormatList>(UiStyle::FormatList()
+ << qMakePair((quint16)0, (quint32)UiStyle::formatType(_styledMsg.type()) | UiStyle::Sender));
+ }
+ return QVariant();
+}
+
+QVariant QmlMessageModelItem::contentsData(int role) const {
+ switch(role) {
+ case QmlMessageModel::DisplayRole:
+ case QmlMessageModel::EditRole:
+ return _styledMsg.plainContents();
+ case QmlMessageModel::BackgroundRole:
+ return backgroundBrush(UiStyle::Contents);
+ case QmlMessageModel::SelectedBackgroundRole:
+ return backgroundBrush(UiStyle::Contents, true);
+ case QmlMessageModel::FormatRole:
+ return QVariant::fromValue<UiStyle::FormatList>(_styledMsg.contentsFormatList());
+ }
+ return QVariant();
+}
+
+quint32 QmlMessageModelItem::messageLabel() const {
+ quint32 label = _styledMsg.senderHash() << 16;
+ if(_styledMsg.flags() & Message::Self)
+ label |= UiStyle::OwnMsg;
+ if(_styledMsg.flags() & Message::Highlight)
+ label |= UiStyle::Highlight;
+ return label;
+}
+
+QVariant QmlMessageModelItem::backgroundBrush(UiStyle::FormatType subelement, bool selected) const {
+ QTextCharFormat fmt = GraphicalUi::uiStyle()->format(UiStyle::formatType(_styledMsg.type()) | subelement, messageLabel() | (selected ? UiStyle::Selected : 0));
+ if(fmt.hasProperty(QTextFormat::BackgroundBrush))
+ return QVariant::fromValue<QBrush>(fmt.background());
+ return QVariant();
+}
QmlMessageModelItem(const Message &msg);
virtual QVariant data(int column, int role) const;
+ virtual bool setData(int column, const QVariant &value, int role);
virtual inline const Message &message() const { return _styledMsg; }
virtual inline const QDateTime ×tamp() const { return _styledMsg.timestamp(); }
virtual inline Message::Flags msgFlags() const { return _styledMsg.flags(); }
private:
+ QVariant timestampData(int role) const;
+ QVariant senderData(int role) const;
+ QVariant contentsData(int role) const;
+
+ QVariant backgroundBrush(UiStyle::FormatType subelement, bool selected = false) const;
+ quint32 messageLabel() const;
+
UiStyle::StyledMessage _styledMsg;
};