/* * ui_basic.c - Atari look&feel user interface driver * * Copyright (C) 1995-1998 David Firth * Copyright (C) 1998-2008 Atari800 development team (see DOC/CREDITS) * * This file is part of the Atari800 emulator project which emulates * the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers. * * Atari800 is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Atari800 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Atari800; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include #include #include /* free() */ #ifdef HAVE_UNISTD_H #include /* getcwd() */ #endif #ifdef HAVE_DIRECT_H #include /* getcwd on MSVC*/ #endif /* XXX: , , */ #ifdef HAVE_DIRENT_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef WIN32 #include #endif #include "antic.h" #include "atari.h" #include "input.h" #include "akey.h" #include "log.h" #include "memory.h" #include "platform.h" #include "screen.h" /* Screen_atari */ #include "ui.h" #include "util.h" #ifdef USE_CURSES extern void curses_clear_screen(void); extern void curses_clear_rectangle(int x1, int y1, int x2, int y2); extern void curses_putch(int x, int y, int ascii, UBYTE fg, UBYTE bg); #endif static int initialised = FALSE; static UBYTE charset[1024]; const unsigned char UI_BASIC_key_to_ascii[256] = { 0x6C, 0x6A, 0x3B, 0x00, 0x00, 0x6B, 0x2B, 0x2A, 0x6F, 0x00, 0x70, 0x75, 0x9B, 0x69, 0x2D, 0x3D, 0x76, 0x00, 0x63, 0x00, 0x00, 0x62, 0x78, 0x7A, 0x34, 0x00, 0x33, 0x36, 0x1B, 0x35, 0x32, 0x31, 0x2C, 0x20, 0x2E, 0x6E, 0x00, 0x6D, 0x2F, 0x00, 0x72, 0x00, 0x65, 0x79, 0x7F, 0x74, 0x77, 0x71, 0x39, 0x00, 0x30, 0x37, 0x7E, 0x38, 0x3C, 0x3E, 0x66, 0x68, 0x64, 0x00, 0x00, 0x67, 0x73, 0x61, 0x4C, 0x4A, 0x3A, 0x00, 0x00, 0x4B, 0x5C, 0x5E, 0x4F, 0x00, 0x50, 0x55, 0x9B, 0x49, 0x5F, 0x7C, 0x56, 0x00, 0x43, 0x00, 0x00, 0x42, 0x58, 0x5A, 0x24, 0x00, 0x23, 0x26, 0x1B, 0x25, 0x22, 0x21, 0x5B, 0x20, 0x5D, 0x4E, 0x00, 0x4D, 0x3F, 0x00, 0x52, 0x00, 0x45, 0x59, 0x9F, 0x54, 0x57, 0x51, 0x28, 0x00, 0x29, 0x27, 0x9C, 0x40, 0x7D, 0x9D, 0x46, 0x48, 0x44, 0x00, 0x00, 0x47, 0x53, 0x41, 0x0C, 0x0A, 0x7B, 0x00, 0x00, 0x0B, 0x1E, 0x1F, 0x0F, 0x00, 0x10, 0x15, 0x9B, 0x09, 0x1C, 0x1D, 0x16, 0x00, 0x03, 0x00, 0x00, 0x02, 0x18, 0x1A, 0x00, 0x00, 0x9B, 0x00, 0x1B, 0x00, 0xFD, 0x00, 0x00, 0x20, 0x60, 0x0E, 0x00, 0x0D, 0x00, 0x00, 0x12, 0x00, 0x05, 0x19, 0x9E, 0x14, 0x17, 0x11, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x7D, 0xFF, 0x06, 0x08, 0x04, 0x00, 0x00, 0x07, 0x13, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; #define KB_DELAY 20 #define KB_AUTOREPEAT 3 static int GetKeyPress(void) { int keycode; if (UI_alt_function >= 0) return 0x1b; /* escape - go to Main Menu */ PLATFORM_DisplayScreen(); for (;;) { static int rep = KB_DELAY; if (PLATFORM_Keyboard() == AKEY_NONE) { rep = KB_DELAY; break; } if (rep == 0) { rep = KB_AUTOREPEAT; break; } rep--; Atari800_Sync(); } do { Atari800_Sync(); keycode = PLATFORM_Keyboard(); switch (keycode) { case AKEY_WARMSTART: UI_alt_function = UI_MENU_RESETW; return 0x1b; /* escape */ case AKEY_COLDSTART: UI_alt_function = UI_MENU_RESETC; return 0x1b; /* escape */ case AKEY_EXIT: UI_alt_function = UI_MENU_EXIT; return 0x1b; /* escape */ case AKEY_UI: if (UI_alt_function >= 0) /* Alt+letter, not F1 */ return 0x1b; /* escape */ break; case AKEY_SCREENSHOT: UI_alt_function = UI_MENU_PCX; return 0x1b; /* escape */ case AKEY_SCREENSHOT_INTERLACE: UI_alt_function = UI_MENU_PCXI; return 0x1b; /* escape */ default: UI_alt_function = -1; /* forget previous Main Menu shortcut */ break; } } while (keycode < 0); return UI_BASIC_key_to_ascii[keycode]; } static void Plot(int fg, int bg, int ch, int x, int y) { #ifdef USE_CURSES curses_putch(x, y, ch, (UBYTE) fg, (UBYTE) bg); #else /* USE_CURSES */ const UBYTE *font_ptr = charset + (ch & 0x7f) * 8; UBYTE *ptr = (UBYTE *) Screen_atari + 24 * Screen_WIDTH + 32 + y * (8 * Screen_WIDTH) + x * 8; int i; int j; for (i = 0; i < 8; i++) { UBYTE data = *font_ptr++; for (j = 0; j < 8; j++) { #ifdef USE_COLOUR_TRANSLATION_TABLE ANTIC_VideoPutByte(ptr++, (UBYTE) colour_translation_table[data & 0x80 ? fg : bg]); #else ANTIC_VideoPutByte(ptr++, (UBYTE) (data & 0x80 ? fg : bg)); #endif data <<= 1; } ptr += Screen_WIDTH - 8; } #endif /* USE_CURSES */ } static void Print(int fg, int bg, const char *string, int x, int y, int maxwidth) { char tmpbuf[40]; if ((int) strlen(string) > maxwidth) { int firstlen = (maxwidth - 3) >> 1; int laststart = strlen(string) - (maxwidth - 3 - firstlen); sprintf(tmpbuf, "%.*s...%s", firstlen, string, string + laststart); string = tmpbuf; } while (*string != '\0') Plot(fg, bg, *string++, x++, y); } static void CenterPrint(int fg, int bg, const char *string, int y) { int length = strlen(string); Print(fg, bg, string, (length < 38) ? (40 - length) >> 1 : 1, y, 38); } static void Box(int fg, int bg, int x1, int y1, int x2, int y2) { int x; int y; for (x = x1 + 1; x < x2; x++) { Plot(fg, bg, 18, x, y1); Plot(fg, bg, 18, x, y2); } for (y = y1 + 1; y < y2; y++) { Plot(fg, bg, 124, x1, y); Plot(fg, bg, 124, x2, y); } Plot(fg, bg, 17, x1, y1); Plot(fg, bg, 5, x2, y1); Plot(fg, bg, 3, x2, y2); Plot(fg, bg, 26, x1, y2); } static void ClearRectangle(int bg, int x1, int y1, int x2, int y2) { #ifdef USE_CURSES curses_clear_rectangle(x1, y1, x2, y2); #else UBYTE *ptr = (UBYTE *) Screen_atari + Screen_WIDTH * 24 + 32 + x1 * 8 + y1 * (Screen_WIDTH * 8); int bytesperline = (x2 - x1 + 1) << 3; UBYTE *end_ptr = (UBYTE *) Screen_atari + Screen_WIDTH * 32 + 32 + y2 * (Screen_WIDTH * 8); while (ptr < end_ptr) { #ifdef USE_COLOUR_TRANSLATION_TABLE ANTIC_VideoMemset(ptr, (UBYTE) colour_translation_table[bg], bytesperline); #else ANTIC_VideoMemset(ptr, (UBYTE) bg, bytesperline); #endif ptr += Screen_WIDTH; } #endif /* USE_CURSES */ } static void ClearScreen(void) { #ifdef USE_CURSES curses_clear_screen(); #else #ifdef USE_COLOUR_TRANSLATION_TABLE ANTIC_VideoMemset((UBYTE *) Screen_atari, colour_translation_table[0x00], Screen_HEIGHT * Screen_WIDTH); #else ANTIC_VideoMemset((UBYTE *) Screen_atari, 0x00, Screen_HEIGHT * Screen_WIDTH); #endif ClearRectangle(0x94, 0, 0, 39, 23); #endif /* USE_CURSES */ } static void TitleScreen(const char *title) { CenterPrint(0x9a, 0x94, title, 0); } static void BasicUIMessage(const char *msg, int waitforkey) { ClearRectangle(0x94, 1, 22, 38, 22); CenterPrint(0x94, 0x9a, msg, 22); if (waitforkey) GetKeyPress(); else PLATFORM_DisplayScreen(); } #ifdef SDL int GetRawKey(void) { ClearRectangle(0x94, 13, 11, 25, 13); Box(0x9a, 0x94, 13, 11, 25, 13); CenterPrint(0x94, 0x9a, "Press a key", 12); PLATFORM_DisplayScreen(); return PLATFORM_GetRawKey(); } #endif static int 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) { int offset = 0; int index = default_item; int localseltype; if (seltype == NULL) seltype = &localseltype; for (;;) { int col; int row; int i; const char *message = global_tip; while (index < offset) offset -= nrows; while (index >= offset + nrows * ncolumns) offset += nrows; ClearRectangle(0x94, xoffset, yoffset, xoffset + ncolumns * (itemwidth + 1) - 2, yoffset + nrows - 1); col = 0; row = 0; for (i = offset; i < nitems; i++) { char szbuf[40 + FILENAME_MAX]; /* allow for prefix and suffix */ char *p = szbuf; if (prefix != NULL && prefix[i] != NULL) p = Util_stpcpy(szbuf, prefix[i]); p = Util_stpcpy(p, item[i]); if (suffix != NULL && suffix[i] != NULL) { char *q = szbuf + itemwidth - strlen(suffix[i]); while (p < q) *p++ = ' '; strcpy(p, suffix[i]); } else { while (p < szbuf + itemwidth) *p++ = ' '; *p = '\0'; } if (i == index) Print(0x94, 0x9a, szbuf, xoffset + col * (itemwidth + 1), yoffset + row, itemwidth); else Print(0x9a, 0x94, szbuf, xoffset + col * (itemwidth + 1), yoffset + row, itemwidth); if (++row >= nrows) { if (++col >= ncolumns) break; row = 0; } } if (tip != NULL && tip[index] != NULL) message = tip[index]; else if (itemwidth < 38 && (int) strlen(item[index]) > itemwidth) /* the selected item was shortened */ message = item[index]; if (message != NULL) CenterPrint(0x94, 0x9a, message, 22); for (;;) { int ascii; int tmp_index; ascii = GetKeyPress(); switch (ascii) { case 0x1c: /* Up */ if (drag) { *seltype = UI_USER_DRAG_UP; return index; } tmp_index = index; do tmp_index--; while (tmp_index >= 0 && nonselectable != NULL && nonselectable[tmp_index]); if (tmp_index >= 0) { index = tmp_index; break; } continue; case 0x1d: /* Down */ if (drag) { *seltype = UI_USER_DRAG_DOWN; return index; } tmp_index = index; do tmp_index++; while (tmp_index < nitems && nonselectable != NULL && nonselectable[tmp_index]); if (tmp_index < nitems) { index = tmp_index; break; } continue; case 0x1e: /* Left */ if (drag) continue; /* cannot drag left */ index = (index > nrows) ? index - nrows : 0; break; case 0x1f: /* Right */ if (drag) continue; /* cannot drag right */ index = (index + nrows < nitems) ? index + nrows : nitems - 1; break; case 0x7f: /* Tab (for exchanging disk directories) */ return -2; /* GOLDA CHANGED */ case 0x20: /* Space */ *seltype = UI_USER_TOGGLE; return index; case 0x7e: /* Backspace */ *seltype = UI_USER_DELETE; return index; case 0x9b: /* Return=Select */ *seltype = UI_USER_SELECT; return index; case 0x1b: /* Esc=Cancel */ return -1; default: if (drag || ascii <= 0x20 || ascii >= 0x7f) continue; tmp_index = index; /* old index */ do { if (++index >= nitems) index = 0; } while (index != tmp_index && !Util_chrieq((char) ascii, item[index][0])); break; } break; } if (message != NULL) ClearRectangle(0x94, 1, 22, 38, 22); } } static int BasicUISelect(const char *title, int flags, int default_item, const UI_tMenuItem *menu, int *seltype) { int nitems; int index; const UI_tMenuItem *pmenu; static const char *prefix[100]; static const char *item[100]; static const char *suffix[100]; static const char *tip[100]; static int nonselectable[100]; int w; int x1, y1, x2, y2; nitems = 0; index = 0; for (pmenu = menu; pmenu->item != NULL; pmenu++) { if (pmenu->flags != UI_ITEM_HIDDEN) { prefix[nitems] = pmenu->prefix; item[nitems] = pmenu->item; if (pmenu->flags & UI_ITEM_TIP) { suffix[nitems] = NULL; tip[nitems] = pmenu->suffix; } else { if ((pmenu->flags & UI_ITEM_TYPE) == UI_ITEM_CHECK) { if (pmenu->flags & UI_ITEM_CHECKED) suffix[nitems] = "Yes"; else suffix[nitems] = "No "; } else suffix[nitems] = pmenu->suffix; tip[nitems] = NULL; } nonselectable[nitems] = (pmenu->retval < 0); if (pmenu->retval == default_item) index = nitems; nitems++; } } if (nitems == 0) return -1; /* cancel immediately */ if (flags & UI_SELECT_POPUP) { int i; w = 0; for (i = 0; i < nitems; i++) { int ws = strlen(item[i]); if (prefix[i] != NULL) ws += strlen(prefix[i]); if (suffix[i] != NULL) ws += strlen(suffix[i]); if (ws > w) w = ws; } if (w > 38) w = 38; x1 = (40 - w) / 2 - 1; x2 = x1 + w + 1; y1 = (24 - nitems) / 2 - 1; y2 = y1 + nitems + 1; } else { ClearScreen(); TitleScreen(title); w = 38; x1 = 0; y1 = 1; x2 = 39; y2 = 23; } Box(0x9a, 0x94, x1, y1, x2, y2); index = Select(index, nitems, item, prefix, suffix, tip, nonselectable, nitems, 1, x1 + 1, y1 + 1, w, (flags & UI_SELECT_DRAG) ? TRUE : FALSE, NULL, seltype); if (index < 0) return index; for (pmenu = menu; pmenu->item != NULL; pmenu++) { if (pmenu->flags != UI_ITEM_HIDDEN) { if (index == 0) return pmenu->retval; index--; } } /* shouldn't happen */ return -1; } static int BasicUISelectInt(int default_value, int min_value, int max_value) { static char item_values[100][4]; static const char *items[100]; int value; int nitems; int nrows; int ncolumns; int x1, y1, x2, y2; if (min_value < 0 || max_value > 99 || min_value > max_value) return default_value; nitems = 0; for (value = min_value; value <= max_value; value++) { items[nitems] = item_values[nitems]; sprintf(item_values[nitems], "%2d", value); nitems++; } if (nitems <= 10) { nrows = nitems; ncolumns = 1; } else { nrows = 10; ncolumns = (nitems + 9) / 10; } x1 = (39 - 3 * ncolumns) >> 1; y1 = (22 - nrows) >> 1; x2 = x1 + 3 * ncolumns; y2 = y1 + nrows + 1; Box(0x9a, 0x94, x1, y1, x2, y2); value = Select((default_value >= min_value && default_value <= max_value) ? default_value - min_value : 0, nitems, items, NULL, NULL, NULL, NULL, nrows, ncolumns, x1 + 1, y1 + 1, 2, FALSE, NULL, NULL); return value >= 0 ? value + min_value : default_value; } #ifdef WIN32 static WIN32_FIND_DATA wfd; static HANDLE dh = INVALID_HANDLE_VALUE; #ifdef _WIN32_WCE /* WinCE's FindFirstFile/FindNext file don't return "." or "..". */ /* We check if the parent folder exists and add ".." if necessary. */ static char parentdir[FILENAME_MAX]; #endif static int BasicUIOpenDir(const char *dirname) { #ifdef UNICODE WCHAR wfilespec[FILENAME_MAX]; if (MultiByteToWideChar(CP_ACP, 0, dirname, -1, wfilespec, FILENAME_MAX - 4) <= 0) return FALSE; wcscat(wfilespec, (dirname[0] != '\0' && dirname[strlen(dirname) - 1] != '\\') ? L"\\*.*" : L"*.*"); dh = FindFirstFile(wfilespec, &wfd); #else /* UNICODE */ char filespec[FILENAME_MAX]; Util_strlcpy(filespec, dirname, FILENAME_MAX - 4); strcat(filespec, (dirname[0] != '\0' && dirname[strlen(dirname) - 1] != '\\') ? "\\*.*" : "*.*"); dh = FindFirstFile(filespec, &wfd); #endif /* UNICODE */ #ifdef _WIN32_WCE Util_splitpath(dirname, parentdir, NULL); #endif if (dh == INVALID_HANDLE_VALUE) { /* don't raise error if the path is ok but has no entries: Win98 returns ERROR_FILE_NOT_FOUND, WinCE returns ERROR_NO_MORE_FILES */ DWORD err = GetLastError(); if (err != ERROR_FILE_NOT_FOUND && err != ERROR_NO_MORE_FILES) return FALSE; } return TRUE; } static int BasicUIReadDir(char *filename, int *isdir) { if (dh == INVALID_HANDLE_VALUE) { #ifdef _WIN32_WCE if (parentdir[0] != '\0' && Util_direxists(parentdir)) { strcpy(filename, ".."); *isdir = TRUE; parentdir[0] = '\0'; return TRUE; } #endif /* _WIN32_WCE */ return FALSE; } #ifdef UNICODE if (WideCharToMultiByte(CP_ACP, 0, wfd.cFileName, -1, filename, FILENAME_MAX, NULL, NULL) <= 0) filename[0] = '\0'; #else Util_strlcpy(filename, wfd.cFileName, FILENAME_MAX); #endif /* UNICODE */ #ifdef _WIN32_WCE /* just in case they will implement it some day */ if (strcmp(filename, "..") == 0) parentdir[0] = '\0'; #endif *isdir = (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? TRUE : FALSE; if (!FindNextFile(dh, &wfd)) { FindClose(dh); dh = INVALID_HANDLE_VALUE; } return TRUE; } #define DO_DIR #elif defined(HAVE_OPENDIR) static char dir_path[FILENAME_MAX]; static DIR *dp = NULL; static int BasicUIOpenDir(const char *dirname) { Util_strlcpy(dir_path, dirname, FILENAME_MAX); dp = opendir(dir_path); return dp != NULL; } static int BasicUIReadDir(char *filename, int *isdir) { struct dirent *entry; char fullfilename[FILENAME_MAX]; struct stat st; entry = readdir(dp); if (entry == NULL) { closedir(dp); dp = NULL; return FALSE; } strcpy(filename, entry->d_name); Util_catpath(fullfilename, dir_path, entry->d_name); stat(fullfilename, &st); *isdir = (st.st_mode & S_IFDIR) ? TRUE : FALSE; return TRUE; } #define DO_DIR #elif defined(PS2) int Atari_OpenDir(const char *filename); static int BasicUIOpenDir(const char *dirname) { char filename[FILENAME_MAX]; Util_catpath(filename, dirname, "*"); return Atari_OpenDir(filename); } int Atari_ReadDir(char *fullpath, char *filename, int *isdir, int *readonly, int *size, char *timetext); #define BasicUIReadDir(filename, isdir) Atari_ReadDir(NULL, filename, isdir, NULL, NULL, NULL) #define DO_DIR #endif /* defined(PS2) */ #ifdef DO_DIR static const char **filenames; #define FILENAMES_INITIAL_SIZE 256 /* preallocate 1 KB */ static int n_filenames; /* filename must be malloc'ed or strdup'ed */ static void FilenamesAdd(const char *filename) { if (n_filenames >= FILENAMES_INITIAL_SIZE && (n_filenames & (n_filenames - 1)) == 0) { /* n_filenames is a power of two: allocate twice as much */ filenames = (const char **) Util_realloc((void *) filenames, 2 * n_filenames * sizeof(const char *)); } filenames[n_filenames++] = filename; } static int FilenamesCmp(const char *filename1, const char *filename2) { if (filename1[0] == '[') { if (filename2[0] != '[') return -1; if (filename1[1] == '.') { if (filename2[1] != '.') return -1; /* return Util_stricmp(filename1, filename2); */ } else if (filename2[1] == '.') return 1; /* return Util_stricmp(filename1, filename2); */ } else if (filename2[0] == '[') return 1; return Util_stricmp(filename1, filename2); } /* quicksort */ static void FilenamesSort(const char **start, const char **end) { while (start + 1 < end) { const char **left = start + 1; const char **right = end; const char *pivot = *start; const char *tmp; while (left < right) { if (FilenamesCmp(*left, pivot) <= 0) left++; else { right--; tmp = *left; *left = *right; *right = tmp; } } left--; tmp = *left; *left = *start; *start = tmp; FilenamesSort(start, left); start = right; } } static void FilenamesFree(void) { while (n_filenames > 0) free((void *) filenames[--n_filenames]); free((void *) filenames); } static void GetDirectory(const char *directory) { #ifdef __DJGPP__ unsigned short s_backup = _djstat_flags; _djstat_flags = _STAT_INODE | _STAT_EXEC_EXT | _STAT_EXEC_MAGIC | _STAT_DIRSIZE | _STAT_ROOT_TIME | _STAT_WRITEBIT; /* we do not need any of those 'hard-to-get' informations */ #endif /* DJGPP */ filenames = (const char **) Util_malloc(FILENAMES_INITIAL_SIZE * sizeof(const char *)); n_filenames = 0; if (BasicUIOpenDir(directory)) { char filename[FILENAME_MAX]; int isdir; while (BasicUIReadDir(filename, &isdir)) { char *filename2; if (filename[0] == '\0' || (filename[0] == '.' && filename[1] == '\0')) continue; if (isdir) { /* add directories as [dir] */ size_t len = strlen(filename); filename2 = (char *) Util_malloc(len + 3); memcpy(filename2 + 1, filename, len); filename2[0] = '['; filename2[len + 1] = ']'; filename2[len + 2] = '\0'; } else filename2 = Util_strdup(filename); FilenamesAdd(filename2); } FilenamesSort(filenames, filenames + n_filenames); } else { Log_print("Error opening '%s' directory", directory); } #ifdef PS2 FilenamesAdd(Util_strdup("[mc0:]")); #endif #ifdef DOS_DRIVES /* in DOS/Windows, add all existing disk letters */ { char letter; #ifdef WIN32 DWORD drive_mask = GetLogicalDrives(); for (letter = 'A'; letter <= 'Z'; letter++) { if (drive_mask & 1) { static char drive2[5] = "[C:]"; drive2[1] = letter; FilenamesAdd(Util_strdup(drive2)); } drive_mask >>= 1; } #else /* WIN32 */ for (letter = 'A'; letter <= 'Z'; letter++) { #ifdef __DJGPP__ static char drive[3] = "C:"; struct stat st; drive[0] = letter; /* don't check floppies - it's slow */ if (letter < 'C' || (stat(drive, &st) == 0 && (st.st_mode & S_IXUSR) != 0)) #endif /* __DJGPP__ */ { static char drive2[5] = "[C:]"; drive2[1] = letter; FilenamesAdd(Util_strdup(drive2)); } } #endif /* WIN32 */ } #endif /* DOS_DRIVES */ #ifdef __DJGPP__ _djstat_flags = s_backup; /* restore the original state */ #endif } static void strcatchr(char *s, char c) { while (*s != '\0') s++; s[0] = c; s[1] = '\0'; } /* Select file or directory. The result is returned in path and path is where selection begins (i.e. it must be initialized). pDirectories are "favourite" directories (there are nDirectories of them). */ static int FileSelector(char *path, int select_dir, char pDirectories[][FILENAME_MAX], int nDirectories) { char current_dir[FILENAME_MAX]; char highlighted_file[FILENAME_MAX + 2]; /* +2 for square brackets */ highlighted_file[0] = '\0'; if (path[0] == '\0' && nDirectories > 0) strcpy(current_dir, pDirectories[0]); else if (select_dir) strcpy(current_dir, path); else Util_splitpath(path, current_dir, highlighted_file); #ifdef __DJGPP__ { char help_dir[FILENAME_MAX]; _fixpath(current_dir, help_dir); strcpy(current_dir, help_dir); } #elif defined(HAVE_GETCWD) if (current_dir[0] == '\0' || (current_dir[0] == '.' && current_dir[1] == '\0')) { if (getcwd(current_dir, FILENAME_MAX) == NULL) { current_dir[0] = '/'; current_dir[1] = '\0'; } } #else if (current_dir[0] == '\0') { current_dir[0] = '.'; current_dir[1] = '\0'; } #endif for (;;) { int index = 0; int i; #define NROWS 20 #define NCOLUMNS 2 #define MAX_FILES (NROWS * NCOLUMNS) /* The WinCE version may spend several seconds when there are many files in the directory. */ /* The extra spaces are needed to clear the previous window title. */ TitleScreen(" Please wait... "); PLATFORM_DisplayScreen(); GetDirectory(current_dir); if (n_filenames == 0) { /* FIXME: change to a safe directory */ FilenamesFree(); BasicUIMessage("No files inside directory", 1); return FALSE; } if (highlighted_file[0] != '\0') { for (i = 0; i < n_filenames; i++) { if (strcmp(filenames[i], highlighted_file) == 0) { index = i; break; } } } for (;;) { int seltype; const char *selected_filename; ClearScreen(); TitleScreen(current_dir); Box(0x9a, 0x94, 0, 1, 39, 23); index = Select(index, n_filenames, filenames, NULL, NULL, NULL, NULL, NROWS, NCOLUMNS, 1, 2, 37 / NCOLUMNS, FALSE, select_dir ? "Space: select current directory" : NULL, &seltype); if (index == -2) { /* Tab = next favourite directory */ if (nDirectories > 0) { /* default: pDirectories[0] */ int current_index = nDirectories - 1; /* are we in one of pDirectories? */ for (i = 0; i < nDirectories; i++) if (strcmp(pDirectories[i], current_dir) == 0) { current_index = i; break; } i = current_index; do { if (++i >= nDirectories) i = 0; if (Util_direxists(pDirectories[i])) { strcpy(current_dir, pDirectories[i]); break; } } while (i != current_index); } highlighted_file[0] = '\0'; break; } if (index < 0) { /* Esc = cancel */ FilenamesFree(); return FALSE; } if (seltype == UI_USER_DELETE) { /* Backspace = parent directory */ char new_dir[FILENAME_MAX]; Util_splitpath(current_dir, new_dir, highlighted_file + 1); if (Util_direxists(new_dir)) { strcpy(current_dir, new_dir); highlighted_file[0] = '['; strcatchr(highlighted_file, ']'); break; } BasicUIMessage("Cannot enter parent directory", 1); continue; } if (seltype == UI_USER_TOGGLE && select_dir) { /* Space = select current directory */ strcpy(path, current_dir); FilenamesFree(); return TRUE; } selected_filename = filenames[index]; if (selected_filename[0] == '[') { /* Change directory */ char new_dir[FILENAME_MAX]; highlighted_file[0] = '\0'; if (strcmp(selected_filename, "[..]") == 0) { /* go up */ Util_splitpath(current_dir, new_dir, highlighted_file + 1); highlighted_file[0] = '['; strcatchr(highlighted_file, ']'); } #ifdef PS2 else if (strcmp(selected_filename, "[mc0:]") == 0) { strcpy(new_dir, "mc0:/"); } #endif #ifdef DOS_DRIVES else if (selected_filename[2] == ':' && selected_filename[3] == ']') { /* disk selected */ new_dir[0] = selected_filename[1]; new_dir[1] = ':'; new_dir[2] = '\\'; new_dir[3] = '\0'; } #endif else { /* directory selected */ char *pbracket = strrchr(selected_filename, ']'); if (pbracket == NULL) continue; /* XXX: regular file? */ *pbracket = '\0'; /*cut ']' */ Util_catpath(new_dir, current_dir, selected_filename + 1); } /* check if new directory is valid */ if (Util_direxists(new_dir)) { strcpy(current_dir, new_dir); break; } BasicUIMessage("Cannot enter selected directory", 1); continue; } if (!select_dir) { /* normal filename selected */ Util_catpath(path, current_dir, selected_filename); FilenamesFree(); return TRUE; } } FilenamesFree(); } } #endif /* DO_DIR */ /* nDirectories >= 0 means we are editing a file name */ static int EditString(int fg, int bg, const char *title, char *string, int size, int x, int y, int width, char pDirectories[][FILENAME_MAX], int nDirectories) { int caret = strlen(string); int offset = 0; for (;;) { int i; char *p; int ascii; Box(fg, bg, x, y, x + 1 + width, y + 2); Print(bg, fg, title, x + 1, y, width); if (caret - offset >= width) offset = caret - width + 1; else if (caret < offset) offset = caret; p = string + offset; for (i = 0; i < width; i++) if (offset + i == caret) Plot(bg, fg, *p != '\0' ? *p++ : ' ', x + 1 + i, y + 1); else Plot(fg, bg, *p != '\0' ? *p++ : ' ', x + 1 + i, y + 1); ascii = GetKeyPress(); switch (ascii) { case 0x1e: /* Cursor Left */ if (caret > 0) caret--; break; case 0x1f: /* Cursor Right */ if (string[caret] != '\0') caret++; break; case 0x7e: /* Backspace */ if (caret > 0) { caret--; p = string + caret; do p[0] = p[1]; while (*p++ != '\0'); } break; case 0xfe: /* Delete */ if (string[caret] != '\0') { p = string + caret; do p[0] = p[1]; while (*p++ != '\0'); } break; case 0x7d: /* Clear screen */ case 0x9c: /* Delete line */ caret = 0; string[0] = '\0'; break; case 0x9b: /* Return */ if (nDirectories >= 0) { /* check filename */ char lastchar; if (string[0] == '\0') return FALSE; lastchar = string[strlen(string) - 1]; return lastchar != '/' && lastchar != '\\'; } return TRUE; case 0x1b: /* Esc */ return FALSE; #ifdef DO_DIR case 0x7f: /* Tab = select directory */ if (nDirectories >= 0) { char temp_filename[FILENAME_MAX + 1]; char temp_path[FILENAME_MAX]; char temp_file[FILENAME_MAX]; char *s; /* FIXME: now we append '*' and then discard it just to workaround Util_splitpath() not recognizing Util_DIR_SEP_CHAR when it's the last character */ strcpy(Util_stpcpy(temp_filename, string), "*"); Util_splitpath(temp_filename, temp_path, temp_file); s = temp_file + strlen(temp_file) - 1; if (*s == '*') { /* XXX: should be always... */ *s = '\0'; if (FileSelector(temp_path, TRUE, pDirectories, nDirectories)) { Util_catpath(string, temp_path, temp_file); caret = strlen(string); offset = 0; } } } break; #endif default: /* Insert character */ i = strlen(string); if (i + 1 < size && ascii >= ' ' && ascii < 0x7f) { do string[i + 1] = string[i]; while (--i >= caret); string[caret++] = (char) ascii; } break; } } } /* returns TRUE if accepted filename */ static int EditFilename(const char *title, char *filename, char directories[][FILENAME_MAX], int n_directories) { char edited_filename[FILENAME_MAX]; strcpy(edited_filename, filename); if (edited_filename[0] == '\0') { if (n_directories > 0) strcpy(edited_filename, directories[0]); #ifdef HAVE_GETCWD if (edited_filename[0] == '\0') { if (getcwd(edited_filename, FILENAME_MAX) == NULL) { edited_filename[0] = '/'; edited_filename[1] = '\0'; } if (edited_filename[0] != '\0' && strlen(edited_filename) < FILENAME_MAX - 1) { char *p = edited_filename + strlen(edited_filename) - 1; if (*p != '/' && *p != '\\') { p[1] = Util_DIR_SEP_CHAR; p[2] = '\0'; } } } #endif } if (!EditString(0x9a, 0x94, title, edited_filename, FILENAME_MAX, 1, 11, 36, directories, n_directories)) return FALSE; strcpy(filename, edited_filename); return TRUE; } static int BasicUIEditString(const char *title, char *string, int size) { return EditString(0x9a, 0x94, title, string, size, 3, 11, 32, NULL, -1); } static int BasicUIGetSaveFilename(char *filename, char directories[][FILENAME_MAX], int n_directories) { #ifdef DO_DIR return EditFilename("Save as ([Tab] = directory locator)", filename, directories, n_directories); #else return EditFilename("Save as", filename, directories, n_directories); #endif } static int BasicUIGetLoadFilename(char *filename, char directories[][FILENAME_MAX], int n_directories) { #ifdef DO_DIR return FileSelector(filename, FALSE, directories, n_directories); #else return EditFilename("Filename", filename, directories, n_directories); #endif } static int BasicUIGetDirectoryPath(char *directory) { #ifdef DO_DIR return FileSelector(directory, TRUE, NULL, 0); #else return EditFilename("Path", directory, NULL, -1); #endif } static void BasicUIInfoScreen(const char *title, const char *message) { int y = 2; ClearScreen(); TitleScreen(title); Box(0x9a, 0x94, 0, 1, 39, 23); while (*message != '\n') { CenterPrint(0x9a, 0x94, message, y++); while (*message++ != '\0'); } BasicUIMessage("Press any key to continue", 1); } static void BasicUIInit(void) { if (!initialised) { MEMORY_GetCharset(charset); initialised = TRUE; } } UI_tDriver UI_BASIC_driver = { &BasicUISelect, &BasicUISelectInt, &BasicUIEditString, &BasicUIGetSaveFilename, &BasicUIGetLoadFilename, &BasicUIGetDirectoryPath, &BasicUIMessage, &BasicUIInfoScreen, &BasicUIInit }; #ifdef USE_UI_BASIC_ONSCREEN_KEYBOARD int UI_BASIC_OnScreenKeyboard(const char *title, int layout) { #define LAYOUT_LEFT 2 #define LAYOUT_TOP 5 #define LAYOUT_WIDTH 36 #define LAYOUT_HEIGHT 6 const char *layout_lines[LAYOUT_HEIGHT]; static int modifiers = 0; static int key_x = 0; static int key_y = 1; BasicUIInit(); ClearScreen(); TitleScreen(title != NULL ? title : "Keyboard emulator"); Box(0x9a, 0x94, 0, 1, 39, 23); #ifdef DREAMCAST CenterPrint(0x9a, 0x94, "Dreamcast controller buttons:", 20); if (title != NULL) { CenterPrint(0x9a, 0x94, "A -- leave with key selected", 21); CenterPrint(0x9a, 0x94, "L, R, B -- leave without selection", 22); } else { CenterPrint(0x9a, 0x94, "A -- leave with key pressed", 21); CenterPrint(0x9a, 0x94, "L, R, B -- leave without keypress", 22); } #endif modifiers &= AKEY_SHFT; switch (layout) { case MACHINE_OSA: case MACHINE_OSB: layout_lines[0] = " Start Select Option Atari Break"; break; case MACHINE_XLXE: layout_lines[0] = " Help Start Select Option Inv Break"; break; case MACHINE_5200: layout_lines[0] = NULL; break; default: layout_lines[0] = NULL; break; } for (;;) { int x; int y; int code; const char *layout_line; if (layout == MACHINE_5200) { layout_lines[1] = " Start Pause Reset "; layout_lines[2] = " --1-- --2-- --3-- "; layout_lines[3] = " --4-- --5-- --6-- "; layout_lines[4] = " --7-- --8-- --9-- "; layout_lines[5] = " --*-- --0-- --#-- "; } else { if ((modifiers & AKEY_CTRL) == 0) Print(0x9a, 0x94, " ", 10, 17, 40); else Print(0x94, 0x9a, " CONTROL ", 10, 17, 40); if ((modifiers & AKEY_SHFT) == 0) { Print(0x9a, 0x94, " ", 2, 17, 40); layout_lines[1] = "-Esc 1 2 3 4 5 6 7 8 9 0 < > BackSpc"; layout_lines[2] = "-Tab- Q W E R T Y U I O P - = Return"; layout_lines[3] = "-Ctrl- A S D F G H J K L ; + * -Caps"; layout_lines[4] = "-Shift- Z X C V B N M , . / --Shift-"; } else { Print(0x94, 0x9a, " SHIFT ", 2, 17, 40); layout_lines[1] = "-Esc ! \" # $ % & ' @ ( ) Clr Ins Del"; layout_lines[2] = "-Tab- Q W E R T Y U I O P _ | Return"; layout_lines[3] = "-Ctrl- A S D F G H J K L : \\ ^ -Caps"; layout_lines[4] = "-Shift- Z X C V B N M [ ] ? --Shift-"; } layout_lines[5] = " -------Space------- "; } for (y = 0; y < LAYOUT_HEIGHT; y++) if (layout_lines[y] != NULL) Print(0x9a, 0x94, layout_lines[y], LAYOUT_LEFT, LAYOUT_TOP + 2 * y, LAYOUT_WIDTH); if (layout_lines[key_y] == NULL) key_y = 1; layout_line = layout_lines[key_y]; x = key_x; /* key_x normally points to inside of a key... */ if (layout_line[x] != ' ') /* find the beginning of this key */ while (x > 0 && layout_line[x - 1] != ' ') x--; /* ... if it does not, take the first key in this line. */ else for (x = 0; layout_line[x] == ' '; x++); /* highlight the key */ do Plot(0x94, 0x9a, layout_line[x], LAYOUT_LEFT + x, LAYOUT_TOP + 2 * key_y); while (layout_line[++x] > ' '); /* handle user input */ switch (GetKeyPress()) { case 0x1c: if (key_y == 0 || layout_lines[key_y - 1] == NULL) break; key_y--; if (key_x > 0 && layout_lines[key_y][key_x] == ' ') key_x--; break; case 0x1d: if (key_y >= LAYOUT_HEIGHT - 1) break; key_y++; if (layout_lines[key_y][key_x] == ' ' && layout_lines[key_y][key_x + 1] != ' ') key_x++; break; case 0x1e: while (x > 0) { if (layout_line[--x] == ' ') { while (x > 0) { if (layout_line[--x] > ' ') { key_x = x; break; } } break; } } break; case 0x1f: while (layout_line[x] == ' ') x++; if (layout_line[x] > ' ') key_x = x; break; case 0x1b: return AKEY_NONE; case 0x9b: #ifdef _WIN32_WCE case 0x20: #endif code = 0; while (--x > 0) { if (layout_line[x] == ' ') { while (x > 0) { if (layout_line[--x] > ' ') { code++; break; } } } } if (layout == MACHINE_5200) { static const UBYTE keycodes_5200[5][3] = { { AKEY_5200_START, AKEY_5200_PAUSE, AKEY_5200_RESET }, { AKEY_5200_1, AKEY_5200_2, AKEY_5200_3 }, { AKEY_5200_4, AKEY_5200_5, AKEY_5200_6 }, { AKEY_5200_7, AKEY_5200_8, AKEY_5200_9 }, { AKEY_5200_ASTERISK, AKEY_5200_0, AKEY_5200_HASH } }; return keycodes_5200[key_y - 1][code]; } else { static const UBYTE keycodes_normal[4][14] = { { AKEY_ESCAPE, AKEY_1, AKEY_2, AKEY_3, AKEY_4, AKEY_5, AKEY_6, AKEY_7, AKEY_8, AKEY_9, AKEY_0, AKEY_LESS, AKEY_GREATER, AKEY_BACKSPACE }, { AKEY_TAB, AKEY_q, AKEY_w, AKEY_e, AKEY_r, AKEY_t, AKEY_y, AKEY_u, AKEY_i, AKEY_o, AKEY_p, AKEY_MINUS, AKEY_EQUAL, AKEY_RETURN }, { AKEY_CTRL, AKEY_a, AKEY_s, AKEY_d, AKEY_f, AKEY_g, AKEY_h, AKEY_j, AKEY_k, AKEY_l, AKEY_SEMICOLON, AKEY_PLUS, AKEY_ASTERISK, AKEY_CAPSTOGGLE }, { AKEY_SHFT, AKEY_z, AKEY_x, AKEY_c, AKEY_v, AKEY_b, AKEY_n, AKEY_m, AKEY_COMMA, AKEY_FULLSTOP, AKEY_SLASH, AKEY_SHFT, AKEY_SHFT, AKEY_SHFT } }; switch (key_y) { case 0: switch (code + (layout != MACHINE_XLXE ? 1 : 0)) { case 0: return AKEY_HELP ^ modifiers; case 1: return AKEY_START; case 2: return AKEY_SELECT; case 3: return AKEY_OPTION; case 4: return AKEY_ATARI ^ modifiers; case 5: return AKEY_BREAK; } case 5: return AKEY_SPACE ^ modifiers; default: code = keycodes_normal[key_y - 1][code]; if (code == AKEY_SHFT || code == AKEY_CTRL) modifiers ^= code; else return code ^ modifiers; break; } } break; default: break; } } } #endif /* USE_UI_BASIC_ONSCREEN_KEYBOARD */