diff --git a/Desktop.qml b/Desktop.qml index 8f3eb023..f2aa83f3 100644 --- a/Desktop.qml +++ b/Desktop.qml @@ -1,333 +1,329 @@ // Skeleton from https://github.com/achipa/outqross_blog.git // Almost everything has been re-adapted import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.2 import QtQuick.Window 2.2 import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.1 import Qt.labs.settings 1.0 import QtGraphicalEffects 1.0 import KDE.Ruqola.Ruqola 1.0 import KDE.Ruqola.DDPClient 1.0 import KDE.Ruqola.Notification 1.0 // import "Log.js" as Log // import "Data.js" as Data ApplicationWindow { property int margin: 11 property string statusText property string lightGreen: "#6ab141"; property string darkGreen: "#00613a"; property string selectedRoomID: ""; id: appid title: qsTr("Ruqola") width: 800 height: 600 visible: true Shortcut { sequence: StandardKey.Quit context: Qt.ApplicationShortcut onActivated: Qt.quit() } Login { id: loginTab visible: (Ruqola.loginStatus == DDPClient.LoginFailed || Ruqola.loginStatus == DDPClient.LoggedOut) // visible: (Ruqola.loginStatus != DDPClient.LoggedIn) anchors.fill:parent z: 10 serverURL: Ruqola.serverURL username: Ruqola.userName onAccepted: { Ruqola.password = loginTab.password; Ruqola.userName = loginTab.username; Ruqola.serverURL = loginTab.serverURL; Ruqola.tryLogin(); } } BusyIndicator { id: busy anchors.centerIn: parent visible: Ruqola.loginStatus == DDPClient.LoggingIn } Item { id: mainWidget anchors.fill: parent visible: !loginTab.visible Rectangle { id: userBox anchors.top: parent.top width: parent.width anchors.left: parent.left anchors.right: roomsList.right height: 40 color: darkGreen Text { verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignRight anchors.rightMargin: 10 anchors.fill: parent font.pointSize: 12 color: "white" text: "Hello, " + Ruqola.userName } } RoomsView { anchors.top: userBox.bottom anchors.left: parent.left anchors.bottom: parent.bottom anchors.margins: 0 width: 200 height: appid.height id: roomsList model: Ruqola.roomModel() - - visible: parent.visible - + visible: parent.visible selectedRoomID: appid.selectedRoomID; - onRoomSelected: { if (roomID == selectedRoomID) { return; } - - console.log("Choosing room", roomID); appid.selectedRoomID = roomID; activeChat.model = Ruqola.getModelForRoom(roomID) topicWidget.selectedRoom = Ruqola.getRoom(roomID) } onCountChanged: { // console.log("We have", roomsList.count, "rooms") } LinearGradient { id: greenGradient anchors.fill: parent start: Qt.point(0, 0) end: Qt.point(roomsList.width, 0) gradient: Gradient { GradientStop { position: 0.0; color: "#6ab141" } GradientStop { position: 1.0; color: "#00613a" } } z: -1; } } //RoomsView Item { + anchors.right: parent.right anchors.left: roomsList.right anchors.top: parent.top - anchors.bottom: messageLine.top + anchors.bottom: input.top id: chatView Rectangle { id: topicWidget color: "#fff" + anchors.top: parent.top anchors.right: parent.right anchors.left: parent.left height: nameLabel.height + topicLabel.height property var selectedRoom; Text { id: nameLabel text: "#" + parent.selectedRoom.name font.pointSize: 18 verticalAlignment: Text.AlignVCenter anchors.leftMargin: 20 height: 40 // height: font.pixelSize + 10 anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right } Text { id: topicLabel text: topicWidget.selectedRoom.topic anchors.top: nameLabel.bottom anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right horizontalAlignment: Text.AlignHCenter height: font.pixelSize + 10 } } ScrollView { anchors.right: parent.right anchors.left: parent.left anchors.top: topicWidget.bottom - anchors.bottom: input.top + anchors.bottom: parent.bottom verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn // visible: parent.visible && (Ruqola.loginStatus != DDPClient.LoggingIn) // visible: !greeter.visible ListView { id: activeChat // model: Ruqola.getModelForRoom(selectedRoomID) onCountChanged: { // console.log("changed") // var newIndex = count - 1 // last index // positionViewAtEnd() positionViewAtIndex(count - 1, ListView.Beginning) // currentIndex = newIndex } // Component.onCompleted: positionViewAtEnd() Component.onCompleted: positionViewAtIndex(count - 1, ListView.Beginning) // onSelectedRoomIDChanged: { console.log("CHANGED"); activeChat.positionViewAtEnd(); } // model: myModel anchors.fill:parent visible : count > 0 z: -1 // ScrollBar.vertical: ScrollBar { } delegate: Message { i_messageText: messageText i_username: username i_systemMessage: systemMessage i_systemMessageType: type - width: parent.width + //width: parent.width } } } } //Item chatView - Item{ + Item { anchors.bottom: parent.bottom - anchors.top: activeChat.bottom anchors.left: roomsList.right anchors.right: parent.right id: input height: 40 TextField { id: messageLine anchors.left: parent.left anchors.bottom: parent.bottom anchors.top: parent.top anchors.right: attachmentsButton.left placeholderText: if (Ruqola.loginStatus != DDPClient.LoggedIn || (selectedRoomID=="")){ qsTr("Please Select a room") } else{ qsTr("Enter message") } - height: 2.7*font.pixelSize + +// height: 2.7*font.pixelSize onAccepted: { if (text != "" && Ruqola.loginStatus == DDPClient.LoggedIn && !(selectedRoomID=="")) { Ruqola.sendMessage(selectedRoomID, text); text = ""; } } } - Button { - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.top: parent.top - width: 50 - id : attachmentsButton - iconName: "Button" - text: "Click" - height: 2.7*font.pixelSize - visible: true - onClicked: Ruqola.attachmentButtonClicked(); - } + Button { + anchors.bottom: parent.bottom + anchors.top: parent.top + anchors.right: parent.right + width: 50 + id : attachmentsButton + iconName: "Button" + text: "Click" + visible: true + onClicked: Ruqola.attachmentButtonClicked(); + } + }//Item input }// mainWidget Item Rectangle { z: -10 anchors.fill: parent color: "white" } onClosing: { console.log("Minimizing to systray..."); hide(); - systrayIcon.windowVisible = visible; } function toggleShow() { if (visible) { hide(); } else { show(); raise(); requestActivate(); } } Component.onCompleted: { systrayIcon.activated.connect(toggleShow); systrayIcon.messageClicked.connect(toggleShow); // roomsList.model = Ruqola.roomModel(); // timer.start(); // timer.fire(); } /* Timer { id: timer interval: 1000 onTriggered: { // console.log("FIRE"); switch (Ruqola.loginStatus) { case Ruqola.NotConnected: statusText = qsTr("Not connected."); break; case Ruqola.LoggedIn: statusText = qsTr("Connected to " + Ruqola.serverURL); break; } } repeat: true }*/ // onStatusTextChanged: timer.restart(); } diff --git a/src/ddpclient.cpp b/src/ddpclient.cpp index 76632339..331e95a1 100644 --- a/src/ddpclient.cpp +++ b/src/ddpclient.cpp @@ -1,305 +1,323 @@ /* * * Copyright 2016 Riccardo Iaconelli * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #include "ddpclient.h" #include #include #include #include #include "ruqola.h" void process_test(QJsonDocument doc) { qDebug() << "Callback test:" << doc; qDebug() << "End callback"; } void login_callback(QJsonDocument doc) { qDebug() << "LOGIN:" << doc; Ruqola::self()->setAuthToken(doc.object().value("token").toString()); qDebug() << "End callback"; } void DDPClient::resume_login_callback(QJsonDocument doc) { qDebug() << "LOGIN:" << doc; Ruqola::self()->setAuthToken(doc.object().value("token").toString()); qDebug() << "End callback"; } void empty_callback(QJsonDocument doc) { Q_UNUSED(doc); } DDPClient::DDPClient(const QString& url, QObject* parent) : QObject(parent), m_url(url), m_uid(1), m_loginJob(0), m_loginStatus(NotConnected), m_connected(false), m_attemptedPasswordLogin(false), m_attemptedTokenLogin(false) { m_webSocket.ignoreSslErrors(); connect(&m_webSocket, &QWebSocket::connected, this, &DDPClient::onWSConnected); connect(&m_webSocket, &QWebSocket::textMessageReceived, this, &DDPClient::onTextMessageReceived); connect(&m_webSocket, &QWebSocket::disconnected, this, &DDPClient::WSclosed); connect(Ruqola::self(), &Ruqola::serverURLChanged, this, &DDPClient::onServerURLChange); if (!url.isEmpty()) { m_webSocket.open(QUrl("wss://"+url+"/websocket")); } qDebug() << "Trying to connect to URL" << url; } DDPClient::~DDPClient() { m_webSocket.close(); } void DDPClient::onServerURLChange() { if (Ruqola::self()->serverURL() != m_url || !m_webSocket.isValid()) { if (m_webSocket.isValid()) { m_webSocket.flush(); m_webSocket.close(); } m_url = Ruqola::self()->serverURL(); m_webSocket.open(QUrl("wss://"+m_url+"/websocket")); connect(&m_webSocket, &QWebSocket::connected, this, &DDPClient::onWSConnected); qDebug() << "Reconnecting" << m_url; //<< m_webSocket.st; } } DDPClient::LoginStatus DDPClient::loginStatus() const { return m_loginStatus; } bool DDPClient::isConnected() const { return m_connected; } bool DDPClient::isLoggedIn() const { return m_loginStatus == LoggedIn; } unsigned int DDPClient::method(const QString& m, const QJsonDocument& params) { return method(m, params, empty_callback); } unsigned int DDPClient::method(const QString& method, const QJsonDocument& params, std::function callback) { QJsonObject json; json["msg"] = "method"; json["method"] = method; json["id"] = QString::number(m_uid); if (params.isArray()){ json["params"] = params.array(); } else if (params.isObject()) { QJsonArray arr; arr.append(params.object()); json["params"] = arr; // params.object(); } qint64 bytes = m_webSocket.sendTextMessage(QJsonDocument(json).toJson(QJsonDocument::Compact)); if (bytes < json.length()) { qDebug() << "ERROR! I couldn't send all of my message. This is a bug! (try again)"; qDebug() << m_webSocket.isValid() << m_webSocket.error() << m_webSocket.requestUrl(); } else { qDebug() << "Successfully sent " << json; } //callback(QJsonDocument::fromJson(json.toUtf8())); m_callbackHash[m_uid] = callback; m_uid++; return m_uid - 1 ; } void DDPClient::subscribe(const QString& collection, const QJsonArray& params) { QJsonObject json; json["msg"] = "sub"; json["id"] = QString::number(m_uid); json["name"] = collection; json["params"] = params; qint64 bytes = m_webSocket.sendTextMessage(QJsonDocument(json).toJson(QJsonDocument::Compact)); if (bytes < json.length()) { qDebug() << "ERROR! I couldn't send all of my message. This is a bug! (try again)"; } m_uid++; } void DDPClient::onTextMessageReceived(QString message) { +// qDebug() << "DDPClient::onTextMessageReceived" << message; QJsonDocument response = QJsonDocument::fromJson(message.toUtf8()); if (!response.isNull() && response.isObject()) { QJsonObject root = response.object(); QString messageType = root.value("msg").toString(); if (messageType == "updated") { } else if (messageType == "result") { -// qDebug() << "got a result" << root; +// qDebug() << "Got a result" << root; unsigned id = root.value("id").toString().toInt(); if (m_callbackHash.contains(id)) { std::function callback = m_callbackHash.take(id); + //---------------------------------------------------- + //Receiving image message + QByteArray ba; + ba.append(root.value("result").toString()); + int imageWidth = 400; + int imageHeight = 300; + int bytesPerPixel = 3; + QImage image((uchar *)ba.data(), imageWidth, imageHeight, imageWidth * bytesPerPixel, QImage::Format_RGB32); + //image.loadFromData(ba); + + if (image.isNull() || image.byteCount() != ba.size()) { + qDebug() << "Error: Image not loaded correctly"; + } + +// QLabel label; +// label.setPixmap(QPixmap::fromImage(image)); +// label.show(); + //---------------------------------------------------------------- callback( QJsonDocument(root.value("result").toObject()) ); + } emit result(id, QJsonDocument(root.value("result").toObject())); if (id == m_loginJob) { if (root.value("error").toObject().value("error").toInt() == 403) { qDebug() << "Wrong password or token expired"; login(); // Let's keep trying to log in } else { Ruqola::self()->setAuthToken(root.value("result").toObject().value("token").toString()); setLoginStatus(DDPClient::LoggedIn); } // emit loggedInChanged(); } } else if (messageType == "connected") { qDebug() << "Connected"; m_connected = true; emit connectedChanged(); setLoginStatus(DDPClient::LoggingIn); - login(); // Try to resume auth token login - - + login(); // Try to resume auth token login } else if (messageType == "error") { qDebug() << "ERROR!!" << message; } else if (messageType == "ping") { qDebug() << "Ping - Pong"; QJsonObject pong; pong["msg"] = "pong"; m_webSocket.sendBinaryMessage(QJsonDocument(pong).toJson(QJsonDocument::Compact)); } else if (messageType == "added"){ qDebug() << "ADDING" <password().isEmpty()) { // If we have a password and we couldn't log in, let's stop here if (m_attemptedPasswordLogin) { setLoginStatus(LoginFailed); return; } m_attemptedPasswordLogin = true; QJsonObject user; user["username"] = Ruqola::self()->userName(); QJsonObject json; json["password"] = Ruqola::self()->password(); json["user"] = user; m_loginJob = method("login", QJsonDocument(json)); } else if (!Ruqola::self()->authToken().isEmpty() && !m_attemptedTokenLogin) { m_attemptedPasswordLogin = true; QJsonObject json; json["resume"] = Ruqola::self()->authToken(); m_loginJob = method("login", QJsonDocument(json)); } else { setLoginStatus(LoginFailed); } } void DDPClient::logOut() { // setLoginStatus(NotConnected); m_webSocket.close(); } void DDPClient::onWSConnected() { qDebug() << "Websocket connected at URL" << m_url; QJsonArray supportedVersions; supportedVersions.append("1"); QJsonObject protocol; protocol["msg"] = "connect"; protocol["version"] = "1"; protocol["support"] = supportedVersions; // QString json("{\"msg\":\"connect\", \"version\": \"1\", \"support\": [\"1\"]}"); QByteArray serialize = QJsonDocument(protocol).toJson(QJsonDocument::Compact); qint64 bytes = m_webSocket.sendTextMessage(serialize); if (bytes < serialize.length()) { qDebug() << "ERROR! I couldn't send all of my message. This is a bug! (try again)"; } else { qDebug() << "Successfully sent " << serialize; } } void DDPClient::WSclosed() { qDebug() << "WebSocket CLOSED" << m_webSocket.closeReason() << m_webSocket.error() << m_webSocket.closeCode(); setLoginStatus(NotConnected); // m_connected = false; } diff --git a/src/ruqola.cpp b/src/ruqola.cpp index 4337afa1..8a65bc52 100644 --- a/src/ruqola.cpp +++ b/src/ruqola.cpp @@ -1,274 +1,277 @@ /* * * Copyright 2016 Riccardo Iaconelli * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #include "ruqola.h" #include "roommodel.h" #include "ddpclient.h" #include "notification.h" #include #include #include Ruqola *Ruqola::m_self = 0; QString Ruqola::authToken() const { return m_authToken; } QString Ruqola::userName() const { return m_userName; } QString Ruqola::userID() const { return m_userID; } QString Ruqola::password() const { return m_password; } void Ruqola::setAuthToken(const QString& token) { qDebug() << "Setting token to" << token; QSettings s; m_authToken = token; s.setValue("authToken", token); } void Ruqola::setPassword(const QString& password) { m_password = password; } void Ruqola::setUserName(const QString& username) { m_userName = username; QSettings s; s.setValue("username", username); emit userNameChanged(); } void Ruqola::setUserID(const QString& userID) { m_userName = userID; QSettings s; s.setValue("userID", userID); emit userIDChanged(); } RoomModel * Ruqola::roomModel() { if (!m_roomModel) { qDebug() << "creating new RoomModel"; m_roomModel = new RoomModel(this); qDebug() << m_roomModel; } return m_roomModel; } DDPClient * Ruqola::ddp() { if (!m_ddp) { m_ddp = new DDPClient(serverURL()); connect(m_ddp, &DDPClient::loginStatusChanged, this, &Ruqola::loginStatusChanged); // connect(m_ddp, &DDPClient::loginStatusChanged, this, [=](){qDebug() << "Signal received";}); } return m_ddp; } Notification * Ruqola::notification() { if (m_notification == NULL) { m_notification = new Notification(); m_notification->show(); } return m_notification; } void Ruqola::attachmentButtonClicked() { //open fileDialogBox, select an image, convert it to bytearray and send as message // QFileDialog dialog(NULL); // dialog.setFileMode(QFileDialog::ExistingFile); // dialog.setViewMode(QFileDialog::List); QString fileName = QFileDialog::getOpenFileName(Q_NULLPTR, "Select one or more files to open", QDir::homePath(), "Images (*.png *.jpeg *.jpg)"); qDebug() << "Selected Image" << fileName; sendImage(fileName); } void Ruqola::sendImage(QString fileName) { - qDebug() << "Sending Image"; QImage image(fileName); QByteArray ba; + qDebug() << "Sending Image of size " << image.byteCount(); + ba.append((char *)image.bits(),image.byteCount()); if (image.byteCount() != ba.size()) qDebug() << "Image not encoded correctly"; QString message(ba); - QString roomID("wtl-test2"); + //hard code roomID for now + QString roomID("3cGRyFLWgnPL7B79n"); sendMessage(roomID,message); } void Ruqola::sendMessage(const QString &roomID, const QString &message) { QString json = "{\"rid\": \"%1\", \"msg\": \"%2\"}"; json = json.arg(roomID, message); + // qDebug() << "Sending json: " << json; ddp()->method("sendMessage", QJsonDocument::fromJson(json.toUtf8())); } MessageModel * Ruqola::getModelForRoom(const QString& roomID) { if (m_messageModels.contains(roomID)) { // qDebug() << "Returning old model for " << roomID; return m_messageModels.value(roomID); } else { // qDebug() << "Creating a new model"; m_messageModels[roomID] = new MessageModel(roomID, this); return m_messageModels[roomID]; } } QString Ruqola::serverURL() const { return m_serverURL; } void Ruqola::setServerURL(const QString& serverURL) { if (m_serverURL == serverURL) { return; } QSettings s; s.setValue("serverURL", serverURL); m_serverURL = serverURL; // m_roomModel->reset(); emit serverURLChanged(); } DDPClient::LoginStatus Ruqola::loginStatus() { if (m_ddp) { return ddp()->loginStatus(); } else { return DDPClient::LoggedOut; } } void Ruqola::tryLogin() { qDebug() << "Attempting login" << userName() << "on" << serverURL(); // Reset model views foreach (const QString key, m_messageModels.keys()) { MessageModel *m = m_messageModels.take(key); delete m; } delete m_ddp; m_ddp = 0; // In the meantime, load cache... m_roomModel->reset(); // This creates a new ddp() object. // DDP will automatically try to connect and login. ddp(); } void Ruqola::logOut() { setAuthToken(QString()); setPassword(QString()); foreach (const QString key, m_messageModels.keys()) { MessageModel *m = m_messageModels.take(key); delete m; } delete m_ddp; m_ddp = 0; emit loginStatusChanged(); m_roomModel->clear(); } QString Ruqola::cacheBasePath() const { if (m_serverURL.isEmpty()) { return QString(); } return QStandardPaths::writableLocation(QStandardPaths::CacheLocation)+'/'+m_serverURL; } // QString Ruqola::activeRoom() const // { // return m_activeRoom; // } // void Ruqola::setActiveRoom(const QString& activeRoom) // { // m_activeRoom = activeRoom; // // roomModel()->setActiveRoom(activeRoom); // emit activeRoomChanged(); // } RoomWrapper * Ruqola::getRoom(const QString& roomID) { return roomModel()->findRoom(roomID); } Ruqola::Ruqola(QObject* parent): QObject(parent), m_ddp(0), m_roomModel(0), m_notification(0) { QSettings s; m_serverURL = s.value("serverURL", "demo.rocket.chat").toString(); m_userName = s.value("username").toString(); m_userID = s.value("userID").toString(); m_authToken = s.value("authToken").toString(); } Ruqola * Ruqola::self() { if (!m_self) { m_self = new Ruqola; // Create DDP object so we try to connect at startup m_self->ddp(); // Clear rooms data and refill it with data in the cache, if there is m_self->roomModel()->reset(); // Create systray to show notifications m_self->notification(); } return m_self; }