From 042880ba2df913916b2cc77f7bb677e07bfd2c58 Mon Sep 17 00:00:00 2001
From: YuSanka <yusanka@gmail.com>
Date: Tue, 31 Mar 2020 22:46:12 +0200
Subject: [PATCH] Search: Implemented highlighting of a letters from the search
 string

---
 src/imgui/imconfig.h              | 11 +++-
 src/imgui/imgui_draw.cpp          | 14 +++++
 src/slic3r/GUI/SearchComboBox.cpp | 98 ++++++++++++++++++++++++++++---
 src/slic3r/GUI/SearchComboBox.hpp |  8 ++-
 4 files changed, 118 insertions(+), 13 deletions(-)

diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h
index 09bfd16c9..26dccd5c3 100644
--- a/src/imgui/imconfig.h
+++ b/src/imgui/imconfig.h
@@ -97,9 +97,14 @@
 //#define IMGUI_DEBUG_PARANOID
 
 //---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files.
-/*
+
 namespace ImGui
 {
-    void MyFunction(const char* name, const MyMatrix44& v);
+    // Special ASCII characters STX and ETX are used here as markup symbols for tokens to be highlighted.
+    const char ColorMarkerStart = 0x2; // STX
+    const char ColorMarkerEnd   = 0x3; // ETX
+
+//    void MyFunction(const char* name, const MyMatrix44& v);
+
 }
-*/
+
diff --git a/src/imgui/imgui_draw.cpp b/src/imgui/imgui_draw.cpp
index 4bb91ccfe..bee1fdfa7 100644
--- a/src/imgui/imgui_draw.cpp
+++ b/src/imgui/imgui_draw.cpp
@@ -33,6 +33,7 @@ Index of this file:
 #define IMGUI_DEFINE_MATH_OPERATORS
 #endif
 #include "imgui_internal.h"
+#include "imconfig.h"
 
 #include <stdio.h>      // vsnprintf, sscanf, printf
 #if !defined(alloca)
@@ -2991,6 +2992,8 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col
     ImDrawIdx* idx_write = draw_list->_IdxWritePtr;
     unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx;
 
+    ImU32 defaultCol = col;
+
     while (s < text_end)
     {
         if (word_wrap_enabled)
@@ -3019,6 +3022,17 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col
             }
         }
 
+        if (*s == ImGui::ColorMarkerStart) {
+            col = ImGui::GetColorU32(ImGuiCol_ButtonHovered);
+            s += 1;
+        }
+        else if (*s == ImGui::ColorMarkerEnd) {
+            col = defaultCol;
+            s += 1;
+            if (s == text_end)
+                break;
+        }
+
         // Decode and advance source
         unsigned int c = (unsigned int)*s;
         if (c < 0x80)
diff --git a/src/slic3r/GUI/SearchComboBox.cpp b/src/slic3r/GUI/SearchComboBox.cpp
index efe46073b..1e1ab738f 100644
--- a/src/slic3r/GUI/SearchComboBox.cpp
+++ b/src/slic3r/GUI/SearchComboBox.cpp
@@ -23,14 +23,15 @@
 #define FTS_FUZZY_MATCH_IMPLEMENTATION
 #include "fts_fuzzy_match.h"
 
+#include "imgui/imconfig.h"
+
 using boost::optional;
 
 namespace Slic3r {
 namespace GUI {
 
-bool SearchOptions::Option::containes(const wxString& search_) const
+bool SearchOptions::Option::fuzzy_match_simple(char const * search_pattern) const
 {
-    char const* search_pattern = search_.utf8_str();
     char const* opt_key_str    = opt_key.c_str();
     char const* label_str      = label.utf8_str();
 
@@ -38,9 +39,20 @@ bool SearchOptions::Option::containes(const wxString& search_) const
             fts::fuzzy_match_simple(search_pattern, opt_key_str )   ; 
 }
 
-bool SearchOptions::Option::is_matched_option(const wxString& search, int& outScore)
+bool SearchOptions::Option::fuzzy_match_simple(const wxString& search) const
 {
     char const* search_pattern = search.utf8_str();
+    return fuzzy_match_simple(search_pattern);
+}
+
+bool SearchOptions::Option::fuzzy_match_simple(const std::string& search) const
+{
+    char const* search_pattern = search.c_str();
+    return fuzzy_match_simple(search_pattern);
+}
+
+bool SearchOptions::Option::fuzzy_match(char const* search_pattern, int& outScore)
+{
     char const* opt_key_str    = opt_key.c_str();
     char const* label_str      = label.utf8_str();
 
@@ -48,6 +60,18 @@ bool SearchOptions::Option::is_matched_option(const wxString& search, int& outSc
             fts::fuzzy_match(search_pattern, opt_key_str , outScore)   ); 
 }
 
+bool SearchOptions::Option::fuzzy_match(const wxString& search, int& outScore)
+{
+    char const* search_pattern = search.utf8_str();
+    return fuzzy_match(search_pattern, outScore); 
+}
+
+bool SearchOptions::Option::fuzzy_match(const std::string& search, int& outScore)
+{
+    char const* search_pattern = search.c_str();
+    return fuzzy_match(search_pattern, outScore);
+}
+
 void SearchOptions::Filter::get_label(const char** out_text) const
 {
     *out_text = label.utf8_str();
@@ -91,15 +115,73 @@ void SearchOptions::append_options(DynamicPrintConfig* config, Preset::Type type
     }
 }
 
+// Wrap a string with ColorMarkerStart and ColorMarkerEnd symbols
+static wxString wrap_string(const wxString& str)
+{
+    return wxString::Format("%c%s%c", ImGui::ColorMarkerStart, str, ImGui::ColorMarkerEnd);
+}
+
+// Mark a string using ColorMarkerStart and ColorMarkerEnd symbols
+static void mark_string(wxString& str, const wxString& search_str)
+{
+    // Try to find whole search string
+    if (str.Replace(search_str, wrap_string(search_str), false) != 0)
+        return;
+
+    // Try to find whole capitalized search string
+    wxString search_str_capitalized = search_str.Capitalize();
+    if (str.Replace(search_str_capitalized, wrap_string(search_str_capitalized), false) != 0)
+        return;
+
+    // if search string is just a one letter now, there is no reason to continue 
+    if (search_str.Len()==1)
+        return;
+
+    // Split a search string for two strings (string without last letter and last letter)
+    // and repeat a function with new search strings
+    mark_string(str, search_str.SubString(0, search_str.Len() - 2));
+    mark_string(str, search_str.Last());
+}
+
+// clear marked string from a redundant use of ColorMarkers
+static void clear_marked_string(wxString& str)
+{
+    // Check if the string has a several ColorMarkerStart in a row and replace them to only one, if any
+    wxString delete_string = wxString::Format("%c%c", ImGui::ColorMarkerStart, ImGui::ColorMarkerStart);
+    if (str.Replace(delete_string, ImGui::ColorMarkerStart, true) != 0) {
+        // If there were several ColorMarkerStart in a row, it means there should be a several ColorMarkerStop in a row,
+        // replace them to only one
+        delete_string = wxString::Format("%c%c", ImGui::ColorMarkerEnd, ImGui::ColorMarkerEnd);
+        str.Replace(delete_string, ImGui::ColorMarkerEnd, true);
+    }
+
+    // And we should to remove redundant ColorMarkers, if they are in "End, Start" sequence in a row
+    delete_string = wxString::Format("%c%c", ImGui::ColorMarkerEnd, ImGui::ColorMarkerStart);
+    str.Replace(delete_string, wxEmptyString, true);
+}
+
 void SearchOptions::apply_filters(const std::string& search)
 {
     clear_filters();
 
     bool full_list = search.empty();
-    for (size_t i=0; i < options.size(); i++) {
-        int score=0;
-        if (full_list || options[i].is_matched_option(search, score))
-            filters.emplace_back(Filter{ options[i].label, i, score });
+
+    for (size_t i=0; i < options.size(); i++)
+    {
+        if (full_list) {
+            filters.emplace_back(Filter{ options[i].label, i, 0 });
+            continue;
+        }
+
+        int score = 0;
+        if (options[i].fuzzy_match_simple(search)/*fuzzy_match(search, score)*/)
+        {
+            wxString label = options[i].label;
+            mark_string(label, from_u8(search));
+            clear_marked_string(label);
+
+            filters.emplace_back(Filter{ label, i, score });
+        }
     }
 
     if (!full_list)
@@ -243,7 +325,7 @@ void SearchComboBox::append_items(const wxString& search)
 */
 
     for (const SearchOptions::Option& option : search_list.options)
-        if (option.containes(search))
+        if (option.fuzzy_match_simple(search))
             append(option.label, (void*)&option);
 
     SuppressUpdate su(this);
diff --git a/src/slic3r/GUI/SearchComboBox.hpp b/src/slic3r/GUI/SearchComboBox.hpp
index 6b93ce9ac..294395b72 100644
--- a/src/slic3r/GUI/SearchComboBox.hpp
+++ b/src/slic3r/GUI/SearchComboBox.hpp
@@ -35,8 +35,12 @@ public:
         Preset::Type    type {Preset::TYPE_INVALID};
         // wxString     grope;
 
-        bool containes(const wxString& search) const;
-        bool is_matched_option(const wxString &search, int &outScore);
+        bool fuzzy_match_simple(char const *search_pattern) const;
+        bool fuzzy_match_simple(const wxString& search) const;
+        bool fuzzy_match_simple(const std::string &search) const;
+        bool fuzzy_match(char const *search_pattern, int &outScore);
+        bool fuzzy_match(const wxString &search, int &outScore);
+        bool fuzzy_match(const std::string &search, int &outScore);
     };
     std::vector<Option> options {};