Παρασκευή 4 Σεπτεμβρίου 2020

Converter Metric to and from U.S. (OOP example + GUI example)

First we will see the OOP part. We have a class Converter which make two objects, Metric and US. Because each one need some literals, strings and numeric, we use two stack objects, which each hold a series of data. Using the symbol ! before a stack object we place the items of stack object, not the object to the calling stack. The items from stack objects just moved to the new stack, so d1 and d2 would have zero length just at the call to class constructor, for each one. We will see a Part 2 which we can use it

Part I



Let d1=Stack, d2=Stack
Stack d1 {
      data "Metric units", 1, 3
      data .01, "Centimeters", "cm", 1, "Meters", "m", 1000,"Kilometers", "km"
}
Stack d2 {
      data "U.S. units", .3048, 4
      data 0.0254,"Inches","in", 0.3048, "Feet", "ft", 0.91441, "Yards", "yd", 1613, "Miles","mi"
}


Class Converter {
private:
      MUnit$, MUsmall$
      Unit=1
      inventory Units
public:
      Final msg$="{0}{1} converted to {2}{3}"
      Property LastUnit${Value}=""
      Function Convert2Meters(V as double, Unit$="Unit", rd=-1) {
            .[LastUnit]$<="m"
            if rd<0 then
                  =V*.Units(Ucase$(Unit$))(0)
            else
                  =Round(V*.Units(Ucase$(Unit$))(0), rd)
            end if
      }
      Function GetSmall$(Unit$="Unit") {
            =.Units$(Ucase$(Unit$))(1)
      }
      Function Convert(Val,Unit1$,To, ToPrint=false) {
            nVal=.Convert2Meters(Val,Unit1$)
            fVal=To(nVal)
            if ToPrint then
                  print Format$(.msg$, Val, .GetSmall$(Unit1$), fVal, To.Unit$)
            end if
            =fVal
      }
      Function ConvertFunc(Unit1$, t) {
            U=.Units(Ucase$(Unit1$))(0)*t(1)
            =lambda U (Val, rd=-1) -> {
                  if rd<0 then
                        =Val*U
                  else
                        =round(Val*U,rd)
                  end if
            }
      }      
      Module ShowConvertion(Val,Unit1$,To) {
            nVal=.Convert2Meters(Val,Unit1$)
            print Format$(.msg$, Val, .GetSmall$(Unit1$), To(nVal), To.Unit$)
      }
      Value (Unit$) {
            Try Ok {
                  f=.Units(Ucase$(Unit$))(0)
            }
            if Error or Not Ok then Error "No such unit:"+Unit$
            if f==0 then Error "Something wrong with unit:"+Unit$
            Group ret {
            Private:
                  factor=f
            Public:
                  Unit$
                  Value (V) {
                        =V/.factor
                  }
            }
            ret.Unit$=.GetSmall$(Unit$)
            =Group(Ret)
      }
Class:
      Module Converter {
            Def uVal as double=1
            read .Name$,.Unit, M
            For i=1 to M
                  read uVal, Unit$, usmall$
                  if uVal==.Unit then
                        .MUnit$<=Unit$
                        .MUsmall$<=usmall$
                        Append .Units,"UNIT":=(.Unit, usmall$)
                  End if
                  Append .Units,Ucase$(Unit$):=(uVal, usmall$)
            Next i
      }
}
Metric=Converter(!d1)
US=Converter(!d2)



Copy this code after part1  to a module Conv1



Inches=Metric.Convert(75,"Centimeters", US("Inches"))
US.ShowConvertion Inches
,"Inches", Metric("Centimeters")
US.ShowConvertion Inches
,"Inches", US("feet")
US.ShowConvertion
100,"Miles", US("feet")
Metric.ShowConvertion
200, "Kilometers", US("Miles")
Metric.ShowConvertion
20, "Kilometers", Metric("Meters")
Miles
=Metric.Convert(500,"Meters", US("Miles"), True)
Print miles;"mi"
Yards
=Metric.Convert(500,"Meters", US("Yards"), True)
Print Yards;"yd"
\\ We can make lambda functions for faster calculation
AnyCentimeterToInches
=Metric.ConvertFunc("Centimeters", US("Inches"))
Print AnyCentimeterToInches(75, 3);"in"  ' 29.528  Inches
Print AnyCentimeterToInches(30, 2);"in"  ' 11.81   ' Inches
Print "Kilometers to Miles"
AnyKilometersToMiles
=Metric.ConvertFunc("Kilometers", US("Miles"))
For i=10 to 180 step 10
      Print i,"km  ->", AnyKilometersToMiles(i, 0),"mi"
next i


The idea is to make a lambda function every time we decide the names of units from one object to other. The converter object return another object (look the value member), which need the name of the unit as a parameter. So US("Miles") returtn an object, but not a converter object. This object also has a value and need a parameter too. Because we want to read another value, we use a group and not a lambda function. Final the ConvertFunc()  function return a lambda function which combine the factors to make a fast multiplication (without using objects).

The last part, the Converter.

Now copy the part one and the code below to a new module say Conv2. Now we place a user interface, and we can insert in two text boxes values, and we can change from dropboxes the units for each system. Also a thread show the time (and refresh the status bar).


Function Local2() is an updated function from info big file (info.gsb), which included in M2000 setup. This used to validate the input string. Validation done without the decimal point. This attatched after this stage. The validation for decimal point happen before the call to Local2() on the ValidString event for each TextBox. A Call Local calls a function as a sub, so we place the same name space (here the Conv2 module is the current name space).


Use Locale 1033 for engilsh or Locale 1032 for greek as first statement. This control the decimal point character, so 1032 set "," (use Monitor statement in M2000 console, to see the state of M2000 Environment).



def SkipValidation1=True, SkipValidation2=True
declare Convertion form
declare TEXTBOX1 TEXTBOX form Convertion
declare COMBO1 COMBOBOX form Convertion
declare TEXTBOX2 TEXTBOX form Convertion
declare COMBO2 COMBOBOX form Convertion
layer Convertion {
      LineSpace 60
      window 10, 12000, 5000;
      Cls 1, 0
      gradient #aa9933,1
      totalwidth=scale.x
}
boxwidth
=totalwidth-2000
method TEXTBOX1, "MOVE", 1000, 1000, boxwidth
method COMBO1, "MOVE", 1000, 1800, boxwidth
method TEXTBOX2, "MOVE", 1000, 2600, boxwidth
method COMBO2, "MOVE", 1000, 3400, boxwidth
with Convertion, "TITLE", "M2000 Example: Converter "
\\ ADJUST PROPERTIES FOR CONTROLS
\\ FROM SOME PROPERTIES  WE WANT SOME IDENTIFIERS TO BOUND TO
with COMBO1, "EDIT", false,"UseOnlyTheList", true,"ShowAlways", false
with COMBO1, "ListText" as ITEM1$, "Text" as COMBO1$, "label","Units:"
ITEM1$
={Centimeters
            Meters
            Kilometers
            }
COMBO1$
="Meters"
with COMBO2, "EDIT", false,"UseOnlyTheList", true,"ShowAlways", false
with COMBO2, "ListText" as ITEM2$, "Text" as COMBO2$, "label","Units:"
ITEM2$
={Inches
            Feet
            Yards
            Miles
            }
COMBO2$
="Yards"
with TEXTBOX1, "Prompt", "Metric: ", "VarText" as textbox1.value$
with TEXTBOX1, "ThisKind" as TK1$,"ShowAlways", true
TK1$
=" "+Metric.GetSmall$(COMBO1$)
textbox1.value$
="0"
with TEXTBOX2, "Prompt", "U.S.: ", "VarText" as textbox2.value$
with TEXTBOX2, "ThisKind" as TK2$,"ShowAlways", true
TK2$
=" "+US.GetSmall$(COMBO2$)
textbox2.value$
="0"
const fstr$="{0} <=> {1}"
caption$
=format$(fstr$, combo1$, combo2$)
FromText1
=Metric.ConvertFunc(Combo1$, US(combo2$))
FromText2
=US.ConvertFunc(Combo2$, Metric(combo1$))
Function Local2(new Feed$, z) {
      \\ this Function can be used from other Integer
      \\ this$ and thispos, exist just before the call of this Function
      local sgn$
      if feed$="" and this$="-" then thispos-- : exit
      if left$(this$,1)="-" then sgn$="-": this$=mid$(this$, 2)
      if this$<>Trim$(this$) then this$=Feed$ : thispos-- : exit
      if Trim$(this$)="" then this$="0" : thispos=2 : exit
      if instr(this$,"+")>0 and sgn$="-" then this$=filter$(this$, "+") : sgn$=""
      if instr(this$,"-")>0 and sgn$="" then this$=filter$(this$, "-") : sgn$="-"
      if filter$(this$,"0123456789")<>"" then this$=Feed$ : thispos-- : exit
      if len(this$)>1 then While left$(this$,1)="0" {this$=mid$(this$, 2)}
      this$=sgn$+this$
      if Z<>0 then FEED$=this$: INSERT Z FEED$=de$: this$=FEED$: thispos++
      if this$="-0" then this$="-" : thispos=2
      }
Function 
TextBox1.ValidString {
      \\ this Function called direct from textbox
      read new &this$, &thispos
      if SkipValidation1 then exit function
      local de$=locale$(14)
      this$=replace$(de$, ".", this$)
      local Z=INSTR(this$,"."), K$
      if Z>0 then
            K$=FILTER$(this$, ".")
            if LEN(K$)+2<LEN(textbox1.value$) then
                  this$=textbox1.value$
                  this$=replace$(de$, ".", this$)
                  Z=INSTR(this$, ".")
                  K$=FILTER$(this$, ".")
            END if
            if z>0 then thispos--
            this$=K$
      End if
      call local local2(textbox1.value$, Z)
}
Function 
TextBox2.ValidString {
      \\ this Function called direct from textbox
      read new &this$, &thispos
      if SkipValidation2 then exit function
      local de$=locale$(14)
      this$=replace$(de$, ".", this$)
      local Z=INSTR(this$, "."), K$
      if Z>0 then
            K$=FILTER$(this$, ".")
            if LEN(K$)+2<LEN(textbox2.value$) then
                  this$=textbox2.value$
                  this$=replace$(de$, ".", this$)
                  Z=INSTR(this$, de$)
                  K$=FILTER$(this$, de$)
            END if
            if z>0 then thispos--
            this$=K$
      End if
      call local local2(textbox2.value$, Z)
}
Function Combo1.dblclick {
      caption$=format$(fstr$,combo1$, combo2$)
      FromText1=Metric.ConvertFunc(Combo1$, US(combo2$))
      FromText2=US.ConvertFunc(Combo2$, Metric(combo1$))
      call local TextBox2.Enter()
      TK1$=" "+Metric.GetSmall$(COMBO1$)
      method Convertion, "refreshall"
}
Function Combo2.dblclick {
      caption$=format$(fstr$, combo1$, combo2$)
      FromText1=Metric.ConvertFunc(Combo1$, US(combo2$))
      FromText2=US.ConvertFunc(Combo2$, Metric(combo1$))
      call local TextBox1.Enter()
      TK2$=" "+US.GetSmall$(COMBO2$)
      method Convertion, "refreshall"
}
Function TextBox1.Enter {
      SkipValidation2~
      textbox2.value$=str$(Fromtext1(val(textbox1.value$, locale$(14))),"")
      SkipValidation2~
      clipboard textbox2.value$+" "+combo2$
}
Function TextBox2.Enter {
      SkipValidation1~
      textbox1.value$=str$(Fromtext2(val(textbox2.value$, locale$(14))),"")
      SkipValidation1~
      clipboard textbox1.value$+" "+combo1$
}
SkipValidation1
=False
SkipValidation2
=False
layer Convertion {
      thread {
            cursor 0, Height-1
            print over $(7, width),~(#FFFFFF), Caption$+" | "+str$(now,"long time")
            print part $(4, width),~(#FFFFFF), "George Karras 2020"
      } as T1 interval 1000/60
}
method Convertion, "SHOW", 1
threads Erase
declare Convertion nothing


George Karras




Δεν υπάρχουν σχόλια:

Δημοσίευση σχολίου

You can feel free to write any suggestion, or idea on the subject.