Thursday, February 1, 2018

Use of C++ for Learn to Code GR application


Use of Windows Controls from C++

On January 22 I wondered if a language like C could invoke JavaScript of an html file or vice versa.  If so, then JavaScript could be used to display Windows forms and controls while C or some other language could be used for computations, reading and updating files, and the like with the ability to actually compile the C and have errors detected via the compile.

I soon decided against that approach since it didn't seem like cross language usage was possible.  So I thought I would find out how to directly invoke Windows control functions from C or C++ without the use of a visual compiler.

The next day I found a Microsoft post with sample code to display a window and spent the day getting it to work using the GNAT compiler for C and C++.  This involved finding where I could find the needed windows.h header file and working out why the Microsoft code wouldn't compile.

Another internet search found the answer as to where windows.h was located.  It said "Check it out under Program Files (x86)\Windows Kits\8.1\Include\um\Windows.h".  Doing a Windows Explorer search of the Program Files (x86) folder produced a number of other folders where I had these headers on my computer.  So I picked the one with the most recent date and copied it to a subfolder in the folder where I was building the new application and changed the GNAT project to include it.

However, the Microsoft example wouldn't compile and I found another function that I needed to include and that some of variables needed to be typecast to enable GNAT, at least, to successfully compile the project.  I also found that the GNAT folders contained the needed headers that, for some reason, the compiler hadn't used when I first started.

After that I could run the application and it would display the window with its three buttons in the upper right corner.  And the buttons worked correctly. But the maximize only had the original portion of the window with a white background and, after the restore of a minimize, the background was black instead of white.

Therefore, I neatened up the code (including added code to find what error I was getting before I solved the last refusal to compile problem when I had removed one statement).  No matter what I did trying to get an errorless compile with WHITE_BRUSH mentioned, I couldn't so I used 0 instead where the wingdi.h header defines WHITE_BRUSH as 0.

The result is as follows.

// from https://msdn.mircosoft.com/en-us/library/windows/desktop/ms633575(v=vs.85).aspx
// as modified

#include <windows.h>
#include <wingdi.h>

// Global variable

HINSTANCE hinst;

// Function prototypes.

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

// Application entry point.

int WINAPI WinMain(HINSTANCE hinstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
  MSG msg;

  BOOL result;
  result = InitApplication(hinstance);
  if (!result)
        return FALSE;

    if (!InitInstance(hinstance, nCmdShow))
        return FALSE;

    BOOL fGotMessage;
    while ((fGotMessage = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0 && fGotMessage != -1)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
    UNREFERENCED_PARAMETER(lpCmdLine);
} // end WinMain

LRESULT CALLBACK MainWndProc( HWND hwnd,        // handle to window
                              UINT uMsg,        // message identifier
                              WPARAM wParam,    // first message parameter
                              LPARAM lParam)    // second message parameter
{ 
  switch (uMsg)
  {
    case WM_CREATE:
      // Initialize the window.
      return 0;

    case WM_PAINT:
      // Paint the window's client area.
      return 0;

    case WM_SIZE:
      // Set the size and position of the window.
      return 0;

    case WM_DESTROY:
      // Clean up window-specific data objects.
      return 0;

    //
    // Process other messages.
    //

    default:
      return DefWindowProc(hwnd, uMsg, wParam, lParam);
  }
  return 0;
} // end MainWndProc

BOOL InitApplication(HINSTANCE hinstance)
{
  WNDCLASSEX wcx;

  // Fill in the window class structure with parameters
  // that describe the main window.

  wcx.cbSize = sizeof(wcx);          // size of structure
  wcx.style = CS_HREDRAW | CS_VREDRAW;      // redraw if size changes
  wcx.lpfnWndProc = (WNDPROC) MainWndProc;  // points to window procedure
  wcx.cbClsExtra = 0;                // no extra class memory
  wcx.cbWndExtra = 0;                // no extra window memory
  wcx.hInstance = hinstance;         // handle to instance
  wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION); // predefined app. icon
  wcx.hCursor = LoadCursor(NULL, IDC_ARROW);   // predefined arrow
  wcx.hbrBackground = (HBRUSH)0;     // white background brush
  wcx.lpszMenuName =  "MainMenu";    // name of menu resource
  wcx.lpszClassName = "MainWClass";  // name of window class
  wcx.hIconSm = (HICON)LoadImage(hinstance, // small class icon
                                 MAKEINTRESOURCE(5),
                                 IMAGE_ICON,
                                 GetSystemMetrics(SM_CXSMICON),
                                 GetSystemMetrics(SM_CYSMICON),
                                 LR_DEFAULTCOLOR);

  // Register the window class.
  ATOM x = RegisterClassEx(&wcx);
  DWORD y;
  if (x==0)
  { y = GetLastError();
  }
 
  // return RegisterClassEx(&wcx);
  return (BOOL)x;
} // end InitApplication

BOOL InitInstance(HINSTANCE hinstance, int nCmdShow)
{
  HWND hwnd;

  // Save the application-instance handle.

  hinst = hinstance;

  // Create the main window.

  hwnd = CreateWindow
         ( "MainWClass",        // name of window class
           "Sample",            // title-bar string
           WS_OVERLAPPEDWINDOW, // top-level window
           CW_USEDEFAULT,       // default horizontal position
           CW_USEDEFAULT,       // default vertical position
           CW_USEDEFAULT,       // default width
           CW_USEDEFAULT,       // default height
           (HWND) NULL,         // no owner window
           (HMENU) NULL,        // use class menu
           hinstance,           // handle to application instance
           (LPVOID) NULL);      // no window-creation data

  if (!hwnd)
    return FALSE;

  // Show the window and send a WM_PAINT message to the window
  // procedure.

  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);
  return TRUE;

} // end InitInstance

While trying to find a way to repaint the screen after a resize so that the background would be white again instead of black, I found out that GNAT had the needed headers all along.  So a mystery why it didn't use its own windows.h and wingdi.h from the start.

I found that I could use
  wcx.hbrBackground = (HBRUSH)WHITE_BRUSH;     // white background brush
if I didn't use SetStockObject which the GNAT linker couldn't find. 

However, changing the window size with its hide button and then restoring it or with its full screen button still resulted in a black background.  Black for the extra screen space in the one case and black after restoring the hidden window in the other.  If the full screen is hidden and then restored, the entire screen is then black.

I added a case statement for WM_ERASEBKGND to the callback and fetched the class info.  It is invoked a few times.  But each time it returns that the HBRUSH color for the background is 0 and hence still white even thou the window is black after the restore from minimize.  So the class info attributes are unchanged but painting the window doesn't seem to be using them after a minimize and restore or a maximize.

I finally found the answer in another internet search where

c++ - How to write text into window? - Stack Overflow

https://stackoverflow.com/.../5946596/how-to-write-text-into-window
appeared as one of the options.  It had the use of
  wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
rather than what the Microsoft example had had.  This worked.  [Of course, the Microsoft example had had
  wcx.hbrBackground = GetStockObject(WHITE_BRUSH);
which didn't work with the GNAT compiler since it couldn't link in GetStockObject.]

The window was created with a white background.  Then after the minimize and restore it was still white.  And upon maximize the entire screen window was all white. 

Problem solved on Thursday afternoon.  So a good share of three days overall to display a window that works correctly using just C++ via the GNAT compiler with its GPS windows interface.

Next Step:

After getting the display of the window to work correctly I decided to implement the same application (Learn to Code GR) of my recent posts in non-visual C++.

As a first step I tried using the OpenFileDialog function of commdlg.h that allows the user to navigate folders and open a particular file so that the file wouldn't need to be hard coded.  In the visual C# application, this was done along with a Windows control to click to enter the event handler code from which this function could be invoked.  So I was thinking of having such a control on the window.  But first just invoking the function after the window had been displayed.

After numerous attempts I couldn't get a GNAT build to include the code corresponding to the commdlg.h header.

Therefore, I stopped work on that approach for the time being and decided to just implement the previous Learn to Code GR application in C++ with the needed file path hard coded in the application.

Since the application is in C++ I used classes to retain the file data for the different keys, their associated values, and the number of times a particular value was specified for a key.

The C++ application code is as follows.  As with the previous versions of the application that used languages that support the concept of a class, most of the code is classless with only the structures to retain the data encoded as a class.  However, unlike the others, this one only reads the file a record at a time rather than the entire file all at once.  The KeyTable class retains the data with multiple instances of the Key class for a particular key with its arrays to retain the key and arrays to retain the various values and the corresponding number of times the value was referenced (the sums array).

WinMain.cpp
// from https://msdn.mircosoft.com/en-us/library/windows/desktop/ms633575(v=vs.85).aspx
// as modified for the WinMain portion of this file.
#include <stdio.h>
#include <iostream>
#include <cstring>

#include <windows.h>
#include <wingdi.h>

#include <keytable.h>

// Global variable

HINSTANCE hinst;

// Function prototypes.

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

BOOL StartApp();

// Application entry point.

int WINAPI WinMain( HINSTANCE hinstance,
                    HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine,
                    int nCmdShow )
{
  MSG msg;

  BOOL fileOpenEvent;
  fileOpenEvent = TRUE; // act as if File control clicked

  BOOL result;
  result = InitApplication(hinstance);
  if (!result)
        return FALSE;

    if (!InitInstance(hinstance, nCmdShow))
        return FALSE;

    BOOL fGotMessage;
    while ((fGotMessage = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0 && fGotMessage != -1)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);

        if (fileOpenEvent)
        {
            fileOpenEvent = FALSE; // dummy indication that can only be used once
            // Start main portion of the application.
            result = StartApp();
        }

    }

    return msg.wParam;
    UNREFERENCED_PARAMETER(lpCmdLine);

} // end WinMain

// To treat Windows events
LRESULT CALLBACK MainWndProc( HWND hwnd,        // handle to window
                              UINT uMsg,        // message identifier
                              WPARAM wParam,    // first message parameter
                              LPARAM lParam )   // second message parameter
{

  PAINTSTRUCT ps;
  HDC hdc;

  switch (uMsg)
  {
    case WM_CREATE:
      // Initialize the window.
      return 0;

    case WM_PAINT:
    {
      // Paint the window's client area.
      return 0;
    }

    case WM_SIZE:
      // Set the size and position of the window.
      return 0;

    case WM_DESTROY:
      // Clean up window-specific data objects.
      return 0;

    // Process other messages.

    default:
      return DefWindowProc(hwnd, uMsg, wParam, lParam);
  }
  return 0;

} // end MainWndProc

BOOL InitApplication(HINSTANCE hinstance)
{
  WNDCLASSEX wcx;

  // Fill in the window class structure with parameters
  // that describe the main window.

  wcx.cbSize = sizeof(wcx);          // size of structure
  wcx.style = CS_HREDRAW | CS_VREDRAW;      // redraw if size changes
  wcx.lpfnWndProc = (WNDPROC) MainWndProc;  // points to window procedure
  wcx.cbClsExtra = 0;                // no extra class memory
  wcx.cbWndExtra = 0;                // no extra window memory
  wcx.hInstance = hinstance;         // handle to instance
  wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);    // predefined app. icon
  wcx.hCursor = LoadCursor(NULL, IDC_ARROW);      // predefined arrow
//wcx.hbrBackground = (HBRUSH)GetStockObject((int)WHITE_BRUSH); // white background brush
  wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // white background brush
  wcx.lpszMenuName =  "MainMenu";    // name of menu resource
  wcx.lpszClassName = "MainWClass";  // name of window class
  wcx.hIconSm = (HICON)LoadImage(hinstance, // small class icon
                                 MAKEINTRESOURCE(5),
                                 IMAGE_ICON,
                                 GetSystemMetrics(SM_CXSMICON),
                                 GetSystemMetrics(SM_CYSMICON),
                                 LR_DEFAULTCOLOR);

  // Register the window class.
  ATOM x = RegisterClassEx(&wcx);
  DWORD y;
  if (x==0)
  { y = GetLastError();
  }

  // return RegisterClassEx(&wcx);
  return (BOOL)x;
} // end InitApplication

BOOL InitInstance(HINSTANCE hinstance, int nCmdShow)
{
  HWND hwnd;

  // Save the application-instance handle.

  hinst = hinstance;

  // Create the main window.

  hwnd = CreateWindow
         ( "MainWClass",        // name of window class
           "LearnToCodeGR",     // title-bar string
           WS_OVERLAPPEDWINDOW, // top-level window
           CW_USEDEFAULT,       // default horizontal position
           CW_USEDEFAULT,       // default vertical position
           CW_USEDEFAULT,       // default width
           CW_USEDEFAULT,       // default height
           (HWND) NULL,         // no owner window
           (HMENU) NULL,        // use class menu
           hinstance,           // handle to application instance
           (LPVOID) NULL);      // no window-creation data

  if (!hwnd)
    return FALSE;

  // Show the window and send a WM_PAINT message to the window
  // procedure.

  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);
  return TRUE;

} // end InitInstance

/*
 * From here to the end is the C++ code for the LearnToCodeGR application.
 */

KeyTable keyTable; // KeyTable class object

// Data to be retained as the key and its associated value with the maximum
// number of references
int maxKey = 0;
int maxValue = 0;
int maxSum = 0;

int savedData[3];

// Report the key and value with the most references
void Report()
{
  // Report individual results
  printf("\n");
  printf("Report   ");
  printf("\n");

  for (int i = 0; i < keyTable.getKeyCount(); i++)
  {
    for (int j = 0; j < keyTable.getValueCount(i); j++)
    {
      std::string report = ("Key ");
      report = report + std::to_string(keyTable.getKey(i));
      report = report + (" Value ");
      report = report + std::to_string(keyTable.getValue(i,j));
      report = report + (" Sum ");
      report = report + std::to_string(keyTable.getSum(i,j));
      std::cout << report << '\n';
    } // end for loop
  } // end for loop

  // Items identifying the key with the largest number of a particular value
  int key = 0;
  int value = 0;
  int sum = 0;

  for (int i = 0; i < keyTable.getKeyCount(); i++)
  {
    for (int j = 0; j < keyTable.getValueCount(i); j++)
    {
      if (keyTable.getSum(i,j) > sum) // save key and value with greater sum
      {
        key = keyTable.getKey(i);
        value = keyTable.getValue(i,j);
        sum = keyTable.getSum(i,j);
      }
    } // end for loop
  } // end for loop

  printf("\n");
  std::string report = ("Key and Value with greatest Sum ");
  report = report + std::to_string(key) + " ";
  report = report + std::to_string(value) + " ";
  report = report + std::to_string(sum);
  std::cout << report << '\n';

  return;
} // end Report

// Update keyTable with data from a parsed line
void Update(int nKey, int nValue1, int nValue2)
{
  // This function first checks if the key is new and, if so, adds it to the
  // keys array.  It then does similar for the values associated with it.

  // Check whether the key is already in the table
  int keyIndex = -1;
  int valueIndex = -1;

  int keyCount = keyTable.getKeyCount();
  for (int i = 0; i < keyCount; i++)
  {
    if (keyTable.getKey(i) == nKey)
    {
      keyIndex = i;  // key already in table
      break; // exit loop
    }
  } // end loop
  if (keyIndex < 0) // key not in the table
  {
    keyIndex = keyTable.setKey(nKey); // add key
    valueIndex = keyTable.setValue(keyIndex,nValue1); // add value for key
    if (nValue1 != nValue2)
    { keyTable.setValue(keyIndex,nValue2); }
    else
    { keyTable.incSum(keyIndex,valueIndex); }
  }
  else // key in the table
  {
    int valueCount = keyTable.getValueCount(keyIndex);
    valueIndex = keyTable.setValue(keyIndex,nValue1); // add value for key
    if (nValue1 != nValue2)
    { keyTable.setValue(keyIndex,nValue2); }
    else
    { keyTable.incSum(keyIndex,valueIndex); }
  }

  return;

} // end Update

#define NINE 57 // ASCII character for digit 9
#define ZERO 48 // ASCII character for digit 0

// Convert character array to int
int toInt(char* data, int length) //iS, int iE)
{
  int index = length - 1; // loop in reverse
  int digit;
  int m = 1;      // multiplier for shift
  int number = 0; // Numeric result

  while (index >= 0) //iS)
  {
    digit = data[index] - ZERO; // convert ASCII to digit
    number = number + (m * digit);
    m = m * 10;
    index--;
  }

  return number;

} // end toInt

enum Scan //Fields
{ BYPASS, FKEY, VALUE1, VALUE2 };

// Extract the fields of interest from the file's line and retain the data
void Parse(int count, char* data)
{
  #define CR '\r' // carriage return
  #define HT '\t' // horizontal tab
  #define LF '\n' // line feed
  #define TRUE 1

  // Parse the supplied line of data from the file to obtain the three
  // fields of interest (key and two values), convert those fields to
  // integers, and then update a data structure (the KeyTable class) to
  // retain the data for evaluation when the complete file has been parsed.

  int keyValue = 0;
  int value1Value = 0;
  int value2Value = 0;

  char alphaDigits [10];
  int numDigits = 0;

  bool updateDone = false;

  int offset = 0; // offset into data
  Scan scanPhase = BYPASS;

  // Parse all bytes of record previously read from the file.  Each field of
  // interest is preceded by a horizontal tab.  The end of the final field
  // is marked by a CR LF or only a LF (NL) except the last record where
  // there are no more characters to be treated.
  while (offset < count)
  {
    switch (scanPhase)
    {
      case BYPASS:
        if (data[offset] == HT)
        {
          scanPhase = FKEY;
          keyValue = 0;
          value1Value = 0;
          value2Value = 0;
          numDigits = 0;
        }
        break;

      case FKEY:
        if (data[offset] != HT)
        {
          if ((data[offset] >= ZERO) & (data[offset] <= NINE))
          {
            alphaDigits[numDigits] = data[offset];
            numDigits++;
          }
        }
        else
        {
          keyValue = toInt(alphaDigits, numDigits);
          scanPhase = VALUE1;
          numDigits = 0;
        }
        break;

      case VALUE1:
        if (data[offset] != HT)
        {
          if ((data[offset] >= ZERO) & (data[offset] <= NINE))
          {
            alphaDigits[numDigits] = data[offset];
            numDigits++;
          }
        }
        else
        {
          value1Value = toInt(alphaDigits, numDigits);
          scanPhase = VALUE2;
          numDigits = 0;
        }
        break;

      case VALUE2:
        if ((data[offset] == LF) | (data[offset] == CR) | offset+1 >= count)
        {
          value2Value = toInt(alphaDigits, numDigits);
          // Update tables with the data from the record.
          Update( keyValue, value1Value, value2Value );
          updateDone = true;

          // Initialize for next record
          keyValue = 0;
          value1Value = 0;
          value2Value = 0;
          numDigits = 0;
          scanPhase = BYPASS;
        }
        else
        {
          if ((data[offset] >= ZERO) & (data[offset] <= NINE))
          {
            alphaDigits[numDigits] = data[offset];
            numDigits++;
          }
        }
        break;
    } // end switch

    // Complete processing of final record when not terminated by New Line
    offset++; // increment to next data buffer position
    if (offset >= count) // no more data
    {
      if (value2Value > 0) // last record fully parsed without trailing NL
      {
        if (!updateDone)
        {
          value2Value = toInt(alphaDigits, numDigits);
          Update( keyValue, value1Value, value2Value );
          updateDone = true;
        }
      }
      break; // exit loop
    }

  } // end loop

  return;

} // end Parse

BOOL StartApp()
// Open the LearnToCodeGR file and treat it.
{
  FILE* file;
  char buffer[30];
  file = fopen("C:\\Source\\LearnToCodeGR\\max-col-sum-by-Key.tsv", "r");

  int currPos;
  int lastPos = 0;
  int length;

  if(file == NULL)
  {
    perror("Error opening file");
    return FALSE;
  }
  else
  { // Read file records until end-of-file
    while (!feof(file))
    {
      fgets(buffer, 30, (FILE*)file);
      currPos = ftell(file);
      length = currPos - lastPos; // number of characters read into buffer
      lastPos = currPos;
      for (int i=0; i < length; i++) // output the
        printf("%c ", buffer[i]);    //   record to
      printf("\n");                  //   the console

      // Parse the line and update to retain all the necessary data.
      Parse(length, buffer);
    }
    fclose(file);

    // Report the results.
    Report();
  }
  return TRUE;
} // end StartApp

KeyTable.h
#ifndef KEYTABLECPP
#define KEYTABLECPP

// Key class
class Key
{
  public:
    Key(); // constructor

    int key;        // value of key
    int valueCount; // number of different values associated with key
    static const int VALUESSIZE = 20; // initial size of values & sums
                                      //  arrays for particular key
    int *values;    // will point to the array
    int *sums;      //  "

}; // end class Key

// KeyTable class
class KeyTable //: public Component
{

  public:
    KeyTable(); // constructor

    int getKey(int index);
    int getKeyCount();
    // Return number of different values for key at index
    int getValueCount(int index);

    int setKey(int key);
    int setValue(int index, int value);
    int getValue(int indexKey, int indexValue);
    int incSum(int indexKey, int indexValue);
    int getSum(int indexKey, int indexValue);

  private:
    int keyCount; // number of different keys
    static const int TABLESIZE = 30; // initial size of keys array
    Key* keys = new Key[TABLESIZE];  // instances of "struct" class

}; // end class KeyTable

#endif // KEYTABLECPP

KeyTable.cpp
#include <iostream>

#include "keytable.h"

Key::Key() // constructor
{
  valueCount = 0;
  key = 0;
  values = new int[VALUESSIZE];
  sums = new int[VALUESSIZE];
  for (int i = 0; i < 20; i++)
  {
    values[i] = -1; // so no match until a value added
    sums[i] = 0;    // so any first increment of the sum will be to 1
  }
} // end Key constructor


KeyTable::KeyTable() // constructor
{
  this->keyCount = 0;
} // end KeyTable constructor

// Return key at index
KeyTable::getKey(int index)
{
  int tempKey;
  if (index < KeyTable::keyCount)
  {
    int key = KeyTable::keys[index].key;
    return key;
  }
  else
  {
    return 0;
  }
} // end getKey

// Return number of keys in table
KeyTable::getKeyCount()
{
  return keyCount;
} // end getKeyCount

KeyTable::getValueCount(int index)
{
  return this->keys[index].valueCount;
} // end getValueCount

KeyTable::setKey(int key)
{
  for (int i = 0; i < keyCount; i++)
  {
    if (this->keys[i].key == key) // key already in table
    {
      return i;
    }
  } // end for loop
  if (this->keyCount < 30) // add key to table if room
  {
    this->keys[keyCount].key = key;
    int index = keyCount;
    keyCount++;
    return index;
  }
  else
  {
    std::cout << "Error: Too many different keys" << "\n";
    return -1;
  }
} // end setKey

// Add value for key at index, return value index
KeyTable::setValue(int index, int value)
{
  for (int i = 0; i < this->keys[index].valueCount; i++)
  {
    if ( this->keys[index].values[i] == value )
    {
      // Increment sum
      this->keys[index].sums[i]++;
      return i;
    }
  } // end for loop

  // Add value to array for key since didn't return for existing value
  this->keys[index].values[this->keys[index].valueCount] = value;
  this->keys[index].sums[this->keys[index].valueCount]++;
  int valueIndex = this->keys[index].valueCount;
  this->keys[index].valueCount++; // increment number of values for key
  return valueIndex;
} // end setValue

// Get value for key at indexes, return value
KeyTable::getValue(int indexKey, int indexValue)
{
  return this->keys[indexKey].values[indexValue];
} // end getValue

// Increment Sum for value of key at indexKey and value at indexValue
KeyTable::incSum(int indexKey, int indexValue)
{
  // Increment sum
  int count = this->keys[indexKey].sums[indexValue]++;

  return count;
} // end incSum

// Get sum for key at indexes, return sum
KeyTable::getSum(int indexKey, int indexValue)
{
  return this->keys[indexKey].sums[indexValue];
} // end getSum

The window that was created by the initial portion of the WinMain.cpp file is below followed by the last portion of the console output from running the application.




Next I will return to my attempt to populate the window with Windows controls using a non-visual version of C++.

Tuesday, January 9, 2018

JavaScript Report 2


Continuing from the previous JavaScript post, I wanted to do some different user interfaces. 

At first I wanted to do a form with the button that allowed the user to navigate and selected the file to be parsed.  And since I found a method to do a popup I thought I would use a popup to report the results.  This post had html that displayed a form with three label, edit box combinations for the entry of a name, address and phone number plus a button to be clicked to cause the entries to be displayed in a popup.

So I wanted to do something similar by just putting the button of the first solution in the form and then when the file had been parsed, displaying the results in a popup.

However, since the reference to the button used in the first solution was somehow hidden – that is, never directly referenced in the solution with no code for it visible in the html.  As mentioned, this hidden button allowed the user to navigate Windows folders to find the wanted file and select it as the text file to be treated.  So the question was "how to select the file to be parsed?"

So I attempted to just have the user enter the path to the file in an edit box.  But the method to open the file from the first post was using a file passed as the first element in an array structure where the selection was somehow more than just the path to file.  So just supplying the path didn't work to open the file.

Continuing the searches I did find another post by Microsoft with the code to select a file via Windows Explorer and drag and drop it into a drop box.  Capturing the code provided it worked to select a file that way.  And in used the files in an array where each element of the array was part of a structure; that is,
            f = files[i]; // If anything goes awry, the error would occur here.
            output.push('<li><strong>',
                        f.name, '</strong> (',
                        f.type || 'unknown file type',
                        ') - ',
                        f.size, ' bytes, last modified: ',
                        f.lastModifiedDate,
                        '</li>');
            document.getElementById('list').innerHTML = output.join('');
to display data about the selected file.  So I figured that perhaps the filePath.files[0] of the first post that was used to open the file would be of the same structure as the files[i] of the Microsoft post.

Therefore, I set about adding to the Microsoft drag and drop box to open the selected file and having the learntocode.js code of my previous post parse the file and determine the file contents key and value with the maximum number of references.

Before lunch it was working and was time to add the popup to display the results.

However the popup refused to pop up.  Even thou the internet samples did.  Then found out that the window.open was returning null when used in my code.

It took quite a while to get a working application.  I would make a change and the html wouldn't work again and I would have to start over since I couldn't detect what part of my change caused it to stop working.  Therefore, I began to retain working samples and make a copy from which to continue so I could go back to the working sample when changes to the copy ceased to work.

Eventually, via alerts, I found some strange behaviour.  The display() function that did a window.open of the popup was invoked following the return from the parse() function of the learntocode.js file.  The parse() function was getting called twice and hence the display() function.  The first time parse was called the buffer from reading the selected file had a 0 length so parse didn't have anything to work with and would return results of 0,0,0.  It would pass this to display.  Then somehow parse would get invoked again with the actual data (as if there was a hidden callback in the display function).  Then, upon return from parse, display would get entered again (although the alert at its beginning wouldn't occur) but now display would have the actual parse results and they would be displayed twice.

Therefore, I added code to only display the results when entered for the second time.

I don't understand this behaviour.  Especially since JavaScript is supposed to be single threaded from what I find on the internet.  I had thought that the window read of the file was being done in another thread so that the regular thread was continuing while the read thread was taking place.  Thus resulting in a buffer length of 0 in the html thread.  Then when the assumed window read thread finished, the buffer contained the data.  Even if so, I don't understand how the parse function was re-entered with the now filled buffer.

But, incrementing the counter in display() and only calling DispWin.document.write (where DispWin is the result of the window.open) upon the second entry resulted in the expected results.

A second problem is that an attempt to display a title for the popup failed.

The browser window, the same one after the file dropped into the drag and drop box, and the popup with the results follow.





This new solution with a drag and drop box for supplying the file to be parsed and the popup to display the results is as follows.

LearntoCode-popup.html

<!DOCTYPE html>
<html>
  <head>
    <title>Drag &amp; Learn To Code - popup</title>
    <meta http-equiv="X-UA-Compatible" content="IE=10">
    <style>
      #fileDropBox {
        width: 20em;
        line-height: 10em;
        border: 1px dashed gray;
        text-align: center;
        color: gray;
        border-radius: 7px;
    </style>

    <script type="text/javascript" src="learntocode.js"></script>

    <script LANGUAGE="JavaScript" type="text/javascript">
    function display(results) {
      var DispWin = window.open('','NewWin', 'toolbar=no,status=no,width=300,height=200');

      message = "<ul><b>Results from Parse of File</b> \n";
      message += "<li><b>NAME: </b>" + results[0];
      message += "<li><b>KEY: </b>" + results[1];
      message += "<li><b>INSTANCES: </b>" + results[2] + "</ul>";

      /* NOTE: For some reason display is entered a second time although after the window.open
               so without the test below the results are displayed twice.  Also, the call to
               parse is done twice that is followed by the call to this function.  The first
               time parse is called, the data buffer is empty whereas the second time it is
               full.  So the first time this function is entered the results passed in are
               0,0,0 while the second time they are the actual results. */
      if (displayEntryCount==1)
      {
          DispWin.document.title = "Learn to Code Popup"; // this doesn't work
          DispWin.document.write(message);
      } // end if
      displayEntryCount++;
    } // end display

    var reader; /*GLOBAL File Reader object*/
    var displayEntryCount = 0; // count of the number of times display is entered

    function checkFileAPI() {
      if (window.File && window.FileReader && window.FileList && window.Blob) {
        reader = new FileReader();
        return true;
      } else {
        alert('The File APIs are not fully supported by your browser. Fallback required.');
        return false;
      }
    } // end checkFileAPI

    function readText(files) {
      var result = checkFileAPI(); // create the FileReader
      var buffer = ""; /* to contain the file text */

      if (files && files[0]) {          
        reader.onload = function (e) {
          buffer = e.target.result;
          treatFile(buffer);
        } // end reader/onload function
        reader.readAsText(files[0]);
        treatFile(buffer);
      } // end if html5 filelist support
      else if (ActiveXObject && files[0]) { // fallback to IE 6-8 support via ActiveX
        try {  //<<< won't work since need filePath that no longer have >>>
          reader = new ActiveXObject("Scripting.FileSystemObject");
          var file = reader.OpenTextFile(files, 1); // ActiveX File Object
          buffer = file.ReadAll(); // text contents of file
          file.Close(); // close file "input stream"
          treatFile(buffer);
        } catch (e) {
          if (e.number == -2146827859) {
            alert('Unable to access local files due to browser security ' +
                  'settings. ' + 'To overcome this, go to ' +
                  'Tools->Internet Options->Security->Custom Level. ' +
                  'Find the setting for "Initialize and script ActiveX ' +
                  'controls not marked as safe" and change it ' +
                  'to "Enable" or "Prompt"');
          }
        }      
      }
      else { // this is where you could fallback to Java Applet, Flash or similar
        return false;
      }      
      return true;

    } // end readText

    function treatFile(buffer)
    {
      var results = [];
      results = parse(buffer);
      display(results);
    } // end treatFile

    </script>
  </head>
  <body>
    <h1>Drag and Drop File Selection</h1>
    <h3>Learn to Code Example 2</h3>
    <p>Using Windows Explorer (or similar), select one or more files (directories are
       not allowed), and then drag them to the below drop box:</p>
    <div id="fileDropBox">Drop files here.</div>
    <ul id="list"></ul>
    <script>
      var message = [];

      if (!window.FileReader) {
        message = '<p>The ' +
                  '<a href="http://dev.w3.org/2006/webapi/FileAPI/" target="_blank">File API</a>s ' +
                  'are not fully supported by this browser.</p>' +
                  '<p>Upgrade your browser to the latest version.</p>';
     
        document.querySelector('body').innerHTML = message;
      }
      else {
        // Set up the file drag and drop listeners:        
        document.getElementById('fileDropBox').addEventListener('dragover', handleDragOver, false);
        document.getElementById('fileDropBox').addEventListener('drop', handleFileSelection, false);
      }

      function handleDragOver(evt) {
        evt.stopPropagation();  // Do not allow the dragover event to bubble.
        evt.preventDefault(); // Prevent default dragover event behavior.
      } // handleDragOver()

      function handleFileSelection(evt) {
        evt.stopPropagation(); // Do not allow the drop event to bubble.
        evt.preventDefault(); // Prevent default drop event behavior.

        var files = evt.dataTransfer.files; // Grab the list of files dragged to the drop box.

        if (!files) {
          msa.alert("<p>At least one selected file is invalid - do not select any folders.</p><p>Please reselect and try again.</p>");
          return;
        }

        // "files" is a FileList of file objects. List a few file object properties:   
        var output = [];
        for (var i = 0, f; i < files.length; i++) {
          try {
            f = files[i]; // If anything goes awry, the error would occur here.
            output.push('<li><strong>',
                        f.name, '</strong> (',
                        f.type || 'unknown file type',
                        ') - ',
                        f.size, ' bytes, last modified: ',
                        f.lastModifiedDate,
                        '</li>');
            document.getElementById('list').innerHTML = output.join('');
          } // try
          catch (fileError) {
            msa.alert("<p>An unspecified file error occurred.</p><p>Selecting one or more folders will cause a file error.</p>");
            console.log("The following error occurred at i = " + i + ": " + fileError); // Send the error object to the browser's debugger console window, if active.
            return;
          } // catch
        } // for
        readText(files);
       
      } // handleFileSelection()     
    </script>   
    <script src="../utilities.js" type="text/javascript"></script> <!-- Provides the msa.alert() method. -->       
  </body>
</html>

The learntocode.js file is unchanged from the initial solution.

learntocode.js

// Global data

// The following would be the KeyData and KeyTable structures if there were
// something like struct or class in JavaScript.
var keyCount = 0;
var valueKey = [];
var valueCount = [];
var valueValues = [,]; // different values of the key
var valueSums = [,];   // sum of values of particular key

// Data to be retained as the key and its associated value with the maximum
// number of references.
var maxKey = 0;
var maxValue = 0;
var maxSum = 0;

// Saved data as parse each line.
var savedData = [];
savedData[0] = 0;
savedData[1] = 0;
savedData[2] = 0;

// Functions

// Parse data lines of the text file.
function parse(data) {

    var CR = "\r";
    var HT = "\t";
    var LF = "\n";

    /* Parse each line of data in the buffer to obtain the three fields of
     * interest, converting those fields to integers into an array, and
     * then updating a data structure to retain the data for evaluation
     * when the complete buffer has been parsed.
     */

    var nextField = 0;   // index of the beginning of next field
    var startField;      // range of indexes of
    var numFields = 0;   // index into dataFields array
    var dataFields = []; // Integer values of the three fields of interest
    dataFields[0] = 0;
    dataFields[1] = 0;
    dataFields[2] = 0;

    var bufSize = data.length;
    var index = 0;

    for (index = 0; index < bufSize; index++ )
    {
        // Parse the buffer line
        if (data[index] == HT) // beginning of a field
        {   startField = nextField; // save starting index
            nextField = index + 1;  // the next byte will contain part of next field
            if (numFields > 0)
            { //convert and store in dataFields
                dataFields[numFields-1] = toInt(data, startField, index - 1);
            } // end if numFields > 0
            numFields++;
        } // end if HT

        // Do the update when find CR or LF of reach the end of the buffer.
        // The last option since the file has no terminators for the last line.
        if ((data[index] == CR) || (data[index] == LF) || (index == (bufSize-1)))
        {
            if (numFields < 4) // only do the update once for each line
            { //convert and store in dataFields
                dataFields[numFields-1] = toInt(data, nextField, index - 1);
                // Save the data to determine the key, value combination with
                // maximum number of references
                update(dataFields);

                // Finished with the line in the data array, initialize
                // for next line.
                nextField = 0;
                startField;
                numFields = 0;
                dataFields[0] = 0;
                dataFields[1] = 0;
                dataFields[2] = 0;
                if ((data[index] == CR) && (data[index+1] == LF))
                { index++ } // bypass the extra char at end of line
            } // end if
        } // end if

    } // end for loop

    // Report the number of references with a particular Key and Value.
    var results = [];
    results = report();
    return results;

}; // end parse
   
// Convert character array to integer
function toInt(data, iS, iE)
{
    var NINE = "9" //57 // ASCII character for digit 9
    var ZERO = "0" //48 // ASCII character for digit 0

    var index = iE; // loop in reverse
    var digit = 0;
    var m = 1;      // multiplier for shift
    var number = 0; // Numeric result

    while (index >= iS)
    {
        if ((data[index] >= ZERO) && (data[index] <= NINE))
        {
            digit = data[index] - ZERO; // convert ASCII to digit
            number = number + (m * digit);
            m = m * 10;
            index--;
        }
    }

    return number;

} // end toInt

// Update the tables for a particular file line.
function update(data)
{
    // This function first checks if the key is new and, if so, adds it to the
    // keys array.  It then does similar for the values associated with it.
    // Note: data[0] is the key while data[1] and data[2] are the two values
    //       associated with the key.

    // Save data to be updated for use by updateTotals since needed by
    // multiple functions
    savedData[0] = data[0];
    savedData[1] = data[1];
    savedData[2] = data[2];

    // Check whether the key is already in the key table
    var keyIndex = -1;
    for (var k = 0; k < keyCount; k++)
    {
        if (valueKey[k] == data[0])
        {
            keyIndex = k; // key already in the table
            break; // exit loop   
        }
    } // end for

    if (keyIndex < 0) // key not in the table
    { // add the key
        keyIndex = keyCount;
        valueKey[keyIndex] = data[0];
        valueCount[keyIndex] = 0;
        keyCount++;
    } // end if

    // Add the values for the key
    addValues(keyIndex, savedData[1], savedData[2]);

} // end update

// Add the values or increment their sums
function addValues(keyIndex, value1, value2)
{
    // Check whether the first value is already in the table
    var valueIndex = -1;
    for (var v = 0; v < valueCount[keyIndex]; v++)
    {
        if (valueValues[keyIndex,v] == value1)
        { // value already in the table
            valueIndex = v;
            valueSums[keyIndex,v]++; // increment its number of references
            if (value1 == value2) // 2nd value the same
            {
                valueSums[keyIndex,v]++; // increment again
            }
            // check if new max
            updateTotals(savedData[0],value1,valueSums[keyIndex,v]);
            break; // exit loop
        }
    } // end loop
 
    if (valueIndex < 0) // value not yet in table
    { // add value to the table - index points to last value checked
        var index = valueCount[keyIndex];
        valueValues[keyIndex,index] = value1;
        valueSums[keyIndex,index] = 1;
        valueCount[keyIndex]++;
        if (value1 == value2) // dupicated value
        {
            valueSums[keyIndex,index]++; // increment to 2
            // check if new max
            updateTotals(savedData[0],value1,valueSums[keyIndex,index]);
        }
        else
        {
            add2ndValue(keyIndex, value2);
        }
    } // end outer if

} // end addValues

function add2ndValue(keyIndex, value2)
{
    // Check whether the second value is already in the table
    var valueIndex = -1;
    for (var v = 0; v < valueCount[keyIndex]; v++)
    {
        if (valueValues[keyIndex,v] == value2)
        { // value already in the table
            valueIndex = v;
            valueSums[keyIndex,valueIndex]++; // increment number of references
            // check if new max
            updateTotals(savedData[0],value2,valueSums[keyIndex,valueIndex]);
            break; // exit loop
        }
    } // end loop
    if (valueIndex < 0) // value not yet in table
    { // add value to the table
        var index = valueCount[keyIndex];
        valueValues[keyIndex,index] = value2;
        valueSums[keyIndex,index] = 1;
        valueCount[keyIndex]++;
        updateTotals(savedData[0],value2,valueSums[keyIndex,index]);
    }

} // end add2ndValue

function updateTotals(key, value, sum)
{
    if (sum > maxSum)
    {
        maxKey = key;
        maxValue = value;
        maxSum = sum;
    }

} // end updateTotals

// Report the key and value with the most references
function report()
{
    var results = [];
    results[0] = maxKey;
    results[1] = maxValue;
    results[2] = maxSum;
    return results;

} // end report