{*********************************************************}
{*                    OVCPB.PAS 4.06                    *}
{*********************************************************}

{* ***** BEGIN LICENSE BLOCK *****                                            *}
{* Version: MPL 1.1                                                           *}
{*                                                                            *}
{* The contents of this file are subject to the Mozilla Public License        *}
{* Version 1.1 (the "License"); you may not use this file except in           *}
{* compliance with the License. You may obtain a copy of the License at       *}
{* http://www.mozilla.org/MPL/                                                *}
{*                                                                            *}
{* Software distributed under the License is distributed on an "AS IS" basis, *}
{* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License   *}
{* for the specific language governing rights and limitations under the       *}
{* License.                                                                   *}
{*                                                                            *}
{* The Original Code is TurboPower Orpheus                                    *}
{*                                                                            *}
{* The Initial Developer of the Original Code is TurboPower Software          *}
{*                                                                            *}
{* Portions created by TurboPower Software Inc. are Copyright (C)1995-2002    *}
{* TurboPower Software Inc. All Rights Reserved.                              *}
{*                                                                            *}
{* Contributor(s):                                                            *}
{*                                                                            *}
{* ***** END LICENSE BLOCK *****                                              *}


{$I OVC.INC}

{$B-} {Complete Boolean Evaluation}
{$I+} {Input/Output-Checking}
{$P+} {Open Parameters}
{$T-} {Typed @ Operator}
{.W-} {Windows Stack Frame}
{$X+} {Extended Syntax}

unit ovcpb;
  {-Base picture field class, Picture and Numeric fields are derived from this}

interface

uses
  {$IFNDEF LCL} Windows, Messages, {$ELSE} LclIntf, LMessages, LclType, MyMisc, {$ENDIF}
  Classes, Graphics, SysUtils, OvcConst,
  OvcData, OvcEF, OvcStr;

type
  TOvcPictureBase = class(TOvcBaseEntryField)
  {.Z+}
  protected {private}
    pfSemiLits   : Byte;               {# of semi-literals in field}
    pfPicFlags   : TPictureFlags;      {picture flags array}
    pfSelPos     : Integer;            {current position of selection highlight}

    {windows message methods}
    procedure WMSetFocus(var Msg : TWMSetFocus);
      message WM_SETFOCUS;
    procedure WMKillFocus(var Msg : TWMKillFocus);
      message WM_KILLFOCUS;

  protected
    procedure CreateWnd;
      override;

    function efCanClose(DoValidation : Boolean) : Boolean;
      override;
      {-return True if window can be closed}
    procedure efCaretToEnd;
      override;
      {-move the caret to the end of the field}
    procedure efCaretToStart;
      override;
      {-move the caret to the beginning of the field}
    procedure efChangeMask(Mask : PAnsiChar);
      override;
      {-change the picture mask}
    function efEditBegin : Word;
      override;
      {-return offset of first editable position in field}
    function efEditEnd : Word;
      {-return offset of last editable position in field}
    function  efFieldIsEmpty : Boolean;
      override;
      {-return True if the field is empty}
    function efGetDisplayString(Dest : PAnsiChar; Size : Word) : PAnsiChar;
      override;
      {-return the display string in Dest and a pointer as the result}
    procedure efRemoveBadOptions;
      override;
      {-remove inappropriate options for this field and data type}
    procedure efSetCaretPos(Value : Integer);
      override;
      {-set position of caret within the field}

    function  pbCalcLength : Word;
      {-calculate length of efEditSt}
    procedure pbCalcWidthAndPlaces(var Width, Places : Word);
      {-calculate width and decimal places for a numeric field}
    procedure pbFixDecimalPoint(P : PAnsiChar);
      {-fix decimal points for real numbers before merging}
    procedure pbFixNumber(DotPos, StBgn, StEnd : Integer; FirstHalf, SecondHalf : Boolean);
      {-fix the first and/or second half of a numeric field}
    function  pbIsLiteral(N : Word) : Boolean;
      {-return True if N'th mask character is a literal. N is 0-based}
    procedure pbInitPictureFlags;
      {-initialize picture mask flags}
    function  pbIsNumber : Boolean;
      {-return True if field is treated as numeric by MergePicture}
    function  pbIsSemiLiteral(N : Word) : Boolean;
      {-return True if N'th mask character is a semi-literal. N is 0-based}
    procedure pbMergePicture(const Dest, Src : PAnsiChar);
      {-merge Src with efPicture and return result in Dest}
    procedure pbOptimizeInitPictureFlags;
      {-see if we can optimize InitPictureFlags}
    procedure pbRemoveSemiLits;
      virtual;
      {-remove semi-literal mask characters from the edit string}
    procedure pbRestoreSemiLits;
      {-restore semi-literal mask characters in the edit string}
    procedure pbStripLiterals(Dest, Src : PAnsiChar);
      {-strip literal mask characters from source and put result in Dest}
    procedure pbStripPicture(const Dest, Src : PAnsiChar);
      {-strip the mask characters from Src and put result in Dest}

  public
    {interfaced for internal use only--to allow validation}
    {helper routines access to the picture flags array}
    procedure pbGetPictureFlags(var Flags : TPictureFlags);
      {-return an array of the fields picture flags}
    function GetStrippedEditString : string;
      override;
      {-return edit string stripped of literals and semi-literals}
    procedure MergeWithPicture(const S : string);
      override;
      {-combines S with the picture mask and updates the edit string}
    function ValidateContents(ReportError : Boolean) : Word;
      override;
  {.Z-}
  end;


implementation


{*** TOvcPictureBase ***}

procedure TOvcPictureBase.CreateWnd;
begin
  pbInitPictureFlags;

  inherited CreateWnd;

  {set flags for Real fields and clear DecimalPlaces if decimal point is in mask}
  case efDataType mod fcpDivisor of
    fsubReal, fsubExtended, fsubDouble, fsubSingle, fsubComp :
      begin
        Include(sefOptions, sefRealVar);
        if StrScan(efPicture, pmDecimalPt) <> nil then
          DecimalPlaces := 0;
      end;
  end;
end;

procedure TOvcPictureBase.pbGetPictureFlags(var Flags : TPictureFlags);
  {-return an array of the fields picture flags}
begin
  Flags := pfPicFlags;
end;

function TOvcPictureBase.pbIsLiteral(N : Word) : Boolean;
  {-return True if N'th mask character is a literal. N is 0-based}
begin
  Result := pfPicFlags[N] = pflagLiteral;
end;

function TOvcPictureBase.pbIsSemiLiteral(N : Word) : Boolean;
  {-return True if N'th mask character is a semi-literal. N is 0-based}
begin
  Result := pfPicFlags[N] = pflagSemiLit;
end;

procedure TOvcPictureBase.pbFixNumber(DotPos, StBgn, StEnd : Integer; FirstHalf, SecondHalf : Boolean);
  {-fix the first and/or second half of a numeric field}
var
  I, J, K : Integer;
  SaveHP  : Word;

  function StartOfSubField : Word;
  begin
    Result := efHPos;
    while (Result > StBgn) and not pbIsLiteral(Result - 1) do
      Dec(Result);
  end;

  function EndOfSubField : Word;
  begin
    Result := efHPos;
    while (Result < StEnd) and not pbIsLiteral(Result + 1) do
      Inc(Result);
  end;

begin
  SaveHP := efHPos;

  if FirstHalf then begin
    {bring numbers to left of decimal flush right}
    if DotPos = -1 then
      efHPos := StEnd
    else
      efHPos := DotPos-1;
    K := EndOfSubField;
    J := StartOfSubField;
    I := J;
    while efEditSt[I] = ' ' do
      Inc(I);
    while I <= K do begin
      if efEditSt[I] = ' ' then begin
        StrStDeletePrim(efEditSt, I, 1);
        StrChInsertPrim(efEditSt, ' ', J);
      end;
      Inc(I);
    end;

    {make sure it isn't all blanks to left of decimal}
    if efEditSt[K] = ' ' then
      efEditSt[K] := '0';
  end;

  if (DotPos <> -1) and SecondHalf then begin
    efHPos := DotPos+1;

    {bring numbers to right of decimal flush left}
    J := EndOfSubField;
    if efHPos <= J then begin
      K := J;
      J := StartOfSubField;
      I := K;
      while efEditSt[I] = ' ' do begin
        efEditSt[I] := '0';
        Dec(I);
      end;
      while I >= J do begin
        if efEditSt[I] = ' ' then begin
          StrStDeletePrim(efEditSt, I, 1);
          StrChInsertPrim(efEditSt, '0', K);
        end;
        Dec(I);
      end;
    end;
  end;

  efHPos := SaveHP;
end;

procedure TOvcPictureBase.pbStripLiterals(Dest, Src : PAnsiChar);
  {-strip literal mask characters from source and put result in Dest}
var
  I : Word;
begin
  for I := 0 to MaxLength-1 do begin
    if not pbIsLiteral(I) then begin
      Dest^ := Src^;
      Inc(Dest);
    end;
    Inc(Src);
  end;
  Dest^ := #0;
end;

procedure TOvcPictureBase.pbFixDecimalPoint(P : PAnsiChar);
  {-fix decimal points for real numbers before merging}
var
  PT : PAnsiChar;
begin
  PT := StrScan(P, pmDecimalPt);
  if PT <> nil then
    PT^ := IntlSupport.DecimalChar;
end;

procedure TOvcPictureBase.pbRestoreSemiLits;
  {-restore semi-literal mask characters in the edit string}
var
  P : PChar;
begin
  if not (sefFixSemiLits in sefOptions) then
    Exit;

  Exclude(sefOptions, sefFixSemiLits);
  pbStripPicture(efEditSt, efEditSt);
  P := StrScan(efEditSt, IntlSupport.DecimalChar);
  if P <> nil then
    P^ := pmDecimalPt;
  pbMergePicture(efEditSt, efEditSt);
  Invalidate;
end;

procedure TOvcPictureBase.pbInitPictureFlags;
var
  I : Word;
begin
  if sefNoLiterals in sefOptions then begin
    FillChar(pfPicFlags, MaxLength, pflagFormat);
    pfPicFlags[MaxLength] := pflagLiteral;
  end else begin
    FillChar(pfPicFlags, MaxLength+1, pflagLiteral);
    for I := 0 to MaxLength-1 do
      if efNthMaskChar(I) in PictureChars then
        pfPicFlags[I] := pflagFormat
      else
        case efNthMaskChar(I) of
          pmFloatDollar, pmComma :
            pfPicFlags[I] := pflagSemiLit;
        end;
  end;
end;

function TOvcPictureBase.pbIsNumber : Boolean;
  {-return True if field is treated as numeric by MergePicture}
begin
  Result :=
    (pfSemiLits <> 0) or
    (sefNumeric in sefOptions) or
    (sefHexadecimal in sefOptions) or
    (sefOctal in sefOptions) or
    (sefBinary in sefOptions) or
    (StrScan(efPicture, pmDecimalPt) <> nil) or
    (StrScan(efPicture, pmCurrencyLt) <> nil) or
    (StrScan(efPicture, pmCurrencyRt) <> nil);
end;

function TOvcPictureBase.pbCalcLength : Word;
  {-calculate length of efEditSt}
var
  I : Integer;
begin
  I := efEditEnd;
  while (I >= 0) and ((efEditSt[I] = ' ') or pbIsLiteral(I)) do
    Dec(I);
  Result := I + 1;
end;

function TOvcPictureBase.efGetDisplayString(Dest : PAnsiChar; Size : Word) : PAnsiChar;
  {-return the display string in Dest}
var
  I : Integer;
begin
  Result := inherited efGetDisplayString(Dest, Size);

  if Uninitialized and not (sefHaveFocus in sefOptions) then begin
    FillChar(Dest[0], MaxLength, ' ');
    Dest[MaxLength] := #0;
    Exit;
  end;

  if (efoPasswordMode in Options) or (PadChar <> ' ') then begin
    I := MaxLength-1;
    while I >= 0 do begin
      while (I >= 0) and pbIsLiteral(I) do
        Dec(I);

      if efFieldClass <> fcNumeric then
        while (I >= 0) and (not pbIsLiteral(I)) and (Dest[I] = ' ') do begin
          Dest[I] := PadChar;
          Dec(I);
        end;

      while (I >= 0) and (not pbIsLiteral(I)) and (Dest[I] <> ' ') do begin
        if (efoPasswordMode in Options) then
          Dest[I] := PasswordChar;
        Dec(I);
      end;

      if efFieldClass = fcNumeric then
        while (I >= 0) and (not pbIsLiteral(I)) and (Dest[I] = ' ') do begin
          Dest[I] := PadChar;
          Dec(I);
        end;
    end;
  end;
end;

function TOvcPictureBase.efFieldIsEmpty : Boolean;
  {-return True if the field is empty}
var
  I : Word;
begin
  I := 0;
  Result := True;
  while (I+1 <= MaxLength) and Result do
    if (not pbIsLiteral(I)) and (efEditSt[I] <> ' ') then
      Result := False
    else
      Inc(I);
end;

procedure TOvcPictureBase.efRemoveBadOptions;
  {-remove inappropriate options for this field and data type}
begin
  if csLoading in ComponentState then
    Exit;

  case efDataType of
    fidPictureString :
      if not (sefNoLiterals in sefOptions) then begin
        Exclude( FOptions, efoRightJustify );
        Exclude( FOptions, efoTrimBlanks );
      end;
  else
    inherited efRemoveBadOptions;
  end;
end;

procedure TOvcPictureBase.efSetCaretPos(Value : Integer);
  {-set position of caret within the field}
begin
  if not (sefHaveFocus in sefOptions) then
    Exit;

  if Value < efEditBegin then
    efHPos := efEditBegin
  else if Value > efEditEnd then
    efHPos := efEditEnd + 1
  else begin
    while pbIsLiteral(Value) do
      Dec(Value);
    efHPos := Value;
  end;
  efPositionCaret(True);
end;

procedure TOvcPictureBase.efCaretToStart;
  {-move the caret to the beginning of the field}
begin
  efHPos := efEditBegin;
  efHOffset := 0;
end;

procedure TOvcPictureBase.efCaretToEnd;
  {-move the caret to the end of the field}
var
  StEnd : Word;
begin
  efHPos := pbCalcLength;
  StEnd := efEditEnd;
  while (efHPos < StEnd) and pbIsLiteral(efHPos) do
    Inc(efHPos);
end;

function TOvcPictureBase.efEditBegin : Word;
  {-return offset of first editable position in field}
var
  I : Word;
begin
  I := 0;
  while (I < MaxLength-1) and (pfPicFlags[I] = pflagLiteral) do
    Inc(I);
  Result := I + pfSemiLits;
end;

function TOvcPictureBase.efEditEnd : Word;
  {-return offset of last editable position in field}
begin
  Result := MaxLength - 1;
  while (Result > 0) and (pfPicFlags[Result] = pflagLiteral) do
    Dec(Result);
end;

procedure TOvcPictureBase.efChangeMask(Mask : PAnsiChar);
  {-change the picture mask}
var
  I   : Cardinal;
  Buf : array[0..MaxEditLen] of Char;
begin
  {save current value}
  pbStripPicture(Buf, efEditSt);

  inherited efChangeMask(Mask);

  {disallow 'p' and 'g' in picture fields}
  if efFieldClass <> fcNumeric then begin
    if StrChPos(efPicture, pmNegParens, I) then
      StrChDeletePrim(efPicture, I);
    if StrChPos(efPicture, pmNegHere, I) then
      StrChDeletePrim(efPicture, I);
  end;

  efPicLen := StrLen(efPicture);
  if (MaxLength < efPicLen) or
     ((MaxLength <> efPicLen) and (efFieldClass = fcNumeric)) then begin
    if not (csLoading in ComponentState) then
      MaxLength := efPicLen;
  end;

  {clear the edit string}
  FillChar(efEditSt, MaxEditLen, #0);

  {see if we can optimize InitPictureFlags}
  pbOptimizeInitPictureFlags;

  {restore value}
  pbMergePicture(efEditSt, Buf);
end;

function TOvcPictureBase.GetStrippedEditString : string;
  {-return edit string stripped of literals and semi-literals}
var
  Buf : TEditString;
begin
  {get copy of edit string and limit to MaxEditLen}
  StrLCopy(Buf, efEditSt, MaxEditLen);

  {strip the copy of the edit string}
  pbStripPicture(Buf, Buf);
  Result := StrPas(Buf);
end;

procedure TOvcPictureBase.MergeWithPicture(const S : string);
  {-combines S with the picture mask and updates the edit string}
var
  Buf : TEditString;
begin
  HandleNeeded;
  StrPCopy(Buf, S);
  pbMergePicture(efEditSt, Buf);
end;

function TOvcPictureBase.efCanClose(DoValidation : Boolean) : Boolean;
  {-return True if window can be closed}
var
  DotPos : Cardinal;
begin
  if efFieldClass = fcPicture then
    if ((sefModified in sefOptions) and (sefHaveFocus in sefOptions)) then
      if StrChPos(efPicture, pmDecimalPt, DotPos) then
        pbFixNumber(DotPos, efEditBegin, efEditEnd, True, True);
  Result := inherited efCanClose(DoValidation);
end;

procedure TOvcPictureBase.pbRemoveSemiLits;
  {-remove semi-literal mask characters from the edit string}
var
  I : Cardinal;
  B : Word;
  E : Word;
  P : Word;
  D : Word;
  Buf : array[0..255] of Char;
begin
  if pfSemiLits = 0 then begin
    if (sefHexadecimal in sefOptions) or (sefOctal in sefOptions) or
       (sefBinary in sefOptions) then
      Include(sefOptions, sefFixSemiLits);
    Exit;
  end;

  Include(sefOptions, sefFixSemiLits);

  B := efEditBegin;
  E := efEditEnd;
  P := B - pfSemiLits;

  if StrScan(efPicture, pmFloatDollar) <> nil then begin
    StrPCopy(Buf, IntlSupport.CurrencyLtStr);
    if StrStPos(efEditSt, Buf, I) then begin
      D := StrLen(Buf);
      StrStDeletePrim(efEditSt, I, D);
      StrInsertChars(efEditSt, ' ', P, D);
      Inc(P, D);
    end;
  end;

  if StrScan(efPicture, pmComma) <> nil then
    for I := P{B} to E do
      if (efEditSt[I] = IntlSupport.CommaChar) then begin
        StrStDeletePrim(efEditSt, I, 1);
        StrChInsertPrim(efEditSt, ' ', P);
        Inc(P);
      end;
end;

procedure TOvcPictureBase.pbCalcWidthAndPlaces(var Width, Places : Word);
  {-calculate width and decimal places for a numeric field}
var
  I      : Word;
  DotPos : Cardinal;
begin
  pbInitPictureFlags;

  {find position of period and calculate decimal places}
  if not StrChPos(efPicture, pmDecimalPt, DotPos) then
    Places := DecimalPlaces
  else begin
    Places := 0;
    I := DotPos+1;
    while (pfPicFlags[I] = pflagFormat) do begin
      Inc(Places);
      Inc(I);
    end;
  end;

  {calculate width}
  I := 0;
  Width := 0;
  while pbIsLiteral(I) or pbIsSemiLiteral(I) do
    Inc(I);
  while (pfPicFlags[I] = pflagFormat) or (efNthMaskChar(I) = pmComma) do begin
    Inc(Width, Ord(pfPicFlags[I] = pflagFormat));
    Inc(I);
  end;

  {add decimal places and period}
  if (DotPos <> $FFFF) and (Places <> 0) then
    Inc(Width, Places + 1);
end;

procedure TOvcPictureBase.pbOptimizeInitPictureFlags;
  {-see if we can optimize InitPictureFlags}
var
  I : Word;
begin
  pfSemiLits := 0;
  Exclude(sefOptions, sefNoLiterals);
  pbInitPictureFlags;
  Include(sefOptions, sefNoLiterals);
  for I := 0 to MaxLength-1 do
    case pfPicFlags[I] of
      pflagLiteral :
        Exclude(sefOptions, sefNoLiterals);
      pflagSemiLit :
        begin
          Inc(pfSemiLits);
          Exclude(sefOptions, sefNoLiterals);
        end;
    end;

  {if we have literals, turn off TrimBlanks and RightJustify}
  if not (sefNoLiterals in sefOptions) then begin
    Exclude( FOptions, efoTrimBlanks );
    Exclude( FOptions, efoRightJustify );
  end;
end;

procedure TOvcPictureBase.pbMergePicture(const Dest, Src : PAnsiChar);
  {-merge Src with efPicture and return result in Dest}
var
  SrcLen     : Integer;
  DestLen    : Integer;
  DotPosP    : Cardinal;
  DotPosS    : Cardinal;
  FloatPos   : Integer;
  FP         : Cardinal;
  I          : Cardinal;
  J, K, N    : Integer;
  PicChar    : AnsiChar;
  NeedFloat  : Boolean;
  CurLeftMax : Byte;
  CurLeftLen : Byte;
  IsNum      : Boolean;
  NeedMinus  : Boolean;
  FoundP     : Boolean;
  FoundS     : Boolean;
  CopyOfSrc  : TEditString;
  Buf        : array[0..255] of Char;

  procedure HandleOtherCases;
  begin
    if NeedFloat then begin
      Dec(CurLeftLen);
      Dest[I] := IntlSupport.CurrencyLtStr[CurLeftLen+1];
      NeedFloat := CurLeftLen <> 0;
    end else if NeedMinus then begin
      Dest[I] := '-';
      NeedMinus := False;
    end else if (sefHexadecimal in sefOptions) or (sefOctal in sefOptions) or
                (sefBinary in sefOptions) then
      Dest[I] := '0'
    else
      Dest[I] := ' ';
  end;

begin
  {get initial size of Src}
  SrcLen := StrLen(Src);

  if SrcLen = 0 then
    CopyOfSrc[0] := #0
  else
    StrCopy(CopyOfSrc, Src);

  {copy picture mask into Dest}
  StrCopy(Dest, efPicture);

  {mask may be artificially short -- extend with last character of mask}
  if MaxLength > efPicLen then begin
    FillChar(Dest[efPicLen], MaxLength-efPicLen, Dest[efPicLen-1]);
    Dest[MaxLength] := #0;
  end;
  DestLen := MaxLength;

  {get position of decimal point}
  FoundP := StrChPos(efPicture, pmDecimalPt, DotPosP);

  {is it a numeric string?}
  IsNum := pbIsNumber;

  {take care of currency strings}
  if StrChPos(efPicture, pmCurrencyLt, I) then begin
    K := I;
    while (K+1 < DestLen) and (efNthMaskChar(K+1) = pmCurrencyLt) do
      Inc(K);
    StrPCopy(Buf, IntlSupport.CurrencyLtStr);
    J := StrLen(Buf);
    for N := K downto I do
      if J > 0 then begin
        Dec(J);
        Dest[N] := Buf[J];
      end else
        Dest[N] := ' ';
  end;

  if StrChPos(efPicture, pmCurrencyRt, I) then begin
    J := 0;
    StrPCopy(Buf, IntlSupport.CurrencyRtStr);
    K := StrLen(Buf);
    while (LongInt(I+1) <= DestLen) and (efNthMaskChar(I) = pmCurrencyRt) do begin
      if J < K then begin
        Dest[I] := Buf[J];
        Inc(J);
      end else
        Dest[I] := ' ';
      Inc(I);
    end;
  end;

  if IsNum then begin
    {we need to fill in the FloatDollar positions too, if any}
    if StrChPos(efPicture, pmFloatDollar, FP) then begin
      FloatPos := FP;
      CurLeftLen := Length(IntlSupport.CurrencyLtStr);
      CurLeftMax := 1;
      while efNthMaskChar(FloatPos+1) = pmFloatDollar do begin
        Inc(FloatPos);
        Inc(CurLeftMax);
      end;
      if CurLeftMax < CurLeftLen then
        CurLeftLen := CurLeftMax;
    end else begin
      CurLeftLen := 0;
      FloatPos := -1;
    end;

    {trim leading and trailing blanks}
    TrimAllSpacesPChar(CopyOfSrc);

    {check for a minus sign}
    NeedMinus := (CopyOfSrc[0] = '-');
    if NeedMinus then
      StrStDeletePrim(CopyOfSrc, 0, 1);

    {it's a numeric field--align the decimal points}
    FoundS := StrChPos(CopyOfSrc, pmDecimalPt, DotPosS);

    {see if we need a floating dollar sign}
    SrcLen := StrLen(CopyOfSrc);
    NeedFloat := (SrcLen <> 0) and (CurLeftLen <> 0);

    {if there's no tail, pretend there's a dot beyond the end of CopyOfSrc}
    if not FoundS then
      K := SrcLen
    else
      K := DotPosS;

    {copy the tail of the string}
    if not FoundP then
      I := DestLen
    else
      I := DotPosP+1;
    J := K+1;
    while (J+1 <= SrcLen) and (LongInt(I+1) <= DestLen) and not pbIsLiteral(I) do begin
      Dest[I] := CopyOfSrc[J];
      Inc(I);
      Inc(J);
    end;

    {pad to end with 0's}
    while (LongInt(I+1) <= DestLen) and not pbIsLiteral(I) do begin
      Dest[I] := '0';
      Inc(I);
    end;

    {handle trailing substitution characters}
    while (LongInt(I+1) <= DestLen) and pbIsLiteral(I) do begin
      PicChar := efNthMaskChar(I);
      case PicChar of
        Subst1..Subst8 : Dest[I] := UserData.SubstChars[PicChar];
      end;
      Inc(I);
    end;

    {merge the head of the string}
    if not FoundP then
      J := DestLen-1
    else
      J := DotPosP;

    if FoundS then
      SrcLen := DotPosS;

    for I := J downto 0 do begin
      PicChar := efNthMaskChar(I);
      case pfPicFlags[I] of
        pflagFormat,
        pflagSemiLit :
          if PicChar = pmComma then begin
            if (SrcLen <> 0) then begin
              if IntlSupport.CommaChar <> #0 then
                Dest[I] := IntlSupport.CommaChar
              else
                StrStDeletePrim(Dest, I, 1);
            end else
              HandleOtherCases;
          end else if (SrcLen > 0) and (Integer(I) > FloatPos) then begin
            Dec(SrcLen);
            Dest[I] := CopyOfSrc[SrcLen];
          end else
            HandleOtherCases;
        pflagLiteral :
          case PicChar of
            Subst1..Subst8 :
              Dest[I] := UserData.SubstChars[PicChar];
            pmDecimalPt :
              Dest[I] := IntlSupport.DecimalChar;
          end;
      end;
    end;

    {put in a 0 before the dot if necessary}
    if FoundP and (Dest[DotPosP-1] = ' ') then
      Dest[DotPosP-1] := '0';
  end else begin
    {deal with problem w/ reals w/ variable # of places}
    if (sefRealVar in sefOptions) and (SrcLen > DestLen) then
      if StrScan(CopyOfSrc, pmDecimalPt) <> nil then begin
        TrimTrailingZerosPChar(CopyOfSrc);
        SrcLen := StrLen(CopyOfSrc);
      end;

    if efoRightJustify in Options then begin
      {fill in the characters from CopyOfSrc}
      J := SrcLen-1;
      for I := DestLen-1 downto 0 do begin
        PicChar := efNthMaskChar(I);
        case pfPicFlags[I] of
          pflagLiteral :
            case PicChar of
              Subst1..Subst8 :
                Dest[I] := UserData.SubstChars[PicChar];
            end;
        else
          if (J = -1) then
            Dest[I] := ' '
          else begin
            Dest[I] := CopyOfSrc[J];
            efFixCase(PicChar, AnsiChar(Dest[I]), #255);
            Dec(J);
          end;
        end;
      end;
    end else begin
      {fill in the characters from CopyOfSrc}
      J := 0;
      for I := 0 to DestLen-1 do begin
        PicChar := efNthMaskChar(I);
        case pfPicFlags[I] of
          pflagLiteral :
            case PicChar of
              Subst1..Subst8 :
                Dest[I] := UserData.SubstChars[PicChar];
            end;
        else
          if (J <= SrcLen-1) then begin
            Dest[I] := CopyOfSrc[J];
            efFixCase(PicChar, AnsiChar(Dest[I]), #255);
            Inc(J);
          end else
            Dest[I] := ' ';
        end;
      end;
    end;

    if sefRealVar in sefOptions then
      pbFixDecimalPoint(Dest);
  end;

  Dest[DestLen] := #0;
end;

procedure TOvcPictureBase.pbStripPicture(const Dest, Src : PAnsiChar);
  {-strip the mask characters from Src and put result in Dest}
var
  SLen   : Byte;
  Found  : Boolean;
  P      : PAnsiChar;
  DotPos : Cardinal;
  I      : Integer;
  CLT    : array[0..5] of AnsiChar;
begin
  {this won't work if string isn't the same length as the picture mask}
  if StrLen(Src) <> MaxLength then begin
    if Pointer(Dest) <> Pointer(Src) then
      StrCopy(Dest, Src);
    Exit;
  end;

  {check for fixed decimal point}
  Found := StrChPos(efPicture, pmDecimalPt, DotPos);

  {copy all non-literals from Src into Dest. Note: it's OK if Dest *is* Src}
  SLen := 0;
  for I := 0 to MaxLength-1 do
    if (Found and (I = LongInt(DotPos))) or not pbIsLiteral(I) then begin
      Inc(SLen);
      Dest[SLen-1] := Src[I];
    end;
  Dest[SLen] := #0;

  {remove floating dollar sign}
  P := StrScan(efPicture, pmFloatDollar);
  if P <> nil then begin
    I := 1;

    {find end  of currency mask}
    while P[1] = pmFloatDollar do begin
      Inc(I);
      Inc(P);
    end;
    StrPCopy(CLT, IntlSupport.CurrencyLtStr);
    CLT[I] := #0;
    P := StrPos(Dest, CLT);
    if P <> nil then
      StrStDeletePrim(P, 0, I);
  end;

  {remove commas}
  if (StrScan(efPicture, pmComma) <> nil) and
     (IntlSupport.CommaChar <> #0) then
    repeat
      P := StrScan(Dest, IntlSupport.CommaChar);
      if P <> nil then
        StrStDeletePrim(P, 0, 1);
    until (P = nil);

  {fix up decimal point}
  if Found then begin
    P := StrScan(Dest, IntlSupport.DecimalChar);
    if P <> nil then
      P^ := pmDecimalPt;
  end;
end;

function TOvcPictureBase.ValidateContents(ReportError : Boolean) : Word;
var
  DotPos : Cardinal;
begin
  if efFieldClass = fcPicture then
    if sefModified in sefOptions then begin
      if StrChPos(efPicture, pmDecimalPt, DotPos) then
        pbFixNumber(DotPos, efEditBegin, efEditEnd, True, True);
    end;

  Result := inherited ValidateContents(ReportError);
end;

procedure TOvcPictureBase.WMSetFocus(var Msg : TWMSetFocus);
var
  B : Boolean;
begin
  pbInitPictureFlags;
  B := sefRetainPos in sefOptions;
  inherited;
  if not B then
    pfSelPos := efSelEnd;
end;

procedure TOvcPictureBase.WMKillFocus(var Msg : TWMKillFocus);
var
  DotPos : Cardinal;
begin
  if efFieldClass = fcPicture then
    if sefModified in sefOptions then begin
      if StrChPos(efPicture, pmDecimalPt, DotPos) then
        pbFixNumber(DotPos, efEditBegin, efEditEnd, True, True);
    end;

  inherited;

  {are we giving up the focus?}
  if not (sefRetainPos in sefOptions) then begin
    {restore semi-literal characters}
    pbRestoreSemiLits;
  end;
end;

end.
