Index: source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp =================================================================== --- source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp +++ source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -29,11 +29,44 @@ #include "GameInterface/Messages.h" #include "wx/busyinfo.h" +#include "wx/wxcrt.h" + +namespace +{ + +int CalculateSearchWeight(const wxString& filterName, const wxString& name) +{ + // At the moment we just use subsequence calculation. + int weight = 0; + // Indices for |filterName| and |name| respectively. + size_t idx_fn = 0, idx_n = 0; + while (idx_fn < filterName.Length() && idx_n < name.Length()) + { + if (wxIsspace(filterName.GetChar(idx_fn))) + ++idx_fn; + else if (wxIsspace(name.GetChar(idx_n))) + ++idx_n; + else if (wxTolower(filterName.GetChar(idx_fn)) == wxTolower(name.GetChar(idx_n))) + { + ++weight; + ++idx_fn; + ++idx_n; + } + else + { + ++idx_n; + } + } + return weight; +} + +} enum { ID_ObjectType = 1, ID_ObjectFilter, + ID_ObjectAdvancedFilter, ID_PlayerSelect, ID_SelectObject, ID_ToggleViewer, @@ -137,6 +170,7 @@ wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(new wxStaticText(scrolledWindow, wxID_ANY, _("Filter")), wxSizerFlags().Align(wxALIGN_CENTER)); + sizer->AddSpacer(2); sizer->Add( Tooltipped( new wxTextCtrl(scrolledWindow, ID_ObjectFilter), @@ -146,6 +180,10 @@ ); scrollSizer->Add(sizer, wxSizerFlags().Expand()); scrollSizer->AddSpacer(3); + scrollSizer->Add(Tooltipped(new wxCheckBox( + scrolledWindow, ID_ObjectAdvancedFilter, _("Advanced Search")), + _("Provides a search without strict string equality"))); + scrollSizer->AddSpacer(3); // ------------------------------------------------------------------------------------------ @@ -222,17 +260,51 @@ m_Impl->m_ObjectListBox->Freeze(); m_Impl->m_ObjectListBox->Clear(); - for (std::vector::iterator it = m_Impl->m_Objects.begin(); it != m_Impl->m_Objects.end(); ++it) + if (wxDynamicCast(FindWindow(ID_ObjectAdvancedFilter), wxCheckBox)->IsChecked() && !filterName.IsEmpty()) { - if (it->type == filterType) + struct Item { + // Weight of the pattern (higher - better). + int weight; + // We store iterator to avoid useless copying. + std::vector::iterator it; + + bool operator<(const Item& other) const + { + return weight > other.weight; + } + }; + std::vector searchItems; + for (std::vector::iterator it = m_Impl->m_Objects.begin(); it != m_Impl->m_Objects.end(); ++it) + { + if (it->type != filterType) + continue; + wxString name = it->name.c_str(); + int weight = CalculateSearchWeight(filterName, name); + // We don't want to show items with zero weight. + if (weight == 0) + continue; + searchItems.push_back({weight, it}); + } + std::sort(searchItems.begin(), searchItems.end()); + for (const Item& item : searchItems) + { + wxString id = item.it->id.c_str(); + wxString name = item.it->name.c_str(); + m_Impl->m_ObjectListBox->Append(name, new wxStringClientData(id)); + } + } + else + { + for (std::vector::iterator it = m_Impl->m_Objects.begin(); it != m_Impl->m_Objects.end(); ++it) + { + if (it->type != filterType) + continue; wxString id = it->id.c_str(); wxString name = it->name.c_str(); if (name.Lower().Find(filterName.Lower()) != wxNOT_FOUND) - { m_Impl->m_ObjectListBox->Append(name, new wxStringClientData(id)); - } } } m_Impl->m_ObjectListBox->Thaw();