diff --git a/plugins/search.cpp b/plugins/search.cpp index 0f296e2d8..cc1d410d3 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -5,14 +5,28 @@ #include -//#include "df/viewscreen_petst.h" +#include "df/viewscreen_announcelistst.h" +#include "df/viewscreen_petst.h" #include "df/viewscreen_storesst.h" #include "df/viewscreen_layer_stockpilest.h" +#include "df/viewscreen_layer_militaryst.h" +#include "df/viewscreen_layer_noblelistst.h" #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_unitlistst.h" +#include "df/viewscreen_buildinglistst.h" +#include "df/viewscreen_joblistst.h" #include "df/interface_key.h" #include "df/interfacest.h" #include "df/layer_object_listst.h" +#include "df/job.h" +#include "df/report.h" +#include "modules/Job.h" +#include "df/global_objects.h" +#include "df/viewscreen_dwarfmodest.h" +#include "modules/Gui.h" +#include "df/unit.h" +#include "df/misc_trait_type.h" +#include "df/unit_misc_trait.h" using std::set; using std::vector; @@ -51,25 +65,56 @@ static bool is_live_screen(const df::viewscreen *screen) return false; } +static string get_unit_description(df::unit *unit) +{ + string desc; + auto name = Units::getVisibleName(unit); + if (name->has_name) + desc = Translation::TranslateName(name, false); + desc += ", " + Units::getProfessionName(unit); // Check animal type too + + return desc; +} + + // -// START: Base Search functionality +// START: Generic Search functionality // -// Parent class that does most of the work -template -class search_parent +template +class search_generic { public: + bool init(S *screen) + { + if (screen != viewscreen && !reset_on_change()) + return false; + + if (!can_init(screen)) + return false; + + if (!is_valid()) + { + this->viewscreen = screen; + this->cursor_pos = get_viewscreen_cursor(); + this->primary_list = get_primary_list(); + this->select_key = get_search_select_key(); + select_token = (df::interface_key) (ascii_to_enum_offset + select_key); + valid = true; + do_post_init(); + } + + return true; + } + // Called each time you enter or leave a searchable screen. Resets everything. - void reset_all() + virtual void reset_all() { reset_search(); valid = false; - sort_list1 = NULL; - sort_list2 = NULL; + primary_list = NULL; viewscreen = NULL; select_key = 's'; - track_secondary_values = false; } bool reset_on_change() @@ -153,48 +198,35 @@ public: return key_processed || entry_mode; // Only pass unrecognized keys down if not in typing mode } - // Called if the search should be redone after the screen processes the keystroke. - // Used by the stocks screen where changing categories should redo the search on - // the new category. - virtual void do_post_update_check() + // Called after a keystroke has been processed + virtual void do_post_input_feed() { - if (redo_search) - { - do_search(); - redo_search = false; - } } - static search_parent *lock; + static search_generic *lock; protected: - const S *viewscreen; - vector saved_list1, reference_list; - vector saved_list2; - vector *sort_list2; - vector saved_indexes; - - bool redo_search; - bool track_secondary_values; - string search_string; + virtual string get_element_description(T element) const = 0; + virtual void render() const = 0; + virtual int32_t *get_viewscreen_cursor() = 0; + virtual vector *get_primary_list() = 0; - search_parent() : ascii_to_enum_offset(interface_key::STRING_A048 - '0'), shift_offset('A' - 'a') + search_generic() : ascii_to_enum_offset(interface_key::STRING_A048 - '0'), shift_offset('A' - 'a') { reset_all(); } - virtual void init(int *cursor_pos, vector *sort_list1, vector *sort_list2 = NULL, char select_key = 's') + virtual bool can_init(S *screen) + { + return true; + } + + virtual void do_post_init() { - this->cursor_pos = cursor_pos; - this->sort_list1 = sort_list1; - this->sort_list2 = sort_list2; - this->select_key = select_key; - select_token = (df::interface_key) (ascii_to_enum_offset + select_key); - track_secondary_values = false; - valid = true; + } - bool is_entry_mode() + bool in_entry_mode() { return entry_mode; } @@ -204,84 +236,59 @@ protected: entry_mode = true; lock = this; } - + void end_entry_mode() { entry_mode = false; lock = NULL; } - void reset_search() + virtual char get_search_select_key() + { + return 's'; + } + + virtual void reset_search() { end_entry_mode(); search_string = ""; saved_list1.clear(); - saved_list2.clear(); - reference_list.clear(); - saved_indexes.clear(); } - // If the second vector is editable (i.e. Trade screen vector used for marking). then it may - // have been edited while the list was filtered. We have to update the original unfiltered - // list with these values. Uses a stored reference vector to determine if the list has been - // reordered after filtering, in which case indexes must be remapped. - void update_secondary_values() + // Shortcut to clear the search immediately + virtual void clear_search() { - if (sort_list2 != NULL && track_secondary_values) + if (saved_list1.size() > 0) { - bool list_has_been_sorted = (sort_list1->size() == reference_list.size() - && *sort_list1 != reference_list); + *primary_list = saved_list1; + saved_list1.clear(); + } + search_string = ""; + } - for (size_t i = 0; i < saved_indexes.size(); i++) - { - int adjusted_item_index = i; - if (list_has_been_sorted) - { - for (size_t j = 0; j < sort_list1->size(); j++) - { - if ((*sort_list1)[j] == reference_list[i]) - { - adjusted_item_index = j; - break; - } - } - } + virtual void save_original_values() + { + saved_list1 = *primary_list; + } + + virtual void do_pre_incremental_search() + { - update_saved_secondary_list_item(saved_indexes[i], adjusted_item_index); - } - saved_indexes.clear(); - } } - virtual void update_saved_secondary_list_item(size_t i, size_t j) + virtual void clear_viewscreen_vectors() { - saved_list2[i] = (*sort_list2)[j]; + primary_list->clear(); } - // Store a copy of filtered list, used later to work out if filtered list has been sorted after filtering - void store_reference_values() + virtual void add_to_filtered_list(size_t i) { - if (track_secondary_values) - reference_list = *sort_list1; + primary_list->push_back(saved_list1[i]); } - // Shortcut to clear the search immediately - virtual void clear_search() + virtual void do_post_search() { - if (saved_list1.size() > 0) - { - *sort_list1 = saved_list1; - if (sort_list2 != NULL) - { - update_secondary_values(); - *sort_list2 = saved_list2; - } - saved_list1.clear(); - saved_list2.clear(); - } - store_reference_values(); - search_string = ""; } // The actual sort @@ -294,22 +301,12 @@ protected: } if (saved_list1.size() == 0) - { // On first run, save the original list - saved_list1 = *sort_list1; - if (sort_list2 != NULL) - saved_list2 = *sort_list2; - } + save_original_values(); else - update_secondary_values(); // Update original list with any modified values + do_pre_incremental_search(); - // Clear viewscreen vectors - sort_list1->clear(); - if (sort_list2 != NULL) - { - sort_list2->clear(); - saved_indexes.clear(); - } + clear_viewscreen_vectors(); string search_string_l = toLower(search_string); for (size_t i = 0; i < saved_list1.size(); i++ ) @@ -318,17 +315,11 @@ protected: string desc = toLower(get_element_description(element)); if (desc.find(search_string_l) != string::npos) { - sort_list1->push_back(element); - if (sort_list2 != NULL) - { - sort_list2->push_back(saved_list2[i]); - if (track_secondary_values) - saved_indexes.push_back(i); // Used to map filtered indexes back to original, if needed - } + add_to_filtered_list(i); } } - store_reference_values(); //Keep a copy, in case user sorts new list + do_post_search(); if (cursor_pos) *cursor_pos = 0; @@ -354,11 +345,13 @@ protected: OutputString(10, x, y, "_"); } - virtual string get_element_description(T element) const = 0; - virtual void render () const = 0; + S *viewscreen; + vector saved_list1, reference_list, *primary_list; + + //bool redo_search; + string search_string; private: - vector *sort_list1; int *cursor_pos; char select_key; bool valid; @@ -369,430 +362,1241 @@ private: const int shift_offset; }; -template search_parent *search_parent ::lock = NULL; -// Parent struct for the hooks, use optional param D to generate multiple classes with same T & V -// but different static modules -template -struct search_hook : T -{ - typedef T interpose_base; +template search_generic *search_generic ::lock = NULL; - static V module; - DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) +// Search class helper for layered screens +template +class layered_search : public search_generic +{ +protected: + virtual bool can_init(S *screen) { - if (!module.init(this)) + auto list = getLayerList(screen); + if (!list->active) { - INTERPOSE_NEXT(feed)(input); - return; - } + if (is_valid()) + { + clear_search(); + reset_all(); + } - if (!module.process_input(input)) - { - INTERPOSE_NEXT(feed)(input); - module.do_post_update_check(); + return false; } + return true; } - DEFINE_VMETHOD_INTERPOSE(void, render, ()) + virtual void do_search() { - bool ok = module.init(this); - INTERPOSE_NEXT(render)(); - if (ok) - module.render(); + search_generic::do_search(); + auto list = getLayerList(viewscreen); + list->num_entries = get_primary_list()->size(); + } + + int32_t *get_viewscreen_cursor() + { + auto list = getLayerList(viewscreen); + return &list->cursor; } -}; - -template V search_hook ::module; + virtual void clear_search() + { + search_generic::clear_search(); + auto list = getLayerList(viewscreen); + list->num_entries = get_primary_list()->size(); + } -// -// END: Base Search functionality -// +private: + static df::layer_object_listst *getLayerList(const df::viewscreen_layer *layer) + { + return virtual_cast(vector_get(layer->layer_objects, LIST_ID)); + } +}; -// -// START: Stocks screen search -// -class stocks_search : public search_parent +// Parent class for screens that have more than one primary list to synchronise +template < class S, class T, class PARENT = search_generic > +class search_multicolumn_modifiable_generic : public PARENT { -public: +protected: + vector reference_list; + vector saved_indexes; + bool read_only; - virtual void render() const + virtual void update_saved_secondary_list_item(size_t i, size_t j) = 0; + virtual void save_secondary_values() = 0; + virtual void clear_secondary_viewscreen_vectors() = 0; + virtual void add_to_filtered_secondary_lists(size_t i) = 0; + virtual void clear_secondary_saved_lists() = 0; + virtual void reset_secondary_viewscreen_vectors() = 0; + virtual void restore_secondary_values() = 0; + + virtual void do_post_init() { - if (!viewscreen->in_group_mode) - print_search_option(2); - else - { - auto dim = Screen::getWindowSize(); - int x = 2, y = dim.y - 2; - OutputString(15, x, y, "Tab to enable Search"); - } + // If true, secondary list isn't modifiable so don't bother synchronising values + read_only = false; } - virtual void do_post_update_check() + void reset_all() { - if (viewscreen->in_group_mode) - { - // Disable search if item lists are grouped - clear_search(); - reset_search(); - } - else - search_parent::do_post_update_check(); + PARENT::reset_all(); + reference_list.clear(); + saved_indexes.clear(); + reset_secondary_viewscreen_vectors(); } - bool init(df::viewscreen_storesst *screen) + void reset_search() { - if (screen != viewscreen && !reset_on_change()) - return false; + PARENT::reset_search(); + reference_list.clear(); + saved_indexes.clear(); + clear_secondary_saved_lists(); + } - if (!is_valid()) + virtual void clear_search() + { + if (saved_list1.size() > 0) { - viewscreen = screen; - search_parent::init(&screen->item_cursor, &screen->items); + do_pre_incremental_search(); + restore_secondary_values(); } - - return true; + clear_secondary_saved_lists(); + PARENT::clear_search(); + do_post_search(); } + virtual bool is_match(T &a, T &b) = 0; -private: - virtual string get_element_description(df::item *element) const + virtual bool is_match(vector &a, vector &b) = 0; + + void do_pre_incremental_search() { - return Items::getDescription(element, 0, true); - } + PARENT::do_pre_incremental_search(); + if (read_only) + return; - virtual bool should_check_input(set *input) - { - if (viewscreen->in_group_mode) - return false; + bool list_has_been_sorted = (primary_list->size() == reference_list.size() + && !is_match(*primary_list, reference_list)); - if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !viewscreen->in_right_list) + for (size_t i = 0; i < saved_indexes.size(); i++) { - // Redo search if category changes - saved_list1.clear(); - end_entry_mode(); - if (search_string.length() > 0) - redo_search = true; + int adjusted_item_index = i; + if (list_has_been_sorted) + { + for (size_t j = 0; j < primary_list->size(); j++) + { + if (is_match((*primary_list)[j], reference_list[i])) + { + adjusted_item_index = j; + break; + } + } + } - return false; + update_saved_secondary_list_item(saved_indexes[i], adjusted_item_index); } + saved_indexes.clear(); + } - return true; + void clear_viewscreen_vectors() + { + search_generic::clear_viewscreen_vectors(); + saved_indexes.clear(); + clear_secondary_viewscreen_vectors(); } -}; + void add_to_filtered_list(size_t i) + { + search_generic::add_to_filtered_list(i); + add_to_filtered_secondary_lists(i); + if (!read_only) + saved_indexes.push_back(i); // Used to map filtered indexes back to original, if needed + } -typedef search_hook stocks_search_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, feed); -template<> IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, render); + virtual void do_post_search() + { + if (!read_only) + reference_list = *primary_list; + } -// -// END: Stocks screen search -// + void save_original_values() + { + search_generic::save_original_values(); + save_secondary_values(); + } +}; +// This basic match function is separated out from the generic multi column class, because the +// pets screen, which uses a union in its primary list, will cause a compile failure is this +// match function exists in the generic class +template < class S, class T, class PARENT = search_generic > +class search_multicolumn_modifiable : public search_multicolumn_modifiable_generic +{ + bool is_match(T &a, T &b) + { + return a == b; + } + bool is_match(vector &a, vector &b) + { + return a == b; + } +}; -// -// START: Unit screen search -// -class unitlist_search : public search_parent +// General class for screens that have only one secondary list to keep in sync +template < class S, class T, class V, class PARENT = search_generic > +class search_twocolumn_modifiable : public search_multicolumn_modifiable { public: +protected: + virtual vector * get_secondary_list() = 0; - virtual void render() const + virtual void do_post_init() { - print_search_option(28); + search_multicolumn_modifiable::do_post_init(); + secondary_list = get_secondary_list(); } - bool init(df::viewscreen_unitlistst *screen) + void save_secondary_values() { - if (screen != viewscreen && !reset_on_change()) - return false; - - if (!is_valid()) - { - viewscreen = screen; - search_parent::init(&screen->cursor_pos[viewscreen->page], &screen->units[viewscreen->page], &screen->jobs[viewscreen->page]); - } + saved_secondary_list = *secondary_list; + } - return true; + void reset_secondary_viewscreen_vectors() + { + secondary_list = NULL; } -private: - virtual string get_element_description(df::unit *element) const + virtual void update_saved_secondary_list_item(size_t i, size_t j) { - string desc = Translation::TranslateName(Units::getVisibleName(element), false); - desc += ", " + Units::getProfessionName(element); // Check animal type too + saved_secondary_list[i] = (*secondary_list)[j]; + } - return desc; + void clear_secondary_viewscreen_vectors() + { + secondary_list->clear(); } - virtual bool should_check_input(set *input) + void add_to_filtered_secondary_lists(size_t i) { - if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || - (!is_entry_mode() && input->count(interface_key::UNITVIEW_PRF_PROF))) - { - if (!is_entry_mode()) - { - // Changing screens, reset search - clear_search(); - reset_all(); - } - else - input->clear(); // Ignore cursor keys when typing + secondary_list->push_back(saved_secondary_list[i]); + } - return false; - } + void clear_secondary_saved_lists() + { + saved_secondary_list.clear(); + } - return true; + void restore_secondary_values() + { + *secondary_list = saved_secondary_list; } + vector *secondary_list, saved_secondary_list; }; -typedef search_hook unitlist_search_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); -template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100); - -// -// END: Unit screen search -// - - -// -// TODO: Animals screen search -// - -// -// END: Animals screen search -// -// -// START: Trade screen search -// -class trade_search_base : public search_parent +// Parent struct for the hooks, use optional param D to generate multiple search classes in the same +// viewscreen but different static modules +template +struct generic_search_hook : T { + typedef T interpose_base; -private: - virtual string get_element_description(df::item *element) const - { - return Items::getDescription(element, 0, true); - } + static V module; - virtual bool should_check_input(set *input) + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { - if (is_entry_mode()) + if (!module.init(this)) + { + INTERPOSE_NEXT(feed)(input); + return; + } + + if (!module.process_input(input)) + { + INTERPOSE_NEXT(feed)(input); + module.do_post_input_feed(); + } + + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + bool ok = module.init(this); + INTERPOSE_NEXT(render)(); + if (ok) + module.render(); + } +}; + +template V generic_search_hook ::module; + + +// Hook definition helpers +#define IMPLEMENT_HOOKS_WITH_ID(screen, module, id) \ + typedef generic_search_hook module##_hook; \ + template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, feed); \ + template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, render) + +#define IMPLEMENT_HOOKS(screen, module) \ + typedef generic_search_hook module##_hook; \ + template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, feed); \ + template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, render) + + +// +// END: Generic Search functionality +// + + +// +// START: Animal screen search +// +typedef df::viewscreen_petst::T_animal T_animal; +typedef df::viewscreen_petst::T_mode T_mode; + +class pets_search : public search_multicolumn_modifiable_generic +{ +public: + void render() const + { + if (viewscreen->mode == T_mode::List) + print_search_option(25, 4); + } + +private: + int32_t *get_viewscreen_cursor() + { + return &viewscreen->cursor; + } + + vector *get_primary_list() + { + return &viewscreen->animal; + } + + virtual void do_post_init() + { + is_vermin = &viewscreen->is_vermin; + pet_info = &viewscreen->pet_info; + is_tame = &viewscreen->is_tame; + is_adopting = &viewscreen->is_adopting; + } + + string get_element_description(df::viewscreen_petst::T_animal element) const + { + return get_unit_description(element.unit); + } + + bool should_check_input() + { + return viewscreen->mode == T_mode::List; + } + + void save_secondary_values() + { + is_vermin_s = *is_vermin; + pet_info_s = *pet_info; + is_tame_s = *is_tame; + is_adopting_s = *is_adopting; + } + + void reset_secondary_viewscreen_vectors() + { + is_vermin = NULL; + pet_info = NULL; + is_tame = NULL; + is_adopting = NULL; + } + + void update_saved_secondary_list_item(size_t i, size_t j) + { + is_vermin_s[i] = (*is_vermin)[j]; + pet_info_s[i] = (*pet_info)[j]; + is_tame_s[i] = (*is_tame)[j]; + is_adopting_s[i] = (*is_adopting)[j]; + } + + void clear_secondary_viewscreen_vectors() + { + is_vermin->clear(); + pet_info->clear(); + is_tame->clear(); + is_adopting->clear(); + } + + void add_to_filtered_secondary_lists(size_t i) + { + is_vermin->push_back(is_vermin_s[i]); + pet_info->push_back(pet_info_s[i]); + is_tame->push_back(is_tame_s[i]); + is_adopting->push_back(is_adopting_s[i]); + } + + void clear_secondary_saved_lists() + { + is_vermin_s.clear(); + pet_info_s.clear(); + is_tame_s.clear(); + is_adopting_s.clear(); + } + + void restore_secondary_values() + { + *is_vermin = is_vermin_s; + *pet_info = pet_info_s; + *is_tame = is_tame_s; + *is_adopting = is_adopting_s; + } + + bool is_match(T_animal &a, T_animal &b) + { + return a.unit == b.unit; + } + + bool is_match(vector &a, vector &b) + { + for (size_t i = 0; i < a.size(); i++) + { + if (!is_match(a[i], b[i])) + return false; + } + + return true; + } + + std::vector *is_vermin, is_vermin_s; + std::vector *pet_info, pet_info_s; + std::vector *is_tame, is_tame_s; + std::vector *is_adopting, is_adopting_s; +}; + +IMPLEMENT_HOOKS(df::viewscreen_petst, pets_search); + +// +// END: Animal screen search +// + + + +// +// START: Stocks screen search +// +class stocks_search : public search_generic +{ +public: + + void render() const + { + if (!viewscreen->in_group_mode) + print_search_option(2); + else + { + auto dim = Screen::getWindowSize(); + int x = 2, y = dim.y - 2; + OutputString(15, x, y, "Tab to enable Search"); + } + } + + bool process_input(set *input) + { + if (viewscreen->in_group_mode) + return false; + + redo_search = false; + + if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !viewscreen->in_right_list) + { + // Redo search if category changes + saved_list1.clear(); + end_entry_mode(); + if (search_string.length() > 0) + redo_search = true; + + return false; + } + + return search_generic::process_input(input); + } + + virtual void do_post_input_feed() + { + if (viewscreen->in_group_mode) + { + // Disable search if item lists are grouped + clear_search(); + reset_search(); + } + else if (redo_search) + { + do_search(); + redo_search = false; + } + } + +private: + int32_t *get_viewscreen_cursor() + { + return &viewscreen->item_cursor; + } + + virtual vector *get_primary_list() + { + return &viewscreen->items; + } + + +private: + string get_element_description(df::item *element) const + { + return Items::getDescription(element, 0, true); + } + + bool redo_search; +}; + + +IMPLEMENT_HOOKS(df::viewscreen_storesst, stocks_search); + +// +// END: Stocks screen search +// + + +// +// START: Unit screen search +// +class unitlist_search : public search_twocolumn_modifiable +{ +public: + void render() const + { + print_search_option(28); + } + +private: + void do_post_init() + { + search_twocolumn_modifiable::do_post_init(); + read_only = true; + } + + static string get_non_work_description(df::unit *unit) + { + for (auto p = unit->status.misc_traits.begin(); p < unit->status.misc_traits.end(); p++) + { + if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) + { + int i = (*p)->value; + return ".on break"; + } + } + + if (unit->profession == profession::BABY || + unit->profession == profession::CHILD || + unit->profession == profession::DRUNK) + { + return ""; + } + + if (ENUM_ATTR(profession, military, unit->profession)) + return ".military"; + + return ".idle.no job"; + } + + string get_element_description(df::unit *unit) const + { + string desc = get_unit_description(unit); + if (!unit->job.current_job) + { + desc += get_non_work_description(unit); + } + + return desc; + } + + bool should_check_input(set *input) + { + if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || + (!in_entry_mode() && input->count(interface_key::UNITVIEW_PRF_PROF))) + { + if (!in_entry_mode()) + { + // Changing screens, reset search + clear_search(); + reset_all(); + } + else + input->clear(); // Ignore cursor keys when typing + + return false; + } + + return true; + } + + vector *get_secondary_list() + { + return &viewscreen->jobs[viewscreen->page]; + } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->cursor_pos[viewscreen->page]; + } + + vector *get_primary_list() + { + return &viewscreen->units[viewscreen->page]; + } +}; + +IMPLEMENT_HOOKS(df::viewscreen_unitlistst, unitlist_search); + +// +// END: Unit screen search +// + + +// +// START: Trade screen search +// +class trade_search_base : public search_twocolumn_modifiable +{ + +private: + string get_element_description(df::item *element) const + { + return Items::getDescription(element, 0, true); + } + + bool should_check_input(set *input) + { + if (in_entry_mode()) + return true; + + if (input->count(interface_key::TRADE_TRADE) || + input->count(interface_key::TRADE_OFFER) || + input->count(interface_key::TRADE_SEIZE)) + { + // Block the keys if were searching + if (!search_string.empty()) + { + input->clear(); + // Send a force clear to other search class too + input->insert(interface_key::CUSTOM_ALT_C); + } + + clear_search_for_trade(); + return false; + } + else if (input->count(interface_key::CUSTOM_ALT_C)) + { + clear_search_for_trade(); return true; + } + + return true; + } + + void clear_search_for_trade() + { + // Trying to trade, reset search + clear_search(); + reset_all(); + } +}; + + +class trade_search_merc : public trade_search_base +{ +public: + virtual void render() const + { + print_search_option(2, 26); + } + +private: + vector *get_secondary_list() + { + return &viewscreen->trader_selected; + } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->trader_cursor; + } + + vector *get_primary_list() + { + return &viewscreen->trader_items; + } + + char get_search_select_key() + { + return 'q'; + } +}; + +IMPLEMENT_HOOKS_WITH_ID(df::viewscreen_tradegoodsst, trade_search_merc, 1); + + +class trade_search_fort : public trade_search_base +{ +public: + virtual void render() const + { + print_search_option(42, 26); + } + +private: + vector *get_secondary_list() + { + return &viewscreen->broker_selected; + } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->broker_cursor; + } + + vector *get_primary_list() + { + return &viewscreen->broker_items; + } + + char get_search_select_key() + { + return 'w'; + } +}; + +IMPLEMENT_HOOKS_WITH_ID(df::viewscreen_tradegoodsst, trade_search_fort, 2); - if (input->count(interface_key::TRADE_TRADE) || - input->count(interface_key::TRADE_OFFER) || - input->count(interface_key::TRADE_SEIZE)) - { - // Block the keys if were searching - if (!search_string.empty()) - input->clear(); +// +// END: Trade screen search +// - // Trying to trade, reset search - clear_search(); - reset_all(); - return false; - } +// +// START: Stockpile screen search +// +typedef layered_search stocks_layer; +class stockpile_search : public search_twocolumn_modifiable +{ +public: + void update_saved_secondary_list_item(size_t i, size_t j) + { + *saved_secondary_list[i] = *(*secondary_list)[j]; + } - return true; + string get_element_description(string *element) const + { + return *element; + } + + void render() const + { + print_search_option(51, 23); + } + + vector *get_primary_list() + { + return &viewscreen->item_names; + } + + vector *get_secondary_list() + { + return &viewscreen->item_status; } + + + }; +IMPLEMENT_HOOKS(df::viewscreen_layer_stockpilest, stockpile_search); -class trade_search_merc : public trade_search_base +// +// END: Stockpile screen search +// + + +// +// START: Military screen search +// +class military_search : public layered_search { public: - virtual void render() const + + string get_element_description(df::unit *element) const { - print_search_option(2, 26); + return get_unit_description(element); + } + + void render() const + { + print_search_option(52, 22); } - bool init(df::viewscreen_tradegoodsst *screen) + char get_search_select_key() { - if (screen != viewscreen && !reset_on_change()) + return 'q'; + } + + bool can_init(df::viewscreen_layer_militaryst *screen) + { + if (screen->page != df::viewscreen_layer_militaryst::Positions) return false; - if (!is_valid()) + return layered_search::can_init(screen); + } + + vector *get_primary_list() + { + return &viewscreen->positions.candidates; + } + + bool should_check_input(set *input) + { + if (input->count(interface_key::SELECT) && !in_entry_mode() && !search_string.empty()) { - viewscreen = screen; - search_parent::init(&screen->trader_cursor, &screen->trader_items, &screen->trader_selected, 'q'); - track_secondary_values = true; + // About to make an assignment, so restore original list (it will be changed by the game) + int32_t *cursor = get_viewscreen_cursor(); + df::unit *selected_unit = get_primary_list()->at(*cursor); + clear_search(); + + for (*cursor = 0; *cursor < get_primary_list()->size(); (*cursor)++) + { + if (get_primary_list()->at(*cursor) == selected_unit) + break; + } + + reset_all(); } return true; } }; -typedef search_hook trade_search_merc_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, feed); -template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, render); +IMPLEMENT_HOOKS(df::viewscreen_layer_militaryst, military_search); + +// +// END: Military screen search +// -class trade_search_fort : public trade_search_base +// +// START: Room list search +// +static map< df::building_type, vector > room_quality_names; +static int32_t room_value_bounds[] = {1, 100, 250, 500, 1000, 1500, 2500, 10000}; + +class roomlist_search : public search_twocolumn_modifiable { public: - virtual void render() const + void render() const { - print_search_option(42, 26); + print_search_option(2, 23); } - bool init(df::viewscreen_tradegoodsst *screen) +private: + void do_post_init() { - if (screen != viewscreen && !reset_on_change()) - return false; + search_twocolumn_modifiable::do_post_init(); + read_only = true; + } - if (!is_valid()) + string get_element_description(df::building *bld) const + { + bool is_ownable_room = (bld->is_room && room_quality_names.find(bld->getType()) != room_quality_names.end()); + + string desc; + desc.reserve(100); + if (bld->owner) + desc += get_unit_description(bld->owner); + else if (is_ownable_room) + desc += "no owner"; + + desc += "."; + + if (is_ownable_room) + { + int32_t value = bld->getRoomValue(NULL); + vector *names = &room_quality_names[bld->getType()]; + string *room_name = &names->at(0); + for (int i = 1; i < 8; i++) + { + if (room_value_bounds[i] > value) + break; + room_name = &names->at(i); + } + + desc += *room_name; + } + else { - viewscreen = screen; - search_parent::init(&screen->broker_cursor, &screen->broker_items, &screen->broker_selected, 'w'); - track_secondary_values = true; + string name; + bld->getName(&name); + if (!name.empty()) + { + desc += name; + } } + + return desc; + } - return true; + vector *get_secondary_list() + { + return &viewscreen->room_value; + } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->cursor; + } + + vector *get_primary_list() + { + return &viewscreen->buildings; } }; -typedef search_hook trade_search_fort_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, feed); -template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, render); +IMPLEMENT_HOOKS(df::viewscreen_buildinglistst, roomlist_search); // -// END: Trade screen search +// END: Room list search // + // -// START: Stockpile screen search +// START: Announcement list search // - -class stockpile_search : public search_parent +class annoucnement_search : public search_generic { public: - void update_saved_secondary_list_item(size_t i, size_t j) + void render() const { - *saved_list2[i] = *(*sort_list2)[j]; + print_search_option(2, gps->dimy - 3); } - string get_element_description(string *element) const +private: + int32_t *get_viewscreen_cursor() { - return *element; + return &viewscreen->anon_3; } - void render() const + virtual vector *get_primary_list() { - print_search_option(51, 23); + return &viewscreen->anon_4; + } + + +private: + string get_element_description(void *element) const + { + return ((df::report *) element)->text; + } +}; + + +IMPLEMENT_HOOKS(df::viewscreen_announcelistst, annoucnement_search); + +// +// END: Announcement list search +// + + +// +// START: Nobles search list +// +typedef df::viewscreen_layer_noblelistst::T_candidates T_candidates; + +class nobles_search : public layered_search +{ +public: + + string get_element_description(T_candidates *element) const + { + if (!element->unit) + return ""; + + return get_unit_description(element->unit); } - static df::layer_object_listst *getLayerList(const df::viewscreen_layer *layer, int idx) + void render() const { - return virtual_cast(vector_get(layer->layer_objects,idx)); + print_search_option(2, 23); } - bool init(df::viewscreen_layer_stockpilest *screen) + bool can_init(df::viewscreen_layer_noblelistst *screen) { - if (screen != viewscreen && !reset_on_change()) + if (screen->mode != df::viewscreen_layer_noblelistst::Appoint) return false; - auto list3 = getLayerList(screen, 2); - if (!list3->active) + return layered_search::can_init(screen); + } + + vector *get_primary_list() + { + return &viewscreen->candidates; + } +}; + +IMPLEMENT_HOOKS(df::viewscreen_layer_noblelistst, nobles_search); + +// +// END: Nobles search list +// + + +// +// START: Job list search +// +void get_job_details(string &desc, df::job *job) +{ + string job_name = ENUM_KEY_STR(job_type,job->job_type); + for (size_t i = 0; i < job_name.length(); i++) + { + char c = job_name[i]; + if (c >= 'A' && c <= 'Z') + desc += " "; + desc += c; + } + desc += "."; + + df::item_type itype = ENUM_ATTR(job_type, item, job->job_type); + + MaterialInfo mat(job); + if (itype == item_type::FOOD) + mat.decode(-1); + + if (mat.isValid() || job->material_category.whole) + { + desc += mat.toString(); + desc += "."; + if (job->material_category.whole) { - if (is_valid()) - { - clear_search(); - reset_all(); - } + desc += bitfield_to_string(job->material_category); + desc += "."; + } + } - return false; + if (!job->reaction_name.empty()) + { + for (size_t i = 0; i < job->reaction_name.length(); i++) + { + if (job->reaction_name[i] == '_') + desc += " "; + else + desc += job->reaction_name[i]; } - if (!is_valid()) + desc += "."; + } + + if (job->flags.bits.suspend) + desc += "suspended."; +} + +class joblist_search : public search_twocolumn_modifiable +{ +public: + void render() const + { + print_search_option(2); + } + +private: + void do_post_init() + { + search_twocolumn_modifiable::do_post_init(); + read_only = true; + } + + string get_element_description(df::job *element) const + { + if (!element) + return "no job.idle"; + + string desc; + desc.reserve(100); + get_job_details(desc, element); + df::unit *worker = DFHack::Job::getWorker(element); + if (worker) + desc += get_unit_description(worker); + else + desc += "Inactive"; + + return desc; + } + + vector *get_secondary_list() + { + return &viewscreen->units; + } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->cursor_pos; + } + + vector *get_primary_list() + { + return &viewscreen->jobs; + } +}; + +IMPLEMENT_HOOKS(df::viewscreen_joblistst, joblist_search); + +// +// END: Job list search +// + + +// +// START: Burrow assignment search +// +using df::global::ui; + +class burrow_search : public search_twocolumn_modifiable +{ +public: + bool can_init(df::viewscreen_dwarfmodest *screen) + { + if (ui->main.mode == df::ui_sidebar_mode::Burrows && ui->burrows.in_add_units_mode) { - viewscreen = screen; - search_parent::init(&list3->cursor, &screen->item_names, &screen->item_status); - track_secondary_values = true; + return search_twocolumn_modifiable::can_init(screen); + } + else if (is_valid()) + { + clear_search(); + reset_all(); } - return true; + return false; + } + + string get_element_description(df::unit *element) const + { + return get_unit_description(element); } - void do_search() + void render() const + { + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = 23; + + print_search_option(x, y); + } + + vector *get_primary_list() + { + return &ui->burrows.list_units; + } + + vector *get_secondary_list() { - search_parent::do_search(); - auto list3 = getLayerList(viewscreen, 2); - list3->num_entries = viewscreen->item_names.size(); + return &ui->burrows.sel_units; } - void clear_search() + virtual int32_t * get_viewscreen_cursor() { - search_parent::clear_search(); - auto list3 = getLayerList(viewscreen, 2); - list3->num_entries = viewscreen->item_names.size(); + return &ui->burrows.unit_cursor_pos; + } + + + bool should_check_input(set *input) + { + if (input->count(interface_key::SECONDSCROLL_UP) || input->count(interface_key::SECONDSCROLL_DOWN) + || input->count(interface_key::SECONDSCROLL_PAGEUP) || input->count(interface_key::SECONDSCROLL_PAGEDOWN)) + { + end_entry_mode(); + return false; + } + + return true; } }; -typedef search_hook stockpile_search_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE(stockpile_search_hook, feed); -template<> IMPLEMENT_VMETHOD_INTERPOSE(stockpile_search_hook, render); +IMPLEMENT_HOOKS(df::viewscreen_dwarfmodest, burrow_search); // -// END: Stockpile screen search +// END: Burrow assignment search // + DFHACK_PLUGIN("search"); +#define SEARCH_HOOKS \ + HOOK_ACTION(unitlist_search_hook) \ + HOOK_ACTION(roomlist_search_hook) \ + HOOK_ACTION(trade_search_merc_hook) \ + HOOK_ACTION(trade_search_fort_hook) \ + HOOK_ACTION(stocks_search_hook) \ + HOOK_ACTION(pets_search_hook) \ + HOOK_ACTION(military_search_hook) \ + HOOK_ACTION(nobles_search_hook) \ + HOOK_ACTION(annoucnement_search_hook) \ + HOOK_ACTION(joblist_search_hook) \ + HOOK_ACTION(burrow_search_hook) \ + HOOK_ACTION(stockpile_search_hook) DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - if (!gps || !gview || - !INTERPOSE_HOOK(unitlist_search_hook, feed).apply() || - !INTERPOSE_HOOK(unitlist_search_hook, render).apply() || - !INTERPOSE_HOOK(trade_search_merc_hook, feed).apply() || - !INTERPOSE_HOOK(trade_search_merc_hook, render).apply() || - !INTERPOSE_HOOK(trade_search_fort_hook, feed).apply() || - !INTERPOSE_HOOK(trade_search_fort_hook, render).apply() || - !INTERPOSE_HOOK(stocks_search_hook, feed).apply() || - !INTERPOSE_HOOK(stocks_search_hook, render).apply() || - !INTERPOSE_HOOK(stockpile_search_hook, feed).apply() || - !INTERPOSE_HOOK(stockpile_search_hook, render).apply()) +#define HOOK_ACTION(hook) \ + !INTERPOSE_HOOK(hook, feed).apply() || \ + !INTERPOSE_HOOK(hook, render).apply() || + + if (!gps || !gview || SEARCH_HOOKS 0) out.printerr("Could not insert Search hooks!\n"); +#undef HOOK_ACTION + + const string a[] = {"Meager Quarters", "Modest Quarters", "Quarters", "Decent Quarters", "Fine Quarters", "Great Bedroom", "Grand Bedroom", "Royal Bedroom"}; + room_quality_names[df::building_type::Bed] = vector(a, a + 8); + + const string b[] = {"Meager Dining Room", "Modest Dining Room", "Dining Room", "Decent Dining Room", "Fine Dining Room", "Great Dining Room", "Grand Dining Room", "Royal Dining Room"}; + room_quality_names[df::building_type::Table] = vector(b, b + 8); + + const string c[] = {"Meager Office", "Modest Office", "Office", "Decent Office", "Splendid Office", "Throne Room", "Opulent Throne Room", "Royal Throne Room"}; + room_quality_names[df::building_type::Chair] = vector(c, c + 8); + + const string d[] = {"Grave", "Servants Burial Chamber", "Burial Chamber", "Tomb", "Fine Tomb", "Mausoleum", "Grand Mausoleum", "Royal Mausoleum"}; + room_quality_names[df::building_type::Coffin] = vector(d, d + 8); + return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { - INTERPOSE_HOOK(unitlist_search_hook, feed).remove(); - INTERPOSE_HOOK(unitlist_search_hook, render).remove(); - INTERPOSE_HOOK(trade_search_merc_hook, feed).remove(); - INTERPOSE_HOOK(trade_search_merc_hook, render).remove(); - INTERPOSE_HOOK(trade_search_fort_hook, feed).remove(); - INTERPOSE_HOOK(trade_search_fort_hook, render).remove(); - INTERPOSE_HOOK(stocks_search_hook, feed).remove(); - INTERPOSE_HOOK(stocks_search_hook, render).remove(); - INTERPOSE_HOOK(stockpile_search_hook, feed).remove(); - INTERPOSE_HOOK(stockpile_search_hook, render).remove(); +#define HOOK_ACTION(hook) \ + INTERPOSE_HOOK(hook, feed).remove(); \ + INTERPOSE_HOOK(hook, render).remove(); + + SEARCH_HOOKS + +#undef HOOK_ACTION + return CR_OK; } DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event event ) { +#define HOOK_ACTION(hook) hook::module.reset_on_change(); + switch (event) { case SC_VIEWSCREEN_CHANGED: - unitlist_search_hook::module.reset_on_change(); - trade_search_merc_hook::module.reset_on_change(); - trade_search_fort_hook::module.reset_on_change(); - stocks_search_hook::module.reset_on_change(); - stockpile_search_hook::module.reset_on_change(); + SEARCH_HOOKS break; default: @@ -800,4 +1604,9 @@ DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_ch } return CR_OK; + +#undef HOOK_ACTION } + +#undef IMPLEMENT_HOOKS +#undef SEARCH_HOOKS