Announcement

Collapse

Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

Calculate the dew point with DewPoint.bas

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • PBWin/PBCC Calculate the dew point with DewPoint.bas

    This program easily calculates Air Temperature, DewPoint and Relative Humidity - assuming constant air pressure.

    In the attached zip file you find source code / compiled program. It uses the include file of "Graphic Buttons" published here:
    http://www.powerbasic.com/support/pb...splay.php?f=12

    The dew point is the temperature at which condensation starts when the original air temperature and humidity are known.
    For example: At 82 °F the relative humidity is 55%. When it gets dark the air cools down because of lack of sun shine
    but it will not fall below the dew point - in this case 64.3 °F.

    The dew point tells you how low the temperature can drop at night because heat is released at the dew point and thus
    keeps the temperature quite stable. It is the heat that was originally necessary to turn water into vapor.

    When two of those values are entered, the third one gets promptly calculated.

    Regards,
    Gert Voland
    Attached Files
    Gert Voland

  • #2
    Updated to work with Unicode version of GrButtons.inc


    Code:
    ' DewPoint.bas
    'https://forum.powerbasic.com/forum/user-to-user-discussions/source-code/49728-calculate-the-dew-point-with-dewpoint-bas
    'Updated by: Jim Fritts on 2018 May 15 to use Unicode
    'Gert Voland's GrButtons.inc Unicode version was slightly modified
    'Thanks Gert
    '
    ' Compiler:       PBCC 6.02 + PB/Win 10.02
    ' WinAPI:         01 Jun 2011
    ' OS:             Windows XP
      $DP_Date      = "31 Aug 2011"
      $DP_Version   = "V0.5"
      $DP_Author    = "GV"
    ' Release:        Public domain by the author
      $DP_Disclaim  = "Use at own risk"
    
    ' Changes in V0.5:
    ' ----------------
    ' PBCC 6.02 used
    ' GLOBALs wGW and hGW replaced by wdGW and htGW
    ' LE text edited and then read (in case of returned $ESC)
    
    ' Changes 16 June 2010 in V0.4:
    ' -----------------------------
    ' SLEEP 1 introduced in Do loops to keep CPU activity optically down
    
    #DIM ALL
    #COMPILE EXE "DewPoint.exe"
    #IF %DEF(%PB_CC32)
        #BREAK ON
        #CONSOLE OFF
    #ENDIF
    #DEBUG DISPLAY OFF                          ' Turn off for final compilation
    #DEBUG ERROR OFF                            ' Turn off for final compilation
    
    
    %GW_Settings = 3
    
    #INCLUDE "GrButtons.inc"                    ' Use V01.0 for PB/CC 6 or PB/Win 10
    'Original non unicode include file was missing from forum
    'Found unicode version instead
    
    ' Equates for button and frame IDs (must be >0 and unique)
    %LE_TAir            = 21
    %LE_TDew            = 22
    %LE_Humidity        = 23
    %LE_PVapor          = 24
    %GB_Help            = 30
    %GB_HelpReturn      = 31
    %GB_Fahrenheit      = 32
    %GB_Quit            = 33
    
    ' Equates for text
    %DP_FontSize        = 13
    %DP_FontStyle       = 1
    $DP_FontName        = "Arial"
    
    GLOBAL TAir, TDew, Humidity, PVapor     AS SINGLE   ' Values of the DewPoint formulas
    GLOBAL lastButton                       AS LONG
    GLOBAL xGW, yGW                         AS LONG     ' x, y position for GW
    GLOBAL wdGW, htGW                       AS LONG     ' Width, height of GW
    GLOBAL hlargeFont                       AS DWORD    ' Font handle Main GW large
    GLOBAL hnormalFont                      AS DWORD    ' Font handle Main GW normal
    GLOBAL helpFont                         AS DWORD    ' Font handle Help GW
    GLOBAL useFahren                        AS LONG     ' Flag: 0 => Celsius, 1 => Fahrenheit
    GLOBAL posx3, txty1, txty2              AS LONG     ' x, y positions for °C/°F
    GLOBAL limit0, limit1, limit2, limit3   AS SINGLE   ' Temp limits for °C and °F
    
    '------------------------------------------------------------------------------
    
    FUNCTION PBMAIN () AS LONG
    
        LOCAL retButton                     AS LONG     ' retButton after activating a LineEntry or button
        DIM t(1 TO 3)                       AS LONG     ' Priority list for calculating new values
        LOCAL i                             AS LONG     ' Indices
        LOCAL iButton                       AS LONG     ' Index of selected button
    
        ' Set text fonts
        FONT NEW $DP_FontName, %DP_FontSize + 10, %DP_FontStyle TO hlargeFont   ' Title is larger
        FONT NEW $DP_FontName, %DP_FontSize,      %DP_FontStyle TO hnormalFont  ' Normal size now
        FONT NEW "Verdana",    10,                1             TO helpFont     ' Help font
        BaseApplication.OverType = 0                                            ' Preset to non-overtype
    
        ' Set GW up
        CALL Create_GW_Buttons(1, " Enter two values => calculate the third")
        t(1) = %LE_TAir
        t(2) = %LE_TDew
        t(3) = %LE_Humidity
    
        ' Preset
        TAir = 30
        TDew = 20
        CALL Calculate_Humidity(TAir, TDew, Humidity, PVapor)
        useFahren = 0                                                               ' Celsius
        limit0 = 0
        limit1 = 50
        limit2 = 60
        limit3 = 100
    
        DisplayTemperature(%LE_TAir, TAir)
        DisplayTemperature(%LE_TDew, TDew)
        CALL DisplayHumidity(Humidity)
        GraphicButtonsSetText(%LE_PVapor, USING$("#.#", PVapor))
    
        ' Main loop for checking activated buttons and recalculation
        DO
            SLEEP 1
            CALL GraphicGetEvents(retButton)                                        ' Which button has been activated?
            lastButton = retButton
    
            SELECT CASE retButton                                                   ' Act on activated LE/button
                iButton = GraphicGetIndex(retButton)
                CASE %LE_TAir
                    GraphicButtons(iButton).TextColor = %RGB_BLUE                   ' Back to normal for editing (.ClearText = 2)
                    GraphicButtons(iButton).HoverTextColor = %RGB_BLUE
                    GraphicEditText(%LE_TAir)                                       ' Edit and read TAir
                    TAir = VAL(GraphicButtonsGetText(%LE_TAir))
                    IF useFahren = 1 THEN TAir = (TAir - 32)/1.8                    ' If it comes in Fahrenheit, then convert to Celsius for calculation
                    DisplayTemperature(%LE_TAir, TAir)
    
                CASE %LE_TDew
                    IF VAL(GraphicButtonsGetText(%LE_TDew)) < 0 THEN ITERATE DO     ' Do not edit TDew when it is negative
                    GraphicButtons(iButton).TextColor = %RGB_BLUE                   ' Back to normal for editing (.ClearText = 2)
                    GraphicButtons(iButton).HoverTextColor = %RGB_BLUE
                    GraphicEditText(%LE_TDew)                                       ' Edit and read TDew
                    TDew = VAL(GraphicButtonsGetText(%LE_TDew))
                    IF useFahren = 1 THEN TDew = (TDew - 32)/1.8                    ' If it comes in Fahrenheit, then convert to Celsius for calculation
                    DisplayTemperature(%LE_TDew, TDew)
    
                CASE %LE_Humidity
                    GraphicEditText(%LE_Humidity)                                   ' Edit and read Humidity
                    Humidity = VAL(GraphicButtonsGetText(%LE_Humidity))
                    CALL DisplayHumidity(Humidity)
    
                CASE %GB_Help
                    CALL Print_Help
    
                CASE %GB_Fahrenheit
                    IF useFahren = 0 THEN
                        useFahren = 1                                               ' Now Fahrenheit
                        GraphicButtonsSetText(%GB_Fahrenheit, "Use °C")
                        limit0 = 32
                        limit1 = 122
                        limit2 = 140
                        limit3 = 212
                    ELSE
                        useFahren = 0                                               ' Now Celsius
                        GraphicButtonsSetText(%GB_Fahrenheit, "Use °F")
                        limit0 = 0
                        limit1 = 50
                        limit2 = 60
                        limit3 = 100
                    END IF
                    DisplayTemperature(%LE_TAir, TAir)
                    DisplayTemperature(%LE_TDew, TDew)
    
                CASE %GB_Quit                                                       ' Quit: Leave loop
                    EXIT LOOP
    
                CASE ELSE
                    ITERATE DO
            END SELECT
    
            ' Find the index of the retButton
            FOR i = 1 TO 3
                IF t(i) = retButton THEN EXIT FOR
            NEXT i
    
            ' Act on the found index
            SELECT CASE LONG i                                              ' Swap the indices: Edited on (1), recalculate on (2), remaining oldest on (3)
                CASE 1                                                      ' Newest value: no action
    
                CASE 2                                                      ' In between
                    SWAP t(1), t(2)
    
                CASE 3                                                      ' Oldest value
                    SWAP t(2), t(3)
                    SWAP t(1), t(2)
            END SELECT
    
            ' Recalculate the oldest, non-edited value: it ended up as (2)
            iButton = GraphicGetIndex(t(3))
            SELECT CASE LONG t(3)
                CASE %LE_TAir
                    Calculate_TAir(TAir, TDew, Humidity, PVapor)
                    DisplayTemperature(%LE_TAir, TAir)
    
                CASE %LE_TDew
                    Calculate_TDew(TAir, TDew, Humidity, PVapor)
                    DisplayTemperature(%LE_TDew, TDew)
    
                CASE %LE_Humidity
                    Calculate_Humidity(TAir, TDew, Humidity, PVapor)
                    CALL DisplayHumidity(Humidity)
            END SELECT
    
            ' Display PVapor
            SELECT CASE PVapor
                CASE >= 100
                    GraphicButtonsSetText(%LE_PVapor, USING$("###", PVapor))
                CASE >= 1
                    GraphicButtonsSetText(%LE_PVapor, USING$("##.#", PVapor))
                CASE < 1
                    PVapor = MAX(PVapor, 0.01)
                    GraphicButtonsSetText(%LE_PVapor, USING$("#.##", PVapor))
            END SELECT
    
        LOOP
    
        GraphicDestroyWindow(1)
    
    END FUNCTION    'PBMAIN
    
    '------------------------------------------------------------------------------
    
    SUB DisplayTemperature(BYVAL nID AS LONG, BYVAL Temp AS SINGLE)
    
        LOCAL iButton                   AS LONG         ' Index
        LOCAL upperlimit, lowerlimit    AS SINGLE
    
        iButton = GraphicGetIndex(nID)
    
        lowerlimit = limit0
        IF nID = %LE_TAir THEN
            upperlimit = limit2
        ELSE
            upperlimit = limit1
        END IF
    
        IF useFahren = 1 THEN Temp = Temp * 1.8 + 32                ' Fahrenheit
        IF Temp <= lowerlimit OR Temp >= upperlimit THEN
            GraphicButtons(iButton).TextColor = %RGB_RED            ' Warning: Invalid values in RED
            GraphicButtons(iButton).HoverTextColor = %RGB_RED
        ELSE
            GraphicButtons(iButton).TextColor = %RGB_BLUE
            GraphicButtons(iButton).HoverTextColor = %RGB_BLUE
        END IF
    
        GraphicButtonsSetText(nID, USING$("#.#", Temp))
    
        ' Position for °C/°F, clear it and print the °
        GRAPHIC SET FONT hnormalFont
        GRAPHIC COLOR %GW_BackColor, %GW_BackColor
        IF nID = %LE_TAir THEN
            GRAPHIC SET POS (posx3, txty1)                          ' Clear °
            GRAPHIC PRINT "    ";
            GRAPHIC SET POS (posx3, txty1)                          ' ° for TAir
        ELSE
            GRAPHIC SET POS (posx3, txty2)                          ' Clear °
            GRAPHIC PRINT "    ";
            GRAPHIC SET POS (posx3, txty2)                          ' ° for TDew
        END IF
    
        GRAPHIC COLOR %RGB_BLUE , -2
        IF useFahren = 0 THEN
            GRAPHIC PRINT "°C";
        ELSE
            GRAPHIC PRINT "°F";
        END IF
    
    
    END SUB 'DisplayTemperature
    
    '------------------------------------------------------------------------------
    
    SUB DisplayHumidity(BYVAL Humid AS SINGLE)
    
        LOCAL iButton           AS LONG         ' Index
    
        iButton = GraphicGetIndex(%LE_Humidity)
    
        IF Humid <= 1.0 OR Humid >= 100 THEN
            GraphicButtons(iButton).TextColor = %RGB_RED            ' Warning: Invalid values in RED
            GraphicButtons(iButton).HoverTextColor = %RGB_RED
        ELSE
            GraphicButtons(iButton).TextColor = %RGB_BLUE
            GraphicButtons(iButton).HoverTextColor = %RGB_BLUE
        END IF
    
        GraphicButtonsSetText(%LE_Humidity, USING$("#.#", Humid))
    
    END SUB ' DisplayHumidity
    
    '------------------------------------------------------------------------------
    
    SUB Calculate_TAir(       TAir      AS SINGLE, _
                        BYVAL TDew      AS SINGLE, _
                        BYVAL Humidity  AS SINGLE, _
                              PVapor    AS SINGLE)
    
        LOCAL pTAir     AS SINGLE
        LOCAL pTDew     AS SINGLE
    
        pTDew = 10^((TDew * 7.4475) / (TDew + 234.67))
        pTAir = pTDew / Humidity * 100
        TAir = 234.67 / (7.4475 / LOG10(pTAir) -1)
        PVapor = pTDew * 6.1
    
    END SUB 'Calculate_TAir
    
    '------------------------------------------------------------------------------
    
    SUB Calculate_TDew( BYVAL TAir      AS SINGLE, _
                              TDew      AS SINGLE, _
                        BYVAL Humidity  AS SINGLE, _
                              PVapor    AS SINGLE)
    
        LOCAL pTAir     AS SINGLE
        LOCAL pTDew     AS SINGLE
    
        pTAir = 10^((TAir * 7.4475) / (TAir + 234.67))
        pTDew = pTAir * Humidity / 100
        TDew = 234.67 / (7.4475 / LOG10(pTDew) -1)
        PVapor = pTDew * 6.1
    
    END SUB 'Calculate_TDew
    
    '------------------------------------------------------------------------------
    
    SUB Calculate_Humidity( BYVAL TAir      AS SINGLE, _
                            BYVAL TDew      AS SINGLE, _
                                  Humidity  AS SINGLE, _
                                  PVapor    AS SINGLE)
    
        LOCAL pTAir     AS SINGLE
        LOCAL pTDew     AS SINGLE
    
        pTAir = 10^((TAir * 7.4475) / (TAir + 234.67))
        pTDew = 10^((TDew * 7.4475) / (TDew + 234.67))
        Humidity = pTDew / pTAir * 100
        PVapor = pTDew * 6.1
    
    END SUB 'Calculate_Humidity
    
    '------------------------------------------------------------------------------
    
    SUB Create_GW_Buttons(BYVAL nID AS LONG, BYVAL caption$)
    ' Create graphic window and buttons
    
        LOCAL BUTTON                    AS GraphicButton    ' Data set for button parameters
        LOCAL x1, x2, x4                AS LONG             ' x positions for text and buttons
        LOCAL btnw, btnh                AS LONG             ' Width and Height of buttons
        LOCAL y1, y2, y3, y4, y5, y6    AS LONG             ' y positions for text and buttons
        LOCAL TXT1                      AS STRING           ' Text to be printed
        LOCAL txtw, txth                AS SINGLE           ' Width and height of strings
        LOCAL txtx, txty                AS LONG             ' x, y position of text
    
        ' GW coordinates
        xGW = 150
        yGW = 100
        wdGW = 300
        htGW = 240
    
        ' Button size and positions
        btnw = 70
        btnh = 30
    
        x4 = 25
        posx3 = wdGW - 50
        x2 = posx3 - 5 - btnw
        x1 = x2 - 10
    
        y1 = 10
        y2 = 60
        y3 = 90
        y4 = 120
        y5 = 150
        y6 = 195
    
        ' Create the GW (=> GrButtons.inc)
        CALL GraphicCreateWindow(nID, caption$, xGW, yGW, wdGW, htGW)
    
        ' Title text    ******************************************************************************
        TXT1 = "D e w P o i n t"
        CALL MeasureTextSize(TXT1, hlargeFont, hGraphicWindow(nGW), %true, txtw, txth)
        txtx = (wdGW - txtw)/2                                        ' Right aligned at x1
        GRAPHIC SET POS (txtx, y1)
        GRAPHIC COLOR %RGB_DARKBLUE , -2
        GRAPHIC PRINT TXT1
    
        ' TAir text     ******************************************************************************
        TXT1 = "Air Temperature :"
        CALL MeasureTextSize(TXT1, hnormalFont, hGraphicWindow(nGW), %true, txtw, txth)
        txtx = x1 - txtw
        txty1 = y2 + (btnh - txth)/2                                 ' Height aligned with buttons
        GRAPHIC SET POS (txtx, txty1)
        GRAPHIC COLOR %RGB_BLUE , -2
        GRAPHIC PRINT TXT1
    
         ' Create LineEntries
        Button.ID               = %LE_TAir
        Button.Type             = %GW_LineEntry
        Button.x                = x2
        Button.y                = y2
        Button.Width            = btnw
        Button.Height           = btnh
        Button.Text             = "0"
        Button.TextAlign        = %GW_Center
        Button.MaxChars         = 6                                 ' Not more than 6 characters can be entered
        Button.ClearText        = 2                                 ' Clear text every time
        Button.nUnderChar       = 0                                 ' Length of text in .OnClick routine
        Button.HotKey           = "@T"                              ' Shortcut character: Alt + T
        Button.FontName         = $DP_FontName
        Button.FontSize         = %DP_FontSize
        Button.FontStyle        = %DP_FontStyle
        Button.TextColor        = %RGB_BLUE
        Button.BackColor        = %RGB_WHITESMOKE
        Button.BorderColor      = %GW_BackColor
        Button.HoverFontStyle   = %DP_FontStyle
        Button.HoverTextColor   = %RGB_BLUE
        Button.HoverBackColor   = %RGB_LIGHTGRAY
        Button.HoverBorderColor = %GW_BackColor
        Button.Focus            = 1                                 ' Start with the focus here
        Button.OnClick          = CODEPTR(Temperature_Check)
        Button.Disable          = 0                                 ' Enabled
        Button.TBTick           = 0                                 ' Hidden use for valid flag with .OnClick routine
        CALL GraphicAddButton(BUTTON)
    
        ' TDew text     ******************************************************************************
        TXT1 = "DewPoint :"
        CALL MeasureTextSize(TXT1, hnormalFont, hGraphicWindow(nGW), %true, txtw, txth)
        txtx = x1 - txtw
        txty2 = y3 + (btnh - txth)/2
        GRAPHIC SET POS (txtx, txty2)
        GRAPHIC PRINT TXT1
    
        Button.ID               = %LE_TDew
        Button.y                = y3
        Button.HotKey           = "@D"
        Button.Focus            = 0
        Button.OnClick          = CODEPTR(Temperature_Check)
        CALL GraphicAddButton(BUTTON)
    
        ' Humidity text ******************************************************************************
        TXT1 = "Relative Humidity :"
        CALL MeasureTextSize(TXT1, hnormalFont, hGraphicWindow(nGW), %true, txtw, txth)
        txtx = x1 - txtw
        txty = y4 + (btnh - txth)/2
        GRAPHIC SET POS (txtx, txty)
        GRAPHIC PRINT TXT1
    
        Button.ID               = %LE_Humidity
        Button.y                = y4
        Button.HotKey           = "@H"
        Button.OnClick          = CODEPTR(Humidity_Check)
        CALL GraphicAddButton(BUTTON)
    
        GRAPHIC SET POS (posx3, txty)
        GRAPHIC SET FONT hnormalFont
        GRAPHIC COLOR %RGB_BLUE , -2
        GRAPHIC PRINT "%"
    
        ' PVapor text   ******************************************************************************
        TXT1 = "Vapor Pressure :"
        CALL MeasureTextSize(TXT1, hnormalFont, hGraphicWindow(nGW), %true, txtw, txth)
        txtx = x1 - txtw
        txty = y5 + (btnh - txth)/2
        GRAPHIC SET POS (txtx, txty)
        GRAPHIC PRINT TXT1
    
        Button.ID               = %LE_PVapor
        Button.y                = y5
        Button.ClearText        = -1                                ' Display-only mode of LE
        Button.BackColor        = %GW_BackColor
        Button.HotKey           = "@P"
        CALL GraphicAddButton(BUTTON)
    
        GRAPHIC SET POS (posx3, txty)
        GRAPHIC SET FONT hnormalFont
        GRAPHIC COLOR %RGB_BLUE , -2
        GRAPHIC PRINT "hPa"
    
        ' Fahrenheit button   ******************************************************************************
        Button.ID               = %GB_Fahrenheit
        Button.Type             = %GW_Button
        Button.x                = wdGW - x4 - btnw
        Button.y                = y6
        Button.Width            = btnw - 15
        Button.Height           = btnh - 5
        Button.Text             = "Use °F"
        Button.nUnderChar       = 1                                 ' "U" as hot key
        Button.HotKey           = "@U"
        Button.FontName         = "MS Sans Serif"
        Button.FontSize         = 9
        Button.FontStyle        = 1
        Button.TextColor        = %RGB_BLUE
        Button.BackColor        = %RGB_WHITESMOKE
        Button.BorderColor      = %RGB_DARKBLUE
        Button.HoverFontStyle   = 1
        Button.HoverTextColor   = %RGB_DARKBLUE
        Button.HoverBackColor   = %RGB_LIGHTGRAY
        Button.HoverBorderColor = %RGB_DARKBLUE
        Button.OnClick          = 0
        Button.Focus            = 0
        CALL GraphicAddButton(BUTTON)
    
        ' Help button   ******************************************************************************
        Button.ID               = %GB_Help
        Button.Type             = %GW_Button
        Button.x                = (wdGW - btnw)/2
        Button.Text             = "Help"
        Button.nUnderChar       = 1                                 ' "H" as hot key
        Button.HotKey           = "F1"
        CALL GraphicAddButton(BUTTON)
    
        ' Quit button   ******************************************************************************
        Button.ID               = %GB_Quit
        Button.Type             = %GW_Button
        Button.x                = x4
        Button.Text             = "Quit"
        Button.nUnderChar       = 1                                 ' "Q" as hot key
        Button.HotKey           = "ESC"
        CALL GraphicAddButton(BUTTON)
    
    
    END SUB 'Create_GW_Buttons
    
    '------------------------------------------------------------------------------
    
    SUB HelpReturnButton(BYVAL w1 AS LONG, BYVAL h1 AS LONG, BYVAL TXT1 AS STRING)
    
        LOCAL BUTTON        AS GraphicButton            ' Data set for button parameters
        LOCAL btnw, btnh    AS LONG                     ' Width and height
    
        btnw = 150
        btnh = 30
    
        Button.ID               = %GB_HelpReturn
        Button.Type             = %GW_Button
        Button.x                = w1 - 20 - btnw
        Button.y                = h1 - 20 - btnh
        Button.Width            = btnw
        Button.Height           = btnh
        Button.Text             = TXT1
        Button.nUnderChar       = 1
        Button.HotKey           = "ESC"
        Button.FontName         = "MS Sans Serif"
        Button.FontSize         = 8
        Button.FontStyle        = 1
        Button.TextColor        = %RGB_BLUE
        Button.BackColor        = %RGB_WHITESMOKE
        Button.BorderColor      = %RGB_DARKBLUE
        Button.HoverFontStyle   = 1
        Button.HoverTextColor   = %RGB_DARKBLUE
        Button.HoverBackColor   = %RGB_LIGHTGRAY
        Button.HoverBorderColor = %RGB_DARKBLUE
        Button.Focus            = 1
        Button.OnClick          = 0                                 ' No callback routine
        Button.Disable          = 0                                 ' Enabled
        CALL GraphicAddButton(BUTTON)
    
    END SUB ' HelpReturnButton
    
    '-----------------------------------------------------------------------------------------------
    
    SUB MeasureTextSize(BYVAL TXT1 AS STRING, BYVAL hFont AS DWORD, BYVAL htGW AS DWORD, BYVAL redrawFlag AS LONG, txtw AS SINGLE, txth AS SINGLE)
    ' Test the size of a text string for a given font
    
    ' Input:    TXT1        =  Text string to be measured
    '           hFont       =  Handle of text font (must be already declared)
    '           htGW         =  Handle of the calling GW (which gets attached again)
    '           redrawFlag  =  %false => no redraw, %true => with redraw
    '
    ' Output:   txtw        =  Width of text string
    '           txth        =  Height of text string
    
        LOCAL hBmp      AS DWORD                ' Handle of bitmap
    
        GRAPHIC BITMAP NEW 0, 0 TO hBmp                             ' Create a bitmap (no icon in taskbar)
        GRAPHIC ATTACH hBmp, 0                                      ' Attach it
        GRAPHIC SET FONT hFont                                      ' Connect the font
        GRAPHIC TEXT SIZE TXT1 TO txtw, txth                        ' Measure the size
        GRAPHIC BITMAP END                                          ' Close the bitmap again
    
        IF ISFALSE redrawFlag THEN                                  ' Re-attach the GW
            GRAPHIC ATTACH htGW, 0                                   ' No redraw
        ELSE
            GRAPHIC ATTACH htGW, 0, REDRAW                           ' With redraw
        END IF
        GRAPHIC SET FONT hFont                                      ' Connect the font to the GW for printing
    
    END SUB 'Measure TextSize
    
    '-----------------------------------------------------------------------------------------------
    
    SUB Temperature_Check
    ' Routine to check the limits of Temperature when entered in the LE
    ' Limits: for TAir 0 and 60 (inclusive)     ' 100 for WF
    '         for TDew 0 and 50 (Inclusive)     ' 100 for WF
    ' Called as .OnClick routine
    
    '+---------------------------------------------------------------------------------------------+
    '| In the .OnClick routine parameters have to be retrieved at the beginning by calling:        |
    '|      Call LEGetTextParameters(sText1$$, caretPos, editMode)                                   |
    '|                                                                                             |
    '| During the edit process editMode is <> 0. Text and/or caretPos may be changed.              |
    '| After editing (CR entered) the .OnClick routine gets called again with editMode = 0         |
    '|                                                                                             |
    '| After checking and possibly changing text, parameters have to be returned by calling:       |
    '|      Call LESetTextParameters(sText1$$, caretPos, validText)                                  |
    '|                                                                                             |
    '| validText has to be set = 0 (false) or = 1 (true), depending if the last edits were valid.  |
    '+---------------------------------------------------------------------------------------------+
    
        LOCAL caretPos, editMode, validText     AS LONG
        LOCAL sText1$$
        LOCAL value                             AS SINGLE
        LOCAL i, x                              AS LONG
        STATIC init                             AS LONG
    
        CALL LEGetTextParameters(sText1$$, caretPos, editMode)        ' Retrieve parameters
    
        IF init = 0 THEN                                            ' Set caret to the left at beginning
            caretPos = 0
            init = 1
        END IF
        IF editMode = 0 THEN init = 0                               ' Reset at end of editing
    
        validText = 0                                               ' Preset
    
        IF editMode = 1 THEN                                            ' Checks while editing ...
            '? sText1$$
            FOR i = 1 TO LEN(sText1$$)                                    ' Check for allowed characters
                x = ASC(sText1$$, i)
                IF x = ASC(".") THEN ITERATE FOR                        ' Allow .
                IF x = ASC("-") AND i > 1 THEN GOTO EndCheck:           ' Allow - at the left to correct a negative value
                IF x < ASC("0") OR x > ASC("9") THEN GOTO EndCheck:     ' Only numbers 0 - 9  allowed
            NEXT j
            IF INSTR(REMAIN$(sText1$$, "."), ".") > 0 THEN GOTO EndCheck: ' Allow only one .
        ELSE                                                            ' Checks when editing is done ...
            value = VAL(sText1$$)
            IF useFahren = 1 THEN value = (value - 32)/1.8
            IF value < 0 OR value > 100 THEN GOTO EndCheck:             ' Entry limits
            IF lastButton = %LE_TAir THEN
                IF value < TDew THEN GOTO EndCheck:                     ' TAir > TDew
            END IF
            IF lastButton = %LE_TDew THEN
                IF value > TAir THEN GOTO EndCheck:                     ' TDew < TAir
            END IF
        END IF
        validText = 1
    
        EndCheck:
        CALL LESetTextParameters(sText1$$, caretPos, validText)       ' Write parameters back
    
    END SUB 'Temperature_Check
    
    '-----------------------------------------------------------------------------------------------
    
    SUB Humidity_Check
    ' Routine to check the limits of Humidity when entered in the LE
    ' Limits: 1 and 100 (inclusive)
    
        LOCAL caretPos, editMode, validText     AS LONG
        LOCAL sText1$$
        LOCAL value                             AS SINGLE
        LOCAL i, x                              AS LONG
        STATIC init                             AS LONG
    
        CALL LEGetTextParameters(sText1$$, caretPos, editMode)        ' Retrieve parameters
    
        IF init = 0 THEN                                            ' Set caret to the left at beginning
            caretPos = 0
            init = 1
        END IF
        IF editMode = 0 THEN init = 0                               ' Reset at end of editing
    
        validText = 0                                               ' Preset
    
        IF editMode = 1 THEN                                            ' Checks while editing ...
            FOR i = 1 TO LEN(sText1$$)                                    ' Check for allowed characters
                x = ASC(sText1$$, i)
                IF x = ASC(".") THEN ITERATE FOR                        ' Allow .
                IF x < ASC("0") OR x > ASC("9") THEN GOTO EndCheck:     ' Only numbers 0 - 9  allowed
            NEXT j
            IF INSTR(REMAIN$(sText1$$, "."), ".") > 0 THEN GOTO EndCheck: ' Allow only one .
        ELSE                                                            ' Checks when editing is done ...
            value = VAL(sText1$$)
            IF value < 0 OR value > 100 THEN GOTO EndCheck:             ' Entry limits
        END IF
        validText = 1
    
        EndCheck:
        CALL LESetTextParameters(sText1$$, caretPos, validText)       ' Write parameters back
    
    END SUB 'Humidity_Check
    
    '-----------------------------------------------------------------------------------------------
    
    SUB Print_Help
    
        LOCAL retButton     AS LONG             ' Return button in this GW
        LOCAL hDC           AS DWORD            ' DC of GW
        LOCAL x1, y1        AS SINGLE
        LOCAL w1, h1        AS SINGLE
    
        x1 = 50
        y1 = 50
        w1 = 670
        h1 = 560
    
        CALL GraphicCreateWindow(2, "Help", x1, y1, w1, h1)
        CALL HelpReturnButton(w1, h1, "Return to DewPoint")
    
        GRAPHIC SET FONT helpFont
    
        GRAPHIC SET POS (0, 0)
        GRAPHIC PRINT
        GRAPHIC PRINT "  Scope of DewPoint:"
        GRAPHIC PRINT "  ------------------"
        GRAPHIC PRINT "  Easy calculation of Air Temperature, DewPoint and Relative Humidity,"
        GRAPHIC PRINT "  assuming constant air pressure."
        GRAPHIC PRINT
    
        GRAPHIC PRINT "  Usage:"
        GRAPHIC PRINT "  ------"
        GRAPHIC PRINT "  The calculation is always done for the ´oldest´ edited value."
        GRAPHIC PRINT "  E.g.: TAir gets edited and then TDew.  Now Humidity will be recalculated. "
        GRAPHIC PRINT "  If then Humidity gets newly entered, TAir, being the ´oldest´ value, gets"
        GRAPHIC PRINT "  recalculated with the last value of TDew.  And so on ..."
        GRAPHIC PRINT
        GRAPHIC PRINT "  The Vapor Pressure gets recalculated for any change and cannot be edited."
        GRAPHIC PRINT
        GRAPHIC PRINT "  Temperatures may be entered between 0 and 100 °C, Humidity between 0 and 100 %."
        GRAPHIC PRINT "  As a general rule only numbers from 0 to 9 and a decimal point can be entered."
        GRAPHIC PRINT "  As soon as a value falls outside the validity range it gets displayed in ";
        GRAPHIC COLOR %RGB_RED
        GRAPHIC PRINT "red";
        GRAPHIC COLOR %RGB_BLUE
        GRAPHIC PRINT " !"
        GRAPHIC PRINT "  An invalid TAir can be edited but an invalid TDew < 0 °C cannot."
        GRAPHIC PRINT "  The entry and display can be switched between °C and °F at any time.
        GRAPHIC PRINT
    
        GRAPHIC PRINT "  Method:"
        GRAPHIC PRINT "  -------"
        GRAPHIC PRINT "  The used formulas are similar to the Magnus-Tetens formulas:"
        GRAPHIC PRINT "  http://www.paroscientific.com/dewpoint.htm"
        GRAPHIC PRINT
        GRAPHIC PRINT "  Validity:  0  <  TAir     <  60 °C                                       0 °C  =    32 °F"
        GRAPHIC PRINT "                   0  <  TDew  <  50 °C                                     50 °C  =  122 °F"
        GRAPHIC PRINT "                   1  <  Rel. Humidity  <  100 %                    60 °C  =  140 °F"
        GRAPHIC PRINT "  Uncertainty:  +/- 0.4 °C  (+/- 0.7 °F)                    100 °C  =  212 °F"
        GRAPHIC PRINT
        GRAPHIC PRINT "  Outside the validity range the uncertainty is unknown."
        GRAPHIC PRINT
        GRAPHIC PRINT "  DewPoint.exe"; " - "; $DP_Version; " - "; $DP_Date; " - by "; $DP_Author; " - "; $DP_Disclaim
    
        GRAPHIC REDRAW
    
        DO
            SLEEP 1
            CALL GraphicGetEvents(retButton)
            IF retButton = %GW_ExtClosed THEN EXIT SUB
    
            GRAPHIC GET DC TO hDC
        LOOP UNTIL retButton = %GB_HelpReturn OR hDC = 0
    
        GraphicDestroyWindow(2)
    
    END SUB ' Print_Help
    
    '--- End of Program -----------------------------------------------------------

    Comment


    • #3
      GrButtons.inc slightly modified.


      Code:
      ' GrButtons.inc
      
      'JRF this is not working
      
          ' Graphic Buttons, TickBoxes, LineEntries, Frames and Text for PB/CC and PB/Win using only a few Win32API calls.
          ' Procedural programming methods are used with an event loop in the user program.
          ' There are two predefined functions: QueryBox for easily displaying and EntryBox for entering text.
          ' Based on Steve Rossel's initial suggestions: http://www.powerbasic.com/support/forums/Forum7/HTML/003139.html
      
          ' Compiler          :  PB/CC 6.03 + PB/Win 10.03
          ' OS                :  Win XP, Win 7
          $$GrButtons_Date    = "04 May 2012"
          $$GrButtons_Version = "V1.2"
          $$Author            = "Gert Voland"
          ' Release           :  Placed in Public Domain by the Author. Use at own risk.
      
      #COMPILER PBCC 6, PBWIN 10                  ' Usable compilers
      #DEBUG DISPLAY OFF                          ' Turn on for debugging only
      #DEBUG ERROR   OFF                          ' Turn on for debugging only
      #UNIQUE VAR ON                              ' GLOBAL and LOCAL names must be different
      
      #IF %DEF(%GWMainEquates)
          %GW_Settings = 0                        ' Use a copy of equates locally in the user program
      #ELSE
          #IF NOT %DEF(%GW_Settings)
              #IF %DEF(%PB_CC32)
                  %GW_Settings = 1                ' Default for PB/CC
              #ELSEIF %DEF(%PB_WIN32)
                  %GW_Settings = 2                ' Default for PB/Win
              #ENDIF
          #ENDIF
      
          #IF %GW_Settings = 1
          '11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
          '111 Copy from here (%GW_Settings = 1, Console style) 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
          '11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
              ' V1.2
              %GWMainEquates = 1                          ' This prevents the equates to be redefined in the .inc file (do not change!)
      
              '--- GW management-- ------------------------------------------------------------------------------------------
              %GW_UseCaption          = 0                 ' = 0 => Useful with #CONSOLE ON. It uses fixed button windows without caption, locked to the Console.
                                                          '        If used without the Console, the first button window (= BaseApplication) will have a caption.
                                                          ' = 1 => All GW use a caption string to get movable button windows, not locked to the Console/BaseApplication
              %GW_Debug               = 0                 '   1 => Print errors to  a QueryBox. 0 => Don't display them.
                                                          '   They get checked in any case; see Return codes for GraphicDestroyWindow(), GraphicGetEvents() etc.
              '--- Button and Frame settings --------------------------------------------------------------------------------
              %ButtonCorners          = 20                ' Percentage of Button corner rounding. Range: 0 to 100. The button focus stays rectangular, though.
              %FrameCorners           = 0                 ' Percentage of Frame corner rounding.
              %LECorners              = 0                 ' Percentage of LE button corner rounding.
              %DeltaFocus             = 4                 ' Number of pixels that the dotted button focus is smaller for GB and bigger for TB (Range: >= 0).
              %OffsetTBText           = 4                 ' Number of FontSize/4 that TB text is placed left of TB (Range: >= 1)
              %UseTBBullet            = 2                 ' 0 => Square boxes for TB and a cross. 1 => Round bullet (Radio button). 2 => Square and OK tick.
              %TickWidth              = 2                 ' 1 => Thin X and OK tick. 2 => thick X and OK tick. For bullets it may be > 2.
              $$LE_Continue           = ".."              ' Continuation string when LE text does not fit in box (may be "..." or "".
              %LE_CaretWidth          = 2                 ' Width of caret (cursor) in LineEntry. Range: 1 or 2
      
              '--- Some colors ----------------------------------------------------------------------------------------------
              %darker_gray            = &h404040
              %lighter_gray           = &hf0f0f0
              %lighter_cyan           = &hFFFF80
              %GW_BackColor           = %RGB_BLACK        ' Background for black Console
              %GW_DisableColor        = %RGB_DIMGRAY      ' Disable color for Button/Tickbox/LineEntry. Set to %GW_BackColor to let GB/TB "disappear" if disabled
              %GW_FocusColor          = %RGB_LIGHTGRAY    ' Color of the button focus dots. => %RGB_LIGTHGRAY for black background (standard Console etc.)
              %LE_CaretColor          = %RGB_WHITE        ' Color used for the caret in LineEntry.
      
              '--- Equates for QueryBox and EntryBox -----------------------------------------------------------------------
              %QB_w                       = 300           ' Width, can be adjusted to suit screen resolution etc.
              %QB_h                       = 150           ' Height, also adjustable
      
              %QB_FrameBorderColor        = %RGB_LIGHTGRAY
              $$QB_FrameFontName          = "Arial"       ' or "Verdana"
              %QB_FrameFontStyle          = 1             ' Bold
              %QB_FrameFontSize           = 10
              %QB_FrameTextColor          = %RGB_YELLOW
      
              %QB_ButtonBorderColor       = %RGB_LIGHTGRAY
              $$QB_ButtonFontName         = "Sans Serif"
              %QB_ButtonFontStyle         = 0             ' Normal
              %QB_ButtonFontSize          = 9
              %QB_ButtonTextColor         = %RGB_KHAKI
              %QB_ButtonBackColor         = %GW_BackColor
              %QB_ButtonHoverBackColor    = %darker_gray
              %QB_ButtonHoverTextColor    = %RGB_YELLOW
              %QB_ButtonHoverBorderColor  = %RGB_LIGHTGRAY
          '111 End of copy (%GW_Settings = 1) 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
      
          #ELSEIF %GW_Settings = 2
      
          '22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
          '222 Copy from here (%GW_Settings = 2, Windows style) 222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
          '22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
              ' V1.2
              %GWMainEquates = 1                          ' This prevents the equates to be redefined in the .inc file (do not change!)
      
              '--- GW management-- ------------------------------------------------------------------------------------------
              %GW_UseCaption          = 0                 ' = 0 => Useful with #CONSOLE ON. It uses fixed button windows without caption, locked to the Console.
                                                          '        If used without the Console, the first button window (= BaseApplication) will have a caption.
                                                          ' = 1 => All GW use a caption string to get movable button windows, not locked to the Console/BaseApplication
              %GW_Debug               = 0                 '   1 => Print errors to  a QueryBox. 0 => Don't display them.
                                                          '   They get checked in any case; see Return codes for GraphicDestroyWindow(), GraphicGetEvents() etc.
              '--- Button and Frame settings --------------------------------------------------------------------------------
              %ButtonCorners          = 20                ' Percentage of Button corner rounding. Range: 0 to 100. The button focus stays rectangular, though.
              %FrameCorners           = 0                 ' Percentage of Frame corner rounding.
              %LECorners              = 5                 ' Percentage of LE button corner rounding.
              %DeltaFocus             = 3                 ' Number of pixels that the dotted button focus is smaller for GB and bigger for TB (Range: >= 0).
              %OffsetTBText           = 4                 ' Number of FontSize/4 that TB text is placed left of TB (Range: >= 1)
              %UseTBBullet            = 0                 ' 0 => Square boxes for TB and a cross. 1 => Round bullet (Radio button). 2 => Square and OK tick.
              %TickWidth              = 2                 ' 1 => Thin X and OK tick. 2 => thick X and OK tick. For bullets it may be > 2.
              $$LE_Continue           = ".."              ' Continuation string when LE text does not fit in box (may be "..." or "".
              %LE_CaretWidth          = 2                 ' Width of caret (cursor) in LineEntry. Range: 1 or 2
      
              '--- Some colors ----------------------------------------------------------------------------------------------
              %darker_gray            = &h404040
              %lighter_gray           = &hf0f0f0
              %lighter_cyan           = &hFFFF80
              %GW_BackColor           = %RGB_WHITE
              %GW_DisableColor        = %RGB_LIGHTGRAY    ' Disable color for Button/Tickbox/LineEntry. Set to %GW_BackColor to let GB/TB "disappear" if disabled
              %GW_FocusColor          = %RGB_BLACK        ' Color of the button focus dots. => %RGB_LIGTHGRAY for black background (standard Console etc.)
              %LE_CaretColor          = %RGB_GRAY         ' Color used for the caret in LineEntry.
      
              '--- Equates for QueryBox ------------------------------------------------------------------------------------
              %QB_w                       = 300           ' Width, can be adjusted to suit screen resolution etc.
              %QB_h                       = 150           ' Height, also adjustable
      
              %QB_FrameBorderColor        = %RGB_GRAY
              $$QB_FrameFontName          = "Arial"
              %QB_FrameFontStyle          = 1             ' Bold
              %QB_FrameFontSize           = 10
              %QB_FrameTextColor          = %RGB_BLUE
      
              %QB_ButtonBorderColor       = %RGB_SLATEGRAY
              $$QB_ButtonFontName         = "Sans Serif"
              %QB_ButtonFontStyle         = 1             ' Bold
              %QB_ButtonFontSize          = 9
              %QB_ButtonTextColor         = %RGB_BLUE
              %QB_ButtonBackColor         = %RGB_WHITESMOKE
              %QB_ButtonHoverBackColor    = %RGB_LIGHTGRAY
              %QB_ButtonHoverTextColor    = %RGB_BLUE
              %QB_ButtonHoverBorderColor  = %RGB_DARKSLATEGRAY
          '222 End of copy (%GW_Settings = 2) 222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
      
          #ELSE
      
          '33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
          '333 Copy from here (%GW_Settings = 3, Free style) 333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
          '33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
              ' V1.2
              %GWMainEquates = 1                          ' This prevents the equates to be redefined in the .inc file (do not change!)
      
              '--- GW management-- ------------------------------------------------------------------------------------------
              %GW_UseCaption          = 1                 ' = 0 => Useful with #CONSOLE ON. It uses fixed button windows without caption, locked to the Console.
                                                          '        If used without the Console, the first button window (= BaseApplication) will have a caption.
                                                          ' = 1 => All GW use a caption string to get movable button windows, not locked to the Console/BaseApplication
              %GW_Debug               = 0                 '   1 => Print errors to  a QueryBox. 0 => Don't display them.
                                                          '   They get checked in any case; see Return codes for GraphicDestroyWindow(), GraphicGetEvents() etc.
              '--- Button and Frame settings --------------------------------------------------------------------------------
              %ButtonCorners          = 20                ' Percentage of Button corner rounding. Range: 0 to 100. The button focus stays rectangular, though.
              %FrameCorners           = 0                 ' Percentage of Frame corner rounding.
              %LECorners              = 10                 ' Percentage of LE button corner rounding.
              %DeltaFocus             = 3                 ' Number of pixels that the dotted button focus is smaller for GB and bigger for TB (Range: >= 0).
              %OffsetTBText           = 4                 ' Number of FontSize/4 that TB text is placed left of TB (Range: >= 1)
              %UseTBBullet            = 2                 ' 0 => Square boxes for TB and a cross. 1 => Round bullet (Radio button). 2 => Square and OK tick.
              %TickWidth              = 2                 ' 1 => Thin X and OK tick. 2 => thick X and OK tick. For bullets it may be > 2.
              $$LE_Continue           = "_"               ' Continuation string when LE text does not fit in box (may be "..." or "".
              %LE_CaretWidth          = 2                 ' Width of caret (cursor) in LineEntry. Range: 1 or 2
      
              '--- Some colors ----------------------------------------------------------------------------------------------
              %darker_gray            = &h404040
              %lighter_gray           = &hf0f0f0
              %lighter_cyan           = &hFFFF80
              %GW_BackColor           = %RGB_LIGHTYELLOW
              %GW_DisableColor        = %RGB_GRAY         ' Disable color for Button/Tickbox/LineEntry. Set to %GW_BackColor to let GB/TB "disappear" if disabled
              %GW_FocusColor          = %RGB_BLACK        ' Color of the button focus dots. => %RGB_LIGTHGRAY for black background (standard Console etc.)
              %LE_CaretColor          = %RGB_DARKSLATEGRAY' Color used for the caret in LineEntry.
      
              '--- Equates for QueryBox ------------------------------------------------------------------------------------
              %QB_w                       = 300           ' Width, can be adjusted to suit screen resolution etc.
              %QB_h                       = 150           ' Height, also adjustable
      
              %QB_FrameBorderColor        = %darker_gray
              $$QB_FrameFontName          = "Arial"
              %QB_FrameFontStyle          = 1             ' Bold
              %QB_FrameFontSize           = 10
              %QB_FrameTextColor          = %RGB_BLACK
      
              %QB_ButtonBorderColor       = %darker_gray
              $$QB_ButtonFontName         = "Sans Serif"
              %QB_ButtonFontStyle         = 0             ' Normal
              %QB_ButtonFontSize          = 9
              %QB_ButtonTextColor         = %RGB_BLACK
              %QB_ButtonBackColor         = %lighter_gray
              %QB_ButtonHoverBackColor    = %RGB_LIGHTGRAY
              %QB_ButtonHoverTextColor    = %RGB_BLUE
              %QB_ButtonHoverBorderColor  = %RGB_DIMGRAY
          '333 End of copy (%GW_Settings = 3) 333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
      
          #ENDIF
      #ENDIF
      
      
          '--- Don't change the following equates! ----------------------------------------------------------------------
          ' V1.2
          '--- Equates for QueryBox and EntryBox window IDs.
          %QB_ID                  = 999               ' Should be different from ID of any other (currently existing) GW
          %EB_ID                  = 998               ' Should be different from ID of any other (currently existing) GW
      
          '--- Equates for QueryBox button IDs. Since the QueryBox/EntryBox have an own event loop, these equates may overlap with other button IDs.
          '--- When the equates are used in user defined windows they should be checked together with the window's ID.
          %QB_Yes                 = 1
          %QB_No                  = 2
          %QB_OK                  = 3
          %QB_Cancel              = 4
          %EB_LE                  = 5
      
          '--- Button types -------------------------------------------------------------------------------------------
          %GW_Button              = 10                ' Standard graphic buttons
          %GW_TickBox             = 11                ' TickBox. Appearance can be set by %UseTBBullet.
          %GW_LineEntry           = 12                ' Graphic input line for any alpha-numerical input
      
          '--- Positions of text used for .TextAlign in GB and LE (not all used yet) ----------------------------------
          %GW_MaxTextChar         = 256               ' Maximum number of character in Button.Text (display) or in LineEntry
          %GW_None                = -1                ' No action, e.g. for drawing triangles
          %GW_Top                 = 0                 ' Default, if .TextAlign is not set
          %GW_Bottom              = 1
          %GW_Left                = 2
          %GW_Right               = 3
          %GW_Center              = 4
      
          %GW_DefaultXPrintFont   = 2                 ' Set by compiler. Used to reset XPRINT fonts: XPRINT SET %GW_GraphicDefaultFont
          %LE_NoCaret             = -99               ' No caret in .Text (i.e. not in editing mode)
      
          '--- Return codes -------------------------------------------------------------------------------------------
          %GW_NoEvent             = 0                 ' No events detected by GraphicGetEvents()
          %GW_ExtClosed           = -1                ' Button window got closed externally (Alt F4, [X] or via icon in task bar etc.)
          %GW_AllClosed           = -2                ' All windows are closed
          %GW_FatalError          = -4                ' Some mismatch occurred between IDs of requested window and the one on top
          '                                           ' or all windows are closed, but arrays are not reduced.
          '
          $$GW_NoInfo             = $$CRLF + $$CRLF + "If you do not wish to get this info" + $$CRLF + "anymore, turn %GW_Debug off."
                                                      ' Text addition for warnings UNICODE for QueryBox).
          '--- Focus flags --------------------------------------------------------------------------------------------
          %GW_Hidden              = 20                ' BaseApplication iconized, button windows hidden (no task bar icon)
          %GW_Covered             = 21                ' Some other window in focus
          %GW_OnTop               = 22                ' Button windows on top of Console/BaseApplication
          $$LE_Active             = "ActiveLE"        ' Signal an active LineEntry
      
          '--- Some Special Unicode characters (requires WSTRING/$$) ----------------------------------------------------------
          %U_ArrowUp              = 9650              ' Solid arrows
          %U_ArrowRight           = 9658
          %U_ArrowDown            = 9660
          %U_ArrowLeft            = 9668
          %U_Vertical             = 9474              ' (Single) Line draw characters
          %U_Horizontal           = 9472
          %U_DownRight            = 9484
          %U_DownLeft             = 9488
          %U_UpRight              = 9492
          %U_UpLeft               = 9496
          %U_VerticalHorizontal   = 9532
          %U_VerticalRight        = 9500
          %U_VerticalLeft         = 9508
          %U_DownHorizontal       = 9516
          %U_UpHorizontal         = 9524
          %U_CenteredBlock        = 9632              ' Solid centered block
          %U_FullBlock            = 9608              ' Solid full block
          %U_SolidCircle          = 9679              ' Centered solid circle
          %U_OpenCircle           = 9675              ' Centered open circle
      
      '-------------------------------------------------------------------------------------
      ' UDT that describes each button        'Example        ' Use for      |  Description
      '-------------------------------------------------------------------------------------
      ' V1.2
      TYPE GraphicButton                      '------------------------------------------------------------------------------------------------------------------------
          ID                  AS LONG         '= 15           ' GB + TB + LE |  Identifier for a Button/TickBox/LineEntry; Best set via equates (e.g. %GB_OK = 5).
                                                              '                 Must be positive (>0 and < 9999)! The ID must be unique for all Graphic Buttons, TickBoxes, LineEntries and Frames!
          Type                AS LONG         '= %GW_Button   ' GB + TB + LE |  Type of Button: see equates %GW_Button, %GW_TickBox, %GW_LineEntry. First TB is kept in a group.
          x                   AS LONG         '= 250          ' GB + TB + LE |  x position of Button/TickBox/LineEntry in pixels.
          y                   AS LONG         '= 100          ' GB + TB + LE |  y position of buttons in pixels. Alignment at top box level for GB, LE. TB aligned text level.
          yText               AS LONG         '= 95           ' GB      + LE |  0  => not used. y position of GB and LE also aligned at text level. TB not changed. It overrides .y!
                                                              '              |  Note: .yText has to be reset to 0 after the assignment in order for .y to be effective.
          WIDTH               AS LONG         '= 80           ' GB      + LE |  Width of Button/LineEntry in pixels.                  -- (ignore for TB)
          Height              AS LONG         '= 30           ' GB      + LE |  Height of Button/LineEntry in pixels.                 -- (ignore for TB)
          Text  AS WSTRINGZ * %GW_MaxTextChar '= "Start"      ' GB + TB + LE |  Text used on Button/TickBox/LineEntry, center aligned in GB, left to TB.
                                                              '                 Leading or trailing blanks will be kept in LE, otherwise eliminated.
                                                              '                 In LineEntry the string can be edited by the user.
                                                              '                 If kept empty (= "") and .TextAlign is set, arrow symbols will be drawn (see .TextAlign)
          nUnderChar          AS LONG         '= 1            ' GB + TB  (LE)|  The nUnderChar character gets underlined. Button gets executed when key is pressed.
                                                              '                 In LE used for caret position (= LEN(ltxt$$)).
          MaxChars            AS LONG         '= 32           '           LE |  Maximum number of characters allowed for LE (up to %GW_MaxTextChar).
                                                              '                 Additional characters are ignored.                    --  (only LE)
          TextAlign           AS LONG         '= %GW_Left     ' GB      + LE |  %GW_Left/%GW_Center/%GW_Right => Align text at left/center/right of GB or LE.
                                                              '                 %GW_Top/%GW_Bottom/%GW_Right/%GW_Left/%GW_None => Draw arrow symbols (see .FontName). -- (GB only)
          ClearText           AS LONG         '= 1            '           LE |   0  => Keep predefined text,
                                                              '                  1  => Clear text at start and set .ClearText = 0,
                                                              '                  2  => Clear text every time,
                                                              '                 -1  => Prevent any editing of text: Display-only mode. --  (only LE)
                                                              '
          HotKey            AS WSTRINGZ * 4   '= "Y" | "ESC"  ' GB + TB + LE |  Possible hot keys: A-Z | 0-9 | ESC | BS | CR | SPC in combination with
                                                              '                 # = Shift | ^ = Ctrl | @ = Left Alt | -none-. Press Shift + Ctrl to display the assigned hot keys.
                                                              '                 The case of nUderChar or HotKey characters is ignored.
          FontName          AS WSTRINGZ * 64  '= "Arial"      ' GB + TB + LE |  Font name of Button/Tickbox/LineEntry text (Default: Sans Serif).
                                                              '                 Unicode characters (such as arrows) will definitely get drawn with Courier New.
          FontSize            AS SINGLE       '= 10           ' GB + TB + LE |  Font size of Button/Tickbox/LineEntry text (Default: 8).
          FontStyle           AS LONG         '= 0            ' GB + TB + LE |  Font style of Button/Tickbox/LineEntry. LineEntry can use only 0 or 1.
                                                              '                 0 => normal, +1 => bold, +2 => italic, +4 => underline, +8 => strikeout.
          TextColor           AS LONG         '= %RGB_YELLOW  ' GB + TB + LE |  Button/TickBox/LineEntry text color.
          BackColor           AS LONG         '= %RGB_BLACK   ' GB + TB + LE |  Background color of Button/TickBox/LineEntry.
          BorderColor         AS LONG         '= %RGB_GREEN   ' GB + TB + LE |  Border Color of Button/TickBox/LineEntry.
                                                              '                 --> For LineEntry all .Hover properties are used during user entry:
          HoverFontStyle      AS LONG         '= 0            ' GB + TB + LE |  Font style when mouse hovers over it.
          HoverTextColor      AS LONG         '= %RGB_RED     ' GB + TB + LE |  Text color when mouse hovers over it.
          HoverBackColor      AS LONG         '= %RGB_GRAY    ' GB + TB + LE |  Button/TickBox/LineEntry color when mouse hovers over it.
          HoverBorderColor    AS LONG         '= %RGB_WHITE   ' GB + TB + LE |  Border and Tick color when mouse hovers over it.
          Focus               AS LONG         '= 1            ' GB + TB + LE |   1  => Button focus dots enabled (first one drawn),
                                                              '                  0  => Button focus enabled (but not drawn),
                                                              '                 -1  => Button focus dots disabled.
                                                              '                 May be changed in program by GraphicsSetFocous(ID).
          OnClick             AS DWORD        '= CODEPTR(Quit)' GB + TB + LE |  Pointer to SUB to be called when Button is activated by click/HotKey/CR (no parameters).
                                                              '                 In LineEntry it calls a user supplied routine to check and enforce editing rules.
          Disable             AS LONG         '= 0            ' GB + TB + LE |  1 => disables Button/TickBox/LineEntry (Text set to %GW_DisableColor) => Can not be selected or clicked),
                                                              '                 0 => enables Button/TickBox/LineEntry.
          TBTick              AS LONG         '= 1            '      TB  (LE)|  State of TickBoxes: 1 => on, 0 => off. May be changed in program by GraphicSetTick(ID)/GraphicClearTick(ID).
                                                              '                 In LE used for flagging valid text.
          TBGroup             AS LONG         '= 2            '      TB  (LE)|  In a Group only one TickBox may be on. If a Group has only one member the TB toggles its state when activated.
                                                              '                 In LE used for the length of text including spaces at the right end.
      END TYPE ' GraphicButton                '------------------------------------------------------------------------------------------------------------------------
      
      
      '---------------------------------------------------------------------
      ' UDT that describes each Frame/Text        'Example        ' Description
      '---------------------------------------------------------------------
      ' V1.2
      TYPE GraphicFrame                           '------------------------------------------------------------------------------------------------------------------------
          ID                  AS LONG             '= 7            '  Identifier for a Frame; best set via equates (e.g. %GF_Settings = 7).
                                                                  '  Must be positive (>0 and < 9999)! The ID must be unique for all GraphicButtons, TickBoxes and Frames
          x                   AS LONG             '= 200          '  x position of Frame/Text in pixels
          y                   AS LONG             '= 800          '  y position of Frame/Text in pixels
          Width               AS LONG             '= 300          '  Width of Frame in pixels. Width = 1 draws a vertical line
          Height              AS LONG             '= 400          '  Height of Frame in pixels. Height = 1 draws a horizontal line
          Text  AS WSTRINGZ * %GW_MaxTextChar     '= "Settings"   '  Title used on Frame, 2 * height from left corner on top line.
                                                                  '  Or used as Text entry if Frame is 0 pixels wide or high.
          TextAlign           AS LONG             '= %GW_Center   '  Align text in frame: %GW_Top    => Text appears in frame line on top left
                                                                  '                       %GW_Center => Text appears in center of frame
          FontName            AS WSTRINGZ * 64    '= "Arial"      '  Font name of Frame text
          FontSize            AS SINGLE           '= 10           '  Font size of Frame text
          FontStyle           AS LONG             '= 0            '  Font style of Frame text:
                                                                  '  0 for normal, +1 for bold, +2 for italic, +4 underline, +8 for strikeout
          TextColor           AS LONG             '= %RGB_GREEN   '  Color of Frame text
          BorderColor         AS LONG             '= %RGB_YELLOW  '  Border color of Frame
          Disable             AS LONG             '= 0            '  1 => disables Frames. Border and text color set to %GW_DisableColor.
                                                                  '  0 => enables Frames. BorderColor and TextColor are used.
      END TYPE    ' GraphicFrame                                  '  N.B.: Frames do not control the Tab order; use GraphicButton.TBGroup.
      
      
      '----------------------------------------------------------------------------
      ' UDT that keeps data of button windows (not filled by user)
      '----------------------------------------------------------------------------
      ' V1.2
      TYPE GWDataStorage                                      ' Used for allowing hierarchical button windows. The data is set when creating a window.
          ID                  AS LONG                         ' ID of the created windows
          Title               AS WSTRINGZ * 32                ' Title of GW (= Caption string). Maximum length = 32 characters.
          x                   AS LONG                         ' x coordinate of GW client origin in absolute pixels (left)
          y                   AS LONG                         ' y coordinate of GW client origin in absolute pixels (upper)
          Width               AS LONG                         ' Width of GW
          Height              AS LONG                         ' Height of GW
          iGBStart            AS LONG                         ' Lower index of buttons in current window (indexed by nGW). Upper index by UBOUND().
          iGFStart            AS LONG                         ' Lower index of frames in current window. Upper index by UBOUND().
          idFocus             AS LONG                         ' .ID of focused button in previous window
          iHighlight          AS LONG                         ' Index of highlighted button. No highlight => -1
          nextIndex           AS LONG                         ' Index of a button to be executed after clicking while editing an LE or set in GraphicSetNextID
          ContextClick        AS DWORD                        ' Remember if there was a Context click
      
      END TYPE    ' GWDataStorage
      
      
      '----------------------------------------------------------------------------
      ' UDT that keeps Console/BaseApplication and GW data (not filled by user)
      '----------------------------------------------------------------------------
      ' V0.9
      TYPE BaseApplStorage                                    ' Data is set when placing the Console/BaseApplication or moving it and x, y offset for GWs
          isCons              AS LONG                         ' Flag to indicate if Console is ON ( = 1) or OFF ( = 0)
          h                   AS DWORD                        ' Handle of Console or BaseApplication
          x                   AS LONG                         ' x coordinate
          y                   AS LONG                         ' y coordinate
          Focus               AS LONG                         ' State of focus (%GW_Hidden, %GW_Covered or %GW_OnTop)
          OverType            AS LONG                         ' 1 => The character right of the caret gets replaced by new one in LEs. 0 => Normal insertion mode.
          hFont               AS LONG                         ' Handle of constantly changing font
          hDefaultFont        AS LONG                         ' Self-defined FONT for "resetting" fonts before assigning a new one.
      END TYPE    ' BaseApplStorage
      
      
      '----------------------------------------------------------------------------
      ' UDT that keeps key and mouse data (not filled by user)
      '----------------------------------------------------------------------------
      ' V1.2
      TYPE KeyMouseStorage                                    ' Store key and mouse values. They can be checked in a GraphicGetEvents() loop.
          ascKey1             AS LONG                         ' ASC value of position 1 of the pressed sKey$$.
          ascKey2             AS LONG                         ' ASC value of position 2 of the pressed sKey$$.
                                                              ' Example: Standard keys: (65, -1) = "A", (0, 72) = Cursor Up
          KeyShift            AS INTEGER                      ' Status of Shift/Ctrl/Alt, when a key is pressed. Coded like INSHIFT.
          Button              AS LONG                         ' Number of mouse button to be checked in GWInMouse(): 1 => Left, 2 => Right, 4 => Middle
          CLICK(1 TO 4)       AS LONG                         ' Last non-zero click status of nButton:
                                                              '                          -1 => Button released (un-clicked)
                                                              '                           0 => No action detected
                                                              '                           1 => One click
                                                              '                           2 => Double click
          DOWN(1 TO 4)        AS LONG                         ' Status of selected mouse button: 0 => up, 1 => down
          x                   AS SINGLE                       ' x coordinate when mouse was clicked, else = -1. Relative coordinates in window nGW
          y                   AS SINGLE                       ' y coordinate when mouse was clicked, else = -1. Relative coordinates in window nGW
          DoubleTimer         AS LONG                         ' (Re-)triggered by any click. Ends GetDoubleClickTime after last click.
          ContextClick        AS LONG                         ' Indicator for a right mouse Context click.
                                                              '                 %GW_NoEvent => No action detected
                                                              '            %GW_ContextClick => Click in hGraphicWindow (no on a button)
                                                              '       GraphicButton(nGW).ID => Clicked on button .ID
      END TYPE    ' KeyMouseStorage
      
      '---------------------------------------------------------------------------------------
      ' Globals for communication between SUBS in GrButtons.inc (and the application routines)
      '---------------------------------------------------------------------------------------
          ' V0.8
          GLOBAL hGraphicWindow()             AS DWORD            ' Storage for button window handles
          GLOBAL nGW                          AS LONG             ' Index of current button window (starts at 0)
          GLOBAL GraphicButtons()             AS GraphicButton    ' Storage for Button/Tickbox/LineEntry data as UDT
          GLOBAL GraphicFrames()              AS GraphicFrame     ' Storage for Frame data as UDT
          GLOBAL GWData()                     AS GWDataStorage    ' Storage for hierarchical button windows as UDT
          GLOBAL BaseApplication              AS BaseApplStorage  ' Storage for the Console/BaseApplication
          GLOBAL KeyMouse                     AS KeyMouseStorage  ' Storage for last pressed key or last mouse click
      
      '----------------------------------------------------------------------------
      
      #INCLUDE ONCE "Win32Api.Inc"                            ' Only needed ONCE. Last update: 23 Apr 2010
      
      '----------------------------------------------------------------------------
      
      SUB GraphicCreateWindow(BYVAL idGW  AS LONG, _
                              BYVAL sCaption$$, _
                              BYVAL x     AS LONG, _
                              BYVAL y     AS LONG, _
                              BYVAL w     AS LONG, _
                              BYVAL h     AS LONG) COMMON
          ' V1.2
          ' Create and display the button window
      
          ' Input:    idGW:       ID of GW
          '           sCaption$$: Caption string
          '           x, y:       Coordinates (absolute pixels on screen)
          '           w, h:       Width and height in pixels
      
          ' Output:   -none-
      
          LOCAL c                 AS LONG                 ' Index of new window
          LOCAL lastFocus         AS LONG                 ' .ID of button that had the focus
          LOCAL prevFocus         AS LONG
          LOCAL w0, h0            AS LONG                 ' Size of desktop
          LOCAL lpPoint           AS POINTAPI             ' Pointer type defined in Win32Api
      
          ' Check some parameters
          DESKTOP GET SIZE TO w0, h0
          IF %GW_Debug = 1 THEN
              ' Warning if GW is outside or larger than screen
              IF x < 0 OR x >= w0 OR y < 0 OR y >= h0 OR w <= 0 OR w >= w0 OR h <= 0 OR h >= h0 THEN
                  QueryBox "*** Warning ***" + $$CRLF + $$CRLF + _
                           "Window " + STR$(idGW) + $$CRLF + "Wrong coordinates:" + $$CRLF + _
                           STR$(x) + STR$(y) + STR$(w) +STR$(h) + $$CRLF + _
                           "Desktop size: " + STR$(w0) + STR$(h0) + $$GW_NoInfo
              END IF
          END IF
      
          ' Prepare a default FONT for "resetting" fonts (could also be used for XPRINT)
          IF BaseApplication.hDefaultFont = 0 THEN FONT NEW "MS Sans Serif", 8, 0 TO BaseApplication.hDefaultFont
      
          ' Is this the first GW?
          c = UBOUND(hGraphicWindow())                                ' = -1 at beginning
          INCR c                                                      ' This becomes the new windows index
      
          ' Get button focus for previous button window
          IF c > 0 THEN
              prevFocus = GWGetCurrentFocus()
              IF prevFocus > 0 THEN lastFocus = GraphicButtons(prevFocus).ID  ' .ID of button in focus in previous window (if it exists). It still needs the last nGW
              IF GWData(c-1).iHighlight <> -1 THEN
                  CALL GraphicDrawButton(GWData(c-1).iHighlight)      ' Erase a highlighted button in the last GW
                  GWData(c-1).iHighlight = -1
              END IF
          END IF
      
          ' Set index of new window
          nGW = c                                                     ' Index of currently created window
          REDIM PRESERVE hGraphicWindow(nGW)
      
      '-----------------------------------------------------------------------------
      
          ' Check if Console is the BaseApplication ...
          #IF %DEF(%PB_CC32)                                          ' Check only for PB/CC
              IF nGW = 0 THEN                                         ' Do it only once (for the first GW with nGW = 0)
                  BaseApplication.h = CON.HANDLE                      ' If Console is turned off => .h = 0
                  IF BaseApplication.h <> 0 THEN
                      BaseApplication.isCons = 1                      ' Console present
                  ELSE
                      BaseApplication.isCons = 0                      ' Console not present
                  END IF
              END IF
          #ELSEIF %DEF(%PB_WIN32)                                     ' For PB/Win ...
              BaseApplication.isCons = 0                              ' ... Console not present in any case
          #ENDIF
      
          ' Change the GW position for Context Windows
          IF KeyMouse.ContextClick <> %GW_NoEvent THEN
              GetCursorPos(lpPoint)                                   ' API call to retrieve cursor position
              x = lpPoint.x
              y = lpPoint.y
              IF x + w >= w0 THEN x = x - w                           ' Place Context GW to right or above cursor
              IF y + h >= h0 THEN y = y - h                           ' if it would fall outside of desktop
          END IF
      
          ' Create and display the button window: Without the Console the caption is always set. For Context Window caption is always off.
          IF (%GW_UseCaption <> 0 OR (nGW = 0 AND BaseApplication.isCons = 0)) AND KeyMouse.ContextClick = %GW_NoEvent THEN
              GRAPHIC WINDOW NEW sCaption$$, x, y, w, h TO hGraphicWindow(nGW)
          ELSE
              GRAPHIC WINDOW NEW "", x, y, w, h TO hGraphicWindow(nGW)
              GRAPHIC SET CAPTION sCaption$$                          ' Add title to Task bar button for GW without caption bar
          END IF
          GRAPHIC ATTACH hGraphicWindow(nGW), 0, REDRAW
      
          ' Prevent closing a GW externally, if it is not the first one (nGW = 0) or the console
          IF nGW <> 0 OR BaseApplication.isCons = 1 THEN GRAPHIC WINDOW STABILIZE hGraphicWindow(nGW)   ' No non-BaseApplication window can be closed externally
      
          ' GRAPHIC SET OVERLAP 1                                     ' When this is used, a GRAPHIC operations have to be reworked ...
          GRAPHIC SCALE PIXELS
      
          GRAPHIC CLEAR %GW_BackColor
          IF nGW = 0 AND BaseApplication.isCons = 0 THEN BaseApplication.h = hGraphicWindow(nGW)
      
          ' Reset for use without Error 5 in GWUpdateFont
          BaseApplication.hFont = 0
      
          ' Keep the button windows always on top
          IF hGraphicWindow(nGW) <> BaseApplication.h THEN SetWindowPos(hGraphicWindow(nGW), %HWND_TOPMOST, 0, 0, 0, 0, %SWP_NOSIZE + %SWP_NOMOVE + %SWP_SHOWWINDOW) ' API call
      
          ' Find coordinates of the BaseApplication for detecting any movement
          CALL GWGetAbsClientPos(BaseApplication.h, BaseApplication.x, BaseApplication.y)
      
          ' Set the new window's dimension and data
          REDIM PRESERVE GWData(nGW)
          GWData(nGW).ID          = idGW                              ' The window's ID given by the user
          GWData(nGW).Title       = sCaption$$                        ' Stored here, no matter if it gets used for the window caption or not
          ' Find the present GW's client origin for checking the hover effect
          CALL GWGetAbsClientPos(hGraphicWindow(nGW), GWData(nGW).x, GWData(nGW).y)
          GWData(nGW).WIDTH       = w
          GWData(nGW).Height      = h
          GWData(nGW).iGBStart    = UBOUND(GraphicButtons()) + 1      ' Used for scanning through buttons and for GraphicDestroyWindow
          GWData(nGW).iGFStart    = UBOUND(GraphicFrames()) + 1       ' Used for scanning through frames and for GraphicDestroyWindow
          GWData(nGW).idFocus     = lastFocus                         ' Store it in data set of new window
          GWData(nGW).iHighlight  = -1                                ' No button highlighted yet
          GWData(nGW).nextIndex   = %GW_FatalError                    ' No button selected for next action
          GWData(nGW).ContextClick = KeyMouse.ContextClick            ' Store the Context click data (Window or button ID)
      
      END SUB ' GraphicCreateWindow
      
      '----------------------------------------------------------------------------
      
      FUNCTION GraphicDestroyWindow(BYVAL reqID AS LONG) AS LONG
          ' V1.2
          ' Destroy the button window with ID reqID.
          ' Close the window and reduce the dimension of arrays and attach the next lower GW.
          ' If it was the last GW all arrays get erased.
          ' %GW_Debug can be set = 1 during program development. When the program structure is stable set it = 0.
      
          ' Input:    reqID    =>  ID of GW to be destroyed
          ' Output:   Function = ID of the new window in case of success, else return an error code:
          '                    = %GW_AllClosed   =>  all windows are closed (also: nGW = -1)
          '                    = %GW_FatalError  =>  requested reqID <> current ID (no action or
          '                                          all GW closed but arrays not reduced/de-allocated)
      
          LOCAL lastFocusID       AS LONG                 ' ID of the button in focus one level below the one that gets destroyed
          LOCAL currentID         AS LONG                 ' ID of the current GW
      
          ' Get the ID of the top GW (index nGW) and DC handle
          currentID = GraphicGetWindowId()
      
          SELECT CASE LONG currentID
      
              ' Check if a valid GW ID exists
              CASE %GW_AllClosed
                  IF %GW_Debug <> 0 THEN
                      QueryBox("*** Error ***" + $$CRLF + $$CRLF + _
                                "Requested GW =" + STR$(reqID) + $$CRLF + $$CRLF + _
                                "There is no GW data set" + $$CRLF + _
                                "to destroy anymore.")
      
                  END IF
                  FUNCTION = %GW_AllClosed
      
              ' A wrong GW was requested
              CASE <> reqID
                  IF %GW_Debug <> 0 THEN
                      QueryBox("*** Error ***" + $$CRLF + $$CRLF + _
                                "Attempt to destroy wrong window:" + $$CRLF + _
                                "-  Requested GW =" + STR$(reqID) + $$CRLF + _
                                "-  Current GW =" + STR$(currentID) + $$CRLF + $$CRLF + _
                                "Neither GW gets destroyed.")
      
                      GRAPHIC ATTACH hGraphicWindow(nGW), 0, REDRAW   ' Put top level window back into focus!
                      GRAPHIC SET FOCUS
                  END IF
                  FUNCTION = %GW_FatalError                           ' No action. Error!
      
              ' Normal closing of windows
              CASE reqID
      
                  ' Close the window
                  GRAPHIC WINDOW END
      
                  ' De-allocate the arrays for windows and buttons
                  IF nGW = 0 THEN                                     ' This is the last button window
      
                      ERASE GraphicButtons()                          ' Erase the arrays for all buttons, frames and windows
                      ERASE GraphicFrames()
                      ERASE hGraphicWindow()
                      ERASE GWData()
                      nGW = UBOUND(GraphicButtons())                  ' = -1
      
                      FUNCTION = %GW_AllClosed                        ' Successful. It was the last window
      
                  ELSE                                                ' At least one GW left
      
                      lastFocusID = GWData(nGW).idFocus
                      IF GWData(nGW).iGBStart > 0 THEN
                          REDIM PRESERVE GraphicButtons(GWData(nGW).iGBStart - 1) ' Reduce the arrays to dimensions of next lower window
                      END IF
                      IF GWData(nGW).iGFStart > 0 THEN
                          REDIM PRESERVE GraphicFrames(GWData(nGW).iGFStart - 1)
                      END IF
                      GWData(nGW).ContextClick = %GW_NoEvent          ' Remove Context click information (if there was any)
      
                      DECR nGW                                        ' Reduce index of window handles
                      REDIM PRESERVE GWData(nGW)                      ' Reduce array dimensions
                      REDIM PRESERVE hGraphicWindow(nGW)
                      CALL GraphicSetFocus(lastFocusID)               ' Reinstall previous button focus
      
                      IF IsWindow(hGraphicWindow(nGW)) = 0 THEN EXIT FUNCTION
                      GRAPHIC ATTACH hGraphicWindow(nGW), 0, REDRAW   ' Keep new top one attached
                      GRAPHIC SET FOCUS
      
                      FUNCTION = GraphicGetWindowId()                 ' Successful. Return ID of new GW on top.
      
                  END IF
          END SELECT
      
          ' Key strokes collected in a GW <> nGW get flushed
          GRAPHIC INPUT FLUSH
      
      END FUNCTION ' GraphicDestroyWindow
      
      '----------------------------------------------------------------------------
      
      FUNCTION GraphicGetWindowId() AS LONG
          ' V1.0
          ' Return the window's ID, which was set when creating the window (hopefully by using uniquely defined equates!)
      
          ' Input:    -none-
          ' Output:    Function > 0              =>  .ID of window currently in focus (= set to foreground)
          '                     = %GW_AllClosed  =>  all button windows closed (nGW = -1 or nGW = 0 but not a window)
      
          IF UBOUND(hGraphicWindow()) = -1 THEN                       ' No GWs created yet
              FUNCTION = %GW_AllClosed
          ELSEIF IsWindow(hGraphicWindow(nGW)) <> 0 THEN
              FUNCTION = GWData(nGW).ID
          ELSE
              FUNCTION = %GW_AllClosed
          END IF
      
      END FUNCTION    ' GraphicGetWindowId
      
      '----------------------------------------------------------------------------'
      
      SUB GraphicAddButton(BYVAL btn AS GraphicButton)
          ' V1.0
          ' Add a Button/TickBox/LineEntry to the button window
      
          ' Input:    btn  =>  Button/TickBox/LineEntry information through UDT
          ' Output:   -none-
      
          LOCAL w, h      AS LONG                         ' Width, height
          LOCAL c, i      AS LONG                         ' Indices
      
          ' Add the Button/TickBox/LineEntry to the internal array of buttons
          c = UBOUND(GraphicButtons())                                                        ' = -1 at beginning
          INCR c
          REDIM PRESERVE GraphicButtons(c) AS GLOBAL GraphicButton
          TYPE SET GraphicButtons(c) = btn                                                    ' Transfer of Button UDT
      
          ' Set as default if no .Type was specified
          IF GraphicButtons(c).TYPE = 0 THEN GraphicButtons(c).TYPE = %GW_Button
      
          ' Get the text width and height to be displayed on the Button/TickBox/LineEntry
          GWUpdateFont(GraphicButtons(c).FontName, GraphicButtons(c).FontSize, 1)             ' Use bold for w, h to be on the safe side
      
          ' Get size for bold text
          IF GraphicButtons(c).TYPE = %GW_LineEntry THEN
              GRAPHIC TEXT SIZE TRIM$("W") TO w, h                                            ' Calculate width of LE as MaxChars * "W"
              w = (GraphicButtons(c).MaxChars + 1) * w                                        ' one more for caret
          ELSE
              GRAPHIC TEXT SIZE TRIM$(GraphicButtons(c).TEXT) TO w, h
          END IF
      
          ' Now set it back as requested
          GWUpdateFont(GraphicButtons(c).FontName, GraphicButtons(c).FontSize, GraphicButtons(c).FontStyle)
      
          SELECT CASE LONG GraphicButtons(c).TYPE
              CASE %GW_Button
                  ' If no width or height specified then use text size plus a rim
                  GraphicButtons(c).WIDTH  = MAX(w + h, GraphicButtons(c).WIDTH)              ' At least w + 2* h/2
                  GraphicButtons(c).Height = MAX(h + h/4, GraphicButtons(c).Height)           ' At least h + 2* h/8
              CASE %GW_TickBox
                  ' If it's a TickBox then use the text width and height of text
                  GraphicButtons(c).WIDTH  = w
                  GraphicButtons(c).Height = h                                                ' Size of TickBox = font height
              CASE %GW_LineEntry
                  ' If it's a LineEntry calculate the width and height
                  IF GraphicButtons(c).WIDTH = 0 THEN GraphicButtons(c).WIDTH = w + h\2       ' Set to fit MaxChar
      
                  ' Limit the maximum no. of characters to 64 (see UDT GraphicButton). Use all if negative or zero.
                  IF GraphicButtons(c).MaxChars < 1 OR GraphicButtons(c).MaxChars > %GW_MaxTextChar THEN GraphicButtons(c).MaxChars = %GW_MaxTextChar
      
                  ' Cut length to MaxChars
                  IF LEN(GraphicButtons(c).TEXT) > GraphicButtons(c).MaxChars THEN
                      GraphicButtons(c).TEXT = LEFT$(GraphicButtons(c).TEXT, GraphicButtons(c).MaxChars)
                  END IF
      
                  ' Store length of text in .TBGroup for LE
                  GraphicButtons(c).TBGroup = LEN(RTRIM$(GraphicButtons(c).TEXT))             ' No trailing blanks allowed when creating the LE
      
                  ' Preset the caret position
                  GraphicButtons(c).nUnderChar = %LE_NoCaret
          END SELECT
      
          ' Then check focus in previous buttons. If one exists, set the new one to 0
          IF GraphicButtons(c).FOCUS = 1 THEN
              FOR i = GWData(nGW).iGBStart TO c-1
                  IF GraphicButtons(i).FOCUS = 1 THEN GraphicButtons(c).FOCUS = 0
              NEXT i
          END IF
      
          ' Check focus in previous tick boxes. If one exists, set the new one to 0
          IF GraphicButtons(c).TBTick = 1 AND GraphicButtons(c).TYPE = %GW_TickBox THEN
              FOR i = GWData(nGW).iGBStart TO c-1                                             ' Check for existing Ticks in same group
                  IF GraphicButtons(i).TBGroup <> GraphicButtons(c).TBGroup THEN ITERATE FOR
                  ' If any earlier one exist, cancel the new one
                  IF GraphicButtons(i).TBTick = 1 THEN GraphicButtons(c).TBTick = 0
              NEXT i
          END IF
      
          ' Preset .HotKey
          IF ASC(GraphicButtons(c).HotKey, 1) = 0 THEN GraphicButtons(c).HotKey = ""
      
          ' Draw the new Button/TickBox/LineEntry on the button window
          CALL GraphicDrawButton(c)
      
      END SUB ' GraphicAddButton
      
      '----------------------------------------------------------------------------
      
      SUB GraphicAddFrame(BYVAL FRAME AS GraphicFrame)
          ' V0.4
          ' Add a Frame to the button window
      
          ' Input:    Frame  =>  Frame information through UDT
          ' Output:   -none-
      
          LOCAL c AS LONG                                     ' Index
      
          ' Add the Frame to the internal array of Frames
          c = UBOUND(GraphicFrames())
          INCR c
          REDIM PRESERVE GraphicFrames(c) AS GLOBAL GraphicFrame
          TYPE SET GraphicFrames(c) = FRAME
      
          ' Draw the new Frame on the button window
          CALL GraphicDrawFrame(c)
      
      END SUB ' GraphicAddFrame
      
      '----------------------------------------------------------------------------
      
      FUNCTION GraphicGetIndex(BYVAL nID AS LONG) AS LONG
          ' V0.8
          ' Return the index of the Button/Tickbox/LineEntry with given ID
          ' End the program, if %GW_Debug = 1 and an index cannot be found
      
          ' Input:    nID      =  .ID of Button/Tickbox/LineEntry
          ' Output:   FUNCTION =  Index of Button/Tickbox/LineEntry (determined by the calling sequence of GraphicAddButton())
          '                    =  %GW_FatalError, if ID, GW do not exist
      
          LOCAL j     AS LONG                     ' Index
      
          FUNCTION = %GW_FatalError                                       ' Return value for non-existing ID
      
          IF nID = 0 OR nGW < 0 THEN EXIT FUNCTION                        ' Leave on invalid ID or when GW got closed
      
          IF UBOUND(GraphicButtons()) >= GWData(nGW).iGBStart THEN
              FOR j = GWData(nGW).iGBStart TO UBOUND(GraphicButtons())    ' Check Buttons
                  IF GraphicButtons(j).ID <> nID THEN ITERATE FOR
                  FUNCTION = j
                  EXIT FUNCTION
              NEXT j
          END IF
      
          ' After Buttons have been tried, do Frames
          IF UBOUND(GraphicFrames()) >= GWData(nGW).iGFStart THEN
              FOR j = 0 TO UBOUND(GraphicFrames())                        ' Check Frames --- also find them in other GWs
                  IF GraphicFrames(j).ID <> nID THEN ITERATE FOR
                  FUNCTION = j
                  EXIT FUNCTION
              NEXT j
          END IF
      
          IF %GW_Debug = 1 THEN
              QueryBox "*** Error ***" + $$CRLF + $$CRLF + _
                        "No index found for ID =" + STR$(nID) + $$CRLF + $$CRLF + _
                        "Ending the program now."
              ExitProcess(1)
          END IF
      
      END FUNCTION ' GraphicGetIndex
      
      '----------------------------------------------------------------------------
      
      FUNCTION GWGetHandle(BYVAL nID AS LONG) AS LONG
          ' V1.2
          ' Return the handle of the Graphic Window with given ID
          ' End the program, if %GW_Debug = 1 and an index cannot be found
      
          ' Input:    nID      =  .ID of Graphic Window
          ' Output:   FUNCTION =  Handle of GW
          '                    =  %GW_FatalError, if ID, GW do not exist
      
          LOCAL j     AS LONG                     ' Index
      
          FUNCTION = %GW_FatalError                                   ' Return value for non-existing ID
      
          IF nID = 0 OR nGW < 0 THEN EXIT FUNCTION                    ' Leave on invalid ID or when GW got closed
      
          FOR j = 0 TO nGW
              IF GWData(j).ID <> nID THEN ITERATE FOR
              FUNCTION = hGraphicWindow(j)
              EXIT FUNCTION
          NEXT j
      
          IF %GW_Debug = 1 THEN
              QueryBox "*** Error ***" + $$CRLF + $$CRLF + _
                        "No handle found for GW ID =" + STR$(nID) + $$CRLF + $$CRLF + _
                        "Ending the program now."
              ExitProcess(1)
          END IF
      
      END FUNCTION ' GWGetHandle
      
      '----------------------------------------------------------------------------
      
      FUNCTION GWGetCurrentFocus() AS LONG
          ' V0.6
          ' Return the index of the current button with focus
      
          ' Input:    -none-
          ' Output:   FUNCTION = Index of Button/TickBoxTLineEntry which has the focus (= -1 when none found)
      
          LOCAL j AS LONG                                 ' Index
      
          FOR j = GWData(nGW).iGBStart TO UBOUND(GraphicButtons())
              IF GraphicButtons(j).FOCUS = 1 THEN
                  FUNCTION = j                                        ' Return the current one
                  EXIT FUNCTION
              END IF
          NEXT j
          FUNCTION = -1                                               ' No button focus found
      
      END FUNCTION ' GWGetCurrentFocus
      
      '----------------------------------------------------------------------------
      
      FUNCTION GraphicGetFocus(BYVAL nID AS LONG) AS LONG
          ' V0.8
          ' Return the .Focus value of the specified button
      
          ' Input:    nID  =>  .ID of Button/Tickbox/LineEntry
          ' Output:   FUNCTION = .Focus value
          '                    = %GW_FatalError, if an index cannot be found
      
          LOCAL i         AS LONG
      
          i = GraphicGetIndex(nID)
          IF i = %GW_FatalError THEN
              FUNCTION = i
          ELSE
              FUNCTION = GraphicButtons(i).FOCUS
          END IF
      
      END FUNCTION ' GraphicGetFocus
      
      '----------------------------------------------------------------------------
      
      SUB GraphicSetFocus(BYVAL nID AS LONG)
          ' V0.8
          ' Erase the button focus from Button/Tickbox/LineEntry icf. Draw a focus on Button/Tickbox/LineEntry with nID
      
          ' Input:    nID  =>  .ID of Button/Tickbox/LineEntry
          ' Output:   -none-
      
          LOCAL i     AS LONG                     ' Index
      
          i = GraphicGetIndex(nID)
          IF i = %GW_FatalError THEN EXIT SUB
      
          CALL GraphicMoveFocus(i, GWGetCurrentFocus)
      
      END SUB ' GraphicSetFocus
      
      '----------------------------------------------------------------------------
      
      SUB GraphicButtonsEnable(BYVAL nID AS LONG)
          ' V0.8
          ' Enable Button/Tickbox/LineEntry by nID and redraw with the defined colors
      
          ' Input:    nID  =>  .ID of Button/Tickbox/LineEntry. -1 => enables all buttons
          ' Output:   -none-
      
          LOCAL j     AS LONG                     ' Index
      
          IF nID = -1 THEN
              FOR j = GWData(nGW).iGBStart TO UBOUND(GraphicButtons())
                  GraphicButtons(j).Disable = 0
                  CALL GraphicDrawButton(j)
              NEXT j
          ELSE
              j = GraphicGetIndex(nID)
              IF j = %GW_FatalError THEN EXIT SUB
      
              GraphicButtons(j).Disable = 0
              CALL GraphicDrawButton(j)
          END IF
      
      END SUB ' GraphicButtonsEnable
      
      '----------------------------------------------------------------------------'
      
      SUB GraphicButtonsDisable(BYVAL nID AS LONG)
          ' V0.8
          ' Disable Button/Tickbox/LineEntry by nID
          ' For GB, TB and LE the edge, text and ticks are drawn with %GW_DisableColor. The background color stays unchanged.
      
          ' Input:    nID  =>  .ID of Button/Tickbox/LineEntry. -1 => disables all buttons
          ' Output:   -none-
      
          LOCAL j     AS LONG                     ' Index
      
          IF nID = -1 THEN
              FOR j = GWData(nGW).iGBStart TO UBOUND(GraphicButtons())
                  GraphicButtons(j).Disable = 1
                  CALL GraphicDrawButton(j)
              NEXT j
          ELSE
              j = GraphicGetIndex(nID)
              IF j = %GW_FatalError THEN EXIT SUB
      
              GraphicButtons(j).Disable = 1
              CALL GraphicDrawButton(j)
          END IF
      
      END SUB ' GraphicButtonsDisable
      
      '----------------------------------------------------------------------------
      
      SUB GraphicFramesEnable(BYVAL nID AS LONG)
          ' V0.8
          ' Enable Frames by nID and redraw with the defined colors
      
          ' Input:    nID  =>  .ID of Frame. -1 => enables all Frames
          ' Output:   -none-
      
          LOCAL j     AS LONG                     ' Index
      
          IF nID = -1 THEN
              FOR j = GWData(nGW).iGFStart TO UBOUND(GraphicFrames())
                  GraphicFrames(j).Disable = 0
                  CALL GraphicDrawFrame(j)
              NEXT j
          ELSE
              j = GraphicGetIndex(nID)
              IF j = %GW_FatalError THEN EXIT SUB
      
              GraphicFrames(j).Disable = 0
              CALL GraphicDrawFrame(j)
          END IF
      
      END SUB ' GraphicFramesEnable
      
      '----------------------------------------------------------------------------'
      
      SUB GraphicFramesDisable(BYVAL nID AS LONG)
          ' V0.8
          ' Disable Frames by nID
          ' For GF the edge and text are drawn with %GW_DisableColor. The background color stays unchanged.
      
          ' Input:    nID  =>  .ID of Frame. -1 => disables all Frames
          ' Output:   -none-
      
          LOCAL j     AS LONG                     ' Index
      
          IF nID = -1 THEN
              FOR j = GWData(nGW).iGFStart TO UBOUND(GraphicFrames())
                  GraphicFrames(j).Disable = 1
                  CALL GraphicDrawFrame(j)
              NEXT j
          ELSE
              j = GraphicGetIndex(nID)
              IF j = %GW_FatalError THEN EXIT SUB
      
              GraphicFrames(j).Disable = 1
              CALL GraphicDrawFrame(j)
          END IF
      
      END SUB ' GraphicFramesDisable
      
      '----------------------------------------------------------------------------'
      
      SUB GraphicSetTick(BYVAL nID AS LONG)
          ' V0.9
          ' Set only one tick in a group
      
          ' Input:     nID  =>  .ID of TickBox
          ' Output:    -none-
      
          LOCAL inew, j   AS LONG                 ' Indices
          LOCAL nGroup    AS LONG                 ' Group number
          LOCAL jact      AS LONG                 ' Index
      
          inew = GraphicGetIndex(nID)                                 ' Get index of this ID
          IF inew = %GW_FatalError THEN EXIT SUB
          nGroup = GraphicButtons(inew).TBGroup                       ' Get new group value
          jact = -1                                                   ' Initialize to none
      
          FOR j = GWData(nGW).iGBStart TO UBOUND(GraphicButtons())
              IF GraphicButtons(j).TBGroup <> nGroup OR GraphicButtons(j).TYPE <> %GW_TickBox THEN ITERATE FOR ' Skip members of any other group
              IF GraphicButtons(j).TBTick = 1 THEN jact = j           ' Presently active
          NEXT j
      
          IF inew <> jact THEN                                        ' Don't do anything when the active TickBox is pressed again
              IF jact > -1 THEN
                  GraphicButtons(jact).TBTick = 0                     ' Reset (= erase) this one
                  CALL GraphicDrawButton(jact)
              END IF
              GraphicButtons(inew).TBTick = 1                         ' Set this one
              CALL TBDrawTick(inew)                                   ' Draw tick
              GRAPHIC REDRAW
          END IF
      
      END SUB ' GraphicSetTick
      
      '----------------------------------------------------------------------------
      
      SUB GraphicClearTick(BYVAL nID AS LONG)
          ' V0.8
          ' Clear the tick if a single-TickBox group tick was set
          ' No effect on  multi-TickBox groups  (use GraphicSetTick to move a tick)
      
          ' Input:    nID  =>  .ID of TickBox to be cleared
          ' Output:   -none-
      
          LOCAL nGroup    AS LONG                 ' Group number
          LOCAL iact      AS LONG                 ' Index
          LOCAL j, n      AS LONG                 ' Indices
      
          iact = GraphicGetIndex(nID)                                 ' Get index of this ID
          IF iact = %GW_FatalError THEN EXIT SUB
          nGroup = GraphicButtons(iact).TBGroup                       ' Get group value
      
          FOR j = GWData(nGW).iGBStart TO UBOUND(GraphicButtons())
              IF GraphicButtons(j).TBGroup <> nGroup OR GraphicButtons(j).TYPE <> %GW_TickBox THEN ITERATE FOR ' Skip members of any other group
              INCR n                                                  ' Count members of group
          NEXT j
      
          IF n = 1 AND GraphicButtons(iact).TBTick = 1 THEN           ' If only one member clear the tick
              GraphicButtons(iact).TBTick = 0
              CALL GraphicDrawButton(iact)
          END IF
      
      END SUB ' GraphicClearTick
      
      '----------------------------------------------------------------------------
      
      FUNCTION GraphicGetTick(BYVAL nID AS LONG) AS LONG
          ' V0.8
          ' Return the tick state of a TickBox by ID
      
          ' Input:    nID  =>  .ID of TickBox
          ' Output:   FUNCTION = 1  =>  TickBox is on
          '                    = 0  =>  TickBox is off
          '                    = %GW_FatalError, if index cannot be found
      
          LOCAL i         AS LONG                 ' Index
      
          i = GraphicGetIndex(nID)
          IF i = %GW_FatalError THEN
              FUNCTION = i
          ELSE
              FUNCTION = GraphicButtons(i).TBTick
          END IF
      
      END FUNCTION    ' GraphicGetTick
      
      '----------------------------------------------------------------------------
      
      SUB GraphicDrawButton(BYVAL i AS LONG, OPT BYVAL useHotKey AS LONG)
          ' V1.2
          ' Draw a Button/Tickbox/LineEntry using its normal colors unless disabled
      
          ' Input:    i          =>  Index of Button/Tickbox/LineEntry to draw
          '           useHotKey  =>  Optional. Print .HotKey string instead of .Text
          ' Output:   -none-
      
          LOCAL w, h                  AS LONG             ' Text width, height
          LOCAL woff, hoff            AS LONG             ' Start of text from .x/.y
          LOCAL w0, h0                AS LONG             ' Button width, height
          LOCAL t$$                                       ' Button/TickBox text trimmed to its length
          LOCAL arr$$                                     ' Unicode arrows
          LOCAL x0, x1, x2, y0, y1    AS LONG             ' Coordinates
          LOCAL edgeColor             AS LONG             ' Color of edge (rim)
          LOCAL delta                 AS LONG             ' Delta for focus
          STATIC maxWidth             AS LONG             ' Maximum width when .Text of a TB changes. It is used to erase an old focus
      
          ' Check, if index i is valid
          IF i < 0 OR i > UBOUND(GraphicButtons()) THEN EXIT SUB
      
          ' Attach the top button window
          IF IsWindow(hGraphicWindow(nGW)) = 0 THEN EXIT SUB
          GRAPHIC ATTACH hGraphicWindow(nGW), 0, REDRAW
          GRAPHIC STYLE 0                                             ' Solid edge
      
          ' In case .Text has been changed
          IF GraphicButtons(i).TYPE = %GW_TickBox THEN
              GWUpdateFont(GraphicButtons(i).FontName, GraphicButtons(i).FontSize, 1)     ' Use bold for w, h to be on the safe side
              GraphicButtons(i).Width = GRAPHIC(TEXT.SIZE.X, GraphicButtons(i).TEXT)      ' Do not recalculate .Height here!!!
              ' Set width to largest string so far ...
              maxWidth = MAX&(maxWidth, GraphicButtons(i).Width)                          ' Remember the largest value for erasing a focus
              GWUpdateFont(GraphicButtons(i).FontName, GraphicButtons(i).FontSize, GraphicButtons(i).FontStyle)   ' Set it back
          END IF
      
          ' Prepare text parameters. In case .Text has been changed and check .yText
          GWUpdateFont(GraphicButtons(i).FontName, GraphicButtons(i).FontSize, 1)         ' Use bold for w, h to be on the safe side
          SELECT CASE AS LONG GraphicButtons(i).TYPE
      
              CASE %GW_TickBox
                  GraphicButtons(i).Width = GRAPHIC(TEXT.SIZE.X, GraphicButtons(i).TEXT)  ' Do not recalculate .Height here!!!
                  ' Set width to largest string so far ...
                  maxWidth = MAX&(maxWidth, GraphicButtons(i).Width)                      ' Remember the largest value for erasing a focus
                  ' Overwrite .y if .yText is used. TB is already aligned at text level.
                  IF GraphicButtons(i).yText > 0 THEN GraphicButtons(i).y = GraphicButtons(i).yText
      
              CASE ELSE
                  ' Overwrite .y if .yText is used. This causes the buttons to be aligned at text level!
                  ' Note: .yText has to be reset to 0 after the assignment!
                  IF GraphicButtons(i).yText > 0 THEN
                      GraphicButtons(i).y = GraphicButtons(i).yText - (GraphicButtons(i).Height - GRAPHIC(TEXT.SIZE.Y, "Q")) / 2
                  END IF
          END SELECT
          GWUpdateFont(GraphicButtons(i).FontName, GraphicButtons(i).FontSize, GraphicButtons(i).FontStyle)   ' Set it back
      
          x0 = GraphicButtons(i).x
          y0 = GraphicButtons(i).y
          w0 = GraphicButtons(i).WIDTH
          h0 = GraphicButtons(i).Height
      
          x1 = x0 + w0
          x2 = x0 + h0
          y1 = y0 + h0
      
          ' Erase possibly existing text and selection dots (button focus)
          delta = MAX(0, %DeltaFocus)                             ' Do not use negative values of %DeltaFocus
          SELECT CASE LONG GraphicButtons(i).TYPE
              CASE %GW_TickBox
                  ' Erase as if the width was maxWidth!
                  GRAPHIC BOX (x0 - delta - 1, y0 - delta - 1) - _
                              (x0 + maxWidth + (1 + MAX(1, %OffsetTBText)/4) * h0 + delta + 1, y1 + delta + 1), _
                              0, %GW_BackColor, %GW_BackColor, 0
          END SELECT
      
          ' Set button edge color according to disable state, unless it is invisible
          IF GraphicButtons(i).Disable = 1 AND GraphicButtons(i).BorderColor <> GraphicButtons(i).BackColor THEN
              edgeColor = %GW_DisableColor
          ELSE
              edgeColor = GraphicButtons(i).BorderColor
          END IF
      
          ' Draw button box
          SELECT CASE LONG GraphicButtons(i).TYPE
              CASE %GW_Button                                         ' GB
                  GRAPHIC BOX (x0, y0) - (x1, y1), %ButtonCorners, edgeColor, GraphicButtons(i).BackColor
              CASE %GW_TickBox                                        ' TB
                  IF %UseTBBullet <> 1 THEN
                      GRAPHIC BOX (x0, y0) - (x2, y1), 0, edgeColor, GraphicButtons(i).BackColor
                  ELSE
                      GRAPHIC ELLIPSE (x0, y0) - (x2, y1), edgeColor, GraphicButtons(i).BackColor
                  END IF
                  IF GraphicButtons(i).TBTick = 1 THEN CALL TBDrawTick(i)
              CASE %GW_LineEntry                                      ' LE:  No rounded corners for LE
                  GRAPHIC BOX (x0, y0) - (x1, y1), %LECorners, edgeColor, GraphicButtons(i).BackColor
          END SELECT
      
          ' Determine size of text again (if things have changed on the fly)
          GWUpdateFont(GraphicButtons(i).FontName, GraphicButtons(i).FontSize, GraphicButtons(i).FontStyle)
      
          ' Load either .Text or .HotKey, depending on useHotKey
          IF useHotKey = 0 THEN
              t$$ = TRIM$(GraphicButtons(i).TEXT)
          ELSE
              t$$ = TRIM$(GraphicButtons(i).HotKey)
              IF t$$ = "" THEN t$$ ="---"
          END IF
          GRAPHIC TEXT SIZE t$$ TO w, h
      
          ' Load Unicode arrows if .TEXT is empty
          IF GraphicButtons(i).TYPE = %GW_Button AND t$$ = "" AND useHotKey = 0 THEN
              SELECT CASE LONG GraphicButtons(i).TextAlign
                  CASE %GW_Top
                      arr$$ = CHR$$(%U_ArrowUp)
                  CASE %GW_Right
                      arr$$ = CHR$$(%U_ArrowRight)
                  CASE %GW_Bottom
                      arr$$ = CHR$$(%U_ArrowDown)
                  CASE %GW_Left
                      arr$$ = CHR$$(%U_ArrowLeft)
              END SELECT
              GRAPHIC TEXT SIZE arr$$ TO w, h
          END IF
      
          ' Set text color according to disable state
          IF GraphicButtons(i).Disable = 1 AND GraphicButtons(i).TextColor <> GraphicButtons(i).BackColor THEN
              GRAPHIC COLOR %GW_DisableColor, -2
          ELSE
              GRAPHIC COLOR GraphicButtons(i).TextColor, -2           ' -2 does not use an own background color for text
          END IF
      
          ' Now print the text into/next to the button box
          ' When useHotKey = 1 then show them in any case
          IF GraphicButtons(i).TYPE = %GW_LineEntry AND useHotKey = 0 THEN
              CALL LEPrintText(i)                                     ' Special text placement for LE (Font is set)
          ELSE
              ' Set Text position
              IF GraphicButtons(i).TYPE = %GW_TickBox THEN            ' HotKeys for TB
                  ' Place TB text twice the height to the left of the box
                  woff = (1 + MAX(1, %OffsetTBText)/4) * h
              ELSE                                                    ' HotKeys for GB, LE
                  SELECT CASE GraphicButtons(i).TextAlign
                      CASE %GW_Left
                          woff = GRAPHIC(TEXT.SIZE.X, "W")
                      CASE %GW_Right
                          woff = w0 - w - GRAPHIC(TEXT.SIZE.X, "W")
                      CASE ELSE                                       ' Default
                          ' Place text in middle of button
                          woff = (w0 - w)\2
                  END SELECT
              END IF
              hoff = (GraphicButtons(i).Height - h)\2
              GRAPHIC SET POS (GraphicButtons(i).x + woff, GraphicButtons(i).y + hoff)
      
              ' Print an underlined hot key character when it was specified. Text and nUnderChar can be changed on the fly!
              IF GraphicButtons(i).nUnderChar > 0 AND GraphicButtons(i).nUnderChar <= LEN(TRIM$(GraphicButtons(i).TEXT)) AND useHotKey = 0 THEN
      
                  ' Print left part of text without underline
                  GRAPHIC PRINT LEFT$(t, GraphicButtons(i).nUnderChar-1);
      
                  ' Print underlined character (+/- 4)
                  IF BIT(GraphicButtons(i).FontStyle, 2) = 0 THEN
                      GWUpdateFont(GraphicButtons(i).FontName, GraphicButtons(i).FontSize, GraphicButtons(i).FontStyle + 4)
                  ELSE
                      GWUpdateFont(GraphicButtons(i).FontName, GraphicButtons(i).FontSize, GraphicButtons(i).FontStyle - 4)
                  END IF
                  GRAPHIC PRINT MID$(t, GraphicButtons(i).nUnderChar, 1);
      
                  ' Print right part of text without underline
                  GWUpdateFont(GraphicButtons(i).FontName, GraphicButtons(i).FontSize, GraphicButtons(i).FontStyle)
                  GRAPHIC PRINT RIGHT$(t, LEN(t) - GraphicButtons(i).nUnderChar)
              ELSE
                  IF GraphicButtons(i).TYPE = %GW_Button AND t$$ = "" AND useHotKey = 0 THEN
                      GRAPHIC PRINT arr$$                             ' Print Unicode arrows
                  ELSE
                      GRAPHIC PRINT t$$                               ' Print without HotKey
                  END IF
              END IF
          END IF
      
          ' Draw button focus only for non-LE
          IF GraphicButtons(i).FOCUS = 1 AND NOT _
              (GraphicButtons(i).TYPE = %GW_LineEntry AND GraphicButtons(i).ClearText = -1) THEN CALL GraphicDrawFocus(i)
          GRAPHIC REDRAW
      
          ' Do not increase count of hFont => kill it again
          GWKillFont
      
      END SUB ' GraphicDrawButton
      
      '----------------------------------------------------------------------------'
      
      SUB GraphicHighlightButton(BYVAL i AS LONG)
          ' V1.2
          ' Draw a Button/Tickbox/LineEntry using its HoverBackColor and HoverTextColor
          '
          ' Input:    i  =>  Index of Button/Tickbox/LineEntry to draw
          ' Output:   -none-
      
          LOCAL w, h                  AS LONG             ' Text width, height
          LOCAL woff, hoff            AS LONG             ' Start of text from .x/.y
          LOCAL w0, h0                AS LONG             ' Button width, height
          LOCAL t$$                                       ' Text string
          LOCAL arr$$                                     ' Unicode arrows
          LOCAL x0, x1, x2, y0, y1    AS LONG             ' Coordinates
          LOCAL delta                 AS LONG             ' Delta of focus
      
          ' Check, if index i is valid
          IF i < 0 OR i > UBOUND(GraphicButtons()) THEN EXIT SUB
      
          IF GraphicButtons(i).Disable = 1 THEN EXIT SUB              ' Return when disabled
      
          GRAPHIC ATTACH hGraphicWindow(nGW), 0, REDRAW
          GRAPHIC STYLE 0                                             ' Solid edge
      
          x0 = GraphicButtons(i).x
          y0 = GraphicButtons(i).y
          w0 = GraphicButtons(i).WIDTH
          h0 = GraphicButtons(i).Height
          x1 = x0 + w0
          x2 = x0 + h0
          y1 = y0 + h0
      
          ' Erase possibly existing text
          delta = MAX(0, %DeltaFocus)                                 ' Do not use negative values of %DeltaFocus
          SELECT CASE LONG GraphicButtons(i).TYPE
              CASE %GW_TickBox
                  GRAPHIC BOX (x0 - delta - 1, y0 - delta - 1) - _
                              (x1 + (1 + MAX(1, %OffsetTBText)/4) * h0 + delta + 1, y1 + delta + 1), _
                              0, %GW_BackColor, %GW_BackColor, 0
              CASE %GW_LineEntry
                  GRAPHIC BOX(x0 - delta , y0 - delta) - (x1 + delta, y1 + delta),0, %GW_BackColor, %GW_BackColor, 0
          END SELECT
      
          ' Draw the box
          SELECT CASE LONG GraphicButtons(i).TYPE
              CASE %GW_Button                                         ' GB
                  GRAPHIC BOX (x0, y0) - (x1, y1), %ButtonCorners, GraphicButtons(i).HoverBorderColor, GraphicButtons(i).HoverBackColor
              CASE %GW_TickBox                                        ' TB
                  IF %UseTBBullet <> 1 THEN
                      GRAPHIC BOX (x0, y0) - (x2, y1), 0, GraphicButtons(i).HoverBorderColor, GraphicButtons(i).HoverBackColor
                  ELSE
                      GRAPHIC ELLIPSE (x0, y0) - (x2, y1), GraphicButtons(i).HoverBorderColor, GraphicButtons(i).HoverBackColor
                  END IF
                  IF GraphicButtons(i).TBTick = 1 THEN CALL TBDrawTick(i)
              CASE %GW_LineEntry                                      ' LE:  No rounded corners for LE
                  GRAPHIC BOX (x0, y0) - (x1, y1), %LECorners, GraphicButtons(i).HoverBorderColor, GraphicButtons(i).HoverBackColor
          END SELECT
      
          ' Set hover font
          GWUpdateFont(GraphicButtons(i).FontName, GraphicButtons(i).FontSize, GraphicButtons(i).HoverFontStyle)
      
          ' Load text
          t$$ = TRIM$(GraphicButtons(i).TEXT)                         ' No leading/trailing blanks!
          GRAPHIC TEXT SIZE t$$ TO w, h
      
          ' Load Unicode arrows if .TEXT is empty
          IF GraphicButtons(i).TYPE = %GW_Button AND t$$ = "" THEN
              SELECT CASE LONG GraphicButtons(i).TextAlign
                  CASE %GW_Top
                      arr$$ = CHR$$(%U_ArrowUp)
                  CASE %GW_Right
                      arr$$ = CHR$$(%U_ArrowRight)
                  CASE %GW_Bottom
                      arr$$ = CHR$$(%U_ArrowDown)
                  CASE %GW_Left
                      arr$$ = CHR$$(%U_ArrowLeft)
              END SELECT
              GRAPHIC TEXT SIZE arr$$ TO w, h
          END IF
      
          ' Set color
          GRAPHIC COLOR GraphicButtons(i).HoverTextColor, -2          ' -2 does not use an own background color for text
      
          IF GraphicButtons(i).TYPE = %GW_LineEntry THEN
              CALL LEPrintText(i)                                     ' Special text placement for LE (Font is set)
          ELSE
              ' Place TB text twice the height to the left of the box
              IF GraphicButtons(i).TYPE = %GW_TickBox THEN
                  woff = (1 + MAX&(1, %OffsetTBText)/4) * h
              ELSE
                  SELECT CASE GraphicButtons(i).TextAlign
                      CASE %GW_Left
                          woff = GRAPHIC(TEXT.SIZE.X, "W")
                      CASE %GW_Right
                          woff = w0 - w - GRAPHIC(TEXT.SIZE.X, "W")
                      CASE ELSE
                          woff = (w0 - w)\2
                  END SELECT
              END IF
              hoff = (h0 - h)\2
              GRAPHIC SET POS (GraphicButtons(i).x + woff, GraphicButtons(i).y + hoff)
      
              ' Print an underlined hot key character when it was specified
              IF GraphicButtons(i).nUnderChar > 0 AND GraphicButtons(i).nUnderChar <= LEN(TRIM$(GraphicButtons(i).TEXT)) THEN
                  ' Print left part of text without underline
                  GRAPHIC PRINT LEFT$(t, GraphicButtons(i).nUnderChar-1);
      
                  ' Print underlined character (+ 4)
                  IF BIT(GraphicButtons(i).HoverFontStyle, 2) = 0 THEN
                      GWUpdateFont(GraphicButtons(i).FontName, GraphicButtons(i).FontSize, GraphicButtons(i).HoverFontStyle + 4)
                  ELSE
                      GWUpdateFont(GraphicButtons(i).FontName, GraphicButtons(i).FontSize, GraphicButtons(i).HoverFontStyle - 4)
                  END IF
                  GRAPHIC PRINT MID$(t, GraphicButtons(i).nUnderChar, 1);
      
                  ' Print right part of text without underline
                  GWUpdateFont(GraphicButtons(i).FontName, GraphicButtons(i).FontSize, GraphicButtons(i).HoverFontStyle)
                  GRAPHIC PRINT RIGHT$(t, LEN(t)-GraphicButtons(i).nUnderChar)
              ELSE
                  IF GraphicButtons(i).TYPE = %GW_Button AND t$$ = "" THEN
                      GRAPHIC PRINT arr$$
                  ELSE
                      GRAPHIC PRINT t$$
                  END IF
              END IF
          END IF
      
          ' Draw button focus
          IF GraphicButtons(i).FOCUS = 1 THEN CALL GraphicDrawFocus(i)
          GRAPHIC REDRAW
      
          ' Do not increase count of hFont => kill it again
          GWKillFont
      
      END SUB ' GraphicHighlightButton
      
      '----------------------------------------------------------------------------'
      
      SUB LEPrintText(BYVAL i AS LONG)
          ' V1.2
          ' Print .Text for LineEntry
          ' Called from GraphicDrawButton() and GraphicHighlightButton()
          ' Test length of text and truncate if needed. Place in box according to .TextAlign:
          ' Case 1: No caret                      =>  Always truncate left
          ' Case 2: Text left of caret fits       =>  Truncate right
          ' Case 3: Text right of caret fits      =>  Truncate left
          ' Case 4: Neither side of caret fits    =>  Truncate both and keep caret in the middle
          ' Overtype caret is hook shaped
      
          ' Input:    i       =>  Index of LE
          ' Output:   -none-
      
          LOCAL uTxt$$                                    ' Text copied from .Text (length = .TBGroup)
          LOCAL wtxt, htxt                AS LONG         ' Width and height of .Text
          LOCAL wbox                      AS LONG         ' Available width in box
          LOCAL cpos                      AS LONG         ' Position of caret (= LEN(ltxt$$))
          LOCAL llen, rlen                AS LONG         ' Length of left and right part
          LOCAL ptxt$$                                    ' Left and right text parts
          LOCAL wp, wx                    AS LONG         ' Width of text parts and of X
          LOCAL x0, y0                    AS LONG         ' Coordinates for printing the caret
          LOCAL cw, dx0, dx1, dx2         AS LONG         ' Width and correction for caret line
          LOCAL dummy                     AS LONG         ' Dummy size value
          LOCAL nCase                     AS LONG         ' Case number
      
          ' Check, if index i is valid
          IF i < 0 OR i > UBOUND(GraphicButtons()) THEN EXIT SUB
          IF GraphicButtons(i).TYPE <> %GW_LineEntry THEN EXIT SUB                ' Use only in LEs
      
          ' Load text
          uTxt$$ = LEFT$(GraphicButtons(i).TEXT, GraphicButtons(i).TBGroup)
          cpos = GraphicButtons(i).nUnderChar                                     ' = LEN(ltxt$$)
          llen = cpos                                                             ' Text length left of caret
          rlen = LEN(uTxt$$) - cpos                                               ' Text length right of caret
      
          ' Get size of text, text field and caret
          GRAPHIC TEXT SIZE "X" TO dummy, htxt                                    ' Height of font
          IF BaseApplication.OverType <> 0 AND rlen = 0 THEN wx = htxt/3          ' Right arm extension of overtype caret
          IF uTxt$$ <> "" THEN GRAPHIC TEXT SIZE uTxt$$ TO wtxt, dummy
      
          IF cpos > %LE_NoCaret THEN                                              ' Width of caret can be 1 or 2
              cw = MIN(2, %LE_CaretWidth)
              cw = MAX(1, cw)
          END IF
          wbox = GraphicButtons(i).WIDTH - htxt\2 - cw - wx - 2 * %DeltaFocus     ' Padding of htxt/4 on both sides, subtract caret width and overtype caret extension
      
          IF wtxt > wbox THEN                                                     ' It needs some kind of truncation
              IF cpos = %LE_NoCaret THEN                                          ' No caret
                  nCase = 1
                  CALL LETruncate(uTxt$$, llen, rlen, wbox, %GW_Left)
              ELSE
                  ptxt$$ = LEFT$(uTxt$$, llen)
                  GRAPHIC TEXT SIZE ptxt$$ TO wp, dummy
                  IF wp < wbox/2 THEN
                      nCase = 2
                      CALL LETruncate(uTxt$$, llen, rlen, wbox, %GW_Right)        ' Continuation character(s) on the right
                  ELSE
                      ptxt$$ = RIGHT$(uTxt$$, rlen)
                      GRAPHIC TEXT SIZE ptxt$$ TO wp, dummy
                      IF wp < wbox/2 THEN
                          nCase = 3
                          CALL LETruncate(uTxt$$, llen, rlen, wbox, %GW_Left)     ' Continuation character(s) on the left
                      ELSE
                          nCase = 4
                          CALL LETruncate(uTxt$$, llen, rlen, wbox, %GW_Center)   ' Continuation character(s) on both sides
                      END IF
                  END IF
              END IF
          ELSE
              nCase = 0
          END IF
      
          ' Set print position
          SELECT CASE LONG nCase
              CASE 1, 0
                  GRAPHIC TEXT SIZE uTxt$$ TO wtxt, dummy
                  wtxt = wtxt + wx
                  SELECT CASE LONG GraphicButtons(i).TextAlign
                      CASE %GW_Right
                          ' Place 1/4 of text size from right edge of box
                          GRAPHIC SET POS (GraphicButtons(i).x + GraphicButtons(i).WIDTH - htxt/4 - wtxt - %DeltaFocus, GraphicButtons(i).y + GraphicButtons(i).Height\2 - htxt\2)
                      CASE %GW_Center
                          ' Place in middle
                          GRAPHIC SET POS (GraphicButtons(i).x + GraphicButtons(i).WIDTH/2 - wtxt/2 , GraphicButtons(i).y + GraphicButtons(i).Height\2 - htxt\2)
      
                      CASE ELSE
                          ' All other .TextAlign place at left edge with htxt/4 padding
                          GRAPHIC SET POS (GraphicButtons(i).x + htxt/4 + %DeltaFocus, GraphicButtons(i).y + GraphicButtons(i).Height\2 - htxt\2)
                  END SELECT
      
              CASE 2, 3
                  ' Place truncated text in middle
                  GRAPHIC TEXT SIZE uTxt$$ TO wp, dummy
                  wp = wp + wx                                                    ' Add caret extension at right side
                  GRAPHIC SET POS (GraphicButtons(i).x + GraphicButtons(i).WIDTH/2 - wp/2 , GraphicButtons(i).y + GraphicButtons(i).Height\2 - htxt\2)
              CASE 4
                  ' Place caret in middle
                  ptxt$$ = LEFT$(uTxt$$, llen)
                  GRAPHIC TEXT SIZE ptxt$$ TO wp, dummy
                  wp = wp + wx                                                    ' Add caret extension at right side
                  GRAPHIC SET POS (GraphicButtons(i).x + INT(GraphicButtons(i).WIDTH/2) - wp , GraphicButtons(i).y + GraphicButtons(i).Height\2 - htxt\2)
          END SELECT
      
          ' Do the printing
          IF cpos > %LE_NoCaret THEN                                              ' Two parts. Caret has own color.
              GRAPHIC PRINT LEFT$(uTxt$$, llen);
              GRAPHIC WIDTH cw                                                    ' Width of caret line
              GRAPHIC GET POS TO x0, y0                                           ' Last print position
      
              ' Some empirical tweaking ...
              IF BIT(GraphicButtons(i).HoverFontStyle, 1) = 1 THEN                ' Italic caret needs a bit more spacing. Some fonts do not look good at all (Script, ...)
                  dx0 = htxt\7
                  dx1 = -htxt\9
              ELSE                                                                ' Non-italic
                  dx0 = 0
                  dx1 = 0
              END IF
              IF GraphicButtons(i).FontSize < 12 THEN
                  dx2 = 1
              ELSE
                  dx2 = 0
              END IF
      
              ' Draw the caret line(s)
                  GRAPHIC LINE (x0 + dx0, y0) - (x0 + dx1, y0 + htxt), %LE_CaretColor             ' Caret size is htxt
                  IF BaseApplication.OverType = 1 THEN
                      dx0 = dx0 - cw + 1                                                          ' Correction for fat caret
                      GRAPHIC LINE (x0 + dx0, y0) - (x0 + dx0 + htxt * 0.36, y0), %LE_CaretColor  ' Draw the top line (= htxt * 0.36)
                  END IF
      
              ' Print right part
              GRAPHIC SET POS (x0 + dx2, y0)                                      ' Correct the position in order for the caret not to interfere with the text too much
              GRAPHIC PRINT RIGHT$(uTxt$$, rlen)
          ELSE
              GRAPHIC PRINT uTxt$$                                                ' Print it without caret
          END IF
      
          GRAPHIC WIDTH 1                                                         ' In case the caret was wider ...
      
      END SUB ' LEPrintText
      
      '----------------------------------------------------------------------------'
      
      SUB LETruncate(uTxt$$, llen AS LONG, rlen AS LONG, BYVAL wbox AS LONG, BYVAL side AS LONG)
          ' V1.2
          ' Reduce number of characters to available width in LE box
          ' Add continuation string to truncated side(s)
      
          ' Input:    uTxt$$  =>  Text (in)
          '           llen    =>  Length of left part
          '           rlen    =>  Length of right part
          '           wbox    =>  Available box width (caret width already subtracted)
          '           side    =>  %GW_Left or %GW_Right => add left or right, %GW_Center => add both
          ' Output:   uTxt$$  =>  Updated text for printing
          '           llen    =>  Updated left length
          '           rlen    =>  Updated right length
      
          LOCAL i, istart             AS LONG                 ' Indices
          LOCAL trunc$$, ltxt$$, rtxt$$                       ' Truncated, left and right text
          LOCAL wtrunc, ww, dummy     AS LONG                 ' Widths, not-used
          LOCAL wcon                  AS LONG                 ' Width of LE continuation string
      
          GRAPHIC TEXT SIZE "W" TO ww, dummy                          ' Assume all wide characters W in text
          GRAPHIC TEXT SIZE $$LE_Continue TO wcon, dummy              ' Width of continuation string
          wbox = wbox - wcon                                          ' Reduce available width by caret and continuation character(s)
          istart = MAX (1, FIX(wbox/ww))                              ' Number of "W" that fit in box. Minimum = 1
      
          SELECT CASE LONG side
              CASE %GW_Right                                          ' Case 2
                  FOR i = istart TO LEN(uTxt$$)                       ' istart is the first index where to start if all were "W"
                      trunc$$ = LEFT$(uTxt$$, i)
                      GRAPHIC TEXT SIZE trunc$$ TO wtrunc, dummy
                      IF wtrunc > wbox THEN EXIT FOR
                  NEXT i
                  uTxt$$ = LEFT$(uTxt$$, i - 1) + $$LE_Continue       ' Use one character less and add continuation character(s)
                  rlen = LEN(uTxt$$) - llen                           ' Right part is shorter
      
              CASE %GW_Left                                           ' Case 3
                  FOR i = istart TO LEN(uTxt$$)                       ' istart is the first index where to start if all were "W"
                      trunc$$ = RIGHT$(uTxt$$, i)
                      GRAPHIC TEXT SIZE trunc$$ TO wtrunc, dummy
                      IF wtrunc > wbox THEN EXIT FOR
                  NEXT i
                  uTxt$$ = $$LE_Continue + RIGHT$(uTxt$$, i - 1)      ' Use one character less and add continuation character(s)
                  llen = LEN(uTxt$$) - rlen                           ' Left part is shorter
      
              CASE %GW_Center                                         ' Case 4: Truncate both sides with caret in middle
                  ltxt$$ = LEFT$(uTxt$$, llen)
                  rtxt$$ = RIGHT$(uTxt$$, rlen)
                  istart = MAX (1, FIX(wbox/ww/2))                    ' Number of "W" to fit in half the box. Minimum = 1
      
                  FOR i = istart TO llen                              ' istart is the first index where to start if all were "W"
                      trunc$$ = RIGHT$(ltxt$$, i)
                      GRAPHIC TEXT SIZE trunc$$ TO wtrunc, dummy
                      IF wtrunc > INT(wbox/2 + 0.5) THEN EXIT FOR     ' Fill left half of box
                  NEXT i
                  ltxt$$ = $$LE_Continue + RIGHT$(ltxt$$, i - 1)      ' Use one character less and add continuation character(s)
                  llen = LEN($$LE_Continue) + i - 1                   ' Updated left part
      
                  FOR i = istart TO rlen                              ' istart is the first index where to start if all were "W"
                      trunc$$ = LEFT$(rtxt$$, i)
                      GRAPHIC TEXT SIZE trunc$$ TO wtrunc, dummy
                      IF wtrunc > INT(wbox/2 + 0.5) THEN EXIT FOR     ' Fill right half of box
                  NEXT i
                  rtxt$$ = LEFT$(rtxt$$, i - 1) + $$LE_Continue       ' Use one character less and add continuation character(s)
                  rlen = i - 1 + LEN($$LE_Continue)                   ' Updated right part
                  uTxt$$ = ltxt$$ + rtxt$$
      
          END SELECT
      
      END SUB ' LETruncate
      
      '----------------------------------------------------------------------------'
      
      FUNCTION GraphicEditText(BYVAL nID AS LONG) AS WSTRING
          ' V1.2
          ' Modify text in LineEntry by user entry.
          ' This SUB is called with nID directly from user programs.
          ' Focus has to be on button window for Graphic input.
          ' In edit mode the .Hover... style is used.
      
          ' Input:    nID         =>  Button ID
          ' Output:   FUNCTION    =>  Edited text when finished by CR or TAB
          '                       =>  $$ESC, when finished by Esc
      
          LOCAL i, j              AS LONG                 ' Button indices
          LOCAL oldtxt$$                                  ' Original text
          LOCAL ctxt$$                                    ' Text from clipboard
          LOCAL ltxt$$, rtxt$$                            ' Left and right portions of txt$$ (without caret)
          LOCAL llen, rlen, clen  AS LONG                 ' Lengths of ltxt$$, rtxt$$, ctxt$$
          LOCAL okltxt$$, okrtxt$$                        ' Last edited text that was OK
          LOCAL lInstat           AS LONG                 ' Flag for pressed key
          LOCAL clicked           AS LONG                 ' Flag for clicked mouse button
          LOCAL keyshift          AS INTEGER              ' Which shift keys are pressed?
          LOCAL sKey$$                                    ' Pressed key
          LOCAL changed           AS LONG                 ' Flag for changed text
          LOCAL hActiveLE         AS LONG                 ' Handle for LE Event
          LOCAL xm, ym            AS SINGLE               ' Mouse coordinates
          LOCAL pressed           AS LONG                 ' Is mouse button held down?
          LOCAL nIndex            AS LONG                 ' Index of button clicked while editing an LE
          LOCAL wasEsc            AS LONG                 ' Flag to indicate if editing was finished by Esc
      
          ' Check correct ID and index first
          i = GraphicGetIndex(nID)                                    ' Retrieve index from ID
          IF i = %GW_FatalError THEN GOTO subEnd:                     ' Wrong ID
          IF GraphicButtons(i).ClearText = -1 THEN GOTO subEnd:       ' No editing of an LE
      
          ' Set a Static Event for signaling a remote program that an LE is active
          hActiveLE = SetStaticEvent($$LE_Active)                     ' When leaving the LE, close the handle again!
      
          ' Make sure the button window is in focus for graphic keyboard input!
          GRAPHIC ATTACH hGraphicWindow(nGW), 0
          GRAPHIC SET FOCUS
      
          ' Set button focus to the LE
          GraphicSetFocus(nID)
      
          ' Retrieve text from UDT. Only leading blanks (on left side) allowed for starting text, no trailing blanks!
          oldtxt$$ = LEFT$(GraphicButtons(i).TEXT, GraphicButtons(i).TBGroup)
          ltxt$$ = oldtxt$$                                           ' Left part
          llen = LEN(ltxt$$)
          rtxt$$ = ""
      
          ' Clear text from LE only once at start if requested
          IF GraphicButtons(i).ClearText > 0 THEN
              ltxt$$ = ""
              llen = 0
              IF GraphicButtons(i).ClearText = 1 THEN GraphicButtons(i).ClearText = 0     ' Reset it for further editing
          END IF
      
          ' Replace border with hover data and redraw box
          GraphicButtons(i).TEXT = ltxt$$ + rtxt$$
          GraphicButtons(i).nUnderChar = llen
          GraphicButtons(i).TBGroup = llen + rlen                     ' Length of text including leading/trailing blanks!
      
          ' Call OnClick before any editing. ValidText (.TBTick) does not get evaluated => Any change of text gets accepted. May be used to move the caret.
          IF GraphicButtons(i).OnClick <> 0 THEN
              CALL LEOnClickCheck(i, ltxt$$, rtxt$$)
              llen = LEN(ltxt$$)
              rlen = LEN(rtxt$$)
              GraphicButtons(i).nUnderchar = llen
          END IF
      
          CALL GraphicHighlightButton(i)                              ' Draw caret there
      
          DO
              SLEEP 1                                                 ' Allow other processes (not really slowing the editing down)
      
              ' End it all immediately, if the BaseApplication gets closed (same as Console)
              GWCheckClosed()
      
              GWCheckFocus()                                          ' Set GWs to right order
              IF %GW_UseCaption = 0 THEN GWLockBaseAppl()             ' Check movement of BaseApplication
      
              ' Store the last text which was OK:
              okltxt$$ = ltxt$$
              okrtxt$$ = rtxt$$
      
              ' Read keyboard in button window
              CALL GWInKey(lInstat, sKey$$, keyshift)
      
              IF ISFALSE lInstat THEN
      
                  ' Now Check for left button mouse clicks
                  GWInMouse(hGraphicWindow(nGW), %VK_LBUTTON, clicked, pressed, xm, ym)   ' Replacement for GRAPHIC WINDOW CLICK
                                                                                          ' Note: pressed, xm, ym not used here
                  ' Any button hovered => Result in GWData(nGW).iHighlight
                  CALL GWCheckHover()
      
                  IF clicked <> 1 THEN
                      ITERATE DO                                                          ' Nothing clicked => continue in edit loop
                  ELSE
                      nIndex = GWData(nGW).iHighlight                                     ' One click: Which button was hovered?
      
                      IF nIndex > -1 THEN                                                 ' Just finish editing, when nIndex = -1 (=> outside any button)
                          ' A button has been clicked
                          IF GraphicButtons(nIndex).ID <> nID THEN
                              GWData(nGW).nextIndex = nIndex                              ' Different button from the currently edited LE
                                                                                          ' => Finish LE and activate that next button
                          ELSE
                              GWData(nGW).nextIndex = %GW_FatalError                      ' The current LE. Keep editing ...
                              ITERATE DO
                          END IF
                      END IF
                      sKey$$ = $$CR                                                       ' Emulate a CR
      
                      ' If ClearText was on and if length of returned text = 0 (no text at all), the last edited text, that was OK, gets restored
                      ' This prevents an LE with ClearText clicked by mistake to get stuck, if no value has been entered.
                      IF GraphicButtons(i).ClearText > 0 AND GraphicButtons(i).nUnderChar = 0 THEN sKey$$ = $$ESC
      
                  END IF
              END IF
      
              keyshift = keyshift AND &B00011111                      ' Mask Num-, Scroll-, and Caps-Lock
      
              ' Detect TAB and Shift+TAB in order to finish editing and advance focus (same as in ExecutePressedKey())
              IF LEN(sKey$$) = 1 AND ASC(sKey$$) = 9 THEN             ' TAB
                  IF BIT(keyshift, 4) = 1 THEN                        ' Shift TAB => move left / up
                      FOR j = i - 1 TO GWData(nGW).iGBStart STEP -1
                          IF GraphicButtons(j).FOCUS = -1 OR GraphicButtons(j).Disable = 1 THEN ITERATE FOR  ' Skip disabled Button/Tickbox/LineEntry
                          IF (GraphicButtons(j).TYPE = %GW_LineEntry AND GraphicButtons(j).ClearText = -1) THEN ITERATE FOR ' No action when an LE has .ClearText = -1
                          CALL GraphicMoveFocus(j, i)
                          EXIT FOR
                      NEXT j
                  ELSE                                                ' No SHIFT => move right / down
                      FOR j = i + 1 TO UBOUND(GraphicButtons())
                          IF GraphicButtons(j).FOCUS = -1 OR GraphicButtons(j).Disable = 1 THEN ITERATE FOR  ' Skip disabled Button/Tickbox/LineEntry
                          IF (GraphicButtons(j).TYPE = %GW_LineEntry AND GraphicButtons(j).ClearText = -1) THEN ITERATE FOR ' No action when an LE has .ClearText = -1
                          CALL GraphicMoveFocus(j, i)
                          EXIT FOR
                      NEXT j
                  END IF
                  sKey$$ = $$CR                                       ' Emulate a CR
                  BIT RESET keyshift, 4                               ' Erase shift bit for executing a CR
              END IF
      
              ' Check overtype
              IF LEN(sKey$$) = 2 AND ASC(sKey$$, 2) = 82 THEN         ' Insert key pressed => toggle .OverType
                  IF BaseApplication.OverType = 0 THEN
                      BaseApplication.OverType = 1
                  ELSE
                      BaseApplication.OverType = 0
                  END IF
      
                  ' Call OnClick only to check overtype effect on caret.
                  ' ValidText (.TBTick) does not get evaluated. That was done after last edit (see lower down before end of loop).
                  ' Should only be used to move the caret.
                  IF GraphicButtons(i).OnClick <> 0 THEN
                      CALL LEOnClickCheck(i, ltxt$$, rtxt$$)
                      llen = LEN(ltxt$$)
                      rlen = LEN(rtxt$$)
                      GraphicButtons(i).nUnderchar = llen
                  END IF
      
                  CALL GraphicHighlightButton(i)                      ' Update text/caret
                  ITERATE DO
              END IF
      
              changed = 1
              wasEsc = 0
              IF LEN(sKey$$) = 1 THEN
                  SELECT CASE LONG ASC(sKey$$)
                      CASE ASC($$BS)                                  ' BS: Delete char left of caret
                          IF llen > 0 THEN
                              DECR llen
                              ltxt$$ = LEFT$(ltxt$$, llen)
                          ELSE
                              changed = 0
                          END IF
                      CASE 127                                        ' Ctrl BS: Cut from caret to left end
                          ltxt$$ = ""
                          llen = 0
                      CASE 3                                          ' Ctrl C: Copy all to clipboard
                          CLIPBOARD RESET
                          CLIPBOARD SET TEXT ltxt$$ + rtxt$$
                      CASE 24                                         ' Ctrl X: Cut all and put on clipboard
                          CLIPBOARD RESET
                          CLIPBOARD SET TEXT ltxt$$ + rtxt$$
                          ltxt$$ = ""                                 ' Erase all text
                          llen = 0
                          rtxt$$ = ""
                          rlen = 0
                      CASE 22                                         ' Ctrl V: Insert text from clipboard before caret
                          CLIPBOARD GET TEXT ctxt$$
                          clen = LEN(ctxt$$)
                          IF BaseApplication.OverType = 0 THEN
                              ' If total length is too long, cancel insert
                              IF llen + rlen + clen > GraphicButtons(i).MaxChars THEN ITERATE LOOP
                              ltxt$$ = ltxt$$ + ctxt$$
                              llen = llen + clen
                          ELSE
                              ' If left + insert text is too long, cancel insert
                              IF llen + clen > GraphicButtons(i).MaxChars THEN ITERATE LOOP
                              ltxt$$ = ltxt$$ + ctxt$$
                              llen = llen + clen
                              IF rlen > clen THEN
                                  rtxt$$ = RIGHT$(rtxt$$, rlen - clen)
                              ELSE
                                  rtxt$$ = ""
                              END IF
                              rlen = LEN(rtxt$$)
                          END IF
                      CASE ASC($$ESC)                                 ' ESC: Replace with old text
                          IF keyshift = 0 THEN                        ' No Shiftkeys allowed
                              wasEsc = 1
                              GraphicButtons(i).TEXT = oldtxt$$
                              GraphicButtons(i).TBGroup = LEN(oldtxt$$)
                              GraphicButtons(i).nUnderChar = %LE_NoCaret
                              EXIT LOOP
                          ELSE
                              changed = 0
                          END IF
                      CASE ASC($$CR)                                              ' CR or TAB = Enter
                          IF keyshift = 0 THEN                                    ' No Shiftkeys allowed
                              GraphicButtons(i).TEXT = ltxt$$ + rtxt$$
                              GraphicButtons(i).TBGroup = LEN(ltxt$$ + rtxt$$)
                              GraphicButtons(i).nUnderChar = %LE_NoCaret          ' No caret => Not in editing mode
      
                              IF GraphicButtons(i).OnClick <> 0 THEN              ' Check if .OnClick is defined
                                  CALL LEOnClickCheck(i, ltxt$$, rtxt$$)
                                  IF GraphicButtons(i).TBTick = 0 THEN            ' Do not leave the editing mode when result is not valid
                                      llen = LEN(ltxt$$)
                                      rlen = LEN(rtxt$$)
                                      GraphicButtons(i).nUnderchar = llen
                                      EXIT SELECT
                                  END IF
                              END IF
      
                              EXIT LOOP                                           ' Now leave (if no .OnClick or result is valid)
                          ELSE
                              changed = 0
                          END IF
                      CASE ELSE
                          changed = 0
                  END SELECT
      
              ELSEIF LEN(sKey$$) = 2 THEN
                  SELECT CASE LONG ASC(sKey$$, 2)
                      CASE 75                                         ' Cursor left: Move one char from left to right
                          IF llen > 0 THEN
                              INCR rlen
                              rtxt$$ = RIGHT$(ltxt$$, 1) + rtxt$$
                              DECR llen
                              ltxt$$ = LEFT$(ltxt$$, llen)
                          ELSE
                              changed = 0
                          END IF
                      CASE 77                                         ' Cursor right: Move one char from right to left
                          IF rlen > 0 THEN
                              INCR llen
                              ltxt$$ = ltxt$$ + LEFT$(rtxt$$, 1)
                              DECR rlen
                              rtxt$$ = RIGHT$(rtxt$$, rlen)
                          ELSE
                              changed = 0
                          END IF
      
                      CASE 71                                         ' Home: Move caret to left end
                          rtxt$$ = ltxt$$ + rtxt$$
                          rlen = llen + rlen
                          ltxt$$ = ""
                          llen = 0
                      CASE 79                                         ' End: Move caret to right end
                          ltxt$$ = ltxt$$ + rtxt$$
                          llen = llen + rlen
                          rtxt$$ = ""
                          rlen = 0
                      CASE 83                                         ' Del
                          IF BIT(keyshift, 2) = 1 OR BIT(keyshift, 3) = 1 THEN    ' Left or right Ctrl pressed
                              rtxt$$ = ""                             ' Ctrl Del: Cut from caret to right end
                              rlen = 0
                          ELSE                                        ' Del: Delete char right of caret
                              IF rlen > 0 THEN
                                  DECR rlen
                                  rtxt$$ = RIGHT$(rtxt$$, rlen)
                              ELSE
                                  changed = 0
                              END IF
                          END IF
                      CASE ELSE
                          changed = 0
                  END SELECT
              END IF
      
              ' If so far no key found (changed = 0), single characters >= 32 are allowed.
              IF LEN(sKey$$) = 1 AND ASC(sKey$$) >= 32 AND changed = 0 THEN
                  ' Don't accept any more characters, if .MaxChars is reached
                  IF llen + rlen >= GraphicButtons(i).MaxChars THEN ITERATE LOOP
      
                      ltxt$$ = ltxt$$ + sKey$$                        ' Add new char to left part
                      INCR llen
                      IF BaseApplication.OverType <> 0 THEN
      
                          IF rlen > 0 THEN                            ' If overtype, delete the leftmost character of rtxt$$
                              DECR rlen
                              rtxt$$ = RIGHT$(rtxt$$, rlen)
                          END IF
                      END IF
                  changed = 1                                         ' Text has been changed
              END IF
      
              ' Check if text has changed
              IF changed = 0 THEN ITERATE LOOP
      
              ' Is there an .OnClick routine to check editing rules?
              ' If result is not valid, the stored OK text parts are used
              IF GraphicButtons(i).OnClick <> 0 THEN
                  CALL LEOnClickCheck(i, ltxt$$, rtxt$$)
                  IF GraphicButtons(i).TBTick = 0 THEN                ' Flagged as invalid
                      ltxt$$ = okltxt$$                               ' Ok text from before editing
                      rtxt$$ = okrtxt$$
                      llen = LEN(ltxt$$)
                      rlen = LEN(rtxt$$)
                      GraphicButtons(i).nUnderchar = llen
                  END IF
              END IF
      
              ' Display edited/restored text
              llen = LEN(ltxt$$)
              rlen = LEN(rtxt$$)
              GraphicButtons(i).TEXT = ltxt$$ + rtxt$$
              GraphicButtons(i).TBGroup = llen + rlen
              GraphicButtons(i).nUnderChar = llen
      
              ' Update text in LE box
              CALL GraphicHighlightButton(i)                          ' Including caret
          LOOP
      
          ' Restore border color and redraw
          CALL GraphicDrawButton(i)                                   ' Normal state again
          GRAPHIC WINDOW CLICK TO changed, changed, changed           ' Dummy call to flush click buffer
      
      
          subEnd:
          CloseHandle(hActiveLE)                                      ' Close the ActiveLE (Static Event) handle again
      
          ' Retrieve text with length stored in .TBGroup
          IF wasEsc = 1 THEN
              FUNCTION = $$ESC
          ELSE
              FUNCTION = LEFT$(GraphicButtons(i).TEXT, GraphicButtons(i).TBGroup)
          END IF
      
      END FUNCTION    ' GraphicEditText
      
      '----------------------------------------------------------------------------
      
      SUB GraphicSetNextID(BYVAL nID AS LONG)
          ' V0.9
          ' Pass a button ID to be executed next without being activated by key or mouse
      
          ' Input:    nID                 =  ID of the next button
          ' Output:   GWData(nGW).nIndex  = Index of next valid button (GLOBAL)
          '                               = %GW_FatalError, if not or wrongly set
      
          LOCAL i     AS LONG                     ' Index
      
          IF GWData(nGW).nextIndex > %GW_FatalError THEN EXIT SUB     ' Already set. Do not overwrite
      
          i = GraphicGetIndex(nID)                                    ' If not valid, i = %GW_FatalError which gets ignored in GraphicGetEvents()
      
          ' Do not show HotKeys for LE in display only mode
          IF GraphicButtons(i).TYPE = %GW_LineEntry AND GraphicButtons(i).ClearText = -1 THEN
              GWData(nGW).nextIndex = %GW_FatalError
          ELSE
              GWData(nGW).nextIndex = i
          END IF
      
      END SUB ' GraphicSetNextID
      
      '----------------------------------------------------------------------------
      
      SUB LEOnClickCheck(BYVAL i AS LONG, ltxt$$, rtxt$$)
          ' V1.2
          ' Call the .OnClick routine for checking and enforcing editing rules
          ' The two text parts ltxt$$ + rtxt$$ get copied to .Text without the caret.
          ' .nUnderChar contains the caret position. It has to be updated when changes have been done.
          ' .TBTick gets set as valid flag and has to get evaluated in the calling program.
          ' The length of the returned .Text string has to be checked and limited to .MaxChars.
      
          '+---------------------------------------------------------------------------------------------+
          '| In the .OnClick routine parameters have to be retrieved at the beginning by calling:        |
          '|      Call LEGetTextParameters(sText$$, caretPos, editMode)                                  |
          '|                                                                                             |
          '| During the edit process editMode is <> 0. Text and/or caretPos may be changed.              |
          '| After editing (CR entered) the .OnClick routine gets called again with editMode = 0         |
          '|                                                                                             |
          '| After checking and possibly changing text, parameters have to be returned by calling:       |
          '|      Call LESetTextParameters(sText$$, caretPos, validText)                                 |
          '|                                                                                             |
          '| validText has to be set = 0 (false) or = 1 (true), depending if the last edits were valid.  |
          '|                                                                                             |
          '| Prototype code:                                                                             |
          '| ---------------                                                                             |
          '
          '   Button.OnClick = CODEPTR(xxxx)                              ' Replace xxxx by file name and set in LE definition
          '
          '    SUB xxxx                                                   ' .OnClick routine
          '        LOCAL sText$$
          '        LOCAL caretPos, editMode, validText  AS LONG
          '
          '        CALL LEGetTextParameters(sText$$, caretPos, editMode)  ' Get parameters
          '            ...
          '            ...                                                ' Check and/or change text and caret position
          '            ...
          '        validText = %true                                      ' Set validText true/false depending if last edit was valid
          '        CALL LESetTextParameters(sText$$, caretPos, validText) ' Set parameters
          '    END SUB  'xxxx
          '+---------------------------------------------------------------------------------------------+
      
          ' Input:    i       =>  Index of LE
          '           ltxt$$  =>  Left part of text (without caret)
          '           rtxt$$  =>  Right part of text (without caret)
          ' Output:   ltxt$$, rtxt$$ may be updated if .TBTick is <> 0 (true)
      
          LOCAL cpos          AS LONG                         ' Position of caret
          LOCAL lent          AS LONG                         ' length of text
          LOCAL uTxt$$                                        ' Copied from .Text (length = .TBGroup)
      
          ' Check, if index i is valid
          IF i < 0 OR i > UBOUND(GraphicButtons()) THEN EXIT SUB
          IF GraphicButtons(i).TYPE <> %GW_LineEntry THEN EXIT SUB    ' Use only in LEs
      
          ' Store incoming text and prepare for .OnClick
          GraphicButtons(i).TEXT = ltxt$$ + rtxt$$                    ' Prepare .Text and save caret position
          GraphicButtons(i).TBGroup = LEN(ltxt$$ + rtxt$$)            ' Real length of text (including blanks)
          IF GraphicButtons(i).nUnderChar <> %LE_NoCaret THEN GraphicButtons(i).nUnderChar = LEN(ltxt$$)
      
          CALL DWORD GraphicButtons(i).OnClick                        ' No parameters for this user supplied routine.
      
          ' No valid editing! TBTick is used as the valid flag here (0 = false = not valid)
          IF GraphicButtons(i).TBTick = 0 THEN
              EXIT SUB
          END IF
      
          ' A valid change has occurred in the .OnClick routine
          lent = GraphicButtons(i).TBGroup
          uTxt$$ = LEFT$(GraphicButtons(i).TEXT, lent)
      
          ' Do not allow more than .MaxChars
          IF lent > GraphicButtons(i).MaxChars THEN
              GraphicButtons(i).TBTick = 0
              EXIT SUB
          END IF
      
          ' Update the text parts
          cpos = GraphicButtons(i).nUnderChar
          IF cpos <> %LE_NoCaret THEN
              SELECT CASE LONG cpos
                  CASE >= 0, <= lent                                  ' Caret position is inside .Text
                      ltxt$$ = LEFT$(uTxt$$, cpos)                    ' Up to caret
                      rtxt$$ = RIGHT$(uTxt$$, lent - cpos)
      
                  CASE ELSE                                           ' Caret position is outside .Text
                      GraphicButtons(i).TBTick = 0                    ' Not valid
              END SELECT
          END IF
      
      END SUB ' LEOnClickCheck
      
      '----------------------------------------------------------------------------
      
      SUB LEGetTextParameters(sText$$, caretPos AS LONG, editMode AS LONG)
          ' V1.2
          ' This SUB gets called at the beginning of a LE .OnClick routine in order to retrieve data from a LE box.
          ' The .OnClick routine can be used to check and/or enforce editing rules during and after the editing process.
          ' When editMode <> 0, the caret position is min. 1 and max. len(xText$$) + 1.
          ' sText$$ can be modified or the caret position can be moved.
          ' The relevant data are exchanged through the GraphicButtons UDT members: .Text, .nUnderChar and .TBTick
      
          ' Input:    -none-
          ' Output:   sText$$     =>  Copy of LE .Text            (Gets declared in the .OnClick routine and modified here)
          '           caretPos    =>  Caret position              (Gets declared in the .OnClick routine and modified here)
          '           editMode    =>  <> 0 : In editing mode.     (Gets declared in the .OnClick routine and modified here)
          '                            = 0 : Not in editing mode.
      
          LOCAL i     AS LONG                                         ' Index
      
          i = GWGetCurrentFocus()                                     ' Get the button index of the currently edited LE
          IF GraphicButtons(i).TYPE <> %GW_LineEntry THEN EXIT SUB    ' Use only in LEs
      
          ' .Text including leading/trailing blanks
          sText$$ = LEFT$(GraphicButtons(i).TEXT, GraphicButtons(i).TBGroup)
      
          ' Caret position
          caretPos = GraphicButtons(i).nUnderChar                     ' Caret position in .nUderChar
      
          ' Editing mode
          IF caretPos = %LE_NoCaret THEN
              editMode = 0                                            ' Not in editing mode: All of .Text in lText$$
          ELSE
              editMode = 1                                            ' Editing mode
          END IF
      
      END SUB ' LEGetTextParameters
      
      '----------------------------------------------------------------------------
      
      SUB LESetTextParameters(BYVAL sText$$, BYVAL caretPos AS LONG, BYVAL validText AS LONG)
          ' V1.2
          ' This SUB gets called at the end of a LE .OnClick routine in order to signal if the edited text is valid and gets copied to .Text.
      
          ' Input:    sText$$     =>  LE .Text
          '           caretPos    =>  Caret position
          '           validText   =>  = 0, sText$$ will not be copied to .Text
          '                       =>  <> 0, sText$$ will get copied to .Text
          ' Output:   -none-
      
          LOCAL i     AS LONG                             ' Index of LE
      
          i = GWGetCurrentFocus()                                     ' Get the button index of the currently edited LE
          IF GraphicButtons(i).TYPE <> %GW_LineEntry THEN EXIT SUB    ' Use only in LEs
      
          IF validText = 0 THEN
              GraphicButtons(i).TBTick = 0
          ELSE
              GraphicButtons(i).TBTick = 1
              GraphicButtons(i).TEXT = sText$$                            ' Restore the .Text
              GraphicButtons(i).TBGroup = LEN(sText$$)
              IF GraphicButtons(i).nUnderChar <> %LE_NoCaret THEN
                  GraphicButtons(i).nUnderChar = caretPos                 ' In editing mode
              END IF
          END IF
      
      END SUB     ' LESetTextParameters
      
      '----------------------------------------------------------------------------'
      
      FUNCTION GraphicGetLEText(BYVAL nID AS LONG) AS WSTRING
          ' V1.2
      
          QueryBox("Function GraphicGetLEText()" + $$CRLF + "is obsolete from V0.9 on." + $$CRLF + $$CRLF + _
                      "Use:  GraphicButtonsGetText()" + $$CRLF + _
                      "for Buttons, TBs and LEs." + $$CRLF + $$CRLF + _
                      "The function GraphicEditText()" + $$CRLF + _
                      "also returns the LE text.")
      
          FUNCTION = GraphicButtonsGetText(nID)
      
      END FUNCTION ' GraphicGetLEText
      
      '----------------------------------------------------------------------------
      
      FUNCTION GraphicSetLEText(BYVAL nID AS LONG, BYVAL sText$$) AS LONG
          ' V1.2
      
          QueryBox("Function GraphicSetLEText()" + $$CRLF + "is obsolete from V0.9 on." + $$CRLF + "Use:  GraphicButtonsSetText()")
      
          GraphicButtonsSetText(nID, sText$$)
      
      END FUNCTION    ' GraphicSetLEText
      
      '----------------------------------------------------------------------------'
      
      FUNCTION GraphicButtonsGetText(BYVAL nID AS LONG) AS WSTRING
          ' V1.2
          ' Retrieve .Text from any button (Button/TickBox/LineEntry)
      
          ' Input:    nID      =>  ID of button
          ' Output:   FUNCTION =>  .Text
          '                    =>  -empty-, if index cannot be found
      
          LOCAL i         AS LONG             ' Index
      
          i = GraphicGetIndex(nID)
          IF i = %GW_FatalError THEN EXIT FUNCTION
      
          SELECT CASE LONG GraphicButtons(i).TYPE
              CASE %GW_Button, %GW_TickBox
                  ' Return .Text including leading/trailing blanks
                  FUNCTION = TRIM$(GraphicButtons(i).TEXT)
              CASE %GW_LineEntry
                  ' Return .Text without any blanks
                  FUNCTION = LEFT$(GraphicButtons(i).TEXT, GraphicButtons(i).TBGroup)
          END SELECT
      
      END FUNCTION ' GraphicButtonsGetText
      
      '----------------------------------------------------------------------------
      
      FUNCTION GraphicButtonsSetText(BYVAL nID AS LONG, BYVAL sText$$) AS LONG
          ' V1.2
          ' Replace .Text of any button (Button/TickBox/LineEntry) by sTxt$$ when not in editing mode and redraw the button
      
          ' Input:    nID         =>  ID of button
          '           sText$$     =>  .Text to be copied to button
          ' Output:   -none
      
          LOCAL i         AS LONG                         ' Index
      
          i = GraphicGetIndex(nID)
          IF i = %GW_FatalError THEN EXIT FUNCTION
      
          SELECT CASE LONG GraphicButtons(i).TYPE
              CASE %GW_Button, %GW_TickBox
                  GraphicButtons(i).TEXT = sText$$
      
              CASE %GW_LineEntry
                  GraphicButtons(i).TEXT = sText$$
                  GraphicButtons(i).TBGroup = LEN(sText$$)
      
          END SELECT
          GraphicDrawButton(i)
      
      END FUNCTION    ' GraphicButtonsSetText
      
      '----------------------------------------------------------------------------'
      
      SUB GraphicDrawFrame(BYVAL i AS LONG)
          ' V1.2
          ' Draw a Frame using its UDT values
      
          ' Input:    i  =>  Index of Frame to draw
          ' Output:   -none-
      
          LOCAL w                     AS LONG             ' Width
          LOCAL h                     AS LONG             ' Height
          LOCAL t$$                                       ' Frame text trimmed to its length  <===  no blanks possible
          LOCAL x0, x1, y0, y1        AS LONG             ' Coordinates
          LOCAL usedColor             AS LONG             ' Color for Disable/Enable
          LOCAL xt, yt                AS SINGLE           ' Coordinates for center text
      
          ' Check, if index i is valid
          IF i < 0 OR i > UBOUND(GraphicFrames()) THEN EXIT SUB
      
          ' Attach the top button window
          GRAPHIC ATTACH hGraphicWindow(nGW), 0, REDRAW
      
          GRAPHIC STYLE 0                                             ' Solid edge
      
          x0 = GraphicFrames(i).x
          y0 = GraphicFrames(i).y
          x1 = x0 + GraphicFrames(i).WIDTH
          y1 = y0 + GraphicFrames(i).Height
          t$$ = TRIM$(GraphicFrames(i).TEXT)
      
          GWUpdateFont(GraphicFrames(i).FontName, GraphicFrames(i).FontSize, GraphicFrames(i).FontStyle)
      
          ' Set edge color depending on .Disable
          IF GraphicFrames(i).Disable = 1 AND GraphicButtons(i).BorderColor <> GraphicButtons(i).BackColor THEN
              usedColor = %GW_DisableColor
          ELSE
              usedColor = GraphicFrames(i).BorderColor
          END IF
      
          IF GraphicFrames(i).WIDTH * GraphicFrames(i).Height <> 0 THEN           ' Only draw when width and height are <> 0
              GRAPHIC BOX (x0, y0) - (x1, y1), %FrameCorners, usedColor, -2, 0    ' Transparent body and solid edge
              GRAPHIC TEXT SIZE t$$ TO w, h
          ELSE
              h = 0                                                   ' Place text at x0, y0 when there is no Frame
          END IF
      
          SELECT CASE LONG GraphicFrames(i).TextAlign
              CASE  %GW_Top
                  ' Place GF text - as title - h away from left upper corner and h/2 above top line
                  GRAPHIC SET POS (x0 + h, y0 - h/2)
      
              CASE %GW_Center
                 ' Place GF text in the middle of the frame
                  xt = x0 + (GraphicFrames(i).WIDTH - w)/2
                  yt = y0 + (GraphicFrames(i).Height - h)/2
                  GRAPHIC SET POS (xt, yt)
          END SELECT
      
          ' Set text color depending on .Disable
          IF GraphicFrames(i).Disable = 1 AND GraphicButtons(i).TextColor <> GraphicButtons(i).BackColor THEN
              usedColor = %GW_DisableColor
          ELSE
              usedColor = GraphicFrames(i).TextColor
          END IF
      
          ' Set text color to back ground
          GRAPHIC COLOR usedColor, %GW_BackColor                      ' Use same background color as for the button window
      
          ' Print it over Frame
          IF LEN(t$$) > 0 THEN
              IF h > 0 AND GraphicFrames(i).TextAlign = %GW_Top THEN t$$ = " " + t$$ + " "    ' Add blanks to cover up the Frame before and after the text
              GRAPHIC PRINT t$$
          END IF
      
          GRAPHIC REDRAW
      
          ' Do not increase count of hFont => kill it again
          GWKillFont
      
      END SUB ' GraphicDrawFrame
      
      '----------------------------------------------------------------------------
      
      SUB GraphicDrawFocus(BYVAL i AS LONG)
          ' V0.9
          ' Draw focus for Button/Tickbox/LineEntry i
      
          ' Input:    i  =>  index of Button/TickBox/LineEntry that gets the focus
          ' Output:   -none-
      
          LOCAL x0, x1, y0, y1    AS LONG                 ' Coordinates
          LOCAL ipos              AS LONG                 ' Position of dot
          LOCAL offset            AS LONG                 ' Prevent double pixels at corners
      
          ' Check, if index i is valid
          IF i < 0 OR i > UBOUND(GraphicButtons()) THEN EXIT SUB
      
          IF GraphicButtons(i).Disable = 1 THEN EXIT SUB              ' Return when disabled
      
          ' Attach the top button window
          IF IsWindow(hGraphicWindow(nGW)) = 0 THEN EXIT SUB
          GRAPHIC ATTACH hGraphicWindow(nGW), 0, REDRAW
      
          GRAPHIC WIDTH 1                                             ' Only with width = 1 dotted is possible
      
          IF %DeltaFocus >= 0 THEN
              SELECT CASE LONG GraphicButtons(i).TYPE
                  CASE %GW_Button, %GW_LineEntry
                      x0 = GraphicButtons(i).x + %DeltaFocus
                      y0 = GraphicButtons(i).y + %DeltaFocus
                      x1 = GraphicButtons(i).x + GraphicButtons(i).WIDTH - %DeltaFocus - 1
                      y1 = GraphicButtons(i).y + GraphicButtons(i).Height - %DeltaFocus - 1
                  CASE %GW_TickBox
                      x0 = GraphicButtons(i).x + (1 + MAX(1, %OffsetTBText)/4) * GraphicButtons(i).Height - %DeltaFocus
                      y0 = GraphicButtons(i).y - %DeltaFocus
                      x1 = GraphicButtons(i).x + (1 + MAX(1, %OffsetTBText)/4) * GraphicButtons(i).Height + GraphicButtons(i).WIDTH + %DeltaFocus - 1  '.Width is too big by .Height/2
                      y1 = GraphicButtons(i).y + GraphicButtons(i).Height + %DeltaFocus - 1
              END SELECT
          END IF
      
          ' Top dots. Set single pixel with 1 pixel spacing
          FOR ipos = x0 TO x1 STEP 2
              GRAPHIC SET PIXEL (ipos, y0), %GW_FocusColor
          NEXT ipos
      
          ' Bottom dots
          offset = (GraphicButtons(i).Height + 1) MOD 2
          FOR ipos = x0 + offset TO x1 STEP 2
              GRAPHIC SET PIXEL (ipos, y1), %GW_FocusColor
          NEXT ipos
      
          ' Left dots
          FOR ipos = y0 TO y1 STEP 2
              GRAPHIC SET PIXEL (x0, ipos), %GW_FocusColor
          NEXT ipos
      
          ' Right dots
          offset = (GraphicButtons(i).WIDTH + 1) MOD 2
          FOR ipos = y0 + offset TO y1 STEP 2
              GRAPHIC SET PIXEL (x1, ipos), %GW_FocusColor
          NEXT ipos
      
          GRAPHIC REDRAW
      
      END SUB ' GraphicDrawFocus
      
      '----------------------------------------------------------------------------
      
      SUB GWMoveFocusUp(BYVAL icf AS LONG)
          ' V1.0
          ' Move selection dots (= button focus) up to next enabled button (- if there was a button focus in this window ...)
      
          ' Input:    icf  =>  index of button with current focus, = -1 if none has the focus
          ' Output:   -none-
      
          LOCAL j         AS LONG                 ' Index
      
          IF icf = -1 THEN EXIT SUB                                   ' There should be a current focus!
          FOR j = icf - 1 TO GWData(nGW).iGBStart STEP -1
              IF GraphicButtons(j).FOCUS = -1 OR GraphicButtons(j).Disable = 1 THEN ITERATE FOR  ' Skip disabled Button/Tickbox/LineEntry
              IF (GraphicButtons(j).TYPE = %GW_LineEntry AND GraphicButtons(j).ClearText = -1) THEN ITERATE FOR ' No action when an LE has .ClearText = -1
              CALL GraphicMoveFocus(j, icf)
              EXIT FOR
          NEXT j
      
      END SUB 'GWMoveFocusUp
      
      '----------------------------------------------------------------------------
      
      SUB GWMoveFocusDown(BYVAL icf AS LONG)
          ' V1.0
          ' Move selection dots (= button focus) down to next enabled button (- if there was a button focus in this window ...)
      
          ' Input:    icf  =>  index of button with current focus, = -1 if none has the focus
          ' Output:   -none-
      
          LOCAL j         AS LONG                 ' Index
      
          IF icf = -1 THEN EXIT SUB                                   ' There should be a current focus!
          FOR j = icf + 1 TO UBOUND(GraphicButtons())
              IF GraphicButtons(j).FOCUS = -1 OR GraphicButtons(j).Disable = 1 THEN ITERATE FOR  ' Skip disabled Button/Tickbox/LineEntry
              IF (GraphicButtons(j).TYPE = %GW_LineEntry AND GraphicButtons(j).ClearText = -1) THEN ITERATE FOR ' No action when an LE has .ClearText = -1
              CALL GraphicMoveFocus(j, icf)
              EXIT FOR
          NEXT j
      
      END SUB 'GWMoveFocusDown
      
      '----------------------------------------------------------------------------
      
      SUB GraphicMoveFocus(BYVAL inew     AS LONG, _
                           BYVAL iold     AS LONG)
          ' V0.8
          ' Erase the focus from Button/TickBox/LineEntry iold and draw it on Button/Tickbox/LineEntry inew
      
          ' Input:    inew  =>  index of new button with focus
          '           iold  =>  index of old focus
          ' Output:   -none-
      
          ' Check, if indices are valid
          IF inew < 0 OR inew > UBOUND(GraphicButtons()) THEN EXIT SUB
      
          IF GraphicButtons(inew).FOCUS = -1 OR inew = iold THEN EXIT SUB ' No action when .Focus is disabled or there are the same indices
      
          ' Draw new button focus
          GraphicButtons(inew).FOCUS = 1
          CALL GraphicDrawFocus(inew)                                 ' Transparent over the existing button
      
          ' Redraw old Button/Tickbox/LineEntry without any focus
          IF iold > -1 AND iold <= UBOUND(GraphicButtons()) THEN
              GraphicButtons(iold).FOCUS = 0
              CALL GraphicDrawButton(iold)                            ' Draw whole button again
          END IF
      
      END SUB ' GraphicMoveFocus
      
      '----------------------------------------------------------------------------
      
      SUB TBChangeTicks(BYVAL i       AS LONG, _
                        BYVAL hover   AS LONG)
          ' V0.9
          ' Erase old tick and set new one as long as there are several in one group
          ' When there is only one in a group, toggle it
          ' The .OnClick routine is called in here - and only at the change of TickState
      
          ' Input:    i  =>  Index of new tick
          '           hover: 1 => draw highlighted TB, 0 => standard draw
          ' Output:   -none-
      
          LOCAL j         AS LONG
          LOCAL nGroup    AS LONG
          LOCAL nTB       AS LONG
          LOCAL jact      AS LONG
      
          ' Check, if index i is valid
          IF i < 0 OR i > UBOUND(GraphicButtons()) THEN EXIT SUB
          ' Do it only for TB!
          IF GraphicButtons(i).TYPE <> %GW_TickBox THEN EXIT SUB
      
          nGroup = GraphicButtons(i).TBGroup                          ' Get new group value
          nTB = 0
          jact = -1
      
          FOR j = GWData(nGW).iGBStart TO UBOUND(GraphicButtons())
              IF GraphicButtons(j).TYPE <> %GW_TickBox THEN ITERATE FOR ' Skip Buttons/LineEntries
              IF GraphicButtons(j).TBGroup <> nGroup THEN ITERATE FOR ' Skip members of any other group
              INCR nTB                                                ' Count the number in this group
              IF GraphicButtons(j).TBTick = 1 THEN jact = j           ' Presently active
          NEXT j
      
          IF nTB = 1 THEN                                             ' Single TB in a group - toggle TBTick
              IF GraphicButtons(i).TBTick = 1 THEN
                  GraphicButtons(i).TBTick = 0                        ' Reset (= erase) this one
              ELSE
                  GraphicButtons(i).TBTick = 1                        ' Set this one
              END IF
              IF hover = 1 THEN
                  CALL GraphicHighlightButton(i)                      ' Keep highlight for single TB and update Tick
              ELSE
                  CALL GraphicDrawButton(i)                           ' Just update TB
              END IF
      
              ' Execute the routine defined for this TB
              IF GraphicButtons(i).OnClick <> 0 THEN CALL DWORD GraphicButtons(i).OnClick
          ELSE                                                        ' More than one in a group
              IF i <> jact THEN                                       ' Don't do anything when the active TickBox is pressed again
                  IF jact > -1 THEN
                      GraphicButtons(jact).TBTick = 0                 ' Reset (= erase) this one
                      CALL GraphicDrawButton(jact)
                  END IF
                  GraphicButtons(i).TBTick = 1                        ' Set this one
                  CALL TBDrawTick(i)                                  ' Draw tick
                  GRAPHIC REDRAW
                  ' Execute the routine defined for this TB
                  IF GraphicButtons(i).OnClick <> 0 THEN CALL DWORD GraphicButtons(i).OnClick
              END IF
          END IF
      
      END SUB ' TBChangeTicks
      
      '----------------------------------------------------------------------------
      
      SUB TBDrawTick(BYVAL i AS LONG)
          ' V0.9
          ' Draw a tick into the TickBox
      
          ' Input:    i  =>  Index of tick to be drawn
          ' Output:   -none-
      
          LOCAL x0, y0, h AS LONG                         ' Coordinates, height
          LOCAL b2r       AS SINGLE                       ' Body to rim relationship
      
          ' Check, if index i is valid
          IF i < 0 OR i > UBOUND(GraphicButtons()) THEN EXIT SUB
          ' Do it only for TB!
          IF GraphicButtons(i).TYPE <> %GW_TickBox THEN EXIT SUB
      
          GRAPHIC STYLE 0                                             ' Solid line
      
          x0 = GraphicButtons(i).x
          y0 = GraphicButtons(i).y
          h = GraphicButtons(i).Height
      
          ' Set color according to Disable state
          IF GraphicButtons(i).Disable = 1 THEN
              GRAPHIC COLOR %GW_DisableColor
          ELSE
              GRAPHIC COLOR GraphicButtons(i).BorderColor
          END IF
          IF %UseTBBullet = 0 THEN
              IF %TickWidth = 2 THEN
                  GRAPHIC WIDTH 2                                     ' Draw a thick X
                  GRAPHIC LINE (x0 + h/5, y0 + h/5)      - (x0 + h*4/5 -1, y0 + h*4/5 -1)
                  GRAPHIC LINE (x0 + h/5, y0 + h*4/5 -1) - (x0 + h*4/5 -1, y0 + h/5)
                  GRAPHIC WIDTH 1
              ELSEIF %TickWidth = 1 THEN
                  GRAPHIC WIDTH 1                                     ' Draw a thin X
                  GRAPHIC LINE (x0 + h/5, y0 + h/5)      - (x0 + h*4/5, y0 + h*4/5)
                  GRAPHIC LINE (x0 + h/5, y0 + h*4/5 -1) - (x0 + h*4/5, y0 + h/5 -1)
              END IF
          ELSEIF %UseTBBullet = 1 THEN
              GRAPHIC WIDTH 1
              b2r = MAX(1, %TickWidth) * 1.1                          ' b2r = body : rim (empirical values)
              GRAPHIC ELLIPSE (x0 + h/(b2r+2), y0 + h/(b2r+2)) - (x0 + h*(b2r+1)/(b2r+2), y0 + h*(b2r+1)/(b2r+2)), , -1
          ELSEIF %UseTBBullet = 2 THEN
              IF %TickWidth = 1 THEN
                  GRAPHIC WIDTH 1
                  GRAPHIC LINE (x0 + h*2/10, y0 + h*5/10)  - STEP(h*2/10, h*2/10)
                  GRAPHIC LINE STEP(0, 0) - STEP(h*4/10, -h*6/10)
              ELSE
                  GRAPHIC WIDTH 2
                  GRAPHIC LINE (x0 + h*2/10, y0 + h*5/10)  - STEP(h*2/10, h*2/10)
                  GRAPHIC LINE STEP(0, 0) - STEP(h*4/10 - 1, -h*6/10 + 1)
                  GRAPHIC WIDTH 1
              END IF
          END IF
      
      END SUB ' TBDrawTick
      
      '----------------------------------------------------------------------------
      
      SUB GraphicGetEvents(retButton AS LONG)
          ' V1.2
          ' Check the focus and hide the GW if Console/BaseApplication is minimized or other windows are in focus, else set them TopMost
          ' Move GWs with the Console/BaseApplication if %GW_UseCaption = 0 (locked).
          ' Check if mouse is over a button in the top button window (hover)
          ' Execute buttons that were set through GWData(nGW).nextIndex
          ' Retrieve keystrokes from the Console/BaseApplication OR button window:
          '   Cursor Up, Down, Enter, Space, (shift)Tab, shortcuts keys for buttons
          ' Also retrieve mouse clicks on buttons.
          ' Via nextActiveID a button may be activated by calling this routine. Useful for LE to go to edit mode directly.
          ' Detect button activation by left clicks (%VK_LBUTTON, clicked = 1)
          ' Context clicks result from right mouse button being released (%VK_RBUTTON, clicked = -1).
          ' The user can check it in the GetEvents loop by asking for KeyMouse.ContextClick <> %GW_NoEvent (in GW or on button).
      
          ' Input:    nextActiveID    .ID of button to be activated by calling this routine and not by keys or mouse clicks - optional
          ' Output:   retButton       > 0                         =>  .ID of activated button
          '                           = %GW_NoEvent               =>  No event detected
          '                           = %GW_FatalError            =>  Fatal errors when closing GWs
      
          LOCAL sKey$$                                    ' Result of keystroke
          LOCAL keyshift          AS INTEGER              ' = INSHIFT, status of shift, ctrl, alt etc.
          LOCAL grinstat          AS LONG                 ' Graphic INSTAT
          LOCAL xmouse, ymouse    AS SINGLE               ' Mouse coordinates when clicked
          LOCAL clicked           AS LONG                 ' Flag for clicked mouse button
          LOCAL hFg               AS DWORD                ' Handle of present foreground window
          LOCAL nIndex            AS LONG                 ' Index of next button after editing an LE
          LOCAL pressed           AS LONG                 ' Is mouse button held down?
          LOCAL nextActiveID      AS LONG                 ' ID of next button to be executed automatically
      
          SLEEP 0                                                             ' This allows other processes to get enough time
                                                                              ' SLEEP 0 only seems to occupy all the time, though.
                                                                              ' SLEEP 1 lets the CPU really wait 16 ms ...
      
          retButton = %GW_NoEvent                                             ' Preset for no events (= 0)
          KeyMouse.ContextClick = %GW_NoEvent                                 ' Value may be set in ExecuteMouseClicks()
          KeyMouse.ascKey1 = -1                                               ' Preset to invalid
          KeyMouse.ascKey2 = -1
      
          ' If BaseApplication gets closed externally then ExitProcess()
          GWCheckClosed()
      
          ' Hide and show button windows with the Console/BaseApplication at bottom and last GW on top.
          IF GWCheckFocus() = 1 THEN EXIT SUB                                 ' Return to Console/BaseApplication to minimize loop time
      
          ' Call function to lock GW and Console/BaseApplication when no caption is used
          IF %GW_UseCaption = 0 THEN                                          ' Take this IF out to always get the locking effect ...
              IF GWLockBaseAppl() = 1 THEN EXIT SUB                           ' Check only if Console/BaseApplication or GWs have moved. If yes, return to Console/BaseApplication
          END IF
      
          IF GWShowHotKeys() = 1 THEN EXIT SUB                                ' Show .HotKey instead of .Text in all buttons
      
          IF GWCheckHover() <> 0 THEN EXIT SUB                                ' Button highlighted or de-highlighted by mouse
      
          ' Is a next button requested automatically (no key, no mouse) or by clicking a button while editing an LE?
          nIndex = GWData(nGW).nextIndex                                      ' .nextIndex set while editing an LE or by GraphicSetNextID()
      
          IF nIndex > %GW_FatalError THEN
              nextActiveID = GraphicButtons(nIndex).ID
              GWData(nGW).nextIndex = %GW_FatalError                          ' Reset to invalid
              IF GWData(nGW).iHighlight <> -1 THEN CALL GraphicDrawButton(GWData(nGW).iHighlight)     ' Remove possible hover highlight
              CALL GraphicSetFocus(nextActiveID)                              ' Set button focus ...
              retButton = GWActivateButtonID(nIndex)                          ' ... and activate
              EXIT SUB                                                        ' Skip the rest of key and mouse prompting after this automatic execution
          END IF
      
          ' Now check for keys and mouse clicks
              hFg = GetForegroundWindow()                                     ' API call
              hFg = GetForegroundWindow()                                     ' API call
      
          #IF %DEF(%PB_CC32)                                                  ' Compile this with PB/CC
              IF BaseApplication.isCons = 1 AND hFg = BaseApplication.h THEN  ' Check events on Console ...
      
                  ' Check events on Console and execute until buffer is empty
                  KeyMouse.ascKey1 = -1                                       ' No key pressed
                  KeyMouse.ascKey2 = -1
                  DO WHILE ISTRUE CON.INSTAT
                      sKey$$ = CON.INKEY$                                     ' Pressed key. Called directly from console (not through GWInkey())
                      INPUT FLUSH                                             ' Empty input buffer in any case
                      keyshift = CON.INSHIFT AND &B0111111                    ' Status of Shift/Ctrl/Alt. Mask "Extended keys".
      
                      ' Now execute the keys (with UCASE$)
                      CALL ExecutePressedKeys(UCASE$(sKey$$), keyshift, retButton)
      
                      ' Register the found keyshift and ascKey values
                      KeyMouse.keyShift = keyShift
                      KeyMouse.ascKey1 = ASC(sKey$$, 1)                           ' If len = 1, .ascKey2 = -1
                      IF LEN(sKey$$) = 2 THEN KeyMouse.ascKey2 = ASC(sKey$$, 2)   ' If len = 2, .ascKey1 =  0
                  LOOP
              ELSEIF hFg = hGraphicWindow(nGW) THEN                           ' ... or on the current top button window
          #ELSE                                                               ' And this with PB/Win
              IF hFg = hGraphicWindow(nGW) THEN
          #ENDIF                                                              ' From here on with any compiler
      
                  ' Check events on button window and execute them
                  ' Use the local correction routine for key events in the GW
                  CALL GWInkey(grInstat, sKey$$, keyshift)
      
                  IF ISTRUE grInstat THEN
                      sKey$$ = UCASE$(sKey$$)
                      CALL ExecutePressedKeys(sKey$$, keyshift, retButton)    ' Now execute the sKey$$
                  ELSE
                      ' Check for mouse clicks on button window (where is the buffer?)
                      GWInMouse(hGraphicWindow(nGW), %VK_LBUTTON, clicked, pressed, xmouse, ymouse)       ' Replacement for GRAPHIC WINDOW CLICK
                      IF clicked = 1 THEN
                          CALL ExecuteMouseClicks(xmouse, ymouse, retButton)
                      ELSE
      
                          ' No left clicks detected. Now try right clicks (Context clicks)
                          GWInMouse(hGraphicWindow(nGW), %VK_RBUTTON, clicked, pressed, xmouse, ymouse)   ' Check for right mouse released ...
                          IF clicked = -1 THEN
                              CALL ExecuteMouseClicks(xmouse, ymouse, retButton)
                          END IF
      
                      END IF
                  END IF
              ELSE
                  ' Key strokes collected in a GW <> nGW get flushed
                  GRAPHIC INPUT FLUSH
              END IF
      
      END SUB ' GraphicGetEvents
      
      '----------------------------------------------------------------------------
      
      SUB GWGetAbsClientPos(BYVAL hGW AS DWORD, x AS LONG, y AS LONG)
          ' V0.5
          ' Retrieve the absolute screen coordinates of the GW's client area origin
      
          ' Input:    hGW     => Handle of GW
          ' Output:   x, y    => Origin of GW client in absolute screen pixels
      
          LOCAL lPoint    AS POINTAPI                     ' Defined in Win32Api.inc
      
          lPoint.x = 0
          lPoint.y = 0
          ClientToScreen(hGW, lPoint)                     ' API call
          x = lPoint.x
          y = lPoint.y
      
      END SUB ' GWGetAbsClientPos
      
      '----------------------------------------------------------------------------
      
      FUNCTION GWCheckClosed() AS LONG
          ' V1.0
          ' To be called from GraphicGetEvents() and GraphicEditText()
          ' Check if the BaseApplication is still alive. If not, close up everything.
      
          ' Input:    -none-
          ' Output:   -none-
      
          ' Check if the BaseApplication got closed externally
          IF IsWindow(BaseApplication.h) = 0 THEN ExitProcess(1)      ' API call ExitProcess = END  -> PB cleans up the rest
      
      END FUNCTION    ' GWCheckClosedGW
      
      '----------------------------------------------------------------------------
      
      FUNCTION GWCheckFocus() AS LONG
          ' V1.0
          ' If Console/BaseApplication is minimized, hide all button GWs (= Hidden)
          ' If the Console/BaseApplication has the focus, the GW become TOPMOST and are all shown in their order (= OnTop).
          ' If some other window has the focus, the GWs loose their TOPMOST status, but don't get hidden ( = Covered).
      
          ' The following actions are done:
          '   case  |    1    |    2    |    3    |
          ' --------+---------+---------+---------+
          '   \ new | Hidden  | Covered |  OnTop  |
          ' old \   |         |         |         |
          ' --------+---------+---------+---------+
          ' Hidden  |   ---   |   n.a.  | TOPMOST |
          ' --------+---------+---------+---------+
          ' Covered |  HIDE   |   ---   | TOPMOST |
          ' --------+---------+---------+---------+
          ' OnTop   |  HIDE   |NOTOPMOST|  ---    |
          ' --------+---------+---------+---------+
      
          ' There are two special checks.
          ' 1a: As soon as a GW gets minimized, it gets automatically maximized again.
          ' 3a: If lower GW gets the focus (by clicking it or through the task bar icon), the focus is given to the top GW immediately.
      
          ' Input:    -none-
          ' Output:   FUNCTION = 1  =>  A change has occurred
          '                    = 0  =>  No change
      
          STATIC oldFocus     AS LONG             ' Last status
          LOCAL newFocus      AS LONG             ' Requested status
          LOCAL hFG           AS DWORD            ' Handle of window in focus (Foreground)
          LOCAL i             AS LONG             ' Index
      
          ' Initialize oldFocus at beginning (only once)
          IF oldFocus = 0 THEN oldFocus = %GW_OnTop
          FUNCTION = 0                                                    ' Default
      
          IF ISTRUE IsIconic(BaseApplication.h) THEN                      ' API call - Check if minimized
              ' Case 1a: BaseApplication is minimized
              newFocus = %GW_Hidden                                       ' BaseApplication iconized (= minimized)
      
              IF oldFocus = newFocus THEN
                  GOTO doNothing:
              ELSE
                  GOTO doFocus:                                           ' Minimize all GWs
              END IF
          END IF
      
          ' If any GW has been minimized, maximize it again
          FOR i = 0 TO nGW
              IF hGraphicWindow(i) = BaseApplication.h THEN ITERATE FOR   ' Do not check the BaseApplication again
              IF ISTRUE IsIconic(hGraphicWindow(i)) THEN                  ' API call
                  ' Case 1b: Another GW minimized
                  GRAPHIC WINDOW NORMALIZE hGraphicWindow(i)              ' Instead of OpenIcon()
                  FUNCTION = 1
                  GOTO doNothing:
              END IF
          NEXT i
      
          ' Nothing is iconized. Which window is in focus?
              hFg = GetForegroundWindow()                                 ' API call
              hFg = GetForegroundWindow()                                 ' API call
      
          IF hFg = BaseApplication.h OR hFg = hGraphicWindow(nGW) THEN    ' Check which is in focus
              ' Case 3a: All of them should be on top
              newFocus = %GW_OnTop
      
              IF oldFocus = newFocus THEN
                  GOTO doNothing:
              ELSE
                  GOTO doFocus:
              END IF
          END IF
      
          ' Is there possibly another GW in focus?
          FOR i = 0 TO nGW - 1                                            ' Check each button window in between (not nGW, the top GW)
              IF hGraphicWindow(i) = BaseApplication.h THEN ITERATE FOR
              IF ISTRUE hGraphicWindow(i) = hFg THEN
                  ' Case 3b: Attempt to get a lower GW into focus
                  ' A GW in between cannot get the focus, set it to nGW
                  GWForceSetForegroundWindow(hGraphicWindow(nGW))         ' Force nGW in focus. Keep oldFocus (Win2k bug)
                  FUNCTION = 1
                  GOTO doNothing:
              END IF
          NEXT i
      
          ' Case 2: BaseApplication and GWs are covered
          newFocus = %GW_Covered
          IF newFocus = oldFocus THEN GOTO doNothing:
      
          ' Now Hide (NOTOPMOST, HIDE), Show Covered (NOTOPMOST, SHOW) or Show OnTop (TOPMOST, SHOW)
          doFocus:
      
          SELECT CASE LONG newFocus
              CASE %GW_Hidden
                  ' Hide all GW
                  FOR i = 0 TO nGW
                      IF hGraphicWindow(i) = BaseApplication.h THEN ITERATE FOR                                                   ' BaseApplication is already hidden
                      SetWindowPos(hGraphicWindow(i), %HWND_NOTOPMOST, 0, 0, 0, 0, %SWP_NOSIZE + %SWP_NOMOVE + %SWP_HIDEWINDOW)   ' API call. Hide all button windows
                  NEXT i
      
              CASE %GW_Covered
                  ' Take TOPMOST away from all (BaseApplication to nGW)
                  SetWindowPos(BaseApplication.h, %HWND_NOTOPMOST, 0, 0, 0, 0, %SWP_NOSIZE + %SWP_NOMOVE + %SWP_SHOWWINDOW)       ' API call
      
                  FOR i = 0 TO nGW
                      IF hGraphicWindow(i) = BaseApplication.h THEN ITERATE FOR                                                   ' Do not hide the BaseApplication
                      SetWindowPos(hGraphicWindow(i), %HWND_NOTOPMOST, 0, 0, 0, 0, %SWP_NOSIZE + %SWP_NOMOVE + %SWP_SHOWWINDOW)   ' API call
                  NEXT i
                  ' Show the window in focus on top of the others
                  ShowWindow(hFg, %SW_SHOWNA)                                                                                     ' API call
      
              CASE %GW_OnTop
                  ' First get BaseApplication back to top
                  SetWindowPos(BaseApplication.h, %HWND_TOPMOST, 0, 0, 0, 0, %SWP_NOSIZE + %SWP_NOMOVE + %SWP_SHOWWINDOW)         ' API call. Set Console TOPMOST in order to bring it to the TOP!!
      
                  ' Then all the GW - in that order
                  FOR i = 0 TO nGW
                      ' Set the focus sequence
                      IF hGraphicWindow(i) = BaseApplication.h THEN ITERATE FOR
                      GWForceSetForegroundWindow(hGraphicWindow(i))                                                               ' Force hGraphicWindow(i) in focus. Keep oldFocus (Win2k bug)
                      SetWindowPos(hGraphicWindow(i), %HWND_TOPMOST, 0, 0, 0, 0, %SWP_NOSIZE + %SWP_NOMOVE + %SWP_SHOWWINDOW)     ' API call. The other GWs should be on top!
                  NEXT i
      
                  ' ... and set to NoTopMost
                  SetWindowPos(BaseApplication.h, %HWND_NOTOPMOST, 0, 0, 0, 0, %SWP_NOSIZE + %SWP_NOMOVE + %SWP_SHOWWINDOW)       ' API call. Take TOPMOST from Console
      
                  ' Reset the focus to the BaseApplication if needed
                  IF hFg = BaseApplication.h THEN GWForceSetForegroundWindow(hFg)                                                 ' Force hFg in focus. Keep oldFocus (Win2k bug)
      
          END SELECT
      
          oldFocus = newFocus                                             ' Remember the newFocus for next round
          BaseApplication.Focus = newFocus
          FUNCTION = 1                                                    ' A change has occurred
          GRAPHIC REDRAW
          EXIT FUNCTION
      
          ' No change in status
          doNothing:
      
      END FUNCTION    ' GWCheckFocus
      
      '----------------------------------------------------------------------------
      
      FUNCTION GWLockBaseAppl() AS LONG
          ' V0.5
          ' Move all button windows with the Console/BaseApplication
      
          ' Input:    -none-
          ' Output:   Function = 1  =>  Console/BaseApplication and GW moved
          '                    = 0  =>  Not moved
      
          LOCAL i                 AS LONG                 ' Index
          LOCAL x1, y1            AS LONG                 ' Coordinates of moved Console/BaseApplication
          LOCAL xdelta, ydelta    AS LONG                 ' No. of pixels the BaseApplication is shifted
      
          IF GetForegroundWindow() <> BaseApplication.h THEN EXIT FUNCTION    ' API call
      
          ' Console/BaseApplication gets moved: lock the GW to move with it
          CALL GWGetAbsClientPos(BaseApplication.h, x1, y1)
          xdelta = x1 - BaseApplication.x
          ydelta = y1 - BaseApplication.y
          BaseApplication.x = x1                                      ' Store new Console/BaseApplication position
          BaseApplication.y = y1
      
          IF xdelta <> 0 OR ydelta <> 0 THEN                          ' Console/BaseApplication has moved
              FOR i = 0 TO nGW                                        ' Move all button windows
                  GWData(i).x = GWData(i).x + xdelta                  ' Add the delta to the absolute position - needed for hover calculations
                  GWData(i).y = GWData(i).y + ydelta
                  GRAPHIC ATTACH hGraphicWindow(i), 0, REDRAW
                  IF hGraphicWindow(i) = BaseApplication.h THEN
                      GRAPHIC REDRAW
                      ITERATE FOR
                  END IF
                  ' Move the others
                  GRAPHIC GET LOC TO x1, y1                           ' This also works with caption!
                  GRAPHIC SET LOC x1 + xdelta, y1 + ydelta
                  GRAPHIC REDRAW                                      ' Otherwise parts of the GW do stay white, when it was moved outside the screen ...
              NEXT i
      
              FUNCTION = 1
          END IF
      
      END FUNCTION    ' GWLockBaseAppl
      
      '----------------------------------------------------------------------------
      
      FUNCTION GWCheckHover() AS LONG
          ' V1.0
          ' Detect the mouse position and highlight the button if it is within its boundaries
          ' Without subclassing the GW we get the absolute cursor positions on the screen.
          ' Therefore the coordinates of the GW's client area have to be subtracted
          ' Index of the hovered button is stored in: GWData(nGW).iHighlight
      
          ' Input:    -none-
          ' Output:   Function =  0  =>  No action
          '                    =  1  =>  A new button gets highlighted
          '                    = -1  =>  A button not highlighted any more (de-highlighted)
      
          LOCAL lpPoint           AS POINTAPI             ' Pointer type defined in Win32Api
          LOCAL i                 AS LONG                 ' Index
          LOCAL x, y              AS LONG                 ' Mouse x and y position
          LOCAL x0, x1, y0, y1    AS LONG                 ' Button Coordinates
      
          ' Retrieve mouse coordinates (also in a moved GW)
          GetCursorPos(lpPoint)                                       ' API call
          ScreenToClient(hGraphicWindow(nGW), lpPoint)                ' Convert to relative cursor coordinates of the selected client GW
          x = lpPoint.x
          y = lpPoint.y
      
          ' Has the mouse left the graphic window?
          IF x < 0 OR y < 0 OR x > GWData(nGW).WIDTH OR y > GWData(nGW).Height THEN EXIT FUNCTION
      
          ' Go through all buttons in this GW
          FOR i = GWData(nGW).iGBStart TO UBOUND(GraphicButtons())
              IF GraphicButtons(i).Disable = 1 THEN ITERATE FOR
              ' Do not show hover for LE that are for display only
              IF GraphicButtons(i).TYPE = %GW_LineEntry AND GraphicButtons(i).ClearText = -1 THEN ITERATE FOR
      
              x0 = GraphicButtons(i).x
              y0 = GraphicButtons(i).y
              x1 = x0 + GraphicButtons(i).WIDTH
              y1 = y0 + GraphicButtons(i).Height
      
              ' Use different coordinates for TickBoxes
              IF GraphicButtons(i).TYPE = %GW_TickBox AND %DeltaFocus >= 0 THEN
                  x0 = x0 - %DeltaFocus
                  y0 = y0 - %DeltaFocus
                  x1 = x1 + (1 + MAX(1, %OffsetTBText)/4) * GraphicButtons(i).Height + %DeltaFocus - 1
                  y1 = y1 + %DeltaFocus - 1
              END IF
      
              ' Highlight Button/Tickbox/LineEntry under the mouse
              IF (x >= x0) AND (x <= x1) AND (y >= y0) AND (y <= y1) THEN
                  IF GWData(nGW).iHighlight <> i THEN                 ' A new button under mouse
                      ' Redraw previously highlighted Button/Tickbox/LineEntry in normal state
                      IF GWData(nGW).iHighlight <> -1 THEN CALL GraphicDrawButton(GWData(nGW).iHighlight)
                      ' Draw a highlighted Button/Tickbox/LineEntry
                      CALL GraphicHighlightButton(i)
                      GWData(nGW).iHighlight = i                      ' Store index of highlighted button
                      FUNCTION = 1
                  END IF
                  EXIT FUNCTION
              END IF
          NEXT i
      
          ' We are now outside a Button/Tickbox/LineEntry. Was any Button/Tickbox/LineEntry previously drawn as highlighted?
          IF GWData(nGW).iHighlight <> -1 THEN
              ' Draw the previous Button/Tickbox/LineEntry in its normal state
              CALL GraphicDrawButton(GWData(nGW).iHighlight)
              GWData(nGW).iHighlight = -1
              FUNCTION = -1
          END IF
      
      END FUNCTION    ' GWCheckHover
      
      '----------------------------------------------------------------------------
      
      FUNCTION GWShowHotKeys() AS LONG
          ' V0.9
          ' Show the .HotKey text strings instead of .Text for all buttons in the top GW after
          ' pressing a key combination which normally does not occur:   Shift + Ctrl
          ' This routine calls the GraphicDrawButton version with an optional useHotKey parameter = 1.
          ' Note: Ideally this should be triggered by a right mouse-click which PB/CC 5 cannot detect.
      
          ' Input:    -none-
          ' Output:   FUNCTION = 1  =>  HotKeys shown
          '                    = 0  =>  no action
      
          LOCAL pressed           AS LONG                 ' Flag if key combination is pressed
          STATIC waspressed       AS LONG                 ' Flag if key combination was pressed
          LOCAL keyshift          AS LONG                 ' Store GWInstat
          LOCAL i                 AS LONG                 ' Index
      
          keyshift = GWInshift()                                      ' Check if keys are pressed
          ' Either Ctrl key plus Shift key must be pressed
          IF (BIT(keyshift, 3) = 1 OR BIT(keyshift, 2) = 1) AND BIT(keyshift, 4) = 1 THEN pressed = 1
      
          IF pressed = 1 THEN
              IF waspressed = 0 THEN
                  ' Show .HotKeys
                  FOR i = GWData(nGW).iGBStart TO UBOUND(GraphicButtons())
                      ' Do not show HotKeys for LE in display only mode
                      IF GraphicButtons(i).TYPE = %GW_LineEntry AND GraphicButtons(i).ClearText = -1 THEN
                          CALL GraphicDrawButton(i, 0)                    ' Optional useHotKey = 0
                      ELSE
                          CALL GraphicDrawButton(i, 1)                    ' Optional useHotKey = 1
                      END IF
                  NEXT i
                  waspressed = 1
              END IF
      
          ELSE ' pressed = 0
              IF waspressed = 1 THEN
                  ' Show .Text
                  FOR i = GWData(nGW).iGBStart TO UBOUND(GraphicButtons())
                      CALL GraphicDrawButton(i, 0)                    ' Optional useHotKey = 0 (could be left out)
                  NEXT i
                  waspressed = 0
                  GWData(nGW).iHighlight = -1                         ' Turn it off for new hover
              END IF
          END IF
      
          FUNCTION = pressed
      
      END FUNCTION    ' GWShowHotKeys
      
      '------------------------------------------------------------
      
      SUB GWInKey(lInstat AS LONG, sKey$$, keyShift AS INTEGER)
          ' V1.2
          ' Replace GRAPHIC INKEY$$ of PB/CC 6 and PB/Win 10 to correct some weaknesses in returning keys and status of Shift/Ctrl/Alt as well as the Lock keys.
          ' The main intention is to allow using the control keys in PB GRAPHIC applications as they were available in console applications (and many Windows applications).
          ' GWInkey() may be used in a TXT. window but no keys get corrected because many key strokes do not produce an event (e.g. Fxx and cursor keys)
          '
          ' Corrections:
          '       - The function GWInshift() returns the same information on a GRAPHIC WINDOW as INSHIFT does on the Console in PB/CC. See (1) below.
          '       - Generation of a key event is prevented when Right Alt (= Alt Gr) or Right Ctrl are pressed alone. See (2) below.
          '       - TAB gets recognized and returns same codes as in PB/CC. See (3) below.
          '       - The correct scan codes get generated, when Alt is pressed with keys 1-9, A-Z, F1-F12, BS and all cursor keys. See (4), (5), (6), (7) and (8) below.
          '       - Ctrl 0-9 get returned as in PB/CC. See (9) below.
          '
          ' Note: - The corrected keys do not get auto-repeated when a key stays pressed!
          '       - No multiple shift keys get corrected (such as Ctrl + Alt + key).
          '       - Special Windows keys are not effected.
          '       - Neither accent keys are effected (´`^), nor other country specific key, such as Umlaut etc.
      
          ' Input:    -none-
          ' Output:   lInstat  =>  Flag indicating a key has been pressed
          '           sKey$$   =>  Return key string of length 1 or 2
          '           keyShift =>  Status of Shiftkeys (same as INSHIFT on Console).
      
          LOCAL k             AS INTEGER          ' Key status
          STATIC kDown        AS LONG             ' Store up/down status of A-Z
          LOCAL isRAlt        AS LONG             ' Flag for Right Alt
          LOCAL isLAlt        AS LONG             ' Flag for Left Alt
          LOCAL isRCtrl       AS LONG             ' Flag for Right Ctrl
          LOCAL isLCtrl       AS LONG             ' Flag for Left Ctrl
          LOCAL isShift       AS LONG             ' Flag for either Shift
          LOCAL isNumLock     AS LONG             ' Flag for Num Lock
          LOCAL i             AS LONG             ' Index
      
          SLEEP 0                                                     ' Allow other processes ...
      
          ' ---------------------------------------------
          ' (1) Calculate the keyShift status in any case
          ' ---------------------------------------------
          keyShift = GWInShift()                                      ' Status of Shift/Ctrl/Alt through own routine
      
          IF BIT(keyShift, 0) = 1  THEN                               ' Right Alt is pressed - and bit 3 (= Left Ctrl) is also set (why ??)
              BIT RESET keyShift, 3                                   ' Reset Left Ctrl to 0 (on the console it is kept ...)
              isRAlt = %true
          END IF
          IF BIT(keyShift, 1) = 1  THEN isLAlt = %true                ' Left Alt is pressed
          IF BIT(keyShift, 2) = 1  THEN isRCtrl = %true               ' Right Ctrl is pressed
          IF BIT(keyShift, 3) = 1  THEN isLCtrl = %true               ' Left Ctrl is pressed
          IF BIT(keyShift, 4) = 1  THEN isShift = %true               ' Either Shift is pressed
          IF BIT(keyShift, 5) = 1  THEN isNumLock = %true             ' Num Lock is on and lit
      
          ' -----------------------
          ' Do the standard polling
          ' -----------------------
          GRAPHIC INSTAT TO lInstat
          IF ISFALSE lInstat THEN
              lInstat = TXT.INSTAT                                    ' When used in a TXT. window try to get a response
          END IF
      
          IF ISTRUE lInstat THEN
              sKey$$ = ""
              GRAPHIC INKEY$ TO sKey$$
              IF sKey$$ = "" THEN sKey$$ = TXT.INKEY$                 ' Only standard A-Z keys are supported in a TXT. window.
      
              ' -----------------------------------------------------------------------------------------------------------------------------------
              ' (2) Prevent repetition when Right Alt or Right Ctrl get pressed alone. Evaluated the additionally pressed key in the next round ...
              ' -----------------------------------------------------------------------------------------------------------------------------------
              IF ISTRUE isRAlt AND ASC(sKey$$, 2) = 56 OR ISTRUE isRCtrl AND ASC(sKey$$, 2) = 29 THEN
                  lInstat = %false
                  sKey$$ = ""
                  GRAPHIC INPUT FLUSH                                 ' Flush any stored key data
              END IF
      
              GOTO exitSub:                                           ' Leave the routine if GRAPHIC INPUT has got a keystroke
          END IF  ' ISTRUE lInstat
      
          ' -------------------------------------
          ' No event returned from GRAPHIC INSTAT
          ' -------------------------------------
          IF kDown = 0 THEN                                           ' Filter the key status, if no key was pressed before
      
              ' ------------------------
              ' (3) Correct code for TAB
              ' ------------------------
              k = GetAsyncKeyState(%VK_TAB)                           ' Check status of TAB key               API call
              IF BIT(k, 15) = 1 THEN                                  ' MSB set => pressed
                  IF ISTRUE (isLCtrl OR isRCtrl OR isRAlt) THEN
                      sKey$$ = CHR$$(0, 15)                           ' Either Ctrl or Right Alt is pressed
                  ELSE
                      sKey$$ = CHR$$(9)                               ' Normal or Shift
                  END IF
                  kDown = %VK_TAB                                     ' Remember that it was pressed
                  lInstat = %true
                  GOTO exitSub:
              END IF
      
              ' Check only if Left Alt is pressed
              IF ISTRUE isLAlt THEN
      
                  ' ----------------------------
                  ' (4) Correct codes of Alt A-Z
                  ' ----------------------------
                  FOR i = %VK_A TO %VK_Z                              ' Go through 26 characters (65 - 90)
                      k = GetAsyncKeyState(i)                         ' Check status of keys A-Z
                      IF BIT(k, 15) = 1 THEN                          ' MSB set => pressed
                          sKey$$ = CHR$$(0, MapVirtualKey(i, 0))      ' Get scan code for virtual key code (do not distinguish between right and left keys)
                          kDown = i                                   ' Remember which key was pressed
                          lInstat = %true
                          GOTO exitSub:
                      END IF
                  NEXT i
      
                  ' ----------------------------
                  ' (5) Correct codes of Alt 0-9
                  ' ----------------------------
                  FOR i = %VK_0 TO %VK_9                              ' Go through numbers 0-9 (48 - 57)
                      k = GetAsyncKeyState(i)                         ' Check status of keys 0-9
                      IF BIT(k, 15) = 1 THEN                          ' MSB set => pressed
                          sKey$$ = CHR$$(MapVirtualKey(i, 2))         ' Get scan code for virtual key code (translated into an unshifted character value
                                                                      ' in the low-order word of the return value)
                          kDown = i                                   ' Remember which key was pressed
                          lInstat = %true
                          GOTO exitSub:
                      END IF
                  NEXT i
      
                  ' ---------------------------------
                  ' (6) Correct codes of Alt F1 - F12
                  ' ---------------------------------
                  IF ISFALSE isRAlt THEN                              ' No change with Right Alt
                      FOR i = %VK_F1 TO %VK_F12                       ' Go through F1-F12
                          IF i = %VK_F10 THEN ITERATE FOR             ' F10 works without correction
                          k = GetAsyncKeyState(i)                     ' Check status of keys F1-F12
                          IF BIT(k, 15) = 1 THEN                      ' MSB set => pressed
                              sKey$$ = CHR$$(0, MapVirtualKey(i, 0))  ' Get scan code for virtual key code (do not distinguish between right and left keys)
                              kDown = i                               ' Remember which key was pressed
                              lInstat = %true
                              GOTO exitSub:
                          END IF
                      NEXT i
                  END IF
      
                  ' ---------------------------------------------------------------------------------
                  ' (7) Correct Alt Cursor keys (INS, Del, Home, End, Page up/dn, Cursor le/up/ri/dn)
                  ' ---------------------------------------------------------------------------------
                  FOR i = %VK_PRIOR TO %VK_DELETE                     ' Go through Cursor keys (33 - 46)
                      k = GetAsyncKeyState(i)                         ' Check status of keys F1-F9
                      IF BIT(k, 15) = 1 THEN                          ' MSB set => pressed
                          sKey$$ = CHR$$(0, MapVirtualKey(i, 0))      ' Get scan code for virtual key code (do not distinguish between right and left keys)
                          kDown = i                                   ' Remember which key was pressed
                          lInstat = %true
                      END IF
                  NEXT i
      
                  ' ---------------------------
                  ' (8) Correct code for Alt BS
                  ' ---------------------------
                  k = GetAsyncKeyState(%VK_BACK)                      ' Check status of BS key
                  IF BIT(k, 15) = 1 THEN                              ' MSB set => pressed
                      sKey$$ = $$BS                                   ' Fujitsu/Siemens Keyboard returns $$BS (8)
                      kDown = %VK_BACK                                ' Remember which key was pressed
                      lInstat = %true
                      GOTO exitSub:
                  END IF
      
              END IF  ' ISTRUE isalt
      
              ' ------------------------------
              ' (9) Correct codes for Ctrl 0-9
              ' ------------------------------
              IF ISTRUE (isRCtrl OR isLCtrl) THEN
                  FOR i = %VK_0 TO %VK_9                              ' Go through numbers 0-9
                      k = GetAsyncKeyState(i)                         ' Check status of keys 0-9
                      IF BIT(k, 15) = 1 THEN                          ' MSB set => pressed
                          sKey$$ = CHR$$(0, MapVirtualKey(i, 0))      ' Get scan code for virtual key code (do not distinguish between right and left keys)
                          kDown = i                                   ' Remember which key was pressed
                          lInstat = %true
                          GOTO exitSub:
                      END IF
                  NEXT i
      
              ' --- not used ----------------
              ' (10) Correct codes for Ctrl+I                         ' Ctrl+I should not be used because it may switch windows
              ' --- not used ----------------
      
              END IF  ' ISTRUE isctrl
      
          ELSE    'kDown > 0                                          ' Release the last pressed key
              k = GetAsyncKeyState(kDown)                             ' Check status of pressed key
              IF BIT(k, 15) = 0 THEN                                  ' MSB not set => released
                  kDown = 0                                           ' Remember that it was released
                  lInstat = %false                                    ' Emulate Instat
              END IF
          END IF  ' kDown = 0
          sKey$$ = ""                                                 ' No event and no correction => return empty string
      
          exitSub:                                                    ' Jump here instead of EXIT SUB
      
          ' --------------------------------------
          ' Register the found keyshift values ...                    '*   <- These statements should be left out when used standalone (= without GrButtons.inc)
          ' --------------------------------------                    '*      unless the KeyMouse UDT has been defined.
          KeyMouse.keyShift = keyShift                                '*
                                                                      '*
          ' ... and the ascKeys                                       '*
          IF ISTRUE lInstat THEN                                      '*
              ' A key has been pressed                                '*
              KeyMouse.ascKey1 = ASC(sKey$$, 1)                       '*
              IF LEN(sKey$$) = 1 THEN                                 '*
                  KeyMouse.ascKey2 = -1                               '*
              ELSE                                                    '*
                  KeyMouse.ascKey2 = ASC(sKey$$, 2)                   '*
              END IF                                                  '*
          ELSE                                                        '*
              ' No key pressed                                        '*
              KeyMouse.ascKey1 = -1                                   '*
              KeyMouse.ascKey2 = -1                                   '*
          END IF                                                      '*
      
      END SUB ' GWInKey
      
      '-------------------------------------------------------------------
      
      FUNCTION GWInShift() AS INTEGER
          ' V0.9
          ' Get the status of Shift, Ctrl, Alt and the Lock keys as in the INSHIFT function
          ' Note: When Alt Gr is pressed keyshift 9 is returned (BIT 0 and BIT 3 are set - why ??)
      
          ' Value of keyshift:
          ' Bit  Value  Definition       Virtual-Key code from Win32Api
          '-----------------------------------------------------
          '  0     1    Right-Alt        %VK_RMENU                    = 165
          '  1     2    Left-Alt         %VK_LMENU                    = 164
          '  2     4    Right-Ctrl       %VK_RCONTROL                 = 163
          '  3     8    Left-Ctrl        %VK_LCONTROL                 = 162
          '  4    16    Shift            %VK_RSHIFT or %VK_LSHIFT     = 161 or 160
          '  5    32    Numlock          %VK_NUMLOCK                  = 144
          '  6    64    Scroll Lock      %VK_SCROLL                   = 145
          '  7   128    Caps Lock        %VK_CAPITAL                  =  20
      
      
          LOCAL keyShift      AS INTEGER          ' Status Byte
          LOCAL sR, sL        AS INTEGER          ' Shift key status
      
          keyShift = 0                                                ' Default
      
          sR = GetAsyncKeyState(%VK_RMENU)                            ' Check if Right Alt is pressed     ' API call
          IF BIT(sR, 15) = 1 THEN BIT SET keyShift, 0
      
          sL = GetAsyncKeyState(%VK_LMENU)                            ' Check if Left Alt is pressed
          IF BIT(sL, 15) = 1 THEN BIT SET keyShift, 1
      
          sR = GetAsyncKeyState(%VK_RCONTROL)                         ' Check if Right Control is pressed
          IF BIT(sR, 15) = 1 THEN BIT SET keyShift, 2
      
          sL = GetAsyncKeyState(%VK_LCONTROL)                         ' Check if Left Control is pressed
          IF BIT(sL, 15) = 1 THEN BIT SET keyShift, 3
      
          sR = GetAsyncKeyState(%VK_RSHIFT)                           ' Check if Right Shift is pressed
          sL = GetAsyncKeyState(%VK_LSHIFT)                           ' Check if Left Shift is pressed
          ' Either MSB = 1  =>  set the SHIFT BIT
          IF (BIT(sR, 15) = 1) OR (BIT(sL, 15) = 1) THEN BIT SET keyShift, 4
      
          sR = GetKeyState(%VK_NUMLOCK)                               ' Check if Num Lock is toggled (= on and lit)
          IF BIT(sR, 0) = 1 THEN BIT SET keyShift, 5
      
          sR = GetKeyState(%VK_SCROLL)                                ' Check if Scroll Lock is toggled (= on and lit)
          IF BIT(sR, 0) = 1 THEN BIT SET keyShift, 6
      
          sL = GetKeyState(%VK_CAPITAL)                               ' Check if Caps Lock is toggled (= on and lit)
          IF BIT(sL, 0) = 1 THEN BIT SET keyShift, 7
      
          FUNCTION = keyShift                                         ' Return value
      
      END FUNCTION    'GWInShift
      
      '------------------------------------------------------------
      
      SUB GWInMouse  (BYVAL hGWInMouse AS DWORD, _
                      BYVAL nBtn AS LONG, _
                      clicked AS LONG, _
                      btnDown AS LONG, _
                      xMouse AS SINGLE, _
                      yMouse AS SINGLE)
          ' V1.2
          ' Different specified mouse buttons may be called in the same DO - LOOP.
          ' New in V1.0: Key.doubleTimer recorded. Start with first click. Ends GetDoubleClickTime() after last click.
          ' Every second click is a double click as long as it happens within the double click time.
          ' Release of a button is flagged by clicked = -1.
          ' Clicks in wrong window cause return values to be set to 0
          ' Return values are always related to the requested button (nBtn)
      
          ' V0.9
          ' This version differs from the 08. April 10 release: KeyMouse. values are filled in here
          ' Detect mouse events of all three mouse buttons: down, clicked once or twice.
          ' The mouse coordinates are returned with every call.
      
          ' Input:    hGWInMouse          =  Handle of GW
          '           nBtn                =  Identify button 1 = %VK_LBUTTON (Left), 4 = %VK_MBUTTON (Middle), 2 = %VK_RBUTTON (Right)
          '                                  It is used as index 1 to 4 (3 not used).
          '
          ' Output:   clicked             =  0 => No event detected
          '                                  1 => One click (down)
          '                                  2 => Double click
          '                                 -1 => Button released
          '           btndown             =  0 => Button is up
          '                                  1 => Button is held down
          '           xMouse              =  x coordinate of mouse cursor in pixels
          '           yMouse              =  y coordinate of mouse cursor in pixels
      
          ' Global output:
          '           KeyMouse.Button         = nBtn
          '                   .Click(nBtn)    = clicked
          '                   .Down(nBtn)     = btnDown
          '                   .x              = xMouse
          '                   .y              = yMouse
          '                   .doubleTimer    = dcTimer
      
          LOCAL hFg               AS DWORD        ' Handle of foreground window
          LOCAL lpPoint           AS POINTAPI     ' Pointer type defined in Win32Api
          LOCAL k                 AS LONG         ' Key status
          DIM lastDown(1 TO 4)    AS STATIC LONG  ' State of mouse button after last call
          DIM nDown(1 TO 4)       AS STATIC LONG  ' Counter for mouse button down
          DIM startTime(1 TO 4)   AS STATIC DOUBLE' Store GetTickCount value in msec
          DIM dcTimer(1 TO 4)     AS STATIC LONG  ' Double click timer (re-triggered). It starts with a click and end double click time after the last click
          DIM outsideCL(1 TO 4)   AS STATIC LONG  ' Flag for having clicked in hGWInMouse, but outside client (e.g. caption bar)
      
          ' Which GW has the focus?
              hFg = GetForegroundWindow()
              hFg = GetForegroundWindow()
      
          ' Check, if in correct GW
          IF hFg <> hGWInMouse THEN
              btnDown = 0                                             ' Set return values to 0
              clicked = 0
              xMouse = 0
              yMouse = 0
      
          ELSE                                                        ' Correct GW
              ' Read cursor position in any case
              GetCursorPos(lpPoint)
              ScreenToClient(hGWInMouse, lpPoint)                     ' Convert to relative cursor coordinates of the selected client GW
              xMouse = lpPoint.x
              yMouse = lpPoint.y
      
              ' Which button is to be checked?
              SELECT CASE LONG nBtn
                  CASE %VK_LBUTTON, %VK_MBUTTON, %VK_RBUTTON          ' Only those 3 are allowed
      
                      ' Check status of selected mouse button
                      k = GetAsyncKeyState(nBtn)
                      btnDown =  BIT(k, 15)
      
                      ' Do not process clicks outside client area of hGWInMouse (e.g. on caption bar).
                      ' Clicks on resize buttons (SET VIRTUAL ...) are processed as normal clicks, though
                      IF outsideCL(nBtn) = 0 THEN
                          IF btnDown = 1 AND _                        ' First down and outside ...
                             (xMouse < 0 OR yMouse < 0 OR _
                              xMouse > GRAPHIC(CLIENT.X) - 1 OR yMouse > GRAPHIC(CLIENT.Y) - 1) THEN
                              outsideCL(nBtn) = 1
                              EXIT SELECT                             ' No return values changed
                          END IF
                      ELSE                                            ' Still outside
                          IF btnDown = 0 THEN                         ' Released => not flagged as outside anymore
                              outsideCL(nBtn) = 0                     ' Normal processing
                          ELSE
                              EXIT SELECT
                          END IF
                      END IF
      
                      ' Reset click counter when the DoubleClickTime period is over - independent of btnDown
                      IF GetTickCount - startTime(nBtn) > GetDoubleClickTime THEN
                          nDown(nBtn) = 0
                          dcTimer(nBtn) = 0                           ' Reset: double click time is over
                      END IF
      
                      ' Set clicked depending on last status
                      IF lastDown(nBtn) = 1 THEN
                          IF btnDown = 1 THEN
                              clicked = 0                             ' 1 -> 1 : Still down
                          ELSE
                              clicked = -1                            ' 1 -> 0 : Release
                          END IF
                      ELSE                                            ' lastDown(nBtn) = 0
                          IF btnDown = 1 THEN                         ' Button clicked
                              ' Count the new clicks during DoubleClickTime (maximum: 2)
                              INCR nDown(nBtn)                        ' 0 -> 1 : Clicked once or twice
                              SELECT CASE LONG nDown(nBtn)
                                  CASE 1
                                      clicked = 1
                                      startTime(nBtn) = GetTickCount  ' Start DoubleClickTime now
                                      dcTimer(nBtn) = 1
                                  CASE 2
                                      clicked = 2
                                      nDown(nBtn) = 0                 ' Reset to get a clicked = 1 at next click
                              END SELECT      ' nDown(nBtn)
                          ELSE                                        ' btnDown = 0
                              clicked = 0                             ' 0 -> 0 : Still up
                          END IF      ' btnDown
                      END IF      ' lastDown(nBtn)
                      lastDown(nBtn) = btnDown                        ' Remember last btnDown status
      
              END SELECT      ' nBtn
      
          END IF      ' hFg <> hGWInMouse
      
          ' -----------------------------------
          ' Register the found GWInMouse values                       '*   <- These statements should be left out when used standalone (= without GrButtons.inc)
          ' -----------------------------------                       '*      unless the KeyMouse UDT has been defined.
          KeyMouse.Button         = nBtn                              '*
          KeyMouse.Down(nBtn)     = btnDown                           '*
          KeyMouse.Click(nBtn)    = clicked                           '*
          KeyMouse.x              = xMouse                            '*
          KeyMouse.y              = yMouse                            '*
          KeyMouse.doubleTimer    = dcTimer(nBtn)                     '*      0 => DoubleClickTime is over, 1 => still on
      
      END SUB 'GWInMouse
      
      '------------------------------------------------------------
      
      FUNCTION GWCheckHotKeys(BYVAL j AS LONG, BYVAL sKey$$, BYVAL keyshift AS INTEGER) AS LONG
          ' V1.2
          ' Check if pressed keys correspond to .HotKeys of buttons
      
          ' Input:    j               =>  Index of button to process
          '           sKey$$          =>  Pressed key from INKEY$ (Console or GRAPHIC)
          '           keyshift        =>  Status of Shift/Ctrl/Alt keys pressed together with other keys
          ' Output:   FUNCTION = 0    =>  No valid key(s)  pressed
          '                    = 1    =>  Valid key(s) pressed
      
          ' Use a prefix for .HotKey to request a shift key.
          ' E.g.: "K" => just the K key,  "#F7" => Shift + F7, "^T" => Ctrl + T,  "@9" => Alt + 9, "^CR" => Ctrl + CR
      
          ' Possible combinations:
          '-------------------------------------------------------------------------------
          '                    \ Key    A - Z   0 - 9   F1 - F12    ESC    BS   CR    SPC
          '            Prefix   \
          '-------------------------------------------------------------------------------
          '            without           OK      OK       OK        OK     OK    -      -
          ' Shift:        #              OK       -       OK         -     OK    -      -
          ' Ctrl:         ^              OK      OK       OK         -     OK   OK     OK
          ' Left Alt:     @              OK      OK       OK         -     OK    -      -
          '-------------------------------------------------------------------------------
      
          LOCAL ok            AS LONG                     ' Flag
          LOCAL HotKey$$                                  ' String to be processed
          LOCAL result        AS LONG                     ' Return value
          LOCAL nkey          AS LONG                     ' Relevant part of ASC(keys)
          LOCAL ks            AS INTEGER                  ' Bits 0 - 4 of keyshift
          LOCAL i             AS LONG                     ' Index
          DIM AltAZ(0 TO 25)  AS STATIC LONG              ' Store the code for Alt A - Alt Z
          DIM loaded          AS STATIC LONG              ' Flag for assigning data to AltAZ
          LOCAL isshift       AS LONG                     ' Flag for either Shift
          LOCAL isctrl        AS LONG                     ' Flag for either Ctrl
          LOCAL isalt         AS LONG                     ' Flag for Left Alt
      
          ' Check, if index j is valid
          IF j < 0 OR j > UBOUND(GraphicButtons()) THEN EXIT FUNCTION
      
          result = 0                                                  ' Default
      
          ' Retrieve hot key string from button
          HotKey$$ = TRIM$(UCASE$(GraphicButtons(j).HotKey))
          IF HotKey$$ = "" THEN GOTO ExitFunct:
      
          ok = 1                                                      ' More defaults
          isshift = 0
          isctrl = 0
          isalt = 0
      
          ' First check if any shift key has been requested and pressed. If yes, remove the shift key from HotKey$$ and continue, else exit with FUNCTION = 0
          ks = keyshift AND &B00011111                                ' Mask bits for NumLock, ScrollLock and CapsLock
      
          SELECT CASE LEFT$(HotKey$$, 1)
              CASE "#"                                                ' Shift (either or both side(s) due to keyshift coding)
                  IF ks <> &B00010000 THEN GOTO ExitFunct:
                  isshift = 1
              CASE "^"                                                ' Left Ctrl or Right Ctrl
                  IF ks <> &B00001000 AND ks <> &B00000100 THEN GOTO ExitFunct:
                  isctrl = 1
              CASE "@"                                                ' Only Left Alt
                  IF ks <> &B00000010 THEN GOTO ExitFunct:
                  isalt = 1
              CASE ELSE
                  IF ks <> &B00000000 THEN GOTO ExitFunct:            ' No shift keys present
                  ok = 0
          END SELECT
          IF ok = 1 THEN HotKey$$ = RIGHT$(HotKey$$, LEN(HotKey$$) - 1)
      
          IF LEN(sKey$$) = 1 THEN
      
              IF LEN(HotKey$$) = 1 THEN
      
                  nkey = ASC(HotKey$$)
                  SELECT CASE LONG nkey
      
                       CASE 65 TO 90                                      '*** Keys: Normal and Ctrl A - Z
                          ' Check if one Ctrl is pressed - then subtract 64
                          IF isctrl = 1 THEN nkey = nkey - 64
                          IF nkey <> ASC(sKey$$) THEN GOTO ExitFunct:
                          result = 1
      
                       CASE 48 TO 57                                      '*** Keys: Normal and Alt 0 - 9
                          ' Check if Shift or any Ctrl is pressed - then exit
                          IF isshift = 1 THEN GOTO ExitFunct:             ' No Shift 0 - 9 allowed
                          IF nkey <> ASC(sKey$$) THEN GOTO ExitFunct:
                          result = 1
      
                  END SELECT
      
              ELSE                                                        ' LEN(HotKey$$) > 1
                                                                          ' Keys: ESC, BS, CR, SPC
                  ok = 1
                  nkey = ASC(sKey$$)
                  SELECT CASE HotKey$$
                      CASE "ESC"
                          IF ks <> 0 THEN GOTO ExitFunct:                 ' Only plain ESC
                          IF nkey <> ASC($$ESC) THEN GOTO ExitFunct:
                      CASE "BS"                                           ' Any shift key for BS
                          IF isctrl = 1 THEN nkey = nkey - 119            ' Correction for ^BS
                          IF nkey <> ASC($$BS) THEN GOTO ExitFunct:
                      CASE "CR"                                           ' Only Ctrl CR
                          IF ks = 0 OR isshift = 1 OR isalt = 1 THEN GOTO ExitFunct:
                          IF nkey <> 10 THEN GOTO ExitFunct:              ' ASC(^CR) = 10
                      CASE "SPC"                                          ' Only Ctrl SPC
                          IF ks = 0 OR isshift = 1 OR isalt = 1 THEN GOTO ExitFunct:
                          IF nkey <> ASC($$SPC) THEN GOTO ExitFunct:
                      CASE ELSE
                          ok = 0
                  END SELECT
                  result = ok
      
              END IF
      
          ELSEIF LEN(sKey$$) = 2 THEN
      
              nkey = ASC(sKey$$, 2)                                       ' Key code
              SELECT CASE LONG nkey
      
                  CASE 59 TO 68, 87, 88                                   '*** Keys: F1 - F12 (any shift key)
                      IF LEFT$(HotKey$$, 1) <> "F" THEN GOTO ExitFunct:   ' Allow only Fxx
                      IF nkey > 86 THEN nkey = nkey - 18                  ' Correction for F11 and F12
      
                      IF nkey - 58 <> VAL(RIGHT$(HotKey$$, LEN(HotKey$$) - 1)) THEN GOTO ExitFunct:
                      result = 1                                          ' ASC("F1") = (0, 59)
      
                  CASE 2 TO 11                                            '*** Keys: Ctrl 0-9 (the only ones with LEN(sKey$$) = 2)
                      IF LEN(HotKey$$) > 1 THEN GOTO ExitFunct:           ' Allow only 1 character
                      IF nkey = 11 THEN nkey = 1                          ' Correction for 0
                      IF nkey + 47 <> ASC(HotKey$$) THEN GOTO ExitFunct:  ' ASC(^1) = (0, 2), ASC(1) = 49
                      result = 1
      
                  CASE 16 TO 50                                           '*** Keys: Alt A-Z (the only ones with LEN(sKey$$) = 2)
                      ' Assign values only when called for first time
                      IF loaded = 0 THEN
                          ARRAY ASSIGN AltAZ() = 30,48,46,32,18,33,34,35,23,36,37,38,50,49,24,25,16,19,31,20,22,47,17,45,44,21
                          '                       A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z
                          loaded = 1
                      END IF
      
                      IF LEN(HotKey$$) > 1 THEN GOTO ExitFunct:           ' Allow only 1 character
                      i = ASC(HotKey$$) - 65                              ' Index: A = 0, B = 1, ... Z = 26
                      IF nkey <> AltAZ(i) THEN GOTO ExitFunct:
                      result = 1
      
              END SELECT
      
          END IF
      
          ExitFunct:
          FUNCTION = result                                               ' Return value
      
      END FUNCTION    ' GWCheckHotKeys
      
      '----------------------------------------------------------------------------
      
      FUNCTION GWCheckUnderChar(BYVAL j AS LONG, BYVAL sKey$$, BYVAL keyshift AS INTEGER) AS LONG
          ' V1.2
          ' Check if pressed keys correspond to .nUnderChar of .Text of buttons
          ' UCASE characters without shift keys are supported
      
          ' Input:    j               =>  Index of button to process
          '           sKey$$          =>  Pressed key from INKEY$ (Console or GRAPHIC)
          '           keyshift        =>  Status of Shift/Ctrl/Alt keys pressed together with other keys
          ' Output:   FUNCTION = 0    =>  No valid key(s)  pressed
          '                    = 1    =>  Valid key(s) pressed
      
          LOCAL underChar$$                               ' Character underlined in .Text of button
          LOCAL ks            AS INTEGER                  ' Bits 0 - 4 of keyshift
          LOCAL nunder        AS LONG                     ' Number of underlined bit
      
          ' First check if any shift - except Shift - key has been pressed
          ks = keyshift AND &B00001111                                ' Mask bits for Shift, NumLock, ScrollLock and CapsLock
      
          ' Only normal or Shift characters with length = 1 allowed
          IF ks <> 0 OR LEN(sKey$$) <> 1 THEN EXIT FUNCTION
      
          ' Check, if index j is valid
          IF j < 0 OR j > UBOUND(GraphicButtons()) THEN EXIT FUNCTION
      
          ' Get the relevant character and compare
          nunder = GraphicButtons(j).nUnderChar
          underChar$$ = UCASE$(MID$(TRIM$(GraphicButtons(j).TEXT), nunder, 1))
      
          IF UCASE$(sKey$$) = underChar$$ THEN FUNCTION = 1
      
      END FUNCTION    ' GWCheckUnderChar
      
      '----------------------------------------------------------------------------
      
      FUNCTION GWUpdateFont(BYVAL fName$$, BYVAL fSize AS SINGLE, BYVAL fStyle AS LONG) AS LONG
          ' V1.2
          ' Update the font with destroying the existing hFont in order to release memory.
          ' This method will prevent the font overflow
      
          ' Input:    fName$$ =>  Font name
          '           fSize   =>  Font size
          '           fStyle  =>  Font Style
          ' Output:   FUNCTION = ERR  => Return a possible Error 5 (Font still in use or Font Overflow)
      
          STATIC ncall    AS LONG                 ' Flag to count recursive calls in case of an Error 5
      
          GWKillFont
      
          FONT NEW fName$$, fSize, fStyle TO BaseApplication.hFont    ' hFont gets a new number
          GRAPHIC SET FONT BaseApplication.hFont                      ' Apply the new font
      
          FUNCTION = ERR                                              ' If Error 5 occurs, FONT END has not worked!
      
          IF ERR = 5 AND ncall = 0 THEN
              INCR ncall
              QueryBox " *** Error 5: Bad FONT management in GWUpdateFont()"
              ExitProcess(1)                                          ' Prevent recursive calls in case of an Error 5
          END IF
                                                                      ' Do not call QueryBox !
      END FUNCTION    'GWUpdateFont
      
      '----------------------------------------------------------------------------
      
      FUNCTION GWKillFont AS LONG
          ' V0.9
          ' Kill a previously generated font by GWUpdate. Thus the increase of hFont is prevented.
          ' Input:    - none -
          ' Output:   - none -
      
          IF BaseApplication.hFont > 2 THEN
              GRAPHIC SET FONT BaseApplication.hDefaultFont           ' Reset font in GW in order to kill .hFont
              FONT END BaseApplication.hFont
              BaseApplication.hFont = 0
          END IF
      
      END FUNCTION    'GWKillFont
      
      '----------------------------------------------------------------------------
      
      SUB ExecutePressedKeys(sKey$$, _
                             keyshift     AS INTEGER, _
                             retButton    AS LONG)
          ' V1.2
          ' Execute the pressed key (from Console/BaseApplication or button window).
          ' Attention: No TAB or SHIFT TAB from button window.
      
          ' Input:     sKey$$     =>  UCASE$$ of pressed key
          '            keyshift   =>  Status of Shift/Ctrl/Alt keys
          ' Output:    retButton  =>  .ID of activated button (not when focus moves!)
      
          LOCAL j, icf            AS LONG                 ' Indices of (current) buttons
      
          ' Attach top button window for coordinate calculations
          GRAPHIC ATTACH hGraphicWindow(nGW), 0, REDRAW
      
          ' Get index of current button with focus
          icf = GWGetCurrentFocus()
      
          ' Check for hot keys as defined in buttons
          FOR j = GWData(nGW).iGBStart TO UBOUND(GraphicButtons())
              IF GraphicButtons(j).Disable = 1 THEN ITERATE FOR
              ' Supported underlined characters see: FUNCTION GWCheckUnderChar()
              ' For hot keys see: FUNCTION GWCheckHotKeys()
              IF GWCheckUnderChar(j, skey$$, keyshift) = 0 AND GWCheckHotKeys(j, skey$$, keyshift) = 0 THEN ITERATE FOR
      
              ' Move the focus to this Button/TickBox/LineEntry as long as it is enabled for a focus
              IF j <> icf AND GraphicButtons(j).FOCUS > -1 THEN CALL GraphicMoveFocus(j, icf)
              retButton = GWActivateButtonID(j)                       ' Return this ID
      
              EXIT SUB                                                ' Prevent further checking once a hot key has been found
          NEXT j
      
          IF LEN(sKey$$) = 1 THEN
              SELECT CASE LONG ASC(sKey$$)
      
                  CASE 13, 32                                         ' CR, Space to execute Button/Tickbox/LineEntry with focus
                  ' Activation of focused button
                  retButton = GWActivateButtonID(icf)
      
                  CASE 9                                              ' TAB
                      IF BIT(keyshift, 4) = 1 THEN                    ' Shift TAB => move left / up
                          GWMoveFocusUp(icf)
                      ELSE                                            ' No SHIFT => move right / down
                          GWMoveFocusDown(icf)
                      END IF
      
                  CASE 27                                             ' Esc
                      IF GWData(nGW).ContextClick <> %GW_NoEvent THEN
                          GraphicDestroyWindow(GWData(nGW).ID)
                          KeyMouse.ascKey1 = 0                        ' Do not return a pressed key in this case ..
                      END IF
      
              END SELECT
      
          ELSEIF LEN(sKey$$) = 2 THEN
              ' Now check for movement of button focus
              SELECT CASE LONG ASC(sKey$$, 2)
      
                  CASE 77, 80                                         ' Cursor right, down
                      GWMoveFocusDown(icf)
      
                  CASE 75, 72                                         ' Cursor left, up
                      GWMoveFocusUp(icf)
              END SELECT
          END IF
      
      END SUB ' ExecutePressedKeys
      
      '----------------------------------------------------------------------------
      
      SUB ExecuteMouseClicks(x            AS SINGLE, _
                             y            AS SINGLE, _
                             retButton    AS LONG)
          ' V1.0
          ' Execute a button if the click has been in its boundary
          ' + detect Context clicks now.
      
          ' Input:    x, y       =>  Mouse coordinates of the click
          ' Output:   retButton  =>  .ID of the activated button
      
          LOCAL i         AS LONG                         ' Index for buttons
          LOCAL x0, y0    AS LONG                         ' coordinates
          LOCAL x1, y1    AS LONG                         ' coordinates
          LOCAL icf       AS LONG                         ' Index current button
      
          ' Preset
          ' Attach top button window for coordinate calculations
          GRAPHIC ATTACH hGraphicWindow(nGW), 0, REDRAW
      
          ' A button mouse click occurred on the button window
          ' Iterate through each Button/Tickbox/LineEntry
          FOR i = GWData(nGW).iGBStart TO UBOUND(GraphicButtons())
              IF GraphicButtons(i).Disable = 1 THEN ITERATE FOR       ' Don't execute when disabled
      
              x0 = GraphicButtons(i).x
              y0 = GraphicButtons(i).y
              x1 = x0 + GraphicButtons(i).WIDTH
              y1 = y0 + GraphicButtons(i).Height
      
              IF GraphicButtons(i).TYPE = %GW_TickBox AND %DeltaFocus >= 0 THEN
              ' Is mouse position within a TB?
                  x0 = x0 - %DeltaFocus
                  y0 = y0 - %DeltaFocus
                  x1 = x1 + (1 + MAX(1, %OffsetTBText)/4) * GraphicButtons(i).Height + %DeltaFocus - 1
                  y1 = y1 + %DeltaFocus - 1
              END IF
      
              ' Is mouse position within a GB/TB/LE?
              IF (x >= x0) AND (x <= x1) AND (y >= y0) AND (y <= y1) THEN
                  ' Move the button focus to the clicked Button/Tickbox/LineEntry as long as it is enabled
                  icf = GWGetCurrentFocus
                  IF i <> icf AND GraphicButtons(i).FOCUS > -1 AND NOT _
                      (GraphicButtons(i).TYPE = %GW_LineEntry AND GraphicButtons(i).ClearText = -1) THEN CALL GraphicMoveFocus(i, icf)
      
                  ' Execute if left-clicked
                  IF KeyMouse.Button = %VK_LBUTTON THEN
                      retButton = GWActivateButtonID(i)
      
                  ' Right-clicked for Context on a Button
                  ELSEIF KeyMouse.Button = %VK_RBUTTON THEN
                      KeyMouse.ContextClick = GraphicButtons(i).ID
                  END IF
                  EXIT SUB
              END IF
          NEXT i
      
          ' Right-clicked for Context on the rest of the window
          IF KeyMouse.Button = %VK_RBUTTON THEN  KeyMouse.ContextClick = GWData(nGW).ID
      
      END SUB ' ExecuteMouseClicks
      
      '----------------------------------------------------------------------------
      
      FUNCTION GWActivateButtonID(BYVAL i AS LONG) AS LONG
          ' V0.9
          ' Activate/execute button and generate ID as retButton
      
          ' Input:    i           => Button index
          ' Output:   FUNCTION    => ID of activated button
      
          LOCAL nID       AS LONG                 ' Button ID
          LOCAL hFg       AS DWORD                ' Handle of window with focus
      
          ' Check, if index i is valid
          IF i < 0 OR i > UBOUND(GraphicButtons()) THEN
              FUNCTION = %GW_FatalError
              EXIT FUNCTION
          END IF
      
          ' Uncover the BaseApplication and put hFg in focus again. Otherwise the partly covered Console could stay covered ...
          IF BaseApplication.isCons = 1 AND BaseApplication.Focus > %GW_Hidden THEN
                  hFg = GetForegroundWindow()
                  hFg = GetForegroundWindow()
      
              IF hFg <> BaseApplication.h THEN
              GWForceSetForegroundWindow(BaseApplication.h)           ' Force BaseApplication.h in focus
              GWForceSetForegroundWindow(hFg)                         ' Force hFg in focus
              END IF
          END IF
      
          nID = GraphicButtons(i).ID
      
          SELECT CASE LONG GraphicButtons(i).TYPE
              CASE %GW_Button                                         ' GB: Execute the Button click routine
                  IF GraphicButtons(i).OnClick <> 0 THEN CALL DWORD GraphicButtons(i).OnClick
              CASE %GW_TickBox                                        ' TB: Toggle if only one in a group, else move tick
                  CALL TBChangeTicks(i, 0)                            ' The .OnClick routine gets called in TBChangeTicks!
              CASE %GW_LineEntry                                      ' LE: No action.
                  'Use GraphicEditText() after clicking an LE. The .OnClick routine gets called there.
          END SELECT
      
          FUNCTION = nID                                              ' Return ID of clicked button
      
      END FUNCTION    ' GWActivateButtonID
      
      '----------------------------------------------------------------------------
      
      SUB GWDSNableAll(BYVAL nID AS LONG, BYVAL ena AS LONG)
          ' V0.8
          ' Disable and enable other buttons when entering and leaving a LE
      
          ' Input:    nID     : ID of the LE that was called
          '           ena     : 0 =>  Store status and disable all other buttons
          '                     1 =>  Restore the old status of enabled buttons
          ' Output    - none -
      
          LOCAL i     AS LONG                                         ' Index
          DIM lastStatus(0 TO UBOUND(GraphicButtons())) AS STATIC LONG    ' Store the dis/enable status
      
          FOR i = GWData(nGW).iGBStart TO UBOUND(GraphicButtons())    ' Do this only in current GW
      
              IF GraphicButtons(i).ID = nID THEN ITERATE FOR          ' Skip the present LE
      
              SELECT CASE LONG ena
                  CASE 0                                              ' Call before entering LE
                      IF GraphicButtons(i).Disable = 0 THEN           ' Was enabled
                          GraphicButtons(i).Disable = 1               ' Enable now
                          GraphicDrawButton(i)
                          lastStatus(i) = 0                           ' Store the .Disable value
                      ELSE
                          lastStatus(i) = 1
                      END IF
                  CASE 1
                      IF lastStatus(i) = 0 THEN                       ' It was stored as enabled
                          GraphicButtons(i).Disable = 0               ' Enable again
                          GraphicDrawButton(i)
                      END IF
              END SELECT
          NEXT i
      
      END SUB 'GWDNAbleAll
      
      '----------------------------------------------------------------------------
      
      FUNCTION GraphicOpenFile(BYVAL spec$$) AS LONG
          ' V1.2
          ' Open a/the file as specified in spec$$
      
          ' Input:    spec$$      =>  Specification of file to open. Either including path (e.g.: c:\copy\abc.txt) or filespec (e.g.: .doc)
          '                           If spec$$ = "." the windows explorer opens for file selection
          ' Output:   FUNCTION    = Return code of API call ShellExecute (Error = 0 - 32)
      
          LOCAL fullname$$
      
          IF INSTR(spec$$, ".") = 0 THEN EXIT FUNCTION                ' Must include a dot
      
          spec$$ = TRIM$(spec$$)                                      ' Remove blanks
      
          IF LEFT$(spec$$, 1) = "." THEN                              ' Only filespec
              spec$$ = "*" + spec$$                                   ' Add "*"
              fullname$$ = DIR$(spec$$)                               ' Search for the *first* file
          ELSE                                                        ' (Full) path + file has been passed
              fullname$$ = spec$$
          END IF
      
          FUNCTION = ShellExecute( %Null, "Open", fullname$$ + "", $$NUL, $$NUL, %SW_SHOWNORMAL )     ' API call to open a file
      
      END FUNCTION    'GraphicOpenFile
      
      '---------------------------------------------------------------
      
      FUNCTION SetStaticEvent(BYVAL eventString AS STRINGZ * 64) AS LONG
          ' V1.2
          '
          ' Purpose:  Create a static Event
          ' Usage:
          ' (1) The first call of  SetStaticEvent("EventName")  creates an Event (signaled = true) and returns the create-handle.
          '     The handle may be kept for possibly using  CloseHandle() later. A second call with same name returns -1 (Event exists).
          '
          ' (2) When an Event is checked (in whichever program) by  GetStaticEvent("EventName")  an OpenEvent is executed.
          '     The open-handle gets closed again. Thus, the creating program is the only one to keep the Event handle.
          '     Almost any type of activity can be monitored this way, e.g. if a program is loaded or if part of a program is active (or done), etc.
          '
          ' (3) Optionally one can destroy the Event using  CloseHandle(create-handle)  in the program where it was created.
          '     Still, when the creating program terminates, the Event object gets destroyed in any case.
          '
          ' (4) Limitation: A non-creating program cannot find out which program created the Event.
          '                 For that, more inter-process communication is needed.
          '
          ' Input:    eventString         :  Name of the Event to be created or checked (here limited to 63 characters)
          '
          ' Output:   FUNCTION = > 0      :  = Handle hEvent after creating Event
          '                    = -1       :  Event exists (it's state is signaled)
          '                    = -2       :  Event not created due to name conflict
      
          LOCAL hEvent    AS LONG             ' Create-handle of Event
          LOCAL lastError AS LONG             ' Value of GetLastError
          LOCAL szEventName AS STRINGZ * 64   ' ASCII name
      
          szEventName = eventString                                   ' ASCII = UNICODE, automatically translated
          hEvent = CreateEvent($NUL, 1, 1, szEventName)               ' Manual reset = 1 and Event is signaled
      
          lastError = GetLastError
          IF lastError = %ERROR_ALREADY_EXISTS THEN
              ' Close this handle in order to keep only the first create-handle
              CloseHandle hEvent
              FUNCTION = -1                                           ' Event exists
          ELSEIF lastError = %ERROR_INVALID_HANDLE THEN
              FUNCTION = -2                                           ' Event object not created due to name conflict
          ELSE
              ' Do not close the create-handle. The calling program is the only one to keep the handle
              FUNCTION = hEvent                                       ' Return the Event create-handle
          END IF
      
      END FUNCTION    ' SetStaticEvent
      
      '---------------------------------------------------------------
      
      FUNCTION GetStaticEvent(BYVAL eventString AS STRINGZ * 64) AS LONG
          ' V1.2
          '
          ' Purpose:  Check if an Event exists (and is signaled)
          ' Usage:    See  SetStaticEvent()
          '
          ' Input:    szEventName         :  Name of the Event to be created or checked (here limited to 63 characters)
          '
          ' Output:   FUNCTION = 0        :  Event does not exist (or it's state is not signaled)
          '                    = -1       :  Event exists (it's state is signaled)
          '                    = -2       :  Event failed to be created
      
          LOCAL hEvent    AS LONG     ' Create-handle of Event
          LOCAL szEventName AS STRINGZ * 64   ' ASCII name
      
          szEventName = eventString                                   ' ASCII = UNICODE, automatically translated
          hEvent = OpenEvent(%Event_ALL_ACCESS, 0, szEventName)
      
          IF hEvent = 0 THEN
              IF GetLastError = %ERROR_INVALID_HANDLE THEN
                  FUNCTION = -2                                       ' Event object not created (e.g. name conflict)
              ELSE
                  ' No need to close a handle; there is none.
                  FUNCTION = 0                                        ' Event does not exist
              END IF
          ELSE
              ' Close the open-handle
              CloseHandle(hEvent)
              FUNCTION = -1                                           ' Event exist
          END IF
      
      END FUNCTION    ' GetStaticEvent
      
      '------------------------------------------------------------------------------
      
      SUB GWForceSetForegroundWindow (BYVAL hwnd AS DWORD)   ' Add COMMON if needed in SLL
          ' V1.0
          ' ========================================================================================
          ' Replacement for the SetForegroundWindow API function. Thanks to Jose Roca.
          ' Changes by GV: Do loop added. No action, if hwnd is in focus.
          ' ========================================================================================
      
          LOCAL hFg, dwThreadId, dwProcessId, dwCurThreadId AS DWORD
      
              hFg = GetForegroundWindow
              hFg = GetForegroundWindow
      
          IF hFg <> hwnd THEN
              dwThreadId = GetWindowThreadProcessId(hFg, dwProcessId)
              dwCurThreadId = GetCurrentThreadId
              AttachThreadInput(dwCurThreadId, dwThreadId, %TRUE)
              SetForegroundWindow(hwnd)
              BringWindowToTop(hwnd)
              SetFocus(hwnd)
              AttachThreadInput(dwCurThreadId, dwThreadId, %FALSE)
          END IF
      
      END SUB
      
      '------------------------------------------------------------------------------
      
      FUNCTION QueryBox(      BYVAL sTxt$$, _
                          OPT BYVAL sYNOC$$, _
                          OPT BYVAL nFocus AS LONG, _
                          OPT BYVAL tAuto AS LONG) AS WSTRING
      
          ' V1.2
          ' This is a pre-defined function using the methods of the hierarchical button windows (it is similar to MessageBox() and Waitkey$)
          ' A QueryBox has its own loop to wait for key/mouse events. It gets the whole CPU and
          ' cannot spawn another button window; it gets automatically destroyed after activating a button.
          ' The parameter tAuto starts an self-closing (auto-termination) process as long as no other button gets activated.
      
          ' Structure: Based on %QB_h. Spacing at top, between text and buttons and at bottom = 0.15 * %QB_h.
          ' One line including spacing = 1.75 * %QB_FrameFontSize. Total height adjusted to number of lines after wrapping.
          ' Spacing right and left = 0.1 * %QB_w.
          ' If QueryBox is called from a standard GW (e.g. hWin), set  BaseApplication.h = hWin  before calling QueryBox for placing it in the middle of hWin.
      
          ' Input:   sText$$ =>  Text to be displayed. A $$CRLF causes a linefeed unless it was the last (two) character.
          '                      The single lines get displayed up to a length of 64 characters!
          '          sYNOC$$ =>  Optional, control string for up to three buttons. Default = OK.
          '          nFocus  =>  Optional, position of button focus. Range: 1 up to number of QueryBox buttons (1, 2 or 3). Default = 1.
          '          tAuto   =>  Optional, time in msec for auto-termination of button nFocus
      
          ' Output:  FUNCTION = "Y", "N", "O", "C" for one of the activated button "Yes", "No", "OK", "Cancel"
          ' The variable KeyMouse.ascKey1 will still be set after the call. Thus, a cancellation by ESC may be detected
          ' If the value is unwanted, KeyMouse.ascKey1 should be set to 0.
      
          LOCAL x0, y0            AS LONG     ' Coordinates
          LOCAL retButton         AS LONG     ' ID of activated button
          LOCAL nLines            AS LONG     ' Number of text lines (0 to ...?)
          LOCAL QB_height         AS LONG     ' Actual height
          LOCAL caption$$                     ' Caption, when %GW_UseCaption = 1
      
          ' Center the QueryBox as needed
          CALL QBCenterCoordinates(x0, y0)
      
          ' Wrap text if it is too long for QueryBox
          CALL QBWrapText(sTxt$$)
      
          ' Calculate final height
          nLines = QBCountLines(sTxt$$)
          QB_height = 0.65 * %QB_h + nLines * %QB_FrameFontSize * 1.75    ' Line = 1.75 * FontSize (including spacing)
                                                                                      ' *** Step 1: Set up window, button and frame IDs by equates (see %QB_... at top of this .inc file)
          ' Create the window
          caption$$ = "QueryBox"
          IF tAuto > 0 THEN caption$$ = " QB (closing after " + FORMAT$(tAuto/1000) + " sec)"
          CALL GraphicCreateWindow(%QB_ID, caption$$, x0, y0, %QB_w, QB_height)       ' *** Step 2: Create button window
      
          ' Create the frame and text                                                 ' *** Step 3: Create Buttons / Tick Boxes / Frames
          CALL QBCreateFrameText(sTxt$$, nLines, %QB_w, QB_height)
      
          ' Create the buttons
          CALL QBCreateButtons(%QB_w, QB_height, sTxt$$, nLines, sYNOC$$, nFocus)
      
          ' Wait for a pressed button or a click
          DO
              SLEEP 1                         ' Pause for about 15 ms
              CALL GraphicGetEvents(retButton)                                        ' *** Step 4: Check for pressed keys, mouse movements or clicks in a loop!
      
              ' First call to initialize. Then check if time is up. Return retButton, if tAuto = 0 or a button was hit.
              retButton = QBAutoTerminate(retButton, tAuto)
      
              SELECT CASE LONG retButton
                  CASE %QB_Yes                ' ID of buttons
                      QueryBox = "Y"
                  CASE %QB_No
                      QueryBox = "N"
                  CASE %QB_OK
                      QueryBox = "O"
                  CASE %QB_Cancel
                      QueryBox = "C"
              END SELECT
      
          LOOP UNTIL retButton <> %GW_NoEvent
      
          IF retButton > %GW_NoEvent THEN
              GraphicDestroyWindow(%QB_ID)                                            ' *** Step 5: Close window and reset buttons, frames and window data
          END IF
      
      END FUNCTION    ' QueryBox
      
      '---------------------------------------------------
      
      FUNCTION QBAutoTerminate(BYVAL retButton AS LONG, BYVAL tAuto AS LONG) AS LONG
          ' V0.8
          ' Extend a GW with self-closing buttons. Already built into QueryBox() with the optional parameter tAuto.
          ' Called in a GraphicGetEvents() loop. First call to initialize.
          ' Further calls to execute the auto-terminating process in a time slot of 200 ms after tAuto msec.
      
          ' Input:    retButton  =>  Last activated button. Used to reset, if <> %GW_NoEvent
          '           tAuto      =>  Auto-terminating time in msec, stored as STATIC
          ' Output:   FUNCTION   =>  %GW_NoEvent, after initialization and when tAuto is still running
          '                      =>  autoID, when auto-terminating
      
          STATIC autoTime     AS DOUBLE           ' Store tAuto/1000
          STATIC autoID       AS LONG             ' Store the ID of the button in focus while initializing
          STATIC startTime    AS DOUBLE           ' Store the start time
          LOCAL passedTime    AS DOUBLE           ' Time passed since initialization
      
          ' Input checks
          IF tAuto <= 0 THEN
              ' No valid time
              FUNCTION = retButton
              EXIT FUNCTION
          END IF
      
          IF retButton <> %GW_NoEvent THEN
              ' A button was activated
              autoTime = 0                                                ' Reset the static values for a new round
              autoID = 0
              startTime = 0
              FUNCTION = retButton                                        ' Return it
              EXIT FUNCTION
          END IF
      
          IF startTime = 0 THEN                                           ' Not yet set
              ' Initialization
              autoTime = tAuto/1000                                       ' In sec for TIMER
              autoID = GraphicButtons(GWGetCurrentFocus).ID               ' ID of focused button. Focus may be changed afterwards ...
              startTime = TIMER
              FUNCTION = %GW_NoEvent                                      ' No action
      
          ELSEIF autoTime > 0 AND autoID > 0 THEN
              ' Execution after initialization
              FUNCTION = %GW_NoEvent                                      ' Default
              passedTime = TIMER - startTime
              IF passedTime > autoTime THEN
                  IF passedTime < autoTime + 0.2 THEN                     ' If the GetEvent loop should take longer than 0.2 sec, this value can be increased here ...
                      FUNCTION = autoID                                   ' This one becomes retButton only when called in the time slot of 200 msec
                  END IF
                  startTime = 0                                           ' Reset to 0. Thus, no action if time slot got missed
              END IF
      
          END IF
      
      END FUNCTION    'QBAutoTerminate
      
      '--------------------------------------------------
      
      SUB QBWrapText(sTxt$$)
          ' V1.2
          ' When the text gets too long for the chosen box width, a $$CRLF gets inserted into the text.
      
          ' Input:    sTxt$$  => Text to be displayed
          ' Output:   sTxt$$  => Text wrapped to width of box
      
          LOCAL x$$, y$$, z$$, yy$$                   ' Partial strings
          LOCAL maxw              AS SINGLE           ' Available width within Frame
          LOCAL ww, wyy, dummy    AS SINGLE           ' Width of "W" (a large character) and dummy
          LOCAL i, istart         AS LONG             ' Indices
          LOCAL leny              AS LONG             ' Length of partial string
          LOCAL hQBBmp, hQBFont   AS DWORD            ' Store handles for a BITMAP and FONT
      
          ' Maximum width that should be filled with text
          maxw = FIX(0.8 * %QB_w)                                      ' Keep 0.1 of width clear on each side
      
          ' Start a BITMAP and FONT for measurement of the text width
          ' There is no graphic on screen, no taskbar icon and the process does not loose the focus ...
          GRAPHIC BITMAP NEW 0, 0 TO hQBBmp
          GRAPHIC ATTACH hQBBmp, 0
      
          ' Set the font for the QueryBox
          FONT NEW TRIM$($$QB_FrameFontName), %QB_FrameFontSize, %QB_FrameFontStyle TO hQBFont
          GRAPHIC SET FONT hQBFont                                    ' After closing the BITMAP, the font gets destroyed
      
          GRAPHIC TEXT SIZE "W" TO ww, dummy                          ' Assume all wide characters W in text
      
          istart = FIX(maxw/ww)                                       ' Index of last character that safely fits
          z$$ = sTxt$$
          x$$ = ""
      
          DO
              y$$ = EXTRACT$(z$$, $$CRLF)                             ' y$$ does not contain any $$CRLF
              leny = LEN(y$$)
              z$$ = REMAIN$(z$$, $$CRLF)
      
              FOR i = istart TO leny                                  ' Up to istart they fit in any case. Check only if leny >= istart
                  yy$$ = LEFT$(y$$, i)
                  GRAPHIC TEXT SIZE yy$$ TO wyy, dummy                ' Test how many characters can be added
                  IF wyy > maxw THEN
                      z$$ = RIGHT$(y$$, leny - i) + $$CRLF + z$$      ' Add a CRLF and display up to i-1
                      y$$ = yy$$
                      EXIT FOR
                  END IF
              NEXT i
      
              IF z$$ <> "" THEN y$$ = y$$ + $$CRLF                    ' Add the $$CRLF that was suppressed by EXTRACT
              x$$ = x$$ + y$$                                         ' Reconstruct the text including new $$CRLF
          LOOP UNTIL z$$ = ""
      
          IF RIGHT$(sTxt$$, 2) = $$CRLF THEN x$$ = x$$ + $$CRLF
          sTxt$$ = x$$
      
          ' Close the BITMAP and FONT
          GRAPHIC BITMAP END
          FONT END hQBFont
      
      END SUB 'QBWrapText
      
      '----------------------------------------------------------------------------
      
      SUB QBCenterCoordinates(x0 AS LONG, y0 AS LONG)
          ' V1.0
          ' Find the coordinates for placing a QueryBox or EntryBox
          ' (a) Center of Console
          ' (b) Center of BaseApplication GW
          ' (c) Center of desktop
      
          ' Input:    - none -
          ' Output:   x0, y0      =>  x, y coordinates
      
          LOCAL w0, h0        AS LONG             ' Coordinates
          LOCAL lPoint        AS POINTAPI         ' Defined in Win32Api
      
          ' Center QB in the middle of calling Console/GW
          lPoint.x = 0                                                ' Defaults
          lPoint.y = 0
          BaseApplication.IsCons = 0
      
          #IF %DEF(%PB_CC32)                                          ' Check for Console only with PB/CC
              IF CON.HANDLE <> 0 THEN
                  ' (a) Place QB in middle of Console
                  BaseApplication.IsCons = 1
                  ClientToScreen(CONSHNDL, lPoint)                    ' API call
                  CON.SIZE TO w0, h0                                  ' Console size
              END IF
          #ENDIF
      
          IF BaseApplication.IsCons = 0 THEN                          ' With PB/CC and Console OFF or with PB/Win
              ' Is there a BaseApplication GW
              IF ISTRUE IsWindow(BaseApplication.h) THEN              ' API call
                  ' (b) No Console but GW: Place QB in middle of GW
                  ClientToScreen(BaseApplication.h, lPoint)           ' API call
                  GRAPHIC ATTACH BaseApplication.h, 0, REDRAW
                  GRAPHIC GET CLIENT TO w0, h0
              ELSE
                  ' (c) No Console, no GW: Place QB in middle of desktop
                  DESKTOP GET SIZE TO w0, h0
              END IF
          END IF
          x0 = lPoint.x
          y0 = lPoint.y
      
          BaseApplication.x = x0                                      ' Save them in case Console/BaseApplication gets moved
          BaseApplication.y = y0
      
          x0 = x0 + (w0 - %QB_w) / 2                                  ' Place in middle of Console/BaseApplication
          y0 = y0 + (h0 - %QB_h) / 2                                  ' Even if final height will be changed ...
      
      END SUB 'QBCenterCoordinates
      
      '----------------------------------------------------------------------------
      
      SUB QBCreateButtons(    BYVAL QB_width AS LONG, _
                              BYVAL QB_height AS LONG, _
                              BYVAL sTxt$$, _
                              BYVAL nLines AS LONG, _
                          OPT BYVAL sYNOC$$, _
                          OPT BYVAL nFocus AS LONG)
          ' V1.2
          ' Display 1, 2 or 3 buttons in any sequence and combination
          ' String sYNOC$$ contains up to three letters: Y for Yes, N for No, O for OK, C for Cancel
          ' E.g.: "OC" => OK + Cancel buttons, "NY" => No and Yes buttons
      
          ' Input:   QB_width   =>  Width of Box
          '          QB_height  =>  Actual height of Box when number of text lines are known
          '          sText$$    =>  Text to be displayed
          '          nLines     =>  Number of lines to be displayed
          '          sYNOC$$    =>  Optional, control string for up to three buttons. Default = OK.
          '          nFocus     =>  Optional, position of button focus. Range: 1 up to number of QueryBox buttons (1, 2 or 3). Default = 1.
          ' Output:  -none-
      
          LOCAL BUTTON            AS GraphicButton        ' Data set for Button parameters
          LOCAL i                 AS LONG                 ' Index
          LOCAL xp0, xp1, xp2     AS LONG                 ' Button positions
          DIM xpos(2)             AS LONG                 ' x -coordinate for 1, 2 or 3 buttons
          LOCAL all$$                                     ' Possible button characters Y N O C
          LOCAL y$$                                       ' Reduced string of sYNOC$$ or line of sTxt$$ for display
          LOCAL z$$                                       ' Characters in sYNOC$$ or rest of sTxt$$
          LOCAL buttonSize        AS LONG                 ' Calculated with %QB_w
          LOCAL lynoc             AS LONG                 ' Length of sYNOC$$
          LOCAL idFocus           AS LONG                 ' ID of QB button focus
      
          ' Create Graphic Buttons (GB)
          ' --- These settings are the same for all buttons on this level (default)
          buttonSize = 0.2 * QB_width
      
          Button.Type             = %GW_Button
          Button.y                = QB_height - 0.35 * %QB_h
          Button.Width            = buttonSize
          Button.Height           = 0.2 * %QB_h
          Button.FontName         = $$QB_ButtonFontName
          Button.FontSize         = %QB_ButtonFontSize
          Button.FontStyle        = %QB_ButtonFontStyle
          Button.TextColor        = %QB_ButtonTextColor
          Button.BorderColor      = %QB_ButtonBorderColor
          Button.HotKey           = ""
          Button.HoverFontStyle   = 1
          Button.HoverTextColor   = %QB_ButtonHoverTextColor
          Button.HoverBackColor   = %QB_ButtonHoverBackColor
          Button.HoverBorderColor = %QB_ButtonHoverBorderColor
          Button.BackColor        = %QB_ButtonBackColor
          Button.OnClick          = 0                                 ' Make sure that no routine is called
          Button.Disable          = 0                                 ' All enabled
      
          ' Prepare buttons
          all$$ = "YNOC"                                              ' Possible button characters
          sYNOC$$ = UCASE$(sYNOC$$)
          y$$ = ""
      
          IF sYNOC$$ = "" THEN
              y$$ = "O"                                               ' Default
              lynoc = 1
          ELSE
              FOR i = 1 TO LEN(sYNOC$$)
                  z$$ = MID$(sYNOC$$, i, 1)
                  IF INSTR(all$$, z$$) > 0 THEN
                      y$$ = y$$ + z$$                                 ' Fill y$$ with correct characters
                      INCR lynoc
                      all$$ = REMOVE$(all$$, z$$)                     ' Remove the chosen characters => place each button only once
                      IF lynoc = 3 THEN EXIT FOR                      ' Max. 3 buttons
                  END IF
              NEXT i
          END IF
      
          ' Positions of 1, 2 or 3 buttons (max. is 3 !)
          xp0 = 0.1 * QB_width
          xp1 = 2 * xp0 + buttonSize
          xp2 = 3 * xp0 + 2 * buttonSize
      
          SELECT CASE LONG lynoc
              CASE 1
                  xpos(0) = xp1                                        ' Position for 1,
              CASE 2
                  xpos(0) = xp1                                        ' ... for 2
                  xpos(1) = xp2
              CASE 3
                  xpos(0) = xp0                                        ' ... and for 3 buttons
                  xpos(1) = xp1
                  xpos(2) = xp2
          END SELECT
      
          FOR i = 1 TO lynoc
      
              SELECT CASE MID$(y$$, i, 1)
                  CASE "Y"
                      Button.ID               = %QB_Yes
                      Button.Text             = "Yes"
                      Button.nUnderChar       = 1
                      Button.HotKey           = ""
      
                  CASE "N"
                      Button.ID               = %QB_No
                      Button.Text             = "No"
                      Button.nUnderChar       = 1
                      ' Set ESC as HotKey for "No" if "Cancel" is not present
                      IF INSTR(y$$, "C") = 0 THEN  Button.HotKey = "ESC"
      
                  CASE "O"
                      Button.ID               = %QB_OK
                      Button.Text             = "OK"
                      Button.nUnderChar       = 1
                      Button.HotKey           = ""
      
                  CASE "C"
                      Button.ID               = %QB_Cancel
                      Button.Text             = "Cancel"
                      Button.nUnderChar       = 1
                      Button.HotKey           = "ESC"
      
              END SELECT
      
              Button.x = xpos(i - 1)                                  ' x-position for each button
              CALL GraphicAddButton(BUTTON)
      
          NEXT i
      
          ' Set the button focus
          SELECT CASE LONG nFocus                                     ' This limits nFocus from 1 to lynoc
              CASE < 1
                  nFocus = 1
              CASE > lynoc
                  nFocus = lynoc                                      ' = Number of buttons
          END SELECT
      
          idFocus = GraphicButtons(GWData(nGW).iGBStart + nFocus - 1).ID  ' .ID of button in this window
          CALL GraphicSetFocus(idFocus)
      
      END SUB ' QBCreateButtons
      
      '----------------------------------------------------------------------------
      
      FUNCTION QBCountLines(BYVAL sTxt$$) AS LONG
          ' V1.2
          ' Count the lines in the incoming text string
      
          ' Input:    sTxt$$  =>  Text to be displayed. It may contain any number of $$CRLF
          ' Output:   nLines  =>  Number of separate lines for the display
      
          LOCAL nLines        AS LONG                     ' Return value
      
          IF LEN(sTxt$$) = 0 THEN
              nLines = 0                                              ' No lines if empty
          ELSE
              nLines = 1
              ' Count the number of $$CRLF
              DO
                  sTxt$$ = REMAIN$(sTxt$$, $$CRLF)                    ' Cut first part up to $$CRLF off
                  IF sTxt$$ = "" THEN EXIT LOOP                       ' Leave when rest is empty
                  INCR nLines                                         ' Increment when a $$CRLF is found
              LOOP
          END IF
      
          FUNCTION = nLines
      
      END FUNCTION    ' QBCountLines
      
      '----------------------------------------------------------------------------
      
      SUB QBCreateFrameText(BYVAL sTxt$$, _
                            BYVAL nLines AS LONG, _
                            BYVAL QB_width AS LONG, _
                            BYVAL QB_height AS LONG)
          ' V1.2
          ' Create the frame for QueryBox and EntryBox depending on %GW_UseCaption.
      
          ' Input:    sTxt$$     =>  Text to display
          '           nLines     =>  Number of lines
          '           QB_width   =>  Width of Box
          '           QB_height  =>  Actual height of Box when number of text lines are known
          ' Output:   -none-
      
          LOCAL FRAME             AS GraphicFrame         ' Data set for Frame parameters
          LOCAL i                 AS LONG                 ' Index
          LOCAL y$$, z$$                                  ' Strings
      
          ' Create Frames (GF) at the edge of the window when no caption is used
          IF %GW_UseCaption = 0 OR KeyMouse.ContextClick <> %GW_NoEvent THEN
              Frame.ID                = 0                             ' Not to be used anywhere
              Frame.x                 = 4
              Frame.y                 = 4
              Frame.Width             = QB_width - 8
              Frame.Height            = QB_height - 8
              Frame.Text              = ""
              Frame.BorderColor       = %QB_FrameBorderColor
              CALL GraphicAddFrame(FRAME)
          END IF
      
          IF nLines > 0 THEN
              z$$ = sTxt$$
              FOR i = 1 TO nLines
                  y$$ = EXTRACT$(z$$, $$CRLF)
                  z$$ = REMAIN$(z$$, $$CRLF)
                  Frame.x                 = 0.1 * QB_width
                  Frame.y                 = 0.15 * %QB_h + (i - 1) * %QB_FrameFontSize * 1.75     ' Line = 1.75 * FontSize (including spacing)
                  Frame.Width             = 0                                 ' Now print text only
                  Frame.Text              = y$$
                  Frame.FontName          = $$QB_FrameFontName
                  Frame.FontSize          = %QB_FrameFontSize
                  Frame.FontStyle         = %QB_FrameFontStyle
                  Frame.TextColor         = %QB_FrameTextColor
                  CALL GraphicAddFrame(FRAME)
              NEXT i
          END IF
      
      END SUB ' QBCreateFrameText
      
      '----------------------------------------------------------------------------
      '----------------------------------------------------------------------------
      
      FUNCTION EntryBox(      BYVAL sTxt$$, _
                          OPT BYVAL sPreset$$, _
                          OPT BYVAL textAlign AS LONG) AS WSTRING
          ' V1.2
          ' This is a pre-defined function using the methods of the hierarchical button windows (it is similar to QueryBox)
          ' An EntryBox has its own loop to wait for key events. The LE will get activated automatically.
          ' It gets the whole CPU and gets automatically destroyed after entering a text
          ' In this routines the same values/equates that are used as for QueryBox.
          ' If EntryBox is called from a standard GW (e.g. hWin), set  BaseApplication.h = hWin  before calling EntryBox for placing it in the middle of hWin.
      
          ' Input:   sText$$   =>  Text to be displayed. A $$CRLF causes a linefeed unless it was the last (two) character.
          '                        The single lines get displayed up to a length of 64 characters!
          '          sPreset$$ =>  Optional, text that gets displayed in the LineEntry at start
          '          textAlign =>  Optional, value to control the alignment of the entered text: %GW_Left, %GW_Right, %GW_Center (= 2, 3, 4). Default = %GW_Left
          ' Output:  FUNCTION  =   Entered text
      
          LOCAL x0, y0            AS LONG         ' Coordinates
          LOCAL retButton         AS LONG         ' ID of activated button
          LOCAL nLines            AS LONG         ' Number of text lines (0 to ...?)
          LOCAL QB_height         AS LONG         ' Actual height
      
          ' Center the EntryBox as needed
          CALL QBCenterCoordinates(x0, y0)
      
          ' Wrap text if it is too long for EntryBox
          CALL QBWrapText(sTxt$$)
      
          ' Calculate final height
          nLines = QBCountLines(sTxt$$)
          QB_height = 0.65 * %QB_h + nLines * %QB_FrameFontSize * 1.75    ' Line = 1.75 * FontSize (including spacing)
      
          ' Create the window
          CALL GraphicCreateWindow(%EB_ID, "EntryBox", x0, y0, %QB_w, QB_height)
      
          ' Create the frame and text
          CALL QBCreateFrameText(sTxt$$, nLines, %QB_w, QB_height)
      
          ' Create the buttons
          IF textAlign = 0 THEN textAlign = %GW_Left                  ' Preset
          CALL EBCreateButtons(sTxt$$, sPreset$$, textAlign, %QB_w, QB_height)
      
          DO
              SLEEP 1                                                 ' Pause for about 15 ms
              GraphicSetNextID(%EB_LE)                                ' Preset the button to be executed
              CALL GraphicGetEvents(retButton)                        ' Wait for an ESC or CR
      
              SELECT CASE LONG retButton
                  CASE %EB_LE                                         ' ID of button
                      GraphicEditText(%EB_LE)                         ' Edit text (Normally the result is the edited text, but the result could be $$ESC)
                      EntryBox = GraphicButtonsGetText(%EB_LE)        ' Retrieve text. No special treatment for returned $$ESC.
              END SELECT
      
          LOOP UNTIL retButton <> %GW_NoEvent
      
          IF retButton > %GW_NoEvent THEN
              GraphicDestroyWindow(%EB_ID)
          END IF
      
      END FUNCTION    ' EntryBox
      
      '----------------------------------------------------------------------------
      
      SUB EBCreateButtons(    BYVAL sTxt$$, _
                              BYVAL sPreset$$, _
                              BYVAL textAlign AS LONG, _
                              BYVAL QB_width AS LONG, _
                              BYVAL QB_height AS LONG)
          ' V1.2
          ' Create the LineEntry button
      
          ' Input:   sTxt$$     =>  Text to be displayed
          '          sPreset$$  =>  Optional, preset text in the LE
          '          textAlign  =>  Optional, position for text in LE (left, center, right)
          '          QB_width   =>  Width of Box
          '          QB_height  =>  Actual height of Box when number of text lines are known
          ' Output:  -none-
      
          LOCAL BUTTON    AS GraphicButton        ' Data set for Button parameters
      
          ' Create Graphic Button (LE)
          Button.ID               = %EB_LE
          Button.Type             = %GW_LineEntry
          Button.x                = 0.1 * QB_width
          Button.y                = QB_height - 0.35 * %QB_h
          Button.Width            = 0.8 * QB_width
          Button.Height           = 3 * %QB_ButtonFontSize
          Button.Text             = sPreset$$
          Button.nUnderChar       = 0
          Button.MaxChars         = %GW_MaxTextChar                   ' Storage allows up to %GW_MaxTextChar characters
          Button.TextAlign        = textAlign
          Button.ClearText        = 0
          Button.HotKey           = ""
          Button.FontName         = $$QB_ButtonFontName
          Button.FontSize         = %QB_ButtonFontSize + 1            ' May be larger because style = 0
          Button.FontStyle        = %QB_ButtonFontStyle
          Button.TextColor        = %QB_ButtonTextColor
          Button.BackColor        = %QB_ButtonBackColor
          Button.BorderColor      = %QB_ButtonBorderColor
          Button.HoverFontStyle   = 0                                 ' When hover state gets redrawn while editing, it looks better not to change
          Button.HoverTextColor   = %QB_ButtonHoverTextColor
          Button.HoverBackColor   = %QB_ButtonHoverBackColor
          Button.HoverBorderColor = %QB_ButtonHoverBorderColor
          Button.Focus            = 1
          Button.OnClick          = 0                                 ' Make sure that no routine is called
          Button.Disable          = 0                                 ' All enabled
          Button.TBTick           = 0                                 ' Hidden use for valid flag with .OnClick routine
          Button.TBGroup          = 0
          CALL GraphicAddButton(BUTTON)
      
      END SUB ' EBCreateButtons
      
      '----------------------------------------------------------------------------
      
      SUB ConsoleResizeEx (OPT BYVAL SColumns??, BYVAL SRows??, BYVAL VColumns??, BYVAL VRows??, BYVAL hBuffer&)
          ' V1.2 - New
      
          ' In Win 7 the Console does not get resized correctly, when larger than default.
          ' This routine allows to set it to the desired screen and virtual size(s).
          ' Thanks to Yuzree: http://www.powerbasic.com/support/pbforums/showthread.php?t=48998
          '
          ' v1.00 by Yuzree Esmera
          ' Requires Windows 2000 or later, PBCC 5 or later.
          '
          ' Resizes a Console Buffer (VIRTUAL) and/or it's Console Window (SCREEN)
          ' according to the specified dimensions.
          '
          ' SColumns?? [In, Optional]
          '   Target width of the Console Window in character columns.
          '   Defaults to current Console Window width if NULL or unspecified.
          '
          ' SRows?? [In, Optional]
          '   Target height of the Console Window in character rows.
          '   Defaults to current Console Window height if NULL or unspecified.
          '
          ' VColumns?? [In, Optional]
          '   Target width of the Console Buffer in character columns.
          '   Defaults to current Console Buffer width if NULL or unspecified.
          '
          ' VRows?? [In, Optional]
          '   Target height of the Console Buffer in character rows.
          '   Defaults to current Console Buffer height if NULL or unspecified.
          '
          ' hBuffer& [In, Optional]
          '   The handle of the Console Buffer to resize.
          '   Defaults to the handle of the Active Console Buffer if NULL or unspecified.
      
          REGISTER Sx??, Sy??, Vx??, Vy??
          LOCAL zTmp AS ASCIZ * 8, bOpenedHandle?, CBI AS CONSOLE_SCREEN_BUFFER_INFO
          IF hBuffer& <= 0& THEN
              'Handle invalid or unspecified; get handle of active Console Buffer
              zTmp = "CONOUT$"
              hBuffer& = CreateFile (BYVAL VARPTR(zTmp), _
                                     BYVAL %GENERIC_READ OR %GENERIC_WRITE, _
                                     BYVAL %FILE_SHARE_WRITE, BYVAL 0&, _
                                     BYVAL %OPEN_EXISTING, BYVAL 0&, BYVAL 0&)
      
              'Exit if unable to get handle
              IF hBuffer& <= 0& THEN EXIT SUB
      
              'Set flag
              bOpenedHandle? = 1&
          END IF
      
          'Exit if unable to get current Console dimensions
          IF GetConsoleScreenBufferInfo (BYVAL hBuffer&, BYVAL VARPTR(CBI)) = 0& THEN EXIT SUB
      
          'Get maximum width & height possible (discounting Console Buffer)
          POKE DWORD, VARPTR(CBI.dwMaximumWindowSize), GetLargestConsoleWindowSize (BYVAL hBuffer&)
      
          'Initialize working variables
          Sx?? = IIF&(SColumns??, SColumns??, CBI.srWindow.xRight - CBI.srWindow.xLeft + 1??)
          Sy?? = IIF&(SRows??,    SRows??,    CBI.srWindow.xBottom - CBI.srWindow.xTop + 1??)
          Vx?? = IIF&(VColumns??, VColumns??, MAX&(CBI.dwSize.X, Sx??))
          Vy?? = IIF&(VRows??,    VRows??,    MAX&(CBI.dwSize.Y, Sy??))
      
          'Limit target Console Window dimensions to lowest of target, Console Buffer or max possible
          Sx?? = MIN&(Sx??, Vx??, CBI.dwMaximumWindowSize.X)
          Sy?? = MIN&(Sy??, Vy??, CBI.dwMaximumWindowSize.Y)
      
          'Resize Console Window to smallest possible
          RESET CBI
          SetConsoleWindowInfo (BYVAL hBuffer&, BYVAL 1&, BYVAL VARPTR(CBI.srWindow))
      
          'Resize Console Buffer to target dimensions
          CBI.dwSize.X = Vx??
          CBI.dwSize.Y = Vy??
          'SetConsoleScreenBufferSize (BYVAL hBuffer&, BYVAL PEEK(DWORD, VARPTR(CBI.dwSize)))   'JRF this is not working
      
          'Resize Console Window to target dimensions
          CBI.srWindow.xRight  = Sx?? - 1??
          CBI.SrWindow.xBottom = Sy?? - 1??
          SetConsoleWindowInfo (BYVAL hBuffer&, BYVAL 1&, BYVAL VARPTR(CBI.srWindow))
      
          'Cleanup
          IF bOpenedHandle? THEN CloseHandle(hBuffer&)
      
      END SUB ' ConsoleResizeEx
      
      '*** End of GrButtons.inc *****************************************************************

      Comment

      Working...
      X