RVThread.pas
上传用户:daoqigc
上传日期:2021-04-20
资源大小:2795k
文件大小:19k
源码类别:

RichEdit

开发平台:

Delphi

  1. {*******************************************************}
  2. {                                                       }
  3. {       RichView                                        }
  4. {       TRVWordEnumThread: thread for word enumeration. }
  5. {       Used for live spelling check.                   }
  6. {                                                       }
  7. {       Copyright (c) Sergey Tkachenko                  }
  8. {       svt@trichview.com                               }
  9. {       http://www.trichview.com                        }
  10. {                                                       }
  11. {*******************************************************}
  12. unit RVThread;
  13. {$I RV_Defs.inc}
  14. interface
  15. uses Windows, Classes,
  16.      RVScroll, CRVData;
  17. {$IFNDEF RVDONOTUSELIVESPELL}
  18. type
  19.   { ---------------------------------------------------------------------------
  20.     TRVWordEnumThread: thread for enumeration of word in TCustomRichView
  21.     (used for live spelling check).
  22.     Fields:
  23.     - FRichView - the component to check;
  24.     - CurRVData, CurItem - item being checked;
  25.     - CurItemCheckStarted - true, if current item checking is already started;
  26.     - CurText - text of curent item;
  27.     - CurTextStartPtr = CurText;
  28.     - CurTextPtr - current position in CurText;
  29.     - StopWorking is used to stop the thread at a safe place. Possible values:
  30.       0 - working;
  31.       1 - is set from the main process; the thread must stop at the safe place
  32.           and set StopWorking to 2;
  33.       2 - thread is ready to stop;
  34.       3 - thread's Execute is finished.
  35.     - WordOffs, WordLen - current word position, used in AddMisspelling;
  36.     - NextRVData, NextItemNo, NextOffs - used to set the spell checking position
  37.       when the thread will be ready to resume.
  38.     - HasModifiedWord - there is a word (in area that's already checked) that
  39.       was modified and should be checked later.
  40.     - CheckUnchecked - the thread must check an item containing modified word.
  41.       Except for this word, this item was checked, and may already contain
  42.       marked misspellings.
  43.     - Delaying - is set to True in ContinueCheck. Instructs the thread to sleep for
  44.       some time.
  45.     - EditedWhileDelayed - is set to True in ContinueCheck. If the tread is sleeping,
  46.       it will be put asleep again when it will wake up.
  47.   }
  48.   TRVWordEnumThread = class (TThread)
  49.     private
  50.       NextItemNo, NextOffs: Integer;
  51.       StopWorking: Integer;
  52.       CurText: String;
  53.       CurTextPtr, CurTextStartPtr: PChar;
  54.       FRichView: TRVScroller;
  55.       CurItemNo, WordOffs, WordLen: Integer;
  56.       CurItemCheckStarted: Boolean;
  57.       NextRVData, CurRVData: TCustomRVData;
  58.       FForceSetBack: Boolean;
  59.       Delaying, EditedWhileDelayed: Boolean;
  60.       procedure AddMisspelling;
  61.       procedure SyncProc;
  62.       function GetNextWord: String;
  63.       procedure GetMisspellingDrawItems(var DItemNo1, DItemNo2: Integer);
  64.       procedure SetBack;
  65.     protected
  66.       procedure Execute; override;
  67.       procedure DoTerminate; override;
  68.     public
  69.       HasModifiedWord, CheckUnchecked: Boolean;
  70.       constructor Create;
  71.       procedure Reset(RichView: TRVScroller);
  72.       procedure LaterSetBackTo(RVData: TCustomRVData; ItemNo, Offs: Integer);
  73.       procedure SetBackToCurItem(RVData: TCustomRVData; ItemNo: Integer);
  74.       function IsChecked(RVData: TCustomRVData; ItemNo: Integer): Boolean;
  75.       procedure RemoveRVData(RVData: TCustomRVData);
  76.       destructor Destroy; override;
  77.       procedure Stop(ResetNexts: Boolean);
  78.       procedure ContinueCheck;
  79.       procedure Finish;
  80.   end;
  81. {$ENDIF}
  82. implementation
  83. {$IFNDEF RVDONOTUSELIVESPELL}
  84. uses RichView, CRVFData, RVItem, RVFuncs, Forms;
  85. {================================= TRVWordEnumThread ==========================}
  86. { Constructor. }
  87. constructor TRVWordEnumThread.Create;
  88. begin
  89.   inherited Create(True);
  90.   FreeOnTerminate := True;
  91.   Priority := tpLowest;
  92.   //MessageBox(0, 'Live spelling thread is created', 'LS', 0);
  93. end;
  94. {------------------------------------------------------------------------------}
  95. { Destructor. }
  96. destructor TRVWordEnumThread.Destroy;
  97. begin
  98.   inherited;
  99. end;
  100. procedure TRVWordEnumThread.DoTerminate;
  101. begin
  102.   inherited;
  103.   //MessageBox(Application.Handle, 'Live spelling thread is destroyed', 'LS', 0);
  104. end;
  105. {------------------------------------------------------------------------------}
  106. { If RichView<>nil, restarts the thread for RichView.
  107.   If RichView=nil, prepares for detaching this thread. }
  108. procedure TRVWordEnumThread.Reset(RichView: TRVScroller);
  109. begin
  110.   FRichView := RichView;
  111.   if RichView<>nil then begin
  112.     CurRVData := TCustomRichView(RichView).RVData;
  113.     CurItemNo := 0;
  114.     CurItemCheckStarted := False;
  115.     FForceSetBack := False;
  116.     CheckUnchecked := False;
  117.     HasModifiedWord := False;
  118.     NextRVData := nil;
  119.     NextItemNo := -1;
  120.   end;
  121. end;
  122. {------------------------------------------------------------------------------}
  123. { Waits while finishing processing the current item and suspends the thread.
  124.   if ResetNexts, (NextRVData,NextItemNo,NextOffs) are set to "undefined".
  125.   ResetNexts=False is used only before resuming stopped thread (for any case -
  126.   it must already be stopped).
  127.   Important case: if the tread is sleeping (Delaying=True), it will not be stopped.
  128.   But, if it will wake up, it will do nothing because StopWorking=1.
  129.   Context: main process (caller). }
  130. procedure TRVWordEnumThread.Stop(ResetNexts: Boolean);
  131. {$IFNDEF RICHVIEWDEF6}
  132. var Msg: TMsg;
  133. {$ENDIF}
  134. begin
  135.   if (StopWorking=3) then
  136.     exit;
  137.   if ResetNexts then begin
  138.     NextRVData := nil;
  139.     NextItemNo := -1;
  140.     NextOffs   := -1;
  141.   end;
  142.   if Suspended then
  143.     exit;
  144.   StopWorking := 1;
  145.   if not Delaying then begin
  146.     Priority := tpNormal;
  147.     while (StopWorking=1) and not Suspended do
  148.       {$IFDEF RICHVIEWDEF6}CheckSynchronize;{$ELSE}
  149.       if PeekMessage(Msg, 0, $8FFF, $8FFF, PM_REMOVE) then
  150.           DispatchMessage(Msg);
  151.       {$ENDIF};
  152.     if not Suspended then
  153.       Suspend;
  154.   end;
  155.   if ResetNexts and (CurRVData<>nil) and (CurItemNo>=0) and
  156.      (CurItemNo<CurRVData.GetRVData.ItemCount) then
  157.     CurRVData.GetRVData.GetItem(CurItemNo).ClearLiveSpellingResult;
  158.   CurItemCheckStarted := False;
  159.   Priority := tpLowest;
  160. end;
  161. {------------------------------------------------------------------------------}
  162. { Resumes the thread stopped with Stop.
  163.   If (NextRVData,NextItemNo,NextOffs) are defined, calls SetBackTo("undefined").
  164.   Context: main process (caller). }
  165. procedure TRVWordEnumThread.ContinueCheck;
  166. begin
  167.   if NextItemNo>=0 then
  168.     SetBack;
  169.   Delaying := True;
  170.   EditedWhileDelayed := True;
  171.   StopWorking := 0;
  172.   if Suspended then
  173.     Resume;
  174. end;
  175. {------------------------------------------------------------------------------}
  176. { Waits while finishing processing the current item and closes the tread.
  177.   Context: main process (caller) }
  178. procedure TRVWordEnumThread.Finish;
  179. {$IFNDEF RICHVIEWDEF6}
  180. var Msg: TMsg;
  181. {$ENDIF}
  182. begin
  183.   if StopWorking=3 then
  184.     exit;
  185.   StopWorking := 1;
  186.   Priority := tpNormal;
  187.   while (StopWorking=1) and not Suspended do
  188.     {$IFDEF RICHVIEWDEF6}CheckSynchronize;{$ELSE}
  189.     if PeekMessage(Msg, 0, $8FFF, $8FFF, PM_REMOVE) then
  190.         TranslateMessage(Msg);
  191.     {$ENDIF};
  192.   if Suspended then
  193.     Resume;
  194.   Terminate;
  195. end;
  196. {------------------------------------------------------------------------------}
  197. { Adds info about misspelling for CurRVData.GetRVData.GetItem(CurItemNo).
  198.   WordOffs, WordLen defines the misspelled word.
  199.   DItemNo1..DItemNo2 - range of draw items displaying this word.
  200.   Context: main process (Synchronize). }
  201. procedure TRVWordEnumThread.AddMisspelling;
  202. var i: Integer;
  203.     DItemNo1, DItemNo2: Integer;
  204. begin
  205.   with TCustomRVFormattedData(CurRVData.GetRVData) do begin
  206.     GetItem(CurItemNo).AddMisspelling(WordOffs, WordLen);
  207.     GetMisspellingDrawItems(DItemNo1, DItemNo2);
  208.     if DItemNo1>=0 then
  209.       for i := DItemNo1 to DItemNo2 do
  210.         InvalidateDrawItem(i, 3);
  211.   end;
  212. end;
  213. {------------------------------------------------------------------------------}
  214. { This procedure is called using Synchronize before suspending the thread when
  215.   all work is complete. It prevents suspending the thread while calling
  216.   Stop or Finish (ony inside CheckSynchronize). }
  217. procedure TRVWordEnumThread.SyncProc;
  218. begin
  219. end;
  220. {------------------------------------------------------------------------------}
  221. { Assigns (NextRVData,NextItemNo,NextOffs) if they are undefined or
  222.   if they define a position after the position specified in parameters.
  223.   It's assumed that if (NextItemNo,NextOffs) are defined they are
  224.   inside the same RVData, or, if RVDatas are different, the parameters define
  225.   the position before (NextRVData,NextItemNo,NextOffs) }
  226. procedure TRVWordEnumThread.LaterSetBackTo(RVData: TCustomRVData; ItemNo, Offs: Integer);
  227. begin
  228.   if (RVData<>NextRVData) or (NextItemNo<0) or
  229.      ((ItemNo<NextItemNo) or ((NextItemNo=ItemNo) and (Offs<NextOffs))) then begin
  230.     NextRVData := RVData;
  231.     NextItemNo := ItemNo;
  232.     NextOffs   := Offs;
  233.   end;
  234. end;
  235. {------------------------------------------------------------------------------}
  236. { If HasModifiedWord, sets (NextRVData,NextItemNo,NextOffs) to the beginning
  237.   of (RVData, ItemNo) (if this position is before the current position).
  238.   This procedure is called at the beginning of all editing operations (except
  239.   for typing), the caret position is passed in parameters. }
  240. procedure TRVWordEnumThread.SetBackToCurItem(RVData: TCustomRVData; ItemNo: Integer);
  241. var r: Integer;
  242. begin
  243.   if not HasModifiedWord then
  244.     exit;
  245.   HasModifiedWord := False;
  246.   r := RVCompareLocations(CurRVData, CurItemNo, RVData, ItemNo);
  247.   if r>=0 then begin
  248.     if r>0 then
  249.       RVData.GetRVData.GetItem(ItemNo).ClearLiveSpellingResult
  250.     else
  251.       CheckUnchecked := True;
  252.     NextRVData := RVData;
  253.     NextItemNo := ItemNo;
  254.     NextOffs   := 0;
  255.     CheckUnchecked := False;
  256.     SetBack;
  257.   end;
  258. end;
  259. {------------------------------------------------------------------------------}
  260. { This method is called when RVData becomes invalid (for example, table cell is
  261.   deleted. If this RVData=CurRVData, we set FForceSetBack flag.
  262.   This flag prevents calling RVCompareLocations() in the SetBackMethod. }
  263. procedure TRVWordEnumThread.RemoveRVData(RVData: TCustomRVData);
  264. begin
  265.   if RVData = CurRVData then
  266.     FForceSetBack := True;
  267. end;
  268. {------------------------------------------------------------------------------}
  269. { Set the current position to (NextRVData,NextItemNo, NextOffs),
  270.   if this position is before the current position.
  271.   Context: main process (caller), the thread is suspended. }
  272. procedure TRVWordEnumThread.SetBack;
  273. var r: Integer;
  274.     RVData : TCustomRVData;
  275.     ItemNo, Offs: Integer;
  276. begin
  277.   if NextItemNo<0 then begin
  278.     if FForceSetBack then
  279.       Reset(FRichView);
  280.     exit;
  281.   end;
  282.   RVData := NextRVData;
  283.   ItemNo := NextItemNo;
  284.   Offs   := NextOffs;
  285.   NextItemNo := -1;
  286.   NextOffs   := -1;
  287.   NextRVData := nil;
  288.   if FForceSetBack then
  289.     r := 1
  290.   else
  291.     r := RVCompareLocations(CurRVData, CurItemNo, RVData, ItemNo);
  292.   if r>=0 then begin
  293.     CurRVData := RVData;
  294.     CurItemNo := ItemNo;
  295.     if r>0 then begin
  296.       CurItemCheckStarted := Offs>1;
  297.       if Offs>1 then begin
  298.         CurText := CurRVData.GetRVData.GetItemTextA(CurItemNo);
  299.         CurTextStartPtr := PChar(CurText);
  300.         CurTextPtr := CurTextStartPtr+Offs-1;
  301.         if CurTextPtr>CurTextStartPtr then
  302.           dec(CurTextPtr);
  303.         while (CurTextPtr>CurTextStartPtr) and
  304.           not CurRVData.IsDelimiterA(CurTextPtr^) do
  305.           dec(CurTextPtr);
  306.       end;
  307.       end
  308.     else if r=0 then begin
  309.       if not CheckUnchecked then
  310.         CurRVData.GetRVData.GetItem(CurItemNo).ClearLiveSpellingResult;
  311.       CurItemCheckStarted := False;
  312.     end
  313.   end;
  314.   FForceSetBack := False;  
  315. end;
  316. {------------------------------------------------------------------------------}
  317. { Was this item already checked? }
  318. function TRVWordEnumThread.IsChecked(RVData: TCustomRVData; ItemNo: Integer): Boolean;
  319. begin
  320.   Result := RVCompareLocations(CurRVData, CurItemNo, RVData, ItemNo)>=0;
  321. end;
  322. {------------------------------------------------------------------------------}
  323. { Returns the next word for for the current text item.
  324.   Item text is stored in CurText,
  325.   pointer to the start of CurText - in CurTextStartPtr,
  326.   pointer to the current position in text - in CurTextPtr.
  327.   Returns '' if the item is completely processed.
  328.   Context: thread. }
  329. function TRVWordEnumThread.GetNextWord: String;
  330. var StartPtr: PChar;
  331.     CM: Boolean;
  332. begin
  333.   Result := '';
  334.   CM := True;
  335.   while True do begin
  336.     if CurTextPtr^=#0 then begin
  337.       CheckUnchecked := False;
  338.       exit;
  339.     end;
  340.     if (CurTextPtr^<>'''') and not CurRVData.IsDelimiterA(CurTextPtr^) then begin
  341.       if CM and (not CheckUnchecked or
  342.         not CurRVData.GetRVData.GetItem(CurItemNo).IsMisspelled(CurTextPtr-CurTextStartPtr+1)) then
  343.         break
  344.       else
  345.         CM := False;
  346.       end
  347.     else
  348.       CM := True;
  349.     inc(CurTextPtr);
  350.   end;
  351.   StartPtr := CurTextPtr;
  352.   while (CurTextPtr^<>#0) and not CurRVData.IsDelimiterA(CurTextPtr^) do
  353.     inc(CurTextPtr);
  354.   while (CurTextPtr-1)^='''' do
  355.     dec(CurTextPtr);
  356.   WordOffs := StartPtr-CurTextStartPtr+1;
  357.   WordLen := CurTextPtr-StartPtr;
  358.   Result := Copy(CurText, WordOffs, WordLen);
  359. end;
  360. {------------------------------------------------------------------------------}
  361. { Returns a range of drawing items used to display misspelling
  362.   in (DItemNo1, DItemNo2).
  363.   Misspelling is defined by CurRVData, CurItemNo, WordOffs, WordLen.
  364.   Context: main process (Synchronize).  }
  365. procedure TRVWordEnumThread.GetMisspellingDrawItems(var DItemNo1, DItemNo2: Integer);
  366. var offs: Integer;
  367. begin
  368.   if TCustomRVFormattedData(CurRVData.GetRVData).DrawItems.Count=0 then begin
  369.     DItemNo1 := -1;
  370.     DItemNo2 := -1;
  371.     exit;
  372.   end;
  373.   try
  374.     TCustomRVFormattedData(CurRVData.GetRVData).Item2DrawItem(CurItemNo, WordOffs, DItemNo1, Offs);
  375.     TCustomRVFormattedData(CurRVData.GetRVData).Item2DrawItem(CurItemNo, WordOffs+WordLen, DItemNo2, Offs);
  376.   except
  377.     DItemNo1 := -1;
  378.     DItemNo2 := -1;  
  379.   end;
  380. end;
  381. {------------------------------------------------------------------------------}
  382. { Main thread procedure. }
  383. procedure TRVWordEnumThread.Execute;
  384. var CurWord: String;
  385.     StoreSub: TRVStoreSubRVData;
  386.     TempRVData: TCustomRVData;
  387.     Misspelled: Boolean;
  388. begin
  389.   while not Terminated do begin
  390.     if Delaying then begin
  391.       EditedWhileDelayed := False;
  392.       Windows.Sleep(50);
  393.       Delaying := EditedWhileDelayed;
  394.       continue;
  395.     end;
  396.     case StopWorking of
  397.       0:
  398.         begin
  399.           if Assigned(FRichView) then
  400.             if CurItemNo>=CurRVData.GetRVData.ItemCount then begin
  401.               if (CurRVData=TCustomRichView(FRichView).RVData) then begin
  402.                 // spelling check is complete
  403.                 if not Terminated then begin
  404.                   Synchronize(SyncProc);
  405.                   if StopWorking=0 then
  406.                     Suspend;
  407.                 end;
  408.                 end
  409.               else begin
  410.                 // spelling check of cell is complete
  411.                 CurRVData.GetParentInfo(CurItemNo, StoreSub);
  412.                 TempRVData :=
  413.                   TCustomRVData(CurRVData.GetAbsoluteParentData.GetItem(CurItemNo).GetSubRVData(StoreSub, rvdNext));
  414.                 if TempRVData<>nil then begin
  415.                   // going to the next cell
  416.                   CurRVData := TempRVData;
  417.                   CurItemNo := 0;
  418.                   end
  419.                 else begin
  420.                   // continuing after the table
  421.                   CurRVData := CurRVData.GetAbsoluteParentData;
  422.                   inc(CurItemNo);
  423.                 end;
  424.                 CurRVData := CurRVData.GetSourceRVData;
  425.                 StoreSub.Free;
  426.                 StoreSub := nil;
  427.               end;
  428.               end
  429.             else begin
  430.               if (CurRVData.GetRVData.GetItemStyle(CurItemNo)>=0) and
  431.                  (CheckUnchecked or
  432.                   not (rvisSpellChecked in CurRVData.GetRVData.GetItem(CurItemNo).ItemState)) then begin
  433.                 // checking a text item
  434.                 if not CurItemCheckStarted then begin
  435.                   CurText := CurRVData.GetRVData.GetItemTextA(CurItemNo);
  436.                   if (CurRVData.GetRVStyle<>nil) and
  437.                      (
  438.                       CurRVData.GetRVStyle.TextStyles[CurRVData.GetRVData.GetItemStyle(CurItemNo)].Jump and
  439.                       (RVIsURL(CurText) or RVIsEmail(CurText))
  440.                      )
  441.                      {$IFDEF RICHVIEWCBDEF3}
  442.                       or
  443.                      (CurRVData.GetRVStyle.TextStyles[CurRVData.GetRVData.GetItemStyle(CurItemNo)].Charset=SYMBOL_CHARSET)
  444.                      {$ENDIF}
  445.                       then begin
  446.                      CurRVData.GetRVData.GetItem(CurItemNo).ClearLiveSpellingResult;
  447.                      CurText := '';
  448.                      end
  449.                   else if not CheckUnchecked then
  450.                     CurRVData.GetRVData.GetItem(CurItemNo).ClearLiveSpellingResult;
  451.                   CurTextStartPtr := PChar(CurText);
  452.                   CurTextPtr := CurTextStartPtr;
  453.                   CurItemCheckStarted := True;
  454.                 end;
  455.                 CurWord := GetNextWord;
  456.                 if CurWord='' then begin
  457.                   Include(CurRVData.GetRVData.GetItem(CurItemNo).ItemState, rvisSpellChecked);
  458.                   inc(CurItemNo);
  459.                   CurItemCheckStarted := False;
  460.                   end
  461.                 else begin
  462.                   Misspelled := False;
  463.                   {$IFDEF RVLIVESPELLEXEVENT}
  464.                   if Assigned(TCustomRichView(FRichView).OnSpellingCheckEx) then
  465.                     TCustomRichView(FRichView).OnSpellingCheckEx(TCustomRichView(FRichView), CurWord,
  466.                       CurRVData, CurItemNo, Misspelled)
  467.                   else
  468.                   {$ENDIF}
  469.                   if Assigned(TCustomRichView(FRichView).OnSpellingCheck) then
  470.                     TCustomRichView(FRichView).OnSpellingCheck(TCustomRichView(FRichView), CurWord,
  471.                       CurRVData.GetRVData.GetItemStyle(CurItemNo), Misspelled);
  472.                   if Misspelled then
  473.                     Synchronize(AddMisspelling);
  474.                 end;
  475.                 end
  476.               else begin
  477.                 TempRVData := TCustomRVData(CurRVData.GetRVData.GetItem(CurItemNo).GetSubRVData(StoreSub, rvdFirst));
  478.                 if TempRVData<>nil then begin
  479.                   // entering table
  480.                   CurRVData := TempRVData;
  481.                   CurItemNo := 0;
  482.                   StoreSub.Free;
  483.                   StoreSub := nil;
  484.                   end
  485.                 else
  486.                   inc(CurItemNo);
  487.               end;
  488.             end;
  489.         end;
  490.       1:
  491.         begin
  492.           StopWorking := 2;
  493.         end;
  494.     end;
  495.   end;
  496.   StopWorking := 3;
  497.   CurText := '';
  498. end;
  499. {$ENDIF}
  500. end.