summaryrefslogblamecommitdiffstatshomepage
path: root/src/frontend/mame/ui/utils.cpp
blob: 58ee1e09f8d4706fc4824c3f1cca3a31439b7678 (plain) (tree)
1
2
3
4
5
6
7
                       
                                                  

                                                                            
                
 
                               




                                                                            
 
                       

                        
                     
                 
 

                     
                    

                     
                 
                 
                             
                  
                  
                   
                        
 
 



              





















                                                                     









                                     
                               

                              

                                   







                                        
                                                                       


                                  
                                

                              

                                







                                          
















































































































                                                                                                                                                             


                                                                                           










                                                                                                                               
                                                                
















                                                                                    

                                                                                                                                 











































                                                                                                                                                                

                                                  




                                                                          
                         
                                                   






                                              



































                                                                                                             




                                                                             










                                                                                                                                     
                                                                                 
                                                                                               
                                                                                          
















































































































































                                                                                                                                    
                                                                                                                                                                 
                            
                                                             

















                                                                                                                                                  
                                                                         


















































































































                                                                                                                                                                                      



                                                                                          
                                                                                                                              




                                                                                                    



                                                                                        
                                                                                                                            
 
                                                                                                                                        






                                                                                           
                                                                                                                               
 
                                                                                                                                    


  



                                                                                     
                                                                                                                         
 
                                                                                                                                      


  



                                                                                        
                                                                                                                            
 
                                                                       
         

                                                                                                   








                                                                                                                                 
                                                                                                                        
 
                                                                       
         
                                                                                                 
                 
                                                                                










                                                                                     
                                                                                                                         
 
                                                                                                                                       






                                                                                         
                                                                                                                             
 
                                                                                                                                 







                                                   


                                                                                                                

                                                                                                                        


         
                                                                       





                                            
                                                                                                               







                                                                                                

                                                                                                                


         
                                                                                                                                                                       











                                                                                  



                                                                                                                    
 
                                                                                                              

  
                                                                      

                                                                       
                                                                 




                                                                     
                                                                                                                          

                                                                                                                              
                                                                                                                  














                                                                                                                        
                                                                                                                              
 
                                                                                        


                                                                                         




                                                                                              





                                                                                                        
                                                                                                                    





























































                                                                                                                                           
                                                                       










                                                                                                               
                                                                 



                                     
                                                                                  


















































                                                                                                                                               
                                                                                 
                                                                                               
                                                                                          





















































































                                                                                                                                                                                                           
                                                                         








































































































                                                                                                                       





                                                                                                                              
                                                                                                                  
                                                                                                             
                              



                                              

                                                                                                                  


                                                      
                                                                                                     











                                                                   

                                                             











                                                               










                                        
                                                                             
         


                                          










                                                                                                 
                                                                                                                     







                                                                                                             
                                                                                                                           







                                                                                                                 
                                                                                                                             




                                                                                                   














                                                                                                                        


                                                                                                         
                                                                                                                         







                                                                                                           
                                                                                                                        







                                                                                                            

                                                                                                                   









                                                                                                                                                             

                                                                                                                        









                                                                       
                                                                                                

                                                                   





                                                                                                             
                                                                                                                           








                                                                                                                             
                                                                                                                                   







                                                                                                                                
                                                                                                                             







                                                                                                                           

                                                                                                                    









                                                                       
                                                                                            

                                                                   





                                                                                                                 

                                                                                                                         









                                                                                                                                                                   


                                                                                                                  


         






                                                                                                                           










                                                                                                                                  
                                                                                                                    

                                                                                                                
























                                                                                                                   
                              

















                                                                                                              
                                           

  


                        

                                                   















































































                                                                                                                                                                   


                                                   
                                                                  
 
                                                   
                                                                                                                 
                                                       


                                                        
                                                                      
 
                                                       




























































                                                                                                                                  


                                                   
                                                                                                                                       




                          
                                                                                       
                       
                                                                                             
                         
                                                                                               
                     
                                                                                           
                         
                                                                                               
                        
                                                                                              
                            
                                                                                                  
                      
                                                                                            
                      
                                                                                            
                  
                                                                                        
                      
                                                                                            
                     
                                                                                           
                    
                                                                                          
                          
                                                                                                
                  
                                                                                        
                  
                                                                                        
                    
                                                                                          
                 
                                                                                       
                   
                                                                                         
                      
                                                                                            
                        
                                                                                              
                    
                                                                                          





                                                                 
                                                                                                            

                                   

                                                     



















                                                                                                          
                                                                             



                       


                                               
                                       




                                                
                                          

 







                                                   



                                                
                                        




                                                 
                                           

 



                                  
                                                                                                                                          









                                                                                                

                                                                                             



























                                                                                                      
                                                                                                               

                                   

                                                     
























                                                                                                          


                 

                                           
 
          
                               



                                          
                               

                                       
                                       
 












                                        
                                     

 








                                                      


                                                                    
                           
                                       
                                                                  
         
                                                                   


                                                          
                                              



                                                  


































                                                                                                    
// license:BSD-3-Clause
// copyright-holders:Maurizio Petrarota, Vas Crabb
/***************************************************************************

    ui/utils.cpp

    Internal UI user interface.

***************************************************************************/

#include "emu.h"
#include "ui/utils.h"

#include "ui/inifile.h"
#include "ui/selector.h"

#include "language.h"
#include "mame.h"

#include "drivenum.h"
#include "rendfont.h"
#include "romload.h"
#include "softlist.h"

#include <atomic>
#include <bitset>
#include <condition_variable>
#include <cstdlib>
#include <cstring>
#include <iterator>
#include <unordered_set>


namespace ui {

namespace {

constexpr char const *SOFTWARE_REGIONS[] = {
		"arab", "arg", "asia", "aus", "aut",
		"bel", "blr", "bra",
		"can", "chi", "chn", "cze",
		"den",
		"ecu", "esp", "euro",
		"fin", "fra",
		"gbr", "ger", "gre",
		"hkg", "hun",
		"irl", "isr", "isv", "ita",
		"jpn",
		"kaz", "kor",
		"lat", "lux",
		"mex",
		"ned", "nld", "nor", "nzl",
		"pol",
		"rus",
		"slo", "spa", "sui", "swe",
		"tha", "tpe", "tw",
		"uk", "ukr", "usa" };

constexpr char const *MACHINE_FILTER_NAMES[machine_filter::COUNT] = {
		__("Unfiltered"),
		__("Available"),
		__("Unavailable"),
		__("Working"),
		__("Not Working"),
		__("Mechanical"),
		__("Not Mechanical"),
		__("Category"),
		__("Favorites"),
		__("BIOS"),
		__("Not BIOS"),
		__("Parents"),
		__("Clones"),
		__("Manufacturer"),
		__("Year"),
		__("Save Supported"),
		__("Save Unsupported"),
		__("CHD Required"),
		__("No CHD Required"),
		__("Vertical Screen"),
		__("Horizontal Screen"),
		__("Custom Filter") };

constexpr char const *SOFTWARE_FILTER_NAMES[software_filter::COUNT] = {
		__("Unfiltered"),
		__("Available"),
		__("Unavailable"),
		__("Favorites"),
		__("Parents"),
		__("Clones"),
		__("Year"),
		__("Publisher"),
		__("Supported"),
		__("Partially Supported"),
		__("Unsupported"),
		__("Release Region"),
		__("Device Type"),
		__("Software List"),
		__("Custom Filter") };



//-------------------------------------------------
//  base implementation for simple filters
//-------------------------------------------------

template <class Base, typename Base::type Type>
class simple_filter_impl_base : public Base
{
public:
	virtual char const *config_name() const override { return Base::config_name(Type); }
	virtual char const *display_name() const override { return Base::display_name(Type); }
	virtual char const *filter_text() const override { return nullptr; }

	virtual void show_ui(mame_ui_manager &mui, render_container &container, std::function<void (Base &)> &&handler) override
	{
		handler(*this);
	}

	virtual bool wants_adjuster() const override { return false; }
	virtual char const *adjust_text() const override { return filter_text(); }
	virtual uint32_t arrow_flags() const override { return 0; }
	virtual bool adjust_left() override { return false; }
	virtual bool adjust_right() override { return false; }

	virtual void save_ini(emu_file &file, unsigned indent) const override
	{
		file.puts(util::string_format("%2$*1$s%3$s = 1\n", 2 * indent, "", config_name()).c_str());
	}

	virtual typename Base::type get_type() const override { return Type; }

	virtual std::string adorned_display_name(typename Base::type n) const override
	{
		std::string result;
		if (Type == n)
		{
			result = "_> ";
			convert_command_glyph(result);
		}
		result.append(Base::display_name(n));
		return result;
	}

	using Base::config_name;
	using Base::display_name;

protected:
	simple_filter_impl_base() { }
};



//-------------------------------------------------
//  base implementation for single-choice filters
//-------------------------------------------------

template <class Base, typename Base::type Type>
class choice_filter_impl_base : public simple_filter_impl_base<Base, Type>
{
public:
	virtual char const *filter_text() const override { return selection_valid() ? selection_text().c_str() : nullptr; }

	virtual void show_ui(mame_ui_manager &mui, render_container &container, std::function<void (Base &)> &&handler) override
	{
		if (m_choices.empty())
		{
			handler(*this);
		}
		else
		{
			menu::stack_push<menu_selector>(
					mui, container,
					std::vector<std::string>(m_choices), // ouch, a vector copy!
					m_selection,
					[this, cb = std::move(handler)] (int selection)
					{
						m_selection = selection;
						cb(*this);
					});
		}
	}

	virtual bool wants_adjuster() const override { return have_choices(); }

	virtual uint32_t arrow_flags() const override
	{
		return ((have_choices() && m_selection) ? menu::FLAG_LEFT_ARROW : 0) | ((m_choices.size() > (m_selection + 1)) ? menu::FLAG_RIGHT_ARROW : 0);
	}

	virtual bool adjust_left() override
	{
		if (!have_choices() || !m_selection)
			return false;
		m_selection = (std::min)(m_selection - 1, unsigned(m_choices.size() - 1));
		return true;
	}

	virtual bool adjust_right() override
	{
		if (m_choices.size() <= (m_selection + 1))
			return false;
		++m_selection;
		return true;
	}

	virtual void save_ini(emu_file &file, unsigned indent) const override
	{
		char const *const text(filter_text());
		file.puts(util::string_format("%2$*1$s%3$s = %4$s\n", 2 * indent, "", this->config_name(), text ? text : "").c_str());
	}

protected:
	choice_filter_impl_base(std::vector<std::string> const &choices, char const *value)
		: m_choices(choices)
		, m_selection(0U)
	{
		if (value)
		{
			std::vector<std::string>::const_iterator const found(std::find(choices.begin(), choices.end(), value));
			if (choices.end() != found)
				m_selection = std::distance(choices.begin(), found);
		}
	}

	bool have_choices() const { return !m_choices.empty(); }
	bool selection_valid() const { return m_choices.size() > m_selection; }
	unsigned selection_index() const { return m_selection; }
	std::string const &selection_text() const { return m_choices[m_selection]; }

private:
	std::vector<std::string> const &m_choices;
	unsigned m_selection;
};



//-------------------------------------------------
//  base implementation for composite filters
//-------------------------------------------------

template <class Impl, class Base, typename Base::type Type>
class composite_filter_impl_base : public simple_filter_impl_base<Base, Type>
{
public:
	virtual void show_ui(mame_ui_manager &mui, render_container &container, std::function<void (Base &)> &&handler) override;

	virtual bool wants_adjuster() const override { return true; }
	virtual char const *adjust_text() const override { return _("<set up filters>"); }

	virtual void save_ini(emu_file &file, unsigned indent) const override
	{
		auto const tail(std::find_if(std::begin(m_filters), std::end(m_filters), [] (typename Base::ptr const &flt) { return !flt; }));
		file.puts(util::string_format("%2$*1$s%3$s = %4$d\n", 2 * indent, "", this->config_name(), std::distance(std::begin(m_filters), tail)).c_str());
		for (auto it = std::begin(m_filters); tail != it; ++it)
			(*it)->save_ini(file, indent + 1);
	}

	virtual std::string adorned_display_name(typename Base::type n) const override
	{
		std::string result;
		if (Type == n)
		{
			result = "_> ";
			convert_command_glyph(result);
		}
		else
		{
			for (unsigned i = 0; (MAX > i) && m_filters[i]; ++i)
			{
				if (m_filters[i]->get_type() == n)
				{
					result = util::string_format("@custom%u ", i + 1);
					convert_command_glyph(result);
					break;
				}
			}
		}
		result.append(Base::display_name(n));
		return result;
	}

	virtual bool apply(typename Base::entry_type const &info) const override
	{
		std::bitset<Base::COUNT> inclusions, included;
		for (typename Base::ptr const &flt : m_filters)
		{
			if (!flt)
				break;

			typename Base::type const t(flt->get_type());
			if (Impl::is_inclusion(t))
			{
				if (!included.test(t))
				{
					inclusions.set(t);
					included.set(t, flt->apply(info));
				}
			}
			else if (!flt->apply(info))
			{
				return false;
			}
		}
		return inclusions == included;
	}

protected:
	composite_filter_impl_base() { }

	void populate(char const *value, emu_file *file, unsigned indent)
	{
		// try to load filters from a file
		if (value && file)
		{
			unsigned const cnt(unsigned((std::max)(std::min(int(MAX), std::atoi(value)), 0)));
			for (unsigned i = 0; cnt > i; ++i)
			{
				typename Base::ptr flt(static_cast<Impl &>(*this).create(*file, indent + 1));
				if (!flt || !check_type(i, flt->get_type()))
					break;
				m_filters[i] = std::move(flt);
			}
		}

		// instantiate first allowed filter type if we're still empty
		for (typename Base::type t = Base::FIRST; (Base::COUNT > t) && !m_filters[0]; ++t)
		{
			if (Impl::type_allowed(0, t))
				m_filters[0] = static_cast<Impl &>(*this).create(t);
		}
	}

private:
	static constexpr unsigned MAX = 8;

	class menu_configure : public menu
	{
	public:
		menu_configure(
				mame_ui_manager &mui,
				render_container &container,
				Impl &parent,
				std::function<void (Base &filter)> &&handler)
			: menu(mui, container)
			, m_parent(parent)
			, m_handler(std::move(handler))
			, m_added(false)
		{
		}

		virtual ~menu_configure() override { m_handler(m_parent); }

	protected:
		virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override
		{
			char const *const text[] = { _("Select custom filters:") };
			draw_text_box(
					std::begin(text), std::end(text),
					x, x2, y - top, y - ui().box_tb_border(),
					ui::text_layout::CENTER, ui::text_layout::NEVER, false,
					ui().colors().text_color(), UI_GREEN_COLOR, 1.0f);
		}

	private:
		enum : uintptr_t
		{
			FILTER_FIRST = 1,
			FILTER_LAST = FILTER_FIRST + MAX - 1,
			ADJUST_FIRST,
			ADJUST_LAST = ADJUST_FIRST + MAX - 1,
			REMOVE_FILTER,
			ADD_FILTER
		};

		virtual void populate(float &customtop, float &custombottom) override;
		virtual void handle() override;

		bool set_filter_type(unsigned pos, typename Base::type n)
		{
			if (!m_parent.m_filters[pos] || (m_parent.m_filters[pos]->get_type()))
			{
				save_filter(pos);
				retrieve_filter(pos, n);
				return true;
			}
			else
			{
				return false;
			}
		}

		bool append_filter()
		{
			unsigned pos = 0;
			while (m_parent.m_filters[pos])
			{
				if (MAX <= ++pos)
					return false;
			}
			for (typename Base::type candidate = Base::FIRST; Base::COUNT > candidate; ++candidate)
			{
				if (m_parent.check_type(pos, candidate))
				{
					set_filter_type(pos, candidate);
					return true;
				}
			}
			return false;
		}

		bool drop_last_filter()
		{
			for (unsigned i = 2; MAX >= i; ++i)
			{
				if ((MAX <= i) || !m_parent.m_filters[i])
				{
					save_filter(i - 1);
					m_parent.m_filters[i - 1].reset();
					return true;
				}
			}
			return false;
		}

		void save_filter(unsigned pos)
		{
			typename Base::ptr &flt(m_parent.m_filters[pos]);
			if (flt && flt->wants_adjuster())
				m_saved_filters[pos][flt->get_type()] = std::move(flt);
		}

		void retrieve_filter(unsigned pos, typename Base::type n)
		{
			typename Base::ptr &flt(m_parent.m_filters[pos]);
			auto const found(m_saved_filters[pos].find(n));
			if (m_saved_filters[pos].end() != found)
			{
				flt = std::move(found->second);
				m_saved_filters[pos].erase(found);
			}
			else
			{
				flt = m_parent.create(n);
			}
		}

		uint32_t get_arrow_flags(unsigned pos)
		{
			uint32_t result(0);
			typename Base::type const current(m_parent.m_filters[pos]->get_type());

			// look for a lower type that's allowed and isn't contradictory
			typename Base::type prev(current);
			while ((Base::FIRST < prev) && !(FLAG_LEFT_ARROW & result))
			{
				if (m_parent.check_type(pos, --prev))
					result |= FLAG_LEFT_ARROW;
			}

			// look for a higher type that's allowed and isn't contradictory
			typename Base::type next(current);
			while ((Base::LAST > next) && !(FLAG_RIGHT_ARROW & result))
			{
				if (m_parent.check_type(pos, ++next))
					result |= FLAG_RIGHT_ARROW;
			}

			return result;
		}

		Impl &m_parent;
		std::map<typename Base::type, typename Base::ptr> m_saved_filters[MAX];
		std::function<void (Base &)> m_handler;
		bool m_added;
	};

	bool check_type(unsigned pos, typename Base::type candidate)
	{
		if (!Impl::type_allowed(pos, candidate))
			return false;
		unsigned j = 0;
		while ((MAX > j) && m_filters[j] && ((pos == j) || !Impl::types_contradictory(m_filters[j]->get_type(), candidate)))
			++j;
		return (MAX <= j) || !m_filters[j];
	};

	typename Base::ptr m_filters[MAX];
};

template <class Impl, class Base, typename Base::type Type>
void composite_filter_impl_base<Impl, Base, Type>::show_ui(
		mame_ui_manager &mui,
		render_container &container,
		std::function<void (Base &filter)> &&handler)
{
	menu::stack_push<menu_configure>(mui, container, static_cast<Impl &>(*this), std::move(handler));
}


template <class Impl, class Base, typename Base::type Type>
void composite_filter_impl_base<Impl, Base, Type>::menu_configure::populate(float &customtop, float &custombottom)
{
	// add items for each active filter
	unsigned i = 0;
	for (i = 0; (MAX > i) && m_parent.m_filters[i]; ++i)
	{
		item_append(util::string_format(_("Filter %1$u"), i + 1), m_parent.m_filters[i]->display_name(), get_arrow_flags(i), (void *)(FILTER_FIRST + i));
		if (m_added)
			set_selected_index(item_count() - 2);
		if (m_parent.m_filters[i]->wants_adjuster())
		{
			std::string name("^!");
			convert_command_glyph(name);
			item_append(name, m_parent.m_filters[i]->adjust_text(), m_parent.m_filters[i]->arrow_flags(), (void *)(ADJUST_FIRST + i));
		}
		item_append(menu_item_type::SEPARATOR);
	}
	m_added = false;

	// add remove/add handlers
	if (1 < i)
		item_append(_("Remove last filter"), "", 0, (void *)REMOVE_FILTER);
	if (MAX > i)
		item_append(_("Add filter"), "", 0, (void *)ADD_FILTER);
	item_append(menu_item_type::SEPARATOR);

	// leave space for heading
	customtop = ui().get_line_height() + 3.0f * ui().box_tb_border();
}

template <class Impl, class Base, typename Base::type Type>
void composite_filter_impl_base<Impl, Base, Type>::menu_configure::handle()
{
	const event *menu_event = process(PROCESS_LR_REPEAT);
	if (menu_event && menu_event->itemref)
	{
		m_added = false;
		bool changed(false);
		uintptr_t const ref(reinterpret_cast<uintptr_t>(menu_event->itemref));
		switch (menu_event->iptkey)
		{
		case IPT_UI_LEFT:
		case IPT_UI_RIGHT:
			if ((FILTER_FIRST <= ref) && (FILTER_LAST >= ref))
			{
				// change filter type
				unsigned const pos(ref - FILTER_FIRST);
				typename Base::type const current(m_parent.m_filters[pos]->get_type());
				if (IPT_UI_LEFT == menu_event->iptkey)
				{
					typename Base::type n(current);
					while ((Base::FIRST < n) && !changed)
					{
						if (m_parent.check_type(pos, --n))
							changed = set_filter_type(pos, n);
					}
				}
				else
				{
					typename Base::type n(current);
					while ((Base::LAST > n) && !changed)
					{
						if (m_parent.check_type(pos, ++n))
							changed = set_filter_type(pos, n);
					}
				}
			}
			else if ((ADJUST_FIRST <= ref) && (ADJUST_LAST >= ref))
			{
				// change filter value
				Base &pos(*m_parent.m_filters[ref - ADJUST_FIRST]);
				changed = (IPT_UI_LEFT == menu_event->iptkey) ? pos.adjust_left() : pos.adjust_right();
			}
			break;

		case IPT_UI_SELECT:
			if ((FILTER_FIRST <= ref) && (FILTER_LAST >= ref))
			{
				// show selector with non-contradictory types
				std::vector<typename Base::type> types;
				std::vector<std::string> names;
				types.reserve(Base::COUNT);
				names.reserve(Base::COUNT);
				int sel(-1);
				unsigned const pos(ref - FILTER_FIRST);
				typename Base::type const current(m_parent.m_filters[pos]->get_type());
				for (typename Base::type candidate = Base::FIRST; Base::COUNT > candidate; ++candidate)
				{
					if (Impl::type_allowed(pos, candidate))
					{
						if (current == candidate)
							sel = types.size();
						unsigned i = 0;
						while ((MAX > i) && m_parent.m_filters[i] && ((pos == i) || !Impl::types_contradictory(m_parent.m_filters[i]->get_type(), candidate)))
							++i;
						if ((MAX <= i) || !m_parent.m_filters[i])
						{
							types.emplace_back(candidate);
							names.emplace_back(Base::display_name(candidate));
						}
					}
				}
				menu::stack_push<menu_selector>(
						ui(),
						container(),
						std::move(names),
						sel,
						[this, pos, t = std::move(types)] (int selection)
						{
							if (set_filter_type(pos, t[selection]))
								reset(reset_options::REMEMBER_REF);
						});
			}
			else if ((ADJUST_FIRST <= ref) && (ADJUST_LAST >= ref))
			{
				// show selected filter's UI
				m_parent.m_filters[ref - ADJUST_FIRST]->show_ui(ui(), container(), [this] (Base &filter) { reset(reset_options::REMEMBER_REF); });
			}
			else if (REMOVE_FILTER == ref)
			{
				changed = drop_last_filter();
			}
			else if (ADD_FILTER == ref)
			{
				m_added = append_filter();
			}
			break;
		}

		// rebuild if anything changed
		if (changed)
			reset(reset_options::REMEMBER_REF);
		else if (m_added)
			reset(reset_options::SELECT_FIRST);
	}
}



//-------------------------------------------------
//  invertable machine filters
//-------------------------------------------------

template <machine_filter::type Type = machine_filter::AVAILABLE>
class available_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type>
{
public:
	available_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_system_info const &system) const override { return system.available; }
};


template <machine_filter::type Type = machine_filter::WORKING>
class working_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type>
{
public:
	working_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_system_info const &system) const override { return !(system.driver->flags & machine_flags::NOT_WORKING); }
};


template <machine_filter::type Type = machine_filter::MECHANICAL>
class mechanical_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type>
{
public:
	mechanical_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::MECHANICAL; }
};


template <machine_filter::type Type = machine_filter::BIOS>
class bios_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type>
{
public:
	bios_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::IS_BIOS_ROOT; }
};


template <machine_filter::type Type = machine_filter::PARENTS>
class parents_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type>
{
public:
	parents_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_system_info const &system) const override
	{
		bool const have_parent(strcmp(system.driver->parent, "0"));
		auto const parent_idx(have_parent ? driver_list::find(system.driver->parent) : -1);
		return !have_parent || (0 > parent_idx) || (driver_list::driver(parent_idx).flags & machine_flags::IS_BIOS_ROOT);
	}
};


template <machine_filter::type Type = machine_filter::CHD>
class chd_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type>
{
public:
	chd_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_system_info const &system) const override
	{
		for (tiny_rom_entry const *rom = system.driver->rom; !ROMENTRY_ISEND(rom); ++rom)
		{
			if (ROMENTRY_ISREGION(rom) && ROMREGION_ISDISKDATA(rom))
				return true;
		}
		return false;
	}
};


template <machine_filter::type Type = machine_filter::SAVE>
class save_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type>
{
public:
	save_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::SUPPORTS_SAVE; }
};


template <machine_filter::type Type = machine_filter::VERTICAL>
class vertical_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type>
{
public:
	vertical_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::SWAP_XY; }
};



//-------------------------------------------------
//  concrete machine filters
//-------------------------------------------------

class manufacturer_machine_filter : public choice_filter_impl_base<machine_filter, machine_filter::MANUFACTURER>
{
public:
	manufacturer_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent)
		: choice_filter_impl_base<machine_filter, machine_filter::MANUFACTURER>(data.manufacturers(), value)
	{
	}

	virtual bool apply(ui_system_info const &system) const override
	{
		if (!have_choices())
			return true;
		else if (!selection_valid())
			return false;

		std::string const name(machine_filter_data::extract_manufacturer(system.driver->manufacturer));
		return !name.empty() && (selection_text() == name);
	}
};


class year_machine_filter : public choice_filter_impl_base<machine_filter, machine_filter::YEAR>
{
public:
	year_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent)
		: choice_filter_impl_base<machine_filter, machine_filter::YEAR>(data.years(), value)
	{
	}

	virtual bool apply(ui_system_info const &system) const override { return !have_choices() || (selection_valid() && (selection_text() == system.driver->year)); }
};



//-------------------------------------------------
//  complementary machine filters
//-------------------------------------------------

template <template <machine_filter::type T> class Base, machine_filter::type Type>
class inverted_machine_filter : public Base<Type>
{
public:
	inverted_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent)
		: Base<Type>(data, value, file, indent)
	{
	}

	virtual bool apply(ui_system_info const &system) const override { return !Base<Type>::apply(system); }
};

using available_machine_filter      = available_machine_filter_impl<>;
using working_machine_filter        = working_machine_filter_impl<>;
using mechanical_machine_filter     = mechanical_machine_filter_impl<>;
using bios_machine_filter           = bios_machine_filter_impl<>;
using parents_machine_filter        = parents_machine_filter_impl<>;
using save_machine_filter           = save_machine_filter_impl<>;
using chd_machine_filter            = chd_machine_filter_impl<>;
using vertical_machine_filter       = vertical_machine_filter_impl<>;

using unavailable_machine_filter    = inverted_machine_filter<available_machine_filter_impl, machine_filter::UNAVAILABLE>;
using not_working_machine_filter    = inverted_machine_filter<working_machine_filter_impl, machine_filter::NOT_WORKING>;
using not_mechanical_machine_filter = inverted_machine_filter<mechanical_machine_filter_impl, machine_filter::NOT_MECHANICAL>;
using not_bios_machine_filter       = inverted_machine_filter<bios_machine_filter_impl, machine_filter::NOT_BIOS>;
using clones_machine_filter         = inverted_machine_filter<parents_machine_filter_impl, machine_filter::CLONES>;
using nosave_machine_filter         = inverted_machine_filter<save_machine_filter_impl, machine_filter::NOSAVE>;
using nochd_machine_filter          = inverted_machine_filter<chd_machine_filter_impl, machine_filter::NOCHD>;
using horizontal_machine_filter     = inverted_machine_filter<vertical_machine_filter_impl, machine_filter::HORIZONTAL>;



//-------------------------------------------------
//  dummy machine filters (special-cased in menu)
//-------------------------------------------------

template <machine_filter::type Type>
class inclusive_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type>
{
public:
	inclusive_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_system_info const &system) const override { return true; }
};

using all_machine_filter            = inclusive_machine_filter_impl<machine_filter::ALL>;
using favorite_machine_filter       = inclusive_machine_filter_impl<machine_filter::FAVORITE>;



//-------------------------------------------------
//  category machine filter
//-------------------------------------------------

class category_machine_filter : public simple_filter_impl_base<machine_filter, machine_filter::CATEGORY>
{
public:
	category_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent)
		: m_ini(0)
		, m_group(0)
		, m_include_clones(false)
		, m_adjust_text()
		, m_cache()
		, m_cache_valid(false)
	{
		inifile_manager const &mgr(mame_machine_manager::instance()->inifile());
		if (value)
		{
			char const *const split(std::strchr(value, '/'));
			std::string ini;
			if (split)
				ini.assign(value, split);
			else
				ini.assign(value);

			for (unsigned i = 0; mgr.get_file_count() > i; ++i)
			{
				if (mgr.get_file_name(i) == ini)
				{
					m_ini = i;
					if (split)
					{
						std::string const group(split + 1);
						for (unsigned j = 0; mgr.get_category_count(i) > j; ++j)
						{
							if (mgr.get_category_name(i, j) == group)
							{
								m_group = j;
								break;
							}
						}
					}
					break;
				}
			}
		}

		if (mgr.get_file_count() > m_ini)
			m_include_clones = include_clones_default(mgr.get_file_name(m_ini));

		set_adjust_text();
	}

	virtual char const *filter_text() const override
	{
		inifile_manager const &mgr(mame_machine_manager::instance()->inifile());
		return ((mgr.get_file_count() > m_ini) && (mgr.get_category_count(m_ini) > m_group)) ? m_adjust_text.c_str() : nullptr;
	}

	virtual void show_ui(mame_ui_manager &mui, render_container &container, std::function<void (machine_filter &)> &&handler) override;

	virtual bool wants_adjuster() const override { return mame_machine_manager::instance()->inifile().get_file_count(); }
	virtual char const *adjust_text() const override { return m_adjust_text.c_str(); }

	virtual void save_ini(emu_file &file, unsigned indent) const override
	{
		char const *const text(filter_text());
		file.puts(util::string_format("%2$*1$s%3$s = %4$s\n", 2 * indent, "", this->config_name(), text ? text : "").c_str());
	}

	virtual bool apply(ui_system_info const &system) const override
	{
		inifile_manager const &mgr(mame_machine_manager::instance()->inifile());
		if (!mgr.get_file_count())
			return true;
		else if ((mgr.get_file_count() <= m_ini) || (mgr.get_category_count(m_ini) <= m_group))
			return false;

		if (!m_cache_valid)
			mame_machine_manager::instance()->inifile().load_ini_category(m_ini, m_group, m_cache);
		m_cache_valid = true;

		if (m_cache.end() != m_cache.find(system.driver))
			return true;

		if (m_include_clones)
		{
			int const found(driver_list::find(system.driver->parent));
			return m_cache.end() != m_cache.find(&driver_list::driver(found));
		}

		return false;
	}

private:
	class menu_configure : public menu
	{
	public:
		menu_configure(
				mame_ui_manager &mui,
				render_container &container,
				category_machine_filter &parent,
				std::function<void (machine_filter &filter)> &&handler)
			: menu(mui, container)
			, m_parent(parent)
			, m_handler(std::move(handler))
			, m_state(std::make_unique<std::pair<unsigned, bool> []>(mame_machine_manager::instance()->inifile().get_file_count()))
			, m_ini(parent.m_ini)
		{
			inifile_manager const &mgr(mame_machine_manager::instance()->inifile());
			for (size_t i = 0; mgr.get_file_count() > i; ++i)
			{
				m_state[i].first = (m_ini == i) ? m_parent.m_group : 0U;
				m_state[i].second = (m_ini == i) ? m_parent.m_include_clones : include_clones_default(mgr.get_file_name(i));
			}
		}

		virtual ~menu_configure() override
		{
			bool const valid(mame_machine_manager::instance()->inifile().get_file_count() > m_ini);
			unsigned const group(valid ? m_state[m_ini].first : 0);
			if ((m_ini != m_parent.m_ini) || (group != m_parent.m_group))
			{
				m_parent.m_cache.clear();
				m_parent.m_cache_valid = false;
			}
			m_parent.m_ini = m_ini;
			m_parent.m_group = group;
			m_parent.m_include_clones = valid ? m_state[m_ini].second : false;
			m_parent.set_adjust_text();
			m_handler(m_parent);
		}

	protected:
		virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override
		{
			char const *const text[] = { _("Select category:") };
			draw_text_box(
					std::begin(text), std::end(text),
					x, x2, y - top, y - ui().box_tb_border(),
					ui::text_layout::CENTER, ui::text_layout::NEVER, false,
					ui().colors().text_color(), UI_GREEN_COLOR, 1.0f);
		}

	private:
		enum : uintptr_t
		{
			INI_FILE = 1,
			SYSTEM_GROUP,
			INCLUDE_CLONES
		};

		virtual void populate(float &customtop, float &custombottom) override;
		virtual void handle() override;

		category_machine_filter &m_parent;
		std::function<void (machine_filter &)> m_handler;
		std::unique_ptr<std::pair<unsigned, bool> []> const m_state;
		unsigned m_ini;
	};

	void set_adjust_text()
	{
		inifile_manager const &mgr(mame_machine_manager::instance()->inifile());
		unsigned const filecnt(mgr.get_file_count());
		if (!filecnt)
		{
			m_adjust_text = _("[no category INI files]");
		}
		else
		{
			m_ini = std::min(m_ini, filecnt - 1);
			unsigned const groupcnt(mgr.get_category_count(m_ini));
			if (!groupcnt)
			{
				m_adjust_text = _("[no groups in INI file]");
			}
			else
			{
				m_group = std::min(m_group, groupcnt - 1);
				m_adjust_text = util::string_format("%s/%s", mgr.get_file_name(m_ini), mgr.get_category_name(m_ini, m_group));
			}
		}
	}

	static bool include_clones_default(std::string const &name)
	{
		return !core_stricmp(name.c_str(), "category.ini") || !core_stricmp(name.c_str(), "alltime.ini");
	}

	unsigned m_ini, m_group;
	bool m_include_clones;
	std::string m_adjust_text;
	mutable std::unordered_set<game_driver const *> m_cache;
	mutable bool m_cache_valid;
};

void category_machine_filter::show_ui(mame_ui_manager &mui, render_container &container, std::function<void (machine_filter &)> &&handler)
{
	menu::stack_push<menu_configure>(mui, container, *this, std::move(handler));
}


void category_machine_filter::menu_configure::populate(float &customtop, float &custombottom)
{
	inifile_manager const &mgr(mame_machine_manager::instance()->inifile());
	unsigned const filecnt(mgr.get_file_count());
	if (!filecnt)
	{
		item_append(_("No category INI files found"), "", FLAG_DISABLE, nullptr);
	}
	else
	{
		m_ini = std::min(m_ini, filecnt - 1);
		item_append(_("File"), mgr.get_file_name(m_ini), get_arrow_flags(0U, filecnt - 1, m_ini), reinterpret_cast<void *>(INI_FILE));
		unsigned const groupcnt(mgr.get_category_count(m_ini));
		if (!groupcnt)
		{
			item_append(_("No groups found in category file"), "", FLAG_DISABLE, nullptr);
		}
		else
		{
			m_state[m_ini].first = std::min(m_state[m_ini].first, groupcnt - 1);
			item_append(_("Group"), mgr.get_category_name(m_ini, m_state[m_ini].first), get_arrow_flags(0U, groupcnt - 1, m_state[m_ini].first), reinterpret_cast<void *>(SYSTEM_GROUP));
			item_append(_("Include clones"), m_state[m_ini].second ? _("Yes") : _("No"), m_state[m_ini].second ? FLAG_LEFT_ARROW : FLAG_RIGHT_ARROW, reinterpret_cast<void *>(INCLUDE_CLONES));
		}
	}
	item_append(menu_item_type::SEPARATOR);
	customtop = ui().get_line_height() + 3.0f * ui().box_tb_border();
}

void category_machine_filter::menu_configure::handle()
{
	const event *menu_event = process(PROCESS_LR_REPEAT);
	if (menu_event && menu_event->itemref)
	{
		bool changed(false);
		uintptr_t const ref(reinterpret_cast<uintptr_t>(menu_event->itemref));
		inifile_manager const &mgr(mame_machine_manager::instance()->inifile());
		switch (menu_event->iptkey)
		{
		case IPT_UI_LEFT:
			if ((INI_FILE == ref) && m_ini)
			{
				--m_ini;
				changed = true;
			}
			else if ((SYSTEM_GROUP == ref) && m_state[m_ini].first)
			{
				--m_state[m_ini].first;
				changed = true;
			}
			else if ((INCLUDE_CLONES == ref) && m_state[m_ini].second)
			{
				m_state[m_ini].second = false;
				changed = true;
			}
			break;
		case IPT_UI_RIGHT:
			if ((INI_FILE == ref) && (mgr.get_file_count() > (m_ini + 1)))
			{
				++m_ini;
				changed = true;
			}
			else if ((SYSTEM_GROUP == ref) && (mgr.get_category_count(m_ini) > (m_state[m_ini].first + 1)))
			{
				++m_state[m_ini].first;
				changed = true;
			}
			else if ((INCLUDE_CLONES == ref) && !m_state[m_ini].second)
			{
				m_state[m_ini].second = true;
				changed = true;
			}
			break;

		case IPT_UI_SELECT:
			if (INI_FILE == ref)
			{
				std::vector<std::string> choices;
				choices.reserve(mgr.get_file_count());
				for (size_t i = 0; mgr.get_file_count() > i; ++i)
					choices.emplace_back(mgr.get_file_name(i));
				menu::stack_push<menu_selector>(
						ui(),
						container(),
						std::move(choices),
						m_ini,
						[this] (int selection)
						{
							if (selection != m_ini)
							{
								m_ini = selection;
								reset(reset_options::REMEMBER_REF);
							}
						});
			}
			else if (SYSTEM_GROUP == ref)
			{
				std::vector<std::string> choices;
				choices.reserve(mgr.get_category_count(m_ini));
				for (size_t i = 0; mgr.get_category_count(m_ini) > i; ++i)
					choices.emplace_back(mgr.get_category_name(m_ini, i));
				menu::stack_push<menu_selector>(
						ui(),
						container(),
						std::move(choices),
						m_state[m_ini].first,
						[this] (int selection)
						{
							if (selection != m_state[m_ini].first)
							{
								m_state[m_ini].first = selection;
								reset(reset_options::REMEMBER_REF);
							}
						});
			}
			else if (INCLUDE_CLONES == ref)
			{
				m_state[m_ini].second = !m_state[m_ini].second;
				reset(reset_options::REMEMBER_REF);
			}
			break;
		}

		// rebuild if anything changed
		if (changed)
			reset(reset_options::REMEMBER_REF);
	}
}



//-------------------------------------------------
//  composite machine filter
//-------------------------------------------------

class custom_machine_filter : public composite_filter_impl_base<custom_machine_filter, machine_filter, machine_filter::CUSTOM>
{
public:
	custom_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent)
		: composite_filter_impl_base<custom_machine_filter, machine_filter, machine_filter::CUSTOM>()
		, m_data(data)
	{
		populate(value, file, indent);
	}

	ptr create(type n) const { return machine_filter::create(n, m_data); }
	ptr create(emu_file &file, unsigned indent) const { return machine_filter::create(file, m_data, indent); }

	static bool type_allowed(unsigned pos, type n)
	{
		return (FIRST <= n) && (LAST >= n) && (ALL != n) && (FAVORITE != n) && (CUSTOM != n);
	}

	static bool types_contradictory(type n, type m)
	{
		switch (n)
		{
		case AVAILABLE:         return UNAVAILABLE == m;
		case UNAVAILABLE:       return AVAILABLE == m;
		case WORKING:           return NOT_WORKING == m;
		case NOT_WORKING:       return WORKING == m;
		case MECHANICAL:        return NOT_MECHANICAL == m;
		case NOT_MECHANICAL:    return MECHANICAL == m;
		case BIOS:              return NOT_BIOS == m;
		case NOT_BIOS:          return BIOS == m;
		case PARENTS:           return CLONES == m;
		case CLONES:            return PARENTS == m;
		case SAVE:              return NOSAVE == m;
		case NOSAVE:            return SAVE == m;
		case CHD:               return NOCHD == m;
		case NOCHD:             return CHD == m;
		case VERTICAL:          return HORIZONTAL == m;
		case HORIZONTAL:        return VERTICAL == m;

		case ALL:
		case CATEGORY:
		case FAVORITE:
		case MANUFACTURER:
		case YEAR:
		case CUSTOM:
		case COUNT:
			break;
		}
		return false;
	}

	static bool is_inclusion(type n)
	{
		return (CATEGORY == n) || (MANUFACTURER == n) || (YEAR == n);
	}

private:
	machine_filter_data const &m_data;
};



//-------------------------------------------------
//  concrete software filters
//-------------------------------------------------

class all_software_filter : public simple_filter_impl_base<software_filter, software_filter::ALL>
{
public:
	all_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_software_info const &info) const override { return true; }
};


class available_software_filter : public simple_filter_impl_base<software_filter, software_filter::AVAILABLE>
{
public:
	available_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_software_info const &info) const override { return info.available; }
};


class unavailable_software_filter : public simple_filter_impl_base<software_filter, software_filter::UNAVAILABLE>
{
public:
	unavailable_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_software_info const &info) const override { return !info.available; }
};


class favorite_software_filter : public simple_filter_impl_base<software_filter, software_filter::FAVORITE>
{
public:
	favorite_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent)
		: m_manager(mame_machine_manager::instance()->favorite())
	{
	}

	virtual bool apply(ui_software_info const &info) const override { return m_manager.is_favorite_software(info); }

private:
	favorite_manager const &m_manager;
};


class parents_software_filter : public simple_filter_impl_base<software_filter, software_filter::PARENTS>
{
public:
	parents_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_software_info const &info) const override { return info.parentname.empty(); }
};


class clones_software_filter : public simple_filter_impl_base<software_filter, software_filter::CLONES>
{
public:
	clones_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_software_info const &info) const override { return !info.parentname.empty(); }
};


class years_software_filter : public choice_filter_impl_base<software_filter, software_filter::YEAR>
{
public:
	years_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent)
		: choice_filter_impl_base<software_filter, software_filter::YEAR>(data.years(), value)
	{
	}

	virtual bool apply(ui_software_info const &info) const override { return !have_choices() || (selection_valid() && (selection_text() == info.year)); }
};


class publishers_software_filter : public choice_filter_impl_base<software_filter, software_filter::PUBLISHERS>
{
public:
	publishers_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent)
		: choice_filter_impl_base<software_filter, software_filter::PUBLISHERS>(data.publishers(), value)
	{
	}

	virtual bool apply(ui_software_info const &info) const override
	{
		if (!have_choices())
			return true;
		else if (!selection_valid())
			return false;

		std::string const name(software_filter_data::extract_publisher(info.publisher));
		return !name.empty() && (selection_text() == name);
	}
};


class supported_software_filter : public simple_filter_impl_base<software_filter, software_filter::SUPPORTED>
{
public:
	supported_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_software_info const &info) const override { return SOFTWARE_SUPPORTED_YES == info.supported; }
};



class partial_supported_software_filter : public simple_filter_impl_base<software_filter, software_filter::PARTIAL_SUPPORTED>
{
public:
	partial_supported_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_software_info const &info) const override { return SOFTWARE_SUPPORTED_PARTIAL == info.supported; }
};


class unsupported_software_filter : public simple_filter_impl_base<software_filter, software_filter::UNSUPPORTED>
{
public:
	unsupported_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { }

	virtual bool apply(ui_software_info const &info) const override { return SOFTWARE_SUPPORTED_NO == info.supported; }
};


class region_software_filter : public choice_filter_impl_base<software_filter, software_filter::REGION>
{
public:
	region_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent)
		: choice_filter_impl_base<software_filter, software_filter::REGION>(data.regions(), value)
	{
	}

	virtual bool apply(ui_software_info const &info) const override
	{
		if (!have_choices())
			return true;
		else if (!selection_valid())
			return false;

		std::string const name(software_filter_data::extract_region(info.longname));
		return !name.empty() && (selection_text() == name);
	}
};


class device_type_software_filter : public choice_filter_impl_base<software_filter, software_filter::DEVICE_TYPE>
{
public:
	device_type_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent)
		: choice_filter_impl_base<software_filter, software_filter::DEVICE_TYPE>(data.device_types(), value)
	{
	}

	virtual bool apply(ui_software_info const &info) const override { return !have_choices() || (selection_valid() && (selection_text() == info.devicetype)); }
};


class list_software_filter : public choice_filter_impl_base<software_filter, software_filter::LIST>
{
public:
	list_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent)
		: choice_filter_impl_base<software_filter, software_filter::LIST>(data.list_descriptions(), value)
		, m_data(data)
	{
	}

	virtual bool apply(ui_software_info const &info) const override
	{
		return !have_choices() || (selection_valid() && (m_data.list_names()[selection_index()] == info.listname));
	}

private:
	software_filter_data const &m_data;
};



//-------------------------------------------------
//  composite software filter
//-------------------------------------------------

class custom_software_filter : public composite_filter_impl_base<custom_software_filter, software_filter, software_filter::CUSTOM>
{
public:
	custom_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent)
		: composite_filter_impl_base<custom_software_filter, software_filter, software_filter::CUSTOM>()
		, m_data(data)
	{
		populate(value, file, indent);
	}

	ptr create(type n) const { return software_filter::create(n, m_data); }
	ptr create(emu_file &file, unsigned indent) const { return software_filter::create(file, m_data, indent); }

	static bool type_allowed(unsigned pos, type n)
	{
		return (FIRST <= n) && (LAST >= n) && (ALL != n) && (CUSTOM != n);
	}

	static bool types_contradictory(type n, type m)
	{
		switch (n)
		{
		case AVAILABLE:         return UNAVAILABLE == m;
		case UNAVAILABLE:       return AVAILABLE == m;
		case PARENTS:           return CLONES == m;
		case CLONES:            return PARENTS == m;
		case SUPPORTED:         return (PARTIAL_SUPPORTED == m) || (UNSUPPORTED == m);
		case PARTIAL_SUPPORTED: return (SUPPORTED == m) || (UNSUPPORTED == m);
		case UNSUPPORTED:       return (SUPPORTED == m) || (PARTIAL_SUPPORTED == m);

		case ALL:
		case FAVORITE:
		case YEAR:
		case PUBLISHERS:
		case REGION:
		case DEVICE_TYPE:
		case LIST:
		case CUSTOM:
		case COUNT:
			break;
		}
		return false;
	}

	static bool is_inclusion(type n)
	{
		return (YEAR == n) || (PUBLISHERS == n) || (REGION == n) || (DEVICE_TYPE == n) || (LIST == n);
	}

private:
	software_filter_data const &m_data;
};

} // anonymous namespace



//-------------------------------------------------
//  static data for machine filters
//-------------------------------------------------

void machine_filter_data::add_manufacturer(std::string const &manufacturer)
{
	std::string name(extract_manufacturer(manufacturer));
	std::vector<std::string>::iterator const pos(std::lower_bound(m_manufacturers.begin(), m_manufacturers.end(), name));
	if ((m_manufacturers.end() == pos) || (*pos != name))
		m_manufacturers.emplace(pos, std::move(name));
}

void machine_filter_data::add_year(std::string const &year)
{
	std::vector<std::string>::iterator const pos(std::lower_bound(m_years.begin(), m_years.end(), year));
	if ((m_years.end() == pos) || (*pos != year))
		m_years.emplace(pos, year);
}

void machine_filter_data::finalise()
{
	std::stable_sort(m_manufacturers.begin(), m_manufacturers.end());
	std::stable_sort(m_years.begin(), m_years.end());
}

std::string machine_filter_data::extract_manufacturer(std::string const &manufacturer)
{
	size_t const found(manufacturer.find('('));
	if ((found != std::string::npos) && (found > 0))
		return manufacturer.substr(0, found - 1);
	else
		return manufacturer;
}

void machine_filter_data::set_filter(machine_filter::ptr &&filter)
{
	m_filters[filter->get_type()] = std::move(filter);
}

machine_filter &machine_filter_data::get_filter(machine_filter::type type)
{
	auto it(m_filters.find(type));
	if (m_filters.end() == it)
		it = m_filters.emplace(type, machine_filter::create(type, *this)).first;

	assert(it->second);
	return *it->second;
}

std::string machine_filter_data::get_config_string() const
{
	auto const active_filter(m_filters.find(m_current_filter));
	if (m_filters.end() != active_filter)
	{
		char const *const val(active_filter->second->filter_text());
		return val ? util::string_format("%s,%s", active_filter->second->config_name(), val) : active_filter->second->config_name();
	}
	else
	{
		return machine_filter::config_name(m_current_filter);
	}
}

bool machine_filter_data::load_ini(emu_file &file)
{
	machine_filter::ptr flt(machine_filter::create(file, *this));
	if (flt)
	{
		// TODO: it should possibly replace an existing item here, but it may be relying on that not happening because it never clears the first start flag
		m_current_filter = flt->get_type();
		m_filters.emplace(m_current_filter, std::move(flt));
		return true;
	}
	else
	{
		return false;
	}
}


//-------------------------------------------------
//  static data for software filters
//-------------------------------------------------

void software_filter_data::add_region(std::string const &longname)
{
	std::string name(extract_region(longname));
	std::vector<std::string>::iterator const pos(std::lower_bound(m_regions.begin(), m_regions.end(), name));
	if ((m_regions.end() == pos) || (*pos != name))
		m_regions.emplace(pos, std::move(name));
}

void software_filter_data::add_publisher(std::string const &publisher)
{
	std::string name(extract_publisher(publisher));
	std::vector<std::string>::iterator const pos(std::lower_bound(m_publishers.begin(), m_publishers.end(), name));
	if ((m_publishers.end() == pos) || (*pos != name))
		m_publishers.emplace(pos, std::move(name));
}

void software_filter_data::add_year(std::string const &year)
{
	std::vector<std::string>::iterator const pos(std::lower_bound(m_years.begin(), m_years.end(), year));
	if ((m_years.end() == pos) || (*pos != year))
		m_years.emplace(pos, year);
}

void software_filter_data::add_device_type(std::string const &device_type)
{
	std::vector<std::string>::iterator const pos(std::lower_bound(m_device_types.begin(), m_device_types.end(), device_type));
	if ((m_device_types.end() == pos) || (*pos != device_type))
		m_device_types.emplace(pos, device_type);
}

void software_filter_data::add_list(std::string const &name, std::string const &description)
{
	m_list_names.emplace_back(name);
	m_list_descriptions.emplace_back(description);
}

void software_filter_data::finalise()
{
	std::stable_sort(m_regions.begin(), m_regions.end());
	std::stable_sort(m_publishers.begin(), m_publishers.end());
	std::stable_sort(m_years.begin(), m_years.end());
	std::stable_sort(m_device_types.begin(), m_device_types.end());
}

std::string software_filter_data::extract_region(std::string const &longname)
{
	std::string fullname(longname);
	strmakelower(fullname);
	std::string::size_type const found(fullname.find('('));
	if (found != std::string::npos)
	{
		std::string::size_type const ends(fullname.find_first_not_of("abcdefghijklmnopqrstuvwxyz", found + 1));
		std::string const temp(fullname.substr(found + 1, ends - found - 1));
		auto const match(std::find_if(
				std::begin(SOFTWARE_REGIONS),
				std::end(SOFTWARE_REGIONS),
				[&temp] (char const *elem) { return temp == elem; }));
		if (std::end(SOFTWARE_REGIONS) != match)
			return longname.substr(found + 1, (std::string::npos != ends) ? (ends - found - 1) : ends);
	}
	return "<none>";
}

std::string software_filter_data::extract_publisher(std::string const &publisher)
{
	std::string::size_type const found(publisher.find('('));
	return publisher.substr(0, found - ((found && (std::string::npos != found)) ? 1 : 0));
}



//-------------------------------------------------
//  public machine filter interface
//-------------------------------------------------

machine_filter::ptr machine_filter::create(type n, machine_filter_data const &data, char const *value, emu_file *file, unsigned indent)
{
	assert(COUNT > n);
	switch (n)
	{
	case ALL:
		return std::make_unique<all_machine_filter>(data, value, file, indent);
	case AVAILABLE:
		return std::make_unique<available_machine_filter>(data, value, file, indent);
	case UNAVAILABLE:
		return std::make_unique<unavailable_machine_filter>(data, value, file, indent);
	case WORKING:
		return std::make_unique<working_machine_filter>(data, value, file, indent);
	case NOT_WORKING:
		return std::make_unique<not_working_machine_filter>(data, value, file, indent);
	case MECHANICAL:
		return std::make_unique<mechanical_machine_filter>(data, value, file, indent);
	case NOT_MECHANICAL:
		return std::make_unique<not_mechanical_machine_filter>(data, value, file, indent);
	case CATEGORY:
		return std::make_unique<category_machine_filter>(data, value, file, indent);
	case FAVORITE:
		return std::make_unique<favorite_machine_filter>(data, value, file, indent);
	case BIOS:
		return std::make_unique<bios_machine_filter>(data, value, file, indent);
	case NOT_BIOS:
		return std::make_unique<not_bios_machine_filter>(data, value, file, indent);
	case PARENTS:
		return std::make_unique<parents_machine_filter>(data, value, file, indent);
	case CLONES:
		return std::make_unique<clones_machine_filter>(data, value, file, indent);
	case MANUFACTURER:
		return std::make_unique<manufacturer_machine_filter>(data, value, file, indent);
	case YEAR:
		return std::make_unique<year_machine_filter>(data, value, file, indent);
	case SAVE:
		return std::make_unique<save_machine_filter>(data, value, file, indent);
	case NOSAVE:
		return std::make_unique<nosave_machine_filter>(data, value, file, indent);
	case CHD:
		return std::make_unique<chd_machine_filter>(data, value, file, indent);
	case NOCHD:
		return std::make_unique<nochd_machine_filter>(data, value, file, indent);
	case VERTICAL:
		return std::make_unique<vertical_machine_filter>(data, value, file, indent);
	case HORIZONTAL:
		return std::make_unique<horizontal_machine_filter>(data, value, file, indent);
	case CUSTOM:
		return std::make_unique<custom_machine_filter>(data, value, file, indent);
	case COUNT: // not valid, but needed to suppress warnings
		break;
	}
	return nullptr;
}

machine_filter::ptr machine_filter::create(emu_file &file, machine_filter_data const &data, unsigned indent)
{
	char buffer[MAX_CHAR_INFO];
	if (!file.gets(buffer, ARRAY_LENGTH(buffer)))
		return nullptr;

	// split it into a key/value or bail
	std::string key(buffer);
	for (std::string::size_type i = 0; (2 * indent) > i; ++i)
	{
		if ((key.length() <= i) || (' ' != key[i]))
			return nullptr;
	}
	key = key.substr(2 * indent);
	std::string::size_type const split(key.find(" = "));
	if (std::string::npos == split)
		return nullptr;
	std::string::size_type const nl(key.find_first_of("\r\n", split));
	std::string const value(key.substr(split + 3, (std::string::npos == nl) ? nl : (nl - split - 3)));
	key.resize(split);

	// look for a filter type that matches
	for (type n = FIRST; COUNT > n; ++n)
	{
		if (key == config_name(n))
			return create(n, data, value.c_str(), &file, indent);
	}
	return nullptr;
}

char const *machine_filter::config_name(type n)
{
	assert(COUNT > n);
	return MACHINE_FILTER_NAMES[n];
}

char const *machine_filter::display_name(type n)
{
	assert(COUNT > n);
	return _(MACHINE_FILTER_NAMES[n]);
}

machine_filter::machine_filter()
{
}


//-------------------------------------------------
//  public software filter interface
//-------------------------------------------------

char const *software_filter::config_name(type n)
{
	assert(COUNT > n);
	return SOFTWARE_FILTER_NAMES[n];
}

char const *software_filter::display_name(type n)
{
	assert(COUNT > n);
	return _(SOFTWARE_FILTER_NAMES[n]);
}

software_filter::software_filter()
{
}

software_filter::ptr software_filter::create(type n, software_filter_data const &data, char const *value, emu_file *file, unsigned indent)
{
	assert(COUNT > n);
	switch (n)
	{
	case ALL:
		return std::make_unique<all_software_filter>(data, value, file, indent);
	case AVAILABLE:
		return std::make_unique<available_software_filter>(data, value, file, indent);
	case UNAVAILABLE:
		return std::make_unique<unavailable_software_filter>(data, value, file, indent);
	case FAVORITE:
		return std::make_unique<favorite_software_filter>(data, value, file, indent);
	case PARENTS:
		return std::make_unique<parents_software_filter>(data, value, file, indent);
	case CLONES:
		return std::make_unique<clones_software_filter>(data, value, file, indent);
	case YEAR:
		return std::make_unique<years_software_filter>(data, value, file, indent);
	case PUBLISHERS:
		return std::make_unique<publishers_software_filter>(data, value, file, indent);
	case SUPPORTED:
		return std::make_unique<supported_software_filter>(data, value, file, indent);
	case PARTIAL_SUPPORTED:
		return std::make_unique<partial_supported_software_filter>(data, value, file, indent);
	case UNSUPPORTED:
		return std::make_unique<unsupported_software_filter>(data, value, file, indent);
	case REGION:
		return std::make_unique<region_software_filter>(data, value, file, indent);
	case DEVICE_TYPE:
		return std::make_unique<device_type_software_filter>(data, value, file, indent);
	case LIST:
		return std::make_unique<list_software_filter>(data, value, file, indent);
	case CUSTOM:
		return std::make_unique<custom_software_filter>(data, value, file, indent);
	case COUNT: // not valid, but needed to suppress warnings
		break;
	}
	return nullptr;
}

software_filter::ptr software_filter::create(emu_file &file, software_filter_data const &data, unsigned indent)
{
	char buffer[MAX_CHAR_INFO];
	if (!file.gets(buffer, ARRAY_LENGTH(buffer)))
		return nullptr;

	// split it into a key/value or bail
	std::string key(buffer);
	for (std::string::size_type i = 0; (2 * indent) > i; ++i)
	{
		if ((key.length() <= i) || (' ' != key[i]))
			return nullptr;
	}
	key = key.substr(2 * indent);
	std::string::size_type const split(key.find(" = "));
	if (std::string::npos == split)
		return nullptr;
	std::string::size_type const nl(key.find_first_of("\r\n", split));
	std::string const value(key.substr(split + 3, (std::string::npos == nl) ? nl : (nl - split - 3)));
	key.resize(split);

	// look for a filter type that matches
	for (type n = FIRST; COUNT > n; ++n)
	{
		if (key == config_name(n))
			return create(n, data, value.c_str(), &file, indent);
	}
	return nullptr;
}

} // namesapce ui


extern const char UI_VERSION_TAG[];
const char UI_VERSION_TAG[] = "# UI INFO ";

// Globals
uint8_t ui_globals::rpanel = 0;
uint8_t ui_globals::curdats_view = 0;
uint8_t ui_globals::cur_sw_dats_total = 0;
uint8_t ui_globals::curdats_total = 0;
uint8_t ui_globals::cur_sw_dats_view = 0;
bool ui_globals::reset = false;
int ui_globals::visible_main_lines = 0;
int ui_globals::visible_sw_lines = 0;
uint16_t ui_globals::panels_status = 0;

char* chartrimcarriage(char str[])
{
	char *pstr = strrchr(str, '\n');
	if (pstr)
		str[pstr - str] = '\0';
	pstr = strrchr(str, '\r');
	if (pstr)
		str[pstr - str] = '\0';
	return str;
}

const char* strensure(const char* s)
{
	return s == nullptr ? "" : s;
}

int getprecisionchr(const char* s)
{
	int precision = 1;
	char *dp = const_cast<char *>(strchr(s, '.'));
	if (dp != nullptr)
		precision = strlen(s) - (dp - s) - 1;
	return precision;
}

std::vector<std::string> tokenize(const std::string &text, char sep)
{
	std::vector<std::string> tokens;
	tokens.reserve(64);
	std::size_t start = 0, end = 0;
	while ((end = text.find(sep, start)) != std::string::npos)
	{
		std::string temp = text.substr(start, end - start);
		if (!temp.empty()) tokens.push_back(temp);
		start = end + 1;
	}
	std::string temp = text.substr(start);
	if (!temp.empty()) tokens.push_back(temp);
	return tokens;
}


ui_software_info::ui_software_info(
		software_info const &info,
		software_part const &p,
		game_driver const &d,
		std::string const &li,
		std::string const &is,
		std::string const &de)
	: shortname(info.shortname()), longname(info.longname()), parentname(info.parentname())
	, year(info.year()), publisher(info.publisher())
	, supported(info.supported())
	, part(p.name())
	, driver(&d)
	, listname(li), interface(p.interface()), instance(is)
	, startempty(0)
	, parentlongname()
	, usage()
	, devicetype(de)
	, available(false)
{
	for (feature_list_item const &feature : info.other_info())
	{
		if (feature.name() == "usage")
		{
			usage = feature.value();
			break;
		}
	}
}

// info for starting empty
ui_software_info::ui_software_info(game_driver const &d)
	: shortname(d.name), longname(d.type.fullname()), driver(&d), startempty(1), available(true)
{
}