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() { - currentIndex = currentIndex - 1; - if (currentIndex == -2) - currentIndex = repeater.count - 1; - + if (bottomToTop) + down_(); + else + up_(); } function down() { + if (bottomToTop) + up_(); + else + down_(); + } + + function up_() { + currentIndex = currentIndex - 1; + if (currentIndex == -2) + currentIndex = listView.count - 1; + + } + + 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 { + ListView { + id: listView + anchors.fill: parent - spacing: 0 + implicitWidth: contentItem.childrenRect.width + model: completer + verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom - Repeater { - id: repeater + delegate: Rectangle { + color: model.index == popup.currentIndex ? colors.highlight : colors.base + height: chooser.childrenRect.height + 4 + implicitWidth: chooser.childrenRect.width + 4 - model: completer + DelegateChooser { + id: chooser - delegate: Rectangle { - color: model.index == popup.currentIndex ? colors.window : colors.alternateBase - height: chooser.childrenRect.height + 4 - width: 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 - - 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 - } + 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 search(const QVector &keys, size_t limit) const + std::vector search(const QVector &keys, + size_t limit, + size_t max_distance = 2) const { std::vector 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 &&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(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;