--- hatari/src/gui-sdl/sdlgui.c 2019/04/01 07:11:38 1.1 +++ hatari/src/gui-sdl/sdlgui.c 2019/04/09 08:58:22 1.1.1.16 @@ -1,742 +1,1444 @@ /* Hatari - sdlgui.c - This file is distributed under the GNU Public License, version 2 or at - your option any later version. Read the file gpl.txt for details. + This file is distributed under the GNU General Public License, version 2 + or at your option any later version. Read the file gpl.txt for details. A tiny graphical user interface for Hatari. */ -char SDLGui_rcsid[] = "Hatari $Id: sdlgui.c,v 1.1 2019/04/01 07:11:38 root Exp $"; +const char SDLGui_fileid[] = "Hatari sdlgui.c : " __DATE__ " " __TIME__; #include #include #include +#include +#include #include "main.h" -#include "memAlloc.h" #include "screen.h" #include "sdlgui.h" +#include "str.h" #include "font5x8.h" #include "font10x16.h" +#define DEBUG_INFO 0 +#if DEBUG_INFO +# define Dprintf(a) printf a +#else +# define Dprintf(a) +#endif + +#if WITH_SDL2 +#define SDL_SRCCOLORKEY SDL_TRUE +#define SDLKey SDL_Keycode +#endif +static SDL_Surface *pSdlGuiScrn; /* Pointer to the actual main SDL screen surface */ static SDL_Surface *pSmallFontGfx = NULL; /* The small font graphics */ static SDL_Surface *pBigFontGfx = NULL; /* The big font graphics */ -static SDL_Surface *fontgfx = NULL; /* The actual font graphics */ -static int fontwidth, fontheight; /* Width & height of the actual font */ +static SDL_Surface *pFontGfx = NULL; /* The actual font graphics */ +static int current_object = SDLGUI_NOTFOUND;/* Current selected object */ +static struct { + Uint32 darkbar, midbar, lightbar; + Uint32 darkgrey, midgrey, lightgrey; + Uint32 focus, cursor, underline, editfield; +} colors; + +int sdlgui_fontwidth; /* Width of the actual font */ +int sdlgui_fontheight; /* Height of the actual font */ + +#define UNDERLINE_INDICATOR '_' /*-----------------------------------------------------------------------*/ -/* - Load an 1 plane XBM into a 8 planes SDL_Surface. -*/ -static SDL_Surface *SDLGui_LoadXBM(int w, int h, Uint8 *srcbits) +/** + * Load an 1 plane XBM into a 8 planes SDL_Surface. + */ +static SDL_Surface *SDLGui_LoadXBM(int w, int h, const Uint8 *pXbmBits) { - SDL_Surface *bitmap; - Uint8 *dstbits; - int x, y, srcpitch; - int mask; - - /* Allocate the bitmap */ - bitmap = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 8, 0, 0, 0, 0); - if (bitmap == NULL) - { - fprintf(stderr, "Couldn't allocate bitmap: %s", SDL_GetError()); - return(NULL); - } - - srcpitch = ((w + 7) / 8); - dstbits = (Uint8 *)bitmap->pixels; - mask = 1; - - /* Copy the pixels */ - for (y = 0 ; y < h ; y++) - { - for (x = 0 ; x < w ; x++) - { - dstbits[x] = (srcbits[x / 8] & mask) ? 1 : 0; - mask <<= 1; - mask |= (mask >> 8); - mask &= 0xFF; - } - dstbits += bitmap->pitch; - srcbits += srcpitch; - } + SDL_Surface *bitmap; + Uint8 *dstbits; + const Uint8 *srcbits; + int x, y, srcpitch; + int mask; + + srcbits = pXbmBits; + + /* Allocate the bitmap */ + bitmap = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 8, 0, 0, 0, 0); + if (bitmap == NULL) + { + fprintf(stderr, "Failed to allocate bitmap: %s", SDL_GetError()); + return NULL; + } + + srcpitch = ((w + 7) / 8); + dstbits = (Uint8 *)bitmap->pixels; + mask = 1; + + /* Copy the pixels */ + for (y = 0 ; y < h ; y++) + { + for (x = 0 ; x < w ; x++) + { + dstbits[x] = (srcbits[x / 8] & mask) ? 1 : 0; + mask <<= 1; + mask |= (mask >> 8); + mask &= 0xFF; + } + dstbits += bitmap->pitch; + srcbits += srcpitch; + } - return(bitmap); + return bitmap; } /*-----------------------------------------------------------------------*/ -/* - Initialize the GUI. -*/ +/** + * Initialize the GUI. + */ int SDLGui_Init(void) { - SDL_Color blackWhiteColors[2] = {{255, 255, 255, 0}, {0, 0, 0, 0}}; + SDL_Color blackWhiteColors[2] = {{255, 255, 255, 255}, {0, 0, 0, 255}}; - /* Initialize the font graphics: */ - pSmallFontGfx = SDLGui_LoadXBM(font5x8_width, font5x8_height, font5x8_bits); - pBigFontGfx = SDLGui_LoadXBM(font10x16_width, font10x16_height, font10x16_bits); - if (pSmallFontGfx == NULL || pBigFontGfx == NULL) - { - fprintf(stderr, "Error: Can not init font graphics!\n"); - return -1; - } - - /* Set color palette of the font graphics: */ - SDL_SetColors(pSmallFontGfx, blackWhiteColors, 0, 2); - SDL_SetColors(pBigFontGfx, blackWhiteColors, 0, 2); - - /* Set font color 0 as transparent: */ - SDL_SetColorKey(pSmallFontGfx, (SDL_SRCCOLORKEY|SDL_RLEACCEL), 0); - SDL_SetColorKey(pBigFontGfx, (SDL_SRCCOLORKEY|SDL_RLEACCEL), 0); + if (pSmallFontGfx && pBigFontGfx) + { + /* already initialized */ + return 0; + } + + /* Initialize the font graphics: */ + pSmallFontGfx = SDLGui_LoadXBM(font5x8_width, font5x8_height, font5x8_bits); + pBigFontGfx = SDLGui_LoadXBM(font10x16_width, font10x16_height, font10x16_bits); + if (pSmallFontGfx == NULL || pBigFontGfx == NULL) + { + fprintf(stderr, "Error: Can not init font graphics!\n"); + return -1; + } + + /* Set color palette of the font graphics: */ + SDL_SetColors(pSmallFontGfx, blackWhiteColors, 0, 2); + SDL_SetColors(pBigFontGfx, blackWhiteColors, 0, 2); + + /* Set font color 0 as transparent: */ + SDL_SetColorKey(pSmallFontGfx, (SDL_SRCCOLORKEY|SDL_RLEACCEL), 0); + SDL_SetColorKey(pBigFontGfx, (SDL_SRCCOLORKEY|SDL_RLEACCEL), 0); - return 0; + return 0; } /*-----------------------------------------------------------------------*/ -/* - Uninitialize the GUI. -*/ +/** + * Uninitialize the GUI. + */ int SDLGui_UnInit(void) { - if (pSmallFontGfx) - { - SDL_FreeSurface(pSmallFontGfx); - pSmallFontGfx = NULL; - } - - if (pBigFontGfx) - { - SDL_FreeSurface(pBigFontGfx); - pBigFontGfx = NULL; - } + if (pSmallFontGfx) + { + SDL_FreeSurface(pSmallFontGfx); + pSmallFontGfx = NULL; + } + + if (pBigFontGfx) + { + SDL_FreeSurface(pBigFontGfx); + pBigFontGfx = NULL; + } - return 0; + return 0; } /*-----------------------------------------------------------------------*/ -/* - Prepare the font to suit the actual resolution. -*/ -int SDLGui_PrepareFont(void) +/** + * Inform the SDL-GUI about the actual SDL_Surface screen pointer and + * prepare the font to suit the actual resolution. + */ +int SDLGui_SetScreen(SDL_Surface *pScrn) { - /* Decide which font to use - small or big one: */ - if (sdlscrn->w >= 640 && sdlscrn->h >= 400 && pBigFontGfx != NULL) - { - fontgfx = pBigFontGfx; - } - else - { - fontgfx = pSmallFontGfx; - } - - if (fontgfx == NULL) - { - fprintf(stderr, "Error: A problem with the font occured!\n"); - return -1; - } - - /* Get the font width and height: */ - fontwidth = fontgfx->w/16; - fontheight = fontgfx->h/16; + pSdlGuiScrn = pScrn; + + /* Decide which font to use - small or big one: */ + if (pSdlGuiScrn->w >= 640 && pSdlGuiScrn->h >= 400 && pBigFontGfx != NULL) + { + pFontGfx = pBigFontGfx; + } + else + { + pFontGfx = pSmallFontGfx; + } + + if (pFontGfx == NULL) + { + fprintf(stderr, "Error: A problem with the font occurred!\n"); + return -1; + } + + /* Get the font width and height: */ + sdlgui_fontwidth = pFontGfx->w/16; + sdlgui_fontheight = pFontGfx->h/16; + + /* scrollbar */ + colors.darkbar = SDL_MapRGB(pSdlGuiScrn->format, 64, 64, 64); + colors.midbar = SDL_MapRGB(pSdlGuiScrn->format,128,128,128); + colors.lightbar = SDL_MapRGB(pSdlGuiScrn->format,196,196,196); + /* buttons, midgray is also normal bg color */ + colors.darkgrey = SDL_MapRGB(pSdlGuiScrn->format,128,128,128); + colors.midgrey = SDL_MapRGB(pSdlGuiScrn->format,192,192,192); + colors.lightgrey = SDL_MapRGB(pSdlGuiScrn->format,255,255,255); + /* others */ + colors.focus = SDL_MapRGB(pSdlGuiScrn->format,212,212,212); + colors.cursor = SDL_MapRGB(pSdlGuiScrn->format,128,128,128); + if (sdlgui_fontheight < 16) + colors.underline = SDL_MapRGB(pSdlGuiScrn->format,255,0,255); + else + colors.underline = SDL_MapRGB(pSdlGuiScrn->format,0,0,0); + colors.editfield = SDL_MapRGB(pSdlGuiScrn->format,160,160,160); - return 0; + return 0; } +/*-----------------------------------------------------------------------*/ +/** + * Return character size for current font in given arguments. + */ +void SDLGui_GetFontSize(int *width, int *height) +{ + *width = sdlgui_fontwidth; + *height = sdlgui_fontheight; +} /*-----------------------------------------------------------------------*/ -/* - Center a dialog so that it appears in the middle of the screen. - Note: We only store the coordinates in the root box of the dialog, - all other objects in the dialog are positioned relatively to this one. -*/ +/** + * Center a dialog so that it appears in the middle of the screen. + * Note: We only store the coordinates in the root box of the dialog, + * all other objects in the dialog are positioned relatively to this one. + */ void SDLGui_CenterDlg(SGOBJ *dlg) { - dlg[0].x = (sdlscrn->w/fontwidth-dlg[0].w)/2; - dlg[0].y = (sdlscrn->h/fontheight-dlg[0].h)/2; + dlg[0].x = (pSdlGuiScrn->w/sdlgui_fontwidth-dlg[0].w)/2; + dlg[0].y = (pSdlGuiScrn->h/sdlgui_fontheight-dlg[0].h)/2; } +/*-----------------------------------------------------------------------*/ +/** + * Return text length which ignores underlining. + */ +static int SDLGui_TextLen(const char *str) +{ + int len; + for (len = 0; *str; str++) + { + if (*str != UNDERLINE_INDICATOR) + len++; + } + return len; +} /*-----------------------------------------------------------------------*/ -/* - Draw a text string. -*/ -static void SDLGui_Text(int x, int y, const char *txt) +/** + * Draw a text string (internal version). + */ +static void SDLGui_TextInt(int x, int y, const char *txt, bool underline) { - int i; - char c; - SDL_Rect sr, dr; - - for(i=0; txt[i]!=0; i++) - { - c = txt[i]; - sr.x=fontwidth*(c%16); sr.y=fontheight*(c/16); - sr.w=fontwidth; sr.h=fontheight; - dr.x=x+i*fontwidth; dr.y=y; - dr.w=fontwidth; dr.h=fontheight; - SDL_BlitSurface(fontgfx, &sr, sdlscrn, &dr); - } + int i, offset; + unsigned char c; + SDL_Rect sr, dr; + + /* underline offset needs to go outside the box for smaller font */ + if (sdlgui_fontheight < 16) + offset = sdlgui_fontheight - 1; + else + offset = sdlgui_fontheight - 2; + + i = 0; + while (txt[i]) + { + dr.x=x; + dr.y=y; + dr.w=sdlgui_fontwidth; + dr.h=sdlgui_fontheight; + + c = txt[i++]; + if (c == UNDERLINE_INDICATOR && underline) + { + dr.h = 1; + dr.y += offset; + SDL_FillRect(pSdlGuiScrn, &dr, colors.underline); + continue; + } + /* for now, assume (only) Linux file paths are UTF-8 */ +#if !(defined(WIN32) || defined(USE_LOCALE_CHARSET)) + /* Quick and dirty conversion for latin1 characters only... */ + if ((c & 0xc0) == 0xc0) + { + c = c << 6; + c |= (txt[i++]) & 0x7f; + } + else if (c >= 0x80) + { + printf("Unsupported character '%c' (0x%x)\n", c, c); + } +#endif + x += sdlgui_fontwidth; + + sr.x=sdlgui_fontwidth*(c%16); + sr.y=sdlgui_fontheight*(c/16); + sr.w=sdlgui_fontwidth; + sr.h=sdlgui_fontheight; + SDL_BlitSurface(pFontGfx, &sr, pSdlGuiScrn, &dr); + } } +/*-----------------------------------------------------------------------*/ +/** + * Draw a text string (generic). + */ +void SDLGui_Text(int x, int y, const char *txt) +{ + SDLGui_TextInt(x, y, txt, false); +} /*-----------------------------------------------------------------------*/ -/* - Draw a dialog text object. -*/ -static void SDLGui_DrawText(SGOBJ *tdlg, int objnum) +/** + * Draw a dialog text object. + */ +static void SDLGui_DrawText(const SGOBJ *tdlg, int objnum) { - int x, y; - x = (tdlg[0].x+tdlg[objnum].x)*fontwidth; - y = (tdlg[0].y+tdlg[objnum].y)*fontheight; - SDLGui_Text(x, y, tdlg[objnum].txt); + int x, y; + x = (tdlg[0].x+tdlg[objnum].x)*sdlgui_fontwidth; + y = (tdlg[0].y+tdlg[objnum].y)*sdlgui_fontheight; + + if (tdlg[objnum].flags & SG_EXIT) + { + SDL_Rect rect; + /* Draw background: */ + rect.x = x; + rect.y = y; + rect.w = tdlg[objnum].w * sdlgui_fontwidth; + rect.h = tdlg[objnum].h * sdlgui_fontheight; + if (tdlg[objnum].state & SG_FOCUSED) + SDL_FillRect(pSdlGuiScrn, &rect, colors.focus); + else + SDL_FillRect(pSdlGuiScrn, &rect, colors.midgrey); + } + + SDLGui_Text(x, y, tdlg[objnum].txt); } /*-----------------------------------------------------------------------*/ -/* - Draw a edit field object. -*/ -static void SDLGui_DrawEditField(SGOBJ *edlg, int objnum) +/** + * Draw a edit field object. + */ +static void SDLGui_DrawEditField(const SGOBJ *edlg, int objnum) { - int x, y; - SDL_Rect rect; + int x, y; + SDL_Rect rect; - x = (edlg[0].x+edlg[objnum].x)*fontwidth; - y = (edlg[0].y+edlg[objnum].y)*fontheight; - SDLGui_Text(x, y, edlg[objnum].txt); - - rect.x = x; rect.y = y + edlg[objnum].h * fontheight; - rect.w = edlg[objnum].w * fontwidth; rect.h = 1; - SDL_FillRect(sdlscrn, &rect, SDL_MapRGB(sdlscrn->format,160,160,160)); + x = (edlg[0].x+edlg[objnum].x)*sdlgui_fontwidth; + y = (edlg[0].y+edlg[objnum].y)*sdlgui_fontheight; + SDLGui_Text(x, y, edlg[objnum].txt); + + rect.x = x; + rect.y = y + edlg[objnum].h * sdlgui_fontheight; + rect.w = edlg[objnum].w * sdlgui_fontwidth; + rect.h = 1; + SDL_FillRect(pSdlGuiScrn, &rect, colors.editfield); } /*-----------------------------------------------------------------------*/ -/* - Draw a dialog box object. -*/ -static void SDLGui_DrawBox(SGOBJ *bdlg, int objnum) +/** + * Draw a dialog box object. + */ +static void SDLGui_DrawBox(const SGOBJ *bdlg, int objnum) { - SDL_Rect rect; - int x, y, w, h, offset; - Uint32 grey = SDL_MapRGB(sdlscrn->format,192,192,192); - Uint32 upleftc, downrightc; - - x = bdlg[objnum].x*fontwidth; - y = bdlg[objnum].y*fontheight; - if(objnum>0) /* Since the root object is a box, too, */ - { /* we have to look for it now here and only */ - x += bdlg[0].x*fontwidth; /* add its absolute coordinates if we need to */ - y += bdlg[0].y*fontheight; - } - w = bdlg[objnum].w*fontwidth; - h = bdlg[objnum].h*fontheight; - - if( bdlg[objnum].state&SG_SELECTED ) - { - upleftc = SDL_MapRGB(sdlscrn->format,128,128,128); - downrightc = SDL_MapRGB(sdlscrn->format,255,255,255); - } - else - { - upleftc = SDL_MapRGB(sdlscrn->format,255,255,255); - downrightc = SDL_MapRGB(sdlscrn->format,128,128,128); - } - - /* The root box should be bigger than the screen, so we disable the offset there: */ - if (objnum != 0) - offset = 1; - else - offset = 0; - - /* Draw background: */ - rect.x = x; rect.y = y; - rect.w = w; rect.h = h; - SDL_FillRect(sdlscrn, &rect, grey); - - /* Draw upper border: */ - rect.x = x; rect.y = y - offset; - rect.w = w; rect.h = 1; - SDL_FillRect(sdlscrn, &rect, upleftc); - - /* Draw left border: */ - rect.x = x - offset; rect.y = y; - rect.w = 1; rect.h = h; - SDL_FillRect(sdlscrn, &rect, upleftc); - - /* Draw bottom border: */ - rect.x = x; rect.y = y + h - 1 + offset; - rect.w = w; rect.h = 1; - SDL_FillRect(sdlscrn, &rect, downrightc); - - /* Draw right border: */ - rect.x = x + w - 1 + offset; rect.y = y; - rect.w = 1; rect.h = h; - SDL_FillRect(sdlscrn, &rect, downrightc); + SDL_Rect rect; + int x, y, w, h, offset; + Uint32 color, upleftc, downrightc; + + if (bdlg[objnum].state & SG_FOCUSED) + color = colors.focus; + else + color = colors.midgrey; + + x = bdlg[objnum].x*sdlgui_fontwidth; + y = bdlg[objnum].y*sdlgui_fontheight; + if (objnum > 0) /* Since the root object is a box, too, */ + { + /* we have to look for it now here and only */ + x += bdlg[0].x*sdlgui_fontwidth; /* add its absolute coordinates if we need to */ + y += bdlg[0].y*sdlgui_fontheight; + } + w = bdlg[objnum].w*sdlgui_fontwidth; + h = bdlg[objnum].h*sdlgui_fontheight; + + if (bdlg[objnum].state & SG_SELECTED) + { + upleftc = colors.darkgrey; + downrightc = colors.lightgrey; + } + else + { + upleftc = colors.lightgrey; + downrightc = colors.darkgrey; + } + + /* The root box should be bigger than the screen, so we disable the offset there: */ + if (objnum != 0) + offset = 1; + else + offset = 0; + + /* Draw background: */ + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; + SDL_FillRect(pSdlGuiScrn, &rect, color); + + /* Draw upper border: */ + rect.x = x; + rect.y = y - offset; + rect.w = w; + rect.h = 1; + SDL_FillRect(pSdlGuiScrn, &rect, upleftc); + + /* Draw left border: */ + rect.x = x - offset; + rect.y = y; + rect.w = 1; + rect.h = h; + SDL_FillRect(pSdlGuiScrn, &rect, upleftc); + + /* Draw bottom border: */ + rect.x = x; + rect.y = y + h - 1 + offset; + rect.w = w; + rect.h = 1; + SDL_FillRect(pSdlGuiScrn, &rect, downrightc); + + /* Draw right border: */ + rect.x = x + w - 1 + offset; + rect.y = y; + rect.w = 1; + rect.h = h; + SDL_FillRect(pSdlGuiScrn, &rect, downrightc); } /*-----------------------------------------------------------------------*/ -/* - Draw a normal button. -*/ -static void SDLGui_DrawButton(SGOBJ *bdlg, int objnum) +/** + * Draw a normal button. + */ +static void SDLGui_DrawButton(const SGOBJ *bdlg, int objnum) { - int x,y; + int x,y; - SDLGui_DrawBox(bdlg, objnum); + SDLGui_DrawBox(bdlg, objnum); - x = (bdlg[0].x+bdlg[objnum].x+(bdlg[objnum].w-strlen(bdlg[objnum].txt))/2)*fontwidth; - y = (bdlg[0].y+bdlg[objnum].y+(bdlg[objnum].h-1)/2)*fontheight; + x = (bdlg[0].x + bdlg[objnum].x + (bdlg[objnum].w-SDLGui_TextLen(bdlg[objnum].txt))/2) * sdlgui_fontwidth; + y = (bdlg[0].y + bdlg[objnum].y + (bdlg[objnum].h-1)/2) * sdlgui_fontheight; - if( bdlg[objnum].state&SG_SELECTED ) - { - x+=1; - y+=1; - } - SDLGui_Text(x, y, bdlg[objnum].txt); + if (bdlg[objnum].state & SG_SELECTED) + { + x+=1; + y+=1; + } + SDLGui_TextInt(x, y, bdlg[objnum].txt, true); } +/*-----------------------------------------------------------------------*/ +/** + * If object is focused, draw a focused background to it + */ +static void SDLGui_DrawFocusBg(const SGOBJ *obj, int x, int y) +{ + SDL_Rect rect; + Uint32 color; + + if (obj->state & SG_WASFOCUSED) + color = colors.midgrey; + else if (obj->state & SG_FOCUSED) + color = colors.focus; + else + return; + + rect.x = x; + rect.y = y; + rect.w = obj->w * sdlgui_fontwidth; + rect.h = obj->h * sdlgui_fontheight; + + SDL_FillRect(pSdlGuiScrn, &rect, color); +} /*-----------------------------------------------------------------------*/ -/* - Draw a dialog radio button object. -*/ -static void SDLGui_DrawRadioButton(SGOBJ *rdlg, int objnum) +/** + * Draw a dialog radio button object. + */ +static void SDLGui_DrawRadioButton(const SGOBJ *rdlg, int objnum) { - char str[80]; - int x, y; + char str[80]; + int x, y; - x = (rdlg[0].x+rdlg[objnum].x)*fontwidth; - y = (rdlg[0].y+rdlg[objnum].y)*fontheight; + x = (rdlg[0].x + rdlg[objnum].x) * sdlgui_fontwidth; + y = (rdlg[0].y + rdlg[objnum].y) * sdlgui_fontheight; + SDLGui_DrawFocusBg(&(rdlg[objnum]), x, y); + + if (rdlg[objnum].state & SG_SELECTED) + str[0]=SGRADIOBUTTON_SELECTED; + else + str[0]=SGRADIOBUTTON_NORMAL; + str[1]=' '; + strcpy(&str[2], rdlg[objnum].txt); - if( rdlg[objnum].state&SG_SELECTED ) - str[0]=SGRADIOBUTTON_SELECTED; - else - str[0]=SGRADIOBUTTON_NORMAL; - str[1]=' '; - strcpy(&str[2], rdlg[objnum].txt); + SDLGui_TextInt(x, y, str, true); +} - SDLGui_Text(x, y, str); + +/*-----------------------------------------------------------------------*/ +/** + * Draw a dialog check box object. + */ +static void SDLGui_DrawCheckBox(const SGOBJ *cdlg, int objnum) +{ + char str[80]; + int x, y; + + x = (cdlg[0].x + cdlg[objnum].x) * sdlgui_fontwidth; + y = (cdlg[0].y + cdlg[objnum].y) * sdlgui_fontheight; + SDLGui_DrawFocusBg(&(cdlg[objnum]), x, y); + + if ( cdlg[objnum].state&SG_SELECTED ) + str[0]=SGCHECKBOX_SELECTED; + else + str[0]=SGCHECKBOX_NORMAL; + str[1]=' '; + strcpy(&str[2], cdlg[objnum].txt); + + SDLGui_TextInt(x, y, str, true); } /*-----------------------------------------------------------------------*/ -/* - Draw a dialog check box object. -*/ -static void SDLGui_DrawCheckBox(SGOBJ *cdlg, int objnum) +/** + * Draw a scrollbar button. + */ +static void SDLGui_DrawScrollbar(const SGOBJ *bdlg, int objnum) +{ + SDL_Rect rect; + int x, y, w, h; + + x = bdlg[objnum].x * sdlgui_fontwidth; + y = bdlg[objnum].y * sdlgui_fontheight + bdlg[objnum].h; + + x += bdlg[0].x*sdlgui_fontwidth; /* add mainbox absolute coordinates */ + y += bdlg[0].y*sdlgui_fontheight; /* add mainbox absolute coordinates */ + + w = 1 * sdlgui_fontwidth; + h = bdlg[objnum].w; + + /* Draw background: */ + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; + SDL_FillRect(pSdlGuiScrn, &rect, colors.midbar); + + /* Draw upper border: */ + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = 1; + SDL_FillRect(pSdlGuiScrn, &rect, colors.lightbar); + + /* Draw bottom border: */ + rect.x = x; + rect.y = y + h - 1; + rect.w = w; + rect.h = 1; + SDL_FillRect(pSdlGuiScrn, &rect, colors.darkbar); +} + +/*-----------------------------------------------------------------------*/ +/** + * Draw a dialog popup button object. + */ +static void SDLGui_DrawPopupButton(const SGOBJ *pdlg, int objnum) { - char str[80]; - int x, y; + int x, y, w; + const char *downstr = "\x02"; - x = (cdlg[0].x+cdlg[objnum].x)*fontwidth; - y = (cdlg[0].y+cdlg[objnum].y)*fontheight; + SDLGui_DrawBox(pdlg, objnum); - if( cdlg[objnum].state&SG_SELECTED ) - str[0]=SGCHECKBOX_SELECTED; - else - str[0]=SGCHECKBOX_NORMAL; - str[1]=' '; - strcpy(&str[2], cdlg[objnum].txt); + x = (pdlg[0].x + pdlg[objnum].x) * sdlgui_fontwidth; + y = (pdlg[0].y + pdlg[objnum].y) * sdlgui_fontheight; + w = pdlg[objnum].w * sdlgui_fontwidth; - SDLGui_Text(x, y, str); + SDLGui_TextInt(x, y, pdlg[objnum].txt, true); + SDLGui_Text(x+w-sdlgui_fontwidth, y, downstr); } /*-----------------------------------------------------------------------*/ -/* - Draw a dialog popup button object. -*/ -static void SDLGui_DrawPopupButton(SGOBJ *pdlg, int objnum) +/** + * Let the user insert text into an edit field object. + * NOTE: The dlg[objnum].txt must point to an an array that is big enough + * for dlg[objnum].w characters! + */ +static void SDLGui_EditField(SGOBJ *dlg, int objnum) { - int x, y, w, h; - const char *downstr = "\x02"; + size_t cursorPos; /* Position of the cursor in the edit field */ + int blinkState = 0; /* Used for cursor blinking */ + int bStopEditing = false; /* true if user wants to exit the edit field */ + char *txt; /* Shortcut for dlg[objnum].txt */ + SDL_Rect rect; + SDL_Event event; +#if !WITH_SDL2 + int nOldUnicodeMode; +#endif + + rect.x = (dlg[0].x + dlg[objnum].x) * sdlgui_fontwidth; + rect.y = (dlg[0].y + dlg[objnum].y) * sdlgui_fontheight; + rect.w = (dlg[objnum].w + 1) * sdlgui_fontwidth - 1; + rect.h = dlg[objnum].h * sdlgui_fontheight; + +#if WITH_SDL2 + SDL_SetTextInputRect(&rect); + SDL_StartTextInput(); +#else + /* Enable unicode translation to get shifted etc chars with SDL_PollEvent */ + nOldUnicodeMode = SDL_EnableUNICODE(true); +#endif + + txt = dlg[objnum].txt; + cursorPos = strlen(txt); + + do + { + /* Look for events */ + if (SDL_PollEvent(&event) == 0) + { + /* No event: Wait some time for cursor blinking */ + SDL_Delay(250); + blinkState ^= 1; + } + else + { + /* Handle events */ + do + { + switch (event.type) + { + case SDL_QUIT: /* User wants to quit */ + bQuitProgram = true; + bStopEditing = true; + break; + case SDL_MOUSEBUTTONDOWN: /* Mouse pressed -> stop editing */ + bStopEditing = true; + break; +#if WITH_SDL2 + case SDL_TEXTINPUT: + if (strlen(txt) < (size_t)dlg[objnum].w) + { + memmove(&txt[cursorPos+1], &txt[cursorPos], + strlen(&txt[cursorPos])+1); + txt[cursorPos] = event.text.text[0]; + cursorPos += 1; + } + break; +#endif + case SDL_KEYDOWN: /* Key pressed */ + switch (event.key.keysym.sym) + { + case SDLK_RETURN: + case SDLK_KP_ENTER: + bStopEditing = true; + break; + case SDLK_LEFT: + if (cursorPos > 0) + cursorPos -= 1; + break; + case SDLK_RIGHT: + if (cursorPos < strlen(txt)) + cursorPos += 1; + break; + case SDLK_BACKSPACE: + if (cursorPos > 0) + { + memmove(&txt[cursorPos-1], &txt[cursorPos], strlen(&txt[cursorPos])+1); + cursorPos -= 1; + } + break; + case SDLK_DELETE: + if (cursorPos < strlen(txt)) + memmove(&txt[cursorPos], &txt[cursorPos+1], strlen(&txt[cursorPos+1])+1); + break; + default: +#if !WITH_SDL2 + /* If it is a "good" key then insert it into the text field */ + if (event.key.keysym.unicode >= 32 && event.key.keysym.unicode < 128 + && event.key.keysym.unicode != PATHSEP) + { + if (strlen(txt) < (size_t)dlg[objnum].w) + { + memmove(&txt[cursorPos+1], &txt[cursorPos], strlen(&txt[cursorPos])+1); + txt[cursorPos] = event.key.keysym.unicode; + cursorPos += 1; + } + } +#endif + break; + } + break; + } + } + while (SDL_PollEvent(&event)); + + blinkState = 1; + } + + /* Redraw the text field: */ + SDL_FillRect(pSdlGuiScrn, &rect, colors.midgrey); /* Draw background */ + /* Draw the cursor: */ + if (blinkState && !bStopEditing) + { + SDL_Rect cursorrect; + cursorrect.x = rect.x + cursorPos * sdlgui_fontwidth; + cursorrect.y = rect.y; + cursorrect.w = sdlgui_fontwidth; + cursorrect.h = rect.h; + SDL_FillRect(pSdlGuiScrn, &cursorrect, colors.cursor); + } + SDLGui_Text(rect.x, rect.y, dlg[objnum].txt); /* Draw text */ + SDL_UpdateRects(pSdlGuiScrn, 1, &rect); + } + while (!bStopEditing); + +#if WITH_SDL2 + SDL_StopTextInput(); +#else + SDL_EnableUNICODE(nOldUnicodeMode); +#endif +} - SDLGui_DrawBox(pdlg, objnum); - x = (pdlg[0].x+pdlg[objnum].x)*fontwidth; - y = (pdlg[0].y+pdlg[objnum].y)*fontheight; - w = pdlg[objnum].w*fontwidth; - h = pdlg[objnum].h*fontheight; +/*-----------------------------------------------------------------------*/ +/** + * Draw single object based on its type + */ +static void SDLGui_DrawObj(const SGOBJ *dlg, int i) +{ + switch (dlg[i].type) + { + case SGBOX: + SDLGui_DrawBox(dlg, i); + break; + case SGTEXT: + SDLGui_DrawText(dlg, i); + break; + case SGEDITFIELD: + SDLGui_DrawEditField(dlg, i); + break; + case SGBUTTON: + SDLGui_DrawButton(dlg, i); + break; + case SGRADIOBUT: + SDLGui_DrawRadioButton(dlg, i); + break; + case SGCHECKBOX: + SDLGui_DrawCheckBox(dlg, i); + break; + case SGPOPUP: + SDLGui_DrawPopupButton(dlg, i); + break; + case SGSCROLLBAR: + SDLGui_DrawScrollbar(dlg, i); + break; + } +} + +/*-----------------------------------------------------------------------*/ +/** + * Draw a whole dialog. + */ +void SDLGui_DrawDialog(const SGOBJ *dlg) +{ + int i; - SDLGui_Text(x, y, pdlg[objnum].txt); - SDLGui_Text(x+w-fontwidth, y, downstr); + for (i = 0; dlg[i].type != SGSTOP; i++) + { + SDLGui_DrawObj(dlg, i); + } + SDL_UpdateRect(pSdlGuiScrn, 0,0,0,0); } /*-----------------------------------------------------------------------*/ -/* - Let the user insert text into an edit field object. - NOTE: The dlg[objnum].txt must point to an an array that is big enough - for dlg[objnum].w characters! -*/ -static void SDLGui_EditField(SGOBJ *dlg, int objnum) +/** + * Search an object at a certain position. + * If found, return its index, otherwise SDLGUI_NOTFOUND. + */ +static int SDLGui_FindObj(const SGOBJ *dlg, int fx, int fy) { - int cursorPos; /* Position of the cursor in the edit field */ - int blinkState = 0; /* Used for cursor blinking */ - BOOL bStopEditing = FALSE; /* TRUE if user wants to exit the edit field */ - char *txt; /* Shortcut for dlg[objnum].txt */ - SDL_Rect rect; - Uint32 grey, cursorCol; - SDL_Event event; - - grey = SDL_MapRGB(sdlscrn->format,192,192,192); - cursorCol = SDL_MapRGB(sdlscrn->format,128,128,128); - - rect.x = (dlg[0].x + dlg[objnum].x) * fontwidth; - rect.y = (dlg[0].y + dlg[objnum].y) * fontheight; - rect.w = (dlg[objnum].w + 1) * fontwidth - 1; - rect.h = dlg[objnum].h * fontheight; - - txt = dlg[objnum].txt; - cursorPos = strlen(txt); - - do - { - /* Look for events */ - if(SDL_PollEvent(&event) == 0) - { - /* No event: Wait some time for cursor blinking */ - SDL_Delay(250); - blinkState ^= 1; - } - else - { - int keysym; - - /* Handle events */ - do - { - switch(event.type) - { - case SDL_QUIT: /* User wants to quit */ - bQuitProgram = TRUE; - bStopEditing = TRUE; - break; - case SDL_MOUSEBUTTONDOWN: /* Mouse pressed -> stop editing */ - bStopEditing = TRUE; - break; - case SDL_KEYDOWN: /* Key pressed */ - keysym = event.key.keysym.sym; - switch(keysym) - { - case SDLK_RETURN: - case SDLK_KP_ENTER: - bStopEditing = TRUE; - break; - case SDLK_LEFT: - if(cursorPos > 0) - cursorPos -= 1; - break; - case SDLK_RIGHT: - if(cursorPos < strlen(txt)) - cursorPos += 1; - break; - case SDLK_BACKSPACE: - if(cursorPos > 0) - { - memmove(&txt[cursorPos-1], &txt[cursorPos], strlen(&txt[cursorPos])+1); - cursorPos -= 1; - } - break; - case SDLK_DELETE: - if(cursorPos < strlen(txt)) - memmove(&txt[cursorPos], &txt[cursorPos+1], strlen(&txt[cursorPos+1])+1); - break; - default: - /* If it is a "good" key then insert it into the text field */ - if(keysym >= 32 && keysym < 256) - { - if(strlen(txt) < dlg[objnum].w) - { - memmove(&txt[cursorPos+1], &txt[cursorPos], strlen(&txt[cursorPos])+1); - if(event.key.keysym.mod & (KMOD_LSHIFT|KMOD_RSHIFT)) - txt[cursorPos] = toupper(keysym); - else - txt[cursorPos] = keysym; - cursorPos += 1; - } - } - break; - } - break; - } - } - while(SDL_PollEvent(&event)); - - blinkState = 1; - } - - /* Redraw the text field: */ - SDL_FillRect(sdlscrn, &rect, grey); /* Draw background */ - /* Draw the cursor: */ - if(blinkState && !bStopEditing) - { - SDL_Rect cursorrect; - cursorrect.x = rect.x + cursorPos * fontwidth; cursorrect.y = rect.y; - cursorrect.w = fontwidth; cursorrect.h = rect.h; - SDL_FillRect(sdlscrn, &cursorrect, cursorCol); - } - SDLGui_Text(rect.x, rect.y, dlg[objnum].txt); /* Draw text */ - SDL_UpdateRects(sdlscrn, 1, &rect); - } - while(!bStopEditing); + int len, i; + int ob = SDLGUI_NOTFOUND; + int xpos, ypos; + + len = 0; + while (dlg[len].type != SGSTOP) + len++; + + xpos = fx / sdlgui_fontwidth; + ypos = fy / sdlgui_fontheight; + + /* Now search for the object. + * Searching is done from end to start, + * as later objects cover earlier ones + */ + for (i = len; i >= 0; i--) + { + /* clicked on a scrollbar ? */ + if (dlg[i].type == SGSCROLLBAR) { + if (xpos >= dlg[0].x+dlg[i].x && xpos < dlg[0].x+dlg[i].x+1) { + ypos = dlg[i].y * sdlgui_fontheight + dlg[i].h + dlg[0].y * sdlgui_fontheight; + if (fy >= ypos && fy < ypos + dlg[i].w) { + ob = i; + break; + } + } + } + /* clicked on another object ? */ + else if (xpos >= dlg[0].x+dlg[i].x && ypos >= dlg[0].y+dlg[i].y + && xpos < dlg[0].x+dlg[i].x+dlg[i].w && ypos < dlg[0].y+dlg[i].y+dlg[i].h) + { + ob = i; + break; + } + } + + return ob; } /*-----------------------------------------------------------------------*/ -/* - Draw a whole dialog. -*/ -static void SDLGui_DrawDialog(SGOBJ *dlg) +/** + * Search an object with a special flag (e.g. SG_DEFAULT or SG_CANCEL). + * If found, return its index, otherwise SDLGUI_NOTFOUND. + */ +static int SDLGui_SearchFlags(const SGOBJ *dlg, int flag) { - int i; - for(i=0; dlg[i].type!=-1; i++ ) - { - switch( dlg[i].type ) - { - case SGBOX: - SDLGui_DrawBox(dlg, i); - break; - case SGTEXT: - SDLGui_DrawText(dlg, i); - break; - case SGEDITFIELD: - SDLGui_DrawEditField(dlg, i); - break; - case SGBUTTON: - SDLGui_DrawButton(dlg, i); - break; - case SGRADIOBUT: - SDLGui_DrawRadioButton(dlg, i); - break; - case SGCHECKBOX: - SDLGui_DrawCheckBox(dlg, i); - break; - case SGPOPUP: - SDLGui_DrawPopupButton(dlg, i); - break; - } - } - SDL_UpdateRect(sdlscrn, 0,0,0,0); + int i = 0; + + while (dlg[i].type != SGSTOP) + { + if (dlg[i].flags & flag) + return i; + i++; + } + return SDLGUI_NOTFOUND; } +/*-----------------------------------------------------------------------*/ +/** + * Search an object with a special state (e.g. SG_FOCUSED). + * If found, return its index, otherwise SDLGUI_NOTFOUND. + */ +static int SDLGui_SearchState(const SGOBJ *dlg, int state) +{ + int i = 0; + + while (dlg[i].type != SGSTOP) + { + if (dlg[i].state & state) + return i; + i++; + } + return SDLGUI_NOTFOUND; +} /*-----------------------------------------------------------------------*/ -/* - Search an object at a certain position. -*/ -static int SDLGui_FindObj(SGOBJ *dlg, int fx, int fy) +/** + * Print dialog object flags & state for debug purposes. + */ +static void SDLGui_DebugPrintDialog(const SGOBJ *dlg) +{ +#if DEBUG_INFO + int i; + printf("obj: flags | state\n"); + for (i = 0; dlg[i].type != SGSTOP; i++) + printf("%3d: 0x%02x | 0x%02x\n", i, dlg[i].flags, dlg[i].state); +#endif +} + +/*-----------------------------------------------------------------------*/ +/** + * For given dialog object type, returns whether it could have shortcut key + */ +static bool SDLGui_CanHaveShortcut(int kind) { - int len, i; - int ob = -1; - int xpos, ypos; - - len = 0; - while( dlg[len].type!=-1) len++; - - xpos = fx/fontwidth; - ypos = fy/fontheight; - /* Now search for the object: */ - for(i=len; i>0; i--) - { - if(xpos>=dlg[0].x+dlg[i].x && ypos>=dlg[0].y+dlg[i].y - && xposformat,192,192,192); - - dlgrect.x = dlg[0].x * fontwidth; - dlgrect.y = dlg[0].y * fontheight; - dlgrect.w = dlg[0].w * fontwidth; - dlgrect.h = dlg[0].h * fontheight; - - bgrect.x = bgrect.y = 0; - bgrect.w = dlgrect.w; - bgrect.h = dlgrect.h; - - /* Save background */ - pBgSurface = SDL_CreateRGBSurface(SDL_SWSURFACE, dlgrect.w, dlgrect.h, sdlscrn->format->BitsPerPixel, - sdlscrn->format->Rmask, sdlscrn->format->Gmask, sdlscrn->format->Bmask, sdlscrn->format->Amask); - if (sdlscrn->format->palette != NULL) - { - SDL_SetColors(pBgSurface, sdlscrn->format->palette->colors, 0, sdlscrn->format->palette->ncolors-1); - } - - if (pBgSurface != NULL) - { - SDL_BlitSurface(sdlscrn, &dlgrect, pBgSurface, &bgrect); - } - else - { - fprintf(stderr, "SDLGUI_DoDialog: CreateRGBSurface failed: %s\n", SDL_GetError()); - } - - /* (Re-)draw the dialog */ - SDLGui_DrawDialog(dlg); - - /* Is the left mouse button still pressed? Yes -> Handle TOUCHEXIT objects here */ - SDL_PumpEvents(); - b = SDL_GetMouseState(&i, &j); - obj = SDLGui_FindObj(dlg, i, j); - if(obj>0 && (dlg[obj].flags&SG_TOUCHEXIT) ) - { - oldbutton = obj; - if( b&SDL_BUTTON(1) ) - { - dlg[obj].state |= SG_SELECTED; - retbutton = obj; - } - } - - /* The main loop */ - while(retbutton==0 && !bQuitProgram) - { - if( SDL_WaitEvent(&evnt)==1 ) /* Wait for events */ - switch(evnt.type) - { - case SDL_KEYDOWN: - break; - case SDL_QUIT: - bQuitProgram = TRUE; - break; - case SDL_MOUSEBUTTONDOWN: - obj = SDLGui_FindObj(dlg, evnt.button.x, evnt.button.y); - if(obj>0) - { - if(dlg[obj].type==SGBUTTON) - { - dlg[obj].state |= SG_SELECTED; - SDLGui_DrawButton(dlg, obj); - SDL_UpdateRect(sdlscrn, (dlg[0].x+dlg[obj].x)*fontwidth-2, (dlg[0].y+dlg[obj].y)*fontheight-2, - dlg[obj].w*fontwidth+4, dlg[obj].h*fontheight+4); - oldbutton=obj; - } - if( dlg[obj].flags&SG_TOUCHEXIT ) - { - dlg[obj].state |= SG_SELECTED; - retbutton = obj; - } - } - break; - case SDL_MOUSEBUTTONUP: - obj = SDLGui_FindObj(dlg, evnt.button.x, evnt.button.y); - if(obj>0) - { - switch(dlg[obj].type) - { - case SGBUTTON: - if(oldbutton==obj) - retbutton=obj; - break; - case SGEDITFIELD: - SDLGui_EditField(dlg, obj); - break; - case SGRADIOBUT: - for(i=obj-1; i>0 && dlg[i].type==SGRADIOBUT; i--) - { - dlg[i].state &= ~SG_SELECTED; /* Deselect all radio buttons in this group */ - rct.x = (dlg[0].x+dlg[i].x)*fontwidth; - rct.y = (dlg[0].y+dlg[i].y)*fontheight; - rct.w = fontwidth; rct.h = fontheight; - SDL_FillRect(sdlscrn, &rct, grey); /* Clear old */ - SDLGui_DrawRadioButton(dlg, i); - SDL_UpdateRects(sdlscrn, 1, &rct); - } - for(i=obj+1; dlg[i].type==SGRADIOBUT; i++) - { - dlg[i].state &= ~SG_SELECTED; /* Deselect all radio buttons in this group */ - rct.x = (dlg[0].x+dlg[i].x)*fontwidth; - rct.y = (dlg[0].y+dlg[i].y)*fontheight; - rct.w = fontwidth; rct.h = fontheight; - SDL_FillRect(sdlscrn, &rct, grey); /* Clear old */ - SDLGui_DrawRadioButton(dlg, i); - SDL_UpdateRects(sdlscrn, 1, &rct); - } - dlg[obj].state |= SG_SELECTED; /* Select this radio button */ - rct.x = (dlg[0].x+dlg[obj].x)*fontwidth; - rct.y = (dlg[0].y+dlg[obj].y)*fontheight; - rct.w = fontwidth; rct.h = fontheight; - SDL_FillRect(sdlscrn, &rct, grey); /* Clear old */ - SDLGui_DrawRadioButton(dlg, obj); - SDL_UpdateRects(sdlscrn, 1, &rct); - break; - case SGCHECKBOX: - dlg[obj].state ^= SG_SELECTED; - rct.x = (dlg[0].x+dlg[obj].x)*fontwidth; - rct.y = (dlg[0].y+dlg[obj].y)*fontheight; - rct.w = fontwidth; rct.h = fontheight; - SDL_FillRect(sdlscrn, &rct, grey); /* Clear old */ - SDLGui_DrawCheckBox(dlg, obj); - SDL_UpdateRects(sdlscrn, 1, &rct); - break; - case SGPOPUP: - dlg[obj].state |= SG_SELECTED; - SDLGui_DrawPopupButton(dlg, obj); - SDL_UpdateRect(sdlscrn, (dlg[0].x+dlg[obj].x)*fontwidth-2, (dlg[0].y+dlg[obj].y)*fontheight-2, - dlg[obj].w*fontwidth+4, dlg[obj].h*fontheight+4); - retbutton=obj; - break; - } - } - if(oldbutton>0) - { - dlg[oldbutton].state &= ~SG_SELECTED; - SDLGui_DrawButton(dlg, oldbutton); - SDL_UpdateRect(sdlscrn, (dlg[0].x+dlg[oldbutton].x)*fontwidth-2, (dlg[0].y+dlg[oldbutton].y)*fontheight-2, - dlg[oldbutton].w*fontwidth+4, dlg[oldbutton].h*fontheight+4); - oldbutton = 0; - } - if( dlg[obj].flags&SG_EXIT ) - { - retbutton = obj; - } - break; - } - } - - /* Restore background */ - if (pBgSurface) - { - SDL_BlitSurface(pBgSurface, &bgrect, sdlscrn, &dlgrect); - SDL_FreeSurface(pBgSurface); - } + int old = i; + if (i == SDLGUI_NOTFOUND) + return i; + + for (;;) + { + i += inc; + + /* wrap */ + if (dlg[i].type == SGSTOP) + { + assert(inc > 0); + i = 0; + } + else if (i == 0) + { + assert(inc < 0); + while (dlg[i].type != SGSTOP) + i++; + i--; + } + /* change focus for items that can have shortcuts + * and for items in Fsel lists + */ + if (SDLGui_CanHaveShortcut(dlg[i].type) || (dlg[i].flags & SG_EXIT) != 0) + { + dlg[i].state |= SG_FOCUSED; + SDLGui_DrawObj(dlg, i); + SDL_UpdateRect(pSdlGuiScrn, 0,0,0,0); + return i; + } + /* wrapped around without even initial one matching */ + if (i == old) + return 0; + } + return old; +} - if(bQuitProgram) - retbutton = -1; - return retbutton; +/*-----------------------------------------------------------------------*/ +/** + * Handle button selection, either with mouse or keyboard. + * If handled, return its index, otherwise SDLGUI_NOTFOUND. + */ +static int SDLGui_HandleSelection(SGOBJ *dlg, int obj, int oldbutton) +{ + SDL_Rect rct; + int i, retbutton = SDLGUI_NOTFOUND; + + switch (dlg[obj].type) + { + case SGBUTTON: + if (oldbutton==obj) + retbutton=obj; + break; + case SGSCROLLBAR: + dlg[obj].state &= ~SG_MOUSEDOWN; + + if (oldbutton==obj) + retbutton=obj; + break; + case SGEDITFIELD: + SDLGui_EditField(dlg, obj); + break; + case SGRADIOBUT: + for (i = obj-1; i > 0 && dlg[i].type == SGRADIOBUT; i--) + { + dlg[i].state &= ~SG_SELECTED; /* Deselect all radio buttons in this group */ + rct.x = (dlg[0].x+dlg[i].x)*sdlgui_fontwidth; + rct.y = (dlg[0].y+dlg[i].y)*sdlgui_fontheight; + rct.w = sdlgui_fontwidth; + rct.h = sdlgui_fontheight; + SDL_FillRect(pSdlGuiScrn, &rct, colors.midgrey); /* Clear old */ + SDLGui_DrawRadioButton(dlg, i); + SDL_UpdateRects(pSdlGuiScrn, 1, &rct); + } + for (i = obj+1; dlg[i].type == SGRADIOBUT; i++) + { + dlg[i].state &= ~SG_SELECTED; /* Deselect all radio buttons in this group */ + rct.x = (dlg[0].x+dlg[i].x)*sdlgui_fontwidth; + rct.y = (dlg[0].y+dlg[i].y)*sdlgui_fontheight; + rct.w = sdlgui_fontwidth; + rct.h = sdlgui_fontheight; + SDL_FillRect(pSdlGuiScrn, &rct, colors.midgrey); /* Clear old */ + SDLGui_DrawRadioButton(dlg, i); + SDL_UpdateRects(pSdlGuiScrn, 1, &rct); + } + dlg[obj].state |= SG_SELECTED; /* Select this radio button */ + rct.x = (dlg[0].x+dlg[obj].x)*sdlgui_fontwidth; + rct.y = (dlg[0].y+dlg[obj].y)*sdlgui_fontheight; + rct.w = sdlgui_fontwidth; + rct.h = sdlgui_fontheight; + SDL_FillRect(pSdlGuiScrn, &rct, colors.midgrey); /* Clear old */ + SDLGui_DrawRadioButton(dlg, obj); + SDL_UpdateRects(pSdlGuiScrn, 1, &rct); + break; + case SGCHECKBOX: + dlg[obj].state ^= SG_SELECTED; + rct.x = (dlg[0].x+dlg[obj].x)*sdlgui_fontwidth; + rct.y = (dlg[0].y+dlg[obj].y)*sdlgui_fontheight; + rct.w = sdlgui_fontwidth; + rct.h = sdlgui_fontheight; + SDL_FillRect(pSdlGuiScrn, &rct, colors.midgrey); /* Clear old */ + SDLGui_DrawCheckBox(dlg, obj); + SDL_UpdateRects(pSdlGuiScrn, 1, &rct); + break; + case SGPOPUP: + dlg[obj].state |= SG_SELECTED; + SDLGui_DrawPopupButton(dlg, obj); + SDL_UpdateRect(pSdlGuiScrn, + (dlg[0].x+dlg[obj].x)*sdlgui_fontwidth-2, + (dlg[0].y+dlg[obj].y)*sdlgui_fontheight-2, + dlg[obj].w*sdlgui_fontwidth+4, + dlg[obj].h*sdlgui_fontheight+4); + retbutton=obj; + break; + } + + if (retbutton == SDLGUI_NOTFOUND && (dlg[obj].flags & SG_EXIT) != 0) + { + retbutton = obj; + } + + return retbutton; } + +/*-----------------------------------------------------------------------*/ +/** + * If object with given shortcut is found, handle that. + * If handled, return its index, otherwise SDLGUI_NOTFOUND. + */ +static int SDLGui_HandleShortcut(SGOBJ *dlg, int key) +{ + int i = 0; + while (dlg[i].type != SGSTOP) + { + if (dlg[i].shortcut == key) + return SDLGui_HandleSelection(dlg, i, i); + i++; + } + return SDLGUI_NOTFOUND; +} + +/** + * Scale mouse coordinates in case we've got a re-sized SDL2 window + */ +static void SDLGui_ScaleMouseButtonCoordinates(SDL_MouseButtonEvent *bev) +{ +#if WITH_SDL2 + int win_width, win_height; + + if (bInFullScreen) + return; + + SDL_GetWindowSize(sdlWindow, &win_width, &win_height); + bev->x = bev->x * pSdlGuiScrn->w / win_width; + bev->y = bev->y * pSdlGuiScrn->h / win_height; +#endif +} + +/*-----------------------------------------------------------------------*/ +/** + * Show and process a dialog. Returns either: + * - index of the GUI item that was invoked + * - SDLGUI_UNKNOWNEVENT if an unsupported event occurred + * (will be stored in parameter pEventOut) + * - SDLGUI_QUIT if user wants to close Hatari + * - SDLGUI_ERROR if unable to show dialog + * GUI item indeces are positive, other return values are negative + */ +int SDLGui_DoDialog(SGOBJ *dlg, SDL_Event *pEventOut, bool KeepCurrentObject) +{ + int oldbutton = SDLGUI_NOTFOUND; + int retbutton = SDLGUI_NOTFOUND; + int i, j, b, value, obj; + SDLKey key; + int focused; + SDL_Event sdlEvent; + SDL_Surface *pBgSurface; + SDL_Rect dlgrect, bgrect; + SDL_Joystick *joy = NULL; +#if !WITH_SDL2 + int nOldUnicodeMode; +#endif + + /* In the case of dialog using a scrollbar, we must keep the previous */ + /* value of current_object, as the same dialog is displayed in a loop */ + /* to handle scrolling. For other dialogs, we need to reset current_object */ + /* (ie no object selected at start when displaying the dialog) */ + if ( !KeepCurrentObject ) + current_object = 0; + + if (pSdlGuiScrn->h / sdlgui_fontheight < dlg[0].h) + { + fprintf(stderr, "Screen size too small for dialog!\n"); + return SDLGUI_ERROR; + } + + dlgrect.x = dlg[0].x * sdlgui_fontwidth; + dlgrect.y = dlg[0].y * sdlgui_fontheight; + dlgrect.w = dlg[0].w * sdlgui_fontwidth; + dlgrect.h = dlg[0].h * sdlgui_fontheight; + + bgrect.x = bgrect.y = 0; + bgrect.w = dlgrect.w; + bgrect.h = dlgrect.h; + + /* Save background */ + pBgSurface = SDL_CreateRGBSurface(SDL_SWSURFACE, dlgrect.w, dlgrect.h, pSdlGuiScrn->format->BitsPerPixel, + pSdlGuiScrn->format->Rmask, pSdlGuiScrn->format->Gmask, pSdlGuiScrn->format->Bmask, pSdlGuiScrn->format->Amask); + if (pSdlGuiScrn->format->palette != NULL) + { + SDL_SetColors(pBgSurface, pSdlGuiScrn->format->palette->colors, 0, pSdlGuiScrn->format->palette->ncolors-1); + } + + if (pBgSurface != NULL) + { + SDL_BlitSurface(pSdlGuiScrn, &dlgrect, pBgSurface, &bgrect); + } + else + { + fprintf(stderr, "SDLGUI_DoDialog: CreateRGBSurface failed: %s\n", SDL_GetError()); + } + SDLGui_DebugPrintDialog(dlg); + + /* focus default button if nothing else is focused */ + focused = SDLGui_SearchState(dlg, SG_FOCUSED); + if (focused == SDLGUI_NOTFOUND) + { + int defbutton = SDLGui_SearchFlags(dlg, SG_DEFAULT); + if (defbutton != SDLGUI_NOTFOUND) + { + dlg[defbutton].state |= SG_FOCUSED; + focused = defbutton; + } + } + Dprintf(("focused: %d\n", focused)); + SDLGui_SetShortcuts(dlg); + + /* (Re-)draw the dialog */ + SDLGui_DrawDialog(dlg); + + /* Is the left mouse button still pressed? Yes -> Handle TOUCHEXIT objects here */ + SDL_PumpEvents(); + b = SDL_GetMouseState(&i, &j); + + /* If current object is the scrollbar, and mouse is still down, we can scroll it */ + /* also if the mouse pointer has left the scrollbar */ + if (current_object != SDLGUI_NOTFOUND && dlg[current_object].type == SGSCROLLBAR) { + obj = current_object; + retbutton = obj; + oldbutton = obj; + if (b & SDL_BUTTON(1)) + { + dlg[obj].state |= SG_MOUSEDOWN; + } + else + { + current_object = 0; + dlg[obj].state &= ~SG_MOUSEDOWN; + } + } + else { + obj = SDLGui_FindObj(dlg, i, j); + current_object = obj; + if (obj != SDLGUI_NOTFOUND && (dlg[obj].flags&SG_TOUCHEXIT) ) + { + oldbutton = obj; + if (b & SDL_BUTTON(1)) + { + dlg[obj].state |= SG_SELECTED; + retbutton = obj; + } + } + } + + if (SDL_NumJoysticks() > 0) + joy = SDL_JoystickOpen(0); + +#if !WITH_SDL2 + /* Enable unicode translation to get shifted etc chars with SDL_PollEvent */ + nOldUnicodeMode = SDL_EnableUNICODE(true); +#endif + Dprintf(("ENTER - obj: %d, old: %d, ret: %d\n", obj, oldbutton, retbutton)); + + /* The main loop */ + while (retbutton == SDLGUI_NOTFOUND && !bQuitProgram) + { + if (SDL_WaitEvent(&sdlEvent) == 1) /* Wait for events */ + + switch (sdlEvent.type) + { + case SDL_QUIT: + retbutton = SDLGUI_QUIT; + break; + + case SDL_MOUSEBUTTONDOWN: + if (sdlEvent.button.button != SDL_BUTTON_LEFT) + { + /* Not left mouse button -> unsupported event */ + if (pEventOut) + retbutton = SDLGUI_UNKNOWNEVENT; + break; + } + /* It was the left button: Find the object under the mouse cursor */ + SDLGui_ScaleMouseButtonCoordinates(&sdlEvent.button); + obj = SDLGui_FindObj(dlg, sdlEvent.button.x, sdlEvent.button.y); + if (obj != SDLGUI_NOTFOUND) + { + if (dlg[obj].type==SGBUTTON) + { + dlg[obj].state |= SG_SELECTED; + SDLGui_DrawButton(dlg, obj); + SDL_UpdateRect(pSdlGuiScrn, (dlg[0].x+dlg[obj].x)*sdlgui_fontwidth-2, (dlg[0].y+dlg[obj].y)*sdlgui_fontheight-2, + dlg[obj].w*sdlgui_fontwidth+4, dlg[obj].h*sdlgui_fontheight+4); + oldbutton=obj; + } + if (dlg[obj].type==SGSCROLLBAR) + { + dlg[obj].state |= SG_MOUSEDOWN; + oldbutton=obj; + } + if ( dlg[obj].flags&SG_TOUCHEXIT ) + { + dlg[obj].state |= SG_SELECTED; + retbutton = obj; + } + } + break; + + case SDL_MOUSEBUTTONUP: + if (sdlEvent.button.button != SDL_BUTTON_LEFT) + { + /* Not left mouse button -> unsupported event */ + if (pEventOut) + retbutton = SDLGUI_UNKNOWNEVENT; + break; + } + /* It was the left button: Find the object under the mouse cursor */ + SDLGui_ScaleMouseButtonCoordinates(&sdlEvent.button); + obj = SDLGui_FindObj(dlg, sdlEvent.button.x, sdlEvent.button.y); + if (obj != SDLGUI_NOTFOUND) + { + retbutton = SDLGui_HandleSelection(dlg, obj, oldbutton); + } + if (oldbutton != SDLGUI_NOTFOUND && dlg[oldbutton].type == SGBUTTON) + { + dlg[oldbutton].state &= ~SG_SELECTED; + SDLGui_DrawButton(dlg, oldbutton); + SDL_UpdateRect(pSdlGuiScrn, (dlg[0].x+dlg[oldbutton].x)*sdlgui_fontwidth-2, (dlg[0].y+dlg[oldbutton].y)*sdlgui_fontheight-2, + dlg[oldbutton].w*sdlgui_fontwidth+4, dlg[oldbutton].h*sdlgui_fontheight+4); + oldbutton = SDLGUI_NOTFOUND; + } + break; + + case SDL_JOYAXISMOTION: + value = sdlEvent.jaxis.value; + if (value < -3200 || value > 3200) + { + if(sdlEvent.jaxis.axis == 0) + { + /* Left-right movement */ + if (value < 0) + retbutton = SDLGui_HandleShortcut(dlg, SG_SHORTCUT_LEFT); + else + retbutton = SDLGui_HandleShortcut(dlg, SG_SHORTCUT_RIGHT); + } + else if(sdlEvent.jaxis.axis == 1) + { + /* Up-Down movement */ + if (value < 0) + { + SDLGui_RemoveFocus(dlg, focused); + focused = SDLGui_FocusNext(dlg, focused, -1); + } + else + { + SDLGui_RemoveFocus(dlg, focused); + focused = SDLGui_FocusNext(dlg, focused, +1); + } + } + } + break; + + case SDL_JOYBUTTONDOWN: + retbutton = SDLGui_HandleSelection(dlg, focused, focused); + break; + + case SDL_JOYBALLMOTION: + case SDL_JOYHATMOTION: + case SDL_MOUSEMOTION: + break; + + case SDL_KEYDOWN: /* Key pressed */ + key = sdlEvent.key.keysym.sym; + /* keyboard shortcuts are with modifiers */ + if (sdlEvent.key.keysym.mod & KMOD_LALT + || sdlEvent.key.keysym.mod & KMOD_RALT) + { + if (key == SDLK_LEFT) + retbutton = SDLGui_HandleShortcut(dlg, SG_SHORTCUT_LEFT); + else if (key == SDLK_RIGHT) + retbutton = SDLGui_HandleShortcut(dlg, SG_SHORTCUT_RIGHT); + else if (key == SDLK_UP) + retbutton = SDLGui_HandleShortcut(dlg, SG_SHORTCUT_UP); + else if (key == SDLK_DOWN) + retbutton = SDLGui_HandleShortcut(dlg, SG_SHORTCUT_DOWN); + else + { +#if !WITH_SDL2 + /* unicode member is needed to handle shifted etc special chars */ + key = sdlEvent.key.keysym.unicode; +#endif + if (key >= 33 && key <= 126) + retbutton = SDLGui_HandleShortcut(dlg, toupper(key)); + } + if (!retbutton && pEventOut) + retbutton = SDLGUI_UNKNOWNEVENT; + break; + } + switch (key) + { + case SDLK_UP: + case SDLK_LEFT: + SDLGui_RemoveFocus(dlg, focused); + focused = SDLGui_FocusNext(dlg, focused, -1); + break; + case SDLK_TAB: + case SDLK_DOWN: + case SDLK_RIGHT: + SDLGui_RemoveFocus(dlg, focused); + focused = SDLGui_FocusNext(dlg, focused, +1); + break; + case SDLK_HOME: + SDLGui_RemoveFocus(dlg, focused); + focused = SDLGui_FocusNext(dlg, 1, +1); + break; + case SDLK_END: + SDLGui_RemoveFocus(dlg, focused); + focused = SDLGui_FocusNext(dlg, 1, -1); + break; + case SDLK_SPACE: + case SDLK_RETURN: + case SDLK_KP_ENTER: + retbutton = SDLGui_HandleSelection(dlg, focused, focused); + break; + case SDLK_ESCAPE: + retbutton = SDLGui_SearchFlags(dlg, SG_CANCEL); + break; + default: + if (pEventOut) + retbutton = SDLGUI_UNKNOWNEVENT; + break; + } + break; + +#if WITH_SDL2 + case SDL_WINDOWEVENT: + if (sdlEvent.window.event == SDL_WINDOWEVENT_SIZE_CHANGED + || sdlEvent.window.event == SDL_WINDOWEVENT_RESTORED + || sdlEvent.window.event == SDL_WINDOWEVENT_EXPOSED) + { + SDL_UpdateRect(pSdlGuiScrn, 0, 0, 0, 0); + } + break; +#endif + + default: + if (pEventOut) + retbutton = SDLGUI_UNKNOWNEVENT; + break; + } + } + + /* Restore background */ + if (pBgSurface) + { + SDL_BlitSurface(pBgSurface, &bgrect, pSdlGuiScrn, &dlgrect); + SDL_FreeSurface(pBgSurface); + } + + /* Copy event data of unsupported events if caller wants to have it */ + if (retbutton == SDLGUI_UNKNOWNEVENT && pEventOut) + memcpy(pEventOut, &sdlEvent, sizeof(SDL_Event)); + + if (retbutton == SDLGUI_QUIT) + bQuitProgram = true; + +#if !WITH_SDL2 + SDL_EnableUNICODE(nOldUnicodeMode); +#endif + if (joy) + SDL_JoystickClose(joy); + + Dprintf(("EXIT - ret: %d, current: %d\n", retbutton, current_object)); + return retbutton; +}