//
// Periodic table of the elements, for Microsoft Windows 3.0.
// Originally written by Gregory A. Jones, uunet!microsoft!gregj.
// Extensively modified by Charles L. Perrin as a Windows learning project. 
//

// This program may be freely distributed.

// Chemical data culled from reference works, including the
//    CRC Handbook of Chemistry and Physics.

#include <windows.h>
#include "element3.h"

//
// Prototypes for functions.
//

long FAR PASCAL WndProc(HWND, unsigned, WORD, LONG);
BOOL FAR PASCAL BoxProcessor(HWND,unsigned,WORD,LONG);
void ElementDisplay(HDC);

//
// A few global variables.
//

char szAppName [] = "Element3";
char szMainMenu [] = "mainmenu";
char szAccTable [] = "mainaccel";
char szHint [] = "HINT:  Point at an element with the mouse.";
char szElement [80];
FARPROC lpBoxHandler;
HANDLE ghInstance, hWhiteBrush, hAccTable, hClipboard;
LPSTR lpszClipboard;
WORD cxBox, cyBox;
int i, iElementOld;
BOOL fThawing, fFreezing, fFrozen;
RECT rect;

//
// Data stored for each element.  Includes name, symbol, atomic weight,
// and location in table (where period one is row 0, family 1a is column 0).
// Atomic number is implicit, since the table is ordered by it.
//

typedef struct tagELEMENT {
    char *pszName;      // Name of the element, i.e. "Hydrogen"
    char *pszSymbol;    // Symbol, i.e. "He"
    char *pszWeight;    // Atomic weight, i.e. "(230)"
    WORD wColumn;       // Column number to draw in
    WORD wRow;          // Row number to draw in
} ELEMENT;

typedef ELEMENT *PELEMENT;

//
// Width and height of the table, and number of elements defined.
//

#define NUM_COLUMNS     18
#define NUM_ROWS        10
#define MAX_ELEMENTS    106

//
// Titles for the families, by column.  Null strings are because "8"
// really covers three columns.
//

char *pszTitles [NUM_COLUMNS] = {
"1a", "2a", "3b", "4b", "5b", "6b", "7b", "", "8", "", "1b", "2b",
"3a", "4a", "5a", "6a", "7a", "0"
};

//
// The elements themselves.  Add new ones on the end if you discover them.
// Also, don't forget to set MAX_ELEMENTS to the new value!
//

ELEMENT elTable [MAX_ELEMENTS] = {
"Hydrogen", "H", "1.0079", 0, 0,
"Helium", "He", "4.00260", 17, 0,
"Lithium", "Li", "6.94", 0, 1,
"Beryllium", "Be", "9.01218", 1, 1,
"Boron", "B", "10.81", 12, 1,
"Carbon", "C", "12.011", 13, 1,
"Nitrogen", "N", "14.0067", 14, 1,
"Oxygen", "O", "15.9994", 15, 1,
"Fluorine", "F", "18.998403", 16, 1,
"Neon", "Ne", "20.17", 17, 1,
"Sodium", "Na", "22.98977", 0, 2,
"Magnesium", "Mg", "24.305", 1, 2,
"Aluminum", "Al", "26.98154", 12, 2,
"Silicon", "Si", "28.0855", 13, 2,
"Phosphorous", "P", "30.97376", 14, 2,
"Sulfur", "S", "32.06", 15, 2,
"Chlorine", "Cl", "35.453", 16, 2,
"Argon", "Ar", "39.948", 17, 2,
"Potassium", "K", "39.0983", 0, 3,
"Calcium", "Ca", "40.08", 1, 3,
"Scandium", "Sc", "44.9559", 2, 3,
"Titanium", "Ti", "47.90", 3, 3,
"Vanadium", "V", "50.9415", 4, 3,
"Chromium", "Cr", "51.996", 5, 3,
"Manganese", "Mn", "54.9380", 6, 3,
"Iron", "Fe", "55.847", 7, 3,
"Cobalt", "Co", "58.9332", 8, 3,
"Nickel", "Ni", "58.71", 9, 3,
"Copper", "Cu", "63.546", 10, 3,
"Zinc", "Zn", "65.38", 11, 3,
"Gallium", "Ga", "69.735", 12, 3,
"Germanium", "Ge", "72.59", 13, 3,
"Arsenic", "As", "74.9216", 14, 3,
"Selenium", "Se", "78.96", 15, 3,
"Bromium", "Br", "79.904", 16, 3,
"Krypton", "Kr", "83.80", 17, 3,
"Rubidium", "Rb", "85.467", 0, 4,
"Strontium", "Sr", "87.62", 1, 4,
"Yttrium", "Y", "88.9059", 2, 4,
"Zirconium", "Zr", "91.22", 3, 4,
"Niobium", "Nb", "92.9064", 4, 4,
"Molybdenum", "Mo", "95.94", 5, 4,
"Technetium", "Tc", "98.9062", 6, 4,
"Ruthenium", "Ru", "101.07", 7, 4,
"Rhodium", "Rh", "102.9055", 8, 4,
"Palladium", "Pd", "106.4", 9, 4,
"Silver", "Ag", "107.868", 10, 4,
"Cadmium", "Cd", "112.41", 11, 4,
"Indium", "In", "114.82", 12, 4,
"Tin", "Sn", "118.69", 13, 4,
"Antimony", "Sb", "121.75", 14, 4,
"Tellurium", "Te", "127.60", 15, 4,
"Iodine", "I", "126.9045", 16, 4,
"Xenon", "Xe", "131.30", 17, 4,
"Cesium", "Cs", "132.9054", 0, 5,
"Barium", "Ba", "137.33", 1, 5,
"Lanthanum", "La", "138.9055", 2, 5,
"Cerium", "Ce", "140.12", 2, 8,
"Praeseodymium", "Pr", "140.9077", 3, 8,
"Neodymium", "Nd", "144.24", 4, 8,
"Promethium", "Pm", "(145)", 5, 8,
"Samarium", "Sm", "150.4", 6, 8,
"Europium", "Eu", "151.96", 7, 8,
"Gadolinium", "Gd", "157.25", 8, 8,
"Terbium", "Tb", "158.9254", 9, 8,
"Dysprosium", "Dy", "162.50", 10, 8,
"Holmium", "Ho", "164.9304", 11, 8,
"Erbium", "Er", "167.26", 12, 8,
"Thulium", "Tm", "168.9342", 13, 8,
"Ytterbium", "Yb", "173.04", 14, 8,
"Lutetium", "Lu", "174.96", 15, 8,
"Hafnium", "Hf", "178.49", 3, 5,
"Tantalum", "Ta", "180.947", 4, 5,
"Tungsten", "W", "183.85", 5, 5,
"Rhenium", "Re", "186.207", 6, 5,
"Osmium", "Os", "190.2", 7, 5,
"Iridium", "Ir", "192.22", 8, 5,
"Platinum", "Pt", "195.09", 9, 5,
"Gold", "Au", "196.9665", 10, 5,
"Mercury", "Hg", "200.59", 11, 5,
"Thallium", "Tl", "204.37", 12, 5,
"Lead", "Pb", "207.2", 13, 5,
"Bismuth", "Bi", "208.9804", 14, 5,
"Polonium", "Po", "(209)", 15, 5,
"Astatine", "At", "(210)", 16, 5,
"Radon", "Rn", "(222)", 17, 5,
"Francium", "Fr", "(223)", 0, 6,
"Radium", "Ra", "226.0254", 1, 6,
"Actinium", "Ac", "(227)", 2, 6,
"Thorium", "Th", "232.0381", 2, 9,
"Protactinium", "Pa", "231.0359", 3, 9,
"Uranium", "U", "238.029", 4, 9,
"Neptunium", "Np", "237.0482", 5, 9,
"Plutonium", "Pu", "(244)", 6, 9,
"Americium", "Am", "(243)", 7, 9,
"Curium", "Cm", "(247)", 8, 9,
"Berkelium", "Bk", "(247)", 9, 9,
"Calfornium", "Cf", "(251)", 10, 9,
"Einsteinium", "Es", "(254)", 11, 9,
"Fermium", "Fm", "(257)", 12, 9,
"Mendelevium", "Md", "(258)", 13, 9,
"Nobelium", "No", "(259)", 14, 9,
"Lawrencium", "Lr", "(260)", 15, 9,
"Rutherfordium", "Rf", "(260)", 3, 6,
"Hahnium", "Ha", "(260)", 4, 6,
"Not Named Yet", "106", "(263)", 5, 6
};

//
// Main entry point for this application.
//

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                        LPSTR lpszCmdLine, int nCmdShow)
{
    HWND hWnd;
    MSG msg;
    int nWidth, nHeight;
    WNDCLASS wndclass;

    //
    // Save handle instance globally.
    //

    ghInstance = hInstance;

    //
    // Register the window class for this application.
    //

    if (!hPrevInstance) {
        wndclass.style = CS_VREDRAW | CS_HREDRAW | CS_SAVEBITS;
        wndclass.lpfnWndProc = WndProc;
        wndclass.cbClsExtra = 0;
        wndclass.cbWndExtra = 0;
        wndclass.hInstance = hInstance;
        wndclass.hIcon = LoadIcon (hInstance, "mainicon");
        wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);
        wndclass.hbrBackground = GetStockObject (WHITE_BRUSH);
        wndclass.lpszMenuName = szMainMenu;
        wndclass.lpszClassName = szAppName;

        if (!RegisterClass (&wndclass))
            return FALSE;
    }

    //
    // Create a white background for future use.
    //
        
    hWhiteBrush = GetStockObject(WHITE_BRUSH);
    
    //
    // Load the keyboard accelerator table.
    //
    
    hAccTable = LoadAccelerators(hInstance,szAccTable);
        
    //
    // Set old element number to cause display on first entry.
    // Set up initial hint text.
    // Set flag to indicate element change is not frozen.
    //
        
    iElementOld = MAX_ELEMENTS + 1;
    lstrcpy(szElement,szHint);
    fFrozen = FALSE;
    
    //
    // Calculate a width of screen width.
    //
    
    nWidth = GetSystemMetrics(SM_CXSCREEN);
    
    //
    // Calculate a height of screen height less twice icon height.
    //
    
    nHeight = GetSystemMetrics(SM_CYSCREEN) - GetSystemMetrics(SM_CYICON) * 2;
        
    //
    // Create and display the main window.
    //

    hWnd = CreateWindow (szAppName, "Periodic Table", WS_OVERLAPPED |
                        WS_CAPTION | WS_THICKFRAME | WS_SYSMENU |
                        WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
                        0, 0, nWidth, nHeight, NULL, NULL,
                        hInstance, NULL);
    if (!hWnd) return FALSE;

    ShowWindow (hWnd, nCmdShow);
    UpdateWindow (hWnd);

    //
    // The world-famous Microsoft Windows message loop with accelerators.
    // By the time you get your first WinApp working, you will be seeing
    // messages in your sleep!
    //

    while (GetMessage (&msg, NULL, 0, 0)) {
    
    //
    // If it's not an accelerator key, translate and dispatch!
    //
    
        if (!TranslateAccelerator(hWnd,hAccTable,&msg))
        {
          TranslateMessage (&msg);
          DispatchMessage (&msg);
        }
    }

    //
    // Return control to caller (Windows).
    //

    return msg.wParam;
}

//
// This module displays the element text in the proper size and color.
//

void ElementDisplay(hDC)
HDC hDC;

{

    // 
    // Calculate bounding box for the DrawText operation (the area that is
    // between Hydrogen and Helium with a 1-pixel border).
    //

    rect.left = ( elTable[0].wColumn + 1 ) * cxBox + 21;
    rect.right = elTable[1].wColumn * cxBox + 19;
    rect.top = elTable[0].wRow  * cyBox + 40;
    rect.bottom = rect.top + cyBox - 1;

    //
    // Fill the element text area with the background.
    //

    FillRect(hDC,&rect,hWhiteBrush);

    // 
    // Switch text color to blue if frozen, or red if not frozen.
    //
        
    if (fFrozen) SetTextColor(hDC,RGB(0,0,255));
            else SetTextColor(hDC,RGB(255,0,0));
    
    //
    // Display the element text, clipped, centered.
    //
        
    DrawText(hDC,szElement,-1,&rect,DT_CENTER | DT_VCENTER);
            
    //  
    // Switch text color back to black.
    //
                
    SetTextColor(hDC,RGB(0,0,0));
    
}    

//
// AboutBox and HelpBox (only the box changes) dialog box function.
//

BOOL FAR PASCAL BoxProcessor(hDlg,message,wParam,lParam)
HWND hDlg;
unsigned message;
WORD wParam;
LONG lParam;

{

//
// What type of message was encountered?
//

    switch (message) {
    
    // 
    // No steps required to initialize dialog.
    // However, it must be indicated that the dialog is initialized.
    //
    
        case WM_INITDIALOG:
             return (TRUE);
        
    //
    // Process command keys germane to the dialog box.
    //
    
        case WM_COMMAND:
    
    //
    // Was the command key "OK" or "Cancel"?
    //
    
        if (wParam == IDOK || wParam ==IDCANCEL) {
    
        //
        // End the dialog and indicate that keys accepted.
        //
        
            EndDialog(hDlg,TRUE);
            return(TRUE);
            
        }
    
    //
    // No processing required for any other function.    
    //

    }
    
    return(FALSE);        

}

//
// Main window function.
//

long FAR PASCAL WndProc (HWND hWnd, unsigned iMessage, WORD wParam, LONG lParam)
{
    static WORD cxClient, cyClient;
    WORD cyChar, xBox, yBox;
    PAINTSTRUCT ps;
    HDC hDC;
    PELEMENT pel;
    char buf [10];
    RECT rect;
    
    // 
    // Clear the "freezing" and "thawing" flags.
    //
    
    fFreezing = FALSE;
    fThawing = FALSE;
    
    switch (iMessage) {
    
            // 
            // Accept the four defined messages.
            //
            
        case WM_COMMAND:
        
            switch (wParam) {
            
               //
               // Did the user request an Exit?
               //
            
               case ID_EXIT:
               
               //
               // Request Windows to nuke the window.
               //
               
               PostQuitMessage(0);
               break;
               
               //
               // Did the user request the element data be placed on the clipboard?
               //
               
               case ID_COPY:
               
               //
               // Get a global block of memory for clipboard use.
               //
               
               hClipboard = GlobalAlloc(GHND,(DWORD)128);
               
               //
               // Convert the handle to a pointer.
               //
               
               lpszClipboard = GlobalLock(hClipboard);
               
               //
               // Store the current line in the clipboard block.
               //
               
               wsprintf(lpszClipboard,"%s\n",(LPSTR)szElement);
               
               //
               // Unlock the clipboard memory block by handle.
               //
               
               GlobalUnlock(hClipboard);
               
               //
               // Transfer the clipboard memory block to the clipboard.
               //
               
               OpenClipboard(hWnd);
               EmptyClipboard();
               SetClipboardData(CF_TEXT,hClipboard);               
               CloseClipboard();
               
               //
               // Clipboard functions complete!
               //               
               
               break; 
               
               //
               // This logic handles the "Help Screen" function.
               //
               
               case ID_HELP:
               
               //
               // Get the famous "instance thunk" pointing to the BoxProcessor.
               //
               
               lpBoxHandler = MakeProcInstance(BoxProcessor,ghInstance);
               
               //
               // Set up the dialog box.
               //
               
               DialogBox(ghInstance,"HelpBox",hWnd,lpBoxHandler);
               
               //
               // Discard the instance thunk.
               //
               
               FreeProcInstance(lpBoxHandler);
               
               //
               // Help functions complete!
               //
               
               break;
               
               //
               // This logic sequence handles "About Element3...".
               //
               
               case ID_ABOUT:    
               
               //
               // Get the famous "instance thunk" pointing to the BoxProcessor.
               //
               
               lpBoxHandler = MakeProcInstance(BoxProcessor,ghInstance);
               
               //
               // Set up the dialog box.
               //
               
               DialogBox(ghInstance,"AboutBox",hWnd,lpBoxHandler);
               
               //
               // Discard the instance thunk.
               //
               
               FreeProcInstance(lpBoxHandler);
               
               //
               // About functions complete!
               //
               
               break;
    
            }
            
            //
            // Command functions complete
            //
            
            break;
    
        case WM_SIZE:

            //
            // When the window changes size, update static variables
            // containing the window size and size of each box.
            //

            cxClient = LOWORD (lParam);
            cyClient = HIWORD (lParam);
            cxBox = (cxClient - 40) / NUM_COLUMNS;
            cyBox = (cyClient - 60) / NUM_ROWS;
            break;
            
        case WM_LBUTTONUP:
        
            // 
            // Handle the case when the user released any mouse button.
            //
            
            //
            // Set flags to indicate freezing or thawing status.
            //
            
            fThawing = fFrozen;
            fFreezing = !fThawing;

        case WM_MOUSEMOVE:

            //
            // Mouse motion has occurred.  Where is that dirty rat?
            //
            
            //
            // Are we not frozen or thawing out?
            //
            
            if (fThawing || !fFrozen) {

                //
                // Set frozen flag if we're in the process of freezing.
                //
               
                fFrozen = fFreezing;
        
                //
                // Convert the mouse location to a corresponding element box.
                //

                xBox = (LOWORD (lParam) - 20) / cxBox;
                yBox = (HIWORD (lParam) - 40) / cyBox;

                //  
                // Scan the element table to determine which box holds the mouse.
                //

                for (i=0; i<MAX_ELEMENTS; i++)
                    if (elTable [i].wColumn == xBox &&
                        elTable [i].wRow == yBox)
                        break;

                //
                // Did the element number or status change?
                //

                if ((i != iElementOld) | fFreezing | fThawing) {

                //
                // Save the old element number.
                //
        
                iElementOld = i;

                //
                // Build a string to indicate which element is selected.
                //

                if (i < MAX_ELEMENTS)
                    wsprintf(szElement,"Element %u - %s(%s) - Atomic Weight %s",i+1,
                       (LPSTR)elTable[i].pszName,
                       (LPSTR)elTable[i].pszSymbol,
                       (LPSTR)elTable[i].pszWeight);
                else
                    lstrcpy(szElement,szHint);
                                
                //
                // Get control of the display context.
                //
            
                hDC = GetDC(hWnd);
                
                //
                // Display the element data.
                //
                
                ElementDisplay(hDC);

                //
                // Release display context for other uses.
                //

                ReleaseDC(hWnd, hDC);
            
               } // Element changed branch exit at this point.
               
            } // Not frozen logic exits at this point.
             
            return 0L;

        case WM_PAINT:

            //
            // Need to redraw the window from scratch.
            //

            hDC = BeginPaint (hWnd, &ps);
            cyChar = HIWORD (GetTextExtent (hDC, "M", 1));

            rect.top = cyChar;
            rect.bottom = cyChar * 2 + 2;

            //
            // Draw the column titles.
            //
            for (i=0; i<NUM_COLUMNS; i++) {
                rect.left = i * cxBox + 20;
                rect.right = rect.left + cxBox;
                DrawText (hDC, pszTitles [i], -1, &rect, DT_CENTER | DT_VCENTER |
                        DT_NOPREFIX | DT_SINGLELINE);
            }

            //
            // Now draw each element.  Each one consists of a rectangle to
            // contain the information, the atomic number in the upper left
            // corner, and the atomic symbol centered.
            //
            for (i=0; i<MAX_ELEMENTS; i++) {
                pel = &elTable [i];
                rect.left = pel->wColumn * cxBox + 20;
                rect.right = rect.left + cxBox;
                rect.top = pel->wRow * cyBox + 40;
                rect.bottom = rect.top + cyBox;
                Rectangle (hDC, rect.left, rect.top, rect.right, rect.bottom);

                wsprintf (buf, "%i", i+1);
                TextOut (hDC, rect.left+1, rect.top+1, buf, lstrlen (buf));

                rect.top += cyChar + 1;

                DrawText (hDC, pel->pszSymbol, -1, &rect, DT_CENTER | DT_VCENTER |
                        DT_NOPREFIX | DT_SINGLELINE);
            }
            
            //
            // Display the element text.
            //
            
            ElementDisplay(hDC);

            EndPaint (hWnd, &ps);

            return 0L;

        case WM_DESTROY:

            //
            // Main window closed.  Quit the program.
            //

            PostQuitMessage (0);
            return 0L;
    }

    return DefWindowProc (hWnd, iMessage, wParam, lParam);
}
