GrGUI Programmer's Guide
Author: Mariano Alvarez Fernández

# GrGUI 1.4.1

# a graphics gui on top of MGRX

## Programmer's Guide by examples

Written by Mariano Alvarez Fernández on October 1, 2019

Last update: July 28, 2022

## Abstract

GrGUI is a mini graphics user interface running on top of MGRX. It wants to be small, easy to use and not intrusive whit your program design, this is why it doesn't have a main loop to take control, instead you send individual events to specific functions when you want the GUI takes care of them. You don't need to use all the GUI functionality, you can (by example) use only the menues or the menu bar or some dialogs or some buttons or the GUI Contexts, it's up to you.

In this document, instead to present a detailed manual of each function, we will present twelve examples that covers the GrGUI basic concepts.

Because all MGRX functions begin with "Gr" and all GrGUI functions begin with "GUI" yo can easily know what code is GrGUI related or standard MGRX code.

You can find all the examples in the MGRX distribution under the "testgui" subdirectory.

## Contents

- Example 01. Initiating and ending GrGUI
- Example 02. GUI Contexts
- Example 03. Menues
- Example 04. Menu bar
- Example 05. GUI Panels
- Example 06. Common Dialogs
- Example 07. GUI Tiles
- Example 08. GUI Objects
- Example 09. GUI Tiles with Objects and TextPanel
- Example 10. GUI Dialog with Objects
- Example 11. Fonts, Colors and Double Buffer
- Example 12. Window resize support
- Example 12b. Internationalization
- More information

## Example 01. Initiating and ending GrGUI

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>

    int main()
    {
        char *abouttext[4] = {
            "Welcome to MGRX and GrGUI",
            "MGRX is a small C 2D graphics library",
            "and GrGUI a miniGUI on top of MGRX",
            "visit mgrx.fgrim.com for more info"};

        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);

        GUICDialogInfo("Hello GrGUI", (void **)abouttext, 4, "Ok");

        GUIEnd();
        GrSetMode(GR_default_text);

        return 0;
    }

Note we only include "grgui.h", because internally it includes "mgrx.h" and even "mgrxkeys.h".

"GUIInit" expect the graphics mode to be set before calling it. GUIInit takes two parameters that can be 1 (true) or 0 (false). The first one indicates if it must init the MGRX input subsystem. The second if it must use a double-buffer for the graphics output. Using a double-buffer we get a smoother output and it can be faster for most videodrivers, but it needs the user to indicate when bitblt to the screen if the graphic output is not done by GrGUI functions. We will see it in the eleven example.

## Example 02. GUI Contexts

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>

    int main()
    {
        GUIContext *gctx1, *gctx2, *gctx3;
        GrEvent ev;

        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);

        GrClearScreen(GrAllocColor(0, 100, 0));
        gctx1 = GUIContextCreate(100, 100, 200, 200, 1);
        gctx2 = GUIContextCreate(150, 150, 250, 250, 1);
        gctx3 = GUIContextCreate(200,  50, 400, 300, 1);
        if (gctx1 == NULL || gctx2 == NULL || gctx3 == NULL) exit(1);

        GrTextXY(10, 10, "Test GUIContexts, press any key to continue",
                 GrWhite(), GrNOCOLOR);

        GUIContextSaveUnder(gctx1);
        GrSetContext(gctx1->c);
        GrClearContext(GrAllocColor(100, 0, 0));
        GrEventWaitKeyOrClick(&ev);

        GUIContextSaveUnder(gctx2);
        GrSetContext(gctx2->c);
        GrClearContext(GrAllocColor(0, 0, 100));
        GrEventWaitKeyOrClick(&ev);

        GUIContextSaveUnder(gctx3);
        GrSetContext(gctx3->c);
        GrClearContext(GrAllocColor(200, 200, 0));
        GrEventWaitKeyOrClick(&ev);

        GUIContextRestoreUnder(gctx3);
        GUIContextDestroy(gctx3);
        GrEventWaitKeyOrClick(&ev);

        GUIContextRestoreUnder(gctx2);
        GUIContextDestroy(gctx2);
        GrEventWaitKeyOrClick(&ev);

        GUIContextRestoreUnder(gctx1);
        GUIContextDestroy(gctx1);
        GrEventWaitKeyOrClick(&ev);

        GUIEnd();
        GrSetMode(GR_default_text);

        return 0;
    }

GUI Contexts are the basic GrGUI container, they provides a screen (or memory if we asked for double-buffer) subcontext to draw in. They can save the actual graphic contents under the context and restore it later.

## Example 03. Menues

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>

    #define COMMAND_OPTION1     1
    #define COMMAND_OPTION2     2
    #define COMMAND_OPTION3     3
    #define COMMAND_OPTION4     4
    #define COMMAND_EXIT        5

    #define ID_MENU1            1
    #define ID_MENU2            2

    void print_line(char *s)
    {
        #define LINE_HIGH 16
        static int ypos = 10;

        if (ypos >= GrMaxY() - LINE_HIGH) {
            GrClearContext(GrBlack());
            ypos = 10;
        }
        GrTextXY(10, ypos, s, GrWhite(), GrBlack());
        ypos += LINE_HIGH;
    }

    int main()
    {
        static GUIMenuItem itemsm1[6] = {
            {GUI_MI_OPER, 1, "Option &1", '1', NULL, 0, COMMAND_OPTION1, 0, 0},
            {GUI_MI_OPER, 1, "Option &2", '2', NULL, 0, COMMAND_OPTION2, 0, 0},
            {GUI_MI_SEP, 1, "", 0, NULL, 0, 0, 0, 0},
            {GUI_MI_MENU, 1, "&Submenu", 'S', NULL, 0, ID_MENU2, 0, 0},
            {GUI_MI_SEP, 1, "", 0, NULL, 0, 0, 0, 0},
            {GUI_MI_OPER, 1, "E&xit", 'X', NULL, 0, COMMAND_EXIT, 0, 0}};
        static GUIMenu menu1 = {ID_MENU1, 6, 0, itemsm1};

        static GUIMenuItem itemsm2[2] = {
            {GUI_MI_OPER, 1, "Option &3", '3', NULL, 0, COMMAND_OPTION3, 0, 0},
            {GUI_MI_OPER, 1, "Option &4", '4', NULL, 0, COMMAND_OPTION4, 0, 0}};
        static GUIMenu menu2 = {ID_MENU2, 2, 0, itemsm2};

        GrEvent ev;
        int result;
        char s[81];

        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);

        GUIMenuRegister(&menu1);
        GUIMenuRegister(&menu2);

        print_line("Press R to run menu, Esc to quit");
        while(1) {
            GrEventWait(&ev);
            if (ev.type == GREV_KEY) {
                if (ev.p1 == GrKey_Escape) break;
                if (ev.p1 == 'r' || ev.p1 == 'R') {
                    result = GUIMenuRun(ID_MENU1, 100, 100, 0);
                    sprintf(s,"GUIMenuRun returned %d", result);
                    print_line(s);
                }
            }
            if (ev.type == GREV_COMMAND) {
                if (ev.p1 == COMMAND_EXIT) {
                    print_line("Received COMMAND_EXIT event, exiting in 3 seconds");
                    GrMouseEraseCursor();
                    GrSleep(3000);
                    break;
                }
                sprintf(s,"Received COMMAND event %ld", ev.p1);
                print_line(s);
            }
        }

        GUIEnd();
        GrSetMode(GR_default_text);

        return 0;
    }

We define menues statically as a list of menu items, they can be Operations, Separator or other Menues. When user selects an Operation a GREV_COMMAND event is generated. You can cancel a menu pressing the Escape key or clicking the mouse outside the menu area.

Menues must be registered with the "GUIMenuRegister" funtion, so that a menu can call another menu.

Obviusly a menu uses a GUI Context.

## Example 04. Menu bar

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>

    #define COMMAND_OPTION1     1
    #define COMMAND_OPTION2     2
    #define COMMAND_OPTION3     3
    #define COMMAND_OPTION4     4
    #define COMMAND_OPTION5     5
    #define COMMAND_OPTION6     6
    #define COMMAND_EXIT        7

    #define ID_MENU1            1
    #define ID_MENU2            2
    #define ID_MENU3            3

    void print_line(char *s)
    {
        #define LINE_HIGH 16
        static int ypos = 10;

        if (ypos >= GrMaxY() - LINE_HIGH) {
            GrClearContext(GrBlack());
            ypos = 10;
        }
        GrTextXY(10, ypos, s, GrWhite(), GrBlack());
        ypos += LINE_HIGH;
    }

    int main()
    {
        static GUIMenuItem itemsm1[6] = {
            {GUI_MI_OPER, 1, "Option &1", '1', NULL, 0, COMMAND_OPTION1, 0, 0},
            {GUI_MI_OPER, 1, "Option &2", '2', NULL, 0, COMMAND_OPTION2, 0, 0},
            {GUI_MI_SEP, 1, "", 0, NULL, 0, 0, 0, 0},
            {GUI_MI_MENU, 1, "&Submenu", 'S', NULL, 0, ID_MENU2, 0, 0},
            {GUI_MI_SEP, 1, "", 0, NULL, 0, 0, 0, 0},
            {GUI_MI_OPER, 1, "E&xit", 'X', "Ctrl+X", GrKey_Control_X, COMMAND_EXIT, 0, 0}};
        static GUIMenu menu1 = {ID_MENU1, 6, 0, itemsm1};

        static GUIMenuItem itemsm2[2] = {
            {GUI_MI_OPER, 1, "Option &3", '3', NULL, 0, COMMAND_OPTION3, 0, 0},
            {GUI_MI_OPER, 1, "Option &4", '4', NULL, 0, COMMAND_OPTION4, 0, 0}};
        static GUIMenu menu2 = {ID_MENU2, 2, 0, itemsm2};

        static GUIMenuItem itemsm3[4] = {
            {GUI_MI_OPER, 1, "Option &5", '5', NULL, 0, COMMAND_OPTION5, 0, 0},
            {GUI_MI_OPER, 1, "Option &6", '6', NULL, 0, COMMAND_OPTION6, 0, 0},
            {GUI_MI_SEP, 1, "", 0, NULL, 0, 0, 0, 0},
            {GUI_MI_MENU, 1, "&Submenu", 'S', NULL, 0, ID_MENU2, 0, 0}};
        static GUIMenu menu3 = {ID_MENU3, 4, 0, itemsm3};

        static GUIMenuBarItem mbitems[2] = {
            {"&First_menu", 1, GrKey_Alt_F, ID_MENU1, 0},
            {"&Second_menu", 1, GrKey_Alt_S, ID_MENU3, 0}};
        static GUIMenuBar menubar = {2 ,0, mbitems};

        GrEvent ev;
        char s[81];
        GrContext *ctx;

        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);

        GUIMenuRegister(&menu1);
        GUIMenuRegister(&menu2);
        GUIMenuRegister(&menu3);
        GUIMenuBarSet(&menubar);
        GUIMenuBarShow();
        ctx = GrCreateSubContext(0, GUIMenuBarGetHeight(),
                                 GrMaxX(), GrMaxY(), NULL, NULL);
        GrSetContext(ctx);

        print_line("Try the MenuBar above");
        while(1) {
            GrEventWait(&ev);
            if (ev.type == GREV_KEY) {
                if (ev.p1 == GrKey_Escape) break;
            }
            if (ev.type == GREV_COMMAND) {
                if (ev.p1 == COMMAND_EXIT) {
                    print_line("Received COMMAND_EXIT event, exiting in 3 seconds");
                    GrMouseEraseCursor();
                    GrSleep(3000);
                    break;
                }
                sprintf(s,"Received COMMAND event %ld", ev.p1);
                print_line(s);
            }
        }

        GUIEnd();
        GrSetMode(GR_default_text);

        return 0;
    }

The menu bar is statically defined as a list of menu bar items. Each item points to a menu id. You can have only a menu bar at any time that is registered using the "GUIMenuBarSet" function. After that you can show it with the "GUIMenuBarShow" function. When showed you can select a menu clicking with the mouse or using the ALT+letter combination defined. When tied to the menu bar, menu operations can define keyboard shortcuts to acces the operation directly, in our example "Ctrl-X" executes the Exit operation.

The funtion "GUIMenuBarGetHeight" is important because it reports the menu bar height in pixels, so we can set a screen subcontext as our drawing area without interfere with the menu bar.

## Example 05. GUI Panels

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>

    typedef struct {
        GUIPanel *gp;
        GrColor fg;
        GrColor bg;
    } UserData;

    void paint_panel1(void *data)
    {
        UserData *ud;

        ud = (UserData *)data;
        GrClearContext(ud->bg);
        GrTextXY(10, 10, "This is a simple panel with 1 px border", ud->fg, ud->bg);
        GrTextXY(10, 26, "Press R to reverse colors, C to continue", ud->fg, ud->bg);
    }

    void paint_panel2(void *data)
    {
        UserData *ud;

        ud = (UserData *)data;
        GrClearContext(ud->bg);
        GrTextXY(10, 10, "This is a panel with 4 px border,", ud->fg, ud->bg);
        GrTextXY(10, 26, "title and scroll bars", ud->fg, ud->bg);
        GrTextXY(10, 42, "Press R to reverse colors, C to continue", ud->fg, ud->bg);
    }

    void paint_panel2_title(void *data)
    {
        UserData *ud;

        ud = (UserData *)data;
        GrClearContext(ud->fg);
        GrTextXY(2, 2, "This is the panel 2 title", ud->bg, ud->fg);
        GrHLine(0, GrMaxX(), GrMaxY(), ud->bg);
    }

    int process_panel_event(void *data, GrEvent *ev)
    {
        UserData *ud;
        GrColor aux;

        ud = (UserData *)data;
        if (ev->type == GREV_KEY) {
            if (ev->p1 == 'r' || ev->p1 == 'R') {
                aux = ud->fg;
                ud->fg = ud->bg;
                ud->bg = aux;
                ud->gp->paintcl(data);
                return 1;
            }
            if (ev->p1 == 'c' || ev->p1 == 'C') {
                return -1;
            }
        }
        return 0;
    }

    int main()
    {
        GUIPanel *gp1, *gp2;
        GrEvent ev;
        UserData ud;
        int ret;

        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);

        GrClearScreen(GrAllocColor(0, 100, 0));
        gp1 = GUIPanelCreate(100, 100, GrMaxX()-200, GrSizeY()-200,
                             GUI_PCAPB_SU, 1, 0);
        gp2 = GUIPanelCreate(100, 100, GrMaxX()-200, GrSizeY()-200,
                             GUI_PCAPB_SU|GUI_PCAPB_VSCB|GUI_PCAPB_HSCB, 4, 20);
        if (gp1 == NULL || gp2 == NULL) exit(1);

        ud.gp = gp1;
        ud.fg = GrBlack();
        ud.bg = GrWhite();

        GUIPanelSetClCallBacks(gp1, paint_panel1, process_panel_event);
        GUIPanelSetUserData(gp1, (void *)&ud);

        GUIPanelPaint(gp1, GrBlack(), GrWhite());
        while(1) {
            GrEventWait(&ev);
            ret = GUIPanelProcessEvent(gp1, &ev);
            if (ret == -1) break;
        }
        GUIContextRestoreUnder(gp1->gc);
        GUIPanelDestroy(gp1);

        ud.gp = gp2;
        ud.fg = GrAllocColor2(0x555555);
        ud.bg = GrAllocColor2(0x55FFFF);

        GUIPanelSetClCallBacks(gp2, paint_panel2, process_panel_event);
        GUIPanelSetTlCallBack(gp2, paint_panel2_title);
        GUIPanelSetUserData(gp2, (void *)&ud);

        GUIPanelPaint(gp2, GrBlack(), GrWhite());
        while(1) {
            GrEventWait(&ev);
            ret = GUIPanelProcessEvent(gp2, &ev);
            if (ret == -1) break;
        }
        GUIContextRestoreUnder(gp2->gc);
        GUIPanelDestroy(gp2);

        GUIEnd();
        GrSetMode(GR_default_text);

        return 0;
    }

A GUI Panel is a more elaborated container on top of a GUI Context, it can have a border, a title area, a client area and Scrollbars. And more important you can attach a client area paint, a title area paint and a event processing functions to them.

## Example 06. Common Dialogs

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>

    void print_line(char *s)
    {
        #define LINE_HIGH 16
        static int ypos = 10;

        if (ypos >= GrMaxY() - LINE_HIGH) {
            GrClearContext(GrBlack());
            ypos = 10;
        }
        GrTextXY(10, ypos, s, GrWhite(), GrBlack());
        ypos += LINE_HIGH;
    }

    int main()
    {
        char *bodytext[2] = {
            "This is a GrGUI common dialog",
            "select one option"};
        GrEvent ev;
        char s[81];
        int result;

        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);

        print_line("Press 1 to run dialog Yes/No");
        print_line("Press 2 to run dialog Yes/No/Cancel");
        print_line("Esc to quit");

        while(1) {
            GrEventWait(&ev);
            if (ev.type == GREV_KEY) {
                if (ev.p1 == GrKey_Escape) break;
                if (ev.p1 == '1') {
                    result = GUICDialogYesNo("Test Yes/No",
                                (void **)bodytext, 2, "Yes", "No");
                    sprintf(s,"Dialog Yes/No returned %d", result);
                    print_line(s);
                }
                if (ev.p1 == '2') {
                    result = GUICDialogYesNoCancel("Test Yes/No/Cancel",
                                (void **)bodytext, 2, "Yes", "No", "Cancel");
                    sprintf(s,"Dialog Yes/No returned %d", result);
                    print_line(s);
                }
            }
        }

        GUIEnd();
        GrSetMode(GR_default_text);

        return 0;
    }

A GUI Dialog is a another container on top of a GUI Panel. GrGUI provides some common dialogs, one of them was used in the first example to show some information to the user. The other two, to ask a Yes/No question or a Yes/No/Cancel question to the user, are showed in this example.

In example 10 we will see how to construct our own dialog.

## Example 07. GUI Tiles

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>

    typedef struct {
        GUITile *gt;
        GrColor fg;
        GrColor bg;
    } UserData;

    void paint_tile1(void *data)
    {
        UserData *ud;

        ud = (UserData *)data;
        GrClearContext(ud->bg);
        GrTextXY(10, 16, "This is a passive tile with 1 px border", ud->fg, ud->bg);
    }

    void paint_tile2(void *data)
    {
        UserData *ud;

        ud = (UserData *)data;
        GrClearContext(ud->bg);
        GrTextXY(10, 10, "This is an active tile", ud->fg, ud->bg);
        GrTextXY(10, 26, "When selected:", ud->fg, ud->bg);
        GrTextXY(10, 42, "  Press R to reverse colors", ud->fg, ud->bg);
        GrTextXY(10, 58, "  Esc to finish", ud->fg, ud->bg);
    }

    void paint_tile3(void *data)
    {
        UserData *ud;

        ud = (UserData *)data;
        GrClearContext(ud->bg);
        GrTextXY(10, 10, "This is a second active tile,", ud->fg, ud->bg);
        GrTextXY(10, 26, "When selected:", ud->fg, ud->bg);
        GrTextXY(10, 42, "  Press R to reverse colors", ud->fg, ud->bg);
        GrTextXY(10, 58, "  Esc to finish", ud->fg, ud->bg);
    }

    void paint_tile4(void *data)
    {
        UserData *ud;

        ud = (UserData *)data;
        GrClearContext(ud->bg);
        GrTextXY(10, 16, "This is a passive tile borderless", ud->fg, ud->bg);
    }

    int process_tile_event(void *data, GrEvent *ev)
    {
        UserData *ud;
        GrColor aux;

        ud = (UserData *)data;
        if (ev->type == GREV_KEY) {
            if (ev->p1 == 'r' || ev->p1 == 'R') {
                aux = ud->fg;
                ud->fg = ud->bg;
                ud->bg = aux;
                ud->gt->p->paintcl(data);
                return 1;
            }
            if (ev->p1 == GrKey_Escape) {
                return -1;
            }
        }
        return 0;
    }

    int main()
    {
        #define IDT1 1
        #define IDT2 2
        #define IDT3 3
        #define IDT4 4

        GUITile *gt1, *gt2, *gt3, *gt4;
        UserData ud1, ud2, ud3, ud4;
        GrEvent ev;
        int ret;

        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);

        //GrClearScreen(GrAllocColor(0, 100, 0));
        gt1 = GUITileCreate(IDT1, GUI_TT_STATICBORDER, 0, 0,
                            GrSizeX(), 50);
        gt2 = GUITileCreate(IDT2, GUI_TT_ACTIVEBORDER, 0, 50,
                            GrSizeX()/2, GrSizeY()-100);
        gt3 = GUITileCreate(IDT3, GUI_TT_ACTIVEBORDER, GrSizeX()/2, 50,
                            GrSizeX()/2, GrSizeY()-100);
        gt4 = GUITileCreate(IDT4, GUI_TT_BORDERLESS, 0,
                            GrSizeY()-50, GrSizeX(), 50);
        if (gt1 == NULL || gt2 == NULL || gt3 == NULL || gt4 == NULL) exit(1);

        GUIPanelSetClCallBacks(gt1->p, paint_tile1, NULL);
        GUIPanelSetUserData(gt1->p, (void *)&ud1);
        ud1.gt = gt1;
        ud1.fg = GrWhite();
        ud1.bg = GrAllocColor2(0x00AAAA);
        GUIPanelSetClCallBacks(gt2->p, paint_tile2, process_tile_event);
        GUIPanelSetUserData(gt2->p, (void *)&ud2);
        ud2.gt = gt2;
        ud2.fg = GrWhite();
        ud2.bg = GrAllocColor2(0x00AA00);
        GUIPanelSetClCallBacks(gt3->p, paint_tile3, process_tile_event);
        GUIPanelSetUserData(gt3->p, (void *)&ud3);
        ud3.gt = gt3;
        ud3.fg = GrWhite();
        ud3.bg = GrAllocColor2(0x00AA00);
        GUIPanelSetClCallBacks(gt4->p, paint_tile4, NULL);
        GUIPanelSetUserData(gt4->p, (void *)&ud4);
        ud4.gt = gt4;
        ud4.fg = GrWhite();
        ud4.bg = GrAllocColor2(0x555555);

        GUITileRegister(gt1);
        GUITileRegister(gt2);
        GUITileRegister(gt3);
        GUITileRegister(gt4);

        GUITilePaint(IDT1);
        GUITilePaint(IDT2);
        GUITilePaint(IDT3);
        GUITilePaint(IDT4);

        while(1) {
            GrEventWait(&ev);
            ret = GUITilesProcessEvent(&ev);
            if (ret == -1) break;
        }

        GUITilesDestroyAll();

        GUIEnd();
        GrSetMode(GR_default_text);

        return 0;
    }

GUI Tiles are containers too, on top of a GUI Panel. But the basic idea of GUI Tiles is to divide the screen area in rectangular areas. Each tile can be passive (only to show information) or active (it can get user input).

Only one of the active tiles have the focus, and you can change it using the mouse or the F8 key. To achieve this functionality, GUI Tiles must be registered using the "GUITileRegister" function.

## Example 08. GUI Objects

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <grgui.h>
    #include <mgrxcolr.h>

    #define COMMAND_EXIT         1
    #define COMMAND_SWITCH_L1    2
    #define COMMAND_SWITCH_L2    3
    #define COMMAND_SWITCH_L3    4
    #define COMMAND_SWITCH_L4    5
    #define COMMAND_GET_DATA     6

    char *listopt[5] = { "Primera opción", "Segunda opción",
        "Tercera opción", "Cuarta opción", "Quinta opción"};
    char bdtline1[81], bdtline2[81], bdtline3[81], bdtline4[81];
    char *buf_test[4] = {bdtline1, bdtline2, bdtline3, bdtline4};

    void add_line_to_buf_test(char *s)
    {
        int i;

        for (i=0; i<3; i++)
            memcpy(buf_test[i], buf_test[i+1], 81);

        strncpy(buf_test[3], s, 80);
        buf_test[3][80] = '\0';
    }

    int process_go_event(GUIGroup *go, GrEvent *ev)
    {
        char aux[81];
        char *s, *sonoff[4];
        char *son = "On";
        char *soff = "Off";
        int i, status;

        if (ev->type == GREV_COMMAND) {
            switch (ev->p1) {
                case COMMAND_EXIT :
                    return -1;
                case COMMAND_SWITCH_L1 :
                    GUIGroupSetOn(go, 0, 1);
                    return 1;
                case COMMAND_SWITCH_L2 :
                    GUIGroupSetOn(go, 1, 1);
                    return 1;
                case COMMAND_SWITCH_L3 :
                    GUIGroupSetOn(go, 2, 1);
                    return 1;
                case COMMAND_SWITCH_L4 :
                    GUIGroupSetOn(go, 3, 1);
                    return 1;
                case COMMAND_GET_DATA :
                    for (i=0; i<4; i++) {
                        status = GUIGroupGetOn(go, i);
                        sonoff[i] = status ? son : soff;
                    }
                    sprintf(aux, "Lights status 1:%s 2:%s 3:%s 4:%s",
                            sonoff[0], sonoff[1], sonoff[2], sonoff[3]);
                    add_line_to_buf_test(aux);
                    s = GUIGroupGetText(go, 5, GR_UTF8_TEXT);
                    sprintf(aux, "Entry: %s", s);
                    add_line_to_buf_test(aux);
                    free(s);
                    GUIGroupRePaintObject(go, 15);
                    return 1;
            }
        }
        if (ev->type == GREV_FCHANGE) {
            sprintf(aux, "Field changed, p1=%ld, p2=%ld", ev->p1, ev->p2);
            add_line_to_buf_test(aux);
            GUIGroupRePaintObject(go, 15);
        }

        return GUIGroupProcessEvent(go, ev);
    }

    int main()
    {
        GUIGroup *go;
        GrEvent ev;

        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);
        GrGenEgaColorTable();
        GrClearContext(EGAC_DARKGRAY);

        GUIObjectsSetColors(EGAC_BLACK, EGAC_LIGHTGRAY, EGAC_DARKGRAY);
        go = GUIGroupCreate(17, 160, 70);
        if (go == NULL) exit(1);
        GUIObjectSetLight(&(go->o[0]), 0,   0, 0, 80, 32, EGAC_LIGHTGREEN, EGAC_BLACK, "Light 1", 0);
        GUIObjectSetLight(&(go->o[1]), 1,  80, 0, 80, 32, EGAC_LIGHTGREEN, EGAC_BLACK, "Light 2", 1);
        GUIObjectSetLight(&(go->o[2]), 2, 160, 0, 80, 32, EGAC_LIGHTRED, EGAC_BLACK, "Light 3", 0);
        GUIObjectSetLight(&(go->o[3]), 3, 240, 0, 80, 32, EGAC_LIGHTCYAN, EGAC_BLACK, "Light 4", 1);
        GUIObjectSetLabel(&(go->o[4]), 4,   0, 40, 160, 30, GrNOCOLOR, EGAC_WHITE, "Editable field");
        GUIObjectSetEntry(&(go->o[5]), 5, 160, 40, 160, 30, EGAC_WHITE, EGAC_BLACK, 30, "entry field");
        GUIObjectSetLabel(&(go->o[6]), 6,   0, 80, 160, 30, GrNOCOLOR, EGAC_WHITE, "List field #1");
        GUIObjectSetList(&(go->o[7]),  7, 160, 80, 160, 30, EGAC_WHITE, EGAC_BLACK, (void **)listopt, 5, 3, 1);
        GUIObjectSetLabel(&(go->o[8]), 8,   0, 120, 160, 30, GrNOCOLOR, EGAC_WHITE, "List field #2");
        GUIObjectSetList(&(go->o[9]),  9, 160, 120, 160, 30, EGAC_WHITE, EGAC_BLACK, (void **)listopt, 5, 5, 2);
        GUIObjectSetButton(&(go->o[10]), 10,   0, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L1", COMMAND_SWITCH_L1, 0, 0);
        GUIObjectSetButton(&(go->o[11]), 11,  40, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L2", COMMAND_SWITCH_L2, 0, 0);
        GUIObjectSetButton(&(go->o[12]), 12,  80, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L3", COMMAND_SWITCH_L3, 0, 0);
        GUIObjectSetButton(&(go->o[13]), 13, 120, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L4", COMMAND_SWITCH_L4, 0, 0);
        GUIObjectSetButton(&(go->o[14]), 14, 160, 160, 160, 40, EGAC_CYAN, EGAC_WHITE, "Get data", COMMAND_GET_DATA, 0, 0);
        GUIObjectSetText(&(go->o[15]), 15, 0, 210, 320, 80, EGAC_LIGHTGRAY, EGAC_BLACK, (void **)buf_test, 4, GR_ALIGN_LEFT, NULL);
        GUIObjectSetButton(&(go->o[16]), 16, 80, 300, 160, 40, EGAC_GREEN, EGAC_WHITE, "Exit", COMMAND_EXIT, 0, 0);
        GUIGroupSetSelected(go, 16, 0);

        GUIGroupPaint(go);

        while(1) {
            GrEventRead(&ev);
            if ((ev.type == GREV_KEY) && (ev.p1 == GrKey_Escape)) break;
            if (process_go_event(go, &ev) < 0) break;
        }

        GUIGroupDestroy(go);
        GUIEnd();
        GrSetMode(GR_default_text);

        return 0;
    }

GUI Objects are widgets, small rectangular objects with some function. There are nine objects types in GrGUI:

- GUIOBJTYPE_LABEL are small pieces of static text.
- GUIOBJTYPE_TEXT are multiline pieces of static text.
- GUIOBJTYPE_LIGHT are rectangular areas that can be on or off.
- GUIOBJTYPE_BUTTON are things you can press and they send a GREV_COMMAND event.
- GUIOBJTYPE_ENTRY are editable text fields.
- GUIOBJTYPE_LIST are drop down list of options from which to choose one.
- GUIOBJTYPE_DLIST are deployed list of options from which to choose one.
- GUIOBJTYPE_REGLIST are deployed list of registers.
- GUIOBJTYPE_EDIT is an multiline editable area.

GUI Objects only can live in a GUI Group. You first create a group and them defines each element to be a GUI Object of any type. All the objects in a group are managed as a whole. In this example we create a group with objects of the first six types. You can see that it is not necesary to attach a group to a container. We will do it in the next two examples.

## Example 09. GUI Tiles with Objects and TextPanel

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <grgui.h>
    #include <mgrxcolr.h>

    #define COMMAND_EXIT         1
    #define COMMAND_LOAD         2
    #define COMMAND_SAVE         3

    typedef struct {
        GUITextPanel *ta;
        GUIGroup *go;
    } UserData;

    void paint_tl1(void *data)
    {
        UserData *ud;

        ud = (UserData *)data;
        GrClearContext(EGAC_LIGHTGRAY);
        GUIGroupPaint(ud->go);
    }

    void paint_tl2(void *data)
    {
        UserData *ud;

        ud = (UserData *)data;
        GUITPReDraw(ud->ta);
    }

    int process_tl1_event(void *data, GrEvent *ev)
    {
        UserData *ud;
        GUITEditStatus tast;
        FILE *fin, *fout;
        char aux[251];
        char *s;
        int len, i;

        ud = (UserData *)data;
        if (ev->type == GREV_COMMAND) {
            switch (ev->p1) {
                case COMMAND_EXIT :
                    return -1;
                case COMMAND_LOAD :
                    s = GUIGroupGetText(ud->go, 1, GR_UTF8_TEXT);
                    fin = fopen(s, "r");
                    free(s);
                    if (fin == NULL) return 1;
                    GUITPHideTCursor(ud->ta);
                    while (fgets(aux, 250, fin) != NULL) {
                        len = strlen(aux);
                        if (len>0 && aux[len-1]=='\n') aux[len-1] = '\0';
                        GUITPPutString(ud->ta, aux, 0, GR_UTF8_TEXT);
                        GUITPNewLine(ud->ta);
                        GrEventFlush(); // needed for X11 flush
                    }
                    GUITPShowTCursor(ud->ta);
                    fclose(fin);
                    return 1;
                case COMMAND_SAVE :
                    s = GUIGroupGetText(ud->go, 3, GR_UTF8_TEXT);
                    fout = fopen(s, "w");
                    free(s);
                    if (fout == NULL) return 1;
                    GUITPGetStatus(ud->ta, &tast);
                    for (i=0; i<tast.nlines; i++) {
                        s = GUITPGetString(ud->ta, i, GR_UTF8_TEXT);
                        if (s == NULL) break;
                        fputs(s, fout);
                        putc( '\n', fout);
                        free(s);
                    }
                    fclose(fout);
                    return 1;
            }
        }

        return GUIGroupProcessEvent(ud->go, ev);
    }

    int process_tl2_event(void *data, GrEvent *ev)
    {
        UserData *ud;

        ud = (UserData *)data;
        return GUITPProcessEvent(ud->ta, ev);
    }

    int main()
    {
        #define IDT1 1
        #define IDT2 2

        GUITile *gt1, *gt2;
        GUITextPanel *tp1;
        GUIGroup *go1;
        UserData ud;
        GrEvent ev;
        int ret;

        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);
        GrGenEgaColorTable();
        GUIScrollbarsSetColors(EGAC_LIGHTGRAY, EGAC_DARKGRAY);
        GUITilesSetColors(EGAC_BLACK, EGAC_LIGHTGRAY, EGAC_YELLOW);
        GUIObjectsSetColors(EGAC_BLACK, EGAC_LIGHTGRAY, EGAC_DARKGRAY);

        gt1 = GUITileCreate(IDT1, GUI_TT_ACTIVEBORDER, 0, 0,
                            168, GrSizeY());
        gt2 = GUITileCreate(IDT2, GUI_TT_ACTIVEBWSCB, 168, 0,
                            GrSizeX()-168, GrSizeY());
        if (gt1 == NULL || gt2 == NULL) exit(1);

        GUITileRegister(gt1);
        GUITileRegister(gt2);

        go1 = GUIGroupCreate(5, 10, 32);
        GUIObjectSetButton(&(go1->o[0]), 0, 0,   0, 140, 40, EGAC_GREEN, EGAC_WHITE, "Load File", COMMAND_LOAD, 0, 0);
        GUIObjectSetEntry(&(go1->o[1]),  1, 0,  44, 140, 30, EGAC_WHITE, EGAC_BLACK, 30, "inputfile");
        GUIObjectSetButton(&(go1->o[2]), 2, 0,  84, 140, 40, EGAC_GREEN, EGAC_WHITE, "Save File", COMMAND_SAVE, 0, 0);
        GUIObjectSetEntry(&(go1->o[3]),  3, 0, 128, 140, 30, EGAC_WHITE, EGAC_BLACK, 30, "outputfile");
        GUIObjectSetButton(&(go1->o[4]), 4, 0, 168, 140, 40, EGAC_RED, EGAC_WHITE, "Exit", COMMAND_EXIT, 0, 0);
        GUIGroupSetSelected(go1, 0, 0);
        GUIGroupSetPanel(go1, gt1->p);

        tp1 = GUITPCreate(gt2->p, NULL);
        if (tp1 == NULL) exit(1);
        //GUITPSetSimpleColors(tp1, EGAC_WHITE, EGAC_BLACK, EGAC_LIGHTRED);
        GUITPSetSimpleColors(tp1, EGAC_DARKGRAY, EGAC_WHITE, EGAC_YELLOW);
        GUITPClear(tp1);

        GUIPanelSetClCallBacks(gt1->p, paint_tl1, process_tl1_event);
        GUIPanelSetClCallBacks(gt2->p, paint_tl2, process_tl2_event);
        GUIPanelSetUserData(gt1->p, (void *)&ud);
        GUIPanelSetUserData(gt2->p, (void *)&ud);
        ud.ta = tp1;
        ud.go = go1;

        GUITilePaint(IDT1);
        GUITilePaint(IDT2);
        GUITPShowTCursor(tp1);

        while(1) {
            GrEventRead(&ev);
            ret = GUITilesProcessEvent(&ev);
            if (ret == -1) break;
        }

        GUIGroupDestroy(go1);
        GUITPDestroy(tp1);
        GUITilesDestroyAll();

        GUIEnd();
        GrSetMode(GR_default_text);

        return 0;
    }

In this example we divide the screen with two active GUI Tiles, we attach a GUI Group of objects to one Tile and a GUI TextPanel to the other one.

A GUI TextPanel is a window over a editable text. So in our present example we have a simple text editor.

In a GUI TextPanel (and in objects of types GUIOBJTYPE_ENTRY and GUIOBJTYPE_EDIT) you can copy, cut and paste text using the standard keys Ctrl+C, Ctrl+X and Ctrl+V.

## Example 10. GUI Dialog with Objects

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <grgui.h>
    #include <mgrxcolr.h>

    #define COMMAND_OK           1
    #define COMMAND_SWITCH_L1    2
    #define COMMAND_SWITCH_L2    3
    #define COMMAND_SWITCH_L3    4
    #define COMMAND_SWITCH_L4    5
    #define COMMAND_GET_DATA     6

    char *listopt[5] = { "Primera opción", "Segunda opción",
        "Tercera opción", "Cuarta opción", "Quinta opción"};
    char bdtline1[81], bdtline2[81], bdtline3[81], bdtline4[81];
    char *buf_test[4] = {bdtline1, bdtline2, bdtline3, bdtline4};

    void print_line(char *s)
    {
        #define LINE_HIGH 16
        static int ypos = 10;

        if (ypos >= GrMaxY() - LINE_HIGH) {
            GrClearContext(GrBlack());
            ypos = 10;
        }
        GrTextXY(10, ypos, s, GrWhite(), GrBlack());
        ypos += LINE_HIGH;
    }

    void add_line_to_buf_test(char *s)
    {
        int i;

        for (i=0; i<3; i++)
            memcpy(buf_test[i], buf_test[i+1], 81);

        strncpy(buf_test[3], s, 80);
        buf_test[3][80] = '\0';
    }

    int process_dlg_event(void *udata, GrEvent *ev)
    {
        GUIDialog *d = (GUIDialog *)udata;
        GUIGroup *go = (GUIGroup *)(d->exdata);
        char aux[81];
        char *s, *sonoff[4];
        char *son = "On";
        char *soff = "Off";
        int i, status;

        if (ev->type == GREV_COMMAND) {
            switch (ev->p1) {
                case COMMAND_OK :
                    return -1;
                case COMMAND_SWITCH_L1 :
                    GUIGroupSetOn(go, 0, 1);
                    return 1;
                case COMMAND_SWITCH_L2 :
                    GUIGroupSetOn(go, 1, 1);
                    return 1;
                case COMMAND_SWITCH_L3 :
                    GUIGroupSetOn(go, 2, 1);
                    return 1;
                case COMMAND_SWITCH_L4 :
                    GUIGroupSetOn(go, 3, 1);
                    return 1;
                case COMMAND_GET_DATA :
                    for (i=0; i<4; i++) {
                        status = GUIGroupGetOn(go, i);
                        sonoff[i] = status ? son : soff;
                    }
                    sprintf(aux, "Lights status 1:%s 2:%s 3:%s 4:%s",
                            sonoff[0], sonoff[1], sonoff[2], sonoff[3]);
                    add_line_to_buf_test(aux);
                    s = GUIGroupGetText(go, 5, GR_UTF8_TEXT);
                    sprintf(aux, "Entry: %s", s);
                    add_line_to_buf_test(aux);
                    free(s);
                    GUIGroupRePaintObject(go, 15);
                    return 1;
            }
        }
        if (ev->type == GREV_FCHANGE) {
            sprintf(aux, "Field changed, p1=%ld, p2=%ld", ev->p1, ev->p2);
            add_line_to_buf_test(aux);
            GUIGroupRePaintObject(go, 15);
        }

        return GUIGroupProcessEvent(go, ev);
    }

    int main()
    {
        GUIGroup *go;
        GUIDialog *d;
        GrEvent ev;
        char s[81];
        int result, i;

        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);
        GrGenEgaColorTable();

        GUIObjectsSetColors(EGAC_BLACK, EGAC_LIGHTGRAY, EGAC_DARKGRAY);
        GUIDialogsSetColors(EGAC_BLACK, EGAC_YELLOW, EGAC_BLUE, EGAC_WHITE);
        go = GUIGroupCreate(17, 160, 70);
        if (go == NULL) exit(1);
        GUIObjectSetLight(&(go->o[0]), 0,   0, 0, 80, 32, EGAC_LIGHTGREEN, EGAC_BLACK, "Light 1", 0);
        GUIObjectSetLight(&(go->o[1]), 1,  80, 0, 80, 32, EGAC_LIGHTGREEN, EGAC_BLACK, "Light 2", 1);
        GUIObjectSetLight(&(go->o[2]), 2, 160, 0, 80, 32, EGAC_LIGHTRED, EGAC_BLACK, "Light 3", 0);
        GUIObjectSetLight(&(go->o[3]), 3, 240, 0, 80, 32, EGAC_LIGHTCYAN, EGAC_BLACK, "Light 4", 1);
        GUIObjectSetLabel(&(go->o[4]), 4,   0, 40, 160, 30, GrNOCOLOR, EGAC_WHITE, "Editable field");
        GUIObjectSetEntry(&(go->o[5]), 5, 160, 40, 160, 30, EGAC_WHITE, EGAC_BLACK, 30, "entry field");
        GUIObjectSetLabel(&(go->o[6]), 6,   0, 80, 160, 30, GrNOCOLOR, EGAC_WHITE, "List field #1");
        GUIObjectSetList(&(go->o[7]),  7, 160, 80, 160, 30, EGAC_WHITE, EGAC_BLACK, (void **)listopt, 5, 3, 1);
        GUIObjectSetLabel(&(go->o[8]), 8,   0, 120, 160, 30, GrNOCOLOR, EGAC_WHITE, "List field #2");
        GUIObjectSetList(&(go->o[9]),  9, 160, 120, 160, 30, EGAC_WHITE, EGAC_BLACK, (void **)listopt, 5, 5, 2);
        GUIObjectSetButton(&(go->o[10]), 10,   0, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L1", COMMAND_SWITCH_L1, 0, 0);
        GUIObjectSetButton(&(go->o[11]), 11,  40, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L2", COMMAND_SWITCH_L2, 0, 0);
        GUIObjectSetButton(&(go->o[12]), 12,  80, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L3", COMMAND_SWITCH_L3, 0, 0);
        GUIObjectSetButton(&(go->o[13]), 13, 120, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L4", COMMAND_SWITCH_L4, 0, 0);
        GUIObjectSetButton(&(go->o[14]), 14, 160, 160, 160, 40, EGAC_CYAN, EGAC_WHITE, "Get data", COMMAND_GET_DATA, 0, 0);
        GUIObjectSetText(&(go->o[15]), 15, 0, 210, 320, 80, EGAC_LIGHTGRAY, EGAC_BLACK, (void **)buf_test, 4, GR_ALIGN_LEFT, NULL);
        GUIObjectSetButton(&(go->o[16]), 16, 80, 300, 160, 40, EGAC_GREEN, EGAC_WHITE, "Exit", COMMAND_OK, 0, 0);
        GUIGroupSetSelected(go, 16, 0);

        d = GUIGroupDialogCreate("Test various objects", go, process_dlg_event);
        if (d == NULL) exit(1);

        print_line("Press D to run dialog");
        print_line("Esc to quit");

        while(1) {
            GrEventRead(&ev);
            if (ev.type == GREV_KEY) {
                if (ev.p1 == GrKey_Escape) break;
                if (ev.p1 == 'd' || ev.p1 == 'D') {
                    result = GUIDialogRun(d);
                    sprintf(s,"Dialog returned %d, Text object contens:", result);
                    print_line(s);
                    for (i=0; i<4; i++)
                        print_line(buf_test[i]);
                }
            }
        }

        GUIDialogDestroy(d);
        GUIGroupDestroy(go);
        GUIEnd();
        GrSetMode(GR_default_text);

        return 0;
    }

Now we reuse the GUi Group we create in the example 8 and attach it to a dialog to show we can have complex dialogs with GrGUI.

## Example 11. Fonts, Colors and Double Buffer

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>
    #include "mgrxcolr.h"

    #if defined(__MSDOS__) || defined(__WIN32__)
    #define JPGIMGBG  "..\\testimg\\jpeg4.jpg"
    #else
    #define JPGIMGBG  "../testimg/jpeg4.jpg"
    #endif

    int main()
    {
        char *abouttext[4] = {
            "Welcome to MGRX and GrGUI",
            "MGRX is a small C 2D graphics library",
            "and GrGUI a miniGUI on top of MGRX",
            "visit mgrx.fgrim.com for more info"};
        GrContext *globctx, *imgctx;
        int imgloaded = 0, imgwidth, imgheight;

        GrSetMode(GR_width_height_bpp_graphics, 640, 480, 32);
        GrGenWebColorTable();
        GrSetFontPath("../fonts/;./");
        GUIInit(1, 1);

        GUIObjectsSetColors(WEBC_KHAKI, WEBC_PERU, WEBC_SIENNA);
        GUIObjectsSetFontByName("tmgrx16b.fnt");
        GUIDialogsSetColors(WEBC_BLACK, WEBC_ORANGE, WEBC_MAROON,
                            WEBC_ANTIQUEWHITE);
        GUICDialogsSetColors(WEBC_TAN, WEBC_BLACK);
        GUIDialogsSetTitleFontByName("ncen40bi.fnt");
        GUICDialogsSetFontByName("tmgrx18b.fnt");

        globctx = GUIGetGlobalContext();
        GrSetContext(globctx);

        if (GrNumColors() > 256 && GrJpegSupport()) {
            if (GrQueryJpeg(JPGIMGBG, &imgwidth, &imgheight) == 0) {
                imgctx = GrCreateContext(imgwidth, imgheight, NULL, NULL);
                if (imgctx != NULL) {
                    if (GrLoadContextFromJpeg(imgctx, JPGIMGBG, 1) == 0) {
                        GrStretchBlt(NULL, 0, 0, GrMaxX(), GrMaxY(),
                                    imgctx, 0, 0, imgwidth-1, imgheight-1, GrWRITE);
                        imgloaded = 1;
                    }
                GrDestroyContext(imgctx);
                }
            }
        }
        if (!imgloaded) {
            GrFilledBox(0, 0, GrMaxX(), GrMaxY(), WEBC_GOLDENROD);
        }
        GUIDBCurCtxBltToScreen();

        GUICDialogInfo("Hello GrGUI", (void **)abouttext, 4, "Okey");

        GUIEnd();
        GrSetMode(GR_default_text);

        return 0;
    }

Until now sometimes we have left the default black and whiite colors, sometime we have used GrGUI functions to set the default colors of some elements. In all cases we have left the default font.

In this example we return to our first hello GrGUI example but changing default colors and fonts. We draw a background image too and activate the double buffer (second parameter of "GUIInit" function).

While we use the GUI functions to do the drawing there are no difference, but in other case we have to use special code. You can notice that to draw the image background instead doing it in the Screen context we use the "GUIGetGlobalContext" to get the memory context GrGUI is using to draw, and after putting our image in, we bitblt to the real Screen using "GUIDBCurCtxBltToScreen".

So it is different to code with or without double buffer. The good news is that "GUIGetGlobalContext" returns the Screen context if GrGUI isn't using double buffer and "GUIDBCurCtxBltToScreen" doesn't do anything in that case. So we can code as the double buffer is enabled and it works ok without double buffer.

## Example 12. Window resize support

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>
    #include <mgrxcolr.h>
    #include "gr12i18n.h"

    #if defined(__MSDOS__) || defined(__WIN32__)
    #define PICDIR  "..\\testimg\\"
    #else
    #define PICDIR  "../testimg/"
    #endif
    #define PICTURE1 PICDIR"jpeg1.jpg"
    #define PICTURE2 PICDIR"jpeg2.jpg"
    #define PICTURE3 PICDIR"jpeg4.jpg"

    /* commands generate by menus */

    #define COMMAND_PICTURE1            1
    #define COMMAND_PICTURE2            2
    #define COMMAND_PICTURE3            3
    #define COMMAND_EXIT               10
    #define COMMAND_DLG_GRGUI          20
    #define COMMAND_DLG_ABOUT          21
    #define COMMAND_LAN_ENGLISH        30
    #define COMMAND_LAN_SPANISH        31
    #define COMMAND_LAN_GREEK          32

    /* tiles ids */

    #define IDT_CANVAS                  1

    /* global variables */

    static GUITile *tile1;
    static GrContext *imgctx = NULL;
    static int imgwidth, imgheight;
    static int nimgloaded = 0;
    static char *imgname[3] = {PICTURE1, PICTURE2, PICTURE3};
    static long restart_command = 0;
    static GrFont *objfont = NULL;
    static GrFont *dlgtfont = NULL;
    static GrFont *cdlgfont = NULL;
    static GrFont *menufont = NULL;
    static int language = 0;

    /* function declarations */
    void setup_menus(void);
    void setup_etc(void);
    void change_language(int lid);
    void load_image(int n);
    void load_image_file(char *s);
    void destroy_image(void);
    void paint_panel1(void *udata);
    int dialog_grgui(void);
    int dialog_about(void);

    int main()
    {
        int w = 640;
        int h = 480;
        int bpp = 32;

        // set default driver and ask for user window resize if it is supported
        GrSetDriverExt(NULL, "rszwin");
        // we can call here because the driver is set
        GrSetUserEncoding(GRENC_UTF_8);
        GrEventGenWMEnd(GR_GEN_WMEND_YES);
        GrSetFontPath("../fonts/;./");
        objfont = GrLoadFont("tmgrx16b.fnt");
        dlgtfont = GrLoadFont("tmgrx32b.fnt");
        cdlgfont = GrLoadFont("tmgrx18b.fnt");
        menufont = GrLoadFont("tmgrx18n.fnt");
        setup_i18n();
        GrI18nSetLang(language);

        while (1) {
            int exitloop = 0;
            GrEvent ev;
            int result;
            int nimg;

            GrSetMode(GR_width_height_bpp_graphics, w, h, bpp);
            GrGenWebColorTable();
            GUIInit(1, 1);
            setup_menus();
            setup_etc();
            GUIMenuBarShow();

            // This is an example of minimal status recovery after a
            // GREV_WSZCHG event, and works okey at the first time
            // 1- repaint the last image, note that load_image only reload
            // the image context if required
            load_image(nimgloaded);
            GUITilePaint(IDT_CANVAS);
            // 2- if we were waiting for a dialog when a GREV_WSZCHG come,
            // relaunch it again
            if (restart_command) {
                GrEventParEnqueue(GREV_COMMAND, restart_command, 0, 0, 0);
                restart_command = 0;
            }

            while (1) {
                GrEventRead(&ev);
                if (((ev.type == GREV_KEY) && (ev.p1 == GrKey_Escape)) ||
                    ((ev.type == GREV_COMMAND) && (ev.p1 == COMMAND_EXIT)) ||
                     (ev.type == GREV_WMEND)) {
                    exitloop = 1;
                    break;
                }
                if (ev.type == GREV_COMMAND) {
                    if (ev.p1 >= COMMAND_PICTURE1 && ev.p1 <= COMMAND_PICTURE3) {
                        nimg = ev.p1 - COMMAND_PICTURE1;
                        load_image(nimg);
                        GUITilePaint(IDT_CANVAS);
                        continue;
                    }
                    if (ev.p1 == COMMAND_DLG_GRGUI) {
                        result = dialog_grgui();
                        if (result == -3) restart_command = COMMAND_DLG_GRGUI;
                        continue;
                    }
                    if (ev.p1 == COMMAND_DLG_ABOUT) {
                        result = dialog_about();
                        if (result == -3) restart_command = COMMAND_DLG_ABOUT;
                        continue;
                    }
                    if (ev.p1 >= COMMAND_LAN_ENGLISH && ev.p1 <= COMMAND_LAN_GREEK) {
                        change_language(ev.p1 - COMMAND_LAN_ENGLISH);
                        continue;
                    }
                }
                if (ev.type == GREV_WSZCHG) {
                    w = ev.p3;
                    h = ev.p4;
                    break;
                }
            }

            GUIEnd();
            if (exitloop) break;
        }
        GrI18nEnd();
        destroy_image();
        GrUnloadFont(menufont);
        GrUnloadFont(objfont);
        GrUnloadFont(dlgtfont);
        GrUnloadFont(cdlgfont);
        GrSetMode(GR_default_text);

        return 0;
    }

    void setup_menus(void)
    {
        static GUIMenuItem itemsm1[5] = {
            {GUI_MI_OPER, 1, "", 0, NULL, 0, COMMAND_PICTURE1, 0, SID_MENU1_1},
            {GUI_MI_OPER, 1, "", 0, NULL, 0, COMMAND_PICTURE2, 0, SID_MENU1_2},
            {GUI_MI_OPER, 1, "", 0, NULL, 0, COMMAND_PICTURE3, 0, SID_MENU1_3},
            {GUI_MI_SEP, 1, "", 0, NULL, 0, 0, 0, 0},
            {GUI_MI_OPER, 1, "", 0, "Alt+X", GrKey_Alt_X, COMMAND_EXIT, 0, SID_MENU1_4}};

        static GUIMenu menu1 = {1, 5, 0, itemsm1};

        static GUIMenuItem itemsm2[2] = {
            {GUI_MI_OPER, 1, "", 0, NULL, 0, COMMAND_DLG_GRGUI, 0, SID_MENU2_1},
            {GUI_MI_OPER, 1, "", 0, NULL, 0, COMMAND_DLG_ABOUT, 0, SID_MENU2_2}};

        static GUIMenu menu2 = {2, 2, 0, itemsm2};

        static GUIMenuItem itemsm3[3] = {
            {GUI_MI_OPER, 1, "", 0, NULL, 0, COMMAND_LAN_ENGLISH, 0, SID_MENU3_1},
            {GUI_MI_OPER, 1, "", 0, NULL, 0, COMMAND_LAN_SPANISH, 0, SID_MENU3_2},
            {GUI_MI_OPER, 1, "", 0, NULL, 0, COMMAND_LAN_GREEK, 0, SID_MENU3_3}};

        static GUIMenu menu3 = {3, 3, 0, itemsm3};

        static GUIMenuBarItem mbitems[3] = {
            {"", 1, GrKey_Alt_F, 1, SID_MBAR1},
            {"", 1, GrKey_Alt_L, 3, SID_MBAR2},
            {"", 1, GrKey_Alt_H, 2, SID_MBAR3}};

        static GUIMenuBar menubar = {3 ,0, mbitems};

        GUIMenusSetChrType(GR_UTF8_TEXT); // this source is UTF8 coded
        GUIMenusSetFont(menufont);
        GUIMenusSetColors(WEBC_WHITE, WEBC_RED, WEBC_BLACK,
                          WEBC_YELLOW, WEBC_LIGHTGRAY, WEBC_DARKGRAY);

        GUIMenuRegister(&menu1);
        GUIMenuRegister(&menu2);
        GUIMenuRegister(&menu3);
        GUIMenuBarSet(&menubar);

        GUIMenusSetI18nFields();
        GUIMenuBarSetI18nFields();
    }

    void setup_etc(void)
    {
        int mbheight;

        GUIObjectsSetColors(WEBC_KHAKI, WEBC_PERU, WEBC_SIENNA);
        GUIObjectsSetFont(objfont);
        GUIDialogsSetColors(WEBC_BLACK, WEBC_ORANGE, WEBC_MAROON,
                                WEBC_ANTIQUEWHITE);
        GUICDialogsSetColors(WEBC_TAN, WEBC_BLACK);
        GUIDialogsSetTitleFont(dlgtfont);
        GUICDialogsSetFont(cdlgfont);

        mbheight = GUIMenuBarGetHeight();
        tile1 = GUITileCreate(IDT_CANVAS, GUI_TT_BORDERLESS, 0, mbheight,
                              GrSizeX(), GrSizeY()-mbheight);
        GUIPanelSetClCallBacks(tile1->p, paint_panel1, NULL);
        GUITileRegister(tile1);
    }

    void change_language(int lid)
    {
        if (lid < 0 || lid >= NUM_LANGUAGES) return;
        if (lid == language) return;
        language = lid;
        GrI18nSetLang(language);
        GUIMenusSetI18nFields();
        GUIMenuBarSetI18nFields();
        GUIMenuBarHide();
        GUIMenuBarShow();
    }

    void load_image(int n)
    {
        if (n<0 || n>2) n = 0; // to be safe

        if (imgctx && n == nimgloaded) {
            // ok we have the image context already, but if the GrSetMode was
            // changed to an incompatible framemoder we have to reload again!!
            //printf("modes: %d %d\n",
            //       imgctx->gc_driver->mode,
            //       GrScreenContext()->gc_driver->rmode);
            if (imgctx->gc_driver->mode == GrScreenContext()->gc_driver->rmode)
                return; // we don't need to reload
        }

        load_image_file(imgname[n]);
        nimgloaded = n;
    }

    void load_image_file(char *fimg)
    {
        destroy_image();

        if (GrNumColors() > 256 && GrJpegSupport()) {
            if (GrQueryJpeg(fimg, &imgwidth, &imgheight) == 0) {
                imgctx = GrCreateContext(imgwidth, imgheight, NULL, NULL);
                if (imgctx != NULL) {
                    if (GrLoadContextFromJpeg(imgctx, fimg, 1) != 0) {
                        destroy_image();;
                    }
                }
            }
        }
    }

    void destroy_image(void)
    {
        if (imgctx) {
            GrDestroyContext(imgctx);
            imgctx = NULL;
        }
    }

    void paint_panel1(void *udata)
    {
        if (imgctx) {
            GrStretchBlt(NULL, 0, 0, GrMaxX(), GrMaxY(), imgctx, 0, 0,
                         imgwidth-1, imgheight-1, GrWRITE);
        } else {
            GrFilledBox(0, 0, GrMaxX(), GrMaxY(), WEBC_GOLDENROD);
        }
    }

    int dialog_grgui(void)
    {
        static char *abouttext[4];

        abouttext[0] = _(SID_DLGHELLO1);
        abouttext[1] = _(SID_DLGHELLO2);
        abouttext[2] = _(SID_DLGHELLO3);
        abouttext[3] = _(SID_DLGHELLO4);

        return GUICDialogInfo(_(SID_DLGHELLOT), (void **)abouttext, 4, _(SID_OK));
    }

    int dialog_about(void)
    {
        static char *abouttext[3];

        abouttext[0] = _(SID_DLGABOUT1);
        abouttext[1] = _(SID_DLGABOUT2);
        abouttext[2] = _(SID_DLGABOUT3);

        return GUICDialogInfo(_(SID_DLGABOUTT), (void **)abouttext, 3, _(SID_OK));
    }

Starting with the 1.3.5 MGRX version the X11 and Win32 drivers support window resize. This is achivied passing a parameter to the driver with "GrSetDriverExt(NULL, "rszwin")" before setting the graphics mode. After that GREV_WSZCHG events will be generate every time the user resize the window.

When we receive such an event we must uninit GrGUI, change the graphics mode and init GrGUI again. Also we must save the program status in some way to restore it later. In this small program the state are the language, the background image and the dialog showed, if any.

Note that this program runs well with drivers that don't support window resize like the linux framebuffer driver or the vesa driver.

GrGUI do not take the input control except in two cases, when a menu is showed or when a dialog is showed. In both cases when a GREV_WSZCHG is received the action taken is to cancel the menu or the dialog and reenqueue the event, so the main program can do the necesary actions.

## Example 12b. Internationalization

    #define NUM_LANGUAGES 3

    #define _(sid) ((char *)GrI18nGetString(sid))

    // english strings
    char *eng_menubar[3] = {
        "&File",
        "&Language",
        "&Help"};
    char *eng_menu1[4] = {
        "Load picture &1",
        "Load picture &2",
        "Load picture &3",
        "E&xit"};
    char *eng_menu2[2] = {
        "&Hello GrGUI",
        "&About this test"};
    char *eng_menu3[3] = {
        "&English",
        "&Spanish",
        "&Greek"};
    char *eng_hello[5] = {
        "Hello GrGUI",
        "Welcome to MGRX and GrGUI",
        "MGRX is a small C 2D graphics library",
        "and GrGUI a miniGUI on top of MGRX",
        "visit mgrx.fgrim.com for more info"};
    char *eng_about[4] = {
        "About",
        "This is the GrGUI programmer's guide",
        "test number 12, a minimal example",
        "ready to manage user window resizes"};
    char *eng_generic[1] = {
        "Okey"};

    // spanish strings
    char *spa_menubar[3] = {
        "&Fichero",
        "&Idioma",
        "&Ayuda"};
    char *spa_menu1[4] = {
        "Cargar imagen &1",
        "Cargar imagen &2",
        "Cargar imagen &3",
        "&Salir"};
    char *spa_menu2[2] = {
        "&Hola GrGUI",
        "&Acerca de"};
    char *spa_menu3[3] = {
        "&Inglés",
        "&Español",
        "&Griego"};
    char *spa_hello[5] = {
        "Hola GrGUI",
        "Bienvenido a MGRX y a GrGUI",
        "MGRX es una librería en C de gráficos 2D",
        "y GrGUI es un miniGUI para MGRX",
        "visita mgrx.fgrim.com para más información"};
    char *spa_about[4] = {
        "Acerca de",
        "Este es el programa de ejemplo número 12",
        "de la guía del programador de GrGUI",
        "muestra como manejar la redimensión de ventanas"};
    char *spa_generic[1] = {
        "Vale"};

    // greek strings
    char *gre_menubar[3] = {
        "&F:Αρχείο",
        "&L:Γλώσσα",
        "&H:Βοήθεια"};
    char *gre_menu1[4] = {
        "Εικόνα &1",
        "Εικόνα &2",
        "Εικόνα &3",
        "&Εξοδος"};
    char *gre_menu2[2] = {
        "&Γειά σου GrGUI",
        "&Σχετικά"};
    char *gre_menu3[3] = {
        "&Αγγλικά",
        "&Ισπανικά",
        "&Ελληνικά"};
    char *gre_hello[5] = {
        "Γειά σου GrGUI",
        "Καλωσήλθατε στην MGRX και GrGUI",
        "Η MGRX είναι μία μικρή βιβλιοθήκη γραφικών 2Δ σε C",
        "και η GrGUI μία διεπαφή χρήστη πάνω στην MGRX",
        "Επισκεφτείτε το mgrx.fgrim.com για περισσότερες πληροφορίες"};
    char *gre_about[4] = {
        "Σχετικά",
        "Αυτό είναι το παράδειγμα 12 που θα βρείτε",
        "στις οδηγίες προγραμματισμού της GrGUI",
        "με δυνατότητα αλλαγής μεγέθους του παραθύρου"};
    char *gre_generic[1] = {
        "Εντάξει"};

    enum SID {
        SID_OK = 0,         // generic strings

        SID_MBAR1,          // menu bar
        SID_MBAR2,
        SID_MBAR3,

        SID_MENU1_1,        // menu 1
        SID_MENU1_2,
        SID_MENU1_3,
        SID_MENU1_4,

        SID_MENU2_1,        // menu 2
        SID_MENU2_2,

        SID_MENU3_1,        // menu 3
        SID_MENU3_2,
        SID_MENU3_3,

        SID_DLGHELLOT,      // dlg hello
        SID_DLGHELLO1,
        SID_DLGHELLO2,
        SID_DLGHELLO3,
        SID_DLGHELLO4,

        SID_DLGABOUTT,      // dlg about
        SID_DLGABOUT1,
        SID_DLGABOUT2,
        SID_DLGABOUT3,

        SID_LAST
    };

    void setup_i18n(void)
    {
        if (!GrI18nInit(NUM_LANGUAGES, SID_LAST, "undef")) {
            fprintf(stderr, "error initing GrI18n");
            exit(1);
        }

        GrI18nSetLabel(0, "English");
        GrI18nSetLabel(1, "Español");
        GrI18nSetLabel(2, "Greek");

        GrI18nAddStrings(0, SID_OK, 1, (void **)eng_generic);
        GrI18nAddStrings(0, SID_MBAR1, 3, (void **)eng_menubar);
        GrI18nAddStrings(0, SID_MENU1_1, 4, (void **)eng_menu1);
        GrI18nAddStrings(0, SID_MENU2_1, 2, (void **)eng_menu2);
        GrI18nAddStrings(0, SID_MENU3_1, 3, (void **)eng_menu3);
        GrI18nAddStrings(0, SID_DLGHELLOT, 5, (void **)eng_hello);
        GrI18nAddStrings(0, SID_DLGABOUTT, 4, (void **)eng_about);

        GrI18nAddStrings(1, SID_OK, 1, (void **)spa_generic);
        GrI18nAddStrings(1, SID_MBAR1, 3, (void **)spa_menubar);
        GrI18nAddStrings(1, SID_MENU1_1, 4, (void **)spa_menu1);
        GrI18nAddStrings(1, SID_MENU2_1, 2, (void **)spa_menu2);
        GrI18nAddStrings(1, SID_MENU3_1, 3, (void **)spa_menu3);
        GrI18nAddStrings(1, SID_DLGHELLOT, 5, (void **)spa_hello);
        GrI18nAddStrings(1, SID_DLGABOUTT, 4, (void **)spa_about);

        GrI18nAddStrings(2, SID_OK, 1, (void **)gre_generic);
        GrI18nAddStrings(2, SID_MBAR1, 3, (void **)gre_menubar);
        GrI18nAddStrings(2, SID_MENU1_1, 4, (void **)gre_menu1);
        GrI18nAddStrings(2, SID_MENU2_1, 2, (void **)gre_menu2);
        GrI18nAddStrings(2, SID_MENU3_1, 3, (void **)gre_menu3);
        GrI18nAddStrings(2, SID_DLGHELLOT, 5, (void **)gre_hello);
        GrI18nAddStrings(2, SID_DLGABOUTT, 4, (void **)gre_about);
    }

MGRX come with a littel catalog implementation that we can use to internationalize our programs.

Revisiting our number 12 example we are coding it to support three languages, using the include file "gr12i18n.h" showed above.

The only problem are that menues and the menu bar are static strucutures so we need some help from the library to set the correct strings at runtime.

That is what the `GUIMenusSetI18nFields` and `GUIMenuBarSetI18nFields` funtions do.

## More information

I think the twelve examples covers most of the GrGUI functionality, but GrGUI is a small GUI, you can use the "grgui.h" main include file as a reference, or even read the source code if nedeed.

A big example is the "demintl2.c" program, it use is mainly to test the recode functionality features of MGRX, but it makes extensive use of GrGUI too. Another small example is the "showfnt2.c" program. You can find them in the MGRX distribution under the "testgui" subdirectory.

Enjoy.