1 /*
2  * ui_basic.c - Atari look&feel user interface driver
3  *
4  * Copyright (C) 1995-1998 David Firth
5  * Copyright (C) 1998-2010 Atari800 development team (see DOC/CREDITS)
6  *
7  * This file is part of the Atari800 emulator project which emulates
8  * the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers.
9  *
10  * Atari800 is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * Atari800 is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Atari800; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23 */
24 
25 #define _POSIX_C_SOURCE 200112L /* for snprintf */
26 
27 #include "config.h"
28 #include <stdio.h>
29 #include <string.h>
30 #include <stdlib.h> /* free() */
31 #ifdef HAVE_UNISTD_H
32 #include <unistd.h> /* getcwd() */
33 #endif
34 #ifdef HAVE_DIRECT_H
35 #include <direct.h> /* getcwd on MSVC*/
36 #endif
37 /* XXX: <sys/dir.h>, <ndir.h>, <sys/ndir.h> */
38 #ifdef HAVE_DIRENT_H
39 #include <dirent.h>
40 #endif
41 #ifdef HAVE_SYS_STAT_H
42 #include <sys/stat.h>
43 #endif
44 #ifdef HAVE_WINDOWS_H
45 #include <windows.h>
46 #endif
47 
48 #include "antic.h"
49 #include "atari.h"
50 #include "input.h"
51 #include "akey.h"
52 #include "log.h"
53 #include "memory.h"
54 #include "platform.h"
55 #include "screen.h" /* Screen_atari */
56 #include "ui.h"
57 #include "util.h"
58 #include "ui_basic.h"
59 
60 #ifdef DIRECTX
61 	#include "win32/main.h"
62 #endif
63 
64 static int initialised = FALSE;
65 static UBYTE charset[1024];
66 
67 #ifdef DIRECTX
68 	POINT UI_mouse_click = {-1, -1};
69 #endif
70 
71 const unsigned char UI_BASIC_key_to_ascii[256] =
72 {
73 	0x6C, 0x6A, 0x3B, 0x00, 0x00, 0x6B, 0x2B, 0x2A, 0x6F, 0x00, 0x70, 0x75, 0x9B, 0x69, 0x2D, 0x3D,
74 	0x76, 0x00, 0x63, 0x00, 0x00, 0x62, 0x78, 0x7A, 0x34, 0x00, 0x33, 0x36, 0x1B, 0x35, 0x32, 0x31,
75 	0x2C, 0x20, 0x2E, 0x6E, 0x00, 0x6D, 0x2F, 0x00, 0x72, 0x00, 0x65, 0x79, 0x7F, 0x74, 0x77, 0x71,
76 	0x39, 0x00, 0x30, 0x37, 0x7E, 0x38, 0x3C, 0x3E, 0x66, 0x68, 0x64, 0x00, 0x00, 0x67, 0x73, 0x61,
77 
78 	0x4C, 0x4A, 0x3A, 0x00, 0x00, 0x4B, 0x5C, 0x5E, 0x4F, 0x00, 0x50, 0x55, 0x9B, 0x49, 0x5F, 0x7C,
79 	0x56, 0x00, 0x43, 0x00, 0x00, 0x42, 0x58, 0x5A, 0x24, 0x00, 0x23, 0x26, 0x1B, 0x25, 0x22, 0x21,
80 	0x5B, 0x20, 0x5D, 0x4E, 0x00, 0x4D, 0x3F, 0x00, 0x52, 0x00, 0x45, 0x59, 0x9F, 0x54, 0x57, 0x51,
81 	0x28, 0x00, 0x29, 0x27, 0x9C, 0x40, 0x7D, 0x9D, 0x46, 0x48, 0x44, 0x00, 0x00, 0x47, 0x53, 0x41,
82 
83 	0x0C, 0x0A, 0x7B, 0x00, 0x00, 0x0B, 0x1E, 0x1F, 0x0F, 0x00, 0x10, 0x15, 0x9B, 0x09, 0x1C, 0x1D,
84 	0x16, 0x00, 0x03, 0x00, 0x00, 0x02, 0x18, 0x1A, 0x00, 0x00, 0x9B, 0x00, 0x1B, 0x00, 0xFD, 0x00,
85 	0x00, 0x20, 0x60, 0x0E, 0x00, 0x0D, 0x00, 0x00, 0x12, 0x00, 0x05, 0x19, 0x9E, 0x14, 0x17, 0x11,
86 	0x00, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x7D, 0xFF, 0x06, 0x08, 0x04, 0x00, 0x00, 0x07, 0x13, 0x01,
87 
88 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
89 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
90 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
91 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
92 };
93 
94 #define KB_DELAY       20
95 #define KB_AUTOREPEAT  3
96 
GetKeyPress(void)97 static int GetKeyPress(void)
98 {
99 	int keycode;
100 
101 	if (UI_alt_function >= 0)
102 		return 0x1b; /* escape - go to Main Menu */
103 
104 	PLATFORM_DisplayScreen();
105 
106 	for (;;) {
107 		static int rep = KB_DELAY;
108 		if (PLATFORM_Keyboard() == AKEY_NONE) {
109 			rep = KB_DELAY;
110 			break;
111 		}
112 
113 		if (rep == 0) {
114 			rep = KB_AUTOREPEAT;
115 			break;
116 		}
117 		rep--;
118 		Atari800_Sync();
119 	}
120 
121 	do {
122 #ifdef DIRECTX
123 		DoEvents();
124 #endif
125 		Atari800_Sync();
126 		keycode = PLATFORM_Keyboard();
127 		switch (keycode) {
128 		case AKEY_WARMSTART:
129 			UI_alt_function = UI_MENU_RESETW;
130 			return 0x1b; /* escape */
131 		case AKEY_COLDSTART:
132 			UI_alt_function = UI_MENU_RESETC;
133 			return 0x1b; /* escape */
134 		case AKEY_EXIT:
135 			UI_alt_function = UI_MENU_EXIT;
136 			return 0x1b; /* escape */
137 		case AKEY_UI:
138 #ifdef DIRECTX
139 			UI_Run();
140 #else
141 			if (UI_alt_function >= 0)  /* Alt+letter, not F1 */
142 #endif
143 			return 0x1b; /* escape */
144 			break;
145 		case AKEY_SCREENSHOT:
146 			UI_alt_function = UI_MENU_PCX;
147 			return 0x1b; /* escape */
148 		case AKEY_SCREENSHOT_INTERLACE:
149 			UI_alt_function = UI_MENU_PCXI;
150 			return 0x1b; /* escape */
151 		default:
152 			UI_alt_function = -1; /* forget previous Main Menu shortcut */
153 			break;
154 		}
155 	} while (keycode < 0);
156 
157 	return UI_BASIC_key_to_ascii[keycode];
158 }
159 
160 #ifdef DIRECTX
161 /* Convert atari-pixel based mouse click coordinates to simplified
162    UI coordinates consisting of 20 horizontal bands and 2 columns */
SetMouseIndex(int x,int y)163 void SetMouseIndex(int x, int y)
164 {
165 	int yband;
166 
167 	/* set the y-band that the user clicked on */
168 	yband = y / DX_MENU_ITEM_HEIGHT - 5;
169 	if (y < 37 || x > 346 || yband < 0 || yband > 20)
170 		UI_mouse_click.y = -1;
171 	else
172 		UI_mouse_click.y = yband;
173 
174 	/* set the x-band that the user clicked on */
175 	if (x >= 37 && x < 186)
176 		UI_mouse_click.x = 1;
177 	else if (x >= 186 && x <= 346)
178 		UI_mouse_click.x = 2;
179 	else
180 		UI_mouse_click.x = -1;
181 
182 	/* set any click outside of any band to -1,-1 */
183 	if (UI_mouse_click.x == -1 || UI_mouse_click.y == -1)
184 		UI_mouse_click.x = UI_mouse_click.y = -1;
185 }
186 #endif
187 
Plot(int fg,int bg,int ch,int x,int y)188 static void Plot(int fg, int bg, int ch, int x, int y)
189 {
190 #ifdef USE_CURSES
191 	curses_putch(x, y, ch, (UBYTE) fg, (UBYTE) bg);
192 #else /* USE_CURSES */
193 	const UBYTE *font_ptr = charset + (ch & 0x7f) * 8;
194 	UBYTE *ptr = (UBYTE *) Screen_atari + 24 * Screen_WIDTH + 32 + y * (8 * Screen_WIDTH) + x * 8;
195 	int i;
196 	int j;
197 
198 	for (i = 0; i < 8; i++) {
199 		UBYTE data = *font_ptr++;
200 		for (j = 0; j < 8; j++) {
201 #ifdef USE_COLOUR_TRANSLATION_TABLE
202 			ANTIC_VideoPutByte(ptr++, (UBYTE) colour_translation_table[data & 0x80 ? fg : bg]);
203 #else
204 			ANTIC_VideoPutByte(ptr++, (UBYTE) (data & 0x80 ? fg : bg));
205 #endif
206 			data <<= 1;
207 		}
208 		ptr += Screen_WIDTH - 8;
209 	}
210 #endif /* USE_CURSES */
211 }
212 
Print(int fg,int bg,const char * string,int x,int y,int maxwidth)213 static void Print(int fg, int bg, const char *string, int x, int y, int maxwidth)
214 {
215 	char tmpbuf[40];
216 	if ((int) strlen(string) > maxwidth) {
217 		int firstlen = (maxwidth - 3) >> 1;
218 		int laststart = strlen(string) - (maxwidth - 3 - firstlen);
219 		snprintf(tmpbuf, sizeof(tmpbuf), "%.*s...%s", firstlen, string, string + laststart);
220 		string = tmpbuf;
221 	}
222 	while (*string != '\0')
223 		Plot(fg, bg, *string++, x++, y);
224 }
225 
CenterPrint(int fg,int bg,const char * string,int y)226 static void CenterPrint(int fg, int bg, const char *string, int y)
227 {
228 	int length = strlen(string);
229 	Print(fg, bg, string, (length < 38) ? (40 - length) >> 1 : 1, y, 38);
230 }
231 
Box(int fg,int bg,int x1,int y1,int x2,int y2)232 static void Box(int fg, int bg, int x1, int y1, int x2, int y2)
233 {
234 	int x;
235 	int y;
236 
237 	for (x = x1 + 1; x < x2; x++) {
238 		Plot(fg, bg, 18, x, y1);
239 		Plot(fg, bg, 18, x, y2);
240 	}
241 
242 	for (y = y1 + 1; y < y2; y++) {
243 		Plot(fg, bg, 124, x1, y);
244 		Plot(fg, bg, 124, x2, y);
245 	}
246 
247 	Plot(fg, bg, 17, x1, y1);
248 	Plot(fg, bg, 5, x2, y1);
249 	Plot(fg, bg, 3, x2, y2);
250 	Plot(fg, bg, 26, x1, y2);
251 }
252 
ClearRectangle(int bg,int x1,int y1,int x2,int y2)253 static void ClearRectangle(int bg, int x1, int y1, int x2, int y2)
254 {
255 #ifdef USE_CURSES
256 	curses_clear_rectangle(x1, y1, x2, y2);
257 #else
258 	UBYTE *ptr = (UBYTE *) Screen_atari + Screen_WIDTH * 24 + 32 + x1 * 8 + y1 * (Screen_WIDTH * 8);
259 	int bytesperline = (x2 - x1 + 1) << 3;
260 	UBYTE *end_ptr = (UBYTE *) Screen_atari + Screen_WIDTH * 32 + 32 + y2 * (Screen_WIDTH * 8);
261 	while (ptr < end_ptr) {
262 #ifdef USE_COLOUR_TRANSLATION_TABLE
263 		ANTIC_VideoMemset(ptr, (UBYTE) colour_translation_table[bg], bytesperline);
264 #else
265 		ANTIC_VideoMemset(ptr, (UBYTE) bg, bytesperline);
266 #endif
267 		ptr += Screen_WIDTH;
268 	}
269 #endif /* USE_CURSES */
270 }
271 
ClearScreen(void)272 static void ClearScreen(void)
273 {
274 #ifdef USE_CURSES
275 	curses_clear_screen();
276 #else
277 #ifdef USE_COLOUR_TRANSLATION_TABLE
278 	ANTIC_VideoMemset((UBYTE *) Screen_atari, colour_translation_table[0x00], Screen_HEIGHT * Screen_WIDTH);
279 #else
280 	ANTIC_VideoMemset((UBYTE *) Screen_atari, 0x00, Screen_HEIGHT * Screen_WIDTH);
281 #endif
282 	ClearRectangle(0x94, 0, 0, 39, 23);
283 #endif /* USE_CURSES */
284 }
285 
TitleScreen(const char * title)286 static void TitleScreen(const char *title)
287 {
288 	CenterPrint(0x9a, 0x94, title, 0);
289 }
290 
BasicUIMessage(const char * msg,int waitforkey)291 static void BasicUIMessage(const char *msg, int waitforkey)
292 {
293 	ClearRectangle(0x94, 1, 22, 38, 22);
294 	CenterPrint(0x94, 0x9a, msg, 22);
295 	if (waitforkey)
296 		GetKeyPress();
297 	else
298 		PLATFORM_DisplayScreen();
299 }
300 
301 #ifdef GUI_SDL
GetRawKey(void)302 int GetRawKey(void)
303 {
304 	ClearRectangle(0x94, 13, 11, 25, 13);
305 	Box(0x9a, 0x94, 13, 11, 25, 13);
306 	CenterPrint(0x94, 0x9a, "Press a key", 12);
307 	PLATFORM_DisplayScreen();
308 	return PLATFORM_GetRawKey();
309 }
310 #endif /* GUI_SDL */
311 
312 #ifdef DIRECTX
GetKeyName(void)313 int GetKeyName(void)
314 {
315 	ClearRectangle(0x94, 13, 11, 25, 13);
316 	Box(0x9a, 0x94, 13, 11, 25, 13);
317 	CenterPrint(0x94, 0x9a, "Press a key", 12);
318 	PLATFORM_DisplayScreen();
319 	return PLATFORM_GetKeyName();
320 }
321 #endif
322 
Select(int default_item,int nitems,const char * item[],const char * prefix[],const char * suffix[],const char * tip[],const int nonselectable[],int nrows,int ncolumns,int xoffset,int yoffset,int itemwidth,int drag,const char * global_tip,int * seltype)323 static int Select(int default_item, int nitems, const char *item[],
324                   const char *prefix[], const char *suffix[],
325                   const char *tip[], const int nonselectable[],
326                   int nrows, int ncolumns, int xoffset, int yoffset,
327                   int itemwidth, int drag, const char *global_tip,
328                   int *seltype)
329 {
330 	int offset = 0;
331 	int index = default_item;
332 	int localseltype;
333 
334 	if (seltype == NULL)
335 		seltype = &localseltype;
336 
337 	for (;;) {
338 		int col;
339 		int row;
340 		int i;
341 		const char *message = global_tip;
342 
343 		while (index < offset)
344 			offset -= nrows;
345 		while (index >= offset + nrows * ncolumns)
346 			offset += nrows;
347 
348 		ClearRectangle(0x94, xoffset, yoffset, xoffset + ncolumns * (itemwidth + 1) - 2, yoffset + nrows - 1);
349 		col = 0;
350 		row = 0;
351 		for (i = offset; i < nitems; i++) {
352 			char szbuf[40 + FILENAME_MAX]; /* allow for prefix and suffix */
353 			char *p = szbuf;
354 			if (prefix != NULL && prefix[i] != NULL)
355 				p = Util_stpcpy(szbuf, prefix[i]);
356 			p = Util_stpcpy(p, item[i]);
357 			if (suffix != NULL && suffix[i] != NULL) {
358 				char *q = szbuf + itemwidth - strlen(suffix[i]);
359 				while (p < q)
360 					*p++ = ' ';
361 				strcpy(p, suffix[i]);
362 			}
363 			else {
364 				while (p < szbuf + itemwidth)
365 					*p++ = ' ';
366 				*p = '\0';
367 			}
368 			if (i == index)
369 				Print(0x94, 0x9a, szbuf, xoffset + col * (itemwidth + 1), yoffset + row, itemwidth);
370 			else
371 				Print(0x9a, 0x94, szbuf, xoffset + col * (itemwidth + 1), yoffset + row, itemwidth);
372 			if (++row >= nrows) {
373 				if (++col >= ncolumns)
374 					break;
375 				row = 0;
376 			}
377 		}
378 		if (tip != NULL && tip[index] != NULL)
379 			message = tip[index];
380 		else if (itemwidth < 38 && (int) strlen(item[index]) > itemwidth)
381 			/* the selected item was shortened */
382 			message = item[index];
383 		if (message != NULL)
384 			CenterPrint(0x94, 0x9a, message, 22);
385 
386 		for (;;) {
387 			int ascii;
388 			int tmp_index;
389 			ascii = GetKeyPress();
390 			switch (ascii) {
391 			case 0x1c:				/* Up */
392 				if (drag) {
393 					*seltype = UI_USER_DRAG_UP;
394 					return index;
395 				}
396 				tmp_index = index;
397 				do
398 					tmp_index--;
399 				while (tmp_index >= 0 && nonselectable != NULL && nonselectable[tmp_index]);
400 				if (tmp_index >= 0) {
401 					index = tmp_index;
402 					break;
403 				}
404 				continue;
405 			case 0x1d:				/* Down */
406 				if (drag) {
407 					*seltype = UI_USER_DRAG_DOWN;
408 					return index;
409 				}
410 				tmp_index = index;
411 				do
412 					tmp_index++;
413 				while (tmp_index < nitems && nonselectable != NULL && nonselectable[tmp_index]);
414 				if (tmp_index < nitems) {
415 					index = tmp_index;
416 					break;
417 				}
418 				continue;
419 			case 0x1e:				/* Left */
420 				if (drag)
421 					continue;		/* cannot drag left */
422 				index = (index > nrows) ? index - nrows : 0;
423 				break;
424 			case 0x1f:				/* Right */
425 				if (drag)
426 					continue;		/* cannot drag right */
427 				index = (index + nrows < nitems) ? index + nrows : nitems - 1;
428 				break;
429 			case 0x7f:				/* Tab (for exchanging disk directories) */
430 				return -2;			/* GOLDA CHANGED */
431 			case 0x20:				/* Space */
432 				*seltype = UI_USER_TOGGLE;
433 				return index;
434 			case 0x7e:				/* Backspace */
435 				*seltype = UI_USER_DELETE;
436 				return index;
437 			case 0x9b:				/* Return=Select */
438 				*seltype = UI_USER_SELECT;
439 				return index;
440 #ifdef DIRECTX
441 			case 0xAA:              /* Mouse click */
442 
443 			/* mouse click location, adjusted by context
444 			   this is all we need for one column */
445 			tmp_index = UI_mouse_click.y - yoffset + 2;
446 
447 			/* handle two column mode scenarios */
448 			if (ncolumns == 2) {
449 				/* special case - do nothing if user clicks empty
450 			       bottom cell in column 1 in two column mode.   */
451 				if (UI_mouse_click.x == 1 && UI_mouse_click.y == 20) {
452 					UI_mouse_click.x = UI_mouse_click.y = -1;
453 					break;
454 				}
455 				/* handle two column, multi-page scenarios */
456 				else if (UI_mouse_click.x == 1)
457 					tmp_index += offset;
458 				else if (UI_mouse_click.x == 2)
459 					tmp_index += offset + 20;
460 			}
461 
462 			/* if cell is a valid one, update the index */
463 			if (tmp_index > -1 && tmp_index < nitems)
464 				index = tmp_index;
465 			else
466 				/* otherwise, invalid item, so do nothing */
467 				UI_mouse_click.x = UI_mouse_click.y = -1;
468 
469 			break;
470 #endif
471 			case 0x1b:				/* Esc=Cancel */
472 				return -1;
473 			default:
474 				if (drag || ascii <= 0x20 || ascii >= 0x7f)
475 					continue;
476 				tmp_index = index; /* old index */
477 				do {
478 					if (++index >= nitems)
479 						index = 0;
480 				} while (index != tmp_index && !Util_chrieq((char) ascii, item[index][0]));
481 				break;
482 			}
483 			break;
484 		}
485 		if (message != NULL)
486 			ClearRectangle(0x94, 1, 22, 38, 22);
487 	}
488 }
489 
BasicUISelect(const char * title,int flags,int default_item,const UI_tMenuItem * menu,int * seltype)490 static int BasicUISelect(const char *title, int flags, int default_item, const UI_tMenuItem *menu, int *seltype)
491 {
492 	int nitems;
493 	int index;
494 	const UI_tMenuItem *pmenu;
495 	static const char *prefix[100];
496 	static const char *item[100];
497 	static const char *suffix[100];
498 	static const char *tip[100];
499 	static int nonselectable[100];
500 	int w;
501 	int x1, y1, x2, y2;
502 
503 	nitems = 0;
504 	index = 0;
505 	for (pmenu = menu; pmenu->flags != UI_ITEM_END; pmenu++) {
506 		if (pmenu->flags != UI_ITEM_HIDDEN) {
507 			prefix[nitems] = pmenu->prefix;
508 			item[nitems] = pmenu->item;
509 			if (pmenu->flags & UI_ITEM_TIP) {
510 				suffix[nitems] = NULL;
511 				tip[nitems] = pmenu->suffix;
512 			}
513 			else {
514 				if ((pmenu->flags & UI_ITEM_TYPE) == UI_ITEM_CHECK) {
515 					if (pmenu->flags & UI_ITEM_CHECKED)
516 						suffix[nitems] = "Yes";
517 					else
518 						suffix[nitems] = "No ";
519 				}
520 				else
521 					suffix[nitems] = pmenu->suffix;
522 				tip[nitems] = NULL;
523 			}
524 			nonselectable[nitems] = (pmenu->retval < 0);
525 			if (pmenu->retval == default_item)
526 				index = nitems;
527 			nitems++;
528 		}
529 	}
530 	if (nitems == 0)
531 		return -1; /* cancel immediately */
532 
533 	if (flags & UI_SELECT_POPUP) {
534 		int i;
535 		w = 0;
536 		for (i = 0; i < nitems; i++) {
537 			int ws = strlen(item[i]);
538 			if (prefix[i] != NULL)
539 				ws += strlen(prefix[i]);
540 			if (suffix[i] != NULL)
541 				ws += strlen(suffix[i]);
542 			if (ws > w)
543 				w = ws;
544 		}
545 		if (w > 38)
546 			w = 38;
547 
548 		x1 = (40 - w) / 2 - 1;
549 		x2 = x1 + w + 1;
550 		y1 = (24 - nitems) / 2 - 1;
551 		y2 = y1 + nitems + 1;
552 	}
553 	else {
554 		ClearScreen();
555 		TitleScreen(title);
556 		w = 38;
557 		x1 = 0;
558 		y1 = 1;
559 		x2 = 39;
560 		y2 = 23;
561 	}
562 
563 	if (y1 < 0)
564 		y1 = 0;
565 	if (y2 > 23)
566 		y2 = 23;
567 
568 	Box(0x9a, 0x94, x1, y1, x2, y2);
569 	index = Select(index, nitems, item, prefix, suffix, tip, nonselectable,
570 	                y2 - y1 - 1, 1, x1 + 1, y1 + 1, w,
571 	                (flags & UI_SELECT_DRAG) ? TRUE : FALSE, NULL, seltype);
572 	if (index < 0)
573 		return index;
574 	for (pmenu = menu; pmenu->flags != UI_ITEM_END; pmenu++) {
575 		if (pmenu->flags != UI_ITEM_HIDDEN) {
576 			if (index == 0)
577 				return pmenu->retval;
578 			index--;
579 		}
580 	}
581 	/* shouldn't happen */
582 	return -1;
583 }
584 
BasicUISelectInt(int default_value,int min_value,int max_value)585 static int BasicUISelectInt(int default_value, int min_value, int max_value)
586 {
587 	static char item_values[100][4];
588 	static const char *items[100];
589 	int value;
590 	int nitems;
591 	int nrows;
592 	int ncolumns;
593 	int x1, y1, x2, y2;
594 	if (min_value < 0 || max_value > 99 || min_value > max_value)
595 		return default_value;
596 	nitems = 0;
597 	for (value = min_value; value <= max_value; value++) {
598 		items[nitems] = item_values[nitems];
599 		snprintf(item_values[nitems], sizeof(item_values[0]), "%2d", value);
600 		nitems++;
601 	}
602 	if (nitems <= 10) {
603 		nrows = nitems;
604 		ncolumns = 1;
605 	}
606 	else {
607 		nrows = 10;
608 		ncolumns = (nitems + 9) / 10;
609 	}
610 	x1 = (39 - 3 * ncolumns) >> 1;
611 	y1 = (22 - nrows) >> 1;
612 	x2 = x1 + 3 * ncolumns;
613 	y2 = y1 + nrows + 1;
614 	Box(0x9a, 0x94, x1, y1, x2, y2);
615 	value = Select((default_value >= min_value && default_value <= max_value) ? default_value - min_value : 0,
616 		nitems, items, NULL, NULL, NULL, NULL, nrows, ncolumns, x1 + 1, y1 + 1, 2, FALSE, NULL, NULL);
617 	return value >= 0 ? value + min_value : default_value;
618 }
619 
SelectSlider(int fg,int bg,int x,int y,int width,char const * title,int start_value,int max_value,void (* label_fun)(char * label,int value,void * user_data),void * user_data)620 static int SelectSlider(int fg, int bg, int x, int y, int width,
621                         char const *title, int start_value, int max_value,
622                         void (*label_fun)(char *label, int value, void *user_data),
623                         void *user_data)
624 {
625 	enum { larrow = 126,
626 	       rarrow = 127,
627 	       bar = 18 };
628 	int i;
629 	int value = start_value;
630 	char label[11];
631 	int label_length;
632 
633 	if (value < 0)
634 		value = 0;
635 	else if (value > max_value)
636 		value = max_value;
637 	Box(fg, bg, x, y, x + 1 + width, y + 2);
638 
639 	Print(bg, fg, title, x + 1, y, width);
640 	Plot(fg, bg, larrow, x + 1, y + 1);
641 	Plot(fg, bg, rarrow, x + width, y + 1);
642 
643 	for (;;) {
644 		int ascii;
645 		for (i = x + 2; i < x + width; ++i)
646 			Plot(fg, bg, bar, i, y + 1);
647 		(*label_fun)(label, value, user_data);
648 		label_length = strlen(label);
649 		Print(bg, fg, label,
650 		      max_value == 0 ? x + 2 + (width - label_length - 2) / 2
651 		                     : x + 2 + (width - label_length - 2) * value / max_value,
652 		      y + 1, label_length);
653 		ascii = GetKeyPress();
654 		switch (ascii) {
655 			case 0x1c:				/* Up */
656 				value = 0;
657 				break;
658 			case 0x1d:				/* Down */
659 				value = max_value;
660 				break;
661 			case 0x1e:				/* Left */
662 				if (value > 0)
663 					--value;
664 				break;
665 			case 0x1f:				/* Right */
666 				if (value < max_value)
667 					++value;
668 				break;
669 			case 0x1b:				/* Esc=Cancel */
670 				/* Restore original state if label_fun causes any side effects. */
671 				(*label_fun)(label, start_value, user_data);
672 				return -1;
673 			case 0x7e:				/* Backspace */
674 				value = start_value;
675 				if (value < 0)
676 					value = 0;
677 				else if (value > max_value)
678 					value = max_value;
679 				break;
680 			case 0x9b:				/* Return=Select */
681 				return value;
682 		}
683 	}
684 	return -1;
685 }
686 
BasicUISelectSlider(char const * title,int start_value,int max_value,void (* label_fun)(char * label,int value,void * user_data),void * user_data)687 static int BasicUISelectSlider(char const *title, int start_value, int max_value,
688                                void (*label_fun)(char *label, int value, void *user_data),
689                                void *user_data)
690 {
691 	return SelectSlider(0x9a, 0x94, 3, 11, 32, title, start_value, max_value,
692 			    label_fun, user_data);
693 }
694 
695 #ifdef HAVE_WINDOWS_H
696 
697 static WIN32_FIND_DATA wfd;
698 static HANDLE dh = INVALID_HANDLE_VALUE;
699 
700 #ifdef _WIN32_WCE
701 /* WinCE's FindFirstFile/FindNext file don't return "." or "..". */
702 /* We check if the parent folder exists and add ".." if necessary. */
703 static char parentdir[FILENAME_MAX];
704 #endif
705 
BasicUIOpenDir(const char * dirname)706 static int BasicUIOpenDir(const char *dirname)
707 {
708 #ifdef UNICODE
709 	WCHAR wfilespec[FILENAME_MAX];
710 	if (MultiByteToWideChar(CP_ACP, 0, dirname, -1, wfilespec, FILENAME_MAX - 4) <= 0)
711 		return FALSE;
712 	wcscat(wfilespec, (dirname[0] != '\0' && dirname[strlen(dirname) - 1] != '\\')
713 		? L"\\*.*" : L"*.*");
714 	dh = FindFirstFile(wfilespec, &wfd);
715 #else /* UNICODE */
716 	char filespec[FILENAME_MAX];
717 	Util_strlcpy(filespec, dirname, FILENAME_MAX - 4);
718 	strcat(filespec, (dirname[0] != '\0' && dirname[strlen(dirname) - 1] != '\\')
719 		? "\\*.*" : "*.*");
720 	dh = FindFirstFile(filespec, &wfd);
721 #endif /* UNICODE */
722 #ifdef _WIN32_WCE
723 	Util_splitpath(dirname, parentdir, NULL);
724 #endif
725 	if (dh == INVALID_HANDLE_VALUE) {
726 		/* don't raise error if the path is ok but has no entries:
727 		   Win98 returns ERROR_FILE_NOT_FOUND,
728 		   WinCE returns ERROR_NO_MORE_FILES */
729 		DWORD err = GetLastError();
730 		if (err != ERROR_FILE_NOT_FOUND && err != ERROR_NO_MORE_FILES)
731 			return FALSE;
732 	}
733 	return TRUE;
734 }
735 
BasicUIReadDir(char * filename,int * isdir)736 static int BasicUIReadDir(char *filename, int *isdir)
737 {
738 	if (dh == INVALID_HANDLE_VALUE) {
739 #ifdef _WIN32_WCE
740 		if (parentdir[0] != '\0' && Util_direxists(parentdir)) {
741 			strcpy(filename, "..");
742 			*isdir = TRUE;
743 			parentdir[0] = '\0';
744 			return TRUE;
745 		}
746 #endif /* _WIN32_WCE */
747 		return FALSE;
748 	}
749 #ifdef UNICODE
750 	if (WideCharToMultiByte(CP_ACP, 0, wfd.cFileName, -1, filename, FILENAME_MAX, NULL, NULL) <= 0)
751 		filename[0] = '\0';
752 #else
753 	Util_strlcpy(filename, wfd.cFileName, FILENAME_MAX);
754 #endif /* UNICODE */
755 #ifdef _WIN32_WCE
756 	/* just in case they will implement it some day */
757 	if (strcmp(filename, "..") == 0)
758 		parentdir[0] = '\0';
759 #endif
760 	*isdir = (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? TRUE : FALSE;
761 	if (!FindNextFile(dh, &wfd)) {
762 		FindClose(dh);
763 		dh = INVALID_HANDLE_VALUE;
764 	}
765 	return TRUE;
766 }
767 
768 #define DO_DIR
769 
770 #elif defined(HAVE_OPENDIR)
771 
772 static char dir_path[FILENAME_MAX];
773 static DIR *dp = NULL;
774 
BasicUIOpenDir(const char * dirname)775 static int BasicUIOpenDir(const char *dirname)
776 {
777 	Util_strlcpy(dir_path, dirname, FILENAME_MAX);
778 	dp = opendir(dir_path);
779 	return dp != NULL;
780 }
781 
BasicUIReadDir(char * filename,int * isdir)782 static int BasicUIReadDir(char *filename, int *isdir)
783 {
784 	struct dirent *entry;
785 	char fullfilename[FILENAME_MAX];
786 	struct stat st;
787 	entry = readdir(dp);
788 	if (entry == NULL) {
789 		closedir(dp);
790 		dp = NULL;
791 		return FALSE;
792 	}
793 	strcpy(filename, entry->d_name);
794 	Util_catpath(fullfilename, dir_path, entry->d_name);
795 	stat(fullfilename, &st);
796 	*isdir = S_ISDIR(st.st_mode);
797 	return TRUE;
798 }
799 
800 #define DO_DIR
801 
802 #elif defined(PS2)
803 
804 int Atari_OpenDir(const char *filename);
805 
BasicUIOpenDir(const char * dirname)806 static int BasicUIOpenDir(const char *dirname)
807 {
808 	char filename[FILENAME_MAX];
809 	Util_catpath(filename, dirname, "*");
810 	return Atari_OpenDir(filename);
811 }
812 
813 int Atari_ReadDir(char *fullpath, char *filename, int *isdir,
814                   int *readonly, int *size, char *timetext);
815 
816 #define BasicUIReadDir(filename, isdir)  Atari_ReadDir(NULL, filename, isdir, NULL, NULL, NULL)
817 
818 #define DO_DIR
819 
820 #endif /* defined(PS2) */
821 
822 
823 #ifdef DO_DIR
824 
825 static const char **filenames;
826 #define FILENAMES_INITIAL_SIZE 256 /* preallocate 1 KB */
827 static int n_filenames;
828 
829 /* filename must be malloc'ed or strdup'ed */
FilenamesAdd(const char * filename)830 static void FilenamesAdd(const char *filename)
831 {
832 	if (n_filenames >= FILENAMES_INITIAL_SIZE && (n_filenames & (n_filenames - 1)) == 0) {
833 		/* n_filenames is a power of two: allocate twice as much */
834 		filenames = (const char **) Util_realloc((void *) filenames, 2 * n_filenames * sizeof(const char *));
835 	}
836 	filenames[n_filenames++] = filename;
837 }
838 
FilenamesCmp(const char * filename1,const char * filename2)839 static int FilenamesCmp(const char *filename1, const char *filename2)
840 {
841 	if (filename1[0] == '[') {
842 		if (filename2[0] != '[')
843 			return -1;
844 		if (filename1[1] == '.') {
845 			if (filename2[1] != '.')
846 				return -1;
847 			/* return Util_stricmp(filename1, filename2); */
848 		}
849 		else if (filename2[1] == '.')
850 			return 1;
851 		/* return Util_stricmp(filename1, filename2); */
852 	}
853 	else if (filename2[0] == '[')
854 		return 1;
855 	return Util_stricmp(filename1, filename2);
856 }
857 
858 /* quicksort */
FilenamesSort(const char ** start,const char ** end)859 static void FilenamesSort(const char **start, const char **end)
860 {
861 	while (start + 1 < end) {
862 		const char **left = start + 1;
863 		const char **right = end;
864 		const char *pivot = *start;
865 		const char *tmp;
866 		while (left < right) {
867 			if (FilenamesCmp(*left, pivot) <= 0)
868 				left++;
869 			else {
870 				right--;
871 				tmp = *left;
872 				*left = *right;
873 				*right = tmp;
874 			}
875 		}
876 		left--;
877 		tmp = *left;
878 		*left = *start;
879 		*start = tmp;
880 		FilenamesSort(start, left);
881 		start = right;
882 	}
883 }
884 
FilenamesFree(void)885 static void FilenamesFree(void)
886 {
887 	while (n_filenames > 0)
888 		free((void *) filenames[--n_filenames]);
889 	free((void *) filenames);
890 }
891 
GetDirectory(const char * directory)892 static void GetDirectory(const char *directory)
893 {
894 #ifdef __DJGPP__
895 	unsigned short s_backup = _djstat_flags;
896 	_djstat_flags = _STAT_INODE | _STAT_EXEC_EXT | _STAT_EXEC_MAGIC | _STAT_DIRSIZE |
897 		_STAT_ROOT_TIME | _STAT_WRITEBIT;
898 	/* we do not need any of those 'hard-to-get' informations */
899 #endif	/* DJGPP */
900 
901 	filenames = (const char **) Util_malloc(FILENAMES_INITIAL_SIZE * sizeof(const char *));
902 	n_filenames = 0;
903 
904 	if (BasicUIOpenDir(directory)) {
905 		char filename[FILENAME_MAX];
906 		int isdir;
907 
908 		while (BasicUIReadDir(filename, &isdir)) {
909 			char *filename2;
910 
911 			if (filename[0] == '\0' ||
912 				(filename[0] == '.' && filename[1] == '\0'))
913 				continue;
914 
915 			if (isdir) {
916 				/* add directories as [dir] */
917 				size_t len = strlen(filename);
918 				filename2 = (char *) Util_malloc(len + 3);
919 				memcpy(filename2 + 1, filename, len);
920 				filename2[0] = '[';
921 				filename2[len + 1] = ']';
922 				filename2[len + 2] = '\0';
923 			}
924 			else
925 				filename2 = Util_strdup(filename);
926 
927 			FilenamesAdd(filename2);
928 		}
929 
930 		FilenamesSort(filenames, filenames + n_filenames);
931 	}
932 	else {
933 		Log_print("Error opening '%s' directory", directory);
934 	}
935 #ifdef PS2
936 	FilenamesAdd(Util_strdup("[mc0:]"));
937 #endif
938 #ifdef DOS_DRIVES
939 	/* in DOS/Windows, add all existing disk letters */
940 	{
941 		char letter;
942 #ifdef HAVE_WINDOWS_H
943 		DWORD drive_mask = GetLogicalDrives();
944 		for (letter = 'A'; letter <= 'Z'; letter++) {
945 			if (drive_mask & 1) {
946 				static char drive2[5] = "[C:]";
947 				drive2[1] = letter;
948 				FilenamesAdd(Util_strdup(drive2));
949 			}
950 			drive_mask >>= 1;
951 		}
952 #else /* HAVE_WINDOWS_H */
953 		for (letter = 'A'; letter <= 'Z'; letter++) {
954 #ifdef __DJGPP__
955 			static char drive[3] = "C:";
956 			struct stat st;
957 			drive[0] = letter;
958 			/* don't check floppies - it's slow */
959 			if (letter < 'C' || (stat(drive, &st) == 0 && (st.st_mode & S_IXUSR) != 0))
960 #endif /* __DJGPP__ */
961 			{
962 				static char drive2[5] = "[C:]";
963 				drive2[1] = letter;
964 				FilenamesAdd(Util_strdup(drive2));
965 			}
966 		}
967 #endif /* HAVE_WINDOWS_H */
968 	}
969 #endif /* DOS_DRIVES */
970 #ifdef __DJGPP__
971 	_djstat_flags = s_backup;	/* restore the original state */
972 #endif
973 }
974 
strcatchr(char * s,char c)975 static void strcatchr(char *s, char c)
976 {
977 	while (*s != '\0')
978 		s++;
979 	s[0] = c;
980 	s[1] = '\0';
981 }
982 
983 /* Fills BUF with the path of the current working directory (or, if it fails,
984    with "." or "/"). */
GetCurrentDir(char buf[FILENAME_MAX])985 static void GetCurrentDir(char buf[FILENAME_MAX])
986 {
987 #ifdef HAVE_GETCWD
988 	if (getcwd(buf, FILENAME_MAX) == NULL) {
989 		buf[0] = '/';
990 		buf[1] = '\0';
991 	}
992 #else
993 	buf[0] = '.';
994 	buf[1] = '\0';
995 #endif
996 }
997 
998 /* Select file or directory.
999    The result is returned in path and path is where selection begins (i.e. it must be initialized).
1000    pDirectories are "favourite" directories (there are nDirectories of them). */
FileSelector(char * path,int select_dir,char pDirectories[][FILENAME_MAX],int nDirectories)1001 static int FileSelector(char *path, int select_dir, char pDirectories[][FILENAME_MAX], int nDirectories)
1002 {
1003 	char current_dir[FILENAME_MAX];
1004 	char highlighted_file[FILENAME_MAX + 2]; /* +2 for square brackets */
1005 	highlighted_file[0] = '\0';
1006 	if (path[0] == '\0' && nDirectories > 0)
1007 		strcpy(current_dir, pDirectories[0]);
1008 	else if (select_dir)
1009 		strcpy(current_dir, path);
1010 	else
1011 		Util_splitpath(path, current_dir, highlighted_file);
1012 #ifdef __DJGPP__
1013 	{
1014 		char help_dir[FILENAME_MAX];
1015 		_fixpath(current_dir, help_dir);
1016 		strcpy(current_dir, help_dir);
1017 	}
1018 #elif defined(HAVE_GETCWD)
1019 	if (current_dir[0] == '\0' || (current_dir[0] == '.' && current_dir[1] == '\0'))
1020 #else
1021 	if (current_dir[0] == '\0')
1022 #endif
1023 		GetCurrentDir(current_dir);
1024 	for (;;) {
1025 		int index = 0;
1026 		int i;
1027 
1028 #define NROWS 20
1029 #define NCOLUMNS 2
1030 #define MAX_FILES (NROWS * NCOLUMNS)
1031 
1032 		/* The WinCE version may spend several seconds when there are many
1033 		   files in the directory. */
1034 		/* The extra spaces are needed to clear the previous window title. */
1035 		TitleScreen("            Please wait...            ");
1036 		PLATFORM_DisplayScreen();
1037 
1038 		for (;;) {
1039 			GetDirectory(current_dir);
1040 
1041 			if (n_filenames > 0)
1042 				break;
1043 
1044 			/* Can't read directory - maybe it doesn't exist?
1045 			   Split the last part from the path and try again. */
1046 			FilenamesFree();
1047 			{
1048 				char temp[FILENAME_MAX];
1049 				strcpy(temp, current_dir);
1050 				Util_splitpath(temp, current_dir, NULL);
1051 			}
1052 			if (current_dir[0] == '\0') {
1053 				/* Path couldn't be split further.
1054 				   Try the working directory as a last resort. */
1055 				GetCurrentDir(current_dir);
1056 				GetDirectory(current_dir);
1057 				if (n_filenames >= 0)
1058 					break;
1059 
1060 				FilenamesFree();
1061 				BasicUIMessage("No files inside directory", 1);
1062 				return FALSE;
1063 			}
1064 		}
1065 
1066 		if (highlighted_file[0] != '\0') {
1067 			for (i = 0; i < n_filenames; i++) {
1068 				if (strcmp(filenames[i], highlighted_file) == 0) {
1069 					index = i;
1070 					break;
1071 				}
1072 			}
1073 		}
1074 
1075 		for (;;) {
1076 			int seltype;
1077 			const char *selected_filename;
1078 
1079 			ClearScreen();
1080 			TitleScreen(current_dir);
1081 			Box(0x9a, 0x94, 0, 1, 39, 23);
1082 
1083 			index = Select(index, n_filenames, filenames, NULL, NULL, NULL, NULL,
1084 			               NROWS, NCOLUMNS, 1, 2, 37 / NCOLUMNS, FALSE,
1085 			               select_dir ? "Space: select current directory" : NULL,
1086 			               &seltype);
1087 
1088 			if (index == -2) {
1089 				/* Tab = next favourite directory */
1090 				if (nDirectories > 0) {
1091 					/* default: pDirectories[0] */
1092 					int current_index = nDirectories - 1;
1093 					/* are we in one of pDirectories? */
1094 					for (i = 0; i < nDirectories; i++)
1095 						if (strcmp(pDirectories[i], current_dir) == 0) {
1096 							current_index = i;
1097 							break;
1098 						}
1099 					i = current_index;
1100 					do {
1101 						if (++i >= nDirectories)
1102 							i = 0;
1103 						if (Util_direxists(pDirectories[i])) {
1104 							strcpy(current_dir, pDirectories[i]);
1105 							break;
1106 						}
1107 					} while (i != current_index);
1108 				}
1109 				highlighted_file[0] = '\0';
1110 				break;
1111 			}
1112 			if (index < 0) {
1113 				/* Esc = cancel */
1114 				FilenamesFree();
1115 				return FALSE;
1116 			}
1117 			if (seltype == UI_USER_DELETE) {
1118 				/* Backspace = parent directory */
1119 				char new_dir[FILENAME_MAX];
1120 				Util_splitpath(current_dir, new_dir, highlighted_file + 1);
1121 				if (Util_direxists(new_dir)) {
1122 					strcpy(current_dir, new_dir);
1123 					highlighted_file[0] = '[';
1124 					strcatchr(highlighted_file, ']');
1125 					break;
1126 				}
1127 				BasicUIMessage("Cannot enter parent directory", 1);
1128 				continue;
1129 			}
1130 			if (seltype == UI_USER_TOGGLE && select_dir) {
1131 				/* Space = select current directory */
1132 				strcpy(path, current_dir);
1133 				FilenamesFree();
1134 				return TRUE;
1135 			}
1136 			selected_filename = filenames[index];
1137 			if (selected_filename[0] == '[') {
1138 				/* Change directory */
1139 				char new_dir[FILENAME_MAX];
1140 
1141 				highlighted_file[0] = '\0';
1142 				if (strcmp(selected_filename, "[..]") == 0) {
1143 					/* go up */
1144 					Util_splitpath(current_dir, new_dir, highlighted_file + 1);
1145 					highlighted_file[0] = '[';
1146 					strcatchr(highlighted_file, ']');
1147 				}
1148 #ifdef PS2
1149 				else if (strcmp(selected_filename, "[mc0:]") == 0) {
1150 					strcpy(new_dir, "mc0:/");
1151 				}
1152 #endif
1153 #ifdef DOS_DRIVES
1154 				else if (selected_filename[2] == ':' && selected_filename[3] == ']') {
1155 					/* disk selected */
1156 					new_dir[0] = selected_filename[1];
1157 					new_dir[1] = ':';
1158 					new_dir[2] = '\\';
1159 					new_dir[3] = '\0';
1160 				}
1161 #endif
1162 				else {
1163 					/* directory selected */
1164 					char *pbracket = strrchr(selected_filename, ']');
1165 					if (pbracket == NULL)
1166 						continue; /* XXX: regular file? */
1167 					*pbracket = '\0';	/*cut ']' */
1168 					Util_catpath(new_dir, current_dir, selected_filename + 1);
1169 				}
1170 				/* check if new directory is valid */
1171 				if (Util_direxists(new_dir)) {
1172 					strcpy(current_dir, new_dir);
1173 					break;
1174 				}
1175 				BasicUIMessage("Cannot enter selected directory", 1);
1176 				continue;
1177 			}
1178 			if (!select_dir) {
1179 				/* normal filename selected */
1180 				Util_catpath(path, current_dir, selected_filename);
1181 				FilenamesFree();
1182 				return TRUE;
1183 			}
1184 		}
1185 
1186 		FilenamesFree();
1187 	}
1188 }
1189 
1190 #endif /* DO_DIR */
1191 
1192 /* nDirectories >= 0 means we are editing a file name */
EditString(int fg,int bg,const char * title,char * string,int size,int x,int y,int width,char pDirectories[][FILENAME_MAX],int nDirectories)1193 static int EditString(int fg, int bg, const char *title,
1194                       char *string, int size, int x, int y, int width,
1195                       char pDirectories[][FILENAME_MAX], int nDirectories)
1196 {
1197 	int caret = strlen(string);
1198 	int offset = 0;
1199 	for (;;) {
1200 		int i;
1201 		char *p;
1202 		int ascii;
1203 		Box(fg, bg, x, y, x + 1 + width, y + 2);
1204 		Print(bg, fg, title, x + 1, y, width);
1205 		if (caret - offset >= width)
1206 			offset = caret - width + 1;
1207 		else if (caret < offset)
1208 			offset = caret;
1209 		p = string + offset;
1210 		for (i = 0; i < width; i++)
1211 			if (offset + i == caret)
1212 				Plot(bg, fg, *p != '\0' ? *p++ : ' ', x + 1 + i, y + 1);
1213 			else
1214 				Plot(fg, bg, *p != '\0' ? *p++ : ' ', x + 1 + i, y + 1);
1215 		ascii = GetKeyPress();
1216 		switch (ascii) {
1217 		case 0x1e:				/* Cursor Left */
1218 			if (caret > 0)
1219 				caret--;
1220 			break;
1221 		case 0x1f:				/* Cursor Right */
1222 			if (string[caret] != '\0')
1223 				caret++;
1224 			break;
1225 		case 0x7e:				/* Backspace */
1226 			if (caret > 0) {
1227 				caret--;
1228 				p = string + caret;
1229 				do
1230 					p[0] = p[1];
1231 				while (*p++ != '\0');
1232 			}
1233 			break;
1234 		case 0xfe:				/* Delete */
1235 			if (string[caret] != '\0') {
1236 				p = string + caret;
1237 				do
1238 					p[0] = p[1];
1239 				while (*p++ != '\0');
1240 			}
1241 			break;
1242 		case 0x7d:				/* Clear screen */
1243 		case 0x9c:				/* Delete line */
1244 			caret = 0;
1245 			string[0] = '\0';
1246 			break;
1247 		case 0x9b:				/* Return */
1248 			if (nDirectories >= 0) {
1249 				/* check filename */
1250 				char lastchar;
1251 				if (string[0] == '\0')
1252 					return FALSE;
1253 				lastchar = string[strlen(string) - 1];
1254 				return lastchar != '/' && lastchar != '\\';
1255 			}
1256 			return TRUE;
1257 		case 0x1b:				/* Esc */
1258 			return FALSE;
1259 #ifdef DO_DIR
1260 		case 0x7f:				/* Tab = select directory */
1261 			if (nDirectories >= 0) {
1262 				char temp_filename[FILENAME_MAX + 1];
1263 				char temp_path[FILENAME_MAX];
1264 				char temp_file[FILENAME_MAX];
1265 				char *s;
1266 				/* FIXME: now we append '*' and then discard it
1267 				   just to workaround Util_splitpath() not recognizing
1268 				   Util_DIR_SEP_CHAR when it's the last character */
1269 				strcpy(Util_stpcpy(temp_filename, string), "*");
1270 				Util_splitpath(temp_filename, temp_path, temp_file);
1271 				s = temp_file + strlen(temp_file) - 1;
1272 				if (*s == '*') { /* XXX: should be always... */
1273 					*s = '\0';
1274 					if (FileSelector(temp_path, TRUE, pDirectories, nDirectories)) {
1275 						Util_catpath(string, temp_path, temp_file);
1276 						caret = strlen(string);
1277 						offset = 0;
1278 					}
1279 				}
1280 			}
1281 			break;
1282 #endif
1283 		default:
1284 			/* Insert character */
1285 			i = strlen(string);
1286 			if (i + 1 < size && ascii >= ' ' && ascii < 0x7f) {
1287 				do
1288 					string[i + 1] = string[i];
1289 				while (--i >= caret);
1290 				string[caret++] = (char) ascii;
1291 			}
1292 			break;
1293 		}
1294 	}
1295 }
1296 
1297 /* returns TRUE if accepted filename */
EditFilename(const char * title,char * filename,char directories[][FILENAME_MAX],int n_directories)1298 static int EditFilename(const char *title, char *filename, char directories[][FILENAME_MAX], int n_directories)
1299 {
1300 	char edited_filename[FILENAME_MAX];
1301 	strcpy(edited_filename, filename);
1302 	if (edited_filename[0] == '\0') {
1303 		if (n_directories > 0)
1304 			strcpy(edited_filename, directories[0]);
1305 #ifdef HAVE_GETCWD
1306 		if (edited_filename[0] == '\0') {
1307 			if (getcwd(edited_filename, FILENAME_MAX) == NULL) {
1308 				edited_filename[0] = '/';
1309 				edited_filename[1] = '\0';
1310 			}
1311 			if (edited_filename[0] != '\0' && strlen(edited_filename) < FILENAME_MAX - 1) {
1312 				char *p = edited_filename + strlen(edited_filename) - 1;
1313 				if (*p != '/' && *p != '\\') {
1314 					p[1] = Util_DIR_SEP_CHAR;
1315 					p[2] = '\0';
1316 				}
1317 			}
1318 		}
1319 #endif
1320 	}
1321 	if (!EditString(0x9a, 0x94, title, edited_filename, FILENAME_MAX, 1, 11, 36, directories, n_directories))
1322 		return FALSE;
1323 	strcpy(filename, edited_filename);
1324 	return TRUE;
1325 }
1326 
BasicUIEditString(const char * title,char * string,int size)1327 static int BasicUIEditString(const char *title, char *string, int size)
1328 {
1329 	return EditString(0x9a, 0x94, title, string, size, 3, 11, 32, NULL, -1);
1330 }
1331 
BasicUIGetSaveFilename(char * filename,char directories[][FILENAME_MAX],int n_directories)1332 static int BasicUIGetSaveFilename(char *filename, char directories[][FILENAME_MAX], int n_directories)
1333 {
1334 #ifdef DO_DIR
1335 	return EditFilename("Save as ([Tab] = directory locator)", filename, directories, n_directories);
1336 #else
1337 	return EditFilename("Save as", filename, directories, n_directories);
1338 #endif
1339 }
1340 
BasicUIGetLoadFilename(char * filename,char directories[][FILENAME_MAX],int n_directories)1341 static int BasicUIGetLoadFilename(char *filename, char directories[][FILENAME_MAX], int n_directories)
1342 {
1343 #ifdef DO_DIR
1344 	return FileSelector(filename, FALSE, directories, n_directories);
1345 #else
1346 	return EditFilename("Filename", filename, directories, n_directories);
1347 #endif
1348 }
1349 
BasicUIGetDirectoryPath(char * directory)1350 static int BasicUIGetDirectoryPath(char *directory)
1351 {
1352 #ifdef DO_DIR
1353 	return FileSelector(directory, TRUE, NULL, 0);
1354 #else
1355 	return EditFilename("Path", directory, NULL, -1);
1356 #endif
1357 }
1358 
BasicUIInfoScreen(const char * title,const char * message)1359 static void BasicUIInfoScreen(const char *title, const char *message)
1360 {
1361 	int y = 2;
1362 	ClearScreen();
1363 	TitleScreen(title);
1364 	Box(0x9a, 0x94, 0, 1, 39, 23);
1365 	while (*message != '\n') {
1366 		CenterPrint(0x9a, 0x94, message, y++);
1367 		while (*message++ != '\0');
1368 	}
1369 	BasicUIMessage("Press any key to continue", 1);
1370 }
1371 
BasicUIInit(void)1372 static void BasicUIInit(void)
1373 {
1374 	if (!initialised) {
1375 		MEMORY_GetCharset(charset);
1376 		initialised = TRUE;
1377 	}
1378 }
1379 
1380 UI_tDriver UI_BASIC_driver = {
1381 	&BasicUISelect,
1382 	&BasicUISelectInt,
1383 	&BasicUISelectSlider,
1384 	&BasicUIEditString,
1385 	&BasicUIGetSaveFilename,
1386 	&BasicUIGetLoadFilename,
1387 	&BasicUIGetDirectoryPath,
1388 	&BasicUIMessage,
1389 	&BasicUIInfoScreen,
1390 	&BasicUIInit
1391 };
1392 
1393 #ifdef USE_UI_BASIC_ONSCREEN_KEYBOARD
1394 
UI_BASIC_OnScreenKeyboard(const char * title,int layout)1395 int UI_BASIC_OnScreenKeyboard(const char *title, int layout)
1396 {
1397 #define LAYOUT_LEFT    2
1398 #define LAYOUT_TOP     5
1399 #define LAYOUT_WIDTH   36
1400 #define LAYOUT_HEIGHT  6
1401 	const char *layout_lines[LAYOUT_HEIGHT];
1402 	static int modifiers = 0;
1403 	static int key_x = 0;
1404 	static int key_y = 1;
1405 
1406 	BasicUIInit();
1407 	ClearScreen();
1408 	TitleScreen(title != NULL ? title : "Keyboard emulator");
1409 	Box(0x9a, 0x94, 0, 1, 39, 23);
1410 #ifdef DREAMCAST
1411 	CenterPrint(0x9a, 0x94, "Dreamcast controller buttons:", 20);
1412 	if (title != NULL) {
1413 		CenterPrint(0x9a, 0x94, "A  --  leave with key selected", 21);
1414 		CenterPrint(0x9a, 0x94, "L, R, B  --  leave without selection", 22);
1415 	}
1416 	else {
1417 		CenterPrint(0x9a, 0x94, "A  --  leave with key pressed", 21);
1418 		CenterPrint(0x9a, 0x94, "L, R, B  --  leave without keypress", 22);
1419 	}
1420 #endif
1421 	modifiers &= AKEY_SHFT;
1422 	switch (layout) {
1423 	case Atari800_MACHINE_800:
1424 		layout_lines[0] = "     Start Select Option Atari Break";
1425 		break;
1426 	case Atari800_MACHINE_XLXE:
1427 		layout_lines[0] = "  Help Start Select Option Inv Break";
1428 		break;
1429 	case Atari800_MACHINE_5200:
1430 		layout_lines[0] = NULL;
1431 		break;
1432 	default:
1433 		layout_lines[0] = NULL;
1434 		break;
1435 	}
1436 	for (;;) {
1437 		int x;
1438 		int y;
1439 		int code;
1440 		const char *layout_line;
1441 		if (layout == Atari800_MACHINE_5200) {
1442 			layout_lines[1] = "        Start  Pause  Reset         ";
1443 			layout_lines[2] = "        --1--  --2--  --3--         ";
1444 			layout_lines[3] = "        --4--  --5--  --6--         ";
1445 			layout_lines[4] = "        --7--  --8--  --9--         ";
1446 			layout_lines[5] = "        --*--  --0--  --#--         ";
1447 		}
1448 		else {
1449 			if ((modifiers & AKEY_CTRL) == 0)
1450 				Print(0x9a, 0x94, "         ", 10, 17, 40);
1451 			else
1452 				Print(0x94, 0x9a, " CONTROL ", 10, 17, 40);
1453 			if ((modifiers & AKEY_SHFT) == 0) {
1454 				Print(0x9a, 0x94, "       ", 2, 17, 40);
1455 				layout_lines[1] = "-Esc 1 2 3 4 5 6 7 8 9 0 < > BackSpc";
1456 				layout_lines[2] = "-Tab- Q W E R T Y U I O P - = Return";
1457 				layout_lines[3] = "-Ctrl- A S D F G H J K L ; + * -Caps";
1458 				layout_lines[4] = "-Shift- Z X C V B N M , . / --Shift-";
1459 			}
1460 			else {
1461 				Print(0x94, 0x9a, " SHIFT ", 2, 17, 40);
1462 				layout_lines[1] = "-Esc ! \" # $ % & ' @ ( ) Clr Ins Del";
1463 				layout_lines[2] = "-Tab- Q W E R T Y U I O P _ | Return";
1464 				layout_lines[3] = "-Ctrl- A S D F G H J K L : \\ ^ -Caps";
1465 				layout_lines[4] = "-Shift- Z X C V B N M [ ] ? --Shift-";
1466 			}
1467 			layout_lines[5] = "        -------Space-------         ";
1468 		}
1469 		for (y = 0; y < LAYOUT_HEIGHT; y++)
1470 			if (layout_lines[y] != NULL)
1471 				Print(0x9a, 0x94, layout_lines[y], LAYOUT_LEFT, LAYOUT_TOP + 2 * y, LAYOUT_WIDTH);
1472 		if (layout_lines[key_y] == NULL)
1473 			key_y = 1;
1474 		layout_line = layout_lines[key_y];
1475 		x = key_x;
1476 		/* key_x normally points to inside of a key... */
1477 		if (layout_line[x] != ' ')
1478 			/* find the beginning of this key */
1479 			while (x > 0 && layout_line[x - 1] != ' ')
1480 				x--;
1481 		/* ... if it does not, take the first key in this line. */
1482 		else
1483 			for (x = 0; layout_line[x] == ' '; x++);
1484 		/* highlight the key */
1485 		do
1486 			Plot(0x94, 0x9a, layout_line[x], LAYOUT_LEFT + x, LAYOUT_TOP + 2 * key_y);
1487 		while (layout_line[++x] > ' ');
1488 		/* handle user input */
1489 		switch (GetKeyPress()) {
1490 		case 0x1c:
1491 			if (key_y == 0 || layout_lines[key_y - 1] == NULL)
1492 				break;
1493 			key_y--;
1494 			if (key_x > 0 && layout_lines[key_y][key_x] == ' ')
1495 				key_x--;
1496 			break;
1497 		case 0x1d:
1498 			if (key_y >= LAYOUT_HEIGHT - 1)
1499 				break;
1500 			key_y++;
1501 			if (layout_lines[key_y][key_x] == ' ' && layout_lines[key_y][key_x + 1] != ' ')
1502 				key_x++;
1503 			break;
1504 		case 0x1e:
1505 			while (x > 0) {
1506 				if (layout_line[--x] == ' ') {
1507 					while (x > 0) {
1508 						if (layout_line[--x] > ' ') {
1509 							key_x = x;
1510 							break;
1511 						}
1512 					}
1513 					break;
1514 				}
1515 			}
1516 			break;
1517 		case 0x1f:
1518 			while (layout_line[x] == ' ')
1519 				x++;
1520 			if (layout_line[x] > ' ')
1521 				key_x = x;
1522 			break;
1523 		case 0x1b:
1524 			return AKEY_NONE;
1525 		case 0x9b:
1526 #ifdef _WIN32_WCE
1527 		case 0x20:
1528 #endif
1529 			code = 0;
1530 			while (--x > 0) {
1531 				if (layout_line[x] == ' ') {
1532 					while (x > 0) {
1533 						if (layout_line[--x] > ' ') {
1534 							code++;
1535 							break;
1536 						}
1537 					}
1538 				}
1539 			}
1540 			if (layout == Atari800_MACHINE_5200) {
1541 				static const UBYTE keycodes_5200[5][3] = {
1542 					{ AKEY_5200_START, AKEY_5200_PAUSE, AKEY_5200_RESET },
1543 					{ AKEY_5200_1, AKEY_5200_2, AKEY_5200_3 },
1544 					{ AKEY_5200_4, AKEY_5200_5, AKEY_5200_6 },
1545 					{ AKEY_5200_7, AKEY_5200_8, AKEY_5200_9 },
1546 					{ AKEY_5200_ASTERISK, AKEY_5200_0, AKEY_5200_HASH }
1547 				};
1548 				return keycodes_5200[key_y - 1][code];
1549 			}
1550 			else {
1551 				static const UBYTE keycodes_normal[4][14] = {
1552 					{ AKEY_ESCAPE, AKEY_1, AKEY_2, AKEY_3, AKEY_4, AKEY_5, AKEY_6,
1553 					  AKEY_7, AKEY_8, AKEY_9, AKEY_0, AKEY_LESS, AKEY_GREATER, AKEY_BACKSPACE },
1554 					{ AKEY_TAB, AKEY_q, AKEY_w, AKEY_e, AKEY_r, AKEY_t, AKEY_y,
1555 					  AKEY_u, AKEY_i, AKEY_o, AKEY_p, AKEY_MINUS, AKEY_EQUAL, AKEY_RETURN },
1556 					{ AKEY_CTRL, AKEY_a, AKEY_s, AKEY_d, AKEY_f, AKEY_g, AKEY_h,
1557 					  AKEY_j, AKEY_k, AKEY_l, AKEY_SEMICOLON, AKEY_PLUS, AKEY_ASTERISK, AKEY_CAPSTOGGLE },
1558 					{ AKEY_SHFT, AKEY_z, AKEY_x, AKEY_c, AKEY_v, AKEY_b, AKEY_n,
1559 					  AKEY_m, AKEY_COMMA, AKEY_FULLSTOP, AKEY_SLASH, AKEY_SHFT, AKEY_SHFT, AKEY_SHFT }
1560 				};
1561 				switch (key_y) {
1562 				case 0:
1563 					switch (code + (layout != Atari800_MACHINE_XLXE ? 1 : 0)) {
1564 					case 0:
1565 						return AKEY_HELP ^ modifiers;
1566 					case 1:
1567 						return AKEY_START;
1568 					case 2:
1569 						return AKEY_SELECT;
1570 					case 3:
1571 						return AKEY_OPTION;
1572 					case 4:
1573 						return AKEY_ATARI ^ modifiers;
1574 					case 5:
1575 						return AKEY_BREAK;
1576 					}
1577 				case 5:
1578 					return AKEY_SPACE ^ modifiers;
1579 				default:
1580 					code = keycodes_normal[key_y - 1][code];
1581 					if (code == AKEY_SHFT || code == AKEY_CTRL)
1582 						modifiers ^= code;
1583 					else
1584 						return code ^ modifiers;
1585 					break;
1586 				}
1587 			}
1588 			break;
1589 		default:
1590 			break;
1591 		}
1592 	}
1593 }
1594 
1595 #endif /* USE_UI_BASIC_ONSCREEN_KEYBOARD */
1596