//////////////////////////////////////////////////////////////////////
// This file is part of Remere's Map Editor
//////////////////////////////////////////////////////////////////////
// Remere's Map Editor is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Remere's Map Editor is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//////////////////////////////////////////////////////////////////////
#include "main.h"
#include "result_window.h"
#include "gui.h"
#include "position.h"
#include "main_menubar.h"
#include
#include
#include "string_utils.h"
BEGIN_EVENT_TABLE(SearchResultWindow, wxPanel)
EVT_LISTBOX(wxID_ANY, SearchResultWindow::OnClickResult)
EVT_BUTTON(wxID_FILE, SearchResultWindow::OnClickExport)
EVT_BUTTON(wxID_CLEAR, SearchResultWindow::OnClickClear)
EVT_BUTTON(SEARCH_RESULT_NEXT_BUTTON, SearchResultWindow::OnClickNext)
END_EVENT_TABLE()
SearchResultWindow::SearchResultWindow(wxWindow* parent) :
wxPanel(parent, wxID_ANY),
use_ignored_ids(false),
last_search_itemid(0),
last_search_on_selection(false),
has_last_search(false),
last_ignored_ids_enabled(false) {
wxSizer* sizer = newd wxBoxSizer(wxVERTICAL);
result_list = newd wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(200, 330), 0, nullptr, wxLB_SINGLE | wxLB_ALWAYS_SB);
sizer->Add(result_list, wxSizerFlags(1).Expand());
wxSizer* buttonsSizer = newd wxBoxSizer(wxHORIZONTAL);
buttonsSizer->Add(newd wxButton(this, SEARCH_RESULT_NEXT_BUTTON, "Next"), wxSizerFlags(0).Center());
buttonsSizer->Add(newd wxButton(this, wxID_FILE, "Export"), wxSizerFlags(0).Center());
buttonsSizer->Add(newd wxButton(this, wxID_CLEAR, "Clear"), wxSizerFlags(0).Center());
sizer->Add(buttonsSizer, wxSizerFlags(0).Center().DoubleBorder());
SetSizerAndFit(sizer);
}
SearchResultWindow::~SearchResultWindow() {
Clear();
}
void SearchResultWindow::Clear() {
for (uint32_t n = 0; n < result_list->GetCount(); ++n) {
delete reinterpret_cast(result_list->GetClientData(n));
}
result_list->Clear();
has_last_search = false;
}
void SearchResultWindow::AddPosition(wxString description, Position pos) {
uint16_t item_id = 0;
wxString item_name;
// Extract the item ID from the description
size_t start = description.Find("(ID: ");
if (start != wxString::npos) {
start += 4;
size_t end = description.find(')', start);
if (end != wxString::npos) {
wxString id_str = description.SubString(start, end - 1);
long temp_id;
if (id_str.ToLong(&temp_id)) {
item_id = static_cast(temp_id);
item_name = description.SubString(0, start - 5).Trim(); // Get name without ID
OutputDebugStringA(wxString::Format("Parsed item: %s with ID: %d\n",
item_name, item_id).c_str());
if (use_ignored_ids) {
if (std::find(ignored_ids.begin(), ignored_ids.end(), item_id) != ignored_ids.end()) {
OutputDebugStringA(wxString::Format("Skipping ignored ID: %d\n", item_id).c_str());
return;
}
for (const auto& range : ignored_ranges) {
if (item_id >= range.first && item_id <= range.second) {
OutputDebugStringA(wxString::Format("Skipping ID in ignored range %d-%d: %d\n",
range.first, range.second, item_id).c_str());
return;
}
}
}
}
}
}
// If we couldn't parse an ID, try to get the item name directly
if (item_name.empty()) {
size_t space_pos = description.Find(' ');
if (space_pos != wxString::npos) {
item_name = description.SubString(0, space_pos - 1);
} else {
item_name = description;
}
OutputDebugStringA(wxString::Format("Using item name without ID: %s\n", item_name).c_str());
}
// Format the display text
wxString display_text;
if (item_id > 0) {
display_text = wxString::Format("%s [ID: %d] at (%d,%d,%d)",
item_name, item_id, pos.x, pos.y, pos.z);
} else {
display_text = wxString::Format("%s at (%d,%d,%d)",
item_name, pos.x, pos.y, pos.z);
}
result_list->Append(display_text, newd Position(pos));
OutputDebugStringA(wxString::Format("Added to result list: %s\n", display_text).c_str());
}
std::vector SearchResultWindow::GetFoundPositions() const {
std::vector positions;
for (uint32_t n = 0; n < result_list->GetCount(); ++n) {
Position* pos = reinterpret_cast(result_list->GetClientData(n));
if (pos) {
positions.push_back(*pos);
}
}
return positions;
}
void SearchResultWindow::StoreSearchInfo(uint16_t itemId, bool onSelection) {
last_search_itemid = itemId;
last_search_on_selection = onSelection;
has_last_search = true;
}
void SearchResultWindow::OnClickResult(wxCommandEvent& event) {
Position* pos = reinterpret_cast(event.GetClientData());
if (pos) {
g_gui.SetScreenCenterPosition(*pos);
}
}
void SearchResultWindow::OnClickExport(wxCommandEvent& WXUNUSED(event)) {
wxFileDialog dialog(this, "Save file...", "", "", "Text Documents (*.txt) | *.txt", wxFD_SAVE);
if (dialog.ShowModal() == wxID_OK) {
wxFile file(dialog.GetPath(), wxFile::write);
if (file.IsOpened()) {
g_gui.CreateLoadBar("Exporting search result...");
file.Write("Generated by Remere's Map Editor " + __RME_VERSION__);
file.Write("\n=============================================\n\n");
wxArrayString lines = result_list->GetStrings();
size_t count = lines.Count();
for (size_t i = 0; i < count; ++i) {
file.Write(lines[i] + "\n");
g_gui.SetLoadScale((int32_t)i, (int32_t)count);
}
file.Close();
g_gui.DestroyLoadBar();
}
}
}
void SearchResultWindow::OnClickClear(wxCommandEvent& WXUNUSED(event)) {
Clear();
}
void SearchResultWindow::OnClickNext(wxCommandEvent& WXUNUSED(event)) {
ContinueSearch();
}
void SearchResultWindow::ContinueSearch() {
if (!has_last_search) {
g_gui.PopupDialog("Search Error", "No previous search available to continue.", wxOK | wxICON_INFORMATION);
return;
}
// Store current positions to avoid duplicates
std::vector existingPositions = GetFoundPositions();
// Custom Finder that skips already found positions
class ContinuedFinder {
public:
ContinuedFinder(uint16_t itemId, const std::vector& existingPositions, uint32_t maxCount) :
itemId(itemId), existingPositions(existingPositions), maxCount(maxCount) {}
uint16_t itemId;
std::vector existingPositions;
uint32_t maxCount;
std::vector> result;
bool limitReached() const {
return result.size() >= (size_t)maxCount;
}
bool isPositionAlreadyFound(const Position& pos) const {
for (const Position& existingPos : existingPositions) {
if (existingPos.x == pos.x && existingPos.y == pos.y && existingPos.z == pos.z) {
return true;
}
}
return false;
}
void operator()(Map& map, Tile* tile, Item* item, long long done) {
if (result.size() >= (size_t)maxCount) {
return;
}
if (done % 0x8000 == 0) {
g_gui.SetLoadDone((unsigned int)(100 * done / map.getTileCount()));
}
if (item->getID() == itemId) {
// Skip if this position was already found
if (!isPositionAlreadyFound(tile->getPosition())) {
result.push_back(std::make_pair(tile, item));
}
}
}
};
// Run the continued search
g_gui.CreateLoadBar("Continuing search...");
ContinuedFinder finder(last_search_itemid, existingPositions,
(uint32_t)g_settings.getInteger(Config::REPLACE_SIZE));
foreach_ItemOnMap(g_gui.GetCurrentMap(), finder, last_search_on_selection);
std::vector>& result = finder.result;
g_gui.DestroyLoadBar();
if (finder.limitReached()) {
wxString msg;
msg << "The configured limit has been reached. Only " << finder.maxCount << " additional results will be displayed.";
g_gui.PopupDialog("Notice", msg, wxOK);
}
if (result.empty()) {
g_gui.PopupDialog("Search Complete", "No more matching items found.", wxOK | wxICON_INFORMATION);
return;
}
// Add new results to the window
for (const auto& pair : result) {
Tile* tile = pair.first;
Item* item = pair.second;
wxString description = wxString::Format("%s (ID: %d)",
wxstr(item->getName()),
item->getID());
AddPosition(description, tile->getPosition());
}
}
void SearchResultWindow::SetIgnoredIds(const wxString& ignored_ids_str, bool enable) {
use_ignored_ids = enable;
last_ignored_ids_text = ignored_ids_str;
last_ignored_ids_enabled = enable;
// Clear previous
ignored_ids.clear();
ignored_ranges.clear();
if (!ignored_ids_str.empty() && enable) {
// Parse the ignored IDs string
wxArrayString parts = wxStringTokenize(ignored_ids_str, ",");
for (const wxString& part : parts) {
// Trim whitespace - create a non-const copy to use Trim
wxString trimmed = part;
trimmed.Trim(true).Trim(false);
// Check if it's a range (contains "-")
size_t rangePos = trimmed.Find('-');
if (rangePos != wxString::npos) {
// It's a range
wxString firstStr = trimmed.Left(rangePos);
wxString secondStr = trimmed.Mid(rangePos + 1);
// Trim these strings as well
firstStr.Trim(true).Trim(false);
secondStr.Trim(true).Trim(false);
long first, second;
if (firstStr.ToLong(&first) && secondStr.ToLong(&second)) {
ignored_ranges.push_back(std::make_pair(
static_cast(first),
static_cast(second)));
}
}
else {
// It's a single ID
long id;
if (trimmed.ToLong(&id)) {
ignored_ids.push_back(static_cast(id));
}
}
}
}
}
wxString SearchResultWindow::GetIgnoredItemsText() const {
return last_ignored_ids_text;
}
bool SearchResultWindow::IsIgnoreListEnabled() const {
return last_ignored_ids_enabled;
}