summary refs log tree commit diff
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2020-11-20 01:22:36 +0100
committerNicolas Werner <nicolas.werner@hotmail.de>2020-11-25 19:05:12 +0100
commitcabeb1464c85d911eea427bd48e8188facde8e56 (patch)
tree329e66698e7bed09b61f47e816c18aad0fc625b2
parentReenable Ctrl+U (diff)
downloadnheko-cabeb1464c85d911eea427bd48e8188facde8e56.tar.xz
WIP Qml completer
-rw-r--r--resources/qml/Completer.qml107
-rw-r--r--resources/qml/MessageInput.qml58
-rw-r--r--resources/res.qrc15
-rw-r--r--src/timeline/InputBar.cpp6
-rw-r--r--src/timeline/InputBar.h2
5 files changed, 174 insertions, 14 deletions
diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml
new file mode 100644

index 00000000..d53eae62 --- /dev/null +++ b/resources/qml/Completer.qml
@@ -0,0 +1,107 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import im.nheko 1.0 + +Popup { + id: popup + + property int currentIndex: -1 + property string completerName + property var completer + + function up() { + currentIndex = currentIndex - 1; + if (currentIndex == -2) + currentIndex = repeater.count - 1; + + } + + function down() { + currentIndex = currentIndex + 1; + if (currentIndex >= repeater.count) + currentIndex = -1; + + } + + function currentCompletion() { + if (currentIndex > -1 && currentIndex < repeater.count) + return completer.completionAt(currentIndex); + else + return null; + } + + onCompleterNameChanged: { + if (completerName) + completer = TimelineManager.timeline.input.completerFor(completerName); + + } + padding: 0 + onAboutToShow: currentIndex = -1 + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + Repeater { + id: repeater + + model: completer + + delegate: Rectangle { + color: model.index == popup.currentIndex ? colors.window : colors.base + height: del.implicitHeight + 4 + width: del.implicitWidth + 4 + + RowLayout { + id: del + + anchors.centerIn: parent + + Avatar { + height: 24 + width: 24 + displayName: model.displayName + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + } + + Label { + text: model.displayName + color: colors.text + } + + } + + } + + } + + } + + enter: Transition { + NumberAnimation { + property: "opacity" + from: 0 + to: 1 + duration: 100 + } + + } + + exit: Transition { + NumberAnimation { + property: "opacity" + from: 1 + to: 0 + duration: 100 + } + + } + + background: Rectangle { + color: colors.base + implicitHeight: popup.contentHeight + implicitWidth: popup.contentWidth + } + +} diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml
index a5c84a17..a4a47a3e 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml
@@ -68,28 +68,72 @@ Rectangle { TextArea { id: textArea + property int completerTriggeredAt: -1 + placeholderText: qsTr("Write a message...") placeholderTextColor: colors.buttonText color: colors.text wrapMode: TextEdit.Wrap onTextChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text) - onCursorPositionChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text) + onCursorPositionChanged: { + TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text); + if (cursorPosition < completerTriggeredAt) { + completerTriggeredAt = -1; + popup.close(); + } + } 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. + Keys.onShortcutOverride: event.accepted = (completerTriggeredAt != -1 && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter)) Keys.onPressed: { if (event.matches(StandardKey.Paste)) { TimelineManager.timeline.input.paste(false); event.accepted = true; + } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_U) { + textArea.clear(); + } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) { + textArea.text = TimelineManager.timeline.input.previousText(); + } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_N) { + textArea.text = TimelineManager.timeline.input.nextText(); + } else if (event.key == Qt.Key_At) { + completerTriggeredAt = cursorPosition + 1; + popup.completerName = "user"; + popup.open(); + } else if (event.key == Qt.Key_Escape && popup.opened) { + completerTriggeredAt = -1; + event.accepted = true; + popup.close(); + } else if (event.matches(StandardKey.InsertParagraphSeparator) && popup.opened) { + var currentCompletion = popup.currentCompletion(); + popup.close(); + if (currentCompletion) { + textArea.remove(completerTriggeredAt - 1, cursorPosition); + textArea.insert(cursorPosition, currentCompletion); + event.accepted = true; + return ; + } + } else if (event.key == Qt.Key_Tab && popup.opened) { + event.accepted = true; + popup.down(); + } 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.matches(StandardKey.InsertParagraphSeparator)) { TimelineManager.timeline.input.send(); textArea.clear(); event.accepted = true; - } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_U) - textArea.clear(); - else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) - textArea.text = TimelineManager.timeline.input.previousText(); - else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_N) - textArea.text = TimelineManager.timeline.input.nextText(); + } + } + + Completer { + id: popup + + x: textArea.positionToRectangle(textArea.completerTriggeredAt).x + y: textArea.positionToRectangle(textArea.completerTriggeredAt).y - height } Connections { diff --git a/resources/res.qrc b/resources/res.qrc
index 02f31498..a01907ec 100644 --- a/resources/res.qrc +++ b/resources/res.qrc
@@ -123,21 +123,22 @@ <file>qtquickcontrols2.conf</file> <file>qml/TimelineView.qml</file> - <file>qml/TopBar.qml</file> - <file>qml/MessageView.qml</file> - <file>qml/MessageInput.qml</file> - <file>qml/TypingIndicator.qml</file> - <file>qml/ReplyPopup.qml</file> <file>qml/ActiveCallBar.qml</file> <file>qml/Avatar.qml</file> + <file>qml/Completer.qml</file> + <file>qml/EncryptionIndicator.qml</file> <file>qml/ImageButton.qml</file> <file>qml/MatrixText.qml</file> + <file>qml/MessageInput.qml</file> + <file>qml/MessageView.qml</file> <file>qml/NhekoBusyIndicator.qml</file> - <file>qml/StatusIndicator.qml</file> - <file>qml/EncryptionIndicator.qml</file> <file>qml/Reactions.qml</file> + <file>qml/ReplyPopup.qml</file> <file>qml/ScrollHelper.qml</file> + <file>qml/StatusIndicator.qml</file> <file>qml/TimelineRow.qml</file> + <file>qml/TopBar.qml</file> + <file>qml/TypingIndicator.qml</file> <file>qml/VideoCall.qml</file> <file>qml/emoji/EmojiButton.qml</file> <file>qml/emoji/EmojiPicker.qml</file> diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index 1eaaaa64..82649faa 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp
@@ -163,6 +163,12 @@ InputBar::nextText() return text(); } +QObject * +InputBar::completerFor(QString completerName) +{ + return nullptr; +} + void InputBar::send() { diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index 5e66e86f..939e8dad 100644 --- a/src/timeline/InputBar.h +++ b/src/timeline/InputBar.h
@@ -41,6 +41,8 @@ public slots: bool uploading() const { return uploading_; } void callButton(); + QObject *completerFor(QString completerName); + private slots: void startTyping(); void stopTyping();