import QtGraphicalEffects 1.0 import QtQuick 2.7 import QtQuick.Controls 2.4 import QtQuick.Controls.Material 2.0 import QtQuick.Layouts 1.3 import "../widgets" import "../widgets/controls" as Awesome import "../fonts/Twemoji.js" as T import "../utils.js" as Utils ColumnLayout { Layout.fillWidth: true property bool loading ListModel { // MESSAGE OBJECTS ARE STORED HERE ... id: messagesModel } ListView { id: messagesListView Layout.fillHeight: true Layout.fillWidth: true model: messagesModel spacing: 6 clip: true ScrollBar.vertical: ScrollBar {} maximumFlickVelocity: 1250 delegate: Message { handle: _handle from: _from displayName: _displayName message: _message rawMessage: _rawMessage image: _image messageID: _mid fromMe: _fromMe timestamp: _ts ackd: _ackd error: _error } Connections { target: gcd onClearMessages: function() { messagesModel.clear() txtMessage.text = "" } onAppendMessage: function(handle, from, displayName, message, image, mid, fromMe, ts, ackd, error) { var msg try { msg = JSON.parse(message) } catch (e) { msg = {"o": 1, "d": "(legacy message type) " + message} } if (msg.o != 1) return messagesModel.append({ "_handle": handle, "_from": from, "_displayName": displayName, "_message":parse(msg.d, 12), "_rawMessage":msg.d, "_image": image, "_mid": mid, "_fromMe": fromMe, "_ts": ts, "_ackd": ackd, "_error": error == true ? "this message failed to send" : "", }) messagesListView.positionViewAtEnd() // If the window is out of focus, alert the user (makes taskbar light up) windowItem.alert(0) if (gcd.os == "android" && windowItem.activeFocusItem == null) { androidCwtchActivity.notification = "New Content" } } onPrependMessage: function(handle, from, displayName, message, image, mid, fromMe, ts, ackd, error) { var msg try { msg = JSON.parse(message) } catch (e) { msg = {"o": 1, "d": "(legacy message type) " + message} } if (msg.o != 1) return messagesModel.insert(0, { "_handle": handle, "_from": from, "_displayName": displayName, "_message":parse(msg.d, 24), "_rawMessage":msg.d, "_image": image, "_mid": mid, "_fromMe": fromMe, "_ts": ts, "_ackd": ackd, "_error": error == true ? "this message failed to send" : "", }) messagesListView.positionViewAtEnd() } onUpdateContact: function(_handle, _displayName, _image, _server, _badge, _status, _trusted, _blocked, _loading) { if (gcd.currentOpenConversation == _handle) { // Group is Synced OR p2p is Authenticated if ( (_handle.length == 32 && _status == 4) || _status == 3) { txtMessage.enabled = true btnSend.enabled = true } else { txtMessage.enabled = false btnSend.enabled = false } } } } } RowLayout { // THE BOTTOM DRAWER Rectangle { // MESSAGE ENTRY TEXTFIELD id: rectMessage Layout.fillWidth: true Layout.minimumHeight: 40 * gcd.themeScale Layout.maximumHeight: 40 * gcd.themeScale color: "#EDEDED" border.color: "#AAAAAA" radius: 10 MouseArea { anchors.fill: parent onClicked: txtMessage.focus = true } Flickable { id: flkMessage anchors.fill: parent//this does nothing! bug in qt Layout.minimumWidth: parent.width Layout.maximumWidth: parent.width Layout.minimumHeight: rectMessage.height Layout.maximumHeight: rectMessage.height contentWidth: txtMessage.width contentHeight: txtMessage.height boundsBehavior: Flickable.StopAtBounds clip:true maximumFlickVelocity: 300 ScrollBar.vertical: ScrollBar{} TextEdit { id: txtMessage font.pixelSize: 10 * gcd.themeScale text: "" padding: 6 wrapMode: TextEdit.Wrap textFormat: Text.RichText width: rectMessage.width property bool skipOneUpdate: false property int previousCursor Keys.onReturnPressed: { // CTRL+ENTER = LINEBREAK if ((event.modifiers & Qt.ControlModifier) && gcd.os != "android") { txtMessage.insert(txtMessage.cursorPosition, "
") } else if (event.modifiers == Qt.NoModifier) { btnSend.clicked() } } // welcome to the emoji parser! it is horrifying code that needs to leave in // while also stripping any other tag, including other images. // TODO: this probably breaks if people actually do want to paste html onTextChanged: { if (gcd.os == "android") { return } // we're taking advantage of TextEdit.getText()'s parsing capability, which means occasionally // passing text into it to be filtered. this prevents recursive calls putting us into an // infinite loop if (skipOneUpdate) { skipOneUpdate = false return } previousCursor = cursorPosition //console.log("onTextChanged() at position " + previousCursor) //console.log("1: " + txtMessage.getText(0, txtMessage.text.length)) // convert tags back to their emoji form // Then parse out the rest of the HTML var nt = restoreEmoji(txtMessage.text) if (nt != txtMessage.text) { skipOneUpdate = true txtMessage.text = nt } //console.log("2: " + txtMessage.getText(0, txtMessage.text.length)) var preserveSpaces = txtMessage.text.replace(/
/g,"[:newline:]"); if (preserveSpaces != txtMessage.text) { skipOneUpdate = true txtMessage.text = preserveSpaces } // strip all HTML tags var theText = Utils.htmlEscaped(txtMessage.getText(0, txtMessage.text.length)) //console.log("3: " + theText) // convert emoji back to tags nt = parse(theText, 10) //console.log("4: " + nt) // preserve double spacing nt = nt.replace(/\s\s/g, "  "); nt = nt.replace(/\[\:newline\:\]/g, "
"); // then we actually put the updated text in skipOneUpdate = true txtMessage.text = nt txtMessage.cursorPosition = previousCursor // autoscroll down only when the scrollbar is already all the way down if (flkMessage.contentY + flkMessage.height >= flkMessage.contentHeight - txtMessage.height && flkMessage.contentHeight > flkMessage.height) { flkMessage.contentY = flkMessage.contentHeight - flkMessage.height } } } } } ColumnLayout { id: colRight spacing: 1 SimpleButton { // SEND MESSAGE BUTTON id: btnSend icon: "regular/paper-plane" text: "send" Layout.minimumWidth: btnEmoji.width + btnAttach.width + 2 Layout.maximumWidth: btnEmoji.width + btnAttach.width + 2 anchors.right: parent.right anchors.rightMargin: 2 property int nextMessageID: 1 TextEdit { id: txtHidden visible: false textFormat: Text.RichText } onClicked: { if (txtMessage.text != "") { txtHidden.text = restoreEmoji(txtMessage.text) txtHidden.text = txtHidden.text.replace(/
/g,"[:newline:]"); var txt = txtHidden.text.trim() if (txt.length > 0) { var rawText = txtHidden.getText(0, txtHidden.text.length) var msg = JSON.stringify({"o":1, "d":rawText.replace(/\[\:newline\:\]/g,"\n")}) gcd.sendMessage(msg, nextMessageID++) } } txtMessage.text = "" } } RowLayout { spacing: 1 SimpleButton { // EMOJI DRAWER BUTTON id: btnEmoji icon: "regular/smile" anchors.right: btnAttach.left anchors.rightMargin: 2 onClicked: { gcd.popup("emoji not yet implemented, sorry") } } SimpleButton { id: btnAttach icon: "solid/paperclip" anchors.right: parent.right anchors.rightMargin: 2 onClicked: { gcd.popup("attachments not yet implemented, sorry") } } } } } }