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,13 @@ #include "GameInterface/Messages.h" #include "wx/busyinfo.h" +#include "wx/wxcrt.h" enum { ID_ObjectType = 1, ID_ObjectFilter, + ID_ObjectAdvancedFilter, ID_PlayerSelect, ID_SelectObject, ID_ToggleViewer, @@ -115,6 +117,44 @@ float m_ActorViewerSpeed; Observable& m_ObjectSettings; + // Cache to prevent frequent reallocations. + std::vector m_LCSGrid; + struct SearchItem + { + // Weight of the pattern (higher - better). + int weight; + // We store iterator to avoid useless copying. + std::vector::iterator it; + + bool operator<(const SearchItem& other) const + { + return weight > other.weight; + } + }; + std::vector m_SearchItems; + + int CalculateSearchWeight(const wxString& filterName, const wxString& name) + { + if (filterName.IsEmpty() || name.IsEmpty()) + return 0; + // We use longest common subsequence to calculate the weight. + m_LCSGrid.resize(filterName.Length() * name.Length()); + for (size_t i = 0; i < filterName.Length(); ++i) + for (size_t j = 0; j < name.Length(); ++j) + { + #define GRID(x, y) m_LCSGrid[(x) * name.Length() + (y)] + GRID(i, j) = wxTolower(filterName.GetChar(i)) == wxTolower(name.GetChar(j)); + if (i && j) + GRID(i, j) += GRID(i - 1, j - 1); + if (i) + GRID(i, j) = std::max(GRID(i, j), GRID(i - 1, j)); + if (j) + GRID(i, j) = std::max(GRID(i, j), GRID(i, j - 1)); + #undef GRID + } + return m_LCSGrid.back(); + } + void ActorViewerPostToGame() { POST_MESSAGE(SetActorViewer, (m_ActorViewerEntity.c_str(), m_ActorViewerAnimation.c_str(), m_ObjectSettings.GetPlayerID(), m_ActorViewerSpeed, false)); @@ -137,6 +177,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 +187,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 +267,39 @@ 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) + 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 = m_Impl->CalculateSearchWeight(filterName, name); + // We don't want to show items with zero weight. + if (weight == 0) + continue; + m_Impl->m_SearchItems.push_back({weight, it}); + } + std::sort(m_Impl->m_SearchItems.begin(), m_Impl->m_SearchItems.end()); + for (const ObjectSidebarImpl::SearchItem& item : m_Impl->m_SearchItems) + { + wxString id = item.it->id.c_str(); + wxString name = item.it->name.c_str(); + m_Impl->m_ObjectListBox->Append(name, new wxStringClientData(id)); + } + m_Impl->m_SearchItems.clear(); + } + 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();