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