{-----------------------------------------------------------------------------
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/MPL-1.1.html

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.

The Original Code is: JvSpellerForm.PAS, released on 2002-06-15.

The Initial Developer of the Original Code is Jan Verhoeven [jan1 dott verhoeven att wxs dott nl]
Portions created by Jan Verhoeven are Copyright (C) 2002 Jan Verhoeven.
All Rights Reserved.

Contributor(s): Robert Love [rlove att slcdug dott org].

You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.sourceforge.net

Known Issues:
-----------------------------------------------------------------------------}
// $Id: JvSpellerForm.pas 10612 2006-05-19 19:04:09Z jfudickar $

unit JvSpellerForm;

{$I jvcl.inc}

interface

uses
  {$IFDEF UNITVERSIONING}
  JclUnitVersioning,
  {$ENDIF UNITVERSIONING}
  SysUtils, Classes, Windows, Messages, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls,
  JvComponent;

type
  TJvSpeller = class;

  TJvSpellerForm = class(TJvForm)
    TextPanel: TPanel;
    LblContext: TLabel;
    TxtSpell: TEdit;
    ButtonPanel: TPanel;
    BtnSkip: TButton;
    BtnChange: TButton;
    BtnCancel: TButton;
    BtnAdd: TButton;
    BtnSkipAll: TButton;
  private
    FSpeller: TJvSpeller;
  end;

  TJvDicIndexArray = array [1..26] of Integer;

  TJvSpeller = class(TComponent)
  private
    FSourceText: string;
    FDict: string;
    FUserDic: string;
    FUserDicChanged: Boolean;
    FDicIndex: TJvDicIndexArray;
    FUserDicIndex: TJvDicIndexArray;
    FSpellerDialog: TJvSpellerForm;
    FWordBegin: Integer;
    FWordEnd: Integer;
    FDictionary: TFileName;
    FUserDictionary: TFileName;
    function WordBegin: Boolean;
    function WordEnd: Boolean;
    function ParseWord: string;
    procedure SpellNext;
    procedure Skip(Sender: TObject);
    procedure Add(Sender: TObject);
    procedure Change(Sender: TObject);
    procedure IndexDictionary;
    procedure IndexUserDictionary;
    procedure SetDictionary(const Value: TFileName);
    procedure SetUserDictionary(const Value: TFileName);
    procedure CreateSpellerDialog(SpellWord: string);
  public
    constructor Create(AOwner: TComponent); override;
    procedure LoadDictionary(AFile: string);
    procedure LoadUserDictionary(AFile: string);
    procedure Spell(var SourceText: string);
  published
    property Dictionary: TFileName read FDictionary write SetDictionary;
    property UserDictionary: TFileName read FUserDictionary write SetUserDictionary;
  end;

{$IFDEF UNITVERSIONING}
const
  UnitVersioning: TUnitVersionInfo = (
    RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_32/run/JvSpellerForm.pas $';
    Revision: '$Revision: 10612 $';
    Date: '$Date: 2006-05-19 21:04:09 +0200 (ven., 19 mai 2006) $';
    LogPath: 'JVCL\run'
  );
{$ENDIF UNITVERSIONING}

implementation

uses
  JvConsts, JvResources, JvTypes;

{$R *.dfm}

function Q_PosStr(const FindString, SourceString: string; StartPos: Integer): Integer;
asm
        PUSH    ESI
        PUSH    EDI
        PUSH    EBX
        PUSH    EDX
        TEST    EAX,EAX
        JE      @@qt
        TEST    EDX,EDX
        JE      @@qt0
        MOV     ESI,EAX
        MOV     EDI,EDX
        MOV     EAX,[EAX-4]
        MOV     EDX,[EDX-4]
        DEC     EAX
        SUB     EDX,EAX
        DEC     ECX
        SUB     EDX,ECX
        JNG     @@qt0
        MOV     EBX,EAX
        XCHG    EAX,EDX
        NOP
        ADD     EDI,ECX
        MOV     ECX,EAX
        MOV     AL,BYTE PTR [ESI]
@@lp1:  CMP     AL,BYTE PTR [EDI]
        JE      @@uu
@@fr:   INC     EDI
        DEC     ECX
        JNZ     @@lp1
@@qt0:  XOR     EAX,EAX
        JMP     @@qt
@@ms:   MOV     AL,BYTE PTR [ESI]
        MOV     EBX,EDX
        JMP     @@fr
@@uu:   TEST    EDX,EDX
        JE      @@fd
@@lp2:  MOV     AL,BYTE PTR [ESI+EBX]
        XOR     AL,BYTE PTR [EDI+EBX]
        JNE     @@ms
        DEC     EBX
        JNE     @@lp2
@@fd:   LEA     EAX,[EDI+1]
        SUB     EAX,[ESP]
@@qt:   POP     ECX
        POP     EBX
        POP     EDI
        POP     ESI
end;

procedure SaveString(AFile, AText: string);
begin
  with TFileStream.Create(AFile, fmCreate) do
  try
    WriteBuffer(AText[1], Length(AText));
  finally
    Free;
  end;
end;

function LoadString(AFile: string): string;
begin
  with TFileStream.Create(AFile, fmOpenRead) do
  try
    SetLength(Result, Size);
    ReadBuffer(Result[1], Size);
  finally
    Free;
  end;
end;

//=== { TJvSpeller } =========================================================

constructor TJvSpeller.Create(AOwner: TComponent);
var
  I: Integer;
begin
  inherited Create(AOwner);
  for I := 1 to 26 do
  begin
    FDicIndex[I] := 1;
    FUserDicIndex[I] := 1;
  end;
end;

procedure TJvSpeller.Add(Sender: TObject);
var
  S: string;
begin
  S := FSpellerDialog.TxtSpell.Text;
  if S = '' then
    Exit;
  FUserDic := FUserDic + LowerCase(S) + Cr;
  FUserDicChanged := True;
  with TStringList.Create do
    try
      Text := FUserDic;
      Sort;
      FUserDic := Text;
    finally
      Free;
    end;
  IndexUserDictionary;
  Skip(Sender);
end;

procedure TJvSpeller.Change(Sender: TObject);
var
  S: string;
begin
  S := FSpellerDialog.TxtSpell.Text;
  if S = '' then
    Exit;
  FSourceText := Copy(FSourceText, 1, FWordBegin - 1) + S +
    Copy(FSourceText, FWordEnd, Length(FSourceText));
  FWordEnd := FWordEnd + (Length(S) - (FWordEnd - FWordBegin));
  Skip(Sender);
end;

procedure TJvSpeller.IndexDictionary;
var
  I, P, StartPos: Integer;
begin
  FDicIndex[1] := 1;
  for I := 2 to 26 do
  begin
    if FDicIndex[I - 1] <> 1 then
      StartPos := FDicIndex[I - 1]
    else
      StartPos := 1;
    P := Q_PosStr(Cr + Chr(96 + I), FDict, StartPos);
    if P <> 0 then
      FDicIndex[I] := P
    else
      FDicIndex[I] := FDicIndex[I - 1];
  end;
end;

procedure TJvSpeller.IndexUserDictionary;
var
  I, P, StartPos: Integer;
begin
  FUserDicIndex[1] := 1;
  for I := 2 to 26 do
  begin
    if FUserDicIndex[I - 1] <> 1 then
      StartPos := FUserDicIndex[I - 1]
    else
      StartPos := 1;
    P := Q_PosStr(Cr + Chr(96 + I), FUserDic, StartPos);
    if P <> 0 then
      FUserDicIndex[I] := P
    else
      FUserDicIndex[I] := FUserDicIndex[I - 1];
  end;
end;

procedure TJvSpeller.LoadDictionary(AFile: string);
begin
  if FileExists(AFile) then
    FDict := LoadString(AFile)
  else
    FDict := '';
  IndexDictionary;
end;

procedure TJvSpeller.LoadUserDictionary(AFile: string);
begin
  UserDictionary := AFile;
  FUserDicChanged := False;
  if FileExists(AFile) then
    FUserDic := LoadString(AFile)
  else
    FUserDic := '';
  IndexUserDictionary;
end;

function TJvSpeller.ParseWord: string;
begin
  Result := '';
  if not WordBegin then
    Exit;
  if not WordEnd then
    Exit;
  Result := Copy(FSourceText, FWordBegin, FWordEnd - FWordBegin);
end;

procedure TJvSpeller.SetDictionary(const Value: TFileName);
begin
  if FDictionary <> Value then
  begin
    FDictionary := Value;
    LoadDictionary(FDictionary);
  end;
end;

procedure TJvSpeller.SetUserDictionary(const Value: TFileName);
begin
  if FUserDictionary <> Value then
  begin
    FUserDictionary := Value;
    LoadUserDictionary(FUserDictionary);
  end;
end;

procedure TJvSpeller.Skip(Sender: TObject);
begin
  FSpellerDialog.TxtSpell.Text := '';
  SpellNext;
end;

procedure TJvSpeller.CreateSpellerDialog(SpellWord: string);
begin
  FSpellerDialog := TJvSpellerForm.Create(Application);
  with FSpellerDialog do
  begin
    FSpeller := Self;
    BtnSkip.OnClick := Skip;
    BtnChange.OnClick := Change;
    BtnAdd.OnClick := Add;
    BtnAdd.Enabled := (UserDictionary <> '');
    TxtSpell.Text := SpellWord;
    LblContext.Caption := Copy(FSourceText, FWordBegin, 75);
  end;
end;

procedure TJvSpeller.Spell(var SourceText: string);
var
  Spw, S: string;
  StartPos, Index: Integer;
begin
  if FDict = '' then
    raise EJVCLException.CreateRes(@RsENoDictionaryLoaded);

  FSourceText := SourceText;
  FWordEnd := 1;
  repeat
    Spw := ParseWord;
    S := LowerCase(Spw);
    if Spw <> '' then
    begin
      Index := Ord(S[1]) - 96;
      if (Index > 0) and (Index < 27) then
        StartPos := FDicIndex[Index]
      else
        StartPos := 1;
      if Q_PosStr(S + Cr, FDict, StartPos) = 0 then
        if FUserDic <> '' then
        begin
          if (Index > 0) and (Index < 27) then
            StartPos := FUserDicIndex[Index]
          else
            StartPos := 1;
          if Q_PosStr(S + Cr, FUserDic, StartPos) = 0 then
          begin
            CreateSpellerDialog(Spw);
            try
              if FSpellerDialog.ShowModal = mrOk then
                SourceText := FSourceText;
              // (rom) the user dictionary has to be saved always!
              if FUserDicChanged then
                if FUserDic <> '' then
                  SaveString(UserDictionary, FUserDic);
            finally
              FSpellerDialog.Free;
            end;
            Exit;
          end
        end
        else
        begin
          CreateSpellerDialog(Spw);
          try
            if FSpellerDialog.ShowModal = mrOk then
              SourceText := FSourceText;
            // (rom) the user dictionary has to be saved always!
            if FUserDicChanged then
              if FUserDic <> '' then
                SaveString(UserDictionary, FUserDic);
          finally
            FSpellerDialog.Free;
          end;
          Exit;
        end;
    end;
  until Spw = '';
end;

procedure TJvSpeller.SpellNext;
var
  Spw, S: string;
  Index, StartPos: Integer;
begin
  repeat
    Spw := ParseWord;
    S := LowerCase(Spw);
    if Spw <> '' then
    begin
      Index := Ord(S[1]) - 96;
      if (Index > 0) and (Index < 27) then
        StartPos := FDicIndex[Index]
      else
        StartPos := 1;
      if Q_PosStr(S + Cr, FDict, StartPos) = 0 then
        if FUserDic <> '' then
        begin
          if (Index > 0) and (Index < 27) then
            StartPos := FUserDicIndex[Index]
          else
            StartPos := 1;
          if Q_PosStr(S + Cr, FUserDic, StartPos) = 0 then
          begin
            FSpellerDialog.TxtSpell.Text := Spw;
            FSpellerDialog.LblContext.Caption := Copy(FSourceText, FWordBegin, 75);
            Exit;
          end;
        end
        else
        begin
          FSpellerDialog.TxtSpell.Text := Spw;
          FSpellerDialog.LblContext.Caption := Copy(FSourceText, FWordBegin, 75);
          Exit;
        end;
    end;
  until Spw = '';
  FSpellerDialog.ModalResult := mrOk;
end;

function TJvSpeller.WordBegin: Boolean;
var
  L: Integer;
begin
  L := Length(FSourceText);
  FWordBegin := FWordEnd;
  while (FWordBegin <= L) and (not (FSourceText[FWordBegin] in ['a'..'z', 'A'..'Z'])) do
    Inc(FWordBegin);
  Result := (FWordBegin <= L);
end;

function TJvSpeller.WordEnd: Boolean;
var
  L: Integer;
begin
  FWordEnd := FWordBegin;
  L := Length(FSourceText);
  while (FWordEnd <= L) and (FSourceText[FWordEnd] in ['a'..'z', 'A'..'Z']) do
    Inc(FWordEnd);
  Result := (FWordEnd <= L);
end;

{$IFDEF UNITVERSIONING}
initialization
  RegisterUnitVersion(HInstance, UnitVersioning);

finalization
  UnregisterUnitVersion(HInstance);
{$ENDIF UNITVERSIONING}

end.

