home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Professional / OS2PRO194.ISO / os2 / wps / progs / chaos / sources / pmchaos.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-12-12  |  49.6 KB  |  1,386 lines

  1. /*  PMChaos.   (C) Copyright Matthew Austern, 1993.
  2.  
  3.     Permission granted to use, distribute, and modify, provided that this
  4.     notice remains intact.  If you distribute a modified version, you must
  5.     identify your modifications as such.
  6. */
  7.  
  8. /* 
  9.    This is a Complicated program that does something very simple: it plots 
  10.    the "standard map", defined by   
  11.                         J'     = J + K sin(theta)
  12.                         theta' = theta + J', mod(2 Pi).
  13.    K is a constant; K=1 is the onset of stochasticity.                         
  14.    (This is Lichtenberg and Lieberman, eq. 3.1.20.)
  15.    Program is complicated because it's all interface!  I'm showing off PM.
  16. */
  17.  
  18. #define INCL_DOS
  19. #define INCL_DOSPROCESS
  20. #define INCL_PM
  21. #define INCL_WINHELP
  22.  
  23. #include <os2.h>
  24. #include <math.h>
  25. #include <stdlib.h>
  26. #include <string.h>
  27. #include <ctype.h>
  28. #include <stdio.h>
  29.  
  30. #include "graph.h"        /* Functions that do the actual graphing. */
  31.  
  32. #include "pmchaos_res.h"        /* Included definitions of resources.  */
  33. #include "color.h"              /*  These six files are definitions of */
  34. #include "kfactor.h"            /*  resources for dialog boxes. */
  35. #include "ranges.h"
  36. #include "delay.h"
  37. #include "curpos.h"
  38. #include "about.h"
  39. #include "inline.h"        /* Defines the macro INLINE, used by Map(). */
  40.  
  41. #define Pi    3.141592653589793238
  42. #define TwoPi (2*Pi)
  43.  
  44. struct point {
  45.   double J, theta;
  46. };
  47.  
  48. INLINE struct point Map (struct point *In, double K)
  49. {
  50.   struct point Out = *In;
  51.  
  52.   Out.J     += K * sin(Out.theta);
  53.   Out.theta += Out.J;
  54.  
  55.   if ((Out.theta = fmod(Out.theta, TwoPi)) < 0)
  56.     Out.theta += TwoPi;
  57. /* Uncommenting the next block speeds up the program, but it changes the
  58.    global topology from cylindrical to toroidal.  Essentially, this merges
  59.    the two fixed points into one, so the separatrix in the middle of the
  60.    screen is no longer much of a separatrix.  The actual plots, though,
  61.    still look much the same. */
  62. /*
  63.   if ((Out.J = fmod(Out.J, TwoPi)) < 0)
  64.     Out.J += TwoPi;
  65. */
  66.   return Out;
  67. }
  68.  
  69. struct Bitmap {
  70.   HBITMAP theBitmap;
  71.   HPS MemPS;
  72.   HDC MemDC;
  73.   int isValid;
  74. };
  75.  
  76. static void CreateBitmap (struct Bitmap *B, HWND Win, POINTL *WinSize)
  77. {
  78.   BITMAPINFOHEADER2 BitmapFormat;
  79.   LONG BitmapPlanesBits[2];
  80.   SIZEL PS_Size = {0L, 0L};
  81.   HPS WinPS;
  82.   
  83.   WinPS = WinGetPS(Win);
  84.   GpiQueryDeviceBitmapFormats (WinPS, 2, BitmapPlanesBits);
  85.  
  86.   memset(&BitmapFormat, 0, sizeof(BITMAPINFOHEADER2));
  87.   BitmapFormat.cbFix = sizeof(BITMAPINFOHEADER2);
  88.   BitmapFormat.cx = (ULONG) WinSize->x;
  89.   BitmapFormat.cy = (ULONG) WinSize->y;
  90.   BitmapFormat.cPlanes = (USHORT) BitmapPlanesBits[0];
  91.   BitmapFormat.cBitCount=(USHORT)BitmapPlanesBits[1];
  92.   B->theBitmap = GpiCreateBitmap(WinPS, &BitmapFormat, 0, 0, 0);
  93.   WinReleasePS(WinPS);
  94.  
  95.   B->MemDC = DevOpenDC(WinQueryAnchorBlock(Win),
  96.                        OD_MEMORY, "*", 0, 0, 0);
  97.   B->MemPS = GpiCreatePS(WinQueryAnchorBlock(Win),
  98.                          B->MemDC, &PS_Size,
  99.                          PU_HIMETRIC | GPIF_DEFAULT | GPIT_MICRO | GPIA_ASSOC);
  100.   GpiSetBitmap(B->MemPS, B->theBitmap);
  101.   B->isValid = 1;
  102. }
  103.  
  104. static void DestroyBitmap (struct Bitmap *B)
  105. {
  106.   if(B->isValid) {
  107.     GpiDestroyPS(B->MemPS);
  108.     DevCloseDC(B->MemDC);
  109.     GpiDeleteBitmap(B->theBitmap);
  110.   }
  111.   B->isValid = 0;
  112. }
  113.  
  114. static void ResetBitmap (struct Bitmap *B, HWND Win, POINTL *WinSize)
  115. {
  116.   DestroyBitmap(B);
  117.   CreateBitmap(B, Win, WinSize);
  118.   GpiErase(B->MemPS);
  119. }
  120.  
  121. /* Dialog Procedures.  We have three dialog boxes: setting the K factor,
  122.    setting the range of the graph, and setting the color. */
  123. /* First, the range dialog. */
  124.  
  125. struct GraphRange {        /* This struct is used when sending  */
  126.   struct point Min;        /*  information to and from the */
  127.   struct point Max;        /*  range dialog. */
  128. };
  129.  
  130. /* We're going to subclass the entry field window procedure so that the
  131.    user can't type keys that are meaningless for entering numbers.  
  132.    EntryFieldProc and EntryFieldNum are used for double, IntEntryFieldProc
  133.    and EntryFieldInteger are used for non-negative integers.
  134. */
  135. static PFNWP OldEntryFieldProc;
  136.  
  137. MRESULT EXPENTRY EntryFieldProc (HWND Win, ULONG msg, MPARAM mp1, MPARAM mp2)
  138. {
  139.   if (msg == WM_CHAR) {
  140.     if ((unsigned long)mp1 & KC_VIRTUALKEY) { /* Process virtual keys. */
  141.       unsigned short theKey = SHORT2FROMMP(mp2);
  142.       if (theKey == VK_SPACE)   /* Filter out space: it's meaningless. */
  143.         return 0;
  144.       else if (theKey == VK_NEWLINE || theKey == VK_ENTER) {
  145.                                 /* Notify parent of ENTER or RETURN. */
  146.         WinSendMsg(WinQueryWindow(Win, QW_PARENT), FEF_Enter,
  147.                    MPFROM2SHORT(WinQueryWindowUShort(Win, QWS_ID), 0),
  148.                    0);          /* Our ID is 1st short in mp2. */
  149.         return (MRESULT) 1;
  150.       }
  151.       else                      /* Normal processing for other virtual keys. */
  152.         return (*OldEntryFieldProc)(Win, msg, mp1, mp2);
  153.     }
  154.     else if ((ULONG)mp1 & KC_CHAR && !((ULONG)mp1 & KC_KEYUP)) {
  155.       char theKey;              /* ASCII code of the key. */
  156.       theKey = (char) (SHORT1FROMMP(mp2));
  157.       if (isdigit(theKey) || theKey == 'e' || theKey == 'E' ||
  158.           theKey == '.' || theKey == '+' || theKey == '-')
  159.         return (*OldEntryFieldProc)(Win, msg, mp1, mp2);
  160.       else                      /* Filter out other meaningless characters. */
  161.         return 0;
  162.     }
  163.     else
  164.       return (*OldEntryFieldProc)(Win, msg, mp1, mp2);
  165.   }
  166.   else
  167.     return (*OldEntryFieldProc)(Win, msg, mp1, mp2);
  168. }
  169.  
  170. MRESULT EXPENTRY IntEntryFieldProc(HWND Win, ULONG msg, MPARAM mp1, MPARAM mp2)
  171. {
  172.   if (msg == WM_CHAR) {
  173.     if ((unsigned long)mp1 & KC_VIRTUALKEY) { /* Process virtual keys. */
  174.       unsigned short theKey = SHORT2FROMMP(mp2);
  175.       if (theKey == VK_SPACE)   /* Filter out space: it's meaningless. */
  176.         return 0;
  177.       else if (theKey == VK_NEWLINE || theKey == VK_ENTER) {
  178.                                 /* Notify parent of ENTER or RETURN. */
  179.         WinSendMsg(WinQueryWindow(Win, QW_PARENT), FEF_Enter,
  180.                    MPFROM2SHORT(WinQueryWindowUShort(Win, QWS_ID), 0),
  181.                    0);          /* Our ID is 1st short in mp2. */
  182.         return (MRESULT) 1;
  183.       }
  184.       else                      /* Normal processing for other virtual keys. */
  185.         return (*OldEntryFieldProc)(Win, msg, mp1, mp2);
  186.     }
  187.     else if ((ULONG)mp1 & KC_CHAR && !((ULONG)mp1 & KC_KEYUP)) 
  188.       if (isdigit((char)(SHORT1FROMMP(mp2)))) /* Accept digits. */
  189.         return (*OldEntryFieldProc)(Win, msg, mp1, mp2);
  190.       else                      /* Filter out other meaningless characters. */
  191.         return 0;
  192.     else
  193.       return (*OldEntryFieldProc)(Win, msg, mp1, mp2);
  194.   }
  195.   else
  196.     return (*OldEntryFieldProc)(Win, msg, mp1, mp2);
  197. }
  198.  
  199.  
  200. /* Parses the text in an entry field.  If it is a valid number, then
  201.    returns 1 and sets *val to that number; otherwise, pops up a message
  202.    box to yell at the user and returns 0.
  203.  
  204.    Win is the window handle of the dialog box, and DlgID is the ID of
  205.    the entry field we're looking at.
  206. */
  207. static int EntryFieldNum (HWND Win, unsigned long DlgID,
  208.                           double Min, double Max,
  209.                           double *val)
  210. {
  211.   char Buffer[100], OutBuffer[200];
  212.   double result;
  213.  
  214.   WinQueryDlgItemText(Win, DlgID, 100, Buffer);
  215.   if (sscanf(Buffer, "%lg", &result) != 1) {
  216.     sprintf (OutBuffer, "Invalid input: %s\0", Buffer);
  217.     WinMessageBox(HWND_DESKTOP, Win, OutBuffer,
  218.                   "PM Chaos", 0, 
  219.                   MB_ERROR | MB_OK | MB_MOVEABLE);
  220.     return 0;
  221.   }
  222.   else if (result < Min || result > Max) {
  223.     sprintf (OutBuffer, "Input out of range, must be between %.3f and %.3f.\0",
  224.                         Min, Max);
  225.     WinMessageBox(HWND_DESKTOP, Win, OutBuffer,
  226.                   "PM Chaos", 0,
  227.                   MB_ERROR | MB_OK | MB_MOVEABLE);
  228.     return 0;
  229.   }
  230.   else {
  231.     sprintf (OutBuffer, "%.3f\0", result);
  232.     WinSetDlgItemText (Win, DlgID, OutBuffer);
  233.     *val = result;
  234.     return 1;
  235.   }
  236. }
  237.  
  238. /* Parses the text in an entry field, requiring that it be an integer. */
  239. static int EntryFieldInteger (HWND Win, unsigned long DlgID,
  240.                   int Min, int Max,
  241.                   int *val)
  242. {
  243.   char Buffer[100], OutBuffer[200];
  244.   int result;
  245.  
  246.   WinQueryDlgItemText(Win, DlgID, 100, Buffer);
  247.   if (sscanf(Buffer, "%d", &result) != 1) {
  248.     sprintf (OutBuffer, "Invalid input: %s\0", Buffer);
  249.     WinMessageBox(HWND_DESKTOP, Win, OutBuffer,
  250.                   "PM Chaos", 0, 
  251.                   MB_ERROR | MB_OK | MB_MOVEABLE);
  252.     return 0;
  253.   }
  254.   else if (result < Min || result > Max) {
  255.     sprintf (OutBuffer, "Input out of range, must be between %d and %d.\0",
  256.                         Min, Max);
  257.     WinMessageBox(HWND_DESKTOP, Win, OutBuffer,
  258.                   "PM Chaos", 0,
  259.                   MB_ERROR | MB_OK | MB_MOVEABLE);
  260.     return 0;
  261.   }
  262.   else {
  263.     sprintf (OutBuffer, "%d\0", result);
  264.     WinSetDlgItemText (Win, DlgID, OutBuffer);
  265.     *val = result;
  266.     return 1;
  267.   }
  268. }
  269.  
  270. /* Dialog procedure for setting the range of the graph.  Note that the 
  271.    range can't extend past 0 or 2 Pi for either of the axes.
  272. */
  273.  
  274. MRESULT EXPENTRY RangeDialog (HWND DlgWin, ULONG Msg, MPARAM mp1, MPARAM mp2)
  275. {
  276.   static struct GraphRange NewRange, *OldRange;
  277.   char Buffer[100];
  278.  
  279.   switch(Msg) {
  280.   case WM_INITDLG:              /* Initialize the dialog. */
  281.     OldRange = (struct GraphRange *) PVOIDFROMMP(mp2);
  282.     NewRange = *OldRange;
  283.  
  284.                                 /* Don't let user type non-numeric values. */
  285.     OldEntryFieldProc = WinSubclassWindow(WinWindowFromID(DlgWin, DID_JMin),
  286.                                           EntryFieldProc);
  287.     WinSubclassWindow(WinWindowFromID(DlgWin, DID_JMax), EntryFieldProc);
  288.     WinSubclassWindow(WinWindowFromID(DlgWin, DID_ThetaMin), EntryFieldProc);
  289.     WinSubclassWindow(WinWindowFromID(DlgWin, DID_ThetaMax), EntryFieldProc);
  290.                                 /* Display text in entry fields. */
  291.     sprintf(Buffer, "%.3f\0", NewRange.Min.J);
  292.     WinSetDlgItemText(DlgWin, DID_JMin, Buffer);
  293.     sprintf(Buffer, "%.3f\0", NewRange.Max.J);
  294.     WinSetDlgItemText(DlgWin, DID_JMax, Buffer);
  295.     sprintf(Buffer, "%.3f\0", NewRange.Min.theta);
  296.     WinSetDlgItemText(DlgWin, DID_ThetaMin, Buffer);
  297.     sprintf(Buffer, "%.3f\0", NewRange.Max.theta);
  298.     WinSetDlgItemText(DlgWin, DID_ThetaMax, Buffer);  
  299.                                 /* Set focus in an entry field. */
  300.     WinSetFocus(HWND_DESKTOP, WinWindowFromID(DlgWin, DID_JMin));
  301.     return (MRESULT) 1;
  302.   case WM_COMMAND:              /* Process user request. */
  303.     switch(SHORT1FROMMP(mp1)) {
  304.     case DID_Range_Cancel:
  305.       WinDismissDlg(DlgWin, FALSE);
  306.       return (MRESULT) 1;
  307.     case DID_Range_OK:
  308.       if (EntryFieldNum(DlgWin, DID_JMin, 0, TwoPi, &NewRange.Min.J) &&
  309.           EntryFieldNum(DlgWin, DID_JMax, 0, TwoPi, &NewRange.Max.J) &&
  310.           EntryFieldNum(DlgWin, DID_ThetaMin, 0, TwoPi, &NewRange.Min.theta) &&
  311.           EntryFieldNum(DlgWin, DID_ThetaMax, 0, TwoPi, &NewRange.Max.theta))
  312.         if (NewRange.Min.J < NewRange.Max.J &&
  313.             NewRange.Min.theta < NewRange.Max.theta) {
  314.           *OldRange = NewRange; /* Return results to caller. */
  315.           WinDismissDlg(DlgWin, TRUE);
  316.           return (MRESULT) 1;
  317.         }
  318.         else {
  319.           WinMessageBox(HWND_DESKTOP, DlgWin,
  320.                         "Error: Maximum value must be larger than minimum.",
  321.                         "PM Chaos: range", 0,
  322.                         MB_ERROR | MB_OK | MB_MOVEABLE);
  323.           return (MRESULT) 1;
  324.         }
  325.       else
  326.         return (MRESULT) 1;     /* Result was invalid. */
  327.     case DID_Range_Help:
  328.       return (MRESULT) 1;
  329.     default:
  330.     return WinDefDlgProc(DlgWin, Msg, mp1, mp2);
  331.     }
  332.   case FEF_Enter: {             /* User hit return in an entry field. */
  333.     unsigned short TheEntryField; /* Which entry field is it? */
  334.     double dummy;               /* We're just validating the result; we */
  335.                                 /*  don't care what it is. */
  336.     TheEntryField = SHORT1FROMMP(mp1);
  337.     EntryFieldNum(DlgWin, TheEntryField, 0, TwoPi, &dummy);
  338.     return (MRESULT) 1;
  339.   }
  340.   default:
  341.     return WinDefDlgProc(DlgWin, Msg, mp1, mp2);
  342.   }
  343. }
  344.  
  345.  
  346. /* Dialog procedure for setting the K constant in the standard map.  We're
  347.    using a slider that runs from 0 to 3.  There are 301 ticks in the 
  348.    slider, so the user gets to specify two digits after the decimal point.
  349. */
  350.  
  351. MRESULT EXPENTRY KFactorDlg (HWND DlgWin, ULONG Msg, MPARAM mp1, MPARAM mp2)
  352. {
  353.   static double *oldK, newK;
  354.   static short Tick;            /* Which tick is the slider at? */
  355.   char Buffer[100];
  356.   unsigned short i;
  357.  
  358.   switch(Msg) {
  359.   case WM_INITDLG:
  360.     oldK = (double *) PVOIDFROMMP(mp2);
  361.     newK = *oldK;
  362.     Tick = (int) floor(newK*100 + 0.5);
  363.  
  364.     sprintf(Buffer, "K = %.2f\0", newK); /* Set up the text. */
  365.     WinSetDlgItemText(DlgWin, DID_K_Text, Buffer);
  366.                                 /* Initialize the slider.  Label 0,1,2,3,
  367.                                 /*  and put ticks at 0.1,0.2,... */
  368.     for (i=0; i<=300; i += 10)
  369.       if (!(i%100))
  370.         WinSendDlgItemMsg(DlgWin, DID_K_Slider, SLM_SETTICKSIZE,
  371.                           MPFROM2SHORT(i,11), 0);
  372.       else if (!(i%50))
  373.         WinSendDlgItemMsg(DlgWin, DID_K_Slider, SLM_SETTICKSIZE,
  374.                           MPFROM2SHORT(i,7), 0);
  375.       else
  376.         WinSendDlgItemMsg(DlgWin, DID_K_Slider, SLM_SETTICKSIZE,
  377.                           MPFROM2SHORT(i,4), 0);
  378.  
  379.     WinSendDlgItemMsg(DlgWin, DID_K_Slider, SLM_SETSCALETEXT,
  380.                       MPFROMSHORT(0), "0.0");
  381.     WinSendDlgItemMsg(DlgWin, DID_K_Slider, SLM_SETSCALETEXT,
  382.                       MPFROMSHORT(100), "1.0");
  383.     WinSendDlgItemMsg(DlgWin, DID_K_Slider, SLM_SETSCALETEXT,
  384.                       MPFROMSHORT(200), "2.0");
  385.     WinSendDlgItemMsg(DlgWin, DID_K_Slider, SLM_SETSCALETEXT,
  386.                       MPFROMSHORT(300), "3.0");
  387.  
  388.                                 /* Set initial slider position. */
  389.     WinSendDlgItemMsg(DlgWin, DID_K_Slider, SLM_SETSLIDERINFO,
  390.                       MPFROM2SHORT(SMA_SLIDERARMPOSITION,SMA_INCREMENTVALUE),
  391.                       MPFROMSHORT(Tick));
  392.  
  393.     WinSetFocus (HWND_DESKTOP, WinWindowFromID(DlgWin, DID_K_Slider));
  394.     return (MRESULT) 1;
  395.   case WM_CONTROL:              /* Get control message from slider. */
  396.     if (SHORT1FROMMP(mp1)==DID_K_Slider && SHORT2FROMMP(mp1)== SLN_CHANGE) { 
  397.                                 /* Find what tick the slider is at. */
  398.         Tick = (short)WinSendDlgItemMsg(DlgWin, DID_K_Slider,
  399.                                         SLM_QUERYSLIDERINFO,
  400.                                         MPFROM2SHORT(SMA_SLIDERARMPOSITION,
  401.                                                      SMA_INCREMENTVALUE),
  402.                                         0);
  403.         newK = Tick/100.;               /* Convert tick number to K value. */
  404.         sprintf(Buffer, "K = %.2f\0", newK); /* Copy it into the text. */
  405.         WinSetDlgItemText(DlgWin, DID_K_Text, Buffer);
  406.       return (MRESULT) 1;
  407.     }
  408.     else
  409.       return (MRESULT) 0;
  410.   case WM_COMMAND:
  411.     switch(SHORT1FROMMP(mp1)) {
  412.     case DID_K_Cancel:
  413.       WinDismissDlg(DlgWin, FALSE);
  414.       return (MRESULT) 1;
  415.     case DID_K_OK:
  416.       *oldK = newK = Tick/100.;
  417.       WinDismissDlg(DlgWin, TRUE);
  418.       return (MRESULT) 1;
  419.     case DID_K_Help:
  420.       return 0;
  421.     default:
  422.       return WinDefDlgProc(DlgWin, Msg, mp1, mp2);
  423.     }
  424.   default:
  425.     return WinDefDlgProc(DlgWin, Msg, mp1, mp2);
  426.   }
  427. }
  428.  
  429. /* Dialog to set the color.  mp2 is a pointer to a long containing 
  430.    the color---i.e., CLR_BLACK, etc.
  431. */
  432.  
  433. MRESULT EXPENTRY ColorDlg (HWND DlgWin, ULONG Msg, MPARAM mp1, MPARAM mp2)
  434. {
  435.   static long ColorNames[] = 
  436.     { CLR_WHITE,    CLR_BLACK,     CLR_BLUE,     CLR_RED,
  437.       CLR_PINK,     CLR_GREEN,     CLR_CYAN,     CLR_YELLOW,
  438.       CLR_PALEGRAY, CLR_DARKGRAY,  CLR_DARKBLUE, CLR_DARKRED,
  439.       CLR_DARKPINK, CLR_DARKGREEN, CLR_DARKCYAN, CLR_BROWN};
  440.   static unsigned long ColorIDs[] =  
  441.     { DID_CLR_WHITE,    DID_CLR_BLACK,     DID_CLR_BLUE,     DID_CLR_RED,
  442.       DID_CLR_PINK,     DID_CLR_GREEN,     DID_CLR_CYAN,     DID_CLR_YELLOW,
  443.       DID_CLR_PALEGRAY, DID_CLR_DARKGRAY,  DID_CLR_DARKBLUE, DID_CLR_DARKRED,
  444.       DID_CLR_DARKPINK, DID_CLR_DARKGREEN, DID_CLR_DARKCYAN, DID_CLR_BROWN};
  445.   int NumColors = 16;
  446.   static long *oldColorName;
  447.   static unsigned long newColorID;
  448.   int i;
  449.  
  450.   switch(Msg) {
  451.   case WM_INITDLG: {
  452.     oldColorName = (long *) mp2; /* Get old color. */
  453.     newColorID = DID_CLR_WHITE; /* Translate it into ID of a radio button. */
  454.     for (i=0; i<NumColors; i++) 
  455.       if (*oldColorName == ColorNames[i])
  456.         newColorID = ColorIDs[i];
  457.                                 /* Check the appropriate radio button, and */
  458.                                 /*  set input focus to it. */
  459.     WinSendDlgItemMsg(DlgWin, newColorID, BM_SETCHECK, (void *) 1, (void *) 0);
  460.     WinSetFocus(HWND_DESKTOP, WinWindowFromID(DlgWin, newColorID));
  461.     for (i=0; i<NumColors; i++)    /* Set button colors correctly. */
  462.       WinSetPresParam(WinWindowFromID(DlgWin, ColorIDs[i]),
  463.               PP_BACKGROUNDCOLORINDEX,
  464.               sizeof(long),
  465.               &(ColorNames[i]));
  466.             
  467.     return (MRESULT) 1;
  468.   }
  469.  
  470.   case WM_COMMAND:
  471.     switch(SHORT1FROMMP(mp1)) {
  472.     case DID_Color_OK:          /* Find which button is checked. */
  473.       i = (int) WinSendDlgItemMsg(DlgWin,
  474.                                   DID_CLR_WHITE, BM_QUERYCHECKINDEX, 0, 0);
  475.       *oldColorName = ColorNames[i];
  476.       WinDismissDlg(DlgWin, TRUE);
  477.       return (MRESULT) TRUE;
  478.     case DID_Color_Cancel:
  479.       WinDismissDlg(DlgWin, FALSE);
  480.       return (MRESULT) TRUE;
  481.     case DID_Color_Help:
  482.       return 0;
  483.     default:
  484.       return WinDefDlgProc(DlgWin, Msg, mp1, mp2);
  485.     }
  486.   default:
  487.     return WinDefDlgProc(DlgWin, Msg, mp1, mp2);
  488.   }
  489. }
  490.  
  491. /* Trivial dialog to put up the About box. */
  492. MRESULT EXPENTRY AboutDlg (HWND DlgWin, ULONG Msg, MPARAM mp1, MPARAM mp2)
  493. {
  494.   switch(Msg) {
  495.   case WM_INITDLG:
  496.     WinSetFocus(HWND_DESKTOP, WinWindowFromID(DlgWin, DID_About_OK));
  497.     return (MRESULT) TRUE;
  498.   case WM_COMMAND:
  499.     switch(SHORT1FROMMP(mp1)) {
  500.     case DID_About_OK:
  501.       WinDismissDlg(DlgWin, 1);
  502.       return (MRESULT) TRUE;
  503.     default:
  504.       return WinDefDlgProc(DlgWin, Msg, mp1, mp2);
  505.     }
  506.   default:
  507.     return WinDefDlgProc(DlgWin, Msg, mp1, mp2);
  508.   }
  509. }
  510.  
  511. /* This next function is also a dialog procedure, but it's rather a
  512.    one-way dialog, and rather a vacuous dialog procedure: it's used to
  513.    show the user the current point.  This is intended to be used as a
  514.    non-modal dialog. 
  515. */
  516. MRESULT EXPENTRY DisplayPoint (HWND Win, ULONG Msg, MPARAM mp1, MPARAM mp2)
  517. {
  518.   switch(Msg) {
  519.   case WM_INITDLG:
  520.     WinSetFocus(HWND_DESKTOP, Win);
  521.     return (MRESULT) 1;
  522.  
  523.   case WM_UpdatePoint: {
  524.     struct point *thePoint = (struct point *) mp1;
  525.     char Buffer[100];
  526.  
  527.     sprintf(Buffer, "%.4f", thePoint->J);
  528.     WinSetDlgItemText(Win, DID_JCoord, Buffer);
  529.     sprintf(Buffer, "%.4f", thePoint->theta);
  530.     WinSetDlgItemText(Win, DID_ThetaCoord, Buffer);
  531.  
  532.     return (MRESULT) 1;
  533.   }
  534.     
  535.   default:
  536.     return WinDefDlgProc(Win, Msg, mp1, mp2);
  537.   }
  538. }
  539.  
  540.  
  541. /* Finally, a dialog procedure to select just how slow the "slow motion"
  542.    actually is.  The user types in how long we'll wait on each point,
  543.    in units of 1/10 sec.
  544.  
  545.    We'll subclass the entry field control, just as in RangeDialog.
  546. */
  547. MRESULT EXPENTRY DelayDlgProc (HWND Win, ULONG Msg, MPARAM mp1, MPARAM mp2)
  548. {
  549.   char Buffer[100];
  550.   static unsigned long *Delay; /* Delay, in units of ms. */
  551.   
  552.   switch(Msg) {
  553.   case WM_INITDLG:        /* Initialize the dialog. */
  554.     Delay = (unsigned long *) mp2;
  555.  
  556.     OldEntryFieldProc = WinSubclassWindow(WinWindowFromID(Win, DID_DelayEntry),
  557.                       IntEntryFieldProc);
  558.     sprintf(Buffer, "%d\0", (int)(*Delay/100.));
  559.     WinSetDlgItemText(Win, DID_DelayEntry, Buffer);
  560.  
  561.     WinSetFocus(HWND_DESKTOP, WinWindowFromID(Win, DID_DelayEntry));
  562.  
  563.     return (MRESULT) 1;
  564.  
  565.   case WM_COMMAND:
  566.     switch(SHORT1FROMMP(mp1)) {
  567.     case DID_Delay_Cancel:
  568.       WinDismissDlg(Win, FALSE);
  569.       return (MRESULT) 1;
  570.  
  571.     case DID_Delay_OK: {
  572.       int NumTenths;
  573.       if (EntryFieldInteger(Win, DID_DelayEntry, 0, 32767, &NumTenths)) {
  574.     *Delay = (unsigned long) (NumTenths * 100);
  575.     *Delay = *Delay > 0 ? *Delay : 1;
  576.     WinDismissDlg(Win, TRUE);
  577.       }
  578.       return (MRESULT) 1;
  579.       
  580.     case DID_Delay_Help:
  581.       return (MRESULT) 1;
  582.     }
  583.     default:
  584.       return WinDefDlgProc(Win, Msg, mp1, mp2);
  585.     }
  586.       
  587.   case FEF_Enter: {             /* User hit return in an entry field. */
  588.     unsigned short TheEntryField; /* Which entry field is it? */
  589.     int dummy;            /* We're just validating the result; we */
  590.                                 /*  don't care what it is. */
  591.     TheEntryField = SHORT1FROMMP(mp1);
  592.     EntryFieldInteger(Win, TheEntryField, 0, 32767, &dummy);
  593.     return (MRESULT) 1;
  594.   }
  595.   default:
  596.     return WinDefDlgProc(Win, Msg, mp1, mp2);
  597.   }    
  598. }
  599.  
  600. /***********************************************************************
  601.  * Now the main part of the program.  We're doing this multi-threaded: *
  602.  * when the user selects START, the computation goes along in another  *
  603.  * thread.  This thread draws points on both the window and on the     *
  604.  * shadow bitmap.  Redraws of the window are done in the main thread,  *
  605.  * but this is never anything more than a copy of the shadow bitmap to *
  606.  * the window.                                                         *
  607.  ***********************************************************************/
  608.  
  609. struct InterThread {
  610.   int isRunning;        /* Are we running?                          */
  611.   int isSlowMotion;        /* Should we pause after each point?        */
  612.   HWND PosWin;            /* Display of coordinates of current point. */
  613.   unsigned long theDelay;    /* If so, for how long?                     */
  614.   int Terminate;        /* Flag: set to 1 when we kill thread 2.    */
  615.   struct point *CurrentPoint;    /* Point where we'll start mapping.         */
  616.   HWND theWindow;        /* Handle of main program window.           */
  617.   struct Bitmap *theBitmap;    /* The shadow bitmap.                       */
  618.   struct GraphInfo *G;        /* Graph range, etc.                        */
  619.   double K;            /* Stochasticity parameter.                 */
  620.   long theColor;        /* Color we're using for the map.           */
  621.   HEV Thread1ToThread2;        /* Semaphores used to synchronize the       */
  622.   HEV Thread2ToThread1;        /*   threads.                               */
  623. };
  624.  
  625.                                 /* Do appropriate semaphore wizardry */
  626.                                 /*  to start mapping in thread 2. */
  627. void StartMapping (struct InterThread *Control)
  628. {
  629.   unsigned long PostCount;
  630.  
  631.   if (!Control->isRunning) {
  632.     if (Control->isSlowMotion)  /* We'll want a position display. */
  633.       WinSetWindowPos(Control->PosWin, HWND_TOP,
  634.               0, 0, 0, 0,
  635.               SWP_SHOW | SWP_ACTIVATE | SWP_ZORDER);
  636.  
  637.     DosPostEventSem(Control->Thread1ToThread2); /* Tell thread 2 to start. */
  638.     DosWaitEventSem(Control->Thread2ToThread1, (ULONG) SEM_INDEFINITE_WAIT);
  639.                                 /* Wait until it starts. */
  640.     DosEnterCritSec();
  641.     Control->isRunning = 1;
  642.     DosResetEventSem(Control->Thread2ToThread1, &PostCount);
  643.     DosExitCritSec();
  644.   }
  645. }
  646.  
  647. void StopMapping (struct InterThread *Control)
  648. {
  649.   unsigned long PostCount;
  650.  
  651.   if (Control->isRunning) {
  652.     DosPostEventSem(Control->Thread1ToThread2); /* Tell thread 2 to stop. */
  653.     DosWaitEventSem(Control->Thread2ToThread1, (ULONG) SEM_INDEFINITE_WAIT); 
  654.                                 /* Wait until it stops. */
  655.     if (Control->isSlowMotion)    /* Get rid of the position display. */
  656.       WinSetWindowPos(Control->PosWin, 0, 0, 0, 0, 0, SWP_HIDE);
  657.  
  658.     DosEnterCritSec();
  659.     DosResetEventSem(Control->Thread2ToThread1, &PostCount);
  660.     Control->isRunning = 0;
  661.     DosExitCritSec();
  662.   }
  663. }
  664.  
  665. void KillMappingThread (struct InterThread *Control)
  666. {
  667.   if (Control->isRunning)
  668.     StopMapping(Control);
  669.  
  670.   Control->Terminate = 1;
  671.   StartMapping(Control);
  672. }
  673.  
  674. /* Most of this function is completely straightforward: wait until thread 1
  675.    tells us to start, then go into the inner mapping loop until thread 1 tells
  676.    us to stop.  The only slightly tricky aspects: (1) We draw each point
  677.    both on the screen and on the shadow bitmap.  (2) We actually have two
  678.    different inner loops, depending on whether we're doing this normally
  679.    or in slow motion.
  680.  
  681.    There are two reasons slow motion is a bit tricky.  First: we have to
  682.    pause between points, but we also have to be ready to quit the loop
  683.    instantly, once thread 1 tells us to stop---we can't tie up the message
  684.    queue for several seconds!  Second: in slow motion, we don't just set a
  685.    single pel.  We want to make it obvious which point is the current point.
  686.    So we have to draw a prominent marker, and then erase it when we move on
  687.    to the next point.
  688. */
  689.  
  690. /* This small helper function is just like GpiSetMarker, really.  I'm
  691.    just defining my own version because I can't get GpiSetMarker to
  692.    work with a mix mode of FM_INVERT. 
  693. */
  694. void DrawMark(HPS PS, POINTL *Pos)
  695. {
  696.   static ARCPARAMS MMCircle = {100, 100, 0, 0};    /* Circle with 1mm radius. */
  697.   GpiSetArcParams(PS, &MMCircle);
  698.  
  699.   GpiMove(PS, Pos);        /* Draw two concentric circles. */
  700.   GpiFullArc(PS, DRO_OUTLINE, MAKEFIXED(2, 0));
  701.   GpiFullArc(PS, DRO_OUTLINE, MAKEFIXED(0, 32768));
  702. }  
  703.  
  704. void MappingThread (void *ControlArgument)
  705. {
  706.   struct InterThread *ThreadControl;
  707.   struct point thePoint;
  708.   POINTL thePel;
  709.   static SIZEL dummy = {0L, 0L};
  710.   unsigned long PostCount;
  711.   HPS WinPS, MemPS;
  712.  
  713.   ThreadControl = (struct InterThread *) ControlArgument;
  714.  
  715.   while(1) {
  716.                                 /* Wait for main thread to post semaphore. */
  717.     DosWaitEventSem(ThreadControl->Thread1ToThread2,
  718.             (ULONG) SEM_INDEFINITE_WAIT);
  719.     DosEnterCritSec();          /* Reset the semaphore. */
  720.     DosResetEventSem(ThreadControl->Thread1ToThread2, &PostCount);      
  721.     DosPostEventSem(ThreadControl->Thread2ToThread1);
  722.                                 /* Signal thread 1 that we're running. */
  723.     DosExitCritSec();
  724.  
  725.     if (ThreadControl->Terminate) { /* See if Thread 1 wants us to stop. */
  726.       DosPostEventSem(ThreadControl->Thread2ToThread1); 
  727.       return;
  728.     }                           /* Now we begin the main mapping loop. 
  729.                                    Continue until the semaphore is posted
  730.                                    again. */
  731.     thePoint = *(ThreadControl->CurrentPoint);
  732.     WinPS    = WinGetPS(ThreadControl->theWindow); /* Get pres. space. */
  733.     MemPS = ThreadControl->theBitmap->MemPS; /* PS for shadow bitmap. */
  734.  
  735.     GpiSetPS (WinPS, &dummy, PU_HIMETRIC); /* Set units of PS measurement. */
  736.     GpiSetPS (MemPS, &dummy, PU_HIMETRIC); 
  737.     ClipToGraph(WinPS, ThreadControl->G); /* Don't draw outside graph. */
  738.     ClipToGraph(MemPS, ThreadControl->G); 
  739.     GpiSetColor (WinPS, ThreadControl->theColor); 
  740.     GpiSetColor (MemPS, ThreadControl->theColor); 
  741.  
  742.     if (ThreadControl->isSlowMotion) { /* Do the mapping step by step. */
  743.       GpiSetMix(WinPS, FM_INVERT);
  744.       GpiSetMix(MemPS, FM_INVERT);
  745.       thePel = toPoint(thePoint.J, thePoint.theta, ThreadControl->G);
  746.  
  747.       DrawMark(WinPS, &thePel); /* Draw the initial point. */
  748.       DrawMark(MemPS, &thePel);
  749.       WinPostMsg(ThreadControl->PosWin, WM_UpdatePoint,
  750.          &thePoint, 0);
  751.  
  752.                 /* Begin the mapping loop. */
  753.       while (DosWaitEventSem(ThreadControl->Thread1ToThread2,
  754.                  ThreadControl->theDelay) != 0) {
  755.     DrawMark(WinPS, &thePel); /* Erase old point.  Note that we're  */
  756.     DrawMark(MemPS, &thePel); /*  using mix mode of FM_INVERT. */
  757.  
  758.     GpiSetMix(WinPS, FM_OVERPAINT);
  759.     GpiSetMix(MemPS, FM_OVERPAINT);
  760.     GpiSetPel(WinPS, &thePel); /* Draw old point now, but just as a */
  761.     GpiSetPel(MemPS, &thePel); /*  single pel. */
  762.  
  763.     thePoint = Map(&thePoint, ThreadControl->K);
  764.     thePel = toPoint(thePoint.J, thePoint.theta, ThreadControl->G);
  765.     GpiSetMix(WinPS, FM_INVERT);
  766.     GpiSetMix(MemPS, FM_INVERT);
  767.  
  768.     DrawMark(WinPS, &thePel); /* Draw the new point. */
  769.     DrawMark(MemPS, &thePel);
  770.     WinPostMsg(ThreadControl->PosWin, WM_UpdatePoint,
  771.            &thePoint, 0);
  772.       }
  773.       DrawMark(WinPS, &thePel); /* Erase the mark now that we're done */
  774.       DrawMark(MemPS, &thePel); /*  mapping. */
  775.     }
  776.     else            /* Run at full speed. */
  777.       while (DosQueryEventSem(ThreadControl->Thread1ToThread2, &PostCount),
  778.          PostCount == 0) {    /* This is the inner mapping loop. */
  779.     thePel = toPoint(thePoint.J, thePoint.theta, ThreadControl->G);
  780.     GpiSetPel(WinPS, &thePel);
  781.     GpiSetPel(MemPS, &thePel);
  782.     thePoint = Map(&thePoint, ThreadControl->K);
  783.       }
  784.  
  785.     DosEnterCritSec();          /* Semaphore was posted; stop now. */
  786.     DosResetEventSem(ThreadControl->Thread1ToThread2, &PostCount);      
  787.     UndoClipping(MemPS);
  788.     UndoClipping(WinPS);
  789.     WinReleasePS(WinPS);
  790.     *(ThreadControl->CurrentPoint) = thePoint; /* Pass back new value. */
  791.                                 /* Signal that we're done. */
  792.     DosPostEventSem(ThreadControl->Thread2ToThread1);
  793.     DosExitCritSec();
  794.   }
  795. }
  796.  
  797.                                 /* Disable a menu item. */
  798. static void MenuDisable (HWND Win, USHORT theItem)
  799. {
  800.   HWND theMenu;
  801.   theMenu = WinWindowFromID(WinQueryWindow(Win, QW_PARENT), FID_MENU);
  802.  
  803.   WinSendMsg(theMenu, MM_SETITEMATTR,
  804.              MPFROM2SHORT(theItem, 1),
  805.              MPFROM2SHORT(MIA_DISABLED, MIA_DISABLED));
  806. }
  807.  
  808.                                 /* Enable a menu item. */
  809. static void MenuEnable (HWND Win, USHORT theItem)
  810. {
  811.   HWND theMenu;
  812.   theMenu = WinWindowFromID(WinQueryWindow(Win, QW_PARENT), FID_MENU);
  813.  
  814.   WinSendMsg(theMenu, MM_SETITEMATTR,
  815.              MPFROM2SHORT(theItem, 1),
  816.              MPFROM2SHORT(MIA_DISABLED, 0));
  817. }
  818.  
  819. /* Do everything necessary to clear the screen and redraw the axes on it. */
  820. static void ClearScreen (struct Bitmap *theBitmap, HWND Win, POINTL *WinSize,
  821.                          struct GraphRange *theRange, struct GraphInfo *G)
  822. {
  823.   SIZEL dummy = {0L, 0L};
  824.   POINTL GpiSize;               /* Window size in GPI units. */
  825.   
  826.   ResetBitmap(theBitmap, Win, WinSize); /* Clear the shadow bitmap. */
  827.   GpiSetPS(theBitmap->MemPS, &dummy, PU_HIMETRIC);
  828.  
  829.   GpiSize = *WinSize;        /* Get window size in Gpi coordinates. */
  830.   GpiConvert(theBitmap->MemPS, CVTC_DEVICE, CVTC_PAGE, 1L, &GpiSize); 
  831.     
  832.   SizeAxes(theBitmap->MemPS,  /* Decide on sizing for graph axes. */
  833.            GpiSize.x, GpiSize.y,
  834.            theRange->Min.J, theRange->Max.J,
  835.            theRange->Min.theta, theRange->Max.theta,
  836.        "J", "Theta",
  837.            G);
  838.  
  839.   WinInvalidateRegion(Win, 0, 0); /* Get shadow copied to the screen. */
  840.  
  841.  
  842. /* given two opposite corners of a rectangle, draw the rectangle.  We draw
  843.      it using FM_INVERT, so that we can erase it with a second call to the
  844.      same function. 
  845.    This function assumes that PS has been set to the correct units and that
  846.      it has already been clipped appropriately.  This is for the sake
  847.      of efficiency: we'll typically want to call it twice in a row (erase the
  848.      old rectangle and draw a new one), so why do the clipping and such twice?
  849. */
  850.  
  851. void DrawRect (HPS PS, POINTL *Corner1, POINTL *Corner2)
  852. {
  853.   GpiSetMix (PS, FM_INVERT);
  854.   GpiMove(PS, Corner1);
  855.   GpiBox(PS, DRO_OUTLINE, Corner2, 0, 0); 
  856. }
  857.  
  858. double fmin (double x, double y)
  859. {
  860.   if (x < y)
  861.     return x;
  862.   else
  863.     return y;
  864. }
  865.  
  866. double fmax (double x, double y)
  867. {
  868.   if (x > y)
  869.     return x;
  870.   else
  871.     return y;
  872. }
  873.  
  874. double InRange (double x, double min, double max)
  875. {
  876.   return fmin(max, fmax(x, min));
  877. }
  878.  
  879. void DestroyHelp (HWND aHelpWindow)
  880. {
  881.   if (aHelpWindow)
  882.     WinDestroyHelpInstance(aHelpWindow);
  883. }
  884.  
  885.  
  886. /* Creates a "help instance" for PM Chaos. */
  887. HWND InitHelp(HWND aWindow)
  888. {
  889.   HELPINIT HelpData = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  890.   HWND theHelpWindow, aFrame;
  891.   HAB theAnchorBlock;
  892.  
  893.   HelpData.cb                 =  sizeof(HELPINIT);
  894.   HelpData.phtHelpTable       = (HELPTABLE*)MAKELONG(ID_MainHelpTable, 0xFFFF);
  895.   HelpData.pszHelpWindowTitle = "PM Chaos Help";
  896.   HelpData.pszHelpLibraryName = "pmchaos.hlp";
  897.  
  898.   theAnchorBlock = WinQueryAnchorBlock(aWindow);
  899.   aFrame = WinQueryWindow(aWindow, QW_PARENT);
  900.  
  901.   theHelpWindow = WinCreateHelpInstance(theAnchorBlock, &HelpData);
  902.   if (!theHelpWindow || HelpData.ulReturnCode ||
  903.       !WinAssociateHelpInstance(theHelpWindow, aFrame)) {
  904.     WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
  905.           "Warning: On-line help is unavailable",
  906.           "PM Chaos", 0,
  907.           MB_OK | MB_WARNING);
  908.     DestroyHelp(theHelpWindow);
  909.     theHelpWindow = 0;
  910.   }
  911.   
  912.   return
  913.     theHelpWindow;
  914. }
  915.     
  916.  
  917. MRESULT EXPENTRY ChaosWindowProc (HWND Win, ULONG Msg, MPARAM mp1, MPARAM mp2)
  918. {
  919.   static struct InterThread theControlParams;
  920.   static HWND theHelpWin;    /* Window that handles the help system. */
  921.   static struct GraphRange theRange;    /* Min and max values of graph. */
  922.   static struct GraphInfo G;    /* Sizing information about the axes. */
  923.   static struct Bitmap ShadowBitmap;
  924.   static struct point StartingPoint;
  925.   static POINTL WinSize;        /* Size of window, in device coordinates. */
  926.  
  927.   static int ButtonDown;        /* Is user holding right mouse button down? */
  928.   static int RectExists;        /* Has user finished drawing a rectangle? */
  929.   static HPOINTER RectPointer;  /* Mouse pointer used for drawing a rect. */
  930.   static POINTL Corner1, Corner2; /* Corner1 is where user first depressed */
  931.                                   /*  button 2, Corner2 is current position. */
  932.   SIZEL dummy = {0L, 0L};    /* Used in calls to GpiSetPS. */
  933.     
  934.   switch(Msg) {
  935.   case WM_CREATE: {
  936.     static unsigned long PostCount;
  937.     
  938.     ShadowBitmap.isValid = 0;   /* We haven't created it yet.  We can't
  939.                                    /*  do so until we receive WM_SIZE. */
  940.     ButtonDown = RectExists = 0;
  941.     RectPointer = WinLoadPointer(HWND_DESKTOP, 0, ID_RectPtr);
  942.  
  943.     theRange.Min.J = theRange.Min.theta = 0; /* Default range of graph. */
  944.     theRange.Max.J = theRange.Max.theta = TwoPi; 
  945.  
  946.     theControlParams.K = 0.85;  /* Set default values. */
  947.     theControlParams.theColor = CLR_BLUE;
  948.  
  949.     theControlParams.isRunning = 0;
  950.     theControlParams.Terminate = 0;
  951.     theControlParams.theWindow = Win;
  952.     theControlParams.theBitmap = &ShadowBitmap;
  953.     theControlParams.G = &G;
  954.     theControlParams.CurrentPoint = &StartingPoint;
  955.  
  956.     theControlParams.isSlowMotion = 0;
  957.     theControlParams.theDelay     = 500; /* 1/2 second. */
  958.     theControlParams.PosWin = 
  959.       WinLoadDlg(HWND_DESKTOP, HWND_DESKTOP,
  960.          DisplayPoint, 0,
  961.          DID_CurPosition, 0); /* This window is created invisible. */
  962.  
  963.                                 /* Create Thread 2. */
  964.     DosCreateEventSem(0, &theControlParams.Thread1ToThread2, 0, 0);
  965.     DosCreateEventSem(0, &theControlParams.Thread2ToThread1, 0, 0);
  966.     DosResetEventSem(theControlParams.Thread1ToThread2, &PostCount);
  967.     DosResetEventSem(theControlParams.Thread2ToThread1, &PostCount);
  968.     _beginthread(MappingThread, 0, 0x10000, &theControlParams); 
  969.                 /* Create the help window. */
  970.     theHelpWin = InitHelp(Win);
  971.                 /* Disable some menu items. */
  972.     MenuDisable(Win, IDM_Stop); /* Can't stop mapping until we start. */
  973.     MenuDisable(Win, IDM_ZoomIn); /* Can't zoom in without a zoom rectangle. */
  974.     return 0;
  975.   }
  976.   case WM_SIZE:
  977.     if (theControlParams.isRunning) /* Stop mapping, if we're doing it. */
  978.       StopMapping(&theControlParams);
  979.     MenuDisable(Win, IDM_Stop);
  980.  
  981.     RectExists = 0;             /* Get rid of zooming rectangle. */
  982.     MenuDisable(Win, IDM_ZoomIn);
  983.  
  984.     WinSize.x = SHORT1FROMMP(mp2); /* Get new size in window coords. */
  985.     WinSize.y = SHORT2FROMMP(mp2); 
  986.                                 /* Reset shodow bitmap and draw axes. */
  987.     ClearScreen(&ShadowBitmap, Win, &WinSize, &theRange, &G);
  988.     return (MRESULT) 1;
  989.  
  990.   case WM_PAINT: {
  991.     POINTL Points[3];
  992.     RECTL InvalidArea;
  993.     HPS PS;
  994.  
  995.     PS = WinBeginPaint(Win, 0, &InvalidArea);
  996.     GpiSetPS (PS, &dummy, PU_HIMETRIC);    
  997.  
  998.     Points[0].x = Points[0].y = Points[2].x = Points[2].y = 0; /* Lower left */
  999.     Points[1] = WinSize;        /* Upper right. */
  1000.  
  1001.     DosEnterCritSec();          /* Prevent conflict over the shadow. */
  1002.     GpiErase(PS);
  1003.     GpiBitBlt(PS, ShadowBitmap.MemPS, 3, Points, ROP_SRCCOPY, BBO_OR);
  1004.     DrawAxes(PS, &G);
  1005.     if(RectExists) {            /* Has user marked a rectangle? */
  1006.       ClipToGraph(PS, &G);      
  1007.       DrawRect(PS, &Corner1, &Corner2); /* Draw the rectangle. */
  1008.       UndoClipping(PS);
  1009.     }
  1010.     DosExitCritSec();
  1011.  
  1012.     WinEndPaint(PS);
  1013.     return (MRESULT) 1;
  1014.   }
  1015.   case WM_DESTROY:              /* Free resources we've allocated. */
  1016.     WinSetCapture(HWND_DESKTOP, 0); /* Ungrab the mouse. */
  1017.     WinDestroyPointer(RectPointer);
  1018.     DestroyHelp(theHelpWin);
  1019.     KillMappingThread(&theControlParams);
  1020.     DestroyBitmap(&ShadowBitmap);
  1021.     WinDestroyWindow(theControlParams.PosWin); /* Get rid of point display. */
  1022.     DosCloseEventSem(theControlParams.Thread1ToThread2);
  1023.     DosCloseEventSem(theControlParams.Thread2ToThread1);
  1024.     return 0;
  1025.   case WM_BUTTON1DOWN: {        /* Start running! */
  1026.     POINTL MousePosition;
  1027.     HPS PS;
  1028.     double J, theta;
  1029.  
  1030.     if (ButtonDown) {           /* If button 2 is down already, puke. */
  1031.       DosBeep(1045, 100);       /* C', for 1/10 sec. */
  1032.       return (MRESULT) 0;
  1033.     }
  1034.  
  1035.     if (theControlParams.isRunning) { /* If we're already running, stop and */
  1036.       StopMapping(&theControlParams); /* restart. */
  1037.       MenuDisable(Win, IDM_Stop);
  1038.     }
  1039.  
  1040.     MousePosition.x = (short) SHORT1FROMMP(mp1);
  1041.     MousePosition.y = (short) SHORT2FROMMP(mp1);
  1042.  
  1043.     PS = WinGetPS(Win);         /* Get position in GPI coordinates. */
  1044.     GpiSetPS (PS, &dummy, PU_HIMETRIC);
  1045.     GpiConvert(PS, CVTC_DEVICE, CVTC_PAGE, 1L, &MousePosition);
  1046.     if (RectExists) {           /* If there's a marked rectangle, kill it. */
  1047.       RectExists = 0;
  1048.       MenuDisable(Win, IDM_ZoomIn); /* Can't do this without a rectangle. */
  1049.       ClipToGraph(PS, &G);
  1050.       DrawRect(PS, &Corner1, &Corner2); /* Erase the rectangle. */
  1051.       UndoClipping(PS);
  1052.     }
  1053.     WinReleasePS(PS);
  1054.                                 /* Convert to graph coordinates. */
  1055.     toXY(MousePosition, &J, &theta, &G);
  1056.                                 /* Verify that mouse was actually clicked */
  1057.                                 /*  within the graph. */
  1058.     if (J     >= theRange.Min.J     && J     <= theRange.Max.J &&
  1059.         theta >= theRange.Min.theta && theta <= theRange.Max.theta) {
  1060.       MenuEnable(Win, IDM_Stop);        /* Allow user to stop the process. */
  1061.       StartingPoint.J     = J;
  1062.       StartingPoint.theta = theta;
  1063.       StartMapping(&theControlParams); /* Signal thread 2 to start. */
  1064.     }
  1065.     else
  1066.       DosBeep(1045, 100);       /* C', for 1/10 sec. */
  1067.  
  1068.     return WinDefWindowProc(Win, Msg, mp1, mp2);
  1069.   }
  1070.   case WM_BUTTON2DOWN: {        /* Begin processing for user to draw a zoom */
  1071.     HPS PS;            /*   rectangle. */
  1072.  
  1073.     ButtonDown = 1;
  1074.     WinSetCapture(HWND_DESKTOP, Win); /* Grab the mouse. */
  1075.     WinSetPointer(HWND_DESKTOP, RectPointer); /* Use box-drawing pointer. */
  1076.  
  1077.     StopMapping(&theControlParams); /* Signal thread 2 to stop. */
  1078.     MenuDisable(Win, IDM_Stop);      
  1079.  
  1080.     PS = WinGetPS(Win);         /* Get a PS to use for drawing the rect. */
  1081.     GpiSetPS (PS, &dummy, PU_HIMETRIC);
  1082.     ClipToGraph(PS, &G);
  1083.  
  1084.     if (RectExists)             /* Erase the old rectangle, if there is one. */
  1085.       DrawRect(PS, &Corner1, &Corner2);
  1086.  
  1087.     RectExists = 1;             /* We have one now, anyway. */
  1088.     MenuEnable(Win, IDM_ZoomIn);
  1089.  
  1090.     Corner1.x = Corner2.x = (short) SHORT1FROMMP(mp1); /* Set the corners. */
  1091.     Corner1.y = Corner2.y = (short) SHORT2FROMMP(mp1);
  1092.     GpiConvert(PS, CVTC_DEVICE, CVTC_PAGE, 1, &Corner1); /* Convert corners */
  1093.     GpiConvert(PS, CVTC_DEVICE, CVTC_PAGE, 1, &Corner2); /*  to win coords */
  1094.  
  1095.     DrawRect(PS, &Corner1, &Corner2); /* Draw the new rectangle. */
  1096.     UndoClipping(PS);
  1097.     WinReleasePS(PS);           /* Get rid of the PS. */
  1098.                                 /* Raise window to the top. */
  1099.     WinSetWindowPos(WinQueryWindow(Win, QW_PARENT),
  1100.                     HWND_TOP,
  1101.                     0, 0, 0, 0,
  1102.                     SWP_ZORDER);
  1103.     WinSetFocus(HWND_DESKTOP, WinQueryWindow(Win, QW_PARENT));
  1104.     return (MRESULT) 1;
  1105.   }
  1106.  
  1107.   case WM_MOUSEMOVE:
  1108.     if (ButtonDown) {           /* Is user drawing a box? */
  1109.       HPS PS;
  1110.       PS = WinGetPS(Win);       /* Get a presentation space. */
  1111.       GpiSetPS (PS, &dummy, PU_HIMETRIC);
  1112.       ClipToGraph(PS, &G);
  1113.  
  1114.       WinSetPointer(HWND_DESKTOP, RectPointer);
  1115.       DrawRect(PS, &Corner1, &Corner2); /* Erase the old rectangle. */
  1116.  
  1117.       Corner2.x = (short) SHORT1FROMMP(mp1); /* Get corner 2 of new rect. */
  1118.       Corner2.y = (short) SHORT2FROMMP(mp1);
  1119.       GpiConvert(PS, CVTC_DEVICE, CVTC_PAGE, 1, &Corner2);
  1120.  
  1121.       DrawRect(PS, &Corner1, &Corner2); /* Draw the new rectangle. */
  1122.       UndoClipping(PS);
  1123.       WinReleasePS(PS);
  1124.       return (MRESULT) 1;
  1125.     }
  1126.     else
  1127.       return WinDefWindowProc(Win, Msg, mp1, mp2);
  1128.  
  1129.   case WM_BUTTON2UP:
  1130.     if (ButtonDown) {           /* Is uer drawing a box? */
  1131.       HPS PS;
  1132.       PS = WinGetPS(Win);       /* Get a presentation space. */
  1133.       GpiSetPS (PS, &dummy, PU_HIMETRIC);
  1134.       ClipToGraph(PS, &G);
  1135.  
  1136.       DrawRect(PS, &Corner1, &Corner2); /* Erase the old rectangle. */
  1137.  
  1138.       Corner2.x = (short) SHORT1FROMMP(mp1); /* Get corner 2 of new rect. */
  1139.       Corner2.y = (short) SHORT2FROMMP(mp1);
  1140.       GpiConvert(PS, CVTC_DEVICE, CVTC_PAGE, 1, &Corner2);
  1141.  
  1142.       DrawRect(PS, &Corner1, &Corner2); /* Draw the new rectangle. */
  1143.       UndoClipping(PS);
  1144.       WinReleasePS(PS);
  1145.  
  1146.       ButtonDown = 0;
  1147.       WinSetCapture(HWND_DESKTOP, 0); /* Ungrab the mouse. */
  1148.     }
  1149.     return WinDefWindowProc(Win, Msg, mp1, mp2);    
  1150.  
  1151.   case WM_SETFOCUS:
  1152.     if (theControlParams.isRunning) {
  1153.       StopMapping(&theControlParams);
  1154.       StartMapping(&theControlParams);
  1155.     }
  1156.     return WinDefWindowProc(Win, Msg, mp1, mp2);    
  1157.    
  1158.   case WM_COMMAND:
  1159.     switch(SHORT1FROMMP(mp1)) { /* Which command was it? */
  1160.  
  1161.     case IDM_Stop:
  1162.       StopMapping(&theControlParams); /* Signal thread 2 to stop. */
  1163.       MenuDisable(Win, IDM_Stop);      
  1164.       return (MRESULT) 1;
  1165.  
  1166.     case IDM_ClearScreen:
  1167.       if (theControlParams.isRunning) 
  1168.         StopMapping(&theControlParams);
  1169.  
  1170.       if (ButtonDown) {         /* This is pathological. */
  1171.         DosBeep(1045, 100);     /* C', for 1/10 sec. */
  1172.         return (MRESULT) 1;
  1173.       }
  1174.       else
  1175.         RectExists = 0;
  1176.  
  1177.       MenuDisable(Win, IDM_ZoomIn);
  1178.       MenuDisable(Win, IDM_Stop);      
  1179.       ClearScreen(&ShadowBitmap, Win, &WinSize, &theRange, &G);
  1180.       return (MRESULT) 1;
  1181.  
  1182.     case IDM_Exit:
  1183.       WinSetCapture(HWND_DESKTOP, 0); /* Ungrab the mouse. */
  1184.       WinPostMsg(Win, WM_QUIT, 0, 0);
  1185.       return (MRESULT) 1;
  1186.  
  1187.     case IDM_SlowMotion:    /* Toggle the slow motion option. */
  1188.       if (theControlParams.isRunning) {
  1189.     StopMapping(&theControlParams);
  1190.     MenuDisable(Win, IDM_Stop);
  1191.       }
  1192.       if (theControlParams.isSlowMotion) { /* Toggle variable and uncheck */
  1193.     theControlParams.isSlowMotion = 0; /*  the menu item. */
  1194.     WinSendMsg(WinWindowFromID(WinQueryWindow(Win, QW_PARENT), FID_MENU),
  1195.            MM_SETITEMATTR,
  1196.            MPFROM2SHORT(IDM_SlowMotion, TRUE),
  1197.            MPFROM2SHORT(MIA_CHECKED, 0));
  1198.       }
  1199.       else {            /* Again: toggle variable and put a check */
  1200.     theControlParams.isSlowMotion = 1; /*  mark on the menu item. */
  1201.     WinSendMsg(WinWindowFromID(WinQueryWindow(Win, QW_PARENT), FID_MENU),
  1202.            MM_SETITEMATTR,
  1203.            MPFROM2SHORT(IDM_SlowMotion, TRUE),
  1204.            MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED));
  1205.       }
  1206.       return (MRESULT) 1;
  1207.  
  1208.     case IDM_DelayTime:        /* Set how slow the slow motion is. */
  1209.       if(WinDlgBox(HWND_DESKTOP, Win, DelayDlgProc, NULLHANDLE, DID_DelayTime,
  1210.                    &theControlParams.theDelay) == DLG_OK)
  1211.     if (theControlParams.isRunning && theControlParams.isSlowMotion) {
  1212.       StopMapping(&theControlParams);
  1213.       MenuDisable(Win, IDM_Stop);
  1214.     }
  1215.       return (MRESULT) 1;
  1216.  
  1217.     case IDM_Colors:            /* Change color of the plot. */
  1218.       if(WinDlgBox(HWND_DESKTOP, Win, ColorDlg, NULLHANDLE, DID_Color,
  1219.                    &theControlParams.theColor) == DLG_OK)
  1220.         if (theControlParams.isRunning) {
  1221.           StopMapping(&theControlParams);
  1222.           MenuDisable(Win, IDM_Stop);
  1223.         }
  1224.       return (MRESULT) 1;
  1225.  
  1226.     case IDM_SetK:              /* All we have to be careful about here */
  1227.                                 /*  is making sure that nothing happens if */
  1228.                                 /*  the user presses CANCEL. */
  1229.       if(WinDlgBox(HWND_DESKTOP, Win, KFactorDlg, NULLHANDLE, DID_KFactor,
  1230.                    &theControlParams.K) == DLG_OK) {
  1231.         if (theControlParams.isRunning)
  1232.           StopMapping(&theControlParams);
  1233.  
  1234.         MenuDisable(Win, IDM_Stop);     
  1235.         RectExists = 0;
  1236.         MenuDisable(Win, IDM_ZoomIn);
  1237.         ClearScreen(&ShadowBitmap, Win, &WinSize, &theRange, &G);
  1238.       }
  1239.       return (MRESULT) 1;
  1240.  
  1241.     case IDM_ZoomOut:           /* Set range to maximum possible. */
  1242.       if (theControlParams.isRunning)
  1243.         StopMapping(&theControlParams);
  1244.  
  1245.       if (ButtonDown) {         /* User shouldn't be issuing commands while */
  1246.         DosBeep(1045, 100);     /*  holding down a mouse button... */
  1247.         return (MRESULT) 1;
  1248.       }
  1249.       else
  1250.         RectExists = 0;         /* Clear marked rectangle. */
  1251.  
  1252.       MenuDisable(Win, IDM_ZoomIn);
  1253.       MenuDisable(Win, IDM_Stop);
  1254.  
  1255.       theRange.Min.J = theRange.Min.theta = 0; 
  1256.       theRange.Max.J = theRange.Max.theta = TwoPi;
  1257.  
  1258.       ClearScreen(&ShadowBitmap, Win, &WinSize, &theRange, &G);
  1259.       return (MRESULT) 1;
  1260.  
  1261.     case IDM_SetRange: {        /* Much the same as above. */
  1262.       if (ButtonDown) {         /* User shouldn't be issuing commands while */
  1263.         DosBeep(1045, 100);     /*  holding down a mouse button... */
  1264.         return (MRESULT) 1;
  1265.       }
  1266.       
  1267.       if (WinDlgBox(HWND_DESKTOP, Win, RangeDialog, 0, DID_Ranges, &theRange)
  1268.       != DLG_OK)
  1269.         return (MRESULT) 1;     /* User selected CANCEL. */
  1270.  
  1271.       if (theControlParams.isRunning)
  1272.         StopMapping(&theControlParams);
  1273.       MenuDisable(Win, IDM_Stop);
  1274.       MenuDisable(Win, IDM_ZoomIn);
  1275.       RectExists = 0;
  1276.       ClearScreen(&ShadowBitmap, Win, &WinSize, &theRange, &G);
  1277.       return (MRESULT) 1;
  1278.     }
  1279.     case IDM_ZoomIn: {          /* Zoom to rectangle the user has chosen. */
  1280.       double x, y, x2, y2;
  1281.  
  1282.       if (theControlParams.isRunning)
  1283.         StopMapping(&theControlParams);
  1284.       MenuDisable(Win, IDM_Stop);
  1285.       MenuDisable(Win, IDM_ZoomIn);
  1286.       RectExists = 0;
  1287.  
  1288.       toXY(Corner1, &x, &y, &G);
  1289.       toXY(Corner2, &x2, &y2, &G);
  1290.  
  1291.       if (x == x2 || y == y2) { /* This would be bad... */
  1292.     HPS PS;
  1293.  
  1294.         PS = WinGetPS(Win);
  1295.         GpiSetPS (PS, &dummy, PU_HIMETRIC);
  1296.         ClipToGraph(PS, &G);
  1297.         DrawRect(PS, &Corner1, &Corner2); /* Erase the rectangle. */
  1298.         WinMessageBox(HWND_DESKTOP, Win,
  1299.                      "Error: Upper and lower bounds of the range must differ.",
  1300.                      "PM Chaos", 0,
  1301.                      MB_ERROR | MB_OK | MB_MOVEABLE);
  1302.     UndoClipping(PS);
  1303.     WinReleasePS(PS);
  1304.         return (MRESULT) 1;
  1305.       }
  1306.  
  1307.       theRange.Min.J     = InRange(fmin(x, x2), 0, TwoPi);
  1308.       theRange.Min.theta = InRange(fmin(y, y2), 0, TwoPi);
  1309.       theRange.Max.J     = InRange(fmax(x, x2), 0, TwoPi);
  1310.       theRange.Max.theta = InRange(fmax(y, y2), 0, TwoPi);
  1311.  
  1312.       ClearScreen(&ShadowBitmap, Win, &WinSize, &theRange, &G);
  1313.       return (MRESULT) 1;
  1314.     }
  1315.  
  1316.     case IDM_HelpAbout:
  1317.       WinDlgBox(HWND_DESKTOP, Win, AboutDlg, 0, DID_About, 0);
  1318.       return (MRESULT) 1;
  1319.  
  1320.     case IDM_HelpHelp:
  1321.       if (theHelpWin)
  1322.     WinSendMsg(theHelpWin, HM_DISPLAY_HELP, 0, 0);
  1323.       return (MRESULT) 1;
  1324.  
  1325.     default:
  1326.       return WinDefWindowProc(Win, Msg, mp1, mp2);
  1327.     }
  1328.   case HM_QUERY_KEYS_HELP:    /* Tell help system where to find keys help. */
  1329.     return (MRESULT) ID_KeysHelpPanel;
  1330.  
  1331.   case HM_ERROR:        /* Oops...  Something has gone wrong. */
  1332.     WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
  1333.           "Warning: the on-line help system has encountered an error.",
  1334.           "PM Chaos", 0,
  1335.           MB_WARNING | MB_OK);
  1336.     return (MRESULT) 0;
  1337.  
  1338.   default:
  1339.     return WinDefWindowProc(Win, Msg, mp1, mp2);
  1340.   }
  1341. }
  1342.  
  1343.  
  1344. int main(void)
  1345. {
  1346.   HAB anchor_block = WinInitialize(0);
  1347.   HMQ Queue = WinCreateMsgQueue (anchor_block, 0);
  1348.   unsigned long StyleFlag;
  1349.   HWND Frame, Window;
  1350.   QMSG message;
  1351.  
  1352.   WinRegisterClass (anchor_block,
  1353.                     (unsigned char *) "Chaos",
  1354.                     ChaosWindowProc,
  1355.                     0,
  1356.                     0);
  1357.  
  1358.   StyleFlag =
  1359.     FCF_TITLEBAR | FCF_SYSMENU | FCF_MINMAX | FCF_SHELLPOSITION | FCF_TASKLIST
  1360.     | FCF_SIZEBORDER | FCF_MENU | FCF_ICON | FCF_ACCELTABLE;
  1361.   Frame = WinCreateStdWindow
  1362.     (HWND_DESKTOP,        /* Parent window. */
  1363.      WS_VISIBLE,        /* Frame window style. */
  1364.      &StyleFlag,        /* Frame creation flage. */
  1365.      (unsigned char *) "Chaos",    /* Class name. */
  1366.      "PM Chaos",        /* Text that goes in the title bar. */
  1367.      0L,            /* Window style. */
  1368.      0,                /* This means resources come from the .EXE */
  1369.      ID_MainWindow,             /* Resource ID. */
  1370.      &Window);
  1371.  
  1372.   do {
  1373.     while (WinGetMsg (anchor_block, &message, 0, 0, 0))
  1374.       WinDispatchMsg (anchor_block, &message);
  1375.   } while (WinMessageBox(HWND_DESKTOP, Window,
  1376.                         "Are you sure you want to exit PMChaos?", "PM Chaos", 
  1377.                         0, MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2)
  1378.            != MBID_YES);
  1379.  
  1380.   WinDestroyWindow (Frame);
  1381.   WinDestroyMsgQueue(Queue);
  1382.   WinTerminate(anchor_block);
  1383.   return 0;
  1384. }
  1385.