tests

tests

USING System.Globalization

function start as void
    local p as psz
    p := String2Psz("abc")
    p := StringAlloc("def")
    MemFree(p)
	wait

FUNCTION Startx1 AS VOID
    setinternational ( #windows )
? "setinternational ( #windows )"
?
// set the default to true, and it´s in sync with the dateformat at startup.
// setcentury(TRUE) // <--------------------

? "Current SetCentury() default setting is " + setcentury() + " , should be TRUE"
? GetDateFormat()
?
setcentury( FALSE )
? "Setcentury(false)"
? GetDateFormat() + _chr(9) + "only ok, if it shows 'DD.MM.YY'"
?
setcentury( TRUE )
? "Setcentury(true)"
? GetDateFormat()
?
setcentury( FALSE )
? "Setcentury(false)"
? GetDateFormat()
?
setcentury( TRUE )
? "Setcentury(true)"
? GetDateFormat()
?
?
?
? "The initial Setinternational (#clipper ) behaviour is already compatible with VO"
SetInternational( #clipper)
? "SetInternational( #clipper)"

SetDatecountry ( 5 ) // "GERMAN" define in VO.
?
? Getdateformat() // "DD.MM.YY"
? setcentury() // false
?
setcentury( TRUE )
? "Setcentury(true)"
? GetDateFormat()
?
setcentury( FALSE )
? "Setcentury(false)"
? GetDateFormat()
?
// switch back to #Windows
setinternational ( #windows )
? "setinternational ( #windows )"
?
? "Current SetCentury() default setting is " + setcentury() + " , should be TRUE"
? GetDateFormat()
wait
RETURN

FUNCTION Start2a AS VOID
    LOCAL o AS OBJECT[]
    LOCAL a AS ARRAY
	a := {"aa", "bb", {1,2,3}}
    o := a
    a := o
	? o
    ? a
    Console.Read()
    RETURN

FUNCTION StartX AS VOID
	LOCAL cA, cB AS STRING
	LOCAL nI, nMax AS INT64
	LOCAL lOk AS LOGIC
	LOCAL p AS PTR
	p := MemAlloc(100)
	MemFree(p)
	SetInternational(#Clipper)
	SetNatDLL("ITALIAN")
	? DateTime.Now:DayOfWeek:ToString()
	? (INT) DateTime.Now:DayOfWeek
	? DOW(Today())
	? CDOW(Today())
	? CMonth(Today())
	wait

	nMax := 10000000// 40000000
	cA := "THE QUICK BROWN FOX JUMP OVER THE LAZY DOG"
	//cB := Left(CA, Slen(Ca)-1)+Lower(right(cA,1))
	cb := lower(ca)
	LOCAL aColl AS STRING[]
	aColl := <STRING>{"Windows", "Clipper","Unicode", "Ordinal"}
	FOREACH VAR s IN aColl
		SetCollation(s)
		LOCAL nSecs AS FLOAT
		nSecs := Seconds()
		FOR nI := 1 TO nMax
			lOk := cA <= cB
		NEXT
		? SetCollation(), transform(nMax,"999,999,999"), seconds() - nSecs
	NEXT
	_wait()
	RETURN

FUNCTION Start2 AS VOID
	LOCAL aDir AS ARRAY
	aDir := Directory("C:\XSharp\DevRt\Runtime", "AHSD")
	FOREACH VAR aFile IN aDir
		? aFile[1], aFile[2], aFile[3], aFile[4], aFile[5]
	NEXT
	LOCAL aFiles AS ARRAY

	aFiles := ArrayCreate(ADir("C:\XSharp\DevRt\Runtime\XSharp.Core\*.prg"))

	ADir("C:\XSharp\DevRt\Runtime\XSharp.Core\*.prg", aFiles)

	AEval(aFiles, {|element| QOut(element)})

	_Wait()
	RETURN

FUNCTION StartA() AS VOID
	LOCAL nX AS DWORD
	CultureInfo.DefaultThreadCurrentCulture := CultureInfo{"EN-us"}
	CultureInfo.DefaultThreadCurrentUICulture := CultureInfo{"EN-us"}

	? Version()
	? "Size of IntPtr", IntPtr.Size
	? "Size of USUAL", SIZEOF(USUAL)
	? "Size of FLOAT", SIZEOF(FLOAT)
	? "Size of DATE", SIZEOF(DATE)
	? "Size of SYMBOL", SIZEOF(SYMBOL)
	?
	? 1.234:ToString()
	? SetNatDLL("Dutch.DLL")
	? GetNatDLL()
	? GetAppLocaleID()
	? SetAppLocaleID(1043)
	? GetAppLocaleID()
	? 1.234:ToString()
	? __CavoStr(VOErrors.TMSG_PRESSANYKEY)
	? VO_Sprintf(VOErrors.__WCSLOADLIBRARYERROR, "CaTo3Cnt.DLL")
	? DosErrString(2)
	? ErrString(EG_ARG)
	Console.WriteLine("")
	LOCAL mem,mem2 AS INT64
	mem := GC.GetTotalMemory(TRUE)

	LOCAL a AS ARRAY
	a := ArrayCreate(1000000)

	mem2 := GC.GetTotalMemory(TRUE) - mem
	mem := mem2
	? "Memory for 1M element ARRAY", mem:ToString("###,### bytes")
	FOR nX := 1 TO 1000000
		a[nX] := 1
	NEXT
	? "Memory for 1M element ARRAY after assigning 1M values", mem:ToString("###,### bytes")
	a := NULL_ARRAY
	GC.Collect()
	a := ArrayCreate(0)
	mem := GC.GetTotalMemory(TRUE)
	FOR nX := 1 TO 1000000
		aadd(a, 1)
	NEXT
	mem2 := GC.GetTotalMemory(TRUE) - mem
	mem := mem2
	? "Memory for 1M element ARRAY after assigning 1M values with AAdd", mem:ToString("###,### bytes")

	TestUsualFloat()
	TestDate()
	GC.KeepAlive(a)

    _wait()
    RETURN

PROCEDURE TestUsualFloat()
	LOCAL u1,u2 AS USUAL
	LOCAL f1,f2 AS FLOAT

    ? "Testing USUAL & Float"
	? "25.000.000 iterations"

	LOCAL d AS DateTime
	d := DateTime.Now

	FOR LOCAL n := 1 AS INT UPTO 25000000
		u1 := 1.1d
		f1 := 1.3d
		u2 := u1 + f1
		f2 := u1 + u2 + f1
		f1 := f2 + u1
		f2 := f2 + u2
	NEXT

	? "Time elapsed:", (DateTime.Now - d):ToString()
	?
    RETURN

PROCEDURE testDate()
	LOCAL d1, d2 AS DATE
    ? "Testing DATE"
	? "25.000.000 iterations"

	LOCAL d AS DateTime
	d := DateTime.Now

	FOR LOCAL n := 1 AS INT UPTO 25000000
		d1 := 2018.04.15
		d2 := d1+1
		d1 := d2

	NEXT

	? "Time elapsed:", (DateTime.Now - d):ToString()
	?
    RETURN

USING System.Globalization

FUNCTION Start AS VOID
    setinternational ( #windows )
? "setinternational ( #windows )"
?
// set the default to true, and it´s in sync with the dateformat at startup.
// setcentury(TRUE) // <--------------------

? "Current SetCentury() default setting is " + setcentury() + " , should be TRUE"
? GetDateFormat()
?
setcentury( FALSE )
? "Setcentury(false)"
? GetDateFormat() + _chr(9) + "only ok, if it shows 'DD.MM.YY'"
?
setcentury( TRUE )
? "Setcentury(true)"
? GetDateFormat()
?
setcentury( FALSE )
? "Setcentury(false)"
? GetDateFormat()
?
setcentury( TRUE )
? "Setcentury(true)"
? GetDateFormat()
?
?
?
? "The initial Setinternational (#clipper ) behaviour is already compatible with VO"
SetInternational( #clipper)
? "SetInternational( #clipper)"

SetDatecountry ( 5 ) // "GERMAN" define in VO.
?
? Getdateformat() // "DD.MM.YY"
? setcentury() // false
?
setcentury( TRUE )
? "Setcentury(true)"
? GetDateFormat()
?
setcentury( FALSE )
? "Setcentury(false)"
? GetDateFormat()
?
// switch back to #Windows
setinternational ( #windows )
? "setinternational ( #windows )"
?
? "Current SetCentury() default setting is " + setcentury() + " , should be TRUE"
? GetDateFormat()
wait
RETURN

FUNCTION Start2a AS VOID
    LOCAL o AS OBJECT[]
    LOCAL a AS ARRAY
	a := {"aa", "bb", {1,2,3}}
    o := a
    a := o
	? o
    ? a
    Console.Read()
    RETURN

FUNCTION StartX AS VOID
	LOCAL cA, cB AS STRING
	LOCAL nI, nMax AS INT64
	LOCAL lOk AS LOGIC
	LOCAL p AS PTR
	p := MemAlloc(100)
	MemFree(p)
	SetInternational(#Clipper)
	SetNatDLL("ITALIAN")
	? DateTime.Now:DayOfWeek:ToString()
	? (INT) DateTime.Now:DayOfWeek
	? DOW(Today())
	? CDOW(Today())
	? CMonth(Today())
	wait

	nMax := 10000000// 40000000
	cA := "THE QUICK BROWN FOX JUMP OVER THE LAZY DOG"
	//cB := Left(CA, Slen(Ca)-1)+Lower(right(cA,1))
	cb := lower(ca)
	LOCAL aColl AS STRING[]
	aColl := <STRING>{"Windows", "Clipper","Unicode", "Ordinal"}
	FOREACH VAR s IN aColl
		SetCollation(s)
		LOCAL nSecs AS FLOAT
		nSecs := Seconds()
		FOR nI := 1 TO nMax
			lOk := cA <= cB
		NEXT
		? SetCollation(), transform(nMax,"999,999,999"), seconds() - nSecs
	NEXT
	_wait()
	RETURN

FUNCTION Start2 AS VOID
	LOCAL aDir AS ARRAY
	aDir := Directory("C:\XSharp\DevRt\Runtime", "AHSD")
	FOREACH VAR aFile IN aDir
		? aFile[1], aFile[2], aFile[3], aFile[4], aFile[5]
	NEXT
	LOCAL aFiles AS ARRAY

	aFiles := ArrayCreate(ADir("C:\XSharp\DevRt\Runtime\XSharp.Core\*.prg"))

	ADir("C:\XSharp\DevRt\Runtime\XSharp.Core\*.prg", aFiles)

	AEval(aFiles, {|element| QOut(element)})

	_Wait()
	RETURN

FUNCTION StartA() AS VOID
	LOCAL nX AS DWORD
	CultureInfo.DefaultThreadCurrentCulture := CultureInfo{"EN-us"}
	CultureInfo.DefaultThreadCurrentUICulture := CultureInfo{"EN-us"}

	? Version()
	? "Size of IntPtr", IntPtr.Size
	? "Size of USUAL", SIZEOF(USUAL)
	? "Size of FLOAT", SIZEOF(FLOAT)
	? "Size of DATE", SIZEOF(DATE)
	? "Size of SYMBOL", SIZEOF(SYMBOL)
	?
	? 1.234:ToString()
	? SetNatDLL("Dutch.DLL")
	? GetNatDLL()
	? GetAppLocaleID()
	? SetAppLocaleID(1043)
	? GetAppLocaleID()
	? 1.234:ToString()
	? __CavoStr(VOErrors.TMSG_PRESSANYKEY)
	? VO_Sprintf(VOErrors.__WCSLOADLIBRARYERROR, "CaTo3Cnt.DLL")
	? DosErrString(2)
	? ErrString(EG_ARG)
	Console.WriteLine("")
	LOCAL mem,mem2 AS INT64
	mem := GC.GetTotalMemory(TRUE)

	LOCAL a AS ARRAY
	a := ArrayCreate(1000000)

	mem2 := GC.GetTotalMemory(TRUE) - mem
	mem := mem2
	? "Memory for 1M element ARRAY", mem:ToString("###,### bytes")
	FOR nX := 1 TO 1000000
		a[nX] := 1
	NEXT
	? "Memory for 1M element ARRAY after assigning 1M values", mem:ToString("###,### bytes")
	a := NULL_ARRAY
	GC.Collect()
	a := ArrayCreate(0)
	mem := GC.GetTotalMemory(TRUE)
	FOR nX := 1 TO 1000000
		aadd(a, 1)
	NEXT
	mem2 := GC.GetTotalMemory(TRUE) - mem
	mem := mem2
	? "Memory for 1M element ARRAY after assigning 1M values with AAdd", mem:ToString("###,### bytes")

	TestUsualFloat()
	TestDate()
	GC.KeepAlive(a)

    _wait()
    RETURN

PROCEDURE TestUsualFloat()
	LOCAL u1,u2 AS USUAL
	LOCAL f1,f2 AS FLOAT

    ? "Testing USUAL & Float"
	? "25.000.000 iterations"

	LOCAL d AS DateTime
	d := DateTime.Now

	FOR LOCAL n := 1 AS INT UPTO 25000000
		u1 := 1.1d
		f1 := 1.3d
		u2 := u1 + f1
		f2 := u1 + u2 + f1
		f1 := f2 + u1
		f2 := f2 + u2
	NEXT

	? "Time elapsed:", (DateTime.Now - d):ToString()
	?
    RETURN

PROCEDURE testDate()
	LOCAL d1, d2 AS DATE
    ? "Testing DATE"
	? "25.000.000 iterations"

	LOCAL d AS DateTime
	d := DateTime.Now

	FOR LOCAL n := 1 AS INT UPTO 25000000
		d1 := 2018.04.15
		d2 := d1+1
		d1 := d2

	NEXT

	? "Time elapsed:", (DateTime.Now - d):ToString()
	?
    RETURN


small changes

small changes

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//
// Most of these settings will Get and Set properties of the Runtime.State class

#command GETSTATE <type> <set>	      =>				 ;
		RETURN XSharp.Runtime.State.GetInstance():GetValue\< <type> \>( <set> )

#command SETSTATE <type> <set>	<value> =>				 ;
		LOCAL oldValue as <type>						 ;;
		VAR oState := XSharp.Runtime.State.GetInstance() ;;
		BEGIN LOCK oState								 ;;
			oldValue := oState:GetValue\<<type>\>(<set>) ;;
			oState:SetValue(<set> , <value> )			 ;;
		END LOCK										 ;;
		RETURN oldValue

	/// <summary>
	/// Returns a string representing the morning extension for time strings in 12-hour format.
	/// </summary>
	/// <returns>
	/// </returns>
	FUNCTION GetAMExt() AS STRING
		RETURN XSharp.Runtime.State.GetInstance():GetValue<STRING>( Set.AmExt )

	/// <summary>
	/// Returns a string representing the morning extension for time strings in 12-hour format.
	/// </summary>
	/// <returns>
	/// </returns>
	FUNCTION SetAMExt() AS STRING
		RETURN GetAmExt()

	/// <summary>
	/// Set the morning extension for time strings in 12-hour format.
	/// </summary>
	/// <param name="cExt"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetAMExt(cExt AS STRING) AS STRING
		LOCAL oldValue as STRING
		VAR oState := XSharp.Runtime.State.GetInstance()
		BEGIN LOCK oState
			oldValue := oState:GetValue<STRING>(Set.AmExt)
			oState:SetValue(Set.AmExt , cExt )
		END LOCK
		RETURN oldValue

	/// <summary>
	/// Returns the setting that determines whether time strings are in 12-hour or 24-hour format.
	/// </summary>
	/// <returns>
	/// </returns>
	FUNCTION GetAmPm() AS LOGIC
		RETURN XSharp.Runtime.State.GetInstance():GetValue<LOGIC>( Set.AmPm )

	/// <summary>
	/// Return and optionally change the setting that determines whether time strings are in 12-hour or 24-hour format.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetAmPm(lSet AS LOGIC) AS LOGIC
		LOCAL oldValue as LOGIC
		VAR oState := XSharp.Runtime.State.GetInstance()
		BEGIN LOCK oState
			oldValue := oState:GetValue<LOGIC>(Set.AmPm)
			oState:SetValue(Set.AmPm, lSet)
		END LOCK
		RETURN oldValue

	/// <summary>
	/// Return and optionally change the setting that determines whether database files are created using ANSI or OEM format and whether certain text file operations convert between the two character sets.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetAnsi(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Sets the locale that the runtime uses for comparing strings when running in Windows collation mode (SetCollation(#Windows)).
	/// </summary>
	/// <param name="dwLocaleId"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetAppLocaleID(dwLocaleId AS DWORD) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Return and optionally change the setting that determines whether a beep is sounded by the error system when an error occurs.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetBeep(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that determines whether to include or omit century digits in the date format.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetCentury(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// </summary>
	/// <param name="pFunc"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetClipCompFunc(pFunc AS OBJECT) AS IntPtr
		/// THROW NotImplementedException{}
	RETURN IntPtr.Zero

	/// <summary>
	/// Return and optionally change the setting that determines the type of central processor you have.
	/// </summary>
	/// <param name="nCpu"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetCpu(nCpu AS OBJECT) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Return and optionally change the setting that determines the <%APP%> date format by selecting from a list of constants with corresponding date formats.
	/// </summary>
	/// <param name="dwCountry"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDateCountry(dwCountry AS OBJECT) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Return the current __VODate format.
	/// </summary>
	/// <returns>
	/// </returns>
	FUNCTION GetDateFormat() AS STRING
		RETURN XSharp.Runtime.State.GetInstance():GetValue<STRING>(Set.DateFormat)

	/// <summary>
	/// Change the setting that determines the <%APP%> date format.
	/// </summary>
	/// <param name="cDateFormat"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDateFormat(cDateFormat AS STRING) AS STRING
		LOCAL oldValue as STRING
		VAR oState := XSharp.Runtime.State.GetInstance()
		BEGIN LOCK oState
			oldValue := oState:GetValue<STRING>(Set.DateFormat)
			oState:SetValue(Set.DateFormat, cDateFormat)
		END LOCK
	RETURN oldValue

	/// <summary>
	/// Return and optionally change the setting that determines the number of decimal places used to display numbers.
	/// </summary>
	/// <param name="nDec"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDecimal(nDec AS DWORD) AS DWORD
		RETURN XSharp.Runtime.State.GetInstance():GetValue<DWORD>(Set.Decimals)

	/// <summary>
	/// Return and optionally change the setting that determines the decimal separation character to be used in numeric-to-string conversion functions.
	/// </summary>
	/// <param name="wSep"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDecimalSep(wSep AS OBJECT) AS WORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Change the setting that determines the <%APP%> default drive and directory.
	/// </summary>
	/// <param name="cDefault"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDefault(cDefault AS STRING) AS VOID
		/// THROW NotImplementedException{}
	RETURN

	/// <summary>
	/// Change the setting that determines the location of the error log file.
	/// </summary>
	/// <param name="pszNew"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDefaultDir(strNew AS STRING) AS VOID
		/// THROW NotImplementedException{}
	RETURN

	/// <summary>
	/// Return and optionally change the setting that determines whether to ignore or include records that are marked for deletion.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDeleted(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that determines the number of digits that will be shown to the left of the decimal point when a number is displayed.
	/// </summary>
	/// <param name="nDig"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDigit(nDig AS OBJECT) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Return and optionally change the setting that fixes the number of digits used to display numeric output.
	/// </summary>
	/// <param name="f"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDigitFixed(f AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Update or replace the contents of a DOS environment variable.
	/// </summary>
	/// <param name="cVar"></param>
	/// <param name="cValue"></param>
	/// <param name="lAppend"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetEnv(cVar AS STRING,cValue AS STRING,lAppend AS LOGIC) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that determines how dates without century digits are interpreted.
	/// </summary>
	/// <param name="wYear"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetEpoch(wYear AS OBJECT) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Return and optionally change the setting that determines whether error information is written to the error log file by the default runtime error handler.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetErrorLog(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Toggles an exact match for character string comparisons.
	/// </summary>
	/// <param name="fExact"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetExact(fExact AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that determines whether to open database files in exclusive or shared mode.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetExclusive(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that determines whether assignments are made to fields or to memory variables.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetFieldStore(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that fixes the number of decimal digits used to display numbers.
	/// </summary>
	/// <param name="fFixed"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetFixed(fFixed AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// </summary>
	/// <param name="dwBytes"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetKidStackSize(dwBytes AS DWORD) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Return and optionally change the setting that determines the internal operational characteristics of the underlying floating-point system.
	/// </summary>
	/// <param name="nFPU"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetMath(nFPU AS OBJECT) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Set the maximum amount of dynamic memory for the main thread.
	/// </summary>
	/// <param name="dwBytes"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetMaxDynSize(dwBytes AS DWORD) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// </summary>
	/// <param name="dwCount"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetMaxRegisteredAxitMethods(dwCount AS DWORD) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Set the maximum number of KID's that can be registered.
	/// </summary>
	/// <param name="dwCount"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetMaxRegisteredKids(dwCount AS DWORD) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Set the maximum amount of dynamic memory for the new thread.
	/// </summary>
	/// <param name="dwBytes"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetMaxThreadDynSize(dwBytes AS DWORD) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Change the setting that determines the <%APP%> search path for opening files.
	/// </summary>
	/// <param name="cPath"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetPath(cPath AS STRING) AS VOID
		/// THROW NotImplementedException{}
	RETURN

	/// <summary>
	/// Returns a string representing the evening extension for time strings in 12-hour format.
	/// </summary>
	/// <returns>
	/// </returns>
	FUNCTION GetPMExt() AS STRING
		RETURN XSharp.Runtime.State.GetInstance():GetValue<STRING>( Set.PmExt )

	/// <summary>
	/// Returns a string representing the evening extension for time strings in 12-hour format.
	/// </summary>
	/// <returns>
	/// </returns>
	FUNCTION SetPMExt() AS STRING
		RETURN GetPmExt()

	/// <summary>
	/// Set the evening extension for time strings in 12-hour format.
	/// </summary>
	/// <param name="cExt"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetPMExt(cExt AS STRING) AS STRING
		LOCAL oldValue as STRING
		VAR oState := XSharp.Runtime.State.GetInstance()
		BEGIN LOCK oState
			oldValue := oState:GetValue<STRING>(Set.PmExt)
			oState:SetValue(Set.PmExt , cExt )
		END LOCK
		RETURN oldValue

	/// <summary>
	/// Save a numeric value to the Registry.
	/// </summary>
	/// <param name="cSubKey"></param>
	/// <param name="cKeyName"></param>
	/// <param name="nKeyVal"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetRTRegInt(cSubKey AS STRING,cKeyName AS STRING,nKeyVal AS DWORD) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Save a string value to the Registry.
	/// </summary>
	/// <param name="cSubKey"></param>
	/// <param name="cKeyName"></param>
	/// <param name="cKeyVal"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetRTRegString(cSubKey AS STRING,cKeyName AS STRING,cKeyVal AS STRING) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that displays numbers in scientific notation.
	/// </summary>
	/// <param name="f"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetScience(f AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that determines whether a seek operation will find a close match when no exact match is found.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetSoftSeek(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that determines the thousands separation character to be used in numeric-to-string conversion functions.
	/// </summary>
	/// <param name="wSep"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetThousandSep(wSep AS OBJECT) AS WORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Change the setting that determines the separation character to be used in time strings.
	/// </summary>
	/// <param name="dwChar"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetTimeSep(dwChar AS DWORD) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Return and optionally change the setting that determines whether to include unique record keys in an order.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetUnique(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// </summary>
	/// <param name="n"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetWinCompFlags(n AS OBJECT) AS LONG
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// </summary>
	/// <param name="pFunc"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetWinCompFunc(pFunc AS OBJECT) AS IntPtr
		/// THROW NotImplementedException{}
	RETURN IntPtr.Zero

	/// <summary>
	/// Set the value of WipeDynSpace.
	/// </summary>
	/// <param name="lWipe"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetWipeDynSpace(lWipe AS LOGIC) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetYield(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//
// Most of these settings will Get and Set properties of the Runtime.State class

#command GETSTATE <type> <set>	      =>				 ;
		RETURN XSharp.Runtime.State.GetInstance():GetValue\< <type> \>( <set> )

#command SETSTATE <type> <set>	<value> =>				 ;
		LOCAL oldValue as <type>						 ;;
		VAR oState := XSharp.Runtime.State.GetInstance() ;;
		BEGIN LOCK oState								 ;;
			oldValue := oState:GetValue\<<type>\>(<set>) ;;
			oState:SetValue(<set> , <value> )			 ;;
		END LOCK										 ;;
		RETURN oldValue

	/// <summary>
	/// Returns a string representing the morning extension for time strings in 12-hour format.
	/// </summary>
	/// <returns>
	/// </returns>
	FUNCTION GetAMExt() AS STRING
		RETURN XSharp.Runtime.State.GetInstance():GetValue<STRING>( Set.AmExt )

	/// <summary>
	/// Set the morning extension for time strings in 12-hour format.
	/// </summary>
	/// <param name="cExt"></param>
	/// <returns>
	/// </returns>

	FUNCTION SetAMExt() AS STRING
		RETURN GetAmExt()

	FUNCTION SetAMExt(cExt AS STRING) AS STRING
		LOCAL oldValue as STRING
		VAR oState := XSharp.Runtime.State.GetInstance()
		BEGIN LOCK oState
			oldValue := oState:GetValue<STRING>(Set.AmExt)
			oState:SetValue(Set.AmExt , cExt )
		END LOCK
		RETURN oldValue

	/// <summary>
	/// Returns the setting that determines whether time strings are in 12-hour or 24-hour format.
	/// </summary>
	/// <returns>
	/// </returns>
	FUNCTION GetAmPm() AS LOGIC
		RETURN XSharp.Runtime.State.GetInstance():GetValue<LOGIC>( Set.AmPm )

	/// <summary>
	/// Return and optionally change the setting that determines whether time strings are in 12-hour or 24-hour format.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetAmPm(lSet AS LOGIC) AS LOGIC
		LOCAL oldValue as LOGIC
		VAR oState := XSharp.Runtime.State.GetInstance()
		BEGIN LOCK oState
			oldValue := oState:GetValue<LOGIC>(Set.AmPm)
			oState:SetValue(Set.AmPm, lSet)
		END LOCK
		RETURN oldValue

	/// <summary>
	/// Return and optionally change the setting that determines whether database files are created using ANSI or OEM format and whether certain text file operations convert between the two character sets.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetAnsi(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Sets the locale that the runtime uses for comparing strings when running in Windows collation mode (SetCollation(#Windows)).
	/// </summary>
	/// <param name="dwLocaleId"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetAppLocaleID(dwLocaleId AS DWORD) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Return and optionally change the setting that determines whether a beep is sounded by the error system when an error occurs.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetBeep(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that determines whether to include or omit century digits in the date format.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetCentury(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// </summary>
	/// <param name="pFunc"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetClipCompFunc(pFunc AS OBJECT) AS IntPtr
		/// THROW NotImplementedException{}
	RETURN IntPtr.Zero

	/// <summary>
	/// Return and optionally change the setting that determines the type of central processor you have.
	/// </summary>
	/// <param name="nCpu"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetCpu(nCpu AS OBJECT) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Return and optionally change the setting that determines the <%APP%> date format by selecting from a list of constants with corresponding date formats.
	/// </summary>
	/// <param name="dwCountry"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDateCountry(dwCountry AS OBJECT) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Return the current __VODate format.
	/// </summary>
	/// <returns>
	/// </returns>
	FUNCTION GetDateFormat() AS STRING
		RETURN XSharp.Runtime.State.GetInstance():GetValue<STRING>(Set.DateFormat)

	/// <summary>
	/// Change the setting that determines the <%APP%> date format.
	/// </summary>
	/// <param name="cDateFormat"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDateFormat(cDateFormat AS STRING) AS STRING
		LOCAL oldValue as STRING
		VAR oState := XSharp.Runtime.State.GetInstance()
		BEGIN LOCK oState
			oldValue := oState:GetValue<STRING>(Set.DateFormat)
			oState:SetValue(Set.DateFormat, cDateFormat)
		END LOCK
	RETURN oldValue

	/// <summary>
	/// Return and optionally change the setting that determines the number of decimal places used to display numbers.
	/// </summary>
	/// <param name="nDec"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDecimal(nDec AS DWORD) AS DWORD
		RETURN XSharp.Runtime.State.GetInstance():GetValue<DWORD>(Set.Decimals)

	/// <summary>
	/// Return and optionally change the setting that determines the decimal separation character to be used in numeric-to-string conversion functions.
	/// </summary>
	/// <param name="wSep"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDecimalSep(wSep AS OBJECT) AS WORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Change the setting that determines the <%APP%> default drive and directory.
	/// </summary>
	/// <param name="cDefault"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDefault(cDefault AS STRING) AS VOID
		/// THROW NotImplementedException{}
	RETURN

	/// <summary>
	/// Change the setting that determines the location of the error log file.
	/// </summary>
	/// <param name="pszNew"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDefaultDir(strNew AS STRING) AS VOID
		/// THROW NotImplementedException{}
	RETURN

	/// <summary>
	/// Return and optionally change the setting that determines whether to ignore or include records that are marked for deletion.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDeleted(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that determines the number of digits that will be shown to the left of the decimal point when a number is displayed.
	/// </summary>
	/// <param name="nDig"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDigit(nDig AS OBJECT) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Return and optionally change the setting that fixes the number of digits used to display numeric output.
	/// </summary>
	/// <param name="f"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetDigitFixed(f AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Update or replace the contents of a DOS environment variable.
	/// </summary>
	/// <param name="cVar"></param>
	/// <param name="cValue"></param>
	/// <param name="lAppend"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetEnv(cVar AS STRING,cValue AS STRING,lAppend AS LOGIC) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that determines how dates without century digits are interpreted.
	/// </summary>
	/// <param name="wYear"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetEpoch(wYear AS OBJECT) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Return and optionally change the setting that determines whether error information is written to the error log file by the default runtime error handler.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetErrorLog(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Toggles an exact match for character string comparisons.
	/// </summary>
	/// <param name="fExact"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetExact(fExact AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that determines whether to open database files in exclusive or shared mode.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetExclusive(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that determines whether assignments are made to fields or to memory variables.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetFieldStore(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that fixes the number of decimal digits used to display numbers.
	/// </summary>
	/// <param name="fFixed"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetFixed(fFixed AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// </summary>
	/// <param name="dwBytes"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetKidStackSize(dwBytes AS DWORD) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Return and optionally change the setting that determines the internal operational characteristics of the underlying floating-point system.
	/// </summary>
	/// <param name="nFPU"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetMath(nFPU AS OBJECT) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Set the maximum amount of dynamic memory for the main thread.
	/// </summary>
	/// <param name="dwBytes"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetMaxDynSize(dwBytes AS DWORD) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// </summary>
	/// <param name="dwCount"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetMaxRegisteredAxitMethods(dwCount AS DWORD) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Set the maximum number of KID's that can be registered.
	/// </summary>
	/// <param name="dwCount"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetMaxRegisteredKids(dwCount AS DWORD) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Set the maximum amount of dynamic memory for the new thread.
	/// </summary>
	/// <param name="dwBytes"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetMaxThreadDynSize(dwBytes AS DWORD) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Change the setting that determines the <%APP%> search path for opening files.
	/// </summary>
	/// <param name="cPath"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetPath(cPath AS STRING) AS VOID
		/// THROW NotImplementedException{}
	RETURN

	/// <summary>
	/// Returns a string representing the evening extension for time strings in 12-hour format.
	/// </summary>
	/// <returns>
	/// </returns>
	FUNCTION GetPMExt() AS STRING
		RETURN XSharp.Runtime.State.GetInstance():GetValue<STRING>( Set.PmExt )

	/// <summary>
	/// Set the evening extension for time strings in 12-hour format.
	/// </summary>
	/// <param name="cExt"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetPMExt() AS STRING
		RETURN GetPmExt()

	FUNCTION SetPMExt(cExt AS STRING) AS STRING
		LOCAL oldValue as STRING
		VAR oState := XSharp.Runtime.State.GetInstance()
		BEGIN LOCK oState
			oldValue := oState:GetValue<STRING>(Set.PmExt)
			oState:SetValue(Set.PmExt , cExt )
		END LOCK
		RETURN oldValue

	/// <summary>
	/// Save a numeric value to the Registry.
	/// </summary>
	/// <param name="cSubKey"></param>
	/// <param name="cKeyName"></param>
	/// <param name="nKeyVal"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetRTRegInt(cSubKey AS STRING,cKeyName AS STRING,nKeyVal AS DWORD) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Save a string value to the Registry.
	/// </summary>
	/// <param name="cSubKey"></param>
	/// <param name="cKeyName"></param>
	/// <param name="cKeyVal"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetRTRegString(cSubKey AS STRING,cKeyName AS STRING,cKeyVal AS STRING) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that displays numbers in scientific notation.
	/// </summary>
	/// <param name="f"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetScience(f AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that determines whether a seek operation will find a close match when no exact match is found.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetSoftSeek(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Return and optionally change the setting that determines the thousands separation character to be used in numeric-to-string conversion functions.
	/// </summary>
	/// <param name="wSep"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetThousandSep(wSep AS OBJECT) AS WORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Change the setting that determines the separation character to be used in time strings.
	/// </summary>
	/// <param name="dwChar"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetTimeSep(dwChar AS DWORD) AS DWORD
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// Return and optionally change the setting that determines whether to include unique record keys in an order.
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetUnique(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// </summary>
	/// <param name="n"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetWinCompFlags(n AS OBJECT) AS LONG
		/// THROW NotImplementedException{}
	RETURN 0

	/// <summary>
	/// </summary>
	/// <param name="pFunc"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetWinCompFunc(pFunc AS OBJECT) AS IntPtr
		/// THROW NotImplementedException{}
	RETURN IntPtr.Zero

	/// <summary>
	/// Set the value of WipeDynSpace.
	/// </summary>
	/// <param name="lWipe"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetWipeDynSpace(lWipe AS LOGIC) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// </summary>
	/// <param name="lSet"></param>
	/// <returns>
	/// </returns>
	FUNCTION SetYield(lSet AS OBJECT) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE


[VOSDK] Fix for https://github.com/X-Sharp/XSharpPublic/issues/1038

 /// <exclude />
CLASS __FormattedString
	PROTECT oEditOwner AS SingleLineEdit
	PROTECT sPicture AS STRING
	PROTECT sTemplate AS STRING
	PROTECT sValue AS STRING
	PROTECT sOldValue AS STRING
	PROTECT FuncFlags IS strucPictureFuncFlags
	PROTECT cType AS STRING
	PROTECT wDecSep AS DWORD		//RvdH 070205 changed from WORD to DWORD
	PROTECT wThousSep AS DWORD		//RvdH 070205 changed from WORD to DWORD
	PROTECT iDelStart AS INT
	PROTECT iDelEnd AS INT
	EXPORT iTemplLen AS INT
	EXPORT lEndKey AS LOGIC
	EXPORT wOverWrite AS LONGINT			//RvdH 070205 changed from WORD to LONG
	EXPORT wScrMode AS LONGINT				//RvdH 070205 changed from WORD to LONG

 /// <exclude />
	METHOD AsString() AS STRING STRICT
	RETURN sValue

 /// <exclude />
METHOD Cut() AS LOGIC STRICT
	LOCAL iStart AS INT
	LOCAL iEnd AS INT
	LOCAL iLen AS INT
	LOCAL hGbl AS PTR
	LOCAL pszGbl AS PSZ

	SendMessage(oEditOwner:Handle(), EM_GETSEL, DWORD(_CAST, @iStart), LONGINT(_CAST, @iEnd))

	IF (iStart == iEnd)
		RETURN FALSE
	ENDIF

	IF (!OpenClipboard(NULL_PTR))
		RETURN FALSE
	ENDIF

	EmptyClipboard()

	iStart++
	iEnd++
	iLen := iEnd - iStart

	hGbl := GlobalAlloc(GMEM_DDESHARE, DWORD(iLen+1))

	IF (hGbl == NULL_PTR)
		CloseClipboard()
		RETURN FALSE
	ENDIF

	pszGbl := GlobalLock(hGbl)
	MemCopy(pszGbl, String2Psz(SubStr3(sValue, DWORD(iStart), DWORD(iLen))), DWORD(iLen+1))

	GlobalUnlock(hGbl)

	SetClipboardData(CF_TEXT, hGbl)

	CloseClipboard()

	SELF:DeleteSelection(iStart, iEnd)
	RETURN TRUE

 /// <exclude />
ACCESS DecimalPos AS INT STRICT
	RETURN INT(_CAST, At2(".", sTemplate))

 /// <exclude />
METHOD DeleteChar(iPos AS INT, lInvert AS LOGIC, lUpdateOwner AS LOGIC) AS VOID STRICT
	LOCAL iCurPos AS INT
	LOCAL iNextPos AS INT
	LOCAL sNextChar AS STRING

	IF IsNil(iPos)
		iCurPos := oEditOwner:__CurPos
	ELSE
		iCurPos := iPos
	ENDIF

	IF !SELF:IsEditPos(iCurPos)
		SELF:InvalidAction()
		RETURN
	ENDIF

	IF !lInvert
		iNextPos := SELF:NextEditPos(iCurPos + 1)
	ELSE
		iNextPos := SELF:PrevEditPos(iCurPos - 1)
	ENDIF

	IF (wScrMode != SCRMODE_NO)
		WHILE (iCurPos > 0) .AND. (iNextPos > 0)
			IF (wScrMode != SCRMODE_FULL) .AND. (Abs(iNextPos - iCurPos) != 1)
				EXIT
			ENDIF
			sNextChar := CharPos(sValue, DWORD(iNextPos))
			// 2.5b TBD chars shouldn't count
			IF ((iNextPos >= iDelStart) .AND. (iNextPos <= iDelEnd)) .OR. (SELF:IsValidChar(sNextChar, iCurPos, TRUE))
				IF SELF:IsEditPos(iNextPos)
					SELF:PutChar(sNextChar, iCurPos)
					SELF:PutChar(" ", iNextPos)
				ELSE
					SELF:PutChar(" ", iCurPos)
				ENDIF
			ELSE
				EXIT
			ENDIF
			iCurPos := iNextPos
			IF !lInvert
				iNextPos := SELF:NextEditPos(iCurPos + 1)
			ELSE
				iNextPos := SELF:PrevEditPos(iCurPos - 1)
			ENDIF
		END
	ENDIF

	IF (SELF:IsEditPos(iCurPos))
		SELF:PutChar(" ", iCurPos)
	ENDIF

	IF lUpdateOwner
		SELF:UpdateEditOwner()
	ENDIF
	RETURN

 /// <exclude />
METHOD DeleteSelection(iStart AS INT, iEnd AS INT) AS VOID STRICT
	LOCAL i AS INT
	LOCAL lInvert AS LOGIC
	LOCAL iDelPos AS INT
	LOCAL iDecPos AS INT
	LOCAL iSignPos AS INT
	LOCAL lNumeric AS LOGIC

	IF (iEnd <= iStart)
		iEnd := iStart
	ELSE
		iEnd--
	ENDIF

	iDelStart := iStart
	iDelEnd := iEnd

	lNumeric := (cType == "N")

	IF lNumeric .AND. ((iDecPos := INT(_CAST, At2(Chr(wDecSep), sValue))) > istart) .AND. (iDecPos < iEnd)
		SELF:DeleteSelection(iStart, iDecPos)
		SELF:DeleteSelection(iDecPos+1, iEnd+1)
	ELSE
		lInvert := ((lNumeric) .AND. (At3(Chr(wDecSep), sValue, DWORD(iEnd)) > 0))
		IF !lInvert
			iDelPos := iStart
			FOR i:= iStart TO iEnd
				IF SELF:IsEditPos(i)
					SELF:DeleteChar(iDelPos, lInvert, FALSE)
					iDelEnd := iDelEnd-1
				ELSEIF (wScrMode == SCRMODE_PART)
					iDelPos := i+1
				ENDIF
			NEXT
		ELSE
			IF (lNumeric)
				iSignPos := INT(_CAST, At2("-", sValue))
				IF (iSignPos > 0)
					SELF:PutChar(" ", iSignPos)
				ENDIF
			ENDIF
			FOR i:= iEnd DOWNTO iStart
				iDelPos := iEnd
				IF SELF:IsEditPos(i)
					SELF:DeleteChar(iDelPos, lInvert, FALSE)
					iDelStart := iDelStart + 1
				ELSE
					iDelPos := i-1
				ENDIF
			NEXT
		ENDIF
		SELF:UpdateEditOwner()

		IF (lInvert .OR. (wScrMode == SCRMODE_NO))
			oEditOwner:__CurPos := SELF:PrevEditPos(iEnd+1)
		ENDIF
	ENDIF

	iDelStart := iDelEnd := 0
	RETURN

 /// <exclude />
ACCESS EmptyValue AS STRING STRICT
	LOCAL sEmpty AS STRING
	LOCAL cCurTplChar AS STRING
	LOCAL i AS DWORD

	sEmpty := ""

	FOR i:=1 TO DWORD(iTemplLen)
		cCurTplChar := CharPos(sTemplate, i)
		IF IsEditTemplChar(cCurTplChar)
			IF (sEmpty == NULL_STRING)
				sEmpty := Space(1)
			ELSE
				sEmpty += " "
			ENDIF
		ELSE
			IF (cCurTplChar == ".") .AND. (cType == "N")
				sEmpty += Chr(wDecSep)
			ELSEIF (cCurTplChar == ",") .AND. (cType == "N")
				sEmpty += Chr(wThousSep)
			ELSE
				sEmpty += cCurTplChar
			ENDIF
		ENDIF
	NEXT

	RETURN sEmpty

 /// <exclude />
CONSTRUCTOR(Owner, PicString, Type, OverWrite, DefTempl, ScrMode)

	DEFAULT(@Type, "C")

	oEditOwner := Owner
	cType := Type // !!! order dependancy: ASSIGN Picture needs type to be set !!!
	SELF:Picture := PicString

	wDecSep := SetDecimalSep()
	wThousSep := SetThousandSep()
	wOverWrite := OverWrite
	wScrMode := ScrMode

	IF !Empty(DefTempl) .AND. Empty(sTemplate)
		sTemplate := DefTempl
		sPicture := sPicture + " " + DefTempl
		iTemplLen := INT(_CAST, SLen(sTemplate))
	ENDIF

	sValue := SELF:EmptyValue

	SELF:UpdateEditOwner()
	RETURN

 /// <exclude />
METHOD InsertChar(cInsChar AS STRING, iPos AS INT, lInvert AS LOGIC) AS VOID STRICT
	LOCAL iCurPos AS INT
	LOCAL iNextPos AS INT
	LOCAL cSaveChar AS STRING
	LOCAL cTempChar AS STRING

	IF IsNil(iPos)
		iCurPos := oEditOwner:__CurPos
	ELSE
		iCurPos := iPos
	ENDIF

	IF !SELF:IsEditPos(iCurPos) .AND. !lInvert
#ifdef __VULCAN__
		IF (cType == "N") .AND. sValue[iCurPos-1] == wDecSep
			iCurPos++
			oEditOwner:__CurPos := iCurPos
		ELSE
			SELF:InvalidAction()
			RETURN
		ENDIF
#else
		IF (cType == "N") .AND. (_NGet(String2Psz( sValue), DWORD(iCurPos-1)) == wDecSep)
			iCurPos++
			oEditOwner:__CurPos := iCurPos
		ELSE
			SELF:InvalidAction()
			RETURN
		ENDIF
#endif
	ENDIF

	IF !lInvert
		iNextPos := SELF:NextEditPos(iCurPos + 1)
	ELSE
		iCurPos--
		iNextPos := SELF:PrevEditPos(iCurPos - 1)
	ENDIF

	cSaveChar := CharPos(sValue, DWORD(iCurPos))
	SELF:PutChar(cInsChar, iCurPos)

	WHILE (iCurPos > 0) .AND. (iNextPos > 0)
		IF (wScrMode != SCRMODE_FULL) .AND. (Abs(iNextPos - iCurPos) != 1)
			//if (iNextPos != iCurPos+1) .and. (wScrMode != SCRMODE_FULL)
			EXIT
		ENDIF
#ifdef __VULCAN__
		IF (cType == "N") .AND. !lInvert .AND. iCurPos < sValue:Length .AND. sValue[iCurPos] == wDecSep
			EXIT
		ENDIF
#else
		IF (cType == "N") .AND. !lInvert .AND. (_NGet(String2Psz( sValue), DWORD(iCurPos)) == wDecSep)
			EXIT
		ENDIF
#endif
		IF (SELF:IsValidChar(cSaveChar, iNextPos, FALSE))
			cTempChar := CharPos(sValue, DWORD(iNextPos))
			SELF:PutChar(cSaveChar, iNextPos)
			cSaveChar := cTempChar
		ELSE
			EXIT
		ENDIF
		iCurPos := iNextPos
		IF !lInvert
			iNextPos := SELF:NextEditPos(iCurPos + 1)
		ELSE
			iNextPos := SELF:PrevEditPos(iCurPos - 1)
		ENDIF
	END
	RETURN

 /// <exclude />
METHOD InvalidAction() AS VOID STRICT
	MessageBeep(0xFFFFFFFF)
   RETURN

 /// <exclude />
METHOD IsEditPos(iPos AS INT) AS LOGIC STRICT
	RETURN IsEditTemplChar(CharPos(sTemplate, DWORD(iPos)))

 /// <exclude />
ACCESS IsEmpty AS LOGIC STRICT
	RETURN (sValue == SELF:EmptyValue)

 /// <exclude />
METHOD IsValidChar(cChar AS STRING, iPos AS INT, lIgnoreBlank AS LOGIC) AS LOGIC STRICT
	RETURN SELF:MatchesTemplChar(cChar, CharPos(sTemplate, DWORD(iPos)), lIgnoreBlank)

#ifdef __VULCAN__
// This implementation avoids unnecesary String -> PSZ conversions that would happen using the VO implementation
 /// <exclude />
METHOD MatchesTemplChar( cTest AS STRING, _cTemplChar AS STRING, lIgnoreBlank AS LOGIC ) AS LOGIC
	LOCAL lIsNumeric := ( cType != "N" ) AS LOGIC
	LOCAL cTemplChar AS CHAR

	IF ! IsEditTemplChar( _cTemplChar ) .AND. lIsNumeric
		RETURN FALSE
	ENDIF

	IF ! String.IsNullOrEmpty( _cTemplChar )
	   cTemplChar := _cTemplChar[0]
	ENDIF

	// speed up this case
	IF cTemplChar == c'X'
		RETURN TRUE
	ENDIF

	IF cTest == " " .AND. lIgnoreBlank
		RETURN TRUE
	ENDIF

	SWITCH cTemplChar

	CASE c'A'
		RETURN IsAlpha( cTest )
	CASE c'N'
		RETURN IsAlNum( cTest )
	CASE c'9'
    CASE c'.'
		RETURN IsDigit( cTest ) .OR. ( cType == "N" .AND. At2( cTest, " +-" ) > 0 )
	CASE c'#'
		RETURN IsDigit( cTest ) .OR. At2( cTest, " +-" ) > 0
	CASE c'!'
		RETURN TRUE
	CASE c'Y'
		RETURN At2( cTest:ToUpper(), __CavoStr( RT_MSG_YNSTRING ) ) > 0
	CASE c'L'
		cTest := cTest:ToUpper()
		RETURN At2( cTest, __CavoStr( RT_MSG_YNSTRING ) ) > 0 .OR. cTest == __CavoStr( RT_MSG_SHORT_TRUE ) .OR. cTest == __CavoStr( RT_MSG_SHORT_FALSE )

	END SWITCH

	RETURN FALSE

#else

 /// <exclude />
METHOD MatchesTemplChar(cTest AS STRING, cTemplChar AS STRING, lIgnoreBlank AS LOGIC) AS LOGIC STRICT
	LOCAL lIsNumeric := (cType != "N") AS LOGIC
   LOCAL cYesNo, cTrue, cFalse AS STRING
	IF !IsEditTemplChar(cTemplChar) .AND. lIsNumeric
		RETURN FALSE
	ENDIF

	// speed up this case
	IF (cTemplChar == "X")
		RETURN TRUE
	ENDIF

	IF (cTest == " " .AND. lIgnoreBlank)
		RETURN TRUE
	ENDIF

	DO CASE
	CASE cTemplChar == "A"
		RETURN IsAlpha(PSZ(_CAST, cTest))
	CASE cTemplChar == "N"
		RETURN IsAlNum(PSZ(_CAST, cTest))
	CASE (cTemplChar == "9" .OR. cTemplChar == ".")
		RETURN IsDigit(PSZ(_CAST, cTest)) .OR. (cType == "N" .AND. (At2(cTest, " +-") > 0))
	CASE cTemplChar == "#"
		RETURN IsDigit(PSZ(_CAST, cTest)) .OR. (At2(cTest, " +-") > 0)
	CASE cTemplChar == "!"
		RETURN TRUE
	CASE cTemplChar == "Y"
		RETURN (At2(Upper(cTest), Psz2String(__CavoStr(RT_MSG_YNSTRING))) > 0)
	CASE cTemplChar == "L"
		cTest := Upper(cTest)
		cYesNo := Psz2String(__CavoStr(RT_MSG_YNSTRING))
		cTrue  := Psz2String(__CavoStr(RT_MSG_SHORT_TRUE))
		cFalse := Psz2String(__CavoStr(RT_MSG_SHORT_FALSE))
		RETURN ((At2(cTest,cYesNo) >0) .OR. (cTest == cTrue) .OR. (cTest == cFalse))
	CASE cTemplChar == "." .AND. cType == "N"
		RETURN TRUE
	ENDCASE

	RETURN FALSE
#endif

 /// <exclude />
METHOD NextEditPos(iPos AS INT) AS INT STRICT
	LOCAL i AS INT

	IF (iPos == -1)
		iPos := oEditOwner:__CurPos + 1
	ENDIF

	FOR i := iPos TO iTemplLen
		IF IsEditTemplChar(CharPos(sTemplate, DWORD(i)))
			RETURN i
		ELSEIF (cType == "N") .AND. (CharPos(sTemplate, DWORD(i)) == ".") .AND. (wOverWrite != OVERWRITE_ALWAYS) .AND.;
			(wOverWrite != OVERWRITE_ONKEY .OR. !IsOverWriteModeEnabled())
			RETURN i
		ENDIF
	NEXT

	RETURN iPos

 /// <exclude />
METHOD Paste() AS LOGIC STRICT
	LOCAL hGlb AS PTR
	LOCAL pszPaste AS PSZ
	LOCAL sPaste AS STRING
	LOCAL i, iLen AS INT
	LOCAL iStart, iEnd AS INT
	LOCAL oKeyEvent AS KeyEvent

	IF (!IsClipboardFormatAvailable(CF_TEXT))
		RETURN FALSE
	ENDIF

	IF (!OpenClipboard(NULL_PTR))
		RETURN FALSE
	ENDIF

	hGlb := GetClipboardData(CF_TEXT)

	IF (hGlb != NULL_PTR)
		pszPaste := GlobalLock(hGlb)
		IF (PTR(_CAST, pszPaste) != NULL_PTR)
			SendMessage(oEditOwner:Handle(), EM_GETSEL, DWORD(_CAST, @iStart), LONGINT(_CAST, @iEnd))
			IF ((iEnd - iStart) > 0)
				SELF:DeleteSelection(iStart+1, iEnd+1)
			ENDIF
			oKeyEvent := KeyEvent{oEditOwner:Handle(), WM_CHAR, 0, 0}
			sPaste := Psz2String(pszPaste)
			iLen := INT(_CAST, SLen(sPaste))
			FOR i := 1 TO iLen
				oKeyEvent:wParam := Asc(CharPos(sPaste,DWORD(i)))
				SELF:ProcessKeyEvent(oKeyEvent)
			NEXT
		ENDIF
		GlobalUnlock(hGlb)
	ENDIF

	CloseClipboard()

	RETURN TRUE

 /// <exclude />
ACCESS Picture AS STRING STRICT
	RETURN sPicture

 /// <exclude />
ASSIGN Picture(cNewPicture AS STRING)  STRICT
	LOCAL pLogic AS LOGIC PTR
	LOCAL iSpcPos, iFuncPos, i AS DWORD
	LOCAL sFuncChar AS STRING
	#ifndef __VULCAN__
	LOCAL iAsc, iAscD, iAscM, iAscY, iAsc9 AS DWORD
	#endif

	sPicture := cNewPicture
	pLogic := (LOGIC PTR) @FuncFlags
	// handle functions
	IF (Left(sPicture, 1) == "@")
		iSpcPos := At2(" ", sPicture)
		IF (iSpcPos == 0)
			iSpcPos :=  SLen(sPicture) + 1
		ENDIF
		FOR i := 2 TO (iSpcPos-1)
			sFuncChar := CharPos(sPicture, i)
			iFuncPos := At2(sFuncChar, "BCDERXZ(!A")
			IF (iFuncPos > 0)
				#warning incompatibility with VO
				// In X#, LOGIC PTR uses increments of 1 bytes, but each LOGIC field in the strucPictureFuncFlags
				// takes 4 bytes, so we need the * 4 below:
				LOGIC(pLogic + (iFuncPos - 1) * 4) := TRUE
//				LOGIC(pLogic + iFuncPos - 1) := TRUE
			ENDIF
		NEXT
	ENDIF

	sTemplate := SubStr2(sPicture, (iSpcPos + 1))
	iTemplLen := INT(_CAST, SLen(sTemplate))

	#ifndef __VULCAN__
	iAscD :=  68 	// Asc("D")
	iAscM :=  77 	// Asc("M")
	iAscY :=  89 	// Asc("Y")
	iAsc9 :=  57 	// Asc("9")
	#endif
	IF ((cType == "D") .OR. (cType == "C")) .AND. FuncFlags:lSetDate
		sTemplate := GetDateFormat()
		iTemplLen := LONGINT(SLen(sTemplate))
#ifdef __VULCAN__
      sTemplate := sTemplate:Replace( 'D', '9' ):Replace( 'M', '9' ):Replace( 'Y', '9' )
#else
		FOR i:=1 TO DWORD(iTemplLen)
			iAsc := Asc(CharPos(sTemplate, i))
			IF (iAsc == iAscD) .OR. (iAsc == iAscM) .OR. (iAsc == iAscY)
				_NPut(PSZ(_CAST, sTemplate), i-1, iAsc9)
			ENDIF
		NEXT
#endif
	ENDIF

	IF (iTemplLen == 0)
		IF FuncFlags:lAlphaOnly
			sTemplate := Replicate("A", Min(Min(DEFAULT_STRING_TEMPL_SIZE, oEditOwner:TextLimit), oEditOwner:__FSLength))
		ELSEIF FuncFlags:lConvUpper
			sTemplate := Replicate("!", Min(Min(DEFAULT_STRING_TEMPL_SIZE, oEditOwner:TextLimit), oEditOwner:__FSLength))
		ENDIF
		iTemplLen := LONGINT(SLen(sTemplate))
	ENDIF

	RETURN

 /// <exclude />
METHOD PrevEditPos(iPos AS INT) AS INT STRICT
	LOCAL i AS DWORD

	IF (iPos == -1)
		iPos := oEditOwner:__CurPos - 1
	ENDIF

	FOR i := DWORD(iPos) DOWNTO 1
		IF IsEditTemplChar(CharPos(sTemplate, i))
			RETURN LONGINT(i)
		ELSEIF (cType == "N") .AND. (CharPos(sTemplate, i) == ".") .AND. (wOverWrite != OVERWRITE_ALWAYS) .AND.;
			(wOverWrite != OVERWRITE_ONKEY .OR. !IsOverWriteModeEnabled())
			RETURN LONGINT(i)
		ENDIF
	NEXT

	RETURN 0L

 /// <exclude />
METHOD ProcessChar(cChar AS STRING) AS LOGIC STRICT
	LOCAL iCurPos AS INT
	LOCAL cTemplChar AS STRING
	LOCAL lInvert AS LOGIC
	LOCAL iDecPos AS INT

	iCurPos 		:= oEditOwner:__CurPos
	cTemplChar 	:= CharPos(sTemplate, DWORD(iCurPos))

	IF SELF:MatchesTemplChar(cChar, cTemplChar, FALSE)
		IF cTemplChar == "!"
			UpperA(@cChar)
		ENDIF
#ifdef __VULCAN__
		IF ((wOverWrite == OVERWRITE_ALWAYS) .OR. (wOverWrite == OVERWRITE_ONKEY .AND. IsOverWriteModeEnabled())) .OR.	((cType == "N") .AND. (sValue[iCurPos-1]) == 32)
			SELF:PutChar(cChar, iCurPos)
		ELSE
			lInvert := (cType == "N") .AND. ((iDecPos := INT(_CAST, At3(Chr(wDecSep), sValue, iCurPos-1))) > 0) .AND. ((At2(" ", SubStr3(sValue, DWORD(_CAST, iCurPos+1), Max(iDecPos-iCurPos,0))) == 0) .OR. (iCurPos == iDecPos)) .AND. sValue[Max(0, SELF:NextEditPos(0)-1)] == 32
			SELF:InsertChar(cChar, iCurPos, lInvert)
		ENDIF
#else
		IF ((wOverWrite == OVERWRITE_ALWAYS) .OR. (wOverWrite == OVERWRITE_ONKEY .AND. IsOverWriteModeEnabled())) .OR.;
			((cType == "N") .AND. (_NGet(String2Psz(sValue), DWORD(iCurPos-1)) == 32))
			SELF:PutChar(cChar, iCurPos)
		ELSE
			lInvert := (cType == "N") .AND. ((iDecPos := INT(_CAST, At3(Chr(wDecSep), sValue, DWORD(iCurPos-1)))) > 0);
				.AND. ((At2(" ", SubStr3(sValue, DWORD(iCurPos+1), Max(iDecPos-iCurPos,0))) == 0) .OR.;
				 (iCurPos == iDecPos)) .AND. (_NGet(String2Psz(sValue), Max(0, SELF:NextEditPos(0)-1)) == 32)
			SELF:InsertChar(cChar, iCurPos, lInvert)
		ENDIF
#endif
		SELF:UpdateEditOwner()
		IF !lInvert
			iCurPos := SELF:NextEditPos(-1)
		ENDIF
		IF ((iCurPos > iTemplLen) .OR. (!SELF:IsEditPos(iCurPos)) .AND. cType != "N") .AND. oEditOwner:AutoFocusChange
			SendMessage(GetParent(oEditOwner:Handle()), WM_NEXTDLGCTL, 0, 0L)
		ELSE
			oEditOwner:__CurPos := iCurPos
		ENDIF
	ELSE
		SELF:InvalidAction()
	ENDIF

	RETURN TRUE
#pragma warnings(219,off)
// warning about assignment to iCurPos. We have to call oEditOwner:__CurPos for code inside that property
 /// <exclude />
METHOD ProcessKeyEvent(oKeyEvt AS KeyEvent) AS LOGIC STRICT
	LOCAL iCurPos AS INT
	LOCAL uMsg AS DWORD
	LOCAL lRet AS LOGIC
	LOCAL iStart AS INT
	LOCAL iEnd AS INT
	LOCAL dwKCode AS DWORD

	uMsg := oKeyEvt:umsg
	// causes code in oEditOwner:__CurPos to be executed, do not remove
	iCurPos := oEditOwner:__CurPos
	lRet := TRUE

    SWITCH uMsg
	CASE WM_KEYDOWN
		dwKCode := oKeyEvt:KeyCode
		SWITCH dwKCode
		CASE KEYARROWLEFT
			oEditOwner:__CurPos := SELF:PrevEditPos(-1)
		CASE KEYARROWRIGHT
			oEditOwner:__CurPos := SELF:NextEditPos(-1)
		CASE KEYHOME
			oEditOwner:__CurPos := SELF:NextEditPos(1)
		CASE KEYEND
			IF lEndKey
				oEditOwner:__CurPos := SELF:PrevEditPos(iTemplLen) + 1
			ELSE
				oEditOwner:__CurPos := SELF:PrevEditPos(INT(_CAST, SLen(Trim(sValue)))) + 1
			ENDIF
			lEndKey := !lEndKey
		CASE KEYDELETE
        CASE KEYBACKSPACE
			sOldValue := SClone(sValue)
			SendMessage(oEditOwner:Handle(), EM_GETSEL, DWORD(_CAST, @iStart), LONG(_CAST, @iEnd))
			IF (dwKCode == KEYDELETE) .OR. (iStart != iEnd)
				SELF:DeleteSelection(iStart+1, iEnd+1)
				//oEditOwner:__CurPos := iStart
			ELSE
				oEditOwner:__CurPos := SELF:PrevEditPos(-1)
				SELF:DeleteChar(oEditOwner:__CurPos, FALSE, TRUE)
			ENDIF
		OTHERWISE
			lRet := FALSE
		END SWITCH
	CASE WM_CHAR
		sOldValue := SClone(sValue)
		IF (oKeyEvt:ASCIIChar == wDecSep) .AND. (cType == "N") .AND. (SELF:DecimalPos > 0)
			oEditOwner:__CurPos := SELF:DecimalPos + 1
			// change for 2.5b Copy/cut/paste didn't work
		ELSEIF (oKeyEvt:wParam >= 32)
			SendMessage(oEditOwner:Handle(), EM_GETSEL, DWORD(_CAST, @iStart), LONG(_CAST, @iEnd))
			IF ((iEnd - iStart) > 0)
				SELF:DeleteSelection(iStart+1, iEnd+1)
			ENDIF
			lRet := SELF:ProcessChar(ChrA(oKeyEvt:ASCIIChar))
		ELSE
			lRet := (oKeyEvt:KeyCode == KEYBACKSPACE)
		ENDIF
	END SWITCH

	RETURN lRet

#pragma warnings(219,on)

 /// <exclude />
METHOD PutChar(cChar AS STRING, iPos AS INT) AS VOID STRICT
#ifdef __VULCAN__
   // VO version modifies existing string, this is illegal in .NET
   IF iPos <= sValue:Length .AND. ! String.IsNullOrEmpty( cChar )
      LOCAL sb := System.Text.StringBuilder{ sValue } AS System.Text.StringBuilder
      LOCAL ch := cChar[0] AS CHAR
      IF FuncFlags:lConvUpper
         ch := Char.ToUpper( ch )
      ENDIF
      sb[iPos - 1] := ch
      sValue := sb:ToString()
   ENDIF
#else
	LOCAL pString AS BYTE PTR
	LOCAL iOffset AS INT

	IF (iPos <= INT(_CAST, SLen(sValue)))
		iOffset := (iPos -1)
		IF FuncFlags:lConvUpper
			UpperA(cChar)
		ENDIF
		pString := PTR(_CAST, sValue)
		BYTE(pString + iOffSet) := Asc(cChar)
	ENDIF
#endif
	RETURN

 /// <exclude />
METHOD TestFirstChar(cChar)
	LOCAL iCurPos    AS LONGINT
	iCurPos := 1
	IF !SELF:IsEditPos(iCurPos)
		iCurPos := SELF:NextEditPos(iCurPos)
		oEditOwner:__CurPos := iCurPos
	ENDIF
	RETURN NIL

 /// <exclude />
ACCESS Type AS STRING STRICT
	RETURN cType

 /// <exclude />
ASSIGN TYPE(cNewType AS STRING)  STRICT
	RETURN (cType := cNewType)

 /// <exclude />
METHOD Undo() AS LOGIC STRICT
	sValue := sOldValue
	SELF:UpdateEditOwner()

	RETURN TRUE

 /// <exclude />
METHOD UpdateEditOwner() AS VOID STRICT
	LOCAL iSaveCurPos AS INT

	iSaveCurPos := oEditOwner:__CurPos

	oEditOwner:__ForceModFlag2True := TRUE
	oEditOwner:__SetText(sValue)
	oEditOwner:Modified := TRUE
	oEditOwner:__ForceModFlag2True := FALSE
	oEditOwner:__CurPos := iSaveCurPos
	RETURN

 /// <exclude />
ASSIGN UsualValue(uNewValue AS USUAL)  STRICT
	IF !IsNil(uNewValue)
		cType := ValType(uNewValue)
	ENDIF

	IF !IsNil(uNewValue) .AND. (CType == "C") .AND. (iTemplLen > INT(_CAST, Len(uNewValue)))
		uNewValue := uNewValue + Space(DWORD(iTemplLen) - Len(uNewValue))
	ENDIF

	IF !IsNil(uNewValue)
		sValue := Transform(uNewValue, sPicture)
	ELSE
		sValue := SELF:EmptyValue
	ENDIF

	oEditOwner:__SetText(sValue)

	RETURN

 /// <exclude />
ACCESS Template
RETURN SELF:sTemplate
END CLASS

STATIC FUNCTION IsEditTemplChar(cTest AS STRING) AS LOGIC STRICT
   RETURN LOGIC(_CAST, At2(cTest, "ANX9!YL#"))

STATIC FUNCTION IsOverWriteModeEnabled() AS LOGIC STRICT
   /*STATIC*/ LOCAL DIM aKeyStates[256] AS BYTE // compiler bug!!!

   GetKeyboardState(@aKeyStates)

   RETURN !LOGIC(_CAST, _AND(aKeyStates[VK_INSERT + 1], 1))

#region defines
DEFINE DEFAULT_STRING_TEMPL_SIZE := 128
#endregion

 /// <exclude />
CLASS __FormattedString
	PROTECT oEditOwner AS SingleLineEdit
	PROTECT sPicture AS STRING
	PROTECT sTemplate AS STRING
	PROTECT sValue AS STRING
	PROTECT sOldValue AS STRING
	PROTECT FuncFlags IS strucPictureFuncFlags
	PROTECT cType AS STRING
	PROTECT wDecSep AS DWORD		//RvdH 070205 changed from WORD to DWORD
	PROTECT wThousSep AS DWORD		//RvdH 070205 changed from WORD to DWORD
	PROTECT iDelStart AS INT
	PROTECT iDelEnd AS INT
	EXPORT iTemplLen AS INT
	EXPORT lEndKey AS LOGIC
	EXPORT wOverWrite AS LONGINT			//RvdH 070205 changed from WORD to LONG
	EXPORT wScrMode AS LONGINT				//RvdH 070205 changed from WORD to LONG

 /// <exclude />
	METHOD AsString() AS STRING STRICT
	RETURN sValue

 /// <exclude />
METHOD Cut() AS LOGIC STRICT
	LOCAL iStart AS INT
	LOCAL iEnd AS INT
	LOCAL iLen AS INT
	LOCAL hGbl AS PTR
	LOCAL pszGbl AS PSZ

	SendMessage(oEditOwner:Handle(), EM_GETSEL, DWORD(_CAST, @iStart), LONGINT(_CAST, @iEnd))

	IF (iStart == iEnd)
		RETURN FALSE
	ENDIF

	IF (!OpenClipboard(NULL_PTR))
		RETURN FALSE
	ENDIF

	EmptyClipboard()

	iStart++
	iEnd++
	iLen := iEnd - iStart

	hGbl := GlobalAlloc(GMEM_DDESHARE, DWORD(iLen+1))

	IF (hGbl == NULL_PTR)
		CloseClipboard()
		RETURN FALSE
	ENDIF

	pszGbl := GlobalLock(hGbl)
	MemCopy(pszGbl, String2Psz(SubStr3(sValue, DWORD(iStart), DWORD(iLen))), DWORD(iLen+1))

	GlobalUnlock(hGbl)

	SetClipboardData(CF_TEXT, hGbl)

	CloseClipboard()

	SELF:DeleteSelection(iStart, iEnd)
	RETURN TRUE

 /// <exclude />
ACCESS DecimalPos AS INT STRICT
	RETURN INT(_CAST, At2(".", sTemplate))

 /// <exclude />
METHOD DeleteChar(iPos AS INT, lInvert AS LOGIC, lUpdateOwner AS LOGIC) AS VOID STRICT
	LOCAL iCurPos AS INT
	LOCAL iNextPos AS INT
	LOCAL sNextChar AS STRING

	IF IsNil(iPos)
		iCurPos := oEditOwner:__CurPos
	ELSE
		iCurPos := iPos
	ENDIF

	IF !SELF:IsEditPos(iCurPos)
		SELF:InvalidAction()
		RETURN
	ENDIF

	IF !lInvert
		iNextPos := SELF:NextEditPos(iCurPos + 1)
	ELSE
		iNextPos := SELF:PrevEditPos(iCurPos - 1)
	ENDIF

	IF (wScrMode != SCRMODE_NO)
		WHILE (iCurPos > 0) .AND. (iNextPos > 0)
			IF (wScrMode != SCRMODE_FULL) .AND. (Abs(iNextPos - iCurPos) != 1)
				EXIT
			ENDIF
			sNextChar := CharPos(sValue, DWORD(iNextPos))
			// 2.5b TBD chars shouldn't count
			IF ((iNextPos >= iDelStart) .AND. (iNextPos <= iDelEnd)) .OR. (SELF:IsValidChar(sNextChar, iCurPos, TRUE))
				IF SELF:IsEditPos(iNextPos)
					SELF:PutChar(sNextChar, iCurPos)
					SELF:PutChar(" ", iNextPos)
				ELSE
					SELF:PutChar(" ", iCurPos)
				ENDIF
			ELSE
				EXIT
			ENDIF
			iCurPos := iNextPos
			IF !lInvert
				iNextPos := SELF:NextEditPos(iCurPos + 1)
			ELSE
				iNextPos := SELF:PrevEditPos(iCurPos - 1)
			ENDIF
		END
	ENDIF

	IF (SELF:IsEditPos(iCurPos))
		SELF:PutChar(" ", iCurPos)
	ENDIF

	IF lUpdateOwner
		SELF:UpdateEditOwner()
	ENDIF
	RETURN

 /// <exclude />
METHOD DeleteSelection(iStart AS INT, iEnd AS INT) AS VOID STRICT
	LOCAL i AS INT
	LOCAL lInvert AS LOGIC
	LOCAL iDelPos AS INT
	LOCAL iDecPos AS INT
	LOCAL iSignPos AS INT
	LOCAL lNumeric AS LOGIC

	IF (iEnd <= iStart)
		iEnd := iStart
	ELSE
		iEnd--
	ENDIF

	iDelStart := iStart
	iDelEnd := iEnd

	lNumeric := (cType == "N")

	IF lNumeric .AND. ((iDecPos := INT(_CAST, At2(Chr(wDecSep), sValue))) > istart) .AND. (iDecPos < iEnd)
		SELF:DeleteSelection(iStart, iDecPos)
		SELF:DeleteSelection(iDecPos+1, iEnd+1)
	ELSE
		lInvert := ((lNumeric) .AND. (At3(Chr(wDecSep), sValue, DWORD(iEnd)) > 0))
		IF !lInvert
			iDelPos := iStart
			FOR i:= iStart TO iEnd
				IF SELF:IsEditPos(i)
					SELF:DeleteChar(iDelPos, lInvert, FALSE)
					iDelEnd := iDelEnd-1
				ELSEIF (wScrMode == SCRMODE_PART)
					iDelPos := i+1
				ENDIF
			NEXT
		ELSE
			IF (lNumeric)
				iSignPos := INT(_CAST, At2("-", sValue))
				IF (iSignPos > 0)
					SELF:PutChar(" ", iSignPos)
				ENDIF
			ENDIF
			FOR i:= iEnd DOWNTO iStart
				iDelPos := iEnd
				IF SELF:IsEditPos(i)
					SELF:DeleteChar(iDelPos, lInvert, FALSE)
					iDelStart := iDelStart + 1
				ELSE
					iDelPos := i-1
				ENDIF
			NEXT
		ENDIF
		SELF:UpdateEditOwner()

		IF (lInvert .OR. (wScrMode == SCRMODE_NO))
			oEditOwner:__CurPos := SELF:PrevEditPos(iEnd+1)
		ENDIF
	ENDIF

	iDelStart := iDelEnd := 0
	RETURN

 /// <exclude />
ACCESS EmptyValue AS STRING STRICT
	LOCAL sEmpty AS STRING
	LOCAL cCurTplChar AS STRING
	LOCAL i AS DWORD

	sEmpty := ""

	FOR i:=1 TO DWORD(iTemplLen)
		cCurTplChar := CharPos(sTemplate, i)
		IF IsEditTemplChar(cCurTplChar)
			IF (sEmpty == NULL_STRING)
				sEmpty := Space(1)
			ELSE
				sEmpty += " "
			ENDIF
		ELSE
			IF (cCurTplChar == ".") .AND. (cType == "N")
				sEmpty += Chr(wDecSep)
			ELSEIF (cCurTplChar == ",") .AND. (cType == "N")
				sEmpty += Chr(wThousSep)
			ELSE
				sEmpty += cCurTplChar
			ENDIF
		ENDIF
	NEXT

	RETURN sEmpty

 /// <exclude />
CONSTRUCTOR(Owner, PicString, Type, OverWrite, DefTempl, ScrMode)

	DEFAULT(@Type, "C")

	oEditOwner := Owner
	cType := Type // !!! order dependancy: ASSIGN Picture needs type to be set !!!
	SELF:Picture := PicString

	wDecSep := SetDecimalSep()
	wThousSep := SetThousandSep()
	wOverWrite := OverWrite
	wScrMode := ScrMode

	IF !Empty(DefTempl) .AND. Empty(sTemplate)
		sTemplate := DefTempl
		sPicture := sPicture + " " + DefTempl
		iTemplLen := INT(_CAST, SLen(sTemplate))
	ENDIF

	sValue := SELF:EmptyValue

	SELF:UpdateEditOwner()
	RETURN

 /// <exclude />
METHOD InsertChar(cInsChar AS STRING, iPos AS INT, lInvert AS LOGIC) AS VOID STRICT
	LOCAL iCurPos AS INT
	LOCAL iNextPos AS INT
	LOCAL cSaveChar AS STRING
	LOCAL cTempChar AS STRING

	IF IsNil(iPos)
		iCurPos := oEditOwner:__CurPos
	ELSE
		iCurPos := iPos
	ENDIF

	IF !SELF:IsEditPos(iCurPos) .AND. !lInvert
#ifdef __VULCAN__
		IF (cType == "N") .AND. sValue[iCurPos-1] == wDecSep
			iCurPos++
			oEditOwner:__CurPos := iCurPos
		ELSE
			SELF:InvalidAction()
			RETURN
		ENDIF
#else
		IF (cType == "N") .AND. (_NGet(String2Psz( sValue), DWORD(iCurPos-1)) == wDecSep)
			iCurPos++
			oEditOwner:__CurPos := iCurPos
		ELSE
			SELF:InvalidAction()
			RETURN
		ENDIF
#endif
	ENDIF

	IF !lInvert
		iNextPos := SELF:NextEditPos(iCurPos + 1)
	ELSE
		iCurPos--
		iNextPos := SELF:PrevEditPos(iCurPos - 1)
	ENDIF

	cSaveChar := CharPos(sValue, DWORD(iCurPos))
	SELF:PutChar(cInsChar, iCurPos)

	WHILE (iCurPos > 0) .AND. (iNextPos > 0)
		IF (wScrMode != SCRMODE_FULL) .AND. (Abs(iNextPos - iCurPos) != 1)
			//if (iNextPos != iCurPos+1) .and. (wScrMode != SCRMODE_FULL)
			EXIT
		ENDIF
#ifdef __VULCAN__
		IF (cType == "N") .AND. !lInvert .AND. iCurPos < sValue:Length .AND. sValue[iCurPos] == wDecSep
			EXIT
		ENDIF
#else
		IF (cType == "N") .AND. !lInvert .AND. (_NGet(String2Psz( sValue), DWORD(iCurPos)) == wDecSep)
			EXIT
		ENDIF
#endif
		IF (SELF:IsValidChar(cSaveChar, iNextPos, FALSE))
			cTempChar := CharPos(sValue, DWORD(iNextPos))
			SELF:PutChar(cSaveChar, iNextPos)
			cSaveChar := cTempChar
		ELSE
			EXIT
		ENDIF
		iCurPos := iNextPos
		IF !lInvert
			iNextPos := SELF:NextEditPos(iCurPos + 1)
		ELSE
			iNextPos := SELF:PrevEditPos(iCurPos - 1)
		ENDIF
	END
	RETURN

 /// <exclude />
METHOD InvalidAction() AS VOID STRICT
	MessageBeep(0xFFFFFFFF)
   RETURN

 /// <exclude />
METHOD IsEditPos(iPos AS INT) AS LOGIC STRICT
	RETURN IsEditTemplChar(CharPos(sTemplate, DWORD(iPos)))

 /// <exclude />
ACCESS IsEmpty AS LOGIC STRICT
	RETURN (sValue == SELF:EmptyValue)

 /// <exclude />
METHOD IsValidChar(cChar AS STRING, iPos AS INT, lIgnoreBlank AS LOGIC) AS LOGIC STRICT
	RETURN SELF:MatchesTemplChar(cChar, CharPos(sTemplate, DWORD(iPos)), lIgnoreBlank)

#ifdef __VULCAN__
// This implementation avoids unnecesary String -> PSZ conversions that would happen using the VO implementation
 /// <exclude />
METHOD MatchesTemplChar( cTest AS STRING, _cTemplChar AS STRING, lIgnoreBlank AS LOGIC ) AS LOGIC
	LOCAL lIsNumeric := ( cType != "N" ) AS LOGIC
	LOCAL cTemplChar AS CHAR

	IF ! IsEditTemplChar( _cTemplChar ) .AND. lIsNumeric
		RETURN FALSE
	ENDIF

	IF ! String.IsNullOrEmpty( _cTemplChar )
	   cTemplChar := _cTemplChar[0]
	ENDIF

	// speed up this case
	IF cTemplChar == c'X'
		RETURN TRUE
	ENDIF

	IF cTest == " " .AND. lIgnoreBlank
		RETURN TRUE
	ENDIF

	SWITCH cTemplChar

	CASE c'A'
		RETURN IsAlpha( cTest )
	CASE c'N'
		RETURN IsAlNum( cTest )
	CASE c'9'
    CASE c'.'
		RETURN IsDigit( cTest ) .OR. ( cType == "N" .AND. At2( cTest, " +-" ) > 0 )
	CASE c'#'
		RETURN IsDigit( cTest ) .OR. At2( cTest, " +-" ) > 0
	CASE c'!'
		RETURN TRUE
	CASE c'Y'
		RETURN At2( cTest:ToUpper(), __CavoStr( RT_MSG_YNSTRING ) ) > 0
	CASE c'L'
		cTest := cTest:ToUpper()
		RETURN At2( cTest, __CavoStr( RT_MSG_YNSTRING ) ) > 0 .OR. cTest == __CavoStr( RT_MSG_SHORT_TRUE ) .OR. cTest == __CavoStr( RT_MSG_SHORT_FALSE )

	END SWITCH

	RETURN FALSE

#else

 /// <exclude />
METHOD MatchesTemplChar(cTest AS STRING, cTemplChar AS STRING, lIgnoreBlank AS LOGIC) AS LOGIC STRICT
	LOCAL lIsNumeric := (cType != "N") AS LOGIC
   LOCAL cYesNo, cTrue, cFalse AS STRING
	IF !IsEditTemplChar(cTemplChar) .AND. lIsNumeric
		RETURN FALSE
	ENDIF

	// speed up this case
	IF (cTemplChar == "X")
		RETURN TRUE
	ENDIF

	IF (cTest == " " .AND. lIgnoreBlank)
		RETURN TRUE
	ENDIF

	DO CASE
	CASE cTemplChar == "A"
		RETURN IsAlpha(PSZ(_CAST, cTest))
	CASE cTemplChar == "N"
		RETURN IsAlNum(PSZ(_CAST, cTest))
	CASE (cTemplChar == "9" .OR. cTemplChar == ".")
		RETURN IsDigit(PSZ(_CAST, cTest)) .OR. (cType == "N" .AND. (At2(cTest, " +-") > 0))
	CASE cTemplChar == "#"
		RETURN IsDigit(PSZ(_CAST, cTest)) .OR. (At2(cTest, " +-") > 0)
	CASE cTemplChar == "!"
		RETURN TRUE
	CASE cTemplChar == "Y"
		RETURN (At2(Upper(cTest), Psz2String(__CavoStr(RT_MSG_YNSTRING))) > 0)
	CASE cTemplChar == "L"
		cTest := Upper(cTest)
		cYesNo := Psz2String(__CavoStr(RT_MSG_YNSTRING))
		cTrue  := Psz2String(__CavoStr(RT_MSG_SHORT_TRUE))
		cFalse := Psz2String(__CavoStr(RT_MSG_SHORT_FALSE))
		RETURN ((At2(cTest,cYesNo) >0) .OR. (cTest == cTrue) .OR. (cTest == cFalse))
	CASE cTemplChar == "." .AND. cType == "N"
		RETURN TRUE
	ENDCASE

	RETURN FALSE
#endif

 /// <exclude />
METHOD NextEditPos(iPos AS INT) AS INT STRICT
	LOCAL i AS INT

	IF (iPos == -1)
		iPos := oEditOwner:__CurPos + 1
	ENDIF

	FOR i := iPos TO iTemplLen
		IF IsEditTemplChar(CharPos(sTemplate, DWORD(i)))
			RETURN i
		ELSEIF (cType == "N") .AND. (CharPos(sTemplate, DWORD(i)) == ".") .AND. (wOverWrite != OVERWRITE_ALWAYS) .AND.;
			(wOverWrite != OVERWRITE_ONKEY .OR. !IsOverWriteModeEnabled())
			RETURN i
		ENDIF
	NEXT

	RETURN iPos

 /// <exclude />
METHOD Paste() AS LOGIC STRICT
	LOCAL hGlb AS PTR
	LOCAL pszPaste AS PSZ
	LOCAL sPaste AS STRING
	LOCAL i, iLen AS INT
	LOCAL iStart, iEnd AS INT
	LOCAL oKeyEvent AS KeyEvent

	IF (!IsClipboardFormatAvailable(CF_TEXT))
		RETURN FALSE
	ENDIF

	IF (!OpenClipboard(NULL_PTR))
		RETURN FALSE
	ENDIF

	hGlb := GetClipboardData(CF_TEXT)

	IF (hGlb != NULL_PTR)
		pszPaste := GlobalLock(hGlb)
		IF (PTR(_CAST, pszPaste) != NULL_PTR)
			SendMessage(oEditOwner:Handle(), EM_GETSEL, DWORD(_CAST, @iStart), LONGINT(_CAST, @iEnd))
			IF ((iEnd - iStart) > 0)
				SELF:DeleteSelection(iStart+1, iEnd+1)
			ENDIF
			oKeyEvent := KeyEvent{oEditOwner:Handle(), WM_CHAR, 0, 0}
			sPaste := Psz2String(pszPaste)
			iLen := INT(_CAST, SLen(sPaste))
			FOR i := 1 TO iLen
				oKeyEvent:wParam := Asc(CharPos(sPaste,DWORD(i)))
				SELF:ProcessKeyEvent(oKeyEvent)
			NEXT
		ENDIF
		GlobalUnlock(hGlb)
	ENDIF

	CloseClipboard()

	RETURN TRUE

 /// <exclude />
ACCESS Picture AS STRING STRICT
	RETURN sPicture

 /// <exclude />
ASSIGN Picture(cNewPicture AS STRING)  STRICT
	LOCAL pLogic AS LOGIC PTR
	LOCAL iSpcPos, iFuncPos, i AS DWORD
	LOCAL sFuncChar AS STRING
	#ifndef __VULCAN__
	LOCAL iAsc, iAscD, iAscM, iAscY, iAsc9 AS DWORD
	#endif

	sPicture := cNewPicture
	pLogic := (LOGIC PTR) @FuncFlags
	// handle functions
	IF (Left(sPicture, 1) == "@")
		iSpcPos := At2(" ", sPicture)
		IF (iSpcPos == 0)
			iSpcPos :=  SLen(sPicture) + 1
		ENDIF
		FOR i := 2 TO (iSpcPos-1)
			sFuncChar := CharPos(sPicture, i)
			iFuncPos := At2(sFuncChar, "BCDERXZ(!A")
			IF (iFuncPos > 0)
				#warning incompatibility with VO
				// In X#, LOGIC PTR uses increments of 1 bytes, but each LOGIC field in the strucPictureFuncFlags
				// takes 4 bytes, so we need the * 4 below:
				LOGIC(pLogic + (iFuncPos - 1) * 4) := TRUE
//				LOGIC(pLogic + iFuncPos - 1) := TRUE
			ENDIF
		NEXT
	ENDIF

	sTemplate := SubStr2(sPicture, (iSpcPos + 1))
	iTemplLen := INT(_CAST, SLen(sTemplate))

	#ifndef __VULCAN__
	iAscD :=  68 	// Asc("D")
	iAscM :=  77 	// Asc("M")
	iAscY :=  89 	// Asc("Y")
	iAsc9 :=  57 	// Asc("9")
	#endif
	IF ((cType == "D") .OR. (cType == "C")) .AND. FuncFlags:lSetDate
		sTemplate := GetDateFormat()
		iTemplLen := LONGINT(SLen(sTemplate))
#ifdef __VULCAN__
      sTemplate := sTemplate:Replace( 'D', '9' ):Replace( 'M', '9' ):Replace( 'Y', '9' )
#else
		FOR i:=1 TO DWORD(iTemplLen)
			iAsc := Asc(CharPos(sTemplate, i))
			IF (iAsc == iAscD) .OR. (iAsc == iAscM) .OR. (iAsc == iAscY)
				_NPut(PSZ(_CAST, sTemplate), i-1, iAsc9)
			ENDIF
		NEXT
#endif
	ENDIF

	IF (iTemplLen == 0)
		IF FuncFlags:lAlphaOnly
			sTemplate := Replicate("A", Min(Min(DEFAULT_STRING_TEMPL_SIZE, oEditOwner:TextLimit), oEditOwner:__FSLength))
		ELSEIF FuncFlags:lConvUpper
			sTemplate := Replicate("!", Min(Min(DEFAULT_STRING_TEMPL_SIZE, oEditOwner:TextLimit), oEditOwner:__FSLength))
		ENDIF
		iTemplLen := LONGINT(SLen(sTemplate))
	ENDIF

	RETURN

 /// <exclude />
METHOD PrevEditPos(iPos AS INT) AS INT STRICT
	LOCAL i AS DWORD

	IF (iPos == -1)
		iPos := oEditOwner:__CurPos - 1
	ENDIF

	FOR i := DWORD(iPos) DOWNTO 1
		IF IsEditTemplChar(CharPos(sTemplate, i))
			RETURN LONGINT(i)
		ELSEIF (cType == "N") .AND. (CharPos(sTemplate, i) == ".") .AND. (wOverWrite != OVERWRITE_ALWAYS) .AND.;
			(wOverWrite != OVERWRITE_ONKEY .OR. !IsOverWriteModeEnabled())
			RETURN LONGINT(i)
		ENDIF
	NEXT

	RETURN 0L

 /// <exclude />
METHOD ProcessChar(cChar AS STRING) AS LOGIC STRICT
	LOCAL iCurPos AS INT
	LOCAL cTemplChar AS STRING
	LOCAL lInvert AS LOGIC
	LOCAL iDecPos AS INT

	iCurPos 		:= oEditOwner:__CurPos
	cTemplChar 	:= CharPos(sTemplate, DWORD(iCurPos))

	IF SELF:MatchesTemplChar(cChar, cTemplChar, FALSE)
		IF cTemplChar == "!"
			UpperA(@cChar)
		ENDIF
#ifdef __VULCAN__
		IF ((wOverWrite == OVERWRITE_ALWAYS) .OR. (wOverWrite == OVERWRITE_ONKEY .AND. IsOverWriteModeEnabled())) .OR.	((cType == "N") .AND. (sValue[iCurPos-1]) == 32)
			SELF:PutChar(cChar, iCurPos)
		ELSE
			lInvert := (cType == "N") .AND. ((iDecPos := INT(_CAST, At3(Chr(wDecSep), sValue, iCurPos-1))) > 0) .AND. ((At2(" ", SubStr3(sValue, DWORD(_CAST, iCurPos+1), Max(iDecPos-iCurPos,0))) == 0) .OR. (iCurPos == iDecPos)) .AND. sValue[Max(0, SELF:NextEditPos(0)-1)] == 32
			SELF:InsertChar(cChar, iCurPos, lInvert)
		ENDIF
#else
		IF ((wOverWrite == OVERWRITE_ALWAYS) .OR. (wOverWrite == OVERWRITE_ONKEY .AND. IsOverWriteModeEnabled())) .OR.;
			((cType == "N") .AND. (_NGet(String2Psz(sValue), DWORD(iCurPos-1)) == 32))
			SELF:PutChar(cChar, iCurPos)
		ELSE
			lInvert := (cType == "N") .AND. ((iDecPos := INT(_CAST, At3(Chr(wDecSep), sValue, DWORD(iCurPos-1)))) > 0);
				.AND. ((At2(" ", SubStr3(sValue, DWORD(iCurPos+1), Max(iDecPos-iCurPos,0))) == 0) .OR.;
				 (iCurPos == iDecPos)) .AND. (_NGet(String2Psz(sValue), Max(0, SELF:NextEditPos(0)-1)) == 32)
			SELF:InsertChar(cChar, iCurPos, lInvert)
		ENDIF
#endif
		SELF:UpdateEditOwner()
		IF !lInvert
			iCurPos := SELF:NextEditPos(-1)
		ENDIF
		IF ((iCurPos > iTemplLen) .OR. (!SELF:IsEditPos(iCurPos)) .AND. cType != "N") .AND. oEditOwner:AutoFocusChange
			SendMessage(GetParent(oEditOwner:Handle()), WM_NEXTDLGCTL, 0, 0L)
		ELSE
			oEditOwner:__CurPos := iCurPos
		ENDIF
	ELSE
		SELF:InvalidAction()
	ENDIF

	RETURN TRUE
#pragma warnings(219,off)
// warning about assignment to iCurPos. We have to call oEditOwner:__CurPos for code inside that property
 /// <exclude />
METHOD ProcessKeyEvent(oKeyEvt AS KeyEvent) AS LOGIC STRICT
	LOCAL iCurPos AS INT
	LOCAL uMsg AS DWORD
	LOCAL lRet AS LOGIC
	LOCAL iStart AS INT
	LOCAL iEnd AS INT
	LOCAL dwKCode AS DWORD

	uMsg := oKeyEvt:umsg
	// causes code in oEditOwner:__CurPos to be executed, do not remove
	iCurPos := oEditOwner:__CurPos
	lRet := TRUE

    SWITCH uMsg
	CASE WM_KEYDOWN
		dwKCode := oKeyEvt:KeyCode
		SWITCH dwKCode
		CASE KEYARROWLEFT
			oEditOwner:__CurPos := SELF:PrevEditPos(-1)
		CASE KEYARROWRIGHT
			oEditOwner:__CurPos := SELF:NextEditPos(-1)
		CASE KEYHOME
			oEditOwner:__CurPos := SELF:NextEditPos(1)
		CASE KEYEND
			IF lEndKey
				oEditOwner:__CurPos := SELF:PrevEditPos(iTemplLen) + 1
			ELSE
				oEditOwner:__CurPos := SELF:PrevEditPos(INT(_CAST, SLen(Trim(sValue)))) + 1
			ENDIF
			lEndKey := !lEndKey
		CASE KEYDELETE
        CASE KEYBACKSPACE
			sOldValue := SClone(sValue)
			SendMessage(oEditOwner:Handle(), EM_GETSEL, DWORD(_CAST, @iStart), LONG(_CAST, @iEnd))
			IF (dwKCode == KEYDELETE) .OR. (iStart != iEnd)
				SELF:DeleteSelection(iStart+1, iEnd+1)
				//oEditOwner:__CurPos := iStart
			ELSE
				oEditOwner:__CurPos := SELF:PrevEditPos(-1)
				SELF:DeleteChar(oEditOwner:__CurPos, FALSE, TRUE)
			ENDIF
		OTHERWISE
			lRet := FALSE
		END SWITCH
	CASE WM_CHAR
		sOldValue := SClone(sValue)
		IF (oKeyEvt:ASCIIChar == wDecSep) .AND. (cType == "N") .AND. (SELF:DecimalPos > 0)
			oEditOwner:__CurPos := SELF:DecimalPos + 1
			// change for 2.5b Copy/cut/paste didn't work
		ELSEIF (oKeyEvt:wParam >= 32)
			SendMessage(oEditOwner:Handle(), EM_GETSEL, DWORD(_CAST, @iStart), LONG(_CAST, @iEnd))
			IF ((iEnd - iStart) > 0)
				SELF:DeleteSelection(iStart+1, iEnd+1)
			ENDIF
			lRet := SELF:ProcessChar(Chr(oKeyEvt:ASCIIChar))
		ELSE
			lRet := (oKeyEvt:KeyCode == KEYBACKSPACE)
		ENDIF
	END SWITCH

	RETURN lRet

#pragma warnings(219,on)

 /// <exclude />
METHOD PutChar(cChar AS STRING, iPos AS INT) AS VOID STRICT
#ifdef __VULCAN__
   // VO version modifies existing string, this is illegal in .NET
   IF iPos <= sValue:Length .AND. ! String.IsNullOrEmpty( cChar )
      LOCAL sb := System.Text.StringBuilder{ sValue } AS System.Text.StringBuilder
      LOCAL ch := cChar[0] AS CHAR
      IF FuncFlags:lConvUpper
         ch := Char.ToUpper( ch )
      ENDIF
      sb[iPos - 1] := ch
      sValue := sb:ToString()
   ENDIF
#else
	LOCAL pString AS BYTE PTR
	LOCAL iOffset AS INT

	IF (iPos <= INT(_CAST, SLen(sValue)))
		iOffset := (iPos -1)
		IF FuncFlags:lConvUpper
			UpperA(cChar)
		ENDIF
		pString := PTR(_CAST, sValue)
		BYTE(pString + iOffSet) := Asc(cChar)
	ENDIF
#endif
	RETURN

 /// <exclude />
METHOD TestFirstChar(cChar)
	LOCAL iCurPos    AS LONGINT
	iCurPos := 1
	IF !SELF:IsEditPos(iCurPos)
		iCurPos := SELF:NextEditPos(iCurPos)
		oEditOwner:__CurPos := iCurPos
	ENDIF
	RETURN NIL

 /// <exclude />
ACCESS Type AS STRING STRICT
	RETURN cType

 /// <exclude />
ASSIGN TYPE(cNewType AS STRING)  STRICT
	RETURN (cType := cNewType)

 /// <exclude />
METHOD Undo() AS LOGIC STRICT
	sValue := sOldValue
	SELF:UpdateEditOwner()

	RETURN TRUE

 /// <exclude />
METHOD UpdateEditOwner() AS VOID STRICT
	LOCAL iSaveCurPos AS INT

	iSaveCurPos := oEditOwner:__CurPos

	oEditOwner:__ForceModFlag2True := TRUE
	oEditOwner:__SetText(sValue)
	oEditOwner:Modified := TRUE
	oEditOwner:__ForceModFlag2True := FALSE
	oEditOwner:__CurPos := iSaveCurPos
	RETURN

 /// <exclude />
ASSIGN UsualValue(uNewValue AS USUAL)  STRICT
	IF !IsNil(uNewValue)
		cType := ValType(uNewValue)
	ENDIF

	IF !IsNil(uNewValue) .AND. (CType == "C") .AND. (iTemplLen > INT(_CAST, Len(uNewValue)))
		uNewValue := uNewValue + Space(DWORD(iTemplLen) - Len(uNewValue))
	ENDIF

	IF !IsNil(uNewValue)
		sValue := Transform(uNewValue, sPicture)
	ELSE
		sValue := SELF:EmptyValue
	ENDIF

	oEditOwner:__SetText(sValue)

	RETURN

 /// <exclude />
ACCESS Template
RETURN SELF:sTemplate
END CLASS

STATIC FUNCTION IsEditTemplChar(cTest AS STRING) AS LOGIC STRICT
   RETURN LOGIC(_CAST, At2(cTest, "ANX9!YL#"))

STATIC FUNCTION IsOverWriteModeEnabled() AS LOGIC STRICT
   /*STATIC*/ LOCAL DIM aKeyStates[256] AS BYTE // compiler bug!!!

   GetKeyboardState(@aKeyStates)

   RETURN !LOGIC(_CAST, _AND(aKeyStates[VK_INSERT + 1], 1))

#region defines
DEFINE DEFAULT_STRING_TEMPL_SIZE := 128
#endregion