jvcllaz: Add TJvSpellChecker incl demo and sample dictionaries

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@6237 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2018-03-12 08:51:28 +00:00
parent 7df733f676
commit 6eb42439cf
16 changed files with 211307 additions and 5 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
Dictionary file format:
The words are compressed.
Each word is prefixed with a digit letter.
The digit gives the number of chars the word shares with the previous uncompressed word.
The digit letters range from '0' to '9'. Longer shared stems are not handled.
The words are sorted ascending according to their uncompressed text per TStringList.Sort.
The lineends are CR only to save space (TStringList.LoadFromFile reads that without problems).

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="11"/>
<PathDelim Value="\"/>
<General>
<Flags>
<UseDefaultCompilerOptions Value="True"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/>
<Title Value="JvSpellCheckerDemo"/>
<Scaled Value="True"/>
<ResourceType Value="res"/>
<UseXPManifest Value="True"/>
<XPManifest>
<DpiAware Value="True"/>
</XPManifest>
<Icon Value="0"/>
</General>
<BuildModes Count="1">
<Item1 Name="Default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
</PublishOptions>
<RunParams>
<FormatVersion Value="2"/>
<Modes Count="0"/>
</RunParams>
<RequiredPackages Count="2">
<Item1>
<PackageName Value="JvCmpR"/>
</Item1>
<Item2>
<PackageName Value="LCL"/>
</Item2>
</RequiredPackages>
<Units Count="3">
<Unit0>
<Filename Value="JvSpellCheckerDemo.lpr"/>
<IsPartOfProject Value="True"/>
</Unit0>
<Unit1>
<Filename Value="JvSpellCheckerForm.pas"/>
<IsPartOfProject Value="True"/>
<HasResources Value="True"/>
</Unit1>
<Unit2>
<Filename Value="MainFrm.pas"/>
<IsPartOfProject Value="True"/>
<ComponentName Value="frmMain"/>
<HasResources Value="True"/>
<ResourceBaseClass Value="Form"/>
</Unit2>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<PathDelim Value="\"/>
<Target>
<Filename Value="..\..\bin\JvSpellCheckerDemo"/>
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/>
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
<Linking>
<Options>
<Win32>
<GraphicApplication Value="True"/>
</Win32>
</Options>
</Linking>
</CompilerOptions>
<Debugging>
<Exceptions Count="3">
<Item1>
<Name Value="EAbort"/>
</Item1>
<Item2>
<Name Value="ECodetoolError"/>
</Item2>
<Item3>
<Name Value="EFOpenError"/>
</Item3>
</Exceptions>
</Debugging>
</CONFIG>

View File

@ -0,0 +1,16 @@
program JvSpellCheckerDemo;
uses
Forms, Interfaces,
MainFrm in 'MainFrm.pas' {frmMain},
JvSpellCheckerForm in 'JvSpellCheckerForm.pas' {frmSpellChecker};
{$R *.res}
begin
Application.Scaled := True;
Application.Initialize;
Application.CreateForm(TfrmMain, frmMain);
Application.CreateForm(TfrmSpellChecker, frmSpellChecker);
Application.Run;
end.

View File

@ -0,0 +1,193 @@
object frmSpellChecker: TfrmSpellChecker
Left = 338
Top = 177
BorderStyle = bsDialog
Caption = 'Spell check document'
ClientHeight = 334
ClientWidth = 384
Color = clBtnFace
DefaultMonitor = dmDesktop
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Shell Dlg 2'
Font.Style = []
FormStyle = fsStayOnTop
OldCreateOrder = False
Position = poMainFormCenter
Scaled = False
OnCreate = FormCreate
OnShow = FormShow
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 16
Top = 16
Width = 72
Height = 13
Caption = 'Not in wordlist:'
end
object Label2: TLabel
Left = 16
Top = 102
Width = 62
Height = 13
Caption = 'Suggestions:'
end
object lblNoSuggestions: TLabel
Left = 179
Top = 101
Width = 93
Height = 13
Caption = '(nothing to display)'
Visible = False
end
object Label3: TLabel
Left = 16
Top = 56
Width = 65
Height = 13
Caption = 'Replace with:'
end
object edNewWord: TEdit
Left = 16
Top = 72
Width = 254
Height = 21
Anchors = [akLeft, akTop, akRight]
TabOrder = 1
end
object lbSuggestions: TListBox
Left = 16
Top = 120
Width = 254
Height = 82
Anchors = [akLeft, akTop, akRight, akBottom]
ItemHeight = 13
TabOrder = 2
OnClick = lbSuggestionsClick
end
object btnIgnore: TButton
Left = 294
Top = 32
Width = 75
Height = 25
Action = acIgnore
Anchors = [akTop, akRight]
TabOrder = 3
end
object btnIgnoreAll: TButton
Left = 294
Top = 61
Width = 75
Height = 25
Action = acIgnoreAll
Anchors = [akTop, akRight]
TabOrder = 4
end
object btnChange: TButton
Left = 294
Top = 120
Width = 75
Height = 25
Action = acChange
Anchors = [akTop, akRight]
TabOrder = 5
end
object btnClose: TButton
Left = 294
Top = 286
Width = 75
Height = 25
Action = acClose
Anchors = [akRight, akBottom]
Cancel = True
TabOrder = 8
end
object btnAdd: TButton
Left = 294
Top = 149
Width = 75
Height = 25
Action = acAdd
Anchors = [akTop, akRight]
TabOrder = 6
end
object GroupBox1: TGroupBox
Left = 16
Top = 209
Width = 257
Height = 105
Anchors = [akLeft, akRight, akBottom]
Caption = ' Ignore: '
TabOrder = 7
object chkUpperCase: TCheckBox
Left = 24
Top = 23
Width = 207
Height = 17
Caption = '&UPPERCASE words'
TabOrder = 0
end
object chkNumber: TCheckBox
Left = 24
Top = 39
Width = 207
Height = 17
Caption = 'Words with &numbers'
TabOrder = 1
end
object chkURL: TCheckBox
Left = 24
Top = 55
Width = 207
Height = 17
Caption = 'Internet and file &paths'
TabOrder = 2
end
object chkHTML: TCheckBox
Left = 24
Top = 71
Width = 207
Height = 17
Caption = 'HT&ML code'
TabOrder = 3
end
end
object edBadWord: TEdit
Left = 16
Top = 32
Width = 254
Height = 21
Anchors = [akLeft, akTop, akRight]
Color = clBtnFace
ReadOnly = True
TabOrder = 0
end
object alSpell: TActionList
OnUpdate = alSpellUpdate
Left = 296
Top = 224
object acIgnore: TAction
Caption = '&Ignore'
OnExecute = acIgnoreExecute
end
object acIgnoreAll: TAction
Caption = 'Ignore &All'
OnExecute = acIgnoreAllExecute
end
object acChange: TAction
Caption = 'C&hange'
OnExecute = acChangeExecute
end
object acAdd: TAction
Caption = 'A&dd'
OnExecute = acAddExecute
end
object acClose: TAction
Caption = '&Close'
ShortCut = 27
OnExecute = acCloseExecute
end
end
end

View File

@ -0,0 +1,329 @@
{******************************************************************
JEDI-VCL Demo
Copyright (C) 2002 Project JEDI
Original author:
Contributor(s):
You may retrieve the latest version of this file at the JEDI-JVCL
home page, located at http://jvcl.delphi-jedi.org
The contents of this file are used with permission, 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_1Final.html
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.
******************************************************************}
unit JvSpellCheckerForm;
interface
{$mode objfpc}{$H+}
//{$I jvcl.inc}
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, JvSpellChecker, ActnList;
type
{ This is an example form with code that shows how to implement a spell check form
that can be displayed to the end user. The TJvSpellChecker is created dynamically
so you don't need to install it to run the demo: just make sure the JvSpellChecker and
JvSpellIntf units are somewhere in your path.
The main tasks of this form is to:
* Scan for the next misspelled word (GetNextWord)
* Display the misspelled word along with suggested replacements
* Call an event handler to highlight the text in the original control
* Call an event handler when the user wants to replace the word
* Add a word to the user dictionary (btnAdd)
* Add a word to the ignore list (btnIgnoreAll)
This form doesn't implement everything needed for a professional looking form (i.e only
enable buttons as needed) but it can serve as a base for a more complete implementation.
}
TJvReplaceTextEvent = procedure(Sender: TObject; StartIndex, ALength: integer; const NewText: string) of object;
TJvSelectTextEvent = procedure(Sender: TObject; StartIndex, ALength: integer) of object;
TfrmSpellChecker = class(TForm)
Label1: TLabel;
edNewWord: TEdit;
Label2: TLabel;
lbSuggestions: TListBox;
btnIgnore: TButton;
btnIgnoreAll: TButton;
btnChange: TButton;
btnClose: TButton;
btnAdd: TButton;
GroupBox1: TGroupBox;
chkUpperCase: TCheckBox;
chkNumber: TCheckBox;
chkURL: TCheckBox;
chkHTML: TCheckBox;
lblNoSuggestions: TLabel;
Label3: TLabel;
edBadWord: TEdit;
alSpell: TActionList;
acIgnore: TAction;
acIgnoreAll: TAction;
acChange: TAction;
acAdd: TAction;
acClose: TAction;
procedure FormCreate(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure acIgnoreExecute(Sender: TObject);
procedure acIgnoreAllExecute(Sender: TObject);
procedure acChangeExecute(Sender: TObject);
procedure acAddExecute(Sender: TObject);
procedure acCloseExecute(Sender: TObject);
procedure alSpellUpdate(AAction: TBasicAction; var Handled: Boolean);
procedure lbSuggestionsClick(Sender: TObject);
private
{ Private declarations }
FStartIndex, FLength: integer;
ASpellChecker: TJvSpellChecker;
FOnReplaceText: TJvReplaceTextEvent;
FOnSelectText: TJvSelectTextEvent;
procedure CloseAndReport(ReportSuccess: boolean);
function GetNextWord: boolean;
procedure DoReplaceText(Sender: TObject; StartIndex, ALength: integer; const NewText: string);
procedure DoSelectText(Sender: TObject; StartIndex, ALength: integer);
function GetSpellText: string;
procedure SetSpellText(const Value: string);
procedure DoCanIgnore(Sender: TObject; const Value: string; var CanIgnore: boolean);
procedure CheckSuggestions;
public
{ Public declarations }
property SpellText: string read GetSpellText write SetSpellText;
property OnReplaceText: TJvReplaceTextEvent read FOnReplaceText write FOnReplaceText;
property OnSelectText: TJvSelectTextEvent read FOnSelectText write FOnSelectText;
end;
var
frmSpellChecker: TfrmSpellChecker;
implementation
{$R *.lfm}
procedure TfrmSpellChecker.FormCreate(Sender: TObject);
var S:string;
begin
ASpellChecker := TJvSpellChecker.Create(self);
// Dictionaries are plain text files, one word per row, preferably sorted.
// If you don't load a dictionary, all words are misspelled and you won't get any suggestions
S := ExtractFilePath(Application.ExeName) + 'english.dic';
if not FileExists(S) then
S := '..\Dict\english.dic';
if FileExists(S) then
ASpellChecker.Dictionary := S
else
ShowMessage('Dictionary file not found: make sure you have an english.dic file in the exe folder!');
// ASpellChecker.UserDictionary.LoadFromFile(Application.ExeName + 'custom.dic'); // you need to create this
// set up a custom ignore filter:
ASpellChecker.OnCanIgnore := @DoCanIgnore;
end;
procedure TfrmSpellChecker.DoReplaceText(Sender: TObject; StartIndex, ALength: integer; const NewText: string);
begin
// this events calls back to the main form where the content of the rich edit is updated
if Assigned(FOnReplaceText) then
FOnReplaceText(self, StartIndex, ALength, NewText);
end;
function TfrmSpellChecker.GetSpellText: string;
begin
Result := ASpellChecker.Text;
end;
procedure TfrmSpellChecker.SetSpellText(const Value: string);
begin
ASpellChecker.Text := Value;
end;
procedure TfrmSpellChecker.CheckSuggestions;
begin
if lbSuggestions.Items.Count = 0 then
begin
lblNoSuggestions.Parent := lbSuggestions;
lblNoSuggestions.Top := 4;
lblNoSuggestions.Left := (lbSuggestions.ClientWidth - lblNoSuggestions.Width) div 2;
lblNoSuggestions.Visible := true;
end
else
lblNoSuggestions.Visible := false;
end;
function TfrmSpellChecker.GetNextWord: boolean;
begin
// scan for the next misspelled word. Returns false if no more misspelled words are found
Result := false;
while ASpellChecker.SpellChecker.Next(FStartIndex, FLength) do
begin
edBadWord.Text := '';
edNewWord.Text := '';
Result := FLength > 0;
if Result then
begin
edBadWord.Text := Copy(ASpellChecker.Text, FStartIndex, FLength);
lbSuggestions.Items := ASpellChecker.SpellChecker.Suggestions;
if lbSuggestions.Items.Count > 0 then
begin
edNewWord.Text := lbSuggestions.Items[0];
lbSuggestions.ItemIndex := 0;
end
else
edNewWord.Text := edBadWord.Text;
edNewWord.SetFocus;
end;
CheckSuggestions;
Exit;
end;
end;
procedure TfrmSpellChecker.FormShow(Sender: TObject);
begin
if GetNextWord then
DoSelectText(self, FStartIndex, FLength)
else
CloseAndReport(false);
end;
procedure TfrmSpellChecker.DoSelectText(Sender: TObject; StartIndex,
ALength: integer);
begin
// this events calls back to the main form where the selection in the rich edit is updated
if Assigned(FOnSelectText) then FOnSelectText(self, StartIndex, ALength);
end;
procedure TfrmSpellChecker.DoCanIgnore(Sender: TObject; const Value: string;
var CanIgnore: boolean);
var
i: integer;
begin
// custom event to manage some of the options in the dialog
// always ignore words shorter than four letter
if Length(Value) < 4 then
begin
CanIgnore := true;
Exit;
end;
// make some additional checks on the current word to determine if we need to spellcheck it
if chkUpperCase.Checked and (AnsiUpperCase(Value) = Value) then // ignore all UPPERCASE words
begin
CanIgnore := true;
Exit;
end;
if chkNumber.Checked then // ignore words that contains numbers
for i := 1 to Length(Value) do
if {$IFDEF RTL200_UP}CharInSet(Value[i], ['0'..'9', '#', '%']){$ELSE}(Value[i] in ['0'..'9', '#', '%']){$ENDIF RTL200_UP} then
begin
CanIgnore := true;
Exit;
end;
if chkURL.Checked then // ignore URL's and file paths (this code is in no way 100% effective...)
for i := 1 to Length(Value) do
if {$IFDEF RTL200_UP}CharInSet(Value[i], [':', '/', '\']){$ELSE}(Value[i] in [':', '/', '\']){$ENDIF RTL200_UP} then
begin
CanIgnore := true;
Exit;
end;
if chkHTML.Checked then // ignore HTML tags (this code is in no way 100% effective...)
CanIgnore := (Length(Value) < 2) or ((Value[1] = '<') or (Value[Length(Value)] = '>')) or
((Value[1] = '&') and (Value[Length(Value)] = ';'));
end;
procedure TfrmSpellChecker.CloseAndReport(ReportSuccess: boolean);
var
S: string;
begin
if ReportSuccess then
S := 'Spell check completed!'
else
S := 'There is nothing to spell check';
ShowMessage(S);
// delay since we might have been called from the OnShow event (can't close in OnShow)
PostMessage(Handle, WM_CLOSE, 0, 0);
end;
procedure TfrmSpellChecker.acIgnoreExecute(Sender: TObject);
begin
// ignore = skip to next word but don't remember the word we just saw
if GetNextWord then
DoSelectText(self, FStartIndex, FLength)
else
CloseAndReport(true)
end;
procedure TfrmSpellChecker.acIgnoreAllExecute(Sender: TObject);
begin
// ignore all = add to ignore list so it will be skipped in the future as well
ASpellChecker.SpellChecker.Ignores.Add(AnsiLowerCase(edBadWord.Text));
if GetNextWord then
DoSelectText(self, FStartIndex, FLength)
else
CloseAndReport(true);
end;
procedure TfrmSpellChecker.acChangeExecute(Sender: TObject);
begin
// replace the current selection with the word in the edit
DoReplaceText(self, FStartIndex, FLength, edNewWord.Text);
if GetNextWord then
DoSelectText(self, FStartIndex, FLength)
else
CloseAndReport(true)
end;
procedure TfrmSpellChecker.acAddExecute(Sender: TObject);
begin
// Add the misspelled word to the user dictionary. To persist, you must add code to call
// UserDictionary.SaveToFile() at close down as well as UserDictionary.LoadFromFile() at start up.
ASpellChecker.SpellChecker.UserDictionary.Add(edBadWord.Text);
edNewWord.Text := edBadWord.Text;
// change the word as well
if not acChange.Execute then
begin
// move on
if GetNextWord then
DoSelectText(self, FStartIndex, FLength)
else
CloseAndReport(true);
end;
end;
procedure TfrmSpellChecker.acCloseExecute(Sender: TObject);
begin
Close;
end;
procedure TfrmSpellChecker.alSpellUpdate(AAction: TBasicAction;
var Handled: Boolean);
begin
acIgnore.Enabled := edBadWord.Text <> '';
acIgnoreAll.Enabled := acIgnore.Enabled;
acChange.Enabled := not AnsiSameText(edBadWord.Text, edNewWord.Text);
acAdd.Enabled := (edBadWord.Text <> '') and (ASpellChecker.UserDictionary.IndexOf(edBadWord.Text) < 0);
end;
procedure TfrmSpellChecker.lbSuggestionsClick(Sender: TObject);
begin
with lbSuggestions do
if ItemIndex > -1 then
edNewWord.Text := Items[ItemIndex];
end;
end.

View File

@ -0,0 +1,93 @@
object frmMain: TfrmMain
Left = 353
Height = 398
Top = 156
Width = 535
Caption = 'JvSpellChecker Demo'
ClientHeight = 378
ClientWidth = 535
Color = clBtnFace
Constraints.MinHeight = 200
Constraints.MinWidth = 320
DefaultMonitor = dmDesktop
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Shell Dlg 2'
Menu = mmMain
Position = poScreenCenter
LCLVersion = '1.9.0.0'
Scaled = False
object reText: TMemo
Left = 0
Height = 355
Top = 0
Width = 535
Align = alClient
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'MS Shell Dlg 2'
HideSelection = False
ParentFont = False
ScrollBars = ssBoth
TabOrder = 0
WantTabs = True
WordWrap = False
end
object StatusBar1: TStatusBar
Left = 0
Height = 23
Top = 355
Width = 535
Panels = <
item
Width = 50
end>
SimplePanel = False
end
object mmMain: TMainMenu
left = 120
top = 64
object File1: TMenuItem
Caption = 'File'
object Open1: TMenuItem
Caption = 'Open...'
ShortCut = 16463
OnClick = Open1Click
end
object N1: TMenuItem
Caption = '-'
end
object Close1: TMenuItem
Caption = 'Close'
ShortCut = 32883
OnClick = Close1Click
end
end
object Edit1: TMenuItem
Caption = 'Edit'
object SpellCheck1: TMenuItem
Caption = 'Spell Check...'
ShortCut = 118
OnClick = SpellCheck1Click
end
object N2: TMenuItem
Caption = '-'
end
object Saveasimage1: TMenuItem
Caption = 'Save as image...'
OnClick = Saveasimage1Click
end
end
end
object OpenDialog1: TOpenDialog
Filter = 'Text files|*.txt|RTF files|*.rtf|All files|*.*'
left = 168
top = 160
end
object SaveDialog1: TSaveDialog
FileName = 'new.bmp'
Options = [ofOverwritePrompt, ofHideReadOnly, ofEnableSizing]
left = 272
top = 192
end
end

View File

@ -0,0 +1,142 @@
{******************************************************************
JEDI-VCL Demo
Copyright (C) 2002 Project JEDI
Original author:
Contributor(s):
You may retrieve the latest version of this file at the JEDI-JVCL
home page, located at http://jvcl.delphi-jedi.org
The contents of this file are used with permission, 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_1Final.html
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.
******************************************************************}
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Menus, ComCtrls, JvComponent;
type
TfrmMain = class(TForm)
reText: TMemo;
mmMain: TMainMenu;
File1: TMenuItem;
Edit1: TMenuItem;
SpellCheck1: TMenuItem;
Open1: TMenuItem;
N1: TMenuItem;
Close1: TMenuItem;
StatusBar1: TStatusBar;
OpenDialog1: TOpenDialog;
N2: TMenuItem;
Saveasimage1: TMenuItem;
SaveDialog1: TSaveDialog;
procedure Open1Click(Sender: TObject);
procedure Close1Click(Sender: TObject);
procedure SpellCheck1Click(Sender: TObject);
procedure Saveasimage1Click(Sender: TObject);
private
{ Private declarations }
// FOffset is used to adjust the insert point in the rich edit when the replaced word
// has a different length than the original word
FOffset: integer;
// called when a word should be replaced
procedure DoReplaceText(Sender: TObject; StartIndex, ALength: integer; const NewText: string);
// called when a word needs to be highlighted. NB: set HideSelection := false or you
// won't see the selection in the edit!
procedure DoSelectText(Sender: TObject; StartIndex, ALength: integer);
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
uses
JvSpellCheckerForm;
{$R *.lfm}
procedure TfrmMain.Open1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
reText.Lines.LoadFromFile(OpenDialog1.Filename);
StatusBar1.Panels[0].Text := ' ' + OpenDialog1.Filename;
end;
end;
procedure TfrmMain.Close1Click(Sender: TObject);
begin
Close;
end;
procedure TfrmMain.SpellCheck1Click(Sender: TObject);
begin
// set up the spell-checker form
if not frmSpellChecker.Visible then
begin
// StartIndex is 1-based, SelStart is 0-based, so we set intial FOffset to 1
FOffset := 1;
// the original text to spell check
frmSpellChecker.SpellText := reText.Lines.Text;
// event handler for when a word is to be replaced
frmSpellChecker.OnReplaceText := @DoReplaceText;
// event handler for when a word needs to be selected
frmSpellChecker.OnSelectText := @DoSelectText;
frmSpellChecker.Show; // ShowModal also works
end;
end;
procedure TfrmMain.DoSelectText(Sender: TObject; StartIndex, ALength: integer);
begin
// just select the text in the rich edit so the user can see were he is
reText.SelStart := StartIndex - FOffset;
reText.SelLength := ALength;
end;
procedure TfrmMain.DoReplaceText(Sender: TObject; StartIndex,
ALength: integer; const NewText: string);
begin
reText.SelStart := StartIndex - FOffset;
reText.SelLength := ALength;
// replace the selected text
reText.SelText := NewText;
// adjust offset for next round
Inc(FOffset, ALength - Length(NewText));
end;
procedure TfrmMain.Saveasimage1Click(Sender: TObject);
var Picture:TPicture;
begin
if SaveDialog1.Execute then
begin
Picture := TPicture.Create;
try
// reText.SaveToImage(Picture);
// Picture.SaveToFile(SaveDialog1.Filename);
finally
Picture.Free;
end;
end;
end;
end.

View File

@ -4,9 +4,6 @@
<Version Value="11"/> <Version Value="11"/>
<PathDelim Value="\"/> <PathDelim Value="\"/>
<General> <General>
<Flags>
<UseDefaultCompilerOptions Value="True"/>
</Flags>
<SessionStorage Value="InProjectDir"/> <SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/> <MainUnit Value="0"/>
<Title Value="SimpleTLTest1"/> <Title Value="SimpleTLTest1"/>
@ -64,7 +61,7 @@
<Version Value="11"/> <Version Value="11"/>
<PathDelim Value="\"/> <PathDelim Value="\"/>
<Target> <Target>
<Filename Value="SimpleTLTest1"/> <Filename Value="..\..\bin\SimpleTLTest1"/>
</Target> </Target>
<SearchPaths> <SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/> <IncludeFiles Value="$(ProjOutDir)"/>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<CONFIG> <CONFIG>
<ProjectGroup FileVersion="1"> <ProjectGroup FileVersion="1">
<Targets Count="16"> <Targets Count="19">
<Target0 FileName="JvCoreLazR.lpk"/> <Target0 FileName="JvCoreLazR.lpk"/>
<Target1 FileName="JvCoreLazD.lpk"/> <Target1 FileName="JvCoreLazD.lpk"/>
<Target2 FileName="JvCtrlsLazR.lpk"/> <Target2 FileName="JvCtrlsLazR.lpk"/>
@ -18,6 +18,9 @@
<Target13 FileName="JvXPCtrlsLazD.lpk"/> <Target13 FileName="JvXPCtrlsLazD.lpk"/>
<Target14 FileName="jvruntimedesignlazr.lpk"/> <Target14 FileName="jvruntimedesignlazr.lpk"/>
<Target15 FileName="jvruntimedesignlazd.lpk"/> <Target15 FileName="jvruntimedesignlazd.lpk"/>
<Target16 FileName="jvcustomlazr.lpk"/>
<Target17 FileName="jvcustomlazd.lpk"/>
<Target18 FileName="jvcmpr.lpk"/>
</Targets> </Targets>
</ProjectGroup> </ProjectGroup>
</CONFIG> </CONFIG>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<Package Version="4">
<PathDelim Value="\"/>
<Name Value="JvCmpR"/>
<Author Value="Various authors - see header of each unit for original author."/>
<CompilerOptions>
<Version Value="11"/>
<PathDelim Value="\"/>
<SearchPaths>
<IncludeFiles Value="..\run\JvCmp"/>
<OtherUnitFiles Value="..\run\JvCmp"/>
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
</CompilerOptions>
<Description Value="Custom controls of the JVCL library (https://sourceforge.net/projects/jvcl/) (runtime code):
- Spellchecker component"/>
<License Value="The JVCL is released in accordance with the MPL 1.1 license. To get your own copy or read it, go to http://www.mozilla.org/MPL/MPL-1.1.html. "/>
<Version Major="1" Release="4"/>
<Files Count="3">
<Item1>
<Filename Value="..\run\JvCmp\JvSpellChecker.pas"/>
<UnitName Value="JvSpellChecker"/>
</Item1>
<Item2>
<Filename Value="..\run\JvCmp\JvSpellerForm.pas"/>
<UnitName Value="JvSpellerForm"/>
</Item2>
<Item3>
<Filename Value="..\run\JvCmp\JvSpellIntf.pas"/>
<UnitName Value="JvSpellIntf"/>
</Item3>
</Files>
<RequiredPkgs Count="2">
<Item1>
<PackageName Value="JvCoreLazR"/>
</Item1>
<Item2>
<PackageName Value="FCL"/>
</Item2>
</RequiredPkgs>
<UsageOptions>
<UnitPath Value="$(PkgOutDir)"/>
</UsageOptions>
<PublishOptions>
<Version Value="2"/>
</PublishOptions>
</Package>
</CONFIG>

View File

@ -0,0 +1,633 @@
{-----------------------------------------------------------------------------
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: JvSpellChecker.PAS, released on 2003-08-19.
The Initial Developer of the Original Code is Peter Th�rnqvist [peter3 at sourceforge dot net]
Portions created by Peter Th�rnqvist are Copyright (C) 2003 Peter Th�rnqvist.
All Rights Reserved.
Contributor(s):
You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.delphi-jedi.org
Known Issues:
- Items in the UserDictionary are not added to the internal WordTable/SoundexTable when
you add a new item (i.e call UserDictionary.Add). This is mostly for performance.
UserDictionary entries are loaded into the dictionary table in BuildTables, so to get
them added make sure UserDictionary is filled before setting the Dictionary property.
-----------------------------------------------------------------------------}
// $Id$
unit JvSpellChecker;
{$mode objfpc}{$H+}
//{$I jvcl.inc}
interface
uses
SysUtils, Classes,
Windows, Controls, Messages,
JvSpellIntf; //, JvComponentBase;
type
TJvSpellChecker = class(TComponent) //TJvComponent)
private
FSpellChecker: IJvSpellChecker;
procedure SetText(const Value: string);
function GetText: string;
function GetDictionary: TFileName;
function GetUserDictionary: TStrings;
procedure SetDictionary(const Value: TFileName);
procedure SetUserDictionary(const Value: TStrings);
function GetSpellChecker: IJvSpellChecker;
function GetDelimiters: TSysCharSet;
procedure SetDelimiters(const Value: TSysCharSet);
function GetIgnores: TStrings;
procedure SetIgnores(const Value: TStrings);
function GetCanIgnore: TJvSpellCheckIgnoreEvent;
procedure SetCanIgnore(const Value: TJvSpellCheckIgnoreEvent);
public
// reference to the actual spell check implementation
property SpellChecker: IJvSpellChecker read GetSpellChecker;
property Delimiters: TSysCharSet read GetDelimiters write SetDelimiters;
published
// Surface interface properties to make it a bit easier to work with this component
property Text: string read GetText write SetText;
property Dictionary: TFileName read GetDictionary write SetDictionary;
property UserDictionary: TStrings read GetUserDictionary write SetUserDictionary;
property Ignores: TStrings read GetIgnores write SetIgnores;
property OnCanIgnore: TJvSpellCheckIgnoreEvent read GetCanIgnore write SetCanIgnore;
end;
{$IFDEF UNITVERSIONING}
const
UnitVersioning: TUnitVersionInfo = (
RCSfile: '$URL$';
Revision: '$Revision$';
Date: '$Date$';
LogPath: 'JVCL\run'
);
{$ENDIF UNITVERSIONING}
implementation
uses
// JclStrings, // StrAddRef, StrDecRef
JvTypes, JvResources;
// NOTE: hash table and soundex lookup code originally from Julian Bucknall's
// "Algorithms Alfresco" column in The Delphi Magazine, Issue 52, December 1999
// Used with permission
const
WordTableSize = 10007; {a prime}
SoundexTableSize = 26 * 7 * 7 * 7; {the exact number of Soundexes}
cDelimiters: TSysCharSet = [#0..#32, '.', ',', '<', '>', '=', '!', '?', ':', ';', '"', '''', '(', ')', '[', ']', '{', '}', '+', '|'];
type
TSoundex = string[4];
// default implementation of the IJvSpellChecker interface. To provide a new implementation,
// assign a function to the CreateSpellChecker function variable in JvSpellIntf that returns an
// instance of your implementation. For more info, see InternalSpellChecker in this unit.
TJvDefaultSpellChecker = class(TInterfacedObject, IJvSpellChecker)
private
FText: string;
FCurrentWord: string;
FPosition: Integer;
FDictionary: string;
FSuggestions: TStringList;
FUserDictionary: TStringList;
FIgnores: TStringList;
FWordTable: TList;
FSoundexTable: TList;
FDelimiters: TSysCharSet;
FOnCanIgnore: TJvSpellCheckIgnoreEvent;
{ IJvSpellChecker }
procedure SetDictionary(const Value: string);
function GetDictionary: string;
function GetUserDictionary: TStrings;
procedure SetUserDictionary(const Value: TStrings);
function GetSuggestions: TStrings;
function GetText: string;
procedure SetText(const Value: string);
function GetIgnores: TStrings;
procedure SetIgnores(const Value: TStrings);
function GetDelimiters: TSysCharSet;
procedure SetDelimiters(const Value: TSysCharSet);
function GetCanIgnore: TJvSpellCheckIgnoreEvent;
procedure SetCanIgnore(const Value: TJvSpellCheckIgnoreEvent);
protected
procedure BuildTables; virtual;
procedure ClearTables; virtual;
function GetCurrentWord: string; virtual;
procedure GetWordSuggestions(const Value: string; AStrings: TStrings); virtual;
procedure AddSoundex(ASoundex: TSoundex; const Value: string); virtual;
procedure AddWord(const Value: string); virtual;
function WordExists(const Value: string): Boolean; virtual;
function CanIgnore(const Value: string): Boolean; virtual;
{ IJvSpellChecker }
function Next(out StartIndex, WordLength: Integer): WordBool; virtual;
procedure Seek(Position: Integer); virtual;
public
constructor Create;
destructor Destroy; override;
property Delimiters: TSysCharSet read GetDelimiters write SetDelimiters;
property Suggestions: TStrings read GetSuggestions;
property Dictionary: string read GetDictionary write SetDictionary;
property UserDictionary: TStrings read GetUserDictionary write SetUserDictionary;
property Text: string read GetText write SetText;
property Ignores: TStrings read GetIgnores write SetIgnores;
property OnCanIgnore: TJvSpellCheckIgnoreEvent read GetCanIgnore write SetCanIgnore;
end;
function InternalCreateSpellChecker: IJvSpellChecker;
begin
// create our implementation of the spell checker interface
Result := TJvDefaultSpellChecker.Create;
end;
function Soundex(const Value: string): TSoundex;
const
Encode: array ['A'..'Z'] of AnsiChar =
('0', '1', '2', '3', '0', '1', '2', '/', '0', '2', '2',
'4', '5', '5', '0', '1', '2', '6', '2', '3', '0', '1',
'/', '2', '0', '2');
var
Ch: Char;
Code, OldCode: AnsiChar;
SxInx: Integer;
I: Integer;
UpperValue: string;
begin
Result := 'A000';
if Value = '' then
Exit;
// raise Exception.Create('Soundex: input string is empty');
UpperValue := AnsiUpperCase(Value);
Ch := UpperValue[1];
if (Ch < 'A') or (Ch > 'Z') then
Ch := 'A';
// raise Exception.Create('Soundex: unknown character in input string');
Result[1] := AnsiChar(Ch);
Code := Encode[Ch];
OldCode := Code;
SxInx := 2;
for I := 2 to Length(UpperValue) do
begin
if (Code <> '/') then
OldCode := Code;
Ch := UpperValue[I];
if not ('A' <= Ch) and (Ch <= 'Z') then
Code := '0'
else
Code := Encode[Ch];
if (Code <> OldCode) and (Code > '0') then
begin
Result[SxInx] := Code;
Inc(SxInx);
if SxInx > 4 then
Break;
end;
end;
end;
function ELFHash(const S: string): Integer;
var
G, I: Integer;
begin
Result := 0;
for I := 1 to Length(S) do
begin
Result := (Result shl 4) + Ord(S[I]);
G := Result and Longint($F0000000);
if G <> 0 then
Result := Result xor (G shr 24);
Result := Result and (not G);
end;
end;
function SoundexHash(const S: TSoundex): Integer;
begin
Result :=
((Ord(S[1]) - Ord('A')) * 343) +
((Ord(S[2]) - Ord('0')) * 49) +
((Ord(S[3]) - Ord('0')) * 7) +
(Ord(S[4]) - Ord('0'));
end;
function GetNextWord(var S: PChar; out Word: string; Delimiters: TSysCharSet): Boolean;
var
Start: PChar;
begin
Word := '';
Result := S = nil;
if Result then
Exit;
Start := nil;
while True do
begin
if S^ = #0 then
begin
Word := Start;
Result := Start <> nil;
Exit;
end
else
if ((S^ <= #255) and (AnsiChar(S^) in Delimiters)) then
begin
if Start <> nil then
begin
SetString(Word, Start, S - Start);
Exit;
end
else
while ((S^ <= #255) and (AnsiChar(S^) in Delimiters)) and (S^ <> #0) do
Inc(S);
end
else
begin
if Start = nil then
Start := S;
Inc(S);
end;
end;
end;
//=== { TJvDefaultSpellChecker } =============================================
constructor TJvDefaultSpellChecker.Create;
begin
inherited Create;
FDelimiters := cDelimiters;
FSuggestions := TStringList.Create;
FUserDictionary := TStringList.Create;
FUserDictionary.Sorted := True;
FIgnores := TStringList.Create;
FIgnores.Sorted := True;
FWordTable := TList.Create;
FWordTable.Count := WordTableSize;
FSoundexTable := TList.Create;
FSoundexTable.Count := SoundexTableSize;
end;
destructor TJvDefaultSpellChecker.Destroy;
begin
ClearTables;
FreeAndNil(FSuggestions);
FreeAndNil(FUserDictionary);
FreeAndNil(FWordTable);
FreeAndNil(FSoundexTable);
FreeAndNil(FIgnores);
inherited Destroy;
end;
procedure TJvDefaultSpellChecker.AddSoundex(ASoundex: TSoundex; const Value: string);
var
Hash: Integer;
begin
Hash := SoundexHash(ASoundex) mod SoundexTableSize;
if FSoundexTable[Hash] = nil then
FSoundexTable[Hash] := TStringList.Create;
TStringList(FSoundexTable[Hash]).Add(Value);
end;
procedure TJvDefaultSpellChecker.AddWord(const Value: string);
var
Hash: Integer;
begin
Hash := ELFHash(Value) mod WordTableSize;
if FWordTable[Hash] = nil then
FWordTable[Hash] := TStringList.Create;
TStringList(FWordTable[Hash]).Add(Value);
end;
procedure TJvDefaultSpellChecker.BuildTables;
var
AFile: TextFile;
Value: string;
LastValue: string;
SoundexVal: TSoundex;
I: Integer;
N: Integer;
begin
ClearTables;
if FileExists(Dictionary) then
begin
System.Assign(AFile, Dictionary);
System.Reset(AFile);
try
repeat
ReadLn(AFile, Value);
if Value <> '' then
begin
// (rom) simple compession for dictionary
N := Ord(Value[1]) - Ord('0');
Value := Copy(Value, 2, Length(Value) - 1);
if N > 0 then
Value := Copy(LastValue, 1, N) + Value;
LastValue := Value;
Value := AnsiLowerCase(Value);
AddWord(Value);
SoundexVal := Soundex(Value);
AddSoundex(SoundexVal, Value);
end;
until Eof(AFile);
finally
System.Close(AFile);
end;
for I := 0 to UserDictionary.Count - 1 do
if UserDictionary[I] <> '' then
begin
Value := AnsiLowerCase(UserDictionary[I]);
AddWord(Value);
SoundexVal := Soundex(Value);
AddSoundex(SoundexVal, Value);
end;
end;
end;
procedure TJvDefaultSpellChecker.ClearTables;
var
I: Integer;
begin
if FSoundexTable <> nil then
for I := 0 to FSoundexTable.Count - 1 do
begin
TObject(FSoundexTable[I]).Free;
FSoundexTable[I] := nil;
end;
if FWordTable <> nil then
for I := 0 to FWordTable.Count - 1 do
begin
TObject(FWordTable[I]).Free;
FWordTable[I] := nil;
end;
end;
function TJvDefaultSpellChecker.GetSuggestions: TStrings;
begin
Result := FSuggestions;
end;
function TJvDefaultSpellChecker.GetText: string;
begin
Result := FText;
end;
procedure TJvDefaultSpellChecker.GetWordSuggestions(const Value: string; AStrings: TStrings);
var
SoundexVal: TSoundex;
Hash: Integer;
begin
if AStrings <> nil then
begin
AStrings.BeginUpdate;
try
AStrings.Clear;
SoundexVal := Soundex(Value);
Hash := SoundexHash(SoundexVal) mod SoundexTableSize;
if FSoundexTable[Hash] <> nil then
AStrings.AddStrings(TStringList(FSoundexTable[Hash]));
finally
AStrings.EndUpdate;
end;
end;
end;
function TJvDefaultSpellChecker.Next(out StartIndex, WordLength: Integer): WordBool;
var
S: PChar;
begin
StartIndex := 0;
WordLength := 0;
Result := False;
if FPosition <= 0 then
FPosition := 1;
if FPosition >= Length(FText) then
Exit;
S := PChar(Text) + FPosition - 1;
if (S = nil) or (S^ = #0) or (Trim(S) = '') then
Exit;
while True do
begin
FCurrentWord := '';
GetNextWord(S, FCurrentWord, Delimiters);
WordLength := Length(FCurrentWord);
StartIndex := S - PChar(Text) - WordLength + 1;
FPosition := StartIndex + WordLength;
if (FCurrentWord <> '') and not CanIgnore(FCurrentWord) then
begin
FSuggestions.Clear;
Result := not WordExists(FCurrentWord);
if Result then
begin
GetWordSuggestions(FCurrentWord, FSuggestions);
Break;
end;
end;
if (S = nil) or (S^ = #0) or (Trim(S) = '') then
begin
Result := False;
Break;
end;
end;
end;
procedure TJvDefaultSpellChecker.Seek(Position: Integer);
begin
FPosition := Position;
end;
procedure TJvDefaultSpellChecker.SetText(const Value: string);
begin
FText := Value;
FPosition := 1;
end;
function TJvDefaultSpellChecker.WordExists(const Value: string): Boolean;
var
I: Integer;
Hash: Integer;
List: TStringList;
FWord: string;
begin
FWord := AnsiLowerCase(Value);
Hash := ELFHash(FWord) mod WordTableSize;
if FWordTable[Hash] <> nil then
begin
List := TStringList(FWordTable[Hash]);
for I := 0 to List.Count - 1 do
if AnsiSameText(PChar(List[I]), FWord) then
begin
Result := True;
Exit;
end;
end;
// ignore or user word?
Result := (UserDictionary.IndexOf(FWord) > -1) or (Ignores.IndexOf(FWord) > -1);
end;
function TJvDefaultSpellChecker.GetDictionary: string;
begin
Result := FDictionary;
end;
function TJvDefaultSpellChecker.GetUserDictionary: TStrings;
begin
Result := FUserDictionary;
end;
procedure TJvDefaultSpellChecker.SetDictionary(const Value: string);
begin
if FDictionary <> Value then
begin
FDictionary := Value;
BuildTables;
end;
end;
procedure TJvDefaultSpellChecker.SetUserDictionary(const Value: TStrings);
begin
FUserDictionary.Assign(Value);
end;
function TJvDefaultSpellChecker.GetIgnores: TStrings;
begin
Result := FIgnores;
end;
procedure TJvDefaultSpellChecker.SetIgnores(const Value: TStrings);
begin
FIgnores.Assign(Value);
end;
function TJvDefaultSpellChecker.GetDelimiters: TSysCharSet;
begin
Result := FDelimiters;
end;
procedure TJvDefaultSpellChecker.SetDelimiters(const Value: TSysCharSet);
begin
FDelimiters := Value;
end;
function TJvDefaultSpellChecker.CanIgnore(const Value: string): Boolean;
begin
Result := False;
if Assigned(FOnCanIgnore) then
FOnCanIgnore(Self, Value, Result);
end;
function TJvDefaultSpellChecker.GetCanIgnore: TJvSpellCheckIgnoreEvent;
begin
Result := FOnCanIgnore;
end;
procedure TJvDefaultSpellChecker.SetCanIgnore(const Value: TJvSpellCheckIgnoreEvent);
begin
FOnCanIgnore := Value;
end;
function TJvDefaultSpellChecker.GetCurrentWord: string;
begin
Result := FCurrentWord;
end;
//=== { TJvSpellChecker } ====================================================
function TJvSpellChecker.GetCanIgnore: TJvSpellCheckIgnoreEvent;
begin
Result := SpellChecker.OnCanIgnore;
end;
function TJvSpellChecker.GetDelimiters: TSysCharSet;
begin
Result := SpellChecker.Delimiters;
end;
function TJvSpellChecker.GetDictionary: TFileName;
begin
Result := SpellChecker.Dictionary;
end;
function TJvSpellChecker.GetIgnores: TStrings;
begin
Result := SpellChecker.Ignores;
end;
function TJvSpellChecker.GetSpellChecker: IJvSpellChecker;
begin
if FSpellChecker = nil then
begin
if Assigned(CreateSpellChecker) then
FSpellChecker := CreateSpellChecker()
else
FSpellChecker := InternalCreateSpellChecker;
end;
Result := FSpellChecker;
end;
function TJvSpellChecker.GetText: string;
begin
Result := SpellChecker.GetText;
end;
function TJvSpellChecker.GetUserDictionary: TStrings;
begin
Result := SpellChecker.UserDictionary;
end;
procedure TJvSpellChecker.SetCanIgnore(const Value: TJvSpellCheckIgnoreEvent);
begin
SpellChecker.OnCanIgnore := Value;
end;
procedure TJvSpellChecker.SetDelimiters(const Value: TSysCharSet);
begin
SpellChecker.Delimiters := Value;
end;
procedure TJvSpellChecker.SetDictionary(const Value: TFileName);
begin
SpellChecker.Dictionary := Value;
end;
procedure TJvSpellChecker.SetIgnores(const Value: TStrings);
begin
SpellChecker.Ignores := Value;
end;
procedure TJvSpellChecker.SetText(const Value: string);
begin
SpellChecker.SetText(Value);
end;
procedure TJvSpellChecker.SetUserDictionary(const Value: TStrings);
begin
SpellChecker.UserDictionary := Value;
end;
{$IFDEF UNITVERSIONING}
initialization
RegisterUnitVersion(HInstance, UnitVersioning);
{$ENDIF UNITVERSIONING}
{$IFDEF UNITVERSIONING}
finalization
UnregisterUnitVersion(HInstance);
{$ENDIF UNITVERSIONING}
end.

View File

@ -0,0 +1,124 @@
{-----------------------------------------------------------------------------
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: JvSpellIntf.PAS, released on 2003-08-19.
The Initial Developer of the Original Code is Peter Thörnqvist [peter3 at sourceforge dot net]
Portions created by Peter Thörnqvist are Copyright (C) 2003 Peter Thörnqvist.
All Rights Reserved.
Contributor(s):
You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.delphi-jedi.org
Description:
Interface declarations for classes that want to implement a spell
checker compatible with the TJvSpellChecker component.
Known Issues:
-----------------------------------------------------------------------------}
// $Id$
unit JvSpellIntf;
{$mode objfpc}{$H+}
//{$I jvcl.inc}
interface
uses
SysUtils, Classes;
type
TJvSpellCheckIgnoreEvent = procedure(Sender: TObject; const Value: string;
var CanIgnore: Boolean) of object;
IJvSpellChecker = interface
['{819CE37A-E3C1-4F54-B9E1-1CFAA8AFB887}']
// GetCurrentWord returns the currently found misspelled or missing word
function GetCurrentWord: string;
// Seek moves the internal text pointer to the position in Text given by Position.
// If Position >= Length(Text), calls to Next always returns false.
// Since positioning is 1-based, Seek(0) = Seek(1).
procedure Seek(Position: Integer);
// Next returns true if a misspelled word was found. If a misspelled word
// was found, StartIndex is set to the start of the word in Text and WordLength
// is set to the length of the word. Note that StartIndex is 1-based, i.e the first
// position in Text is 1. If Next returns false, it means that no more misspelled words
// can be found (i.e either when at end of Text or everything from the current point and
// onward is correctly spelled)
function Next(out StartIndex, WordLength: Integer): WordBool;
// The Text to spell check. When Text is changed, the internal position is reset
// to the start of Text (no need to call Seek)
function GetText: string;
procedure SetText(const Value: string);
property Text: string read GetText write SetText;
// Delimiters specifies the characters that are used to break strings into words.
function GetDelimiters: TSysCharSet;
procedure SetDelimiters(const Value: TSysCharSet);
property Delimiters: TSysCharSet read GetDelimiters write SetDelimiters;
// Adds the content of a dictionary to the internal list of words that are scanned for matches.
procedure SetDictionary(const Value: string);
function GetDictionary: string;
property Dictionary: string read GetDictionary write SetDictionary;
// "User" dictionary. This is a list of words, sorted.
// Manage the user dictionary by using the methods of TStrings.
// The main difference between a dictionary and a user dictionary is that you cannot change
// the content of the main dictionary from the interface. In addition, the UserDictionary is presumed to
// contain a list of words, one per line, sorted whereas the dictionary can be in any format (determined
// by the actual implementation).
function GetUserDictionary: TStrings;
procedure SetUserDictionary(const Value: TStrings);
property UserDictionary: TStrings read GetUserDictionary write SetUserDictionary;
// Ignores are used for words that should be ignored in the current session.
// To make an ignore persistent, you should call UserDictionary.Add
// and then save/load from file as needed.
function GetIgnores: TStrings;
procedure SetIgnores(const Value: TStrings);
property Ignores: TStrings read GetIgnores write SetIgnores;
// Suggestion returns the suggested replacements for a misspelled word. How the
// implementation determines valid and/or useful replacement words is defined
// by the implementation.
function GetSuggestions: TStrings;
property Suggestions: TStrings read GetSuggestions;
// Assign a handler to this event when you need to set up ignores for words
// that can't be captured using the ignore list and/or the user dictionary.
function GetCanIgnore: TJvSpellCheckIgnoreEvent;
procedure SetCanIgnore(const Value: TJvSpellCheckIgnoreEvent);
property OnCanIgnore: TJvSpellCheckIgnoreEvent read GetCanIgnore write SetCanIgnore;
end;
var
CreateSpellChecker: function: IJvSpellChecker = nil;
{$IFDEF UNITVERSIONING}
const
UnitVersioning: TUnitVersionInfo = (
RCSfile: '$URL$';
Revision: '$Revision$';
Date: '$Date$';
LogPath: 'JVCL\run'
);
{$ENDIF UNITVERSIONING}
implementation
{$IFDEF UNITVERSIONING}
initialization
RegisterUnitVersion(HInstance, UnitVersioning);
finalization
UnregisterUnitVersion(HInstance);
{$ENDIF UNITVERSIONING}
end.

View File

@ -0,0 +1,114 @@
object JvSpellerForm: TJvSpellerForm
Left = 306
Top = 251
BorderStyle = bsDialog
Caption = 'Spelling checker'
ClientHeight = 153
ClientWidth = 371
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object LblContext: TLabel
Left = 0
Top = 33
Width = 371
Height = 86
Hint = 'look ahead box'
Align = alClient
Caption = 'LblContext'
ParentShowHint = False
ShowHint = True
WordWrap = True
end
object TextPanel: TPanel
Left = 0
Top = 0
Width = 371
Height = 33
Align = alTop
BevelInner = bvRaised
BevelOuter = bvLowered
TabOrder = 0
object TxtSpell: TEdit
Left = 7
Top = 7
Width = 293
Height = 21
TabOrder = 0
Text = 'TxtSpell'
end
end
object ButtonPanel: TPanel
Left = 0
Top = 119
Width = 371
Height = 34
Align = alBottom
BevelInner = bvRaised
BevelOuter = bvLowered
TabOrder = 1
object BtnSkip: TButton
Left = 13
Top = 7
Width = 61
Height = 20
Hint = 'Skip this word'
Caption = '&Skip'
ParentShowHint = False
ShowHint = True
TabOrder = 0
end
object BtnChange: TButton
Left = 228
Top = 7
Width = 60
Height = 20
Hint = 'Change to corrected word'
Caption = '&Change'
ParentShowHint = False
ShowHint = True
TabOrder = 3
end
object BtnCancel: TButton
Left = 299
Top = 7
Width = 61
Height = 20
Hint = 'Abort all changes'
Caption = 'Cancel'
ModalResult = 2
ParentShowHint = False
ShowHint = True
TabOrder = 4
end
object BtnAdd: TButton
Left = 156
Top = 7
Width = 61
Height = 20
Hint = 'Add to user Dictionary'
Caption = '&Add'
ParentShowHint = False
ShowHint = True
TabOrder = 2
end
object BtnSkipAll: TButton
Left = 85
Top = 7
Width = 60
Height = 20
Hint = 'Skip all, update and finish'
Caption = 'S&kip All'
ModalResult = 1
ParentShowHint = False
ShowHint = True
TabOrder = 1
end
end
end

View File

@ -0,0 +1,433 @@
{-----------------------------------------------------------------------------
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.delphi-jedi.org
Known Issues:
-----------------------------------------------------------------------------}
// $Id$
unit JvSpellerForm;
{$mode objfpc}{$H+}
//{$I jvcl.inc}
interface
uses
SysUtils, Classes, Windows, Messages, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls,
JvComponent;
type
TJvSpeller = class;
TJvSpellerForm = class(TForm) //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(const SpellWord: string);
public
constructor Create(AOwner: TComponent); override;
procedure LoadDictionary(const AFile: string);
procedure LoadUserDictionary(const 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$';
Revision: '$Revision$';
Date: '$Date$';
LogPath: 'JVCL\run'
);
{$ENDIF UNITVERSIONING}
implementation
uses
StrUtils,
{$IFNDEF COMPILER12_UP}
JvJCLUtils,
{$ENDIF ~COMPILER12_UP}
JvConsts, JvResources, JvTypes;
{$R *.lfm}
procedure SaveAnsiFileFromString(const AFile, AText: string);
var
AnsiText: AnsiString;
begin
AnsiText := AnsiString(AText);
with TFileStream.Create(AFile, fmCreate) do
try
WriteBuffer(AnsiText[1], Length(AnsiText));
finally
Free;
end;
end;
function LoadAnsiFileToString(const AFile: string): string;
var
AnsiText: AnsiString;
begin
with TFileStream.Create(AFile, fmOpenRead) do
try
SetLength(AnsiText, Size);
if AnsiText <> '' then
ReadBuffer(AnsiText[1], Size);
finally
Free;
end;
Result := string(AnsiText);
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
begin
FSourceText := Copy(FSourceText, 1, FWordBegin - 1) + S +
Copy(FSourceText, FWordEnd, Length(FSourceText));
FWordEnd := FWordEnd + (Length(S) - (FWordEnd - FWordBegin));
Skip(Sender);
end;
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 := PosEx(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 := PosEx(Cr + Chr(96 + I), FUserDic, StartPos);
if P <> 0 then
FUserDicIndex[I] := P
else
FUserDicIndex[I] := FUserDicIndex[I - 1];
end;
end;
procedure TJvSpeller.LoadDictionary(const AFile: string);
begin
if FileExists(AFile) then
FDict := LoadAnsiFileToString(AFile)
else
FDict := '';
IndexDictionary;
end;
procedure TJvSpeller.LoadUserDictionary(const AFile: string);
begin
UserDictionary := AFile;
FUserDicChanged := False;
if FileExists(AFile) then
FUserDic := LoadAnsiFileToString(AFile)
else
FUserDic := '';
IndexUserDictionary;
end;
function TJvSpeller.ParseWord: string;
begin
if WordBegin and WordEnd then
Result := Copy(FSourceText, FWordBegin, FWordEnd - FWordBegin)
else
Result := '';
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(const 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;
Spw := ParseWord;
while Spw <> '' do
begin
S := AnsiLowerCase(Spw);
Index := Ord(S[1]) - 96;
if (Index > 0) and (Index < 27) then
StartPos := FDicIndex[Index]
else
StartPos := 1;
if PosEx(S + Cr, FDict, StartPos) = 0 then
begin
if FUserDic <> '' then
begin
if (Index > 0) and (Index < 27) then
StartPos := FUserDicIndex[Index]
else
StartPos := 1;
if PosEx(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
SaveAnsiFileFromString(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
SaveAnsiFileFromString(UserDictionary, FUserDic);
finally
FSpellerDialog.Free;
end;
Exit;
end;
end;
Spw := ParseWord;
end;
end;
procedure TJvSpeller.SpellNext;
var
Spw, S: string;
Index, StartPos: Integer;
begin
Spw := ParseWord;
while Spw <> '' do
begin
S := AnsiLowerCase(Spw);
Index := Ord(S[1]) - 96;
if (Index > 0) and (Index < 27) then
StartPos := FDicIndex[Index]
else
StartPos := 1;
if PosEx(S + Cr, FDict, StartPos) = 0 then
begin
if FUserDic <> '' then
begin
if (Index > 0) and (Index < 27) then
StartPos := FUserDicIndex[Index]
else
StartPos := 1;
if PosEx(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;
Spw := ParseWord;
end;
FSpellerDialog.ModalResult := mrOk;
end;
function TJvSpeller.WordBegin: Boolean;
var
L: Integer;
begin
L := Length(FSourceText);
FWordBegin := FWordEnd;
while (FWordBegin <= L) and (not CharInSet(FSourceText[FWordBegin], ['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 CharInSet(FSourceText[FWordEnd], ['a'..'z', 'A'..'Z']) do
Inc(FWordEnd);
Result := (FWordEnd <= L);
end;
{$IFDEF UNITVERSIONING}
initialization
RegisterUnitVersion(HInstance, UnitVersioning);
finalization
UnregisterUnitVersion(HInstance);
{$ENDIF UNITVERSIONING}
end.