home *** CD-ROM | disk | FTP | other *** search
/ Monster Media 1994 #1 / monster.zip / monster / PROG_GEN / FACETV.ZIP / TINPMOUS.CPP < prev    next >
C/C++ Source or Header  |  1994-01-04  |  18KB  |  752 lines

  1. /************************************************************************
  2. **
  3. ** @(#)tinpmous.cpp    06/09/93    Chris Ahlstrom
  4. **
  5. ** "Variations on TInputLine in C++", Op. 3.03
  6. **
  7. **    This module provides an object that inherits capabilities
  8. ** from TInputFloat, and extends it to handle changing values with
  9. ** the mouse or up/down arrows.
  10. **
  11. **    To implement these functions, we must override handleEvent()
  12. ** and maybe some other TInputLine routines.
  13. **
  14. **    One important note... for now, I have derived this mouse- and
  15. ** key-handling object (TInputFloatControl) from TInputFloat, which is
  16. ** itself derived from TExtended, which already has its own rudimentary
  17. ** mouse- and key-handling system.  So far, they don't seem to interfere,
  18. ** but maybe there's a better way to do this.
  19. **
  20. *************************************************************************/
  21.  
  22. #define TINPMOUS_cpp
  23.  
  24. #include <conio.h>
  25. #include <dos.h>
  26. #include <math.h>
  27.  
  28. #include "tinpmous.h"
  29.  
  30. extern "C"
  31. {
  32.     #include "tmouse.h"
  33. }
  34.  
  35. __link(RInputFloatControl)    // ready it for streaming
  36.  
  37.  
  38.  
  39. /************************************************************************
  40. ** TInputFloatControl
  41. *************************************************************************/
  42.  
  43. const char * const TInputFloatControl::name = "TInputFloatControl";
  44.  
  45. TInputFloatControl::TInputFloatControl
  46. (
  47.     const TRect& bounds,    // rectangle for Float's input-line
  48.     int fmaxLen,        // maximum length to use for buffer
  49.     const Range& code,        // range of internal representation
  50.     char *formatstring,        // desired display format
  51.     int mapped,            // should remapping be performed?
  52.     const Range& user,        // range of user's representation
  53.     int typeofmap,        // 1=using exponential mapping
  54.     int maxmickey,        // highest mouse changes we support
  55.     int maxkey,            // highest key changes we support
  56.     CtrlFunction ctrlf        // function to use for control
  57. ) :
  58.     TInputFloat        (bounds, fmaxLen, code, formatstring, mapped, user),
  59.     mouseKeyMap        ((MapType) typeofmap),
  60.     mouseMin        (maxmickey),
  61.     mouseMax        (0),
  62.     mouseSlope        (0.0),
  63.     keyMin        (0),
  64.     keyMax        (maxkey),
  65.     keySlope        (0.0),
  66.     userMin        (user.minimum),
  67.     userMax        (user.maximum),
  68.     userControl        (ctrlf),
  69.     yMouse        (0),
  70.     yKey        (0),
  71.     inControl        (0)
  72. {
  73.  
  74.     /********************************************************
  75.     ** Prepare the internal mappings for this field.
  76.     *********************************************************/
  77.  
  78.     setupMouseMapping();
  79.     setupKeyMapping();
  80. }
  81.  
  82.  
  83. void
  84. TInputFloatControl::handleEvent
  85. (
  86.     TEvent& event
  87. )
  88. {
  89.     /********************************************************************
  90.     ** Handling of the Up and Down arrow keys
  91.     **
  92.     **    We don't call clearEvent() unless the keystroke is one we need to
  93.     ** handle.
  94.     *********************************************************************/
  95.  
  96.     if (event.what == evMouseDown)        // preempt handleEvent()
  97.     {
  98.     if (event.mouse.buttons == RIGHT_MOUSE_BUTTON)
  99.     {
  100.         if (!inControl)
  101.         {
  102.         (void) handleMouseControl();    // home-grown loop
  103.         setMousePosition        // restore cursor location
  104.         (
  105.             event.mouse.where.x, event.mouse.where.y
  106.         );
  107.         }
  108.         clearEvent(event);            // don't let TV have it
  109.     }
  110.     }
  111.  
  112.     /********************************************************************
  113.     ** Normal event-handling
  114.     **
  115.     **    Although TInputFloatControl is derived from TInputFloat, we
  116.     ** do not want to call TInputFloat::handleEvent(event), because
  117.     ** it will intercept the up/down arrows and prevent them from
  118.     ** attempting to execute the userControl() function.
  119.     *********************************************************************/
  120.  
  121.     TInputLine::handleEvent(event);        // handle the TV stuff
  122.  
  123.     /********************************************************************
  124.     ** Handling of the Mouse
  125.     **
  126.     **    We must first handle the right-click that starts the allowing
  127.     ** of the mouse to control the number.  Since TInputLine's
  128.     ** handleEvent() clears all evMouseDown events, we have to
  129.     ** intercept it here, to see if it's the right-button click
  130.     ** we want to handle.
  131.     **
  132.     **    Then we enter a closed loop that takes total control of the
  133.     ** mouse until the left mouse button is pressed.  Is this the
  134.     ** best way?  I don't know.
  135.     *********************************************************************/
  136.  
  137.     if (event.what == evKeyDown)
  138.     {
  139.     int goodkey;
  140.  
  141.     switch (event.keyDown.keyCode)
  142.     {
  143.     case kbUp:
  144.  
  145.         goodkey = 1;
  146.         break;
  147.  
  148.     case kbDown:
  149.  
  150.         goodkey = 1;
  151.         break;
  152.  
  153.     default:
  154.  
  155.         goodkey = 0;
  156.         break;
  157.     }
  158.     if (goodkey)
  159.     {
  160.         Value = handleKeyControl(event.keyDown.keyCode);
  161.         setData(&Value);
  162.         if (userControl != NULL)
  163.         {
  164.         (void) (*userControl)(Value);    // implement knob twist
  165.         }
  166.         clearEvent(event);
  167.     }
  168.     }
  169. }
  170.  
  171.  
  172. TStreamableClass
  173. RInputFloatControl
  174. (
  175.     TInputFloatControl::name,
  176.     TInputFloatControl::build,
  177.     __DELTA(TInputFloatControl)
  178. );
  179.  
  180.  
  181. /************************************************************************
  182. ** handleMouseControl()
  183. **
  184. **    Handle the Mousestroke (instead of the mouse).
  185. **
  186. **    This routine is entered by pressing the RIGHT mouse button.
  187. ** It is exited when the LEFT mouse button is pressed; if you try
  188. ** to use the RIGHT button for this purpose, well, no user ever
  189. ** lets go quickly enough to avoid hitting the button test!
  190. **
  191. **    Note that when we enter this routine, we use the current
  192. ** Value to properly position the logical cursor.
  193. **
  194. *************************************************************************/
  195.  
  196. double
  197. TInputFloatControl::handleMouseControl
  198. (
  199.     void
  200. )
  201. {
  202.     int ypos, ymick;
  203.  
  204.     (void) readYmickey();        // establish zero mickey delta
  205.     yMouse = mouseMapping(Value);    // obtain current position
  206.     ypos = yMouse;            // store initial position
  207.     inControl = 1;            // keep mouse in control
  208.     selectAll(True);            // ensure field looks "in use"
  209.     TMouse::hide();            // the mouse cursor confuses us
  210.     while (inControl)
  211.     {
  212.     ymick = readYmickey();        // see if mouse moved any
  213.     if (ymick != 0)            // did it move in y-dimension?
  214.     {
  215.         ypos += ymick;
  216.         ypos = mouseCorrect(ypos);    // make sure it's ok
  217.         if (ypos != yMouse)
  218.         {
  219.         yMouseOld = yMouse;        // save old position
  220.         yMouse = ypos;            // save new position
  221.         }
  222.         Value = userMouseMapping(yMouse);    // convert mouse y-value
  223.         setData(&Value);            // update the screen
  224.         if (userControl != NULL)        // a function supplied?
  225.         {
  226.         (void) (*userControl)(Value);    // implement knob twist
  227.         }
  228.     }
  229.     if (readMouseButtons() == LEFT_MOUSE_BUTTON)
  230.     {
  231.         inControl = 0;
  232.     }
  233.     }
  234.     while (kbhit())            // gobble up any stray keystrokes
  235.     (void) getch();
  236.     TMouse::show();
  237.     selectAll(False);            // unhighlight the field
  238.     return Value;
  239. }
  240.  
  241.  
  242. /************************************************************************
  243. ** mouseCorrect()
  244. **
  245. **    Makes sure the current mouse position is in the correct
  246. ** range, and returns a corrected position if so; otherwise, returns
  247. ** the position unaltered.
  248. **
  249. **    The test isn't quite so simple, because we invert the normal
  250. ** direction of the mouse, so that moving the cursor up increases the
  251. ** number
  252. **
  253. *************************************************************************/
  254.  
  255. int
  256. TInputFloatControl::mouseCorrect
  257. (
  258.     int ypos
  259. )
  260. {
  261.     if (mouseMin < mouseMax)
  262.     {
  263.     if (ypos < mouseMin)
  264.         ypos = mouseMin;
  265.     else if (ypos > mouseMax)
  266.         ypos = mouseMax;
  267.     }
  268.     else
  269.     {
  270.     if (ypos > mouseMin)
  271.         ypos = mouseMin;
  272.     else if (ypos < mouseMax)
  273.         ypos = mouseMax;
  274.     }
  275.     return ypos;
  276. }
  277.  
  278.  
  279. /************************************************************************
  280. ** handleKeyControl()
  281. **
  282. **    Handle the keystroke (instead of the mouse).
  283. **
  284. **    For safety, we retrieve the previous value, in case the mouse had
  285. ** modified it.
  286. **
  287. *************************************************************************/
  288.  
  289. double
  290. TInputFloatControl::handleKeyControl
  291. (
  292.     int key
  293. )
  294. {
  295.     yKey = keyMapping(Value);        // obtain current key level
  296.     yKeyOld = yKey;            // store initial position
  297.     if (key == kbUp)            // up-arrow key
  298.     {
  299.     yKey++;
  300.  
  301.     }
  302.     else if (key == kbDown)        // down-arrow key
  303.     {
  304.     yKey--;
  305.     }
  306.  
  307.     if (yKey < keyMin)
  308.     yKey = keyMin;
  309.     else if (yKey > keyMax)
  310.     yKey = keyMax;
  311.     if (yKey != yKeyOld)
  312.     {
  313.     yKeyOld = yKey;            // save new position
  314.     }
  315.     return userKeyMapping(yKey);    // get the appropriate user value
  316. }
  317.  
  318.  
  319. /************************************************************************
  320. ** Mouse Mapping
  321. **
  322. **    To save time (by passing parameters just once and making the
  323. ** slope calculation just once), this routine logs the parameters of
  324. ** the linear mapping conversion *and* the logarithmic (exponential)
  325. ** mapping conversion.
  326. **
  327. **    Note that we could reprogram the mouse itself, using the
  328. ** mouse mapping profile function.  (But maybe that affects only
  329. ** the acceleration.)
  330. **
  331. **
  332. **            LINEAR                EXPONENTIAL
  333. **        |                |
  334. **    userMax-|                   -|            *
  335. **        |                |            *
  336. **        |            *    |            *
  337. **        |             *        |               *
  338. **        |          *        |              *
  339. **        |           *        |             *
  340. **        |        *            |           *
  341. **        |     *            |        *
  342. **        |     *                |        *
  343. **    userMin-|  *                   -|   *  *
  344. **        |                |
  345. **        |                |
  346. **        |                |
  347. **         --------------------------     -------------------------
  348. **           |            |        |            |
  349. **         mouseMin       mouseMax    mouseMin       mouseMax
  350. **
  351. **
  352. **    LINEAR:
  353. **
  354. **        This mapping is the simple slope-intercept form of
  355. **    a linear equation.  It is created by using the fact that
  356. **    the extrema of the line form a similar triangle to that formed
  357. **    by the minimum point of the line and the desired data point.
  358. **
  359. **        x = mouse values    (x0 = mouseMin, x1 = mouseMax)
  360. **        y = user values        (y0 = userMin,  y1 = userMax)
  361. **        m = slope        (mouseSlope)
  362. **
  363. **             y1 - y0
  364. **        m = ---------
  365. **             x1 - x0
  366. **
  367. **        y = y0 + m(x - x0)
  368. **
  369. **    EXPONENTIAL:
  370. **
  371. **        To make this mapping, we simply transform y into
  372. **    a logarithmic variable by the transformation
  373. **
  374. **        z = ln y        (ln is the natural logarithm)
  375. **
  376. **    Then, we form the linear equation above using z instead of y.
  377. **    This gives us:
  378. **
  379. **                  z1 - z0
  380. **        z = z0 + --------- (x - x0)
  381. **                  x1 - x0
  382. **
  383. **    which is really
  384. **
  385. **                ln(y1) - ln(y0)
  386. **        ln y = ln y0 + ----------------- (x - x0)
  387. **                    x1 - x0
  388. **
  389. **    We raise e to both size, which gives us x and y defined as
  390. **    above, but with a different m:
  391. **
  392. **             ln(y1) - ln(y0)
  393. **        m = -----------------
  394. **                 x1 - x0
  395. **
  396. **            m(x - x0)
  397. **        y = y0 e
  398. **
  399. **
  400. *************************************************************************/
  401.  
  402.  
  403. /************************************************************************
  404. ** setupMouseMapping()
  405. **
  406. **    Sets up the above statics for use with mouseMapping() and
  407. ** userMouseMapping() below.
  408. **
  409. **    Note that, if the programmer supplies a 0 minimum value and
  410. ** asks for an exponential mapping, we force the minimum value to
  411. ** create a 40 dB range in the user's variable.
  412. **
  413. *************************************************************************/
  414.  
  415. void
  416. TInputFloatControl::setupMouseMapping
  417. (
  418.     void
  419. )
  420. {
  421.     double denominator;
  422.     double slope;
  423.  
  424.     denominator = (double) (mouseMax - mouseMin);
  425.     switch (mouseKeyMap)
  426.     {
  427.     case LINEAR:
  428.  
  429.     if (denominator == 0.0)        // if bad mouse range
  430.         slope = 0.0;        // set up so that y = y0
  431.     else
  432.         slope = (userMax - userMin) / denominator;
  433.     break;
  434.  
  435.     case EXPONENTIAL:
  436.  
  437.     if (userMin <= 0.0)        // bad minimum value
  438.     {
  439.         userMin = userMax / 100.0;    // make it a 40 dB range
  440.     }
  441.     denominator = (double) (mouseMax - mouseMin);
  442.     if (denominator == 0.0)        // if bad mouse range
  443.         slope = 0.0;        // set up so that y = y0
  444.     else
  445.         slope = (log(userMax) - log(userMin)) / denominator;
  446.     break;
  447.     }
  448.     mouseSlope = slope;
  449. }
  450.  
  451.  
  452. /************************************************************************
  453. ** setupKeyMapping()
  454. **
  455. **    Sets up the above statics for use with keyMapping() and
  456. ** userKeyMapping() below.
  457. **
  458. **    Note that, if the programmer supplies a 0 minimum value and
  459. ** asks for an exponential mapping, we force the minimum value to
  460. ** create a 40 dB range in the user's variable.
  461. **
  462. *************************************************************************/
  463.  
  464. void
  465. TInputFloatControl::setupKeyMapping
  466. (
  467.     void
  468. )
  469. {
  470.     double denominator;
  471.     double slope;
  472.  
  473.     denominator = (double) (keyMax - keyMin);
  474.     switch (mouseKeyMap)
  475.     {
  476.     case LINEAR:
  477.  
  478.     if (denominator == 0.0)        // if bad key range
  479.         slope = 0.0;        // set up so that y = y0
  480.     else
  481.         slope = (userMax - userMin) / denominator;
  482.     break;
  483.  
  484.     case EXPONENTIAL:
  485.  
  486.     if (userMin <= 0.0)        // bad minimum value
  487.     {
  488.         userMin = userMax / 100.0;    // make it a 40 dB range
  489.     }
  490.     denominator = (double) (keyMax - keyMin);
  491.     if (denominator == 0.0)        // if bad key range
  492.         slope = 0.0;        // set up so that y = y0
  493.     else
  494.         slope = (log(userMax) - log(userMin)) / denominator;
  495.     break;
  496.     }
  497.     keySlope = slope;
  498. }
  499.  
  500.  
  501. /************************************************************************
  502. ** userMouseMapping()
  503. **
  504. **    Converts the given mouse value (usually in the range of
  505. ** 0 to 392) to the user domain.  If any screwups occur, the value
  506. ** is restricted to the range
  507. **
  508. **        userMin <= userMapping <= userMax
  509. **
  510. *************************************************************************/
  511.  
  512. double
  513. TInputFloatControl::userMouseMapping
  514. (
  515.     int mousevalue        // current value of mouse y-mickey
  516. )
  517. {
  518.     double uservalue;
  519.  
  520.     switch (mouseKeyMap)
  521.     {
  522.     case LINEAR:
  523.  
  524.     uservalue = userMouseLinear(mousevalue);
  525.     break;
  526.  
  527.     case EXPONENTIAL:
  528.  
  529.     uservalue = userMouseExp(mousevalue);
  530.     break;
  531.     }
  532.     if (uservalue > userMax)
  533.     uservalue = userMax;
  534.     else if (uservalue < userMin)
  535.     uservalue = userMin;
  536.     else if (uservalue == -0.0)
  537.     uservalue = 0.0;
  538.  
  539.     return uservalue;
  540. }
  541.  
  542.  
  543. /************************************************************************
  544. ** userKeyMapping()
  545. **
  546. **    Converts the given mouse value (usually in the range of
  547. ** 0 to 392) to the user domain.  If any screwups occur, the value
  548. ** is restricted to the range
  549. **
  550. **        userMin <= userMapping <= userMax
  551. **
  552. *************************************************************************/
  553.  
  554. double
  555. TInputFloatControl::userKeyMapping
  556. (
  557.     int keyvalue        // current value of key counter
  558. )
  559. {
  560.     double uservalue;
  561.  
  562.     switch (mouseKeyMap)
  563.     {
  564.     case LINEAR:
  565.  
  566.     uservalue = userKeyLinear(keyvalue);
  567.     break;
  568.  
  569.     case EXPONENTIAL:
  570.  
  571.     uservalue = userKeyExp(keyvalue);
  572.     break;
  573.     }
  574.     if (uservalue > userMax)
  575.     uservalue = userMax;
  576.     else if (uservalue < userMin)
  577.     uservalue = userMin;
  578.     else if (uservalue == -0.0)
  579.     uservalue = 0.0;
  580.  
  581.     return uservalue;
  582. }
  583.  
  584.  
  585. /************************************************************************
  586. ** mouseMapping()
  587. **
  588. **    Converts the given user value back to the corresponding
  589. ** mouse value (usually in the range of 0 to 392) to the user domain.
  590. ** If any screwups occur, the value is restricted to the range
  591. **
  592. **        mouseMin <= mouseKeyMap <= mouseMax
  593. **
  594. *************************************************************************/
  595.  
  596. int
  597. TInputFloatControl::mouseMapping
  598. (
  599.     double uservalue        // value of numeric field
  600. )
  601. {
  602.     double mousevalue;
  603.  
  604.     switch (mouseKeyMap)
  605.     {
  606.     case LINEAR:
  607.  
  608.     mousevalue = mouseLinear(uservalue);
  609.     break;
  610.  
  611.     case EXPONENTIAL:
  612.  
  613.     mousevalue = mouseExp(uservalue);
  614.     break;
  615.     }
  616.     return mouseCorrect((int) mousevalue);    // make sure it's ok
  617. }
  618.  
  619.  
  620. /************************************************************************
  621. ** keyMapping()
  622. **
  623. **    Converts the given user value back to the corresponding
  624. ** key value (usually in the range of 0 to 392) to the user domain.
  625. ** If any screwups occur, the value is restricted to the range
  626. **
  627. **        keyMin <= mouseKeyMapping <= keyMax
  628. **
  629. *************************************************************************/
  630.  
  631. int
  632. TInputFloatControl::keyMapping
  633. (
  634.     double uservalue        // value of the numeric field
  635. )
  636. {
  637.     double keyvalue;
  638.  
  639.     switch (mouseKeyMap)
  640.     {
  641.     case LINEAR:
  642.  
  643.     keyvalue = keyLinear(uservalue);
  644.     break;
  645.  
  646.     case EXPONENTIAL:
  647.  
  648.     keyvalue = keyExp(uservalue);
  649.     break;
  650.     }
  651.     if (keyvalue > keyMax)
  652.     keyvalue = keyMax;
  653.     else if (keyvalue < keyMin)
  654.     keyvalue = keyMin;
  655.  
  656.     return (int) keyvalue;
  657. }
  658.  
  659.  
  660. /************************************************************************
  661. ** Various mapping formulas
  662. **
  663. **    I'd like to just have these be functions associated with
  664. ** objects created in setupMouseMapping() and setupKeyMapping(),
  665. ** and thus called indirectly.  For now, I don't want to bother with
  666. ** such elegance.
  667. **
  668. *************************************************************************/
  669.  
  670. double
  671. TInputFloatControl::userMouseLinear
  672. (
  673.     double mousevalue
  674. )
  675. {
  676.     return (userMin + (mousevalue - mouseMin) * mouseSlope);
  677. }
  678.  
  679.  
  680. double
  681. TInputFloatControl::userMouseExp
  682. (
  683.     double mousevalue
  684. )
  685. {
  686.     return (userMin * exp((mousevalue - mouseMin) * mouseSlope));
  687. }
  688.  
  689.  
  690. double
  691. TInputFloatControl::userKeyLinear
  692. (
  693.     double keyvalue
  694. )
  695. {
  696.     return (userMin + (keyvalue - keyMin) * keySlope);
  697. }
  698.  
  699.  
  700. double
  701. TInputFloatControl::userKeyExp
  702. (
  703.     double keyvalue
  704. )
  705. {
  706.     return (userMin * exp((keyvalue - keyMin) * keySlope));
  707. }
  708.  
  709.  
  710. double
  711. TInputFloatControl::mouseLinear
  712. (
  713.     double uservalue
  714. )
  715. {
  716.     return (mouseSlope != 0.0) ?
  717.     (mouseMin + ((uservalue - userMin) / mouseSlope)) : uservalue;
  718. }
  719.  
  720.  
  721. double
  722. TInputFloatControl::mouseExp
  723. (
  724.     double uservalue
  725. )
  726. {
  727.     return (mouseSlope != 0.0) ?
  728.     (mouseMin + (log(uservalue/userMin) / mouseSlope)) : uservalue;
  729. }
  730.  
  731.  
  732. double
  733. TInputFloatControl::keyLinear
  734. (
  735.     double uservalue
  736. )
  737. {
  738.     return (keySlope != 0.0) ?
  739.     (keyMin + ((uservalue - userMin) / keySlope)) : uservalue;
  740. }
  741.  
  742.  
  743. double
  744. TInputFloatControl::keyExp
  745. (
  746.     double uservalue
  747. )
  748. {
  749.     return (keySlope != 0.0) ?
  750.     (keyMin + (log(uservalue/userMin) / keySlope)) : uservalue;
  751. }
  752.