nheko/resources/qml/MessageInput.qml

389 lines
15 KiB
QML
Raw Normal View History

2021-03-05 00:35:15 +01:00
// SPDX-FileCopyrightText: 2021 Nheko Contributors
2021-03-14 02:45:20 +01:00
//
2021-03-05 00:35:15 +01:00
// SPDX-License-Identifier: GPL-3.0-or-later
2021-07-15 20:37:52 +02:00
import "./emoji"
2020-12-30 21:03:07 +01:00
import "./voip"
2021-02-21 02:11:50 +01:00
import QtQuick 2.12
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import QtQuick.Window 2.2
2020-10-31 23:24:07 +01:00
import im.nheko 1.0
Rectangle {
2021-02-21 02:11:50 +01:00
id: inputBar
2021-05-13 08:23:56 +02:00
color: Nheko.colors.window
Layout.fillWidth: true
2021-02-21 02:11:50 +01:00
Layout.preferredHeight: row.implicitHeight
Layout.minimumHeight: 40
2020-12-18 18:49:24 +01:00
Component {
id: placeCallDialog
PlaceCall {
}
2021-01-11 23:51:39 +01:00
2020-12-18 18:49:24 +01:00
}
RowLayout {
2021-02-21 02:11:50 +01:00
id: row
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
anchors.fill: parent
ImageButton {
visible: CallManager.callsSupported
2021-01-11 23:51:39 +01:00
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"
2020-11-15 23:14:47 +01:00
ToolTip.visible: hovered
ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : qsTr("Place a call")
2021-02-21 02:11:50 +01:00
Layout.margins: 8
2020-12-18 18:49:24 +01:00
onClicked: {
if (room) {
2020-12-18 18:49:24 +01:00
if (CallManager.haveCallInvite) {
2021-01-11 23:51:39 +01:00
return ;
} else if (CallManager.isOnCall) {
2020-12-18 18:49:24 +01:00
CallManager.hangUp();
2021-01-11 23:51:39 +01:00
} else {
2020-12-18 18:49:24 +01:00
var dialog = placeCallDialog.createObject(timelineRoot);
2020-12-30 21:03:07 +01:00
dialog.open();
2020-12-18 18:49:24 +01:00
}
}
}
}
ImageButton {
Layout.alignment: Qt.AlignBottom
hoverEnabled: true
width: 22
height: 22
image: ":/icons/icons/ui/paper-clip-outline.png"
2021-02-21 02:11:50 +01:00
Layout.margins: 8
onClicked: room.input.openFileSelection()
2020-11-15 23:14:47 +01:00
ToolTip.visible: hovered
ToolTip.text: qsTr("Send a file")
2020-11-15 04:52:49 +01:00
Rectangle {
anchors.fill: parent
2021-05-13 08:23:56 +02:00
color: Nheko.colors.window
visible: room && room.input.uploading
2020-11-15 04:52:49 +01:00
NhekoBusyIndicator {
anchors.fill: parent
running: parent.visible
}
}
}
2021-02-21 02:11:50 +01:00
ScrollView {
id: textInput
2021-02-21 02:11:50 +01:00
Layout.alignment: Qt.AlignBottom // | Qt.AlignHCenter
Layout.maximumHeight: Window.height / 4
2021-01-17 04:05:02 +01:00
Layout.minimumHeight: Settings.fontSize
2021-07-15 20:37:52 +02:00
implicitWidth: inputBar.width - 5 * (22 + 16) - 24
TextArea {
id: messageInput
2020-10-31 23:24:07 +01:00
2020-11-20 01:22:36 +01:00
property int completerTriggeredAt: -1
2020-11-24 17:32:45 +01:00
function insertCompletion(completion) {
messageInput.remove(completerTriggeredAt, cursorPosition);
messageInput.insert(cursorPosition, completion);
2020-11-24 17:32:45 +01:00
}
2020-11-25 19:03:22 +01:00
function openCompleter(pos, type) {
completerTriggeredAt = pos;
popup.completerName = type;
popup.open();
popup.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition));
2020-11-25 19:03:22 +01:00
}
function positionCursorAtEnd() {
cursorPosition = messageInput.length;
}
function positionCursorAtStart() {
cursorPosition = 0;
}
2020-11-30 12:09:24 +01:00
selectByMouse: true
placeholderText: qsTr("Write a message...")
2021-05-13 08:23:56 +02:00
placeholderTextColor: Nheko.colors.buttonText
color: Nheko.colors.text
2021-01-17 04:05:02 +01:00
width: textInput.width
wrapMode: TextEdit.Wrap
2021-02-21 02:11:50 +01:00
padding: 8
focus: true
2021-01-19 03:25:56 +01:00
onTextChanged: {
if (room)
room.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
2021-01-19 23:58:25 +01:00
forceActiveFocus();
2021-01-19 03:25:56 +01:00
}
2020-11-20 01:22:36 +01:00
onCursorPositionChanged: {
if (!room)
2021-01-19 03:25:56 +01:00
return ;
room.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
2020-11-20 02:38:08 +01:00
if (cursorPosition <= completerTriggeredAt) {
2020-11-20 01:22:36 +01:00
completerTriggeredAt = -1;
popup.close();
}
if (popup.opened)
popup.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition));
2020-11-20 01:22:36 +01:00
}
onSelectionStartChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
onSelectionEndChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
2020-11-24 17:32:45 +01:00
// Ensure that we get escape key press events first.
2020-11-20 01:22:36 +01:00
Keys.onShortcutOverride: event.accepted = (completerTriggeredAt != -1 && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter))
2020-10-31 23:24:07 +01:00
Keys.onPressed: {
if (event.matches(StandardKey.Paste)) {
room.input.paste(false);
2020-11-15 04:52:49 +01:00
event.accepted = true;
} else if (event.key == Qt.Key_Space) {
// close popup if user enters space after colon
if (cursorPosition == completerTriggeredAt + 1)
popup.close();
if (popup.opened && popup.count <= 0)
popup.close();
2020-11-20 01:22:36 +01:00
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_U) {
messageInput.clear();
2020-11-20 01:22:36 +01:00
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) {
messageInput.text = room.input.previousText();
2020-11-20 01:22:36 +01:00
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_N) {
messageInput.text = room.input.nextText();
2020-11-20 01:22:36 +01:00
} else if (event.key == Qt.Key_At) {
messageInput.openCompleter(cursorPosition, "user");
2020-11-20 01:22:36 +01:00
popup.open();
2020-11-20 04:33:11 +01:00
} else if (event.key == Qt.Key_Colon) {
messageInput.openCompleter(cursorPosition, "emoji");
2020-11-20 04:33:11 +01:00
popup.open();
} else if (event.key == Qt.Key_NumberSign) {
messageInput.openCompleter(cursorPosition, "roomAliases");
popup.open();
2020-11-20 01:22:36 +01:00
} else if (event.key == Qt.Key_Escape && popup.opened) {
completerTriggeredAt = -1;
2020-11-20 02:38:08 +01:00
popup.completerName = "";
2020-11-20 01:22:36 +01:00
event.accepted = true;
popup.close();
2020-11-25 02:20:42 +01:00
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
if (popup.opened) {
var currentCompletion = popup.currentCompletion();
popup.completerName = "";
popup.close();
if (currentCompletion) {
messageInput.insertCompletion(currentCompletion);
2020-11-25 02:20:42 +01:00
event.accepted = true;
return ;
}
2020-11-20 01:22:36 +01:00
}
room.input.send();
2020-11-25 02:20:42 +01:00
event.accepted = true;
2020-11-25 19:03:22 +01:00
} else if (event.key == Qt.Key_Tab) {
2020-11-20 01:22:36 +01:00
event.accepted = true;
2020-11-25 19:03:22 +01:00
if (popup.opened) {
popup.up();
} else {
var pos = cursorPosition - 1;
while (pos > -1) {
var t = messageInput.getText(pos, pos + 1);
2020-11-25 19:03:22 +01:00
console.log('"' + t + '"');
if (t == '@') {
messageInput.openCompleter(pos, "user");
2020-11-25 19:03:22 +01:00
return ;
} else if (t == ' ' || t == '\t') {
messageInput.openCompleter(pos + 1, "user");
return ;
2020-11-25 19:03:22 +01:00
} else if (t == ':') {
messageInput.openCompleter(pos, "emoji");
2020-11-25 19:03:22 +01:00
return ;
}
pos = pos - 1;
}
// At start of input
messageInput.openCompleter(0, "user");
2020-11-25 19:03:22 +01:00
}
2020-11-20 01:22:36 +01:00
} 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();
} else if (event.key == Qt.Key_Up && event.modifiers == Qt.NoModifier) {
2021-02-24 23:51:05 +01:00
if (cursorPosition == 0) {
event.accepted = true;
var idx = room.edit ? room.idToIndex(room.edit) + 1 : 0;
2021-02-24 23:51:05 +01:00
while (true) {
var id = room.indexToId(idx);
if (!id || room.getDump(id, "").isEditable) {
room.edit = id;
2021-02-24 23:51:05 +01:00
cursorPosition = 0;
Qt.callLater(positionCursorAtEnd);
2021-02-24 23:51:05 +01:00
break;
}
idx++;
}
} else if (positionAt(0, cursorRectangle.y) === 0) {
2021-02-24 23:51:05 +01:00
event.accepted = true;
positionCursorAtStart();
2021-02-24 23:51:05 +01:00
}
} else if (event.key == Qt.Key_Down && event.modifiers == Qt.NoModifier) {
if (cursorPosition == messageInput.length && room.edit) {
2021-02-24 23:51:05 +01:00
event.accepted = true;
var idx = room.idToIndex(room.edit) - 1;
2021-02-24 23:51:05 +01:00
while (true) {
var id = room.indexToId(idx);
if (!id || room.getDump(id, "").isEditable) {
room.edit = id;
Qt.callLater(positionCursorAtStart);
2021-02-24 23:51:05 +01:00
break;
}
idx--;
}
} else if (positionAt(width, cursorRectangle.y + 2) === messageInput.length) {
event.accepted = true;
positionCursorAtEnd();
2021-02-24 23:51:05 +01:00
}
2020-11-20 01:22:36 +01:00
}
}
2021-01-17 04:05:02 +01:00
background: null
2020-11-20 01:22:36 +01:00
2020-11-20 02:38:08 +01:00
Connections {
onRoomChanged: {
messageInput.clear();
messageInput.append(room.input.text());
messageInput.completerTriggeredAt = -1;
2020-11-20 02:38:08 +01:00
popup.completerName = "";
messageInput.forceActiveFocus();
2020-11-20 02:38:08 +01:00
}
target: timelineView
2020-11-20 02:38:08 +01:00
}
2020-11-24 17:32:45 +01:00
Connections {
onCompletionClicked: messageInput.insertCompletion(completion)
2020-11-24 17:32:45 +01:00
target: popup
}
2020-11-20 02:38:08 +01:00
2020-11-20 01:22:36 +01:00
Completer {
id: popup
x: messageInput.completerTriggeredAt >= 0 ? messageInput.positionToRectangle(messageInput.completerTriggeredAt).x : 0
y: messageInput.completerTriggeredAt >= 0 ? messageInput.positionToRectangle(messageInput.completerTriggeredAt).y - height : 0
2020-11-15 04:52:49 +01:00
}
Connections {
2021-01-19 03:25:56 +01:00
ignoreUnknownSignals: true
onInsertText: {
2021-04-15 01:45:18 +02:00
messageInput.remove(messageInput.selectionStart, messageInput.selectionEnd);
messageInput.insert(messageInput.cursorPosition, text);
}
onTextChanged: {
messageInput.text = newText;
messageInput.cursorPosition = newText.length;
}
target: room ? room.input : null
2020-10-31 23:24:07 +01:00
}
Connections {
ignoreUnknownSignals: true
onReplyChanged: messageInput.forceActiveFocus()
onEditChanged: messageInput.forceActiveFocus()
target: room
}
Connections {
target: TimelineManager
onFocusInput: messageInput.forceActiveFocus()
}
MouseArea {
// workaround for wrong cursor shape on some platforms
anchors.fill: parent
2020-10-31 23:24:07 +01:00
acceptedButtons: Qt.MiddleButton
cursorShape: Qt.IBeamCursor
onClicked: room.input.paste(true)
}
2021-01-17 04:05:02 +01:00
}
}
2021-07-15 20:37:52 +02:00
ImageButton {
id: stickerButton
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins: 8
hoverEnabled: true
width: 22
height: 22
image: ":/icons/icons/ui/sticky-note-solid.svg"
ToolTip.visible: hovered
ToolTip.text: qsTr("Stickers")
2021-07-19 17:45:55 +02:00
onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId(), function(row) {
2021-07-15 20:37:52 +02:00
room.input.sticker(stickerPopup.model.sourceModel, row);
TimelineManager.focusMessageInput();
})
StickerPicker {
id: stickerPopup
colors: Nheko.colors
}
}
ImageButton {
2020-11-16 18:41:29 +01:00
id: emojiButton
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
2021-02-21 02:11:50 +01:00
Layout.margins: 8
hoverEnabled: true
width: 22
height: 22
image: ":/icons/icons/ui/smile.png"
2020-11-15 23:14:47 +01:00
ToolTip.visible: hovered
ToolTip.text: qsTr("Emoji")
2021-07-15 20:37:52 +02:00
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(function(emoji) {
messageInput.insert(messageInput.cursorPosition, emoji);
TimelineManager.focusMessageInput();
2020-11-16 18:41:29 +01:00
})
}
ImageButton {
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
2021-02-21 02:11:50 +01:00
Layout.margins: 8
hoverEnabled: true
width: 22
height: 22
image: ":/icons/icons/ui/cursor.png"
2021-01-17 04:05:02 +01:00
Layout.rightMargin: 8
2020-11-15 23:14:47 +01:00
ToolTip.visible: hovered
ToolTip.text: qsTr("Send")
2020-11-15 04:52:49 +01:00
onClicked: {
room.input.send();
2020-11-15 04:52:49 +01:00
}
}
}
Text {
anchors.centerIn: parent
visible: room ? (!room.permissions.canSend(MtxEvent.TextMessage)) : false
text: qsTr("You don't have permission to send messages in this room")
2021-05-13 08:23:56 +02:00
color: Nheko.colors.text
}
}