Merge remote-tracking branch 'origin/master' into ys_layouts
This commit is contained in:
commit
53516c8086
13 changed files with 739 additions and 711 deletions
1
deps/wxWidgets/wxWidgets.cmake
vendored
1
deps/wxWidgets/wxWidgets.cmake
vendored
|
@ -19,6 +19,7 @@ prusaslicer_add_cmake_project(wxWidgets
|
|||
-DwxBUILD_PRECOMP=ON
|
||||
${_wx_toolkit}
|
||||
"-DCMAKE_DEBUG_POSTFIX:STRING="
|
||||
-DwxBUILD_DEBUG_LEVEL=0
|
||||
-DwxUSE_DETECT_SM=OFF
|
||||
-DwxUSE_UNICODE=ON
|
||||
-DwxUSE_OPENGL=ON
|
||||
|
|
|
@ -67,7 +67,7 @@ struct ArrangeParams {
|
|||
|
||||
/// The minimum distance which is allowed for any
|
||||
/// pair of items on the print bed in any direction.
|
||||
coord_t min_obj_distance = 0.;
|
||||
coord_t min_obj_distance = 0;
|
||||
|
||||
/// The accuracy of optimization.
|
||||
/// Goes from 0.0 to 1.0 and scales performance as well
|
||||
|
|
|
@ -134,11 +134,8 @@ wxFont get_default_font_for_dpi(int dpi)
|
|||
NONCLIENTMETRICS nm;
|
||||
memset(&nm, 0, sizeof(NONCLIENTMETRICS));
|
||||
nm.cbSize = sizeof(NONCLIENTMETRICS);
|
||||
if (SystemParametersInfoForDpi_fn(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &nm, 0, dpi)) {
|
||||
wxNativeFontInfo info;
|
||||
info.lf = nm.lfMessageFont;
|
||||
return wxFont(info);
|
||||
}
|
||||
if (SystemParametersInfoForDpi_fn(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &nm, 0, dpi))
|
||||
return wxFont(wxNativeFontInfo(nm.lfMessageFont));
|
||||
}
|
||||
// Then try to guesstimate the font DPI scaling on Windows 8.
|
||||
// Let's hope that the font returned by the SystemParametersInfo(), which is used by wxWidgets internally, makes sense.
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
|
@ -1098,7 +1099,7 @@ void Sidebar::search()
|
|||
void Sidebar::jump_to_option(size_t selected)
|
||||
{
|
||||
const Search::Option& opt = p->searcher.get_option(selected);
|
||||
wxGetApp().get_tab(opt.type)->activate_option(opt.opt_key, opt.category);
|
||||
wxGetApp().get_tab(opt.type)->activate_option(boost::nowide::narrow(opt.opt_key), boost::nowide::narrow(opt.category));
|
||||
|
||||
// Switch to the Settings NotePad
|
||||
wxGetApp().mainframe->select_tab();
|
||||
|
|
|
@ -125,6 +125,7 @@ public:
|
|||
TYPE_FILAMENT,
|
||||
TYPE_SLA_MATERIAL,
|
||||
TYPE_PRINTER,
|
||||
TYPE_COUNT,
|
||||
};
|
||||
|
||||
Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {}
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
|
@ -23,81 +25,24 @@ using GUI::into_u8;
|
|||
|
||||
namespace Search {
|
||||
|
||||
static std::map<Preset::Type, std::string> NameByType = {
|
||||
{ Preset::TYPE_PRINT, L("Print") },
|
||||
{ Preset::TYPE_FILAMENT, L("Filament") },
|
||||
{ Preset::TYPE_SLA_MATERIAL, L("Material") },
|
||||
{ Preset::TYPE_SLA_PRINT, L("Print") },
|
||||
{ Preset::TYPE_PRINTER, L("Printer") }
|
||||
};
|
||||
|
||||
FMFlag Option::fuzzy_match_simple(char const * search_pattern) const
|
||||
static const std::vector<std::wstring>& NameByType()
|
||||
{
|
||||
return fts::fuzzy_match_simple(search_pattern, label_local.utf8_str()) ? fmLabelLocal :
|
||||
fts::fuzzy_match_simple(search_pattern, group_local.utf8_str()) ? fmGroupLocal :
|
||||
fts::fuzzy_match_simple(search_pattern, category_local.utf8_str()) ? fmCategoryLocal :
|
||||
fts::fuzzy_match_simple(search_pattern, opt_key.c_str()) ? fmOptKey :
|
||||
fts::fuzzy_match_simple(search_pattern, label.utf8_str()) ? fmLabel :
|
||||
fts::fuzzy_match_simple(search_pattern, group.utf8_str()) ? fmGroup :
|
||||
fts::fuzzy_match_simple(search_pattern, category.utf8_str()) ? fmCategory : fmUndef ;
|
||||
}
|
||||
|
||||
FMFlag Option::fuzzy_match_simple(const wxString& search) const
|
||||
{
|
||||
char const* search_pattern = search.utf8_str();
|
||||
return fuzzy_match_simple(search_pattern);
|
||||
}
|
||||
|
||||
FMFlag Option::fuzzy_match_simple(const std::string& search) const
|
||||
{
|
||||
char const* search_pattern = search.c_str();
|
||||
return fuzzy_match_simple(search_pattern);
|
||||
}
|
||||
|
||||
FMFlag Option::fuzzy_match(char const* search_pattern, int& outScore) const
|
||||
{
|
||||
FMFlag flag = fmUndef;
|
||||
int score;
|
||||
|
||||
if (fts::fuzzy_match(search_pattern, label_local.utf8_str(), score) && outScore < score) {
|
||||
outScore = score; flag = fmLabelLocal ; }
|
||||
if (fts::fuzzy_match(search_pattern, group_local.utf8_str(), score) && outScore < score) {
|
||||
outScore = score; flag = fmGroupLocal ; }
|
||||
if (fts::fuzzy_match(search_pattern, category_local.utf8_str(), score) && outScore < score) {
|
||||
outScore = score; flag = fmCategoryLocal; }
|
||||
if (fts::fuzzy_match(search_pattern, opt_key.c_str(), score) && outScore < score) {
|
||||
outScore = score; flag = fmOptKey ; }
|
||||
if (fts::fuzzy_match(search_pattern, label.utf8_str(), score) && outScore < score) {
|
||||
outScore = score; flag = fmLabel ; }
|
||||
if (fts::fuzzy_match(search_pattern, group.utf8_str(), score) && outScore < score) {
|
||||
outScore = score; flag = fmGroup ; }
|
||||
if (fts::fuzzy_match(search_pattern, category.utf8_str(), score) && outScore < score) {
|
||||
outScore = score; flag = fmCategory ; }
|
||||
|
||||
return flag;
|
||||
}
|
||||
|
||||
FMFlag Option::fuzzy_match(const wxString& search, int& outScore) const
|
||||
{
|
||||
char const* search_pattern = search.utf8_str();
|
||||
return fuzzy_match(search_pattern, outScore);
|
||||
}
|
||||
|
||||
FMFlag Option::fuzzy_match(const std::string& search, int& outScore) const
|
||||
{
|
||||
char const* search_pattern = search.c_str();
|
||||
return fuzzy_match(search_pattern, outScore);
|
||||
}
|
||||
|
||||
void FoundOption::get_label(const char** out_text) const
|
||||
{
|
||||
*out_text = label.utf8_str();
|
||||
static std::vector<std::wstring> data;
|
||||
if (data.empty()) {
|
||||
data.assign(Preset::TYPE_COUNT, std::wstring());
|
||||
data[Preset::TYPE_PRINT ] = _L("Print" ).ToStdWstring();
|
||||
data[Preset::TYPE_FILAMENT ] = _L("Filament" ).ToStdWstring();
|
||||
data[Preset::TYPE_SLA_MATERIAL ] = _L("Material" ).ToStdWstring();
|
||||
data[Preset::TYPE_SLA_PRINT ] = _L("Print" ).ToStdWstring();
|
||||
data[Preset::TYPE_PRINTER ] = _L("Printer" ).ToStdWstring();
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
void FoundOption::get_marked_label_and_tooltip(const char** label_, const char** tooltip_) const
|
||||
{
|
||||
*label_ = marked_label.utf8_str();
|
||||
*tooltip_ = tooltip.utf8_str();
|
||||
*label_ = marked_label.c_str();
|
||||
*tooltip_ = tooltip.c_str();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
|
@ -121,14 +66,18 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty
|
|||
return;
|
||||
|
||||
wxString suffix;
|
||||
if (gc.category == "Machine limits")
|
||||
wxString suffix_local;
|
||||
if (gc.category == "Machine limits") {
|
||||
suffix = opt_key.back()=='1' ? L("Stealth") : L("Normal");
|
||||
suffix_local = " " + _(suffix);
|
||||
suffix = " " + suffix;
|
||||
}
|
||||
|
||||
if (!label.IsEmpty())
|
||||
options.emplace_back(Option{ opt_key, type,
|
||||
label+ " " + suffix, _(label)+ " " + _(suffix),
|
||||
gc.group, _(gc.group),
|
||||
gc.category, _(gc.category) });
|
||||
options.emplace_back(Option{ boost::nowide::widen(opt_key), type,
|
||||
(label + suffix).ToStdWstring(), (_(label) + suffix_local).ToStdWstring(),
|
||||
gc.group.ToStdWstring(), _(gc.group).ToStdWstring(),
|
||||
gc.category.ToStdWstring(), _(gc.category).ToStdWstring() });
|
||||
};
|
||||
|
||||
for (std::string opt_key : config->keys())
|
||||
|
@ -157,7 +106,7 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty
|
|||
emplace(opt_key, label);
|
||||
else
|
||||
for (int i = 0; i < cnt; ++i)
|
||||
emplace(opt_key + "#" + std::to_string(i), label);
|
||||
emplace(opt_key + "[" + std::to_string(i) + "]", label);
|
||||
|
||||
/*const GroupAndCategory& gc = groups_and_categories[opt_key];
|
||||
if (gc.group.IsEmpty() || gc.category.IsEmpty())
|
||||
|
@ -178,41 +127,32 @@ static wxString wrap_string(const wxString& str)
|
|||
}
|
||||
|
||||
// Mark a string using ColorMarkerStart and ColorMarkerEnd symbols
|
||||
static void mark_string(wxString& str, const wxString& search_str)
|
||||
static std::wstring mark_string(const std::wstring &str, const std::vector<uint16_t> &matches)
|
||||
{
|
||||
// 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);
|
||||
str.Replace(delete_string, ImGui::ColorMarkerStart, true);
|
||||
// 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);
|
||||
std::wstring out;
|
||||
if (matches.empty())
|
||||
out = str;
|
||||
else {
|
||||
out.reserve(str.size() * 2);
|
||||
if (matches.front() > 0)
|
||||
out += str.substr(0, matches.front());
|
||||
for (size_t i = 0;;) {
|
||||
// Find the longest string of successive indices.
|
||||
size_t j = i + 1;
|
||||
while (j < matches.size() && matches[j] == matches[j - 1] + 1)
|
||||
++ j;
|
||||
out += ImGui::ColorMarkerStart;
|
||||
out += str.substr(matches[i], matches[j - 1] - matches[i] + 1);
|
||||
out += ImGui::ColorMarkerEnd;
|
||||
if (j == matches.size()) {
|
||||
out += str.substr(matches[j - 1] + 1);
|
||||
break;
|
||||
}
|
||||
out += str.substr(matches[j - 1] + 1, matches[j] - matches[j - 1] - 1);
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool OptionsSearcher::search()
|
||||
|
@ -220,6 +160,20 @@ bool OptionsSearcher::search()
|
|||
return search(search_line, true);
|
||||
}
|
||||
|
||||
static bool fuzzy_match(const std::wstring &search_pattern, const std::wstring &label, int& out_score, std::vector<uint16_t> &out_matches)
|
||||
{
|
||||
uint16_t matches[fts::max_matches + 1]; // +1 for the stopper
|
||||
int score;
|
||||
if (fts::fuzzy_match(search_pattern.c_str(), label.c_str(), score, matches)) {
|
||||
size_t cnt = 0;
|
||||
for (; matches[cnt] != fts::stopper; ++cnt);
|
||||
out_matches.assign(matches, matches + cnt);
|
||||
out_score = score;
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OptionsSearcher::search(const std::string& search, bool force/* = false*/)
|
||||
{
|
||||
if (search_line == search && !force)
|
||||
|
@ -228,54 +182,89 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/)
|
|||
found.clear();
|
||||
|
||||
bool full_list = search.empty();
|
||||
wxString sep = " : ";
|
||||
std::wstring sep = L" : ";
|
||||
const std::vector<std::wstring>& name_by_type = NameByType();
|
||||
|
||||
auto get_label = [this, sep](const Option& opt)
|
||||
auto get_label = [this, &name_by_type, &sep](const Option& opt)
|
||||
{
|
||||
wxString label;
|
||||
if (view_params.type)
|
||||
label += _(NameByType[opt.type]) + sep;
|
||||
if (view_params.category)
|
||||
label += opt.category_local + sep;
|
||||
if (view_params.group)
|
||||
label += opt.group_local + sep;
|
||||
label += opt.label_local;
|
||||
return label;
|
||||
std::wstring out;
|
||||
const std::wstring *prev = nullptr;
|
||||
for (const std::wstring * const s : {
|
||||
view_params.type ? &(name_by_type[opt.type]) : nullptr,
|
||||
view_params.category ? &opt.category_local : nullptr,
|
||||
view_params.group ? &opt.group_local : nullptr,
|
||||
&opt.label_local })
|
||||
if (s != nullptr && (prev == nullptr || *prev != *s)) {
|
||||
if (! out.empty())
|
||||
out += sep;
|
||||
out += *s;
|
||||
prev = s;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
auto get_tooltip = [this, sep](const Option& opt)
|
||||
auto get_label_english = [this, &name_by_type, &sep](const Option& opt)
|
||||
{
|
||||
return _(NameByType[opt.type]) + sep +
|
||||
std::wstring out;
|
||||
const std::wstring*prev = nullptr;
|
||||
for (const std::wstring * const s : {
|
||||
view_params.type ? &name_by_type[opt.type] : nullptr,
|
||||
view_params.category ? &opt.category : nullptr,
|
||||
view_params.group ? &opt.group : nullptr,
|
||||
&opt.label })
|
||||
if (s != nullptr && (prev == nullptr || *prev != *s)) {
|
||||
if (! out.empty())
|
||||
out += sep;
|
||||
out += *s;
|
||||
prev = s;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
auto get_tooltip = [this, &name_by_type, &sep](const Option& opt)
|
||||
{
|
||||
return name_by_type[opt.type] + sep +
|
||||
opt.category_local + sep +
|
||||
opt.group_local + sep + opt.label_local;
|
||||
};
|
||||
|
||||
std::vector<uint16_t> matches, matches2;
|
||||
for (size_t i=0; i < options.size(); i++)
|
||||
{
|
||||
const Option &opt = options[i];
|
||||
if (full_list) {
|
||||
wxString label = get_label(opt);
|
||||
found.emplace_back(FoundOption{ label, label, get_tooltip(opt), i, 0 });
|
||||
std::string label = into_u8(get_label(opt));
|
||||
found.emplace_back(FoundOption{ label, label, boost::nowide::narrow(get_tooltip(opt)), i, 0 });
|
||||
continue;
|
||||
}
|
||||
|
||||
int score = 0;
|
||||
|
||||
FMFlag fuzzy_match_flag = opt.fuzzy_match(search, score);
|
||||
if (fuzzy_match_flag != fmUndef)
|
||||
{
|
||||
wxString label = get_label(opt);
|
||||
|
||||
if ( fuzzy_match_flag == fmLabel ) label += "(" + opt.label + ")";
|
||||
else if (fuzzy_match_flag == fmGroup ) label += "(" + opt.group + ")";
|
||||
else if (fuzzy_match_flag == fmCategory) label += "(" + opt.category + ")";
|
||||
else if (fuzzy_match_flag == fmOptKey ) label += "(" + opt.opt_key + ")";
|
||||
|
||||
wxString marked_label = label;
|
||||
mark_string(marked_label, from_u8(search));
|
||||
clear_marked_string(marked_label);
|
||||
|
||||
found.emplace_back(FoundOption{ label, marked_label, get_tooltip(opt), i, score });
|
||||
std::wstring wsearch = boost::nowide::widen(search);
|
||||
boost::trim_left(wsearch);
|
||||
std::wstring label = get_label(opt);
|
||||
std::wstring label_english = get_label_english(opt);
|
||||
int score = std::numeric_limits<int>::min();
|
||||
int score2;
|
||||
matches.clear();
|
||||
fuzzy_match(wsearch, label, score, matches);
|
||||
if (fuzzy_match(wsearch, opt.opt_key, score2, matches2) && score2 > score) {
|
||||
for (fts::pos_type &pos : matches2)
|
||||
pos += label.size() + 1;
|
||||
label += L"(" + opt.opt_key + L")";
|
||||
append(matches, matches2);
|
||||
score = score2;
|
||||
}
|
||||
if (fuzzy_match(wsearch, label_english, score2, matches2) && score2 > score) {
|
||||
label = std::move(label_english);
|
||||
matches = std::move(matches2);
|
||||
score = score2;
|
||||
}
|
||||
if (score > std::numeric_limits<int>::min()) {
|
||||
label = mark_string(label, matches);
|
||||
std::string label_u8 = into_u8(label);
|
||||
std::string label_plain = label_u8;
|
||||
boost::erase_all(label_plain, std::string(1, char(ImGui::ColorMarkerStart)));
|
||||
boost::erase_all(label_plain, std::string(1, char(ImGui::ColorMarkerEnd)));
|
||||
found.emplace_back(FoundOption{ label_plain, label_u8, boost::nowide::narrow(get_tooltip(opt)), i, score });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -357,7 +346,7 @@ bool SearchComboPopup::Create(wxWindow* parent)
|
|||
void SearchComboPopup::SetStringValue(const wxString& s)
|
||||
{
|
||||
int n = wxListBox::FindString(s);
|
||||
if (n >= 0 && n < wxListBox::GetCount())
|
||||
if (n >= 0 && n < int(wxListBox::GetCount()))
|
||||
wxListBox::Select(n);
|
||||
|
||||
// save a combo control's string
|
||||
|
@ -549,7 +538,7 @@ void SearchDialog::update_list()
|
|||
|
||||
const std::vector<FoundOption>& filters = searcher->found_options();
|
||||
for (const FoundOption& item : filters)
|
||||
search_list->Append(item.label);
|
||||
search_list->Append(from_u8(item.label));
|
||||
}
|
||||
|
||||
void SearchDialog::OnKeyDown(wxKeyEvent& event)
|
||||
|
|
|
@ -36,48 +36,31 @@ struct GroupAndCategory {
|
|||
wxString category;
|
||||
};
|
||||
|
||||
// fuzzy_match flag
|
||||
enum FMFlag
|
||||
{
|
||||
fmUndef = 0, // didn't find
|
||||
fmOptKey,
|
||||
fmLabel,
|
||||
fmLabelLocal,
|
||||
fmGroup,
|
||||
fmGroupLocal,
|
||||
fmCategory,
|
||||
fmCategoryLocal
|
||||
};
|
||||
|
||||
struct Option {
|
||||
bool operator<(const Option& other) const { return other.label > this->label; }
|
||||
bool operator>(const Option& other) const { return other.label < this->label; }
|
||||
|
||||
std::string opt_key;
|
||||
// Fuzzy matching works at a character level. Thus matching with wide characters is a safer bet than with short characters,
|
||||
// though for some languages (Chinese?) it may not work correctly.
|
||||
std::wstring opt_key;
|
||||
Preset::Type type {Preset::TYPE_INVALID};
|
||||
wxString label;
|
||||
wxString label_local;
|
||||
wxString group;
|
||||
wxString group_local;
|
||||
wxString category;
|
||||
wxString category_local;
|
||||
|
||||
FMFlag fuzzy_match_simple(char const *search_pattern) const;
|
||||
FMFlag fuzzy_match_simple(const wxString& search) const;
|
||||
FMFlag fuzzy_match_simple(const std::string &search) const;
|
||||
FMFlag fuzzy_match(char const *search_pattern, int &outScore) const;
|
||||
FMFlag fuzzy_match(const wxString &search, int &outScore) const ;
|
||||
FMFlag fuzzy_match(const std::string &search, int &outScore) const ;
|
||||
std::wstring label;
|
||||
std::wstring label_local;
|
||||
std::wstring group;
|
||||
std::wstring group_local;
|
||||
std::wstring category;
|
||||
std::wstring category_local;
|
||||
};
|
||||
|
||||
struct FoundOption {
|
||||
wxString label;
|
||||
wxString marked_label;
|
||||
wxString tooltip;
|
||||
// UTF8 encoding, to be consumed by ImGUI by reference.
|
||||
std::string label;
|
||||
std::string marked_label;
|
||||
std::string tooltip;
|
||||
size_t option_idx {0};
|
||||
int outScore {0};
|
||||
|
||||
void get_label(const char** out_text) const;
|
||||
// Returning pointers to contents of std::string members, to be used by ImGUI for rendering.
|
||||
void get_marked_label_and_tooltip(const char** label, const char** tooltip) const;
|
||||
};
|
||||
|
||||
|
@ -106,7 +89,7 @@ class OptionsSearcher
|
|||
}
|
||||
void sort_found() {
|
||||
std::sort(found.begin(), found.end(), [](const FoundOption& f1, const FoundOption& f2) {
|
||||
return f1.outScore > f2.outScore; });
|
||||
return f1.outScore > f2.outScore || (f1.outScore == f2.outScore && f1.label < f2.label); });
|
||||
};
|
||||
|
||||
size_t options_size() const { return options.size(); }
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "BonjourDialog.hpp"
|
||||
#include "WipeTowerDialog.hpp"
|
||||
#include "ButtonsDescription.hpp"
|
||||
#include "Search.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/button.h>
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
#include "Event.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "ConfigManipulation.hpp"
|
||||
#include "Search.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
|
|
@ -29,7 +29,7 @@ inline wxString format_wxstr(const wxString& fmt, TArgs&&... args) {
|
|||
}
|
||||
template<typename... TArgs>
|
||||
inline std::string format(const wxString& fmt, TArgs&&... args) {
|
||||
return format(fmt.ToUTF8().data(), std::forward<TArgs>(args)...);
|
||||
return Slic3r::format(fmt.ToUTF8().data(), std::forward<TArgs>(args)...);
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// LICENSE
|
||||
// LICENSE
|
||||
//
|
||||
// This software is dual-licensed to the public domain and under the following
|
||||
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||
|
@ -23,7 +23,7 @@
|
|||
// Performs exhaustive search via recursion to find all possible matches and match with highest score.
|
||||
// Scores values have no intrinsic meaning. Possible score range is not normalized and varies with pattern.
|
||||
// Recursion is limited internally (default=10) to prevent degenerate cases (pattern="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
||||
// Uses uint8_t for match indices. Therefore patterns are limited to 256 characters.
|
||||
// Uses uint8_t for match indices. Therefore patterns are limited to max_matches characters.
|
||||
// Score system should be tuned for YOUR use case. Words, sentences, file names, or method names all prefer different tuning.
|
||||
|
||||
|
||||
|
@ -37,56 +37,67 @@
|
|||
|
||||
#include <cstdio>
|
||||
|
||||
#include "../Utils/ASCIIFolding.hpp"
|
||||
|
||||
// Public interface
|
||||
namespace fts {
|
||||
static bool fuzzy_match_simple(char const * pattern, char const * str);
|
||||
static bool fuzzy_match(char const * pattern, char const * str, int & outScore);
|
||||
static bool fuzzy_match(char const * pattern, char const * str, int & outScore, uint8_t * matches, int maxMatches);
|
||||
}
|
||||
using char_type = wchar_t;
|
||||
using pos_type = uint16_t;
|
||||
static constexpr pos_type stopper = pos_type(-1);
|
||||
static constexpr int max_matches = 255;
|
||||
|
||||
static bool fuzzy_match(char_type const * pattern, char_type const * str, int & outScore);
|
||||
static bool fuzzy_match(char_type const * pattern, char_type const * str, int & outScore, pos_type * matches);
|
||||
}
|
||||
|
||||
#ifdef FTS_FUZZY_MATCH_IMPLEMENTATION
|
||||
namespace fts {
|
||||
|
||||
// Forward declarations for "private" implementation
|
||||
namespace fuzzy_internal {
|
||||
static bool fuzzy_match_recursive(const char * pattern, const char * str, int & outScore, const char * strBegin,
|
||||
uint8_t const * srcMatches, uint8_t * newMatches, int maxMatches, int nextMatch,
|
||||
int & recursionCount, int recursionLimit);
|
||||
static bool fuzzy_match_recursive(const char_type * pattern, const char_type * str, int & outScore, const char_type * const strBegin,
|
||||
pos_type const * srcMatches, pos_type * newMatches, int nextMatch,
|
||||
int recursionCount, const int recursionLimit);
|
||||
static void copy_matches(pos_type * dst, pos_type const* src);
|
||||
}
|
||||
|
||||
// Public interface
|
||||
static bool fuzzy_match_simple(char const * pattern, char const * str) {
|
||||
while (*pattern != '\0' && *str != '\0') {
|
||||
if (tolower(*pattern) == tolower(*str))
|
||||
++pattern;
|
||||
++str;
|
||||
static bool fuzzy_match(char_type const * pattern, char_type const * str, int & outScore) {
|
||||
pos_type matches[max_matches + 1]; // with the room for the stopper
|
||||
matches[0] = stopper;
|
||||
return fuzzy_match(pattern, str, outScore, matches);
|
||||
}
|
||||
|
||||
return *pattern == '\0' ? true : false;
|
||||
}
|
||||
|
||||
static bool fuzzy_match(char const * pattern, char const * str, int & outScore) {
|
||||
|
||||
uint8_t matches[256];
|
||||
return fuzzy_match(pattern, str, outScore, matches, sizeof(matches));
|
||||
}
|
||||
|
||||
static bool fuzzy_match(char const * pattern, char const * str, int & outScore, uint8_t * matches, int maxMatches) {
|
||||
static bool fuzzy_match(char_type const * pattern, char_type const * str, int & outScore, pos_type * matches) {
|
||||
int recursionCount = 0;
|
||||
int recursionLimit = 10;
|
||||
|
||||
return fuzzy_internal::fuzzy_match_recursive(pattern, str, outScore, str, nullptr, matches, maxMatches, 0, recursionCount, recursionLimit);
|
||||
static constexpr int recursionLimit = 10;
|
||||
return fuzzy_internal::fuzzy_match_recursive(pattern, str, outScore, str, nullptr, matches, 0, recursionCount, recursionLimit);
|
||||
}
|
||||
|
||||
// Private implementation
|
||||
static bool fuzzy_internal::fuzzy_match_recursive(const char * pattern, const char * str, int & outScore,
|
||||
const char * strBegin, uint8_t const * srcMatches, uint8_t * matches, int maxMatches,
|
||||
int nextMatch, int & recursionCount, int recursionLimit)
|
||||
static bool fuzzy_internal::fuzzy_match_recursive(
|
||||
// Pattern to match over str.
|
||||
const char_type * pattern,
|
||||
// Text to match the pattern over.
|
||||
const char_type * str,
|
||||
// Score of the pattern matching str. Output variable.
|
||||
int & outScore,
|
||||
// The very start of str, for calculating indices of matches and for calculating matches from the start of the input string.
|
||||
const char_type * const strBegin,
|
||||
// Matches when entering this function.
|
||||
pos_type const * srcMatches,
|
||||
// Output matches.
|
||||
pos_type * matches,
|
||||
// Number of matched characters stored in srcMatches when entering this function, also tracking the successive matches.
|
||||
int nextMatch,
|
||||
// Recursion count is input / output to track the maximum depth reached.
|
||||
// Was given by reference &recursionCount, see discussion in https://github.com/forrestthewoods/lib_fts/issues/21
|
||||
// int & recursionCount,
|
||||
int recursionCount,
|
||||
const int recursionLimit)
|
||||
{
|
||||
// Count recursions
|
||||
++recursionCount;
|
||||
if (recursionCount >= recursionLimit)
|
||||
if (++ recursionCount >= recursionLimit)
|
||||
return false;
|
||||
|
||||
// Detect end of strings
|
||||
|
@ -95,59 +106,75 @@ namespace fts {
|
|||
|
||||
// Recursion params
|
||||
bool recursiveMatch = false;
|
||||
uint8_t bestRecursiveMatches[256];
|
||||
pos_type bestRecursiveMatches[max_matches + 1]; // with the room for the stopper
|
||||
int bestRecursiveScore = 0;
|
||||
|
||||
// Loop through pattern and str looking for a match
|
||||
bool first_match = true;
|
||||
while (*pattern != '\0' && *str != '\0') {
|
||||
|
||||
int num_matched = std::tolower(*pattern) == std::tolower(*str) ? 1 : 0;
|
||||
bool folded_match = false;
|
||||
if (! num_matched) {
|
||||
char tmp[4];
|
||||
char *end = Slic3r::fold_to_ascii(*str, tmp);
|
||||
char *c = tmp;
|
||||
for (const wchar_t* d = pattern; c != end && *d != 0 && wchar_t(std::tolower(*c)) == std::tolower(*d); ++c, ++d);
|
||||
if (c == end) {
|
||||
folded_match = true;
|
||||
num_matched = end - tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// Found match
|
||||
if (tolower(*pattern) == tolower(*str)) {
|
||||
if (num_matched) {
|
||||
|
||||
// Supplied matches buffer was too short
|
||||
if (nextMatch >= maxMatches)
|
||||
if (nextMatch + num_matched > max_matches)
|
||||
return false;
|
||||
|
||||
// "Copy-on-Write" srcMatches into matches
|
||||
if (first_match && srcMatches) {
|
||||
memcpy(matches, srcMatches, nextMatch);
|
||||
memcpy(matches, srcMatches, sizeof(pos_type) * (nextMatch + 1)); // including the stopper
|
||||
first_match = false;
|
||||
}
|
||||
|
||||
// Recursive call that "skips" this match
|
||||
uint8_t recursiveMatches[256];
|
||||
pos_type recursiveMatches[max_matches + 1]; // with the room for the stopper
|
||||
int recursiveScore;
|
||||
if (fuzzy_match_recursive(pattern, str + 1, recursiveScore, strBegin, matches, recursiveMatches, sizeof(recursiveMatches), nextMatch, recursionCount, recursionLimit)) {
|
||||
if (fuzzy_match_recursive(pattern, str + 1, recursiveScore, strBegin, matches, recursiveMatches, nextMatch, recursionCount, recursionLimit)) {
|
||||
|
||||
// Pick best recursive score
|
||||
if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
|
||||
memcpy(bestRecursiveMatches, recursiveMatches, 256);
|
||||
copy_matches(bestRecursiveMatches, recursiveMatches);
|
||||
bestRecursiveScore = recursiveScore;
|
||||
}
|
||||
recursiveMatch = true;
|
||||
}
|
||||
|
||||
// Advance
|
||||
matches[nextMatch++] = (uint8_t)(str - strBegin);
|
||||
++pattern;
|
||||
matches[nextMatch++] = (pos_type)(str - strBegin);
|
||||
// Write a stopper sign.
|
||||
matches[nextMatch] = stopper;
|
||||
// Advance pattern by the number of matched characters (could be more if ASCII folding triggers in).
|
||||
pattern += num_matched;
|
||||
}
|
||||
++str;
|
||||
}
|
||||
|
||||
// Determine if full pattern was matched
|
||||
bool matched = *pattern == '\0' ? true : false;
|
||||
bool matched = *pattern == '\0';
|
||||
|
||||
// Calculate score
|
||||
if (matched) {
|
||||
const int sequential_bonus = 15; // bonus for adjacent matches
|
||||
const int separator_bonus = 30; // bonus if match occurs after a separator
|
||||
const int camel_bonus = 30; // bonus if match is uppercase and prev is lower
|
||||
const int first_letter_bonus = 15; // bonus if the first letter is matched
|
||||
static constexpr int sequential_bonus = 15; // bonus for adjacent matches
|
||||
static constexpr int separator_bonus = 30; // bonus if match occurs after a separator
|
||||
static constexpr int camel_bonus = 30; // bonus if match is uppercase and prev is lower
|
||||
static constexpr int first_letter_bonus = 15; // bonus if the first letter is matched
|
||||
|
||||
const int leading_letter_penalty = -5; // penalty applied for every letter in str before the first match
|
||||
const int max_leading_letter_penalty = -15; // maximum penalty for leading letters
|
||||
const int unmatched_letter_penalty = -1; // penalty for every letter that doesn't matter
|
||||
static constexpr int leading_letter_penalty = -5; // penalty applied for every letter in str before the first match
|
||||
static constexpr int max_leading_letter_penalty = -15; // maximum penalty for leading letters
|
||||
static constexpr int unmatched_letter_penalty = -1; // penalty for every letter that doesn't matter
|
||||
|
||||
// Iterate str to end
|
||||
while (*str != '\0')
|
||||
|
@ -156,46 +183,47 @@ namespace fts {
|
|||
// Initialize score
|
||||
outScore = 100;
|
||||
|
||||
// Apply leading letter penalty
|
||||
int penalty = leading_letter_penalty * matches[0];
|
||||
if (penalty < max_leading_letter_penalty)
|
||||
penalty = max_leading_letter_penalty;
|
||||
outScore += penalty;
|
||||
// Start of the first group that contains matches[0].
|
||||
const char_type *group_start = strBegin + matches[0];
|
||||
for (const char_type *c = group_start; c >= strBegin && *c != ':'; -- c)
|
||||
if (*c != ' ' && *c != '\t')
|
||||
group_start = c;
|
||||
|
||||
// Apply leading letter penalty or bonus.
|
||||
outScore += matches[0] == int(group_start - strBegin) ?
|
||||
first_letter_bonus :
|
||||
std::max((matches[0] - int(group_start - strBegin)) * leading_letter_penalty, max_leading_letter_penalty);
|
||||
|
||||
// Apply unmatched letters after the end penalty
|
||||
// outScore += (int(str - group_start) - matches[nextMatch-1] + 1) * unmatched_letter_penalty;
|
||||
// Apply unmatched penalty
|
||||
int unmatched = (int)(str - strBegin) - nextMatch;
|
||||
outScore += unmatched_letter_penalty * unmatched;
|
||||
outScore += (int(str - group_start) - nextMatch) * unmatched_letter_penalty;
|
||||
|
||||
// Apply ordering bonuses
|
||||
int sequential_state = sequential_bonus;
|
||||
for (int i = 0; i < nextMatch; ++i) {
|
||||
uint8_t currIdx = matches[i];
|
||||
|
||||
if (i > 0) {
|
||||
uint8_t prevIdx = matches[i - 1];
|
||||
|
||||
// Sequential
|
||||
if (currIdx == (prevIdx + 1))
|
||||
outScore += sequential_bonus;
|
||||
}
|
||||
pos_type currIdx = matches[i];
|
||||
|
||||
// Check for bonuses based on neighbor character value
|
||||
if (currIdx > 0) {
|
||||
// Camel case
|
||||
// ::islower() expects an unsigned char in range of 0 to 255.
|
||||
unsigned char uneighbor = ((unsigned char *)strBegin)[currIdx - 1];
|
||||
unsigned char ucurr = ((unsigned char*)strBegin)[currIdx];
|
||||
if (::islower(uneighbor) && ::isupper(ucurr))
|
||||
outScore += camel_bonus;
|
||||
|
||||
// Separator
|
||||
char neighbor = strBegin[currIdx - 1];
|
||||
bool neighborSeparator = neighbor == '_' || neighbor == ' ';
|
||||
if (neighborSeparator)
|
||||
outScore += separator_bonus;
|
||||
if (i > 0 && currIdx == matches[i - 1] + 1) {
|
||||
// Sequential
|
||||
outScore += sequential_state;
|
||||
// Exponential grow of the sequential bonus.
|
||||
sequential_state = std::min(5 * sequential_bonus, sequential_state + sequential_state / 3);
|
||||
} else {
|
||||
// Reset the sequential bonus exponential grow.
|
||||
sequential_state = sequential_bonus;
|
||||
}
|
||||
else {
|
||||
// First letter
|
||||
outScore += first_letter_bonus;
|
||||
char_type prev = strBegin[currIdx - 1];
|
||||
/*
|
||||
// Camel case
|
||||
if (std::islower(prev) && std::isupper(strBegin[currIdx]))
|
||||
outScore += camel_bonus;
|
||||
*/
|
||||
// Separator
|
||||
if (prev == '_' || prev == ' ')
|
||||
outScore += separator_bonus;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -203,7 +231,7 @@ namespace fts {
|
|||
// Return best result
|
||||
if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {
|
||||
// Recursive score is better than "this"
|
||||
memcpy(matches, bestRecursiveMatches, maxMatches);
|
||||
copy_matches(matches, bestRecursiveMatches);
|
||||
outScore = bestRecursiveScore;
|
||||
return true;
|
||||
}
|
||||
|
@ -216,6 +244,15 @@ namespace fts {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy matches up to a stopper.
|
||||
static void fuzzy_internal::copy_matches(pos_type * dst, pos_type const* src)
|
||||
{
|
||||
while (*src != stopper)
|
||||
*dst++ = *src++;
|
||||
*dst = stopper;
|
||||
}
|
||||
|
||||
} // namespace fts
|
||||
|
||||
#endif // FTS_FUZZY_MATCH_IMPLEMENTATION
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -10,6 +10,20 @@ namespace Slic3r {
|
|||
extern std::string fold_utf8_to_ascii(const char *src);
|
||||
extern std::string fold_utf8_to_ascii(const std::string &src);
|
||||
|
||||
// Convert the input UNICODE character to a string of maximum 4 output ASCII characters.
|
||||
// Return the end of the string written to the output.
|
||||
// The output buffer must be at least 4 characters long.
|
||||
extern char* fold_to_ascii(wchar_t c, char *out);
|
||||
|
||||
template<typename OUTPUT_ITERATOR>
|
||||
void fold_to_ascii(wchar_t c, OUTPUT_ITERATOR out)
|
||||
{
|
||||
char tmp[4];
|
||||
char *end = fold_to_ascii(c, tmp);
|
||||
for (char *it = tmp; it != end; ++ it)
|
||||
*out = *it;
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_ASCIIFolding_hpp_ */
|
||||
|
|
Loading…
Reference in a new issue