//------------------------------------------------------------------------
//
//  Calculator.wfm  --  Calculator
//
//  Dependencies: Calculator.ico
//  
//  dBASE Samples Group
//
//  $Revision:   1.18  $
//
//   
//  Copyright (c) 2000-2013, dBase, LLC. 
//  All rights reserved.
//
//------------------------------------------------------------------------
//

set procedure to :CoreShared:qlanguage.cc additive

#define BASE_HEX             16
#define BASE_DEC             10
#define BASE_OCT              8
#define BASE_BIN              2

#define MAX_DIGITS_DEC       18
#define MAX_DIGITS_OCT       16
#define MAX_DIGITS_BIN       16
#define MAX_DIGITS_HEX        8
#define DISPLAY_LEN          18
#define HEX_OVERFLOW       2^32
#define DEC_OVERFLOW      10^18
#define KEY_BACKSPACE         8
#define ALLTRIM(x)        LTRIM(RTRIM(x))

set talk off
local f
** END HEADER -- do not remove this line
//
// Generated on 03/07/2013
//
parameter bModal
local f
f = new CALCULATORFORM()
if (bModal)
   f.mdi = false // ensure not MDI
   f.readModal()
else
   f.open()
endif

class CALCULATORFORM of FORM
   with (this)
      onGotFocus = class::FORMONGOTFOCUS
      onClose = class::FORMONCLOSE
      open = class::FORMOPEN
      scaleFontSize = 8.0
      scaleFontBold = false
      colorNormal = "BtnFace"
      height = 13.0455
      left = 16.0
      top = 3.0
      width = 42.5714
      text = "Calculator"
      mdi = false
      sizeable = false
      mousePointer = 1	// Arrow
      maximize = false
      icon = "FILE Calculator.ico"
   endwith

   this.RECTANGLE1 = new RECTANGLE(this)
   with (this.RECTANGLE1)
      left = 1.0
      top = 2.3182
      width = 41.0
      height = 1.25
      text = ""
      colorNormal = "WindowText/BtnFace"
      fontSize = 8.0
   endwith

   this.KEYHANDLER = new PAINTBOX(this)
   with (this.KEYHANDLER)
      onChar = class::KEYHANDLERONCHAR
      colorNormal = "BtnFace/BtnFace"
      height = 0.5
      left = 0.1
      top = 0.5
      width = 0.1
   endwith

   this.MEMORY = new ENTRYFIELD(this)
   with (this.MEMORY)
      enabled = false
      height = 1.0
      left = 1.0
      top = 11.5
      width = 41.0
      function = "J"
      colorNormal = "BtnText/BtnFace"
      fontSize = 8.0
      value = "                  "
      validRequired = true
      borderStyle = 7	// Client
   endwith

   this.B7 = new PUSHBUTTON(this)
   with (this.B7)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 1.0
      top = 4.0
      width = 6.0
      text = "7"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "B/BtnFace"
   endwith

   this.B8 = new PUSHBUTTON(this)
   with (this.B8)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 8.0
      top = 4.0
      width = 6.0
      text = "8"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "B/BtnFace"
   endwith

   this.B9 = new PUSHBUTTON(this)
   with (this.B9)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 15.0
      top = 4.0
      width = 6.0
      text = "9"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "B/BtnFace"
   endwith

   this.B4 = new PUSHBUTTON(this)
   with (this.B4)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 1.0
      top = 5.5
      width = 6.0
      text = "4"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "B/BtnFace"
   endwith

   this.B5 = new PUSHBUTTON(this)
   with (this.B5)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 8.0
      top = 5.5
      width = 6.0
      text = "5"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "B/BtnFace"
   endwith

   this.B6 = new PUSHBUTTON(this)
   with (this.B6)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 15.0
      top = 5.5
      width = 6.0
      text = "6"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "B/BtnFace"
   endwith

   this.B1 = new PUSHBUTTON(this)
   with (this.B1)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 1.0
      top = 7.0
      width = 6.0
      text = "1"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "B/BtnFace"
   endwith

   this.B2 = new PUSHBUTTON(this)
   with (this.B2)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 8.0
      top = 7.0
      width = 6.0
      text = "2"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "B/BtnFace"
   endwith

   this.B3 = new PUSHBUTTON(this)
   with (this.B3)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 15.0
      top = 7.0
      width = 6.0
      text = "3"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "B/BtnFace"
   endwith

   this.OPPLUSMINUS = new PUSHBUTTON(this)
   with (this.OPPLUSMINUS)
      onClick = class::OPPLUSMINUSONCLICK
      height = 1.0
      left = 29.0
      top = 7.0
      width = 6.0
      text = "+/-"
      speedBar = true
      fontSize = 8.0
      group = true
   endwith

   this.B0 = new PUSHBUTTON(this)
   with (this.B0)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 1.0
      top = 8.5
      width = 6.0
      text = "0"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "B/BtnFace"
   endwith

   this.POINT = new PUSHBUTTON(this)
   with (this.POINT)
      onClick = class::POINTONCLICK
      height = 1.0
      left = 8.0
      top = 8.5
      width = 6.0
      text = ". "
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "B/BtnFace"
   endwith

   this.OPPOWER = new PUSHBUTTON(this)
   with (this.OPPOWER)
      onClick = class::OPCLICK
      height = 1.0
      left = 29.0
      top = 5.5
      width = 6.0
      text = "^"
      speedBar = true
      fontSize = 8.0
      group = true
   endwith

   this.OPTIMES = new PUSHBUTTON(this)
   with (this.OPTIMES)
      onClick = class::OPCLICK
      height = 1.0
      left = 22.0
      top = 5.5
      width = 6.0
      text = "X"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "darkgreen/BtnFace"
   endwith

   this.OPDIV = new PUSHBUTTON(this)
   with (this.OPDIV)
      onClick = class::OPCLICK
      height = 1.0
      left = 22.0
      top = 4.0
      width = 6.0
      text = "/"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "darkgreen/BtnFace"
   endwith

   this.OPMINUS = new PUSHBUTTON(this)
   with (this.OPMINUS)
      onClick = class::OPCLICK
      height = 1.0
      left = 22.0
      top = 7.0
      width = 6.0
      text = "-"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "darkgreen/BtnFace"
   endwith

   this.BUTTONCLEAR = new PUSHBUTTON(this)
   with (this.BUTTONCLEAR)
      onClick = class::BUTTONCLEARONCLICK
      height = 1.0
      left = 29.0
      top = 4.0
      width = 6.0
      text = "CE\C"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "WHITE/RED"
   endwith

   this.OPEQUAL = new PUSHBUTTON(this)
   with (this.OPEQUAL)
      onClick = class::OPCLICK
      height = 1.0
      left = 15.0
      top = 8.5
      width = 6.0
      text = "="
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "Darkgreen/BtnFace"
   endwith

   this.OPPLUS = new PUSHBUTTON(this)
   with (this.OPPLUS)
      onClick = class::OPCLICK
      height = 1.0
      left = 22.0
      top = 8.5
      width = 6.0
      text = "+"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "darkgreen/BtnFace"
   endwith

   this.MADD = new PUSHBUTTON(this)
   with (this.MADD)
      onClick = class::MEMONCLICK
      height = 1.0
      left = 36.0
      top = 4.0
      width = 6.0
      text = "M+"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "WHITE/BLUE"
   endwith

   this.MSUB = new PUSHBUTTON(this)
   with (this.MSUB)
      onClick = class::MEMONCLICK
      height = 1.0
      left = 36.0
      top = 5.5
      width = 6.0
      text = "M-"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "WHITE/BLUE"
   endwith

   this.MR = new PUSHBUTTON(this)
   with (this.MR)
      onClick = class::MRONCLICK
      height = 1.0
      left = 36.0
      top = 7.0
      width = 6.0
      text = "MR"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "WHITE/BLUE"
   endwith

   this.MC = new PUSHBUTTON(this)
   with (this.MC)
      onClick = class::MCONCLICK
      height = 1.0
      left = 36.0
      top = 8.5
      width = 6.0
      text = "MC"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "WHITE/BLUE"
   endwith

   this.B_A = new PUSHBUTTON(this)
   with (this.B_A)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 1.0
      top = 10.0
      width = 6.0
      text = "A"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "Blue/BtnFace"
   endwith

   this.B_B = new PUSHBUTTON(this)
   with (this.B_B)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 8.0
      top = 10.0
      width = 6.0
      text = "B"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "Blue/BtnFace"
   endwith

   this.B_C = new PUSHBUTTON(this)
   with (this.B_C)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 15.0
      top = 10.0
      width = 6.0
      text = "C"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "Blue/BtnFace"
   endwith

   this.B_D = new PUSHBUTTON(this)
   with (this.B_D)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 22.0
      top = 10.0
      width = 6.0
      text = "D"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "Blue/BtnFace"
   endwith

   this.B_E = new PUSHBUTTON(this)
   with (this.B_E)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 29.0
      top = 10.0
      width = 6.0
      text = "E"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "Blue/BtnFace"
   endwith

   this.B_F = new PUSHBUTTON(this)
   with (this.B_F)
      onClick = class::DIGIT_ONCLICK
      height = 1.0
      left = 36.0
      top = 10.0
      width = 6.0
      text = "F"
      speedBar = true
      fontSize = 8.0
      group = true
      colorNormal = "Blue/BtnFace"
   endwith

   this.DISPLAY = new TEXT(this)
   with (this.DISPLAY)
      height = 1.6818
      left = 6.0
      top = 0.3182
      width = 35.0
      border = true
      colorNormal = "WHITE/BLACK"
      marginHorizontal = 0.1905
      alignVertical = 1	// Middle
      alignHorizontal = 2	// Right
      fontName = "Fixedsys"
      fontSize = 9.0
      text = "TEXT1"
      borderStyle = 5	// Double
   endwith

   this.RADIOHEX = new RADIOBUTTON(this)
   with (this.RADIOHEX)
      onGotFocus = {; this.form.KEYHANDLER.setFocus()}
      onChange = class::RADIOHEXONCHANGE
      height = 0.8
      left = 3.0
      top = 2.6
      width = 7.0
      text = "Hex"
      colorNormal = "WindowText/BtnFace"
      fontSize = 8.0
      tabStop = false
      group = true
   endwith

   this.RADIODEC = new RADIOBUTTON(this)
   with (this.RADIODEC)
      onGotFocus = {; this.form.KEYHANDLER.setFocus()}
      onChange = class::RADIODECONCHANGE
      height = 0.8
      left = 14.0
      top = 2.6
      width = 7.0
      text = "Dec"
      colorNormal = "WindowText/BtnFace"
      fontSize = 8.0
      tabStop = false
      value = true
   endwith

   this.RADIOOCT = new RADIOBUTTON(this)
   with (this.RADIOOCT)
      onGotFocus = {; this.form.KEYHANDLER.setFocus()}
      onChange = class::RADIOOCTONCHANGE
      height = 0.8
      left = 24.0
      top = 2.6
      width = 7.0
      text = "Oct"
      colorNormal = "WindowText/BtnFace"
      fontSize = 8.0
      tabStop = false
   endwith

   this.RADIOBIN = new RADIOBUTTON(this)
   with (this.RADIOBIN)
      onGotFocus = {; this.form.KEYHANDLER.setFocus()}
      onChange = class::RADIOBINONCHANGE
      height = 0.8
      left = 34.0
      top = 2.6
      width = 7.0
      text = "Bin"
      colorNormal = "WindowText/BtnFace"
      fontSize = 8.0
      tabStop = false
   endwith

   this.OPSQRT = new PUSHBUTTON(this)
   with (this.OPSQRT)
      onClick = class::OPSQRTONCLICK
      height = 1.0
      left = 29.0
      top = 8.5
      width = 6.0
      text = "sqrt"
      speedBar = true
      fontSize = 8.0
      group = true
   endwith

   this.TEXTMINDICATOR = new TEXT(this)
   with (this.TEXTMINDICATOR)
      height = 1.7
      left = 2.0
      top = 0.3
      width = 4.0
      colorNormal = "BtnText"
      alignVertical = 1	// Middle
      text = " "
   endwith


   function changeBase( newBase )
      //
      // Change base number system
      //
      local value, memValue, dispFraction, memFraction

      // Get values in display and memory before changing 
      // hex indicator
      value    = CLASS::numVal( this.form.display.text )
      memValue = CLASS::numVal( this.form.memory.value )
      this.form.beforePoint := true
      SET DECIMALS TO this.form.decPlaces

      dispFraction = val(substr( this.form.display.text,;
             at( SETTO("POINT"), this.form.display.text)))
      memFraction  = val(substr(form.memory.value,;
             at( SETTO("POINT"), this.form.memory.value)))

      this.form.numberSys := newBase 

      if ( abs(value) >= HEX_OVERFLOW )
         // Temporary
         this.form.display.text := space(DISPLAY_LEN - MAX_DIGITS_HEX) +;
                             replicate("*", MAX_DIGITS_HEX)
      else
         this.form.display.text := CLASS::charVal( value + dispFraction )
      endif

      if ( abs(memValue) >= HEX_OVERFLOW )
         this.form.memory.value  := space(DISPLAY_LEN - MAX_DIGITS_HEX) +;
                             replicate("*", MAX_DIGITS_HEX)
      else
         this.form.memory.value := CLASS::charVal( memValue + memFraction )
      endif

   return ( this.form.numberSys )

   function digit_onClick
   return ( class::enterDigit( this.text ) )

   function opPlusMinusOnClick
      //
      // Toggle sign of number in display.
      //

      local num

      num = class::NumVal( this.form.display.text )
      with ( this.form )
         display.text := class::charVal(num * -1)
         lastKeyOperator := false
      endwith
   return ( this.form.display.text )

   function opSqrtOnClick
      //
      // Replace current diplay with square root of display.
      //

      local num, sText
      num = class::NumVal( this.form.display.text )
      if ( num < 0 )
         this.form.display.text := REPLICATE( "*", MAX_DIGITS_DEC )
      else
         this.form.display.text := class::charVal( SQRT( num ) )
      endif
      this.form.lastKeyOperator := false
   return ( this.form.display.text )

   function enterDigit( sDigit )
      //
      // Process a digit.
      //
      local num

      if ( this.form.lastKeyOperator )
         with ( this.form )
            lastKeyOperator := false
            beforePoint     := true
            display.text    := space(MAX_DIGITS_DEC - 1) +;
                               class::displayValue( sDigit )
         endwith
      else
   
         do case
            case class::displayFull()
                 ?? chr(7)
            case this.form.beforePoint
                 // Only want rightmost DISPLAY_LEN characters
                 this.form.display.text := ;
                    right( class::displayValue( this.form.display.text ) + ;
                           class::displayValue( sDigit ), ;
                           DISPLAY_LEN )
            otherwise
                 this.form.display.text := ;
                    class::addAfterPoint( class::displayValue( sDigit ) )
         endcase

      endif

   return ( this.form.display.text )

   function opClick
   return ( class::enterOp( this ) )

   function pointOnClick
      //
      // Process decimal point.
      //

      if ( this.form.beforePoint )
         with ( this.form )
             beforePoint := false
             decPlaces    := 1
         endwith
         set decimals to 1
         if ( this.form.lastKeyOperator )
            with ( this.form )
               lastKeyOperator := false
               display.text := ;
                 space(MAX_DIGITS_DEC - 1) + SETTO("POINT")
            endwith
         else
            this.form.display.text := ;
               class::AddAfterPoint( SETTO("POINT") )
         endif
      endif

   return ( this.form.display.text )

   function enterOp( thisOp )
      //
      // Process operator.
      //
      local lastOperation, lastOp, lastValue, tempOpList
      if ( this.form.lastKeyOperator or ;
           this.form.operationStack.isEmpty() )
         this.form.operationStack.pop()
         this.form.lastValue := class::numVal( this.form.display.text )
      else
         // Process all previous operations with >= precedence
         do while ( this.form.operationStack.prevPrecedenceGreaterOrEqual( ;
                    thisOp.precedence ) )
            lastOperation = this.form.operationStack.pop()

            if ( lastOperation.getPrecedence() > 0 ) // Don't process "="
               lastOp    = lastOperation.getOp()
               lastValue = lastOperation.getValue()
               set decimals to this.form.mostDecPlaces
               this.form.lastValue := ;
                  lastOp( lastValue, class::numVal( this.form.display.text ) )
               this.form.display.text := class::charVal( this.form.lastValue )
               this.form.decPlaces    := 1
               set decimals to 1
            endif
         enddo
      endif
      with ( this.form )
         beforePoint     := true
         lastKeyOperator := true
      endwith

      thisOp.opValue = class::numVal( this.form.display.text )
      this.form.operationStack.push( ( thisOp ) )

   return ( thisOp.opValue )

   function memOnClick
      //
      // Process a memory action button.
      //

      local result
      result = this.doIt( class::numVal( this.form.memory.value ),;
                          class::numVal( this.form.display.text ),;
                          "vallapoosa";
                          )
      with ( this.form )
         memory.value := class::CharVal(result)
         textMindicator.text := "M"
      endwith
   return ( result )

   function radioBinOnChange       

      with ( this.form )
         b2.enabled := ( not this.value )
         b3.enabled := ( not this.value )
         b4.enabled := ( not this.value )
         b5.enabled := ( not this.value )
         b6.enabled := ( not this.value )
         b7.enabled := ( not this.value )
         b8.enabled := ( not this.value )
         b9.enabled := ( not this.value )
      endwith

      if ( this.value )
         class::changeBase( BASE_BIN )
      endif

   return ( this.value )

   function radioDecOnChange
      // Enable keys applicable to decimal calculations
      with ( this.form )
         opPlusMinus.enabled := ( this.value )
         point.enabled       := ( this.value )  
      endwith

      if ( this.value )
         class::changeBase( BASE_DEC )
      endif

   return ( this.value )

   function radioHexOnChange      

      if ( this.value )
         this.form.height := 11.5
         class::changeBase( BASE_HEX )
      else
         this.form.height := 10
      endif  
   return ( this.value )

   function radioOctOnChange

      with ( this.form )
         b8.enabled := ( not this.value )
         b9.enabled := ( not this.value )
      endwith

      if ( this.value )
         class::changeBase( BASE_OCT )
      endif

   return ( this.value )

   function displayFull
      //
      // Check if display already has MAX_DIGITS_DEC digits in it
      //

      local bFull, maxValueLen
      bFull       = false
      maxValueLen = MAX_DIGITS_DEC
      do case
         case ( this.form.numberSys == BASE_HEX )
              maxValueLen := MAX_DIGITS_HEX
         case ( this.form.numberSys == BASE_OCT )
              maxValueLen := MAX_DIGITS_OCT
         otherwise
              maxValueLen := MAX_DIGITS_DEC
      endcase

      // Check if leftmost digit in current display = " "
      bFull := ( not empty( left( right( this.form.display.text, ;
                                maxValueLen ), 1) ) )

   return ( bFull )

   function about
      form.qLang.iMsgBox( "A dBASE Sample Form","About Calculator", 0)
      return

   function moreAbout
      form.qLang.iMsgBox( "This file contains a calculator form.  This calculator contains pushbuttons for numeric input and operations.  It also contains 2 entryfields -- the main display, showing the current calculation, a hidden entry field for storing values to memory. Keyboard input is handled through a paintbox control that is not visible to the user.","About Calculator", 0)
      return

   function addAfterPoint( char )
      //
      // Add fractional digits after decimal point.
      //
      this.form.decPlaces := this.form.decPlaces + 1
      set decimals to this.form.decPlaces
      this.form.mostDecPlaces := max( this.form.decPlaces, ;
                                      this.form.mostDecPlaces )

   return ( class::DisplayValue( this.form.display.text ) + char )

   function buttonClearOnClick
      //
      // Set custom form properties to default values
      // and clear display..
      //
      this.form.operationStack.Initialize() // Empty operation stack         
      set decimals to 1                     // Initial decimal places
      with ( this.form )
           lastValue       := 0        // Clear value
           lastKeyOperator := null     // Clear operators
           decPlaces       := 1  
           mostDecPlaces   := 1
           beforePoint     := true     // Start with whole number entry
           display.text    := SPACE( MAX_DIGITS_DEC - 1 ) + "0"
      endwith   
   return ( this.form.display.text )

   function displayValue( value )
      //
      //  Display value without the pick character.
      //
      local num, pickLoc
      num = value
      pickLoc = at("&",num)

      do case
         case ( pickLoc <> 0 )
              num := stuff(num,pickLoc,1,"")
         case ( right(num,2) = " 0" )
              num := space(MAX_DIGITS_DEC)
         case ( left(num,1) == SPACE(1) )
              num := substr(num,2)
       endcase

   return ( num )

   function open()
     // load international class
     this.qLang = new qLanguage()
     // pass 
     //   the relative location of the dtf folder,
     //   the dtf file 'base' name (will find "calculator_EN.dtf", etc., depending on preferences 
     //   the reference to the current form
     //   the base language - (the one used by the text in the form)
     this.qLang.SetMembersFromDTF( "dtf\" + "calculator", ( this ), "EN" )

     return Super::open()
     return

   function formOnClose
      //
      // Clean up
      //
      CLOSE PROCEDURE PROGRAM(1)
   return ( true )

   function formOnGotFocus
      //
      // Make sure decimal places is correct every time 
      // this form gets focus.
      //
      set decimals to this.decPlaces
   return ( SET("DECIMALS") )

   function keyHandlerOnChar(nChar, nRepCnt, nFlags)
      local sChar, vChar
      sChar = UPPER( CHR( nChar ) )
      vChar = VAL( sChar )
      if ( vChar > 0 AND vChar < this.form.numberSys )
         class::enterDigit( sChar )
      else
         do case
            case ( sChar == "0" )
                 class::enterDigit( sChar )
            case ( sChar == "/" )
                 class::enterOp( this.form.opDiv )
            case ( ( sChar == "*" ) or ;
                   ( sChar == "X" ) )
                 class::enterOp( this.form.opTimes )
            case ( sChar == "-" )
                 class::enterOp( this.form.opMinus )
            case ( sChar == "+" )
                 class::enterOp( this.form.opPlus )
            case ( sChar == "^" )
                 class::enterOp( this.form.opPower )
            case ( ( sChar == "=" ) or ;
                   ( nChar == 13  ) )
                 class::enterOp( this.form.opEqual )
            case ( ( sChar == SETTO("POINT") ) and ;
                   ( this.form.point.enabled ) )
                 class::pointOnClick()
            case ( nChar == KEY_BACKSPACE )
                 class::removeLastDigit()
         endcase
      endif
   return (nChar)

   function removeLastDigit
      //
      // Remove last digit for backspace key
      //
      if ( not this.form.lastKeyOperator )
 
         if ( not ( class::numVal( this.form.display.text ) == 0 ) )
            this.form.display.text := ;
               this.form.display.text.substring( 0, this.form.display.text.length - 1 )
         endif

         if ( class::numVal( this.form.display.text ) == 0 )
            this.form.display.text := SPACE( MAX_DIGITS_DEC - 1 ) + "0"
         endif

      endif

   return true

   function McOnClick      
      //
      // Memory Clear
      //
      local thisForm
      if ( TYPE("this.form") == "O" )
         thisForm = this.form
      else
         thisForm = this
      endif
      with ( thisForm )
         lastKeyOperator     := true
         memory.value        := space(MAX_DIGITS_DEC - 1) + "0"
         textMIndicator.text := null
      endwith
   return ( thisForm.memory.value )

   function MrOnClick
      //
      // Recall from memory.
      //

      if ( this.form.lastKeyOperator )
         with ( this.form )
            lastKeyOperator := false
            beforePoint     := true    
         endwith
      endif
      this.form.lastValue    := class::numVal( this.form.display.text )
      this.form.display.text := this.form.memory.value
   return ( this.form.display.text )

   function formOpen
      set procedure to program(1) additive
      set decimals  to 1 
      // add clipboard entryField
      this.eClip = new EntryField(this)
      this.eClip.visible = false

      // add menu
      this.root = new MenuBar(this)
      this.root.mEdit = new Menu(this.root)
      this.root.mEdit.text := "Edit"
      this.root.mEdit.mCopy = new Menu( this.root.mEdit )
      with ( this.root.mEdit.mCopy )
         text     := "Copy"
         shortCut := "Ctrl+C"
         onClick  := class::copyToClip
      endwith

      this.root.mHelp = new Menu(this.root)
      this.root.mHelp.text := "Help"
      this.root.mHelp.mAbout = new Menu( this.root.mHelp )
      with ( this.root.mHelp.mAbout )
         text     := "About Calculator"
         onClick  := class::about
      endwith
            
      this.root.mHelp.mAbout2 = new Menu( this.root.mHelp )
      with ( this.root.mHelp.mAbout2 )
         text     := "More"
         onClick  := class::MoreAbout
      endwith

      this.height     := 10             // hide hex 
      this.point.text := SETTO("POINT") // Match current country  "," or "."

      this.opPlus.doIt        = { |a,b| ; return ( a + b ) }
      this.opPlus.precedence  = 1

      this.opMinus.doIt       = { |a,b| ; return ( a - b ) }
      this.opMinus.precedence = 1

      this.opTimes.doIt       = { |a,b| ; return ( a * b ) }
      this.opTimes.precedence = 2

      this.opDiv.doIt         = class::opDivideIt
      this.opDiv.precedence   = 2

      this.opPower.doIt       = { |a,b| ; return ( a ^ b ) }
      this.opPower.precedence = 3

      this.opEqual.doIt       = { |a,b| ; return null }
      this.opEqual.precedence = 0

      this.mAdd.doIt          = { |a,b| ; return ( a + b ) }
      this.mAdd.precedence    = 1

      this.mSub.doIt          = { |a,b| ; return ( a - b ) }
      this.mSub.precedence    = 1

      //
      // Create custom form properties
      // 
      // Stack for storing operation states
      this.operationStack = new OperationStackClass()
      this.operationStack.parent = this
      this.operationStack.Initialize() // Empty operation stack         
 
      this.numberSys = BASE_DEC 
      this.decPlaces      = 1    // Start with minimum decimal places
      this.mostDecPlaces  = 1    // Most decimal places in an operand

      this.beforePoint     = true // Add numbers before or after decimal pt.
      this.lastKeyOperator = null
      this.lastValue       = 0        
      this.display.text := SPACE( MAX_DIGITS_DEC - 1 ) + "0"
      class::mcOnClick()

      this.KEYHANDLER.setFocus()
   return ( CALCULATORFORM::OPEN() )

   function copyToClip
      this.parent.parent.parent.eClip.value := ;
         LTRIM( this.parent.parent.parent.display.text )
      this.parent.parent.parent.eClip.selectAll := true
      this.parent.parent.parent.eClip.setFocus()
      this.parent.parent.parent.eClip.copy() 
      this.parent.parent.parent.keyHandler.setFocus()
   return true

   function opDivideIt( a, b )
      local qq,nResult
      nResult = 0
      if ( b == 0 )
         // there is no this here
         qq = new qLanguage()
         // initialize international class
         qq.loadDTF( "dtf\" + "calculator", "EN" )
         qq.iMsgBox("Cannot divide by zero.", "Alert",48)
      else
         nResult := ( a / b )
      endif
   return ( nResult )

   function charVal( num )
      //
      // Convert decimal number to string using 
      // current base: hex, decimal, octal, binary.
      //
      local string, fractionVal
      local nPlace
      local nPValue
      string = ""

      do case
         case ( this.form.numberSys == BASE_HEX )

              if ( ABS(num) >= HEX_OVERFLOW )
                 string := replicate("*", MAX_DIGITS_HEX)
              else
                 string := itoh(num)
                 string := space(MAX_DIGITS_DEC - len(string)) + string
              endif

         case ( this.form.numberSys == BASE_OCT )
              string := class::toBaseString( num, 16, BASE_OCT )

         case ( this.form.numberSys == BASE_BIN )
              string := class::toBaseString( num, 16, BASE_BIN )

         case ( this.form.numberSys == BASE_DEC )
              if ( ABS(num) >= DEC_OVERFLOW )
                 string := replicate("*", MAX_DIGITS_DEC)
              else
                 string := class::decimalShowString( num )
              endif

      endcase
   return ( string )

   function decimalShowString( n )
      local nDec, sInt, sMask, sVal     
      nDec = SET("DECIMALS")
      sInt  = ""
      sMask = ""
      sVal  = ""
      SET DECIMALS TO 18

      if ( n < 0 ) and ( n > -1 )
         sInt := "-0"
      else
         sInt := int( n )+"".rightTrim()
      endif

      sMask := replicate("9", sInt.length ) + "." + ;
                 replicate("9", ( 14 - sInt.length ) )
      sVal := TRANSFORM(n,sMask)

      do while ( SETTO("POINT") $ sVal ) and ( sVal.right(1) == "0" )
         sVal := sVal.substring( 0, sVal.length - 1 )
      enddo

      if sVal.right(1) == SETTO("POINT")
         sVal := sVal.substring( 0, sVal.length - 1 )
      endif

      SET DECIMALS TO ( nDec )
   return ( sVal )

   function toBaseString( nDecValue, nMaxPlace, nBase )
      local nPlace
      local npValue
      local nAdd
      local sValue
      local bStart
      local bCarry

      nPlace  = nMaxPlace
      npValue = 0
      sValue  = ""
      bStart  = false
      if ( ABS( nDecValue ) >= nBase ^ nPlace )
         sValue = REPLICATE("*", nMaxPlace )
      else                
         do while ( nPlace >= 0 )
            if ( nDecValue == 0 )
               sValue := sValue + "0"
            else
               npValue := ( nBase ^ nPlace )
               if ( ( npValue <= nDecValue ) or ;
                    ( npValue == 1 ) )
                  bStart := true
                  if ( nDecValue / npValue ) == nBase
                     sValue := sValue + "10"
                  else
                     sValue := sValue + ;
                              ALLTRIM( STR( INT( nDecValue / npValue )  ) )
                  endif
                  nDecValue := MOD( nDecValue, npValue )
               else
                  if ( bStart )
                     sValue := sValue + "0"
                  endif
               endif
            endif
            nPlace := nPlace - 1
         enddo
         if ( VAL( sValue ) == 0 )
            sValue := "0"
         endif
         sValue := SPACE( MAX_DIGITS_DEC - LEN( sValue ) ) + sValue
      endif
   return ( sValue )

   function numVal( string )
      //
      // Return decimal value of display value using
      // current base to translate.
      //
      local h, num, nPoint
      local sValue
      local nDigit, nPlace
      private macro

      sValue  = ALLTRIM( string )
      h       = ""
      macro   = ""
      num     = 0
      nDigit  = 0
      nPlace  = 0
      nPoint  = 0

      do case

         case ( this.form.numberSys == BASE_HEX )

              h   := htoi(string)
              num := iif(h >= HEX_OVERFLOW/2, bitxor(h, HEX_OVERFLOW), h)

         case ( this.form.numberSys == BASE_OCT )

              if ( sValue == null )
                 num := 0
              else
                 if ( SUBSTR( sValue, 1, 1 ) == "*" )
                    num := ( 1 / 0 )
                 else
                    macro := "0" + sValue
                    num := &macro.
                 endif
              endif

         case ( this.form.numberSys == BASE_BIN )

              do while ( LEN( sValue ) > 0 )
                 nDigit := IIF( RIGHT( sValue, 1 ) == "1", 1, 0 )
                 sValue := SUBSTR( sValue, 1, ( LEN( sValue ) - 1 ) )
                 num    := num + ( nDigit * (2 ^ nPlace) )
                 nPlace := nPlace + 1
              enddo

         otherwise            
              // decimal
              nPoint := AT( SETTO("POINT"), sValue )

              if ( nPoint <> 0  )               
                 num := VAL( STUFF( sValue, nPoint, 1, SETTO("POINT") ) )
              else
                 num := VAL( sValue )
              endif

      endcase
    return ( num )

endclass

class OperationStackClass
   //
   // This class defines a stack object for storing 
   // mathematical operation states.  This is necessary 
   // for using operators that have different precedence, like
   // + and *.  This is a stack of OperationStateClass objects.
   //
   
   class::initialize()


   function initialize
      //
      // Create empty stack
      //

      // Bottom (and top, at first) of Stack
      this.stackTop = new OperationStateClass()
   return true

   function isEmpty
      //
      // Check if stack is empty
      //

   return empty( this.stackTop.getNext() ) // Empty if no more operations


   function pop
      //
      // Retrieve last operation state
      //
      local xOp
      xOp = false

      if ( not empty(this.stackTop.getNext()) )
         xOp := this.stackTop
         this.stackTop = this.stackTop.getNext()
      endif

   return xOp


   function push( operation )
      //
      // Save last operation state
      //
      local newStackTop

      newStackTop = new operationStateClass()  // Create new operationState
      newStackTop.assign( operation )          // Assign to it properties of
                                               // operation
      newStackTop.setNext( this.stackTop )     // Make current top be next

      this.stackTop = newStackTop              // Make new operation be top

   return true


   function prevPrecedenceGreaterOrEqual(curPrecedence)
      //
      // Retrieve last operation state
      //
   return ( this.stackTop.getPrecedence() >= curPrecedence )

endclass

class OperationStateClass
   //
   // This class defines a single operation state, which is to 
   // be stored on the operationStack
   //

   this.op         = { |a,b| ; return null } 
   this.precedence = -1              
   this.opValue    = 0
   this.next       = false

   function getOp
   return ( this.op )


   function setOp( newOp )
      this.op := newOp
   return ( this.op )


   function getPrecedence
   return ( this.precedence )


   function setPrecedence( newPrecedence )
      this.precedence := newPrecedence
   return ( this.precedence )


   function getValue
   return ( this.opValue )

   function setValue( newValue )
      this.opValue := newValue
   return ( this.opValue )


   function getNext
   return ( this.next )

   function setNext(newNext)
      this.next := newNext
   return ( this.next )

   function assign( newOperationState )
      with ( this )
         op         := newOperationState.doIt
         precedence := newOperationState.precedence
         opValue    := newOperationState.opValue
      endwith
   return ( this.opValue )

endclass
