1 // license:BSD-3-Clause
2 // copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods
3 /***************************************************************************
4
5 ui/simpleselgame.cpp
6
7 Game selector
8
9 ***************************************************************************/
10
11 #include "emu.h"
12
13 #include "ui/simpleselgame.h"
14
15 #include "ui/info.h"
16 #include "ui/miscmenu.h"
17 #include "ui/optsmenu.h"
18 #include "ui/ui.h"
19 #include "ui/utils.h"
20
21 #include "audit.h"
22 #include "drivenum.h"
23 #include "emuopts.h"
24 #include "mame.h"
25 #include "uiinput.h"
26
27 #include <cctype>
28
29
30 namespace ui {
31
32 //-------------------------------------------------
33 // ctor
34 //-------------------------------------------------
35
simple_menu_select_game(mame_ui_manager & mui,render_container & container,const char * gamename)36 simple_menu_select_game::simple_menu_select_game(mame_ui_manager &mui, render_container &container, const char *gamename)
37 : menu(mui, container)
38 , m_error(false), m_rerandomize(false)
39 , m_search()
40 , m_skip_main_items(0)
41 , m_driverlist(driver_list::total() + 1)
42 , m_drivlist()
43 , m_cached_driver(nullptr)
44 , m_cached_flags(machine_flags::NOT_WORKING)
45 , m_cached_unemulated(device_t::feature::NONE), m_cached_imperfect(device_t::feature::NONE)
46 , m_cached_color(ui().colors().background_color())
47 {
48 build_driver_list();
49 if (gamename)
50 m_search.assign(gamename);
51 m_matchlist[0] = -1;
52 }
53
54
55 //-------------------------------------------------
56 // dtor
57 //-------------------------------------------------
58
~simple_menu_select_game()59 simple_menu_select_game::~simple_menu_select_game()
60 {
61 }
62
63
64
65 //-------------------------------------------------
66 // build_driver_list - build a list of available
67 // drivers
68 //-------------------------------------------------
69
build_driver_list()70 void simple_menu_select_game::build_driver_list()
71 {
72 // start with an empty list
73 m_drivlist = std::make_unique<driver_enumerator>(machine().options());
74 m_drivlist->exclude_all();
75
76 // open a path to the ROMs and find them in the array
77 file_enumerator path(machine().options().media_path());
78
79 // iterate while we get new objects
80 for (const osd::directory::entry *dir = path.next(); dir; dir = path.next())
81 {
82 char drivername[50];
83 char *dst = drivername;
84 const char *src;
85
86 // build a name for it
87 for (src = dir->name; *src != 0 && *src != '.' && dst < &drivername[ARRAY_LENGTH(drivername) - 1]; src++)
88 *dst++ = tolower((uint8_t)*src);
89 *dst = 0;
90
91 int drivnum = m_drivlist->find(drivername);
92 if (drivnum != -1)
93 m_drivlist->include(drivnum);
94 }
95
96 // now build the final list
97 m_drivlist->reset();
98 int listnum = 0;
99 while (m_drivlist->next())
100 m_driverlist[listnum++] = &m_drivlist->driver();
101
102 // NULL-terminate
103 m_driverlist[listnum] = nullptr;
104 }
105
106
107
108 //-------------------------------------------------
109 // handle - handle the game select menu
110 //-------------------------------------------------
111
handle()112 void simple_menu_select_game::handle()
113 {
114 // ignore pause keys by swallowing them before we process the menu
115 machine().ui_input().pressed(IPT_UI_PAUSE);
116
117 // process the menu
118 const event *menu_event = process(0);
119 if (menu_event && menu_event->itemref)
120 {
121 if (m_error)
122 {
123 // reset the error on any future menu_event
124 m_error = false;
125 machine().ui_input().reset();
126 }
127 else
128 {
129 // handle selections
130 switch(menu_event->iptkey)
131 {
132 case IPT_UI_SELECT:
133 inkey_select(menu_event);
134 break;
135 case IPT_UI_CANCEL:
136 inkey_cancel();
137 break;
138 case IPT_SPECIAL:
139 inkey_special(menu_event);
140 break;
141 }
142 }
143 }
144
145 // if we're in an error state, overlay an error message
146 if (m_error)
147 {
148 ui().draw_text_box(
149 container(),
150 _("The selected game is missing one or more required ROM or CHD images. "
151 "Please select a different game.\n\nPress any key to continue."),
152 ui::text_layout::CENTER, 0.5f, 0.5f, UI_RED_COLOR);
153 }
154 }
155
156
157 //-------------------------------------------------
158 // inkey_select
159 //-------------------------------------------------
160
inkey_select(const event * menu_event)161 void simple_menu_select_game::inkey_select(const event *menu_event)
162 {
163 const game_driver *driver = (const game_driver *)menu_event->itemref;
164
165 // special case for configure inputs
166 if ((uintptr_t)driver == 1)
167 {
168 menu::stack_push<menu_simple_game_options>(
169 ui(),
170 container(),
171 [this] () { reset(reset_options::SELECT_FIRST); });
172 }
173
174 // anything else is a driver
175 else
176 {
177 // audit the game first to see if we're going to work
178 driver_enumerator enumerator(machine().options(), *driver);
179 enumerator.next();
180 media_auditor auditor(enumerator);
181 media_auditor::summary summary = auditor.audit_media(AUDIT_VALIDATE_FAST);
182
183 if (summary == media_auditor::CORRECT || summary == media_auditor::BEST_AVAILABLE || summary == media_auditor::NONE_NEEDED)
184 {
185 // if everything looks good, schedule the new driver
186 mame_machine_manager::instance()->schedule_new_driver(*driver);
187 machine().schedule_hard_reset();
188 stack_reset();
189 }
190 else
191 {
192 // otherwise, display an error
193 reset(reset_options::REMEMBER_REF);
194 m_error = true;
195 }
196 }
197 }
198
199
200 //-------------------------------------------------
201 // inkey_cancel
202 //-------------------------------------------------
203
inkey_cancel()204 void simple_menu_select_game::inkey_cancel()
205 {
206 // escape pressed with non-empty text clears the text
207 if (!m_search.empty())
208 {
209 m_search.clear();
210 reset(reset_options::SELECT_FIRST);
211 }
212 }
213
214
215 //-------------------------------------------------
216 // inkey_special - typed characters append to the buffer
217 //-------------------------------------------------
218
inkey_special(const event * menu_event)219 void simple_menu_select_game::inkey_special(const event *menu_event)
220 {
221 // typed characters append to the buffer
222 size_t old_size = m_search.size();
223 if (input_character(m_search, menu_event->unichar, uchar_is_printable))
224 {
225 if (m_search.size() < old_size)
226 m_rerandomize = true;
227 reset(reset_options::SELECT_FIRST);
228 }
229 }
230
231
232 //-------------------------------------------------
233 // populate - populate the game select menu
234 //-------------------------------------------------
235
populate(float & customtop,float & custombottom)236 void simple_menu_select_game::populate(float &customtop, float &custombottom)
237 {
238 int matchcount;
239 int curitem;
240
241 for (curitem = matchcount = 0; m_driverlist[curitem] != nullptr && matchcount < VISIBLE_GAMES_IN_LIST; curitem++)
242 matchcount++;
243
244 // if nothing there, add a single multiline item and return
245 if (matchcount == 0)
246 {
247 std::string txt = string_format(
248 _("No machines found. Please check the rompath specified in the %1$s.ini file.\n\n"
249 "If this is your first time using %2$s, please see the config.txt file in "
250 "the docs directory for information on configuring %2$s."),
251 emulator_info::get_configname(),
252 emulator_info::get_appname());
253 item_append(txt, "", FLAG_MULTILINE | FLAG_REDTEXT, nullptr);
254 return;
255 }
256
257 // otherwise, rebuild the match list
258 assert(m_drivlist != nullptr);
259 if (!m_search.empty() || m_matchlist[0] == -1 || m_rerandomize)
260 m_drivlist->find_approximate_matches(m_search, matchcount, m_matchlist);
261 m_rerandomize = false;
262
263 // iterate over entries
264 for (curitem = 0; curitem < matchcount; curitem++)
265 {
266 int curmatch = m_matchlist[curitem];
267 if (curmatch != -1)
268 {
269 int cloneof = m_drivlist->non_bios_clone(curmatch);
270 item_append(m_drivlist->driver(curmatch).name, m_drivlist->driver(curmatch).type.fullname(), (cloneof == -1) ? 0 : FLAG_INVERT, (void *)&m_drivlist->driver(curmatch));
271 }
272 }
273
274 // if we're forced into this, allow general input configuration as well
275 if (stack_has_special_main_menu())
276 {
277 item_append(menu_item_type::SEPARATOR);
278 item_append(_("Configure Options"), "", 0, (void *)1);
279 m_skip_main_items = 1;
280 }
281
282 // configure the custom rendering
283 customtop = ui().get_line_height() + 3.0f * ui().box_tb_border();
284 custombottom = 4.0f * ui().get_line_height() + 3.0f * ui().box_tb_border();
285 }
286
287
288 //-------------------------------------------------
289 // custom_render - perform our special rendering
290 //-------------------------------------------------
291
custom_render(void * selectedref,float top,float bottom,float origx1,float origy1,float origx2,float origy2)292 void simple_menu_select_game::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2)
293 {
294 const game_driver *driver;
295 std::string tempbuf[5];
296
297 // display the current typeahead
298 if (!m_search.empty())
299 tempbuf[0] = string_format(_("Type name or select: %1$s_"), m_search);
300 else
301 tempbuf[0] = _("Type name or select: (random)");
302
303 // draw the top box
304 draw_text_box(
305 tempbuf, tempbuf + 1,
306 origx1, origx2, origy1 - top, origy1 - ui().box_tb_border(),
307 ui::text_layout::CENTER, ui::text_layout::TRUNCATE, false,
308 ui().colors().text_color(), ui().colors().background_color(), 1.0f);
309
310 // determine the text to render below
311 driver = ((uintptr_t)selectedref > m_skip_main_items) ? (const game_driver *)selectedref : nullptr;
312 if (driver)
313 {
314 // first line is game name
315 tempbuf[0] = string_format(_("%1$-.100s"), driver->type.fullname());
316
317 // next line is year, manufacturer
318 tempbuf[1] = string_format(_("%1$s, %2$-.100s"), driver->year, driver->manufacturer);
319
320 // next line source path
321 tempbuf[2] = string_format(_("Driver: %1$-.100s"), core_filename_extract_base(driver->type.source()));
322
323 // update cached values if selection changed
324 if (driver != m_cached_driver)
325 {
326 emu_options clean_options;
327 machine_static_info const info(ui().options(), machine_config(*driver, clean_options));
328 m_cached_driver = driver;
329 m_cached_flags = info.machine_flags();
330 m_cached_unemulated = info.unemulated_features();
331 m_cached_imperfect = info.imperfect_features();
332 m_cached_color = info.status_color();
333 }
334
335 // next line is overall driver status
336 if (m_cached_flags & machine_flags::NOT_WORKING)
337 tempbuf[3] = _("Overall: NOT WORKING");
338 else if ((m_cached_unemulated | m_cached_imperfect) & device_t::feature::PROTECTION)
339 tempbuf[3] = _("Overall: Unemulated Protection");
340 else
341 tempbuf[3] = _("Overall: Working");
342
343 // next line is graphics, sound status
344 if (m_cached_unemulated & device_t::feature::GRAPHICS)
345 tempbuf[4] = _("Graphics: Unimplemented, ");
346 else if ((m_cached_unemulated | m_cached_imperfect) & (device_t::feature::GRAPHICS | device_t::feature::PALETTE))
347 tempbuf[4] = _("Graphics: Imperfect, ");
348 else
349 tempbuf[4] = _("Graphics: OK, ");
350
351 if (m_cached_flags & machine_flags::NO_SOUND_HW)
352 tempbuf[4].append(_("Sound: None"));
353 else if (m_cached_unemulated & device_t::feature::SOUND)
354 tempbuf[4].append(_("Sound: Unimplemented"));
355 else if (m_cached_imperfect & device_t::feature::SOUND)
356 tempbuf[4].append(_("Sound: Imperfect"));
357 else
358 tempbuf[4].append(_("Sound: OK"));
359 }
360 else
361 {
362 const char *s = emulator_info::get_copyright();
363 unsigned line = 0;
364
365 // first line is version string
366 tempbuf[line++] = string_format("%s %s", emulator_info::get_appname(), build_version);
367
368 // output message
369 while (line < ARRAY_LENGTH(tempbuf))
370 {
371 if (!(*s == 0 || *s == '\n'))
372 tempbuf[line].push_back(*s);
373
374 if (*s == '\n')
375 {
376 line++;
377 s++;
378 } else if (*s != 0)
379 s++;
380 else
381 line++;
382 }
383 }
384
385 // draw the bottom box
386 draw_text_box(
387 tempbuf, tempbuf + 4,
388 origx1, origx2, origy2 + ui().box_tb_border(), origy2 + bottom,
389 ui::text_layout::CENTER, ui::text_layout::TRUNCATE, true,
390 ui().colors().text_color(), driver ? m_cached_color : ui().colors().background_color(), 1.0f);
391 }
392
393
394 //-------------------------------------------------
395 // force_game_select - force the game
396 // select menu to be visible and inescapable
397 //-------------------------------------------------
398
force_game_select(mame_ui_manager & mui,render_container & container)399 void simple_menu_select_game::force_game_select(mame_ui_manager &mui, render_container &container)
400 {
401 char *gamename = (char *)mui.machine().options().system_name();
402
403 // reset the menu stack
404 menu::stack_reset(mui.machine());
405
406 // add the quit entry followed by the game select entry
407 menu::stack_push_special_main<menu_quit_game>(mui, container);
408 menu::stack_push<simple_menu_select_game>(mui, container, gamename);
409
410 // force the menus on
411 mui.show_menu();
412
413 // make sure MAME is paused
414 mui.machine().pause();
415 }
416
417 } // namespace ui
418