Skip to content
Snippets Groups Projects
MessageInput.qml 13.4 KiB
Newer Older
trilene's avatar
trilene committed
import "./voip"
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import QtQuick.Window 2.2
Nicolas Werner's avatar
Nicolas Werner committed
import im.nheko 1.0

Rectangle {
    color: colors.window
    Layout.fillWidth: true
    Layout.preferredHeight: textInput.height + 16
    Layout.minimumHeight: 40

trilene's avatar
trilene committed
    Component {
        id: placeCallDialog

        PlaceCall {
        }
    RowLayout {
        id: inputBar

        anchors.fill: parent
        anchors.margins: 8
        spacing: 16

        ImageButton {
            visible: CallManager.callsSupported
            opacity: CallManager.haveCallInvite ? 0.3 : 1
            Layout.alignment: Qt.AlignBottom
            hoverEnabled: true
            width: 22
            height: 22
            image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.png" : ":/icons/icons/ui/place-call.png"
            ToolTip.visible: hovered
            ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : qsTr("Place a call")
            Layout.leftMargin: 8
trilene's avatar
trilene committed
            onClicked: {
                if (TimelineManager.timeline) {
                    if (CallManager.haveCallInvite) {
                        return ;
                    } else if (CallManager.isOnCall) {
trilene's avatar
trilene committed
                        CallManager.hangUp();
                    } else {
                        CallManager.refreshDevices();
trilene's avatar
trilene committed
                        var dialog = placeCallDialog.createObject(timelineRoot);
trilene's avatar
trilene committed
                        dialog.open();
        }

        ImageButton {
            Layout.alignment: Qt.AlignBottom
            hoverEnabled: true
            width: 22
            height: 22
            image: ":/icons/icons/ui/paper-clip-outline.png"
            Layout.leftMargin: CallManager.callsSupported ? 0 : 8
Nicolas Werner's avatar
Nicolas Werner committed
            onClicked: TimelineManager.timeline.input.openFileSelection()
            ToolTip.visible: hovered
            ToolTip.text: qsTr("Send a file")
Nicolas Werner's avatar
Nicolas Werner committed

            Rectangle {
                anchors.fill: parent
                color: colors.window
Nicolas Werner's avatar
Nicolas Werner committed
                visible: TimelineManager.timeline && TimelineManager.timeline.input.uploading
Nicolas Werner's avatar
Nicolas Werner committed

                NhekoBusyIndicator {
                    anchors.fill: parent
                    running: parent.visible
                }

            }

        Flickable {
            function ensureVisible(r) {
                if (contentX >= r.x)
                    contentX = r.x;
                else if (contentX + width <= r.x + r.width)
                    contentX = r.x + r.width - width;
                if (contentY >= r.y)
                    contentY = r.y;
                else if (contentY + height <= r.y + r.height)
                    contentY = r.y + r.height - height;
            }

            Layout.alignment: Qt.AlignBottom
            Layout.maximumHeight: Window.height / 4
            Layout.minimumHeight: Settings.fontSize
            Layout.fillWidth: true
            clip: true
            boundsBehavior: Flickable.StopAtBounds
            flickableDirection: Flickable.VerticalFlick
            implicitWidth: messageInput.width
            implicitHeight: messageInput.height
            contentWidth: messageInput.width
            contentHeight: messageInput.height
Nicolas Werner's avatar
Nicolas Werner committed
                property int completerTriggeredAt: -1

                function insertCompletion(completion) {
                    messageInput.remove(completerTriggeredAt, cursorPosition);
                    messageInput.insert(cursorPosition, completion);
Nicolas Werner's avatar
Nicolas Werner committed
                function openCompleter(pos, type) {
                    completerTriggeredAt = pos;
                    popup.completerName = type;
                    popup.open();
                    popup.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition));
                selectByMouse: true
                placeholderText: qsTr("Write a message...")
                //placeholderTextColor: colors.buttonText
Nicolas Werner's avatar
Nicolas Werner committed
                // only set the anchors on Qt 5.12 or higher
                // see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop
                Component.onCompleted: {
                    if (placeholderTextColor !== undefined)
                        placeholderTextColor = colors.buttonText;
Nicolas Werner's avatar
Nicolas Werner committed
                }
                width: textInput.width
                wrapMode: TextEdit.Wrap
                padding: 0
Nicolas Werner's avatar
Nicolas Werner committed
                onTextChanged: {
                    if (TimelineManager.timeline)
Nicolas Werner's avatar
Nicolas Werner committed
                        TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
                    forceActiveFocus();
Nicolas Werner's avatar
Nicolas Werner committed
                }
                onCursorRectangleChanged: textInput.ensureVisible(cursorRectangle)
Nicolas Werner's avatar
Nicolas Werner committed
                onCursorPositionChanged: {
Nicolas Werner's avatar
Nicolas Werner committed
                    if (!TimelineManager.timeline)
                        return ;

Nicolas Werner's avatar
Nicolas Werner committed
                    TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
Nicolas Werner's avatar
Nicolas Werner committed
                    if (cursorPosition <= completerTriggeredAt) {
Nicolas Werner's avatar
Nicolas Werner committed
                        completerTriggeredAt = -1;
                        popup.close();
                    }
                        popup.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition));
Nicolas Werner's avatar
Nicolas Werner committed
                }
Nicolas Werner's avatar
Nicolas Werner committed
                onSelectionStartChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
                onSelectionEndChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
                // Ensure that we get escape key press events first.
Nicolas Werner's avatar
Nicolas Werner committed
                Keys.onShortcutOverride: event.accepted = (completerTriggeredAt != -1 && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter))
Nicolas Werner's avatar
Nicolas Werner committed
                Keys.onPressed: {
                    if (event.matches(StandardKey.Paste)) {
Nicolas Werner's avatar
Nicolas Werner committed
                        TimelineManager.timeline.input.paste(false);
                        event.accepted = true;
                    } else if (event.key == Qt.Key_Space) {
                        // close popup if user enters space after colon
                        if (cursorPosition == completerTriggeredAt + 1)
                        if (popup.opened && popup.count <= 0)
Nicolas Werner's avatar
Nicolas Werner committed
                    } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_U) {
Nicolas Werner's avatar
Nicolas Werner committed
                    } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) {
                        messageInput.text = TimelineManager.timeline.input.previousText();
Nicolas Werner's avatar
Nicolas Werner committed
                    } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_N) {
                        messageInput.text = TimelineManager.timeline.input.nextText();
Nicolas Werner's avatar
Nicolas Werner committed
                    } else if (event.key == Qt.Key_At) {
                        messageInput.openCompleter(cursorPosition, "user");
Nicolas Werner's avatar
Nicolas Werner committed
                        popup.open();
Nicolas Werner's avatar
Nicolas Werner committed
                    } else if (event.key == Qt.Key_Colon) {
                        messageInput.openCompleter(cursorPosition, "emoji");
Nicolas Werner's avatar
Nicolas Werner committed
                        popup.open();
                    } else if (event.key == Qt.Key_NumberSign) {
                        messageInput.openCompleter(cursorPosition, "roomAliases");
Nicolas Werner's avatar
Nicolas Werner committed
                    } else if (event.key == Qt.Key_Escape && popup.opened) {
                        completerTriggeredAt = -1;
Nicolas Werner's avatar
Nicolas Werner committed
                        popup.completerName = "";
Nicolas Werner's avatar
Nicolas Werner committed
                        event.accepted = true;
                        popup.close();
                    } else if (event.matches(StandardKey.InsertParagraphSeparator)) {
                        if (popup.opened) {
                            var currentCompletion = popup.currentCompletion();
                            popup.completerName = "";
                            popup.close();
                            if (currentCompletion) {
                                messageInput.insertCompletion(currentCompletion);
                                event.accepted = true;
                                return ;
                            }
Nicolas Werner's avatar
Nicolas Werner committed
                        }
                        TimelineManager.timeline.input.send();
                        event.accepted = true;
Nicolas Werner's avatar
Nicolas Werner committed
                    } else if (event.key == Qt.Key_Tab) {
Nicolas Werner's avatar
Nicolas Werner committed
                        event.accepted = true;
Nicolas Werner's avatar
Nicolas Werner committed
                        if (popup.opened) {
                            popup.up();
                        } else {
                            var pos = cursorPosition - 1;
                            while (pos > -1) {
                                var t = messageInput.getText(pos, pos + 1);
Nicolas Werner's avatar
Nicolas Werner committed
                                console.log('"' + t + '"');
                                    messageInput.openCompleter(pos, "user");
Nicolas Werner's avatar
Nicolas Werner committed
                                    return ;
                                } else if (t == ' ' || t == '\t') {
                                    messageInput.openCompleter(pos + 1, "user");
                                    return ;
Nicolas Werner's avatar
Nicolas Werner committed
                                } else if (t == ':') {
                                    messageInput.openCompleter(pos, "emoji");
Nicolas Werner's avatar
Nicolas Werner committed
                                    return ;
                                }
                                pos = pos - 1;
                            }
                            // At start of input
                            messageInput.openCompleter(0, "user");
Nicolas Werner's avatar
Nicolas Werner committed
                    } else if (event.key == Qt.Key_Up && popup.opened) {
                        event.accepted = true;
                        popup.up();
                    } else if (event.key == Qt.Key_Down && popup.opened) {
                        event.accepted = true;
                        popup.down();
                    }
                }
                background: null
Nicolas Werner's avatar
Nicolas Werner committed
                Connections {
                    onActiveTimelineChanged: {
                        messageInput.clear();
                        messageInput.append(TimelineManager.timeline.input.text());
                        messageInput.completerTriggeredAt = -1;
Nicolas Werner's avatar
Nicolas Werner committed
                        popup.completerName = "";
                        messageInput.forceActiveFocus();
Nicolas Werner's avatar
Nicolas Werner committed
                    }
                    target: TimelineManager
                }

                Connections {
                    onCompletionClicked: messageInput.insertCompletion(completion)
                    target: popup
                }
Nicolas Werner's avatar
Nicolas Werner committed
                Completer {
                    id: popup

                    x: messageInput.completerTriggeredAt >= 0 ? messageInput.positionToRectangle(messageInput.completerTriggeredAt).x : 0
                    y: messageInput.completerTriggeredAt >= 0 ? messageInput.positionToRectangle(messageInput.completerTriggeredAt).y - height : 0
Nicolas Werner's avatar
Nicolas Werner committed
                }

                Connections {
Nicolas Werner's avatar
Nicolas Werner committed
                    ignoreUnknownSignals: true
                    onInsertText: {
                        messageInput.insert(messageInput.cursorPosition, text);
                    }
                    onTextChanged: {
                        messageInput.text = newText;
                        messageInput.cursorPosition = newText.length;
                    }
Nicolas Werner's avatar
Nicolas Werner committed
                    target: TimelineManager.timeline ? TimelineManager.timeline.input : null
                Connections {
                    ignoreUnknownSignals: true
                    onReplyChanged: messageInput.forceActiveFocus()
                    onEditChanged: messageInput.forceActiveFocus()
                    target: TimelineManager.timeline
                }

                Connections {
                    target: TimelineManager
                    onFocusInput: messageInput.forceActiveFocus()
                }

                MouseArea {
                    // workaround for wrong cursor shape on some platforms
                    anchors.fill: parent
Nicolas Werner's avatar
Nicolas Werner committed
                    acceptedButtons: Qt.MiddleButton
                    cursorShape: Qt.IBeamCursor
Nicolas Werner's avatar
Nicolas Werner committed
                    onClicked: TimelineManager.timeline.input.paste(true)
            ScrollBar.vertical: ScrollBar {
            id: emojiButton

            Layout.alignment: Qt.AlignRight | Qt.AlignBottom
            hoverEnabled: true
            width: 22
            height: 22
            image: ":/icons/icons/ui/smile.png"
            ToolTip.visible: hovered
            ToolTip.text: qsTr("Emoji")
            onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, function(emoji) {
                messageInput.insert(messageInput.cursorPosition, emoji);
                TimelineManager.focusMessageInput();
        }

        ImageButton {
            Layout.alignment: Qt.AlignRight | Qt.AlignBottom
            hoverEnabled: true
            width: 22
            height: 22
            image: ":/icons/icons/ui/cursor.png"
            Layout.rightMargin: 8
            ToolTip.visible: hovered
            ToolTip.text: qsTr("Send")
Nicolas Werner's avatar
Nicolas Werner committed
            onClicked: {
                TimelineManager.timeline.input.send();
Nicolas Werner's avatar
Nicolas Werner committed
            }