summary refs log tree commit diff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--resources/qml/ActiveCallBar.qml1
-rw-r--r--resources/qml/Completer.qml119
-rw-r--r--resources/qml/MessageView.qml1
-rw-r--r--resources/qml/TimelineView.qml2
-rw-r--r--src/CompletionProxyModel.h89
5 files changed, 144 insertions, 68 deletions
diff --git a/resources/qml/ActiveCallBar.qml b/resources/qml/ActiveCallBar.qml
index cbe36e6d..49c0e255 100644
--- a/resources/qml/ActiveCallBar.qml
+++ b/resources/qml/ActiveCallBar.qml
@@ -53,7 +53,6 @@ Rectangle {
 
         Connections {
             target: TimelineManager
-
             onCallStateChanged: {
                 switch (state) {
                 case WebRTCState.INITIATING:
diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml
index dda5b896..7074de81 100644
--- a/resources/qml/Completer.qml
+++ b/resources/qml/Completer.qml
@@ -9,105 +9,120 @@ Popup {
     property int currentIndex: -1
     property string completerName
     property var completer
+    property bool bottomToTop: true
 
     function up() {
+        if (bottomToTop)
+            down_();
+        else
+            up_();
+    }
+
+    function down() {
+        if (bottomToTop)
+            up_();
+        else
+            down_();
+    }
+
+    function up_() {
         currentIndex = currentIndex - 1;
         if (currentIndex == -2)
-            currentIndex = repeater.count - 1;
+            currentIndex = listView.count - 1;
 
     }
 
-    function down() {
+    function down_() {
         currentIndex = currentIndex + 1;
-        if (currentIndex >= repeater.count)
+        if (currentIndex >= listView.count)
             currentIndex = -1;
 
     }
 
     function currentCompletion() {
-        if (currentIndex > -1 && currentIndex < repeater.count)
+        if (currentIndex > -1 && currentIndex < listView.count)
             return completer.completionAt(currentIndex);
         else
             return null;
     }
 
     onCompleterNameChanged: {
-        if (completerName)
+        if (completerName) {
             completer = TimelineManager.timeline.input.completerFor(completerName);
-        else
+            completer.setSearchString("");
+        } else {
             completer = undefined;
+        }
     }
     padding: 0
     onAboutToShow: currentIndex = -1
+    height: listView.contentHeight
 
     Connections {
         onTimelineChanged: completer = null
         target: TimelineManager
     }
 
-    ColumnLayout {
-        anchors.fill: parent
-        spacing: 0
-
-        Repeater {
-            id: repeater
-
-            model: completer
+    ListView {
+        id: listView
 
-            delegate: Rectangle {
-                color: model.index == popup.currentIndex ? colors.window : colors.alternateBase
-                height: chooser.childrenRect.height + 4
-                width: chooser.childrenRect.width + 4
+        anchors.fill: parent
+        implicitWidth: contentItem.childrenRect.width
+        model: completer
+        verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom
 
-                DelegateChooser {
-                    id: chooser
+        delegate: Rectangle {
+            color: model.index == popup.currentIndex ? colors.highlight : colors.base
+            height: chooser.childrenRect.height + 4
+            implicitWidth: chooser.childrenRect.width + 4
 
-                    roleValue: popup.completerName
-                    anchors.centerIn: parent
+            DelegateChooser {
+                id: chooser
 
-                    DelegateChoice {
-                        roleValue: "user"
+                roleValue: popup.completerName
+                anchors.centerIn: parent
 
-                        RowLayout {
-                            id: del
+                DelegateChoice {
+                    roleValue: "user"
 
-                            anchors.centerIn: parent
+                    RowLayout {
+                        id: del
 
-                            Avatar {
-                                height: 24
-                                width: 24
-                                displayName: model.displayName
-                                url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
-                            }
+                        anchors.centerIn: parent
 
-                            Label {
-                                text: model.displayName
-                                color: colors.text
-                            }
+                        Avatar {
+                            height: 24
+                            width: 24
+                            displayName: model.displayName
+                            url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
+                        }
 
+                        Label {
+                            text: model.displayName
+                            color: model.index == popup.currentIndex ? colors.highlightedText : colors.text
                         }
 
                     }
 
-                    DelegateChoice {
-                        roleValue: "emoji"
+                }
 
-                        RowLayout {
-                            id: del
+                DelegateChoice {
+                    roleValue: "emoji"
 
-                            anchors.centerIn: parent
+                    RowLayout {
+                        id: del
 
-                            Label {
-                                text: model.unicode
-                                color: colors.text
-                                font: Settings.emojiFont
-                            }
+                        anchors.centerIn: parent
 
-                            Label {
-                                text: model.shortName
-                                color: colors.text
-                            }
+                        Label {
+                            text: model.unicode
+                            color: model.index == popup.currentIndex ? colors.highlightedText : colors.text
+                            font: Settings.emojiFont
+                        }
 
+                        Label {
+                            text: model.shortName
+                            color: model.index == popup.currentIndex ? colors.highlightedText : colors.text
                         }
 
                     }
@@ -141,7 +156,7 @@ Popup {
     }
 
     background: Rectangle {
-        color: colors.alternateBase
+        color: colors.base
         implicitHeight: popup.contentHeight
         implicitWidth: popup.contentWidth
     }
diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index 71c85276..679c1f50 100644
--- a/resources/qml/MessageView.qml
+++ b/resources/qml/MessageView.qml
@@ -182,7 +182,6 @@ ListView {
 
         Connections {
             target: chat
-
             onMovementEnded: {
                 if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
                     chat.model.currentIndex = index;
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index c23564c1..ac998a81 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -131,7 +131,6 @@ Page {
 
         Connections {
             target: TimelineManager
-
             onNewDeviceVerificationRequest: {
                 var dialog = deviceVerificationDialog.createObject(timelineRoot, {
                     "flow": flow
@@ -142,7 +141,6 @@ Page {
 
         Connections {
             target: TimelineManager.timeline
-            
             onOpenProfile: {
                 var userProfile = userProfileComponent.createObject(timelineRoot, {
                     "profile": profile
diff --git a/src/CompletionProxyModel.h b/src/CompletionProxyModel.h
index fa22c61e..ba28e84c 100644
--- a/src/CompletionProxyModel.h
+++ b/src/CompletionProxyModel.h
@@ -43,29 +43,94 @@ struct trie
                                 return ret;
                         else {
                                 auto temp = t.valuesAndSubvalues(limit - ret.size());
-                                ret.insert(ret.end(), temp.begin(), temp.end());
+                                for (auto &&v : temp) {
+                                        if (ret.size() >= limit)
+                                                return ret;
+
+                                        if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
+                                                ret.push_back(std::move(v));
+                                        }
+                                }
                         }
                 }
 
                 return ret;
         }
 
-        std::vector<Value> search(const QVector<Key> &keys, size_t limit) const
+        std::vector<Value> search(const QVector<Key> &keys,
+                                  size_t limit,
+                                  size_t max_distance = 2) const
         {
                 std::vector<Value> ret;
-                auto t = this;
-                int i  = 0;
-                for (; i < (int)keys.size(); i++) {
-                        if (auto e = t->next.find(keys[i]); e != t->next.end()) {
-                                t = &e->second;
-                        } else {
-                                t = nullptr;
-                                break;
+                if (!limit)
+                        return ret;
+
+                auto append = [&ret, limit](std::vector<Value> &&in) {
+                        for (auto &&v : in) {
+                                if (ret.size() >= limit)
+                                        return;
+
+                                if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
+                                        ret.push_back(std::move(v));
+                                }
+                        }
+                };
+
+                {
+                        auto t = this;
+                        int i  = 0;
+                        for (; i < (int)keys.size(); i++) {
+                                if (auto e = t->next.find(keys[i]); e != t->next.end()) {
+                                        t = &e->second;
+                                } else {
+                                        t = nullptr;
+                                        break;
+                                }
+                        }
+
+                        if (t) {
+                                ret = t->valuesAndSubvalues(limit);
                         }
                 }
 
-                if (t) {
-                        ret = t->valuesAndSubvalues(limit);
+                if (max_distance && keys.size() < static_cast<int>(limit) && keys.size() > 1) {
+                        max_distance -= 1;
+
+                        // swap chars case
+                        if (keys.size() >= 2) {
+                                auto t = this;
+                                for (int i = 1; i >= 0; i--) {
+                                        if (auto e = t->next.find(keys[i]); e != t->next.end()) {
+                                                t = &e->second;
+                                        } else {
+                                                t = nullptr;
+                                                break;
+                                        }
+                                }
+
+                                if (t) {
+                                        append(t->search(
+                                          keys.mid(2), (limit - ret.size()) * 2, max_distance));
+                                }
+                        }
+
+                        // delete character case
+                        append(this->search(keys.mid(1), (limit - ret.size()) * 2, max_distance));
+
+                        // substitute and insert cases
+                        for (const auto &[k, t] : this->next) {
+                                if (k == keys[0] || ret.size() >= limit)
+                                        break;
+
+                                // substitute
+                                append(this->search(keys.mid(1), limit - ret.size(), max_distance));
+
+                                if (ret.size() >= limit)
+                                        break;
+
+                                // insert
+                                append(this->search(keys, limit - ret.size(), max_distance));
+                        }
                 }
 
                 return ret;