summaryrefslogtreecommitdiffstatshomepage
path: root/src/frontend/mame/ui/state.cpp
blob: 81c210cced21a28bad317e8069f573ab3a775811 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
// license:BSD-3-Clause
// copyright-holders:Nathan Woods
/***************************************************************************

    ui/state.cpp

    Menus for saving and loading state

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

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

#include "emuopts.h"
#include "inputdev.h"


namespace ui {

/***************************************************************************
    ANONYMOUS NAMESPACE
***************************************************************************/

namespace {

//-------------------------------------------------
//  keyboard_input_item_name
//-------------------------------------------------

std::string keyboard_input_item_name(input_item_id id)
{
	if (id >= ITEM_ID_A && id <= ITEM_ID_Z)
		return std::string(1, char(id - ITEM_ID_A + 'a'));
	if (id >= ITEM_ID_0 && id <= ITEM_ID_9)
		return std::string(1, char(id - ITEM_ID_0 + '0'));

	// only supported for A-Z/0-9
	throw false;
}


//-------------------------------------------------
//  code_item_pair
//-------------------------------------------------

std::pair<std::string, std::string> code_item_pair(const running_machine &machine, input_item_id id)
{
	// only supported for A-Z|0-9
	assert((id >= ITEM_ID_A && id <= ITEM_ID_Z) || (id >= ITEM_ID_0 && id <= ITEM_ID_9));
	input_code const code = input_code(DEVICE_CLASS_KEYBOARD, 0, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, id);

	return std::make_pair(keyboard_input_item_name(id), machine.input().code_name(code));
}

} // anonymous namespace


/***************************************************************************
    FILE ENTRY
***************************************************************************/

std::string menu_load_save_state_base::s_last_file_selected;


//-------------------------------------------------
//  file_entry ctor
//-------------------------------------------------

menu_load_save_state_base::file_entry::file_entry(std::string &&file_name, std::string &&visible_name, const std::chrono::system_clock::time_point &last_modified)
	: m_file_name(std::move(file_name))
	, m_visible_name(std::move(visible_name))
	, m_last_modified(last_modified)
{
}


/***************************************************************************
    BASE CLASS FOR LOAD AND SAVE
***************************************************************************/

//-------------------------------------------------
//  ctor
//-------------------------------------------------

menu_load_save_state_base::menu_load_save_state_base(mame_ui_manager &mui, render_container &container, std::string_view header, std::string_view footer, bool must_exist)
	: menu(mui, container)
	, m_switch_poller(machine().input())
	, m_header(header)
	, m_footer(footer)
	, m_must_exist(must_exist)
	, m_was_paused(false)
	, m_keys_released(false)
{
}


//-------------------------------------------------
//  dtor
//-------------------------------------------------

menu_load_save_state_base::~menu_load_save_state_base()
{
	// resume if appropriate (is the destructor really the right place
	// to do this sort of activity?)
	if (!m_was_paused)
		machine().resume();
}


//-------------------------------------------------
//  populate
//-------------------------------------------------

void menu_load_save_state_base::populate(float &customtop, float &custombottom)
{
	// build the "filename to code" map, if we have not already (if it were not for the
	// possibility that the system keyboard can be changed at runtime, I would put this
	// into a static)
	if (m_filename_to_code_map.empty())
	{
		// loop through A-Z/0-9
		for (input_item_id id = ITEM_ID_A; id <= ITEM_ID_Z; id++)
			m_filename_to_code_map.emplace(code_item_pair(machine(), id));
		for (input_item_id id = ITEM_ID_0; id <= ITEM_ID_9; id++)
			m_filename_to_code_map.emplace(code_item_pair(machine(), id));

		// do joysticks
		input_class const &sticks = machine().input().device_class(DEVICE_CLASS_JOYSTICK);
		if (sticks.enabled())
		{
			for (int i = 0; sticks.maxindex() >= i; ++i)
			{
				input_device const *const stick = sticks.device(i);
				if (stick)
				{
					for (input_item_id j = ITEM_ID_BUTTON1; (ITEM_ID_BUTTON32 >= j) && (stick->maxitem() >= j); ++j)
					{
						input_device_item const *const item = stick->item(j);
						if (item && (item->itemclass() == ITEM_CLASS_SWITCH))
						{
							m_filename_to_code_map.emplace(
									util::string_format("joy%i-%i", i, j - ITEM_ID_BUTTON1 + 1),
									machine().input().code_name(item->code()));
						}
					}
				}
			}
		}
	}

	// open the state directory
	osd::directory::ptr dir = osd::directory::open(state_directory());

	// create a separate vector, so we can add sorted entries to the menu
	std::vector<const file_entry *> m_entries_vec;

	// populate all file entries
	m_file_entries.clear();
	if (dir)
	{
		const osd::directory::entry *entry;
		while ((entry = dir->read()) != nullptr)
		{
			if (core_filename_ends_with(entry->name, ".sta"))
			{
				// get the file name of the entry
				std::string file_name = core_filename_extract_base(entry->name, true);

				// try translating it
				std::string visible_name = get_visible_name(file_name);

				// and proceed
				file_entry fileent(std::string(file_name), std::move(visible_name), entry->last_modified);
				auto iter = m_file_entries.emplace(std::make_pair(std::move(file_name), std::move(fileent))).first;
				m_entries_vec.push_back(&iter->second);
			}
		}
	}

	// sort the vector; put recently modified state files at the top
	std::sort(
			m_entries_vec.begin(),
			m_entries_vec.end(),
			[] (const file_entry *a, const file_entry *b)
			{
				return a->last_modified() > b->last_modified();
			});

	// add the entries
	for (const file_entry *entry : m_entries_vec)
	{
		// get the time as a local time string
		char time_string[128];
		auto last_modified_time_t = std::chrono::system_clock::to_time_t(entry->last_modified());
		std::strftime(time_string, sizeof(time_string), "%c", std::localtime(&last_modified_time_t));

		// format the text
		std::string text = util::string_format("%s: %s",
				entry->visible_name(),
				time_string);

		// append the menu item
		void *const itemref = itemref_from_file_entry(*entry);
		item_append(std::move(text), 0, itemref);

		// is this item selected?
		if (entry->file_name() == s_last_file_selected)
			set_selection(itemref);
	}

	if (m_entries_vec.empty())
	{
		item_append(_("No save states found"), 0, nullptr);
		set_selection(nullptr);
	}
	item_append(menu_item_type::SEPARATOR);

	// set up custom render proc
	customtop = ui().get_line_height() + 3.0f * ui().box_tb_border();
	custombottom = ui().get_line_height() + 3.0f * ui().box_tb_border();

	// pause if appropriate
	m_was_paused = machine().paused();
	if (!m_was_paused)
		machine().pause();

	// get ready to poll inputs
	m_switch_poller.reset();
	m_keys_released = false;
}


//-------------------------------------------------
//  handle
//-------------------------------------------------

void menu_load_save_state_base::handle()
{
	// process the menu
	event const *const event = process(0);

	// process the event
	if (event && (event->iptkey == IPT_UI_SELECT))
	{
		if (event->itemref)
		{
			// user selected one of the entries
			file_entry const &entry = file_entry_from_itemref(event->itemref);
			slot_selected(std::string(entry.file_name()));
		}
	}
	else
	{
		// poll inputs
		std::string name = poll_inputs();
		if (!name.empty())
			try_select_slot(std::move(name));
	}
}


//-------------------------------------------------
//  get_visible_name
//-------------------------------------------------

std::string menu_load_save_state_base::get_visible_name(const std::string &file_name)
{
	auto const iter = m_filename_to_code_map.find(file_name);
	if (iter != m_filename_to_code_map.end())
		return iter->second;

	// otherwise these are the same
	return file_name;
}


//-------------------------------------------------
//  poll_inputs
//-------------------------------------------------

std::string menu_load_save_state_base::poll_inputs()
{
	input_code const code = m_switch_poller.poll();
	if (INPUT_CODE_INVALID == code)
	{
		m_keys_released = true;
	}
	else if (m_keys_released)
	{
		input_item_id const id = code.item_id();

		// keyboard A-Z and 0-9
		if (((ITEM_ID_A <= id) && (ITEM_ID_Z >= id)) || ((ITEM_ID_0 <= id) && (ITEM_ID_9 >= id)))
			return keyboard_input_item_name(id);

		// joystick buttons
		if ((DEVICE_CLASS_JOYSTICK == code.device_class()) && (ITEM_CLASS_SWITCH == code.item_class()) && (ITEM_MODIFIER_NONE == code.item_modifier()) && (ITEM_ID_BUTTON1 <= id) && (ITEM_ID_BUTTON32 >= id))
			return util::string_format("joy%i-%i", code.device_index(), id - ITEM_ID_BUTTON1 + 1);
	}
	return "";
}


//-------------------------------------------------
//  try_select_slot
//-------------------------------------------------

void menu_load_save_state_base::try_select_slot(std::string &&name)
{
	if (!m_must_exist || is_present(name))
		slot_selected(std::move(name));
}


//-------------------------------------------------
//  slot_selected
//-------------------------------------------------

void menu_load_save_state_base::slot_selected(std::string &&name)
{
	// handle it
	process_file(std::string(name));

	// record the last slot touched
	s_last_file_selected = std::move(name);

	// no matter what, pop out
	menu::stack_pop(machine());
}


//-------------------------------------------------
//  custom_render - perform our special rendering
//-------------------------------------------------

void menu_load_save_state_base::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2)
{
	extra_text_render(top, bottom, origx1, origy1, origx2, origy2, m_header, std::string_view());
	if (!m_footer.empty())
	{
		std::string_view const text[] = { m_footer };
		draw_text_box(
				std::begin(text), std::end(text),
				origx1, origx2, origy2 + ui().box_tb_border(), origy2 + bottom,
				ui::text_layout::CENTER, ui::text_layout::NEVER, false,
				ui().colors().text_color(), ui().colors().background_color(), 1.0f);
	}
}


//-------------------------------------------------
//  itemref_from_file_entry
//-------------------------------------------------

void *menu_load_save_state_base::itemref_from_file_entry(const menu_load_save_state_base::file_entry &entry)
{
	return (void *)&entry;
}


//-------------------------------------------------
//  file_entry_from_itemref
//-------------------------------------------------

const menu_load_save_state_base::file_entry &menu_load_save_state_base::file_entry_from_itemref(void *itemref)
{
	return *((const file_entry *)itemref);
}


//-------------------------------------------------
//  state_name
//-------------------------------------------------

std::string menu_load_save_state_base::state_directory() const
{
	const char *stateopt = machine().options().state_name();
	return util::string_format("%s%s%s",
			machine().options().state_directory(),
			PATH_SEPARATOR,
			machine().get_statename(stateopt));
}


//-------------------------------------------------
//  is_present
//-------------------------------------------------

bool menu_load_save_state_base::is_present(const std::string &name) const
{
	return m_file_entries.find(name) != m_file_entries.end();
}


/***************************************************************************
    LOAD STATE
***************************************************************************/

//-------------------------------------------------
//  ctor
//-------------------------------------------------

menu_load_state::menu_load_state(mame_ui_manager &mui, render_container &container)
	: menu_load_save_state_base(mui, container, _("Load State"), _("Select state to load"), true)
{
}


//-------------------------------------------------
//  process_file
//-------------------------------------------------

void menu_load_state::process_file(std::string &&file_name)
{
	machine().schedule_load(std::move(file_name));
}


/***************************************************************************
    SAVE STATE
***************************************************************************/

//-------------------------------------------------
//  ctor
//-------------------------------------------------

menu_save_state::menu_save_state(mame_ui_manager &mui, render_container &container)
	: menu_load_save_state_base(mui, container, _("Save State"), _("Press a key or joystick button, or select state to overwrite"), false)
{
}


//-------------------------------------------------
//  process_file
//-------------------------------------------------

void menu_save_state::process_file(std::string &&file_name)
{
	machine().schedule_save(std::move(file_name));
}


} // namespace ui