Browse Source

Add images support for simple chat example

Alexey Edelev 5 years ago
parent
commit
1ec605fc80

+ 100 - 13
examples/simplechat/ChatView.qml

@@ -1,7 +1,9 @@
 import QtQuick 2.4
 import examples.simplechat 1.0
+import qtprotobuf.examples 1.0
 
 Rectangle {
+    id: root
     anchors.fill: parent
     color: "#303030"
     onVisibleChanged: {
@@ -20,27 +22,95 @@ Rectangle {
         anchors.right: parent.right
         model: scEngine.messages
         delegate: Item {
-            height: childrenRect.height
-            width: _inputField.width
-            Text {
-                id: _userName
-                font.pointSize: 12
-                font.weight: Font.Bold
-                color: "#ffffff"
-                text: model.modelData.from + ": "
+            height: _imageWrapper.height + 10
+            width: root.width
+            Item {
+                id: _imageWrapper
+                height: _messageColumn.height + 20
+                width: parent.width/2 - 20
+                property bool ownMessage: scEngine.userName === model.modelData.from
+                anchors{
+                    right: _imageWrapper.ownMessage ? parent.right : undefined
+                    left: _imageWrapper.ownMessage ? undefined : parent.left
+                    rightMargin: _imageWrapper.ownMessage ? 10 : 0
+                    leftMargin: _imageWrapper.ownMessage ? 0 : 10
+                    verticalCenter: parent.verticalCenter
+                }
+
+                Rectangle {
+                    anchors.fill: parent
+                    radius: 20
+                    color: "#424242"
+                    border.color: _imageWrapper.ownMessage ? "#E91E63" : "#F48FB1"
+                    border.width: 1
+                }
+
+                Column {
+                    id: _messageColumn
+                    anchors {
+                        left: parent.left
+                        right: parent.right
+                        leftMargin: 10
+                        rightMargin: 10
+                        verticalCenter: parent.verticalCenter
+                    }
+                    Text {
+                        id: _userName
+                        property string from: _imageWrapper.ownMessage ? qsTr("You") : model.modelData.from
+                        anchors.left: parent.left
+                        anchors.right: parent.right
+                        font.pointSize: 12
+                        font.weight: Font.Bold
+                        color: "#ffffff"
+                        text: _userName.from + ": "
+                    }
+
+                    Loader {
+                        id: delegateLoader
+                        anchors.left: parent.left
+                        anchors.right: parent.right
+                        height: item ? item.height : 0
+                        sourceComponent:  model.modelData.type === ChatMessage.Image ? imageDelegate : textDelegate
+                        onItemChanged:  {
+                            if (item) {
+                                item.modelData = model.modelData
+                            }
+                        }
+                    }
+                }
             }
+        }
+        onCountChanged: {
+            positionViewAtEnd()
+        }
+    }
 
+    Component {
+        id: textDelegate
+        Item {
+            property var modelData: null
+            height: childrenRect.height
             Text {
-                anchors.left: _userName.right
+                anchors.left: parent.left
                 anchors.right: parent.right
+                height: implicitHeight
                 font.pointSize: 12
                 color: "#ffffff"
                 wrapMode: Text.Wrap
-                text: scEngine.getText(model.modelData.content)
+                text: scEngine.getText(modelData.content)
             }
         }
-        onCountChanged: {
-            positionViewAtEnd()
+    }
+    Component {
+        id: imageDelegate
+        Item {
+            property var modelData: null
+            height: childrenRect.height
+            Image {
+                width: implicitWidth
+                height: implicitHeight
+                source: scEngine.getImageThumbnail(modelData.content)
+            }
         }
     }
 
@@ -53,10 +123,27 @@ Rectangle {
             bottom: parent.bottom
             margins: 20
         }
-        placeholderText: qsTr("Start type here")
+
+        placeholderText: qsTr("Start type here or paste image")
         onAccepted: {
             scEngine.sendMessage(_inputField.text)
             _inputField.text = ""
         }
+
+        Keys.onPressed: {
+            if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
+                console.log("Ctrl + V")
+                switch (scEngine.clipBoardContentType) {
+                case ChatMessage.Text:
+                    paste()
+                    break
+                case ChatMessage.Image:
+                    scEngine.sendImageFromClipboard()
+                    break
+                }
+
+                event.accepted = true
+            }
+        }
     }
 }

+ 1 - 0
examples/simplechat/main.cpp

@@ -46,6 +46,7 @@ int main(int argc, char *argv[])
     QGuiApplication app(argc, argv);
 
     QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+
     qtprotobuf::QtProtobuf::init();
     ChatMessages::registerTypes();
     ChatMessage::registerTypes();

+ 13 - 0
examples/simplechat/main.qml

@@ -42,7 +42,9 @@ ApplicationWindow {
     }
 
     Column {
+        id: _loginControl
         spacing: 5
+        visible: true
         anchors.centerIn: parent
         ChatInputField {
             id: _loginField
@@ -51,6 +53,16 @@ ApplicationWindow {
             onAccepted:             {
                 scEngine.login(_loginField.text, _passwordField.text)
             }
+            onVisibleChanged: {
+                if(visible) {
+                    _loginField.forceActiveFocus()
+                }
+            }
+            Component.onCompleted: {
+                if(visible) {
+                    _loginField.forceActiveFocus()
+                }
+            }
         }
         ChatInputField {
             id: _passwordField
@@ -102,6 +114,7 @@ ApplicationWindow {
             target: scEngine
             onLoggedIn: {
                 _chatView.visible = true
+                _loginControl.visible = false
             }
         }
     }

+ 84 - 4
examples/simplechat/simplechatengine.cpp

@@ -34,6 +34,12 @@
 #include <QSslConfiguration>
 #include <QCryptographicHash>
 #include <QDateTime>
+#include <QClipboard>
+#include <QGuiApplication>
+#include <QMimeData>
+#include <QImage>
+#include <QByteArray>
+#include <QBuffer>
 
 using namespace qtprotobuf::examples;
 
@@ -42,12 +48,15 @@ class AuthCredentials : public qtprotobuf::CallCredentials
 public:
     AuthCredentials(const QString &userName, const QString &password) :
         CallCredentials(CredentialMap{{QLatin1String("user-name"), QVariant::fromValue(userName)},
-                                      {QLatin1String("user-password"), QVariant::fromValue(password)}}) {}
+    {QLatin1String("user-password"), QVariant::fromValue(password)}}) {}
 };
 
 SimpleChatEngine::SimpleChatEngine(QObject *parent) : QObject(parent), m_client(new SimpleChatClient)
+  , m_clipBoard(QGuiApplication::clipboard())
 {
-
+    if(m_clipBoard) {
+        connect(m_clipBoard, &QClipboard::dataChanged, this, &SimpleChatEngine::clipBoardContentTypeChanged);
+    }
 }
 
 SimpleChatEngine::~SimpleChatEngine()
@@ -70,8 +79,12 @@ void SimpleChatEngine::login(const QString &name, const QString &password)
 
     m_client->attachChannel(channel);
     m_client->subscribeMessageListUpdates(None());
-    QObject::connect(m_client, &SimpleChatClient::messageListUpdated, this, [this](const qtprotobuf::examples::ChatMessages &messages){
-        loggedIn();
+    QObject::connect(m_client, &SimpleChatClient::messageListUpdated, this, [this, name](const qtprotobuf::examples::ChatMessages &messages){
+        if (m_userName != name) {
+            m_userName = name;
+            userNameChanged();
+            loggedIn();
+        }
         m_messages.reset(messages.messages());
     });
 }
@@ -80,3 +93,70 @@ void SimpleChatEngine::sendMessage(const QString &content)
 {
     m_client->sendMessage(ChatMessage(QDateTime::currentMSecsSinceEpoch(), content.toUtf8(), ChatMessage::ContentType::Text));
 }
+
+qtprotobuf::examples::ChatMessage::ContentType SimpleChatEngine::clipBoardContentType() const
+{
+    if(m_clipBoard != nullptr) {
+        const QMimeData *mime = m_clipBoard->mimeData();
+        if (mime != nullptr) {
+            if (mime->hasImage() || mime->hasUrls()) {
+                return qtprotobuf::examples::ChatMessage::ContentType::Image;
+            } else if(mime->hasText()) {
+                return qtprotobuf::examples::ChatMessage::ContentType::Text;
+            }
+        }
+    }
+    return qtprotobuf::examples::ChatMessage::Unknown;
+}
+
+void SimpleChatEngine::sendImageFromClipboard()
+{
+    if(m_clipBoard == nullptr) {
+        return;
+    }
+
+    QByteArray imgData;
+    const QMimeData *mime = m_clipBoard->mimeData();
+    if (mime != nullptr) {
+        if(mime->hasImage()) {
+            QImage img = mime->imageData().value<QImage>();
+            QBuffer buffer(&imgData);
+            buffer.open(QIODevice::WriteOnly);
+            img.save(&buffer, "PNG");
+            buffer.close();
+        } else if(mime->hasUrls()) {
+            QUrl imgUrl = mime->urls().first();
+            if (!imgUrl.isLocalFile()) {
+                qWarning() << "Only supports transfer of local images";
+                return;
+            }
+            QImage img(imgUrl.toLocalFile());
+            if(img.isNull()) {
+                qWarning() << "Invalid image format";
+                return;
+            }
+
+            QBuffer buffer(&imgData);
+            buffer.open(QIODevice::WriteOnly);
+            img.save(&buffer, "PNG");
+            buffer.close();
+        }
+    }
+
+    if(imgData.isEmpty()) {
+        return;
+    }
+
+    m_client->sendMessage(ChatMessage(QDateTime::currentMSecsSinceEpoch(), imgData, qtprotobuf::examples::ChatMessage::ContentType::Image));
+}
+
+QString SimpleChatEngine::getImageThumbnail(const QByteArray &data) const
+{
+    QImage img = QImage::fromData(data, "PNG");
+    img = img.scaled(200, 200, Qt::KeepAspectRatio);
+    QByteArray scaledData;
+    QBuffer buffer(&scaledData);
+    img.save(&buffer, "PNG");
+    return getImage(scaledData);
+}
+

+ 23 - 1
examples/simplechat/simplechatengine.h

@@ -26,11 +26,13 @@
 #pragma once
 
 #include <QObject>
+
 #include <chatmessages.h>
 #include "simplechatclient.h"
 
 #include "universallistmodel.h"
 
+class QClipboard;
 
 namespace qtprotobuf {
 namespace examples {
@@ -40,25 +42,45 @@ using ChatMessageModel = UniversalListModel<qtprotobuf::examples::ChatMessage>;
 class SimpleChatEngine : public QObject
 {
     Q_OBJECT
+    Q_PROPERTY(QString userName READ userName NOTIFY userNameChanged)
     Q_PROPERTY(qtprotobuf::examples::ChatMessageModel *messages READ messages CONSTANT)
+    Q_PROPERTY(qtprotobuf::examples::ChatMessage::ContentType clipBoardContentType READ clipBoardContentType NOTIFY clipBoardContentTypeChanged)
 
     ChatMessageModel m_messages;
     SimpleChatClient *m_client;
+    QClipboard *m_clipBoard;
+    QString m_userName;
+
 public:
     explicit SimpleChatEngine(QObject *parent = nullptr);
     ~SimpleChatEngine();
     Q_INVOKABLE void login(const QString &name, const QString &password);
     Q_INVOKABLE void sendMessage(const QString &message);
 
-    Q_INVOKABLE QString getText(const QByteArray &data) { return QString::fromUtf8(data); }
+    Q_INVOKABLE QString getText(const QByteArray &data) const { return QString::fromUtf8(data); }
+    Q_INVOKABLE QString getImage(const QByteArray &data) const { return QString("data:image/png;base64,") + data.toBase64(); }
+    Q_INVOKABLE QString getImageThumbnail(const QByteArray &data) const;
+    Q_INVOKABLE void sendImageFromClipboard();
 
     ChatMessageModel *messages()
     {
         return &m_messages;
     }
+
+
+    qtprotobuf::examples::ChatMessage::ContentType clipBoardContentType() const;
+
+    QString userName() const
+    {
+        return m_userName;
+    }
+
 Q_SIGNALS:
     void loggedIn();
     void authFailed();
+    void clipBoardContentTypeChanged();
+    void userNameChanged();
+
 };
 
 }