Author Topic: Programming with Unicode  (Read 2240 times)

0 Members and 1 Guest are viewing this topic.

Arnold

  • Guest
Programming with Unicode
« on: March 06, 2020, 10:21:02 AM »
Hi Charles,

after doing some more intensive experiments, I am fully convinced that it is possible to create programs in Oxygenbasic that can use Unicode, in 32-bit and 64-bit. The L" option was the key. Source code with foreign character sets has to be saved as UTF 16-LE, which is not a problem with co2.exe, but otherwise I can save the code as a normal ansi text file.

Before I go any further, I will create corewinW.inc, kernelW.inc etc. and WinDataW.inc. This will not conflict with any existing code, but I could then use the normal naming of the WinApi functions. Maybe you have been thinking about another plan? I myself would consider it a mistake not to try the possibilities.

Roland

Charles Pegge

  • Guest
Re: Programming with Unicode
« Reply #1 on: March 08, 2020, 02:14:22 AM »
Hi Roland,

Good idea.

Do you think we need KernelW.inc. UserW.inc etc to set aliases for W procedures instead of A procedures? I could generate them.

Arnold

  • Guest
Re: Programming with Unicode
« Reply #2 on: March 09, 2020, 02:15:40 AM »
Hi Charles,

I have created corewinW.inc and dependent files derived from corewin.inc etc. This was not a big deal, I changed ..A ..A" to ..W ..W" and vice versa. Maybe not everything is complete, but it will turn out. I assume in windataW.inc some types must be adapted too. I copied the files into the inc folder of Oxygenbasic, they will not conflict with other files. But as you have created many useful helper include files, I assume some of them must be adapted too at some time. Perhaps a separate unicode distro would make sense then?

Currently I have started to port Petzold's examples to unicode. Some of them work quite nice and there is a difference between using corewin and corewinW. But sometimes I also find a problem. One of them I will describe in my next message.

Arnold

  • Guest
Re: Programming with Unicode
« Reply #3 on: March 09, 2020, 02:23:06 AM »
This is the environ.c example ported to Oxygen using unicode. The example is discussed in Chapter 9 of "Programming Windows". The app will not work with corewin.inc.

In sub FillListBox GetEnvironmentStrings/W should be applied. I tried to use a similar approach like examples/system/GetEnvironment.o2bas using: wide byte, wchar and wstring, but I did not succeed although I can see that GetEnvironmentStringsW creates a wide char string. So I cheated and used GetEnvironmentStringsA instead, using a bit lousy implementation. And of course this is not correct. Perhaps there is a better solution?

This is the procedure in environ.c:
Code: [Select]
void FillListBox (HWND hwndList)
{
     int     iLength ;
     TCHAR * pVarBlock, * pVarBeg, * pVarEnd, * pVarName ;

     pVarBlock = GetEnvironmentStrings () ;  // Get pointer to environment block

     while (*pVarBlock)
     {
          if (*pVarBlock != '=')   // Skip variable names beginning with '='
          {
               pVarBeg = pVarBlock ;              // Beginning of variable name
               while (*pVarBlock++ != '=') ;      // Scan until '='
               pVarEnd = pVarBlock - 1 ;          // Points to '=' sign
               iLength = pVarEnd - pVarBeg ;      // Length of variable name

                    // Allocate memory for the variable name and terminating
                    // zero. Copy the variable name and append a zero.

               pVarName = calloc (iLength + 1, sizeof (TCHAR)) ;
               CopyMemory (pVarName, pVarBeg, iLength * sizeof (TCHAR)) ;
               pVarName[iLength] = '\0' ;

                    // Put the variable name in the list box and free memory.

               SendMessage (hwndList, LB_ADDSTRING, 0, (LPARAM) pVarName) ;
               free (pVarName) ;
          }
          while (*pVarBlock++ != '\0') ;     // Scan until terminating zero
     }
     FreeEnvironmentStrings (pVarBlock) ;
}

And this is the code of Environ_Test.o2bas:
(deleted)
« Last Edit: March 10, 2020, 02:08:15 AM by Arnold »

Arnold

  • Guest
Re: Programming with Unicode
« Reply #4 on: March 09, 2020, 02:25:22 AM »
And now after looking at the code again I suspect that I have to check function replace a bit more?

Aurel

  • Guest
Re: Programming with Unicode
« Reply #5 on: March 09, 2020, 04:33:41 AM »
Quote
Perhaps a separate unicode distro would make sense then?

Arnold
That make sense to me
otherwise you can create lot of problems

Charles Pegge

  • Guest
Re: Programming with Unicode
« Reply #6 on: March 09, 2020, 05:48:00 AM »
Hi Roland,

Many thanks for corewinw. I will include it in 0.2.9.

This is one way to get each string in the environment strings:
Code: [Select]
'uses corewinw
'indexbase 0
word *w : @w=GetEnvironmentStrings ()   // Get pointer to environment block
wstring sw
sys bw=@w 'start of words
int a
do
  if w[0]=0 'end of varname=content pair
    wchar*wc
    @wc=bw
    bw=@w+2
    sw+=wc+wchr(13,10) 'capture line
    if w[1]=0 ' \0 \0 end of list
      exit do
    endif
  endif
  a++
  @w+=2
loop
print sw

FillListBox procedure
Code: [Select]
sub FillListBox (sys hwndList)
    word *w
    @w=GetEnvironmentStrings ()   // Get pointer to environment block
    wstring sw
    sys bw=@w 'start of words
    sys ew    'end of var name
    int a
    do
      if w=61 '= end of var name
        ew=@w
      elseif w=0 'end of var content
        wchar*wc
        @wc=bw
        sw=left(wc,(ew-bw)>>1)
        SendMessage (hwndList, LB_ADDSTRING, 0, sw)
        bw=@w+2
        if w[1]=0 ' \0 \0 end of list
          exit do
        endif
      endif
      @w+=2
    loop
end sub
« Last Edit: March 09, 2020, 08:08:27 AM by Charles Pegge »

Arnold

  • Guest
Re: Programming with Unicode
« Reply #7 on: March 10, 2020, 02:03:30 AM »
Thank you Charles. Your routine saved a lot of unnecessary lines. It will be helpful with similar code. I just did not recognize the type word as a 16-bit character. But now with me everything works fine like the original program, in 32-bit and 64-bit (and using unicode).

Environ.o2bas:
Code: [Select]
/*-----------------------------------------------------------
   Environ.o2bas -- Environment List Box,
   adapted to Unicode and ported to Oxygen Basic
   The code in C is discussed in: Programming Windows, Fifth Edition
                (c) Charles Petzold, 1998
  -----------------------------------------------------------*/
$ filename "Environ.exe"
'uses rtl32
'uses rtl64

uses corewinW

% SM_CXVSCROLL 2
% SS_LEFT 0

indexbase 0

% ID_LIST     1
% ID_TEXT     2

sys hInstance = GetModuleHandle (NULL)
int iCmdShow = SW_SHOW

function WinMain () as sys
     wstring      szAppName = L"Environ"
     sys          hwnd
     MSG          msg
     WNDCLASS     wndclass
     
     wndclass.style         = CS_HREDRAW or CS_VREDRAW
     wndclass.lpfnWndProc   = @WndProc
     wndclass.cbClsExtra    = 0
     wndclass.cbWndExtra    = 0
     wndclass.hInstance     = hInstance
     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION)
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW)
     wndclass.hbrBackground = GetStockObject (WHITE_BRUSH)
     wndclass.lpszMenuName  = NULL
     wndclass.lpszClassName = strptr szAppName

     if not RegisterClass (&wndclass) then
          MessageBox (NULL, L"Cannot RegisterClass wndclass",
                      szAppName, MB_ICONERROR)
          return 0
     end if
     
     hwnd = CreateWindowEx (0,
                            szAppName, L"Environment List Box",
                            WS_OVERLAPPEDWINDOW,
                            CW_USEDEFAULT, CW_USEDEFAULT,
                            CW_USEDEFAULT, CW_USEDEFAULT,
                            NULL, NULL, hInstance, NULL)
     
     if not hwnd then                       
          MessageBox (NULL, L"Cannot Create Main Window",
                      szAppName, MB_ICONERROR)
          return 0
     end if
     
     ShowWindow (hwnd, iCmdShow)
     UpdateWindow (hwnd)
     
     while GetMessage (&msg, NULL, 0, 0)
          TranslateMessage (&msg)
          DispatchMessage (&msg)
     wend

end function

sub FillListBox (sys hwndList)
    word *w
    @w=GetEnvironmentStrings ()   // Get pointer to environment block
    wstring sw
    sys bw=@w 'start of words
    sys ew    'end of var name
    int a
    do
      if w=61 '= end of var name
        ew=@w
      elseif w=0 'end of var content
        wchar*wc
        @wc=bw
        sw=left(wc,(ew-bw)>>1)
        SendMessage (hwndList, LB_ADDSTRING, 0, sw)
        bw=@w+2
        if w[1]=0 ' \0 \0 end of list
          exit do
        endif
      endif
      @w+=2
    loop

    SendMessage (hwndList, LB_DELETESTRING, 0, 0)   '=::
#ifdef mode64bit
    SendMessage (hwndList, LB_DELETESTRING, 0, 0)   '__COMPAT_LAYER
#endif   
end sub

function WndProc (sys hwnd, uint message, sys wParam, sys lParam) as sys callback
     static sys  hwndList, hwndText
     int         iIndex, iLength, cxChar, cyChar
     wzstring    pVarName[100]
     wzstring    pVarValue[800]
 
     select case message
     case WM_CREATE
          cxChar = LOWORD (GetDialogBaseUnits ())
          cyChar = HIWORD (GetDialogBaseUnits ())

               // Create listbox and static text windows.         
          hwndList = CreateWindowEx (0,
                              L"listbox", NULL,
                              WS_CHILD or WS_VISIBLE or LBS_STANDARD,
                              cxChar, cyChar * 3,
                              cxChar * 16 + GetSystemMetrics (SM_CXVSCROLL),
                              cyChar * 15,
                              hwnd, ID_LIST,
                              GetWindowLongPtr (hwnd, GWL_HINSTANCE),
                              NULL)
          if not hwndList then                       
              MessageBox (NULL, L"Cannot Create ListBox",
                          L"Error!" , MB_ICONERROR)
              return 0
          end if
         
          hwndText = CreateWindowEx (0,
                              L"static", NULL,
                              WS_CHILD or WS_VISIBLE or SS_LEFT,
                              cxChar, cyChar,
                              GetSystemMetrics (SM_CXSCREEN), cyChar,
                              hwnd, ID_TEXT,
                              GetWindowLongPtr (hwnd, GWL_HINSTANCE),
                              NULL)
          if not hwndText then                       
              MessageBox (NULL, L"Cannot Create Label",
                          L"Error!", MB_ICONERROR)
              return 0
          end if

          FillListBox (hwndList) 
         
     case WM_SETFOCUS
          SetFocus (hwndList) 
         
     case WM_COMMAND
          if LOWORD (wParam) = ID_LIST and HIWORD (wParam) = LBN_SELCHANGE then
                    // Get current selection.
               iIndex  = SendMessage (hwndList, LB_GETCURSEL, 0, 0)
               iLength = SendMessage (hwndList, LB_GETTEXTLEN, iIndex, 0) + 1 
               SendMessage (hwndList, LB_GETTEXT, iIndex, @pVarName)

                    // Get environment string.
               iLength = GetEnvironmentVariable (pVarName, NULL, 0) 
               GetEnvironmentVariable (@pVarName, pVarValue, iLength)

                    // Show it in window.               
               SetWindowText (hwndText, @pVarValue)
          end if
          return 0

     case WM_DESTROY
          PostQuitMessage (0) 
     end select
     return DefWindowProc (hwnd, message, wParam, lParam)
end function

WinMain()
« Last Edit: March 10, 2020, 02:10:38 AM by Arnold »

Arnold

  • Guest
Re: Programming with Unicode
« Reply #8 on: March 12, 2020, 11:43:50 AM »
This is another nice example which demonstrates the difference between Ansi and Unicode. The app creates an edit control. It can be checked by clicking the right mouse button which opens a popup menu. (RichEdit does not) This is only a small demo, but you can already copy unicode into the edit control, which is not possible with the Ansi version.

Code: [Select]
/*-------------------------------------------------------
   PopPad1.o2bas -- Popup Editor using child window edit box
   adapted to Unicode and ported to Oxygen Basic
   The code in C is discussed in: Programming Windows, Fifth Edition
                (c) Charles Petzold, 1998
  -------------------------------------------------------*/
$ filename "PopPad1.exe"
'uses rtl32
'uses rtl64

uses corewinW

indexbase 0


% ID_EDIT     1
wstring szAppName = L"PopPad1"

sys hInstance = GetModuleHandle (NULL)
int iCmdShow = SW_SHOW

function WinMain () as sys
     sys          hwnd
     MSG          msg
     WNDCLASS     wndclass
     
     wndclass.style         = CS_HREDRAW or CS_VREDRAW
     wndclass.lpfnWndProc   = @WndProc
     wndclass.cbClsExtra    = 0
     wndclass.cbWndExtra    = 0
     wndclass.hInstance     = hInstance
     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION)
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW)
     wndclass.hbrBackground = GetStockObject (WHITE_BRUSH)
     wndclass.lpszMenuName  = NULL
     wndclass.lpszClassName = strptr szAppName

     if not RegisterClass (&wndclass) then
          MessageBox (NULL, L"Cannot RegisterClass wndclass",
                      szAppName, MB_ICONERROR)
          return 0
     end if
     
     hwnd = CreateWindowEx (0,
                            szAppName, szAppName,
                            WS_OVERLAPPEDWINDOW,
                            CW_USEDEFAULT, CW_USEDEFAULT,
                            CW_USEDEFAULT, CW_USEDEFAULT,
                            NULL, NULL, hInstance, NULL)
     
     if not hwnd then                       
          MessageBox (NULL, L"Cannot Create Main Window",
                      szAppName, MB_ICONERROR)
          return 0
     end if
     
     ShowWindow (hwnd, iCmdShow)
     UpdateWindow (hwnd)
     
     while GetMessage (&msg, NULL, 0, 0)
          TranslateMessage (&msg)
          DispatchMessage (&msg)
     wend

end function

function WndProc (sys hwnd, uint message, sys wParam, sys lParam) as sys callback
     static sys hwndEdit
     
     select case message
     case WM_CREATE
          CREATESTRUCT LPCREATESTRUCT at lParam
     
          hwndEdit = CreateWindowEx (0,  L"edit", NULL,
                                     WS_CHILD or WS_VISIBLE or WS_HSCROLL or WS_VSCROLL or
                                     WS_BORDER or ES_LEFT or ES_MULTILINE or
                                     ES_AUTOHSCROLL or ES_AUTOVSCROLL,
                                     0, 0, 0, 0, hwnd, ID_EDIT,
                                     LPCREATESTRUCT.hInstance, NULL)
          if not hwndEdit then                       
              MessageBox (NULL, L"Cannot Create Edit Control",
                      szAppName, MB_ICONERROR)
          return 0
     end if
                 
         
     case WM_SETFOCUS
          SetFocus (hwndEdit) 
         
     case WM_SIZE 
          MoveWindow (hwndEdit, 0, 0, LOWORD (lParam), HIWORD (lParam), TRUE) 
         
     case WM_COMMAND
          if LOWORD (wParam) = ID_EDIT then
               if HIWORD (wParam) = EN_ERRSPACE or
                         HIWORD (wParam) = EN_MAXTEXT then

                    MessageBox (hwnd, L"Edit control out of space.",
                                szAppName, MB_OK or MB_ICONSTOP) 
               end if
          end if
               
     case WM_DESTROY
          PostQuitMessage (0) 
     end select
     return DefWindowProc (hwnd, message, wParam, lParam)
end function

WinMain ()

@Charles: if you consider to add CorewinW.inc etc in some way, perhaps you can check windataW.inc for the types? In this example it does not matter, but I suppose char* must be changed to wchar* etc. I also forgot to modify in CorewinW.inc the title Corewin to CorewinW.

But everthing is there to start real Windows programming using Unicode.

Roland

Patrice Terrier

  • Guest
Re: Programming with Unicode
« Reply #9 on: March 12, 2020, 12:51:27 PM »
As for myself, since i moved to 64-bit, i only use UNICODE, because both 64-bit and UNICODE are the OS native for more than a decade...

Charles Pegge

  • Guest
Re: Programming with Unicode
« Reply #10 on: March 12, 2020, 02:20:42 PM »
O2 will also accept source code in both Ansi and Unicode.

It is possible to define symbols in Unicode:

double ∞ = 1/0
double ☠ = -∞
print l"☠ "  ☠  '#-INF


But Scintilla does not support Unicode, so we will have to switch to a RichText-based edit control for the IDE...

Arnold

  • Guest
Re: Programming with Unicode
« Reply #11 on: March 13, 2020, 01:27:59 AM »
I would not change anything with Oxygenbasic. Yet the L" notation for wstring texts and some extra include files will make it extremely comfortable to code with Unicode. To develop an app with Unicode I can still use ansi text files. And if I use UTF16-LE files with foreign character sets (Oxide cannot read them at the moment) then until now I found no problem with co2.exe. It is exciting that there are so many options.

Charles Pegge

  • Guest
Re: Programming with Unicode
« Reply #12 on: March 13, 2020, 02:11:36 AM »
O2 can read UTF-16BE (big endian) as well as UT-16LE (little endian) and UTF-8 source code.

Internally  the text of each source file is converted into ascii with encodings for Unicode characters

Arnold

  • Guest
Re: Programming with Unicode
« Reply #13 on: March 17, 2020, 03:30:13 PM »
This is another instructive example of "Programming Windows". It can be run either using corewinW.inc (found in reply #2 above) or using corewin.inc - in this case the WinApi functions must be adapted to the Unicode versions, which is an interesting exercise anyway. Details and info about the program can be found in Chapter 6 of the book. I wonder how the output will look like in other languages.

One aspect of the app impressed me a lot. I did not expect that using TextOut in about line 237 would work as is in Oxygenbasic, with applying wsprintf and szFormat and the different expressions. But it worked! Very cool.

KeyView2.o2bas
Code: [Select]
/*--------------------------------------------------------
   KeyView2.o2bas -- Displays Keyboard and Character Messages
   adapted to Unicode and ported to Oxygen Basic
   The code in C is discussed in: Programming Windows, Fifth Edition
                 (c) Charles Petzold, 1998
  --------------------------------------------------------*/
$ filename "KeyView2.exe"
'uses rtl32
'uses rtl64

uses corewinW

% SM_CXMAXIMIZED 61
% SM_CYMAXIMIZED 62
% SYSTEM_FONT 13
% WM_INPUTLANGCHANGE 0x0051
% WM_DISPLAYCHANGE 0x007E

type TEXTMETRICW
  long tmHeight
  long tmAscent
  long tmDescent
  long tmInternalLeading
  long tmExternalLeading
  long tmAveCharWidth
  long tmMaxCharWidth
  long tmWeight
  long tmOverhang
  long tmDigitizedAspectX
  long tmDigitizedAspectY
  wchar tmFirstChar
  wchar tmLastChar
  wchar tmDefaultChar
  wchar tmBreakChar
  byte tmItalic
  byte tmUnderlined
  byte tmStruckOut
  byte tmPitchAndFamily
  byte tmCharSet
end type
typedef TEXTMETRICW TEXTMETRIC


' Helper functions
macro iif int(R, A,B,C)
if A then R=B else R=C
end macro

macro iif$ wstring(R, A,B,C)
if A then R=B else R=C
end macro

function min(int a,b)
   if a<b then return a
   return b
end function

indexbase 0

sys hInstance = GetModuleHandle (NULL)
int iCmdShow = SW_SHOW

function WinMain () as sys
     wstring      szAppName = L"KeyView2"
     sys          hwnd
     MSG          msg
     WNDCLASS     wndclass
     
     wndclass.style         = CS_HREDRAW or CS_VREDRAW
     wndclass.lpfnWndProc   = @WndProc
     wndclass.cbClsExtra    = 0
     wndclass.cbWndExtra    = 0
     wndclass.hInstance     = hInstance
     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION)
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW)
     wndclass.hbrBackground = GetStockObject (WHITE_BRUSH)
     wndclass.lpszMenuName  = NULL
     wndclass.lpszClassName = strptr szAppName

     if not RegisterClass (&wndclass) then
          MessageBox (NULL, L"Cannot RegisterClass wndclass",
                      szAppName, MB_ICONERROR)
          return 0
     end if
     
     hwnd = CreateWindowEx (0,
                            szAppName, L"Keyboard Message Viewer #2",
                            WS_OVERLAPPEDWINDOW,
                            CW_USEDEFAULT, CW_USEDEFAULT,
                            CW_USEDEFAULT, CW_USEDEFAULT,
                            NULL, NULL, hInstance, NULL)
     
     if not hwnd then                       
          MessageBox (NULL, L"Cannot Create Main Window",
                      szAppName, MB_ICONERROR)
          return 0
     end if
     
     ShowWindow (hwnd, iCmdShow)
     UpdateWindow (hwnd)
     
     while GetMessage (&msg, NULL, 0, 0)
          TranslateMessage (&msg)
          DispatchMessage (&msg)
     wend
 
end function

function WndProc (sys hwnd, uint message, sys wParam, sys lParam) as sys callback
     static DWORD dwCharSet = DEFAULT_CHARSET             
     static int   cxClientMax, cyClientMax, cxClient, cyClient, cxChar, cyChar
     static int   cLinesMax, cLines
     static MSG   *pmsg
     static sys   pMem
     static RECT  rectScroll
     
     wstring szTop = L"Message        Key       Char         Repeat Scan Ext ALT Prev Tran"
     wstring szUnd = L"_______        ___       ____         ______ ____ ___ ___ ____ ____"

     ' array of two strings!
     wstring szFormat[] = { L"%-13s %3d %-19s%c%6u %4d %3s %3s %4s %4s",
                            L"%-13s            0x%04X%1s%c  %9u %4d %3s %3s %4s %4s" }

     wstring szYes  = L"Yes"
     wstring szNo   = L"No"
     wstring szDown = L"Down"
     wstring szUp   = L"Up"

     ' array of 8 strings
     wstring szMessage [] = {
                         L"WM_KEYDOWN",    L"WM_KEYUP",
                         L"WM_CHAR",       L"WM_DEADCHAR",
                         L"WM_SYSKEYDOWN", L"WM_SYSKEYUP",
                         L"WM_SYSCHAR",    L"WM_SYSDEADCHAR" }
     sys          hdc
     int          i, iType
     PAINTSTRUCT  ps
     wchar        szBuffer[128], szKeyName [32]
     TEXTMETRIC   tm

     switch message {
     case WM_INPUTLANGCHANGE
          dwCharSet = wParam
                                   // fall through
     case WM_CREATE
     case WM_DISPLAYCHANGE
     
               // Get maximum size of client area
          cxClientMax = GetSystemMetrics (SM_CXMAXIMIZED)
          cyClientMax = GetSystemMetrics (SM_CYMAXIMIZED)

              // Get character size for fixed-pitch font
          hdc = GetDC (hwnd)

          SelectObject (hdc, CreateFont (0, 0, 0, 0, 0, 0, 0, 0,
                         dwCharSet, 0, 0, 0, FIXED_PITCH, NULL))                 
          GetTextMetrics (hdc, &tm)
          cxChar = tm.tmAveCharWidth
          cyChar = tm.tmHeight

          DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT)))
          ReleaseDC (hwnd, hdc)

               // Allocate memory for display lines
          if pMem then freememory pmsg

          cLinesMax = cyClientMax / cyChar

          pMem = getmemory (cLinesMax * sizeof (MSG))
          @pmsg = pMem
          cLines = 0
                                   // fall through
     case WM_SIZE
          if message = WM_SIZE then
               cxClient = LOWORD (lParam)
               cyClient = HIWORD (lParam)
          end if
         
               // Calculate scrolling rectangle
          rectScroll.left   = 0
          rectScroll.right  = cxClient
          rectScroll.top    = cyChar
          rectScroll.bottom = cyChar * (cyClient / cyChar)

          InvalidateRect (hwnd, NULL, TRUE)

          if message = WM_INPUTLANGCHANGE then return TRUE
          return 0
         
     case WM_KEYDOWN
     case WM_KEYUP
     case WM_CHAR
     case WM_DEADCHAR
     case WM_SYSKEYDOWN
     case WM_SYSKEYUP
     case WM_SYSCHAR
     case WM_SYSDEADCHAR

               // Rearrange storage array
          for i = cLinesMax - 1 to > 0 step -1         
               pmsg[i] = pmsg[i - 1]
          next i

               // Store new message
          pmsg[0].hwnd = hwnd
          pmsg[0].message = message
          pmsg[0].wParam = wParam
          pmsg[0].lParam = lParam

          cLines = min (cLines + 1, cLinesMax)

               // Scroll up the display
          ScrollWindow (hwnd, 0, -cyChar, &rectScroll, &rectScroll)

          break         // ie, call DefWindowProc so Sys messages work
         
     case WM_PAINT
          hdc = BeginPaint (hwnd, &ps)

          SelectObject (hdc, CreateFont (0, 0, 0, 0, 0, 0, 0, 0,
                          dwCharSet, 0, 0, 0, FIXED_PITCH, NULL)) 
         
          SetBkMode (hdc, TRANSPARENT)
          TextOut (hdc, 0, 0, szTop, len (szTop))
          TextOut (hdc, 0, 0, szUnd, len (szUnd))

          int limit = min (cLines, cyClient / cyChar - 1)         
          for i = 0 to < limit
               ' Oxygen: true=-1
               iType = abs(pmsg[i].message == WM_CHAR ||
                           pmsg[i].message == WM_SYSCHAR ||
                           pmsg[i].message == WM_DEADCHAR ||
                           pmsg[i].message == WM_SYSDEADCHAR)
                       
               GetKeyNameText (pmsg[i].lParam, @szKeyName, 32)

               TextOut (hdc, 0, (cyClient \ cyChar - 1 - i) * cyChar, szBuffer,
                        wsprintf (szBuffer, szFormat [iType],
                                  szMessage [pmsg[i].message - WM_KEYFIRST],                   
                                  pmsg[i].wParam,
                                  iif$ (iType != 0, L" " , szKeyName),
                                  iif (iType != 0, pmsg[i].wParam , L" "),
                                  LOWORD (pmsg[i].lParam),
                                  HIWORD (pmsg[i].lParam) & 0xFF,
                                  iif$ (0x01000000 & pmsg[i].lParam, szYes , szNo),
                                  iif$ (0x20000000 & pmsg[i].lParam, szYes , szNo),
                                  iif$ (0x40000000 & pmsg[i].lParam, szDown, szUp),
                                  iif$ (0x80000000 & pmsg[i].lParam, szUp  , szDown)
                                  ))
          next i
          DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT)))
          EndPaint (hwnd, &ps)
          return 0

     case WM_DESTROY
          PostQuitMessage (0)
          return 0
     } 'end switch
     return DefWindowProc (hwnd, message, wParam, lParam)
end function

WinMain ()