Bad RTF table causing RichView to error

General TRichView support forum. Please post your questions here
jgkoehn
Posts: 322
Joined: Thu Feb 20, 2020 9:32 pm

Bad RTF table causing RichView to error

Post by jgkoehn »

Greetings Sergey,

I am getting an error in this code:
First chance exception at $75D50144. Exception class EListError with message 'List index out of bounds (-1). TRVList is empty'.
I know it is bad RTF code on a Table (however, I can't always fix the bad RTF tables. These can be from users. Is there a way to handle this more robustly?)

Current code in RV22.2

Code: Select all

function TRVRTFReader.DoTable(WhatHappens: TRVRTFTableEventKind): Boolean;
begin
  Result := True;
  UpdateMarker;
  if Assigned(FOnTable) then
    FOnTable(Self, WhatHappens, Result);
  AfterTableRow := WhatHappens in [rvf_tbl_RowEnd, rvf_tbl_TableEnd,
    rvf_tbl_TableForcedEnd];
end;
Ideas?
Sergey Tkachenko
Site Admin
Posts: 17878
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Re: Bad RTF table causing RichView to error

Post by Sergey Tkachenko »

This exception happens in try..except block, and finally LoadRTF function simply returns False.
The exception is not noticeable unless debugging in Delhi IDE.
jgkoehn
Posts: 322
Joined: Thu Feb 20, 2020 9:32 pm

Re: Bad RTF table causing RichView to error

Post by jgkoehn »

Is this related Sergey?
It is showing up in the Richview interface but not always.
Attachments
Screenshot 2025-07-13 072233.png
Screenshot 2025-07-13 072233.png (30.67 KiB) Viewed 51142 times
jgkoehn
Posts: 322
Joined: Thu Feb 20, 2020 9:32 pm

Re: Bad RTF table causing RichView to error

Post by jgkoehn »

It is highly possible I've done something wrong and just have no idea on how to get to the bottom of it.
Sergey Tkachenko
Site Admin
Posts: 17878
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Re: Bad RTF table causing RichView to error

Post by Sergey Tkachenko »

If the error text is shown on TRichView itself, this mean that an exception happens while drawing content.
Most probably. it happens because TRichView is not formatted.
Do not forget to call Format after loading.
jgkoehn
Posts: 322
Joined: Thu Feb 20, 2020 9:32 pm

Re: Bad RTF table causing RichView to error

Post by jgkoehn »

I have found the problem but not sure why it exists:

Code: Select all

      //SHIFT not pressed (UP)
    if GetKeyState(VK_SHIFT) and $ff00 = 0 then begin
      try
        s := reader.GetItemTag(i);
        p := Pos(':', s);
        if (p>0) and(p<6) then continue;
        reader.SetItemTag(i, ''); <------------if I comment this out I am fine
      except
        on E: Exception do begin
          OutputDebugString(PChar('WMAfterPaste GetItemTag error: ' + E.Message));
          Continue;
        end;
      end;
    end;
reader.SetItemTag(i, ''); <------------if I comment this out I am fine

I have tried format, reformat, formattail, SetItemTagEd and many more items. This is in a Timer event. I can run through it just fine in Delphi12.3 debugger but as soon as I click on another open TRichViewEdit I get an this TRichViewItem list error.
The Style type is set to rvf_sInsertMerge.

And then I just start getting a cascade of TRVItemList errors......until my program crashes basically.
standay
Posts: 302
Joined: Fri Jun 18, 2021 3:07 pm

Re: Bad RTF table causing RichView to error

Post by standay »

You might check that i <> -1, and i <= reader.itemcount-1.

Stan
jgkoehn
Posts: 322
Joined: Thu Feb 20, 2020 9:32 pm

Re: Bad RTF table causing RichView to error

Post by jgkoehn »

Here is the function it starts in readerpaste and ends in WMAfterPaste and it all seems to work fine until I click on a different reader....

Code: Select all

var
  MuteReaderPasteDelegate: Boolean = False;
  TextStyleBeforePaste, ParaStyleBeforePaste: Integer;
procedure TfmBookView.readerPaste(Sender: TCustomRichViewEdit;
  var DoDefault: Boolean);
var
  sItemNo, sItemOffs, eItemno, eItemOffs: Integer;
  CurItem: Integer;
  InsertWS: String;
begin
  //So Reader paste doesn't take over edtTopics.
 { if edtTopics.Focused then begin
    //edtTopics.PasteFromClipboard;
    exit;
  end else if edtVol.Focused then begin
    //edtVol.PasteFromClipboard;
    exit;
  end;}

  // Early validation
  if not Assigned(reader) then begin
    DoDefault := True;
    Exit;
  end;

  // Ensure document is formatted before accessing properties
  //reader.ReFormat; //keeps the caret and selection

  // Safe bounds checking
  if reader.ItemCount = 0 then begin
    DoDefault := True;
    Exit;
  end;

  try
    CurItem := reader.CurItemNo;
    // Validate CurItem is within bounds
    if (CurItem < 0) or (CurItem >= reader.ItemCount) then begin
      CurItem := 0;
    end;

    // Safe access to style properties
    TextStyleBeforePaste := 0;
    ParaStyleBeforePaste := 0;

    if (reader.CurTextStyleNo >= 0) and (reader.CurTextStyleNo < reader.Style.TextStyles.Count) then
      TextStyleBeforePaste := reader.CurTextStyleNo;

    if (reader.CurParaStyleNo >= 0) and (reader.CurParaStyleNo < reader.Style.ParaStyles.Count) then
      ParaStyleBeforePaste := reader.CurParaStyleNo;


    //If Called from Paste To Editor but only from inside of Bookview this boolean helps with that.

    if FVerseLinkPasteToEditor and
      (TextStyleBeforePaste < reader.Style.TextStyles.Count) and
      reader.Style.TextStyles[TextStyleBeforePaste].Jump then begin
      //Check if the item is a jump link should be since that is where this came from.
      reader.SetSelectionBounds(CurItem,reader.GetOffsAfterItem(CurItem),CurItem,reader.GetOffsAfterItem(CurItem));
      InsertWS := _getGlobalIni.ReadString('general','default.bkv.paste.to.editor.str', ' ');
      InsertWS := GlobalUtils.StringReplaceU(InsertWS,'#9', #9);
      InsertWS := GlobalUtils.StringReplaceU(InsertWS,'#13', #13);
      InsertWS := GlobalUtils.StringReplaceU(InsertWS,'#10', #10);
      reader.InsertTextW(InsertWS); //Now insert a space, enter something the user desires.
    end;

    FVerseLinkPasteToEditor := False; //Unfortunately this should be done immeditately after the popup closes but I can't get it to trigger OnClose
    //This problem can cause a user to paste in a link and could cause a problem but will work the second time because this will be set.

    reader.GetSelectionBounds(sItemNo, sItemOffs, eItemno, eItemOffs, True);
    if (sItemNo = eItemNo) and (sItemOffs = eItemOffs) and (sItemOffs = 1) then
      Dec(CurItem);

    // Validate CurItem again after potential decrement
    if CurItem < 0 then CurItem := 0;

    //call the WMAfterPaste in 300ms...
    //need to use a timer because there is a Application.ProcessMessages; call in
    //the uFrmCopyVerses that would 'eat' the PostMessage below
    //FAfterPaste_Handle := Handle;
    FAfterPaste_CurItem := CurItem;
    FAfterPaste_ItemCount := reader.ItemCount;
    // Only set timer if window handle is valid
    {if IsWindow(Handle) then
      SetTimer(Handle, 1, 300, @TimerProc_AfterPaste);}
      // Use timer instead of Windows callback
    FAfterPasteTimer.Enabled := False;  // Stop any existing timer
    FAfterPasteTimer.Enabled := True;   // Start new timer
    // Post message directly instead of using timer
    //PostMessage(Handle, WM_AFTERPASTE, FAfterPaste_CurItem, FAfterPaste_ItemCount);


    //This handler is called with CTRL+V. In that case, the Paste method of RVE
    //is called which does NOT handle the HTML format. To handle HTML, we need
    //to calle the RichViewActions.OnPaste action. So, if the clipboard contains
    //HTML, with call the rvActionPaste2 action and set DoDefault to false, to not
    //alow the RVE to handle the Paste itself.
    if IsClipboardFormatAvailable(CFRV_HTML) and
       (not IsClipboardFormatAvailable(CFRV_RVF)) and
       (not IsClipboardFormatAvailable(CFRV_RTF)) and
       (not MuteReaderPasteDelegate)then begin
      try
        MuteReaderPasteDelegate := True;
        MainReaderForm.rvActionPaste2.Execute;
        DoDefault := False;
      finally
        MuteReaderPasteDelegate := False;
      end
    end
    else begin
      DoDefault := True;
    end;
  except
    on E: Exception do begin
      debugOnScreen(PChar('readerPaste error: ' + E.Message));
      DoDefault := True;
    end;
  end;
end;

Code: Select all

var
  FixEswordLinksOnPasteRE: TRegExx = ();

{ This code tries to remove dead hyperlinks on paste... I am not sure at all
  about it. Commented out above
  Need to do ApplyStyleConversion here!!! }
procedure TFmBookView.WMAfterPaste(var msg: TMessage);
var
  i, CurItem, ItemCount, c, p, minIndex, maxIndex: Integer;
  RVTag: TRVTag;
  cpd: TCheckpointData;
  s: string;
  name: TRVUnicodeString;
  re: Boolean;
  readerName: String;
begin


  // Validate we're still in the correct context
  if not (Self.Focused or Self.ContainsControl(Screen.ActiveControl)) then begin
    debugOnScreen('WMAfterPaste: BookView no longer active, ignoring');
    Exit;
  end;

  // Validate that the reader is still valid and assigned
  if not Assigned(reader) then begin
    debugOnScreen('WMAfterPaste: Reader not assigned, ignoring message');
    Exit;
  end;

  // Check if reader handle is valid
  if not reader.HandleAllocated then begin
    debugOnScreen('WMAfterPaste: Reader handle not allocated, ignoring message');
    Exit;
  end;

  // Validate that we're not in a destroying state
  if (csDestroying in ComponentState) or (csLoading in ComponentState) then begin
    debugOnScreen('WMAfterPaste: Component destroying/loading, ignoring message');
    Exit;
  end;

  //RvBookUtil.ConvertToUnicodeAndMisc(reader.RVData, FCurModule, False, 0);
  if (reader.BiDiMode = rvbdUnspecified) then
    if RVShouldUserComplexRendering(reader) then
      reader.BiDiMode := rvbdLeftToRight;

  //remove possible protections from everything pasted
  //with reader.Style do
    if Assigned(FCurModule) then begin
      //user module: remove protection
      if FCurModule.IsUserModule then begin
        for i:=0 to reader.Style.TextStyles.Count-1 do
          reader.Style.TextStyles[i].Protection := [];
        for i:=0 to reader.Style.ParaStyles.Count-1 do
          reader.Style.ParaStyles[i].Options := reader.Style.ParaStyles[i].Options - [rvpaoReadOnly, rvpaoNoWrap,
              rvpaoDoNotWantReturns];
      end
    end;


  CurItem := msg.WParam;
  ItemCount := msg.LParam;

  // Validate parameters and get current item count
  if (CurItem < 0) or (reader.ItemCount = 0) then Exit;

  // Ensure CurItem is within valid range
  if CurItem >= reader.ItemCount then
    CurItem := reader.ItemCount - 1;

  //Check if CurItem is a TBitmap if it is convert it to PNG
  BitmapToPNG(CurItem);

  // Calculate safe range for the loop
  maxIndex := Min(CurItem + reader.ItemCount - ItemCount + 1, reader.ItemCount - 1);

   // Additional safety check
  if maxIndex < CurItem + 1 then
    maxIndex := reader.ItemCount - 1;

  minIndex := CurItem+1;
  if minIndex > maxIndex then Exit; // Nothing to process


  for i:=maxIndex downto minIndex do begin //JGK was CurItem+reader.ItemCount-ItemCount+1 do begin
    // Double-check bounds before accessing
    if (i < 0) or (i >= reader.ItemCount) then //just to be sure...
      Break;


      //SHIFT not pressed (UP)
    if GetKeyState(VK_SHIFT) and $ff00 = 0 then begin
      try
        s := reader.GetItemTag(i);
        p := Pos(':', s);
        if (p>0) and(p<6) then continue;
        //JGK ERROR THIS IS A PROBLEM
        //IT needs used otherwise we have tags users don't need
        //But it is causing a TRVItemList error cascade.
        reader.SetItemTag(i, '');
      except
        on E: Exception do begin
          debugOnScreen(PChar('WMAfterPaste GetItemTag error: ' + E.Message));
          Continue;
        end;
      end;
    end;

    try
      // Validate style index before accessing
      if (reader.GetItemStyle(i) >= 0) and (reader.GetItemStyle(i) < reader.Style.TextStyles.Count) then begin
        if reader.Style.TextStyles[reader.GetItemStyle(i)].Jump then begin
          if reader.GetItemTag(i) = '' then
            reader.Style.TextStyles[reader.GetItemStyle(i)].Jump := False;
        end
      end;
    except
      on E: Exception do begin
        debugOnScreen(PChar('WMAfterPaste TextStyles error: ' + E.Message));
        Continue;
      end;
    end;

    //if pasting tables, add the rvtoEditing (see  http://www.trichview.com/forums/viewtopic.php?p=10747#10747)
    //bug when pasting from compare view to book view
    try
      if reader.GetItemStyle(i) = rvsTable then
        with TRVTableItemInfo(reader.GetItem(i)) do
          Options := Options + [rvtoEditing];
    except
      on E: Exception do begin
        debugOnScreen(PChar('WMAfterPaste Table error: ' + E.Message));
        Continue;
      end;
    end;

    //remove checkpoint when pasting from bible view
    try
      cpd := reader.GetItemCheckpoint(i);
      if ( cpd<>nil ) then begin
        reader.GetCheckpointInfo(cpd, RVTag, name, re);
        if GlobalUtils.StrSameStartU('_TW_VLIDX', RVTag) then
          reader.RemoveCheckpointEd(i);
      end;
    except
      on E: Exception do begin
        debugOnScreen(PChar('WMAfterPaste Checkpoint error: ' + E.Message));
        Continue;
      end;
    end;
  end;

  //if SHIFT is down, fix e-Sword style links: for pasting from eSword
  if GetKeyState(VK_SHIFT) and $ff00 <> 0 then begin
    c := reader.ItemCount-ItemCount;
    if c>0 then begin
      if not FixEswordLinksOnPasteRE.IsInit then FixEswordLinksOnPasteRE.Create(gc_AutoRefRegex, [roCompiled]);

      // Safer bounds for RVIterateAllItems
      maxIndex := Math.Min(CurItem+c+1, reader.ItemCount-1);
      if maxIndex >= minIndex then begin
        RVIterateAllItems(reader.RVData, [], DetectVRefsOnPasteCB, 'WMAfterPaste minIndex to maxIndex', minIndex,
          maxIndex);
      end else begin
        // Fallback: use full range if calculation fails
        maxIndex := reader.ItemCount - 1;
        if maxIndex >= minIndex then begin
          RVIterateAllItems(reader.RVData, [], DetectVRefsOnPasteCB, 'WMAfterPaste minIndex to maxIndex', minIndex, maxIndex);
        end;
        {$IFDEF DEBUG}
        debugOnScreen(PChar('WMAfterPaste: Used fallback maxIndex ' + IntToStr(maxIndex)));
        {$ENDIF}
      end;
      cmdAutoDetectVREFsClick(nil);
    end;
  end;
end;
if I comment out:

Code: Select all

reader.SetItemTag(i, '');
in WMAfterPaste it works fine but off course there are tags that we don't want.
jgkoehn
Posts: 322
Joined: Thu Feb 20, 2020 9:32 pm

Re: Bad RTF table causing RichView to error

Post by jgkoehn »

OOps I missed the timer funciton it goes to before WMAfterPaste
This is to allow the paste function to complete.

Code: Select all

// Replace the timer callback with this method:
procedure TfmBookView.OnAfterPasteTimer(Sender: TObject);
begin
  FAfterPasteTimer.Enabled := False;
  try
    // Validate we're still in the same context as when timer was set
    if not IsWindow(Handle) then
      Exit;

    // Critical: Check if we're still the active/focused book view
    if not (Self.Focused or Self.ContainsControl(Screen.ActiveControl)) then
      Exit;

    // Validate reader is still valid and hasn't changed
    if not Assigned(reader) or not reader.HandleAllocated then
      Exit;

    // Validate the stored item count matches current state
    //if reader.ItemCount <> FAfterPaste_ItemCount then
      //Exit;

    // Validate CurItem is still valid
    if (FAfterPaste_CurItem < 0) or (FAfterPaste_CurItem >= reader.ItemCount) then
      Exit;

    PostMessage(Handle, WM_AFTERPASTE, FAfterPaste_CurItem, FAfterPaste_ItemCount);
  except
    on E: Exception do begin
      debugOnScreen(PChar('OnAfterPasteTimer error: ' + E.Message));
    end;
  end;
end;
standay
Posts: 302
Joined: Fri Jun 18, 2021 3:07 pm

Re: Bad RTF table causing RichView to error

Post by standay »

Sergey may have a better idea, but here's mine.

I'm not entirely sure what all the vars are that are in play, but maybe change this:

Code: Select all

  for i:=maxIndex downto minIndex do begin //JGK was CurItem+reader.ItemCount-ItemCount+1 do begin
    // Double-check bounds before accessing
    if (i < 0) or (i >= reader.ItemCount) then //just to be sure...
      Break;
...
To something like this:

Code: Select all

if (i > -1) and (i <= reader.ItemCount -1) then  
for i := 0 to reader.ItemCount -1 do 
begin 
  s := reader.GetItemTag(i);
  p := Pos(':', s);
  if not ((p>0) and (p<6)) then //or whatever the logic is you need
    reader.SetItemTag(i, ''); //if just removing tag, should not need to loop in reverse
end;
It seems like your loop is still outside the range of reader items. Just a guess.

Stan
jgkoehn
Posts: 322
Joined: Thu Feb 20, 2020 9:32 pm

Re: Bad RTF table causing RichView to error

Post by jgkoehn »

Well I found the cause but after multiple time of trying to format, reformat etc. It is just not working.
It is when reader.RVData.NormalizeDocument(0,True); is ran then when it tries to conceat I believe it is the RVItemList messes up.
How can I fix this. It is like the Pasting is still in process so reader.Format etc doesn't entirely work.
Sergey Tkachenko
Site Admin
Posts: 17878
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Re: Bad RTF table causing RichView to error

Post by Sergey Tkachenko »

Please create a compliable project reproducing the problem and send it to me.
I am afraid, without reproducing the problem, I cannot help.
I cannot even understand the problem. First, the topic was about a (handled) error when loading RTF. Then about exception on drawing. Then an error in a code executed on a timer.
jgkoehn
Posts: 322
Joined: Thu Feb 20, 2020 9:32 pm

Re: Bad RTF table causing RichView to error

Post by jgkoehn »

Sorry Sergey,
It is a bug that as I dug deeper got closer and closer to the problem.
1. RVItemList is the interface info
2. It happens inside pasting to a richview
3. The cause seems to be deep in DrawingBackground of CRVFData
4. But all of this as you said is so hard to follow that it is impossible to solve. (I am having that problem also)

I found if instead of removing the tags I just rename them to REMOVE_ME_ + IntToStr(i) that they never concecat inside of Normalize and so they never trigger. It is like I found a way to get inbetween Paste/Format and it can't be fixed directly.

A compilable project would be next to impossible. I think I will just have to be satisified with not truly removing the tags and leave it at that for now. Maybe one day the solution will come to light.

Sorry for the confusion in it. It is just that complicated of a bug and is probably on my end.
Sergey Tkachenko
Site Admin
Posts: 17878
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Re: Bad RTF table causing RichView to error

Post by Sergey Tkachenko »

1) If the exception on pasting happens in RTF loading code, it must not affect subsequent drawing. On exception, RTF loading is aborted, but the items that are already added must be formatted and must be displayed correctly.
If the error happens on some specific RTF, please send this RTF to me. If it happens only on pasting, you can use this utility to save RTF from the Clipboard to a file: https://www.trichview.com/support/files ... ofile2.zip

2) TRVItemList is a list of items in the document (an item can be: a text item, a picture, a table, etc,). This is a type of TRichView.RVData.Items.
If the error on your screenshot shows that you have only 2 items in your document (indexes 0 and 1), but some code tries to access the item with the index 2 (that does not exist) while drawing.

3) The comment says: "causing a TRVItemList error cascade." I understand that it means that is a repeating error. The error can be repeated if it occurs while drawing. But you said that your code is executed on timer, so the error in this code must not be repeated.
Can you post a call stack window content at the moment of this exception?
jgkoehn
Posts: 322
Joined: Thu Feb 20, 2020 9:32 pm

Re: Bad RTF table causing RichView to error

Post by jgkoehn »

Thanks Sergey for being willing to help me I understand this is complex:
To produce the error it seems RichView Text must be pasted from one RichView to another but make sure there is a background color in the pasted text.

Here is the call stack:

Code: Select all

:770d0144 KERNELBASE.RaiseException + 0x64
:00af6809 TList.Get + $19
:00af6809 TList.Get + $19
CRVFData.TCustomRVFormattedData.PaintTo($19417F10,(0, 0, 505, 880, (0, 0), (505, 880)),False,False,False,False,True,0,0,0,4,nil,nil,False,False)
RVCtrlData.TRVControlData.PaintBuffered((0, 0, 505, 880, (0, 0), (505, 880)))
RVERVData.TRVEditRVData.PaintBuffered((7335540, 12663373, 7335520, 12663405, (7335540, 12663373), (7335520, 12663405)))
RichView.TCustomRichView.Paint
:00c13a4d TCustomControl.PaintWindow + $5D
:00c0d3f3 TWinControl.PaintHandler + $5B
:00c12058 TWinControl.WMPrintClient + $74
:00c07b29 TControl.WndProc + $2C1
:00c0d1e0 TWinControl.WndProc + $6AC
RichView.TCustomRichView.WndProc(???)
uFmBookView.TFmBookView.ReaderWindowProc((792, 3523282062, 4, 0, 1166, 53761, (), 4, 0, (), 0, 0, ()))
twSpTBXDkPanelsEx.TControlWindowProc.SubClassWndProc((792, 3523282062, 4, 0, 1166, 53761, (), 4, 0, (), 0, 0, ()))
:00c0775f TControl.Perform + $27
:00c0dc93 TWinControl.WMPaint + $F3
:00c139e6 TCustomControl.WMPaint + $16
:00c0d1e0 TWinControl.WndProc + $6AC
RichView.TCustomRichView.WndProc(???)
uFmBookView.TFmBookView.ReaderWindowProc((15, 0, 0, 0, 0, 0, (), 0, 0, (), 0, 0, ()))
twSpTBXDkPanelsEx.TControlWindowProc.SubClassWndProc((15, 0, 0, 0, 0, 0, (), 0, 0, (), 0, 0, ()))
:00c0c6a3 TWinControl.MainWndProc + $2F
:00b11402 StdWndProc + $16
:753d9e93 ; C:\WINDOWS\SysWOW64\USER32.dll
:753c800d ; C:\WINDOWS\SysWOW64\USER32.dll
:753c7ab0 ; C:\WINDOWS\SysWOW64\USER32.dll
:753d4329 ; C:\WINDOWS\SysWOW64\USER32.dll
:772cbdc6 ntdll.KiUserCallbackDispatcher + 0x36
:753c7000 USER32.DispatchMessageW + 0x10
And I have simplified the code some only two functions:
readerPaste which is assigned to OnPaste

Code: Select all

var
  MuteReaderPasteDelegate: Boolean = False;
  TextStyleBeforePaste, ParaStyleBeforePaste: Integer;
procedure TfmBookView.readerPaste(Sender: TCustomRichViewEdit;
  var DoDefault: Boolean);
var
  sItemNo, sItemOffs, eItemno, eItemOffs: Integer;
  CurItem: Integer;
  InsertWS: String;
begin
  //So Reader paste doesn't take over edtTopics.
 { if edtTopics.Focused then begin
    //edtTopics.PasteFromClipboard;
    exit;
  end else if edtVol.Focused then begin
    //edtVol.PasteFromClipboard;
    exit;
  end;}

  // Early validation
  if not Assigned(reader) then begin
    DoDefault := True;
    Exit;
  end;

  // Safe bounds checking
  if reader.ItemCount = 0 then begin
    DoDefault := True;
    Exit;
  end;

  try
    CurItem := reader.CurItemNo;
    // Validate CurItem is within bounds
    if (CurItem < 0) or (CurItem >= reader.ItemCount) then begin
      CurItem := 0;
    end;

    // Safe access to style properties
    TextStyleBeforePaste := 0;
    ParaStyleBeforePaste := 0;

    if (reader.CurTextStyleNo >= 0) and (reader.CurTextStyleNo < reader.Style.TextStyles.Count) then
      TextStyleBeforePaste := reader.CurTextStyleNo;

    if (reader.CurParaStyleNo >= 0) and (reader.CurParaStyleNo < reader.Style.ParaStyles.Count) then
      ParaStyleBeforePaste := reader.CurParaStyleNo;


    //If Called from Paste To Editor but only from inside of Bookview this boolean helps with that.

    if FVerseLinkPasteToEditor and
      (TextStyleBeforePaste < reader.Style.TextStyles.Count) and
      reader.Style.TextStyles[TextStyleBeforePaste].Jump then begin
      //Check if the item is a jump link should be since that is where this came from.
      reader.SetSelectionBounds(CurItem,reader.GetOffsAfterItem(CurItem),CurItem,reader.GetOffsAfterItem(CurItem));
      InsertWS := _getGlobalIni.ReadString('general','default.bkv.paste.to.editor.str', ' ');
      InsertWS := GlobalUtils.StringReplaceU(InsertWS,'#9', #9);
      InsertWS := GlobalUtils.StringReplaceU(InsertWS,'#13', #13);
      InsertWS := GlobalUtils.StringReplaceU(InsertWS,'#10', #10);
      reader.InsertTextW(InsertWS); //Now insert a space, enter something the user desires.
    end;

    FVerseLinkPasteToEditor := False; //Unfortunately this should be done immeditately after the popup closes but I can't get it to trigger OnClose
    //This problem can cause a user to paste in a link and could cause a problem but will work the second time because this will be set.

    reader.GetSelectionBounds(sItemNo, sItemOffs, eItemno, eItemOffs, True);
    if (sItemNo = eItemNo) and (sItemOffs = eItemOffs) and (sItemOffs = 1) then
      Dec(CurItem);

    // Validate CurItem again after potential decrement
    if CurItem < 0 then CurItem := 0;

    //call the WMAfterPaste in 300ms...
    //need to use a timer because there is a Application.ProcessMessages; call in
    //the uFrmCopyVerses that would 'eat' the PostMessage below
    //FAfterPaste_Handle := Handle;
    // Save for timer
    FAfterPaste_sItemNo := sItemNo;
    FAfterPaste_eItemNo := eItemNo;
    FAfterPaste_sItemOffs := sItemOffs;
    FAfterPaste_eItemOffs := eItemOffs;
    FAfterPaste_CurItem := reader.CurItemNo;
    FAfterPaste_OldItemCount := reader.ItemCount;
    // Only set timer if window handle is valid
    {if IsWindow(Handle) then
      SetTimer(Handle, 1, 300, @TimerProc_AfterPaste);}
      // Use timer instead of Windows callback
    FAfterPasteTimer.Enabled := False;  // Stop any existing timer
    FAfterPasteTimer.Enabled := True;   // Start new timer
    // Post message directly instead of using timer
    //PostMessage(Handle, WM_AFTERPASTE, FAfterPaste_CurItem, FAfterPaste_ItemCount);


    //This handler is called with CTRL+V. In that case, the Paste method of RVE
    //is called which does NOT handle the HTML format. To handle HTML, we need
    //to calle the RichViewActions.OnPaste action. So, if the clipboard contains
    //HTML, with call the rvActionPaste2 action and set DoDefault to false, to not
    //alow the RVE to handle the Paste itself.
    if IsClipboardFormatAvailable(CFRV_HTML) and
       (not IsClipboardFormatAvailable(CFRV_RVF)) and
       (not IsClipboardFormatAvailable(CFRV_RTF)) and
       (not MuteReaderPasteDelegate)then begin
      try
        MuteReaderPasteDelegate := True;
        MainReaderForm.rvActionPaste2.Execute;
        DoDefault := False;
      finally
        MuteReaderPasteDelegate := False;
      end
    end
    else begin
      DoDefault := True;
    end;
  except
    on E: Exception do begin
      debugOnScreen(PChar('readerPaste error: ' + E.Message));
      DoDefault := True;
    end;
  end;
end;
DoAfterPaste

Code: Select all

var
  FixEswordLinksOnPasteRE: TRegExx = ();

{ This code tries to remove dead hyperlinks on paste... I am not sure at all
  about it. Commented out above
  Need to do ApplyStyleConversion here!!! }
procedure TFmBookView.DoAfterPaste;
var
  i, c, p, minIndex, maxIndex: Integer;
  RVTag: TRVTag;
  cpd: TCheckpointData;
  s: string;
  name: TRVUnicodeString;
  re: Boolean;
  readerName: String;
  itemStyle: Integer;
  sItemNo, sItemOffs, eItemNo, eItemOffs, CurItem, OldItemCount, NewItemCount: Integer;
  replacedCount, pastedCount: Integer;
  procedure Swap(var A, B: Integer);
  var
    T: Integer;
  begin
    T := A;
    A := B;
    B := T;
  end;
begin
  // Validate we're still in the correct context
  if not (Self.Focused or Self.ContainsControl(Screen.ActiveControl)) then begin
    debugOnScreen('DoAfterPaste: BookView no longer active, ignoring');
    Exit;
  end;

  // Validate that the reader is still valid and assigned
  if not Assigned(reader) then begin
    debugOnScreen('DoAfterPaste: Reader not assigned, ignoring message');
    Exit;
  end;

  // Check if reader handle is valid
  if not reader.HandleAllocated then begin
    debugOnScreen('DoAfterPaste: Reader handle not allocated, ignoring message');
    Exit;
  end;

  // Validate that we're not in a destroying state
  if (csDestroying in ComponentState) or (csLoading in ComponentState) then begin
    debugOnScreen('DoAfterPaste: Component destroying/loading, ignoring message');
    Exit;
  end;

  //RvBookUtil.ConvertToUnicodeAndMisc(reader.RVData, FCurModule, False, 0);
  if (reader.BiDiMode = rvbdUnspecified) then
    if RVShouldUserComplexRendering(reader) then
      reader.BiDiMode := rvbdLeftToRight;

  //remove possible protections from everything pasted
  //with reader.Style do
    if Assigned(FCurModule) then begin
      //user module: remove protection
      if FCurModule.IsUserModule then begin
        for i:=0 to reader.Style.TextStyles.Count-1 do
          reader.Style.TextStyles[i].Protection := [];
        for i:=0 to reader.Style.ParaStyles.Count-1 do
          reader.Style.ParaStyles[i].Options := reader.Style.ParaStyles[i].Options - [rvpaoReadOnly, rvpaoNoWrap,
              rvpaoDoNotWantReturns];
      end
    end;


  // After paste
  NewItemCount := reader.ItemCount;

  // Normalize selection
  sItemNo := FAfterPaste_sItemNo;
  eItemNo := FAfterPaste_eItemNo;
  sItemOffs := FAfterPaste_sItemOffs;
  eItemOffs := FAfterPaste_eItemOffs;
  CurItem := FAfterPaste_CurItem;
  OldItemCount := FAfterPaste_OldItemCount;

  if sItemNo > eItemNo then
  begin
    Swap(sItemNo, eItemNo);
  end;

  if (sItemNo <> eItemNo) or (sItemOffs <> eItemOffs) then
  begin
    minIndex := sItemNo;
    replacedCount := eItemNo - sItemNo + 1;
    pastedCount := NewItemCount - OldItemCount + replacedCount;
    maxIndex := minIndex + pastedCount - 1;
  end
  else
  begin
    pastedCount := NewItemCount - OldItemCount;
    if pastedCount <= 0 then
    begin
      minIndex := -1;
      maxIndex := -1;
    end
    else
    begin
      // If selection was "select all", pasted items start at 0
      if (sItemNo = 0) and (eItemNo = OldItemCount - 1) then
        minIndex := 0
      // If caret was at start, pasted items start at 0 (offsets are 1)
      else if (sItemNo = 0) and (sItemOffs = 1) and (eItemNo = 0) and (eItemOffs = 1) then
        minIndex := 0
      // If caret was at the end, pasted items start at OldItemCount
      else if (sItemNo = OldItemCount - 1) and (sItemOffs = Length(reader.GetItemText(sItemNo)) + 1) then
        minIndex := OldItemCount
      // Otherwise, pasted items start at caret position
      else
        minIndex := sItemNo;

      // Clamp minIndex to valid range
      if minIndex >= NewItemCount then
        minIndex := NewItemCount - pastedCount;
      if minIndex < 0 then minIndex := 0;

      maxIndex := minIndex + pastedCount - 1;
      if maxIndex >= NewItemCount then maxIndex := NewItemCount - 1;
    end;
  end;



  if (minIndex >= 0) and (maxIndex >= minIndex) and (maxIndex < NewItemCount) then
    for i:= minIndex to maxIndex do begin //JGK was CurItem+reader.ItemCount-ItemCount+1 do begin
      // Double-check bounds before accessing
      if (i >= 0) AND (i < NewItemCount) then begin //just to be sure...
          //SHIFT not pressed (UP)
        if GetKeyState(VK_SHIFT) and $ff00 = 0 then begin
          try
            s := reader.GetItemTag(i);
            p := Pos(':', s);
            if (p>0) and(p<6) then continue;
            //reader.SetItemTag(i, 'REMOVE_ME_' + IntToStr(i));//<------------This can be used instead because it keeps the tags unique and so they don't get conceated in the Normalize I believe.
            reader.SetItemTag(i, ''); //<---------IF this is removed then there is no error.
          except
            on E: Exception do begin
              debugOnScreen(PChar('DoAfterPaste GetItemTag error: ' + E.Message));
              Continue;
            end;
          end;
        end;

        try
          // Validate style index before accessing
          if (reader.GetItemStyle(i) >= 0) and (reader.GetItemStyle(i) < reader.Style.TextStyles.Count) then begin
            if reader.Style.TextStyles[reader.GetItemStyle(i)].Jump then begin
              if (Pos('REMOVE_ME_', reader.GetItemTag(i)) > 0) OR (reader.GetItemTag(i) = '') then
                reader.Style.TextStyles[reader.GetItemStyle(i)].Jump := False;
            end
          end;
        except
          on E: Exception do begin
            debugOnScreen(PChar('DoAfterPaste TextStyles error: ' + E.Message));
            Continue;
          end;
        end;

        //if pasting tables, add the rvtoEditing (see  http://www.trichview.com/forums/viewtopic.php?p=10747#10747)
        //bug when pasting from compare view to book view
        try
          if reader.GetItemStyle(i) = rvsTable then
            with TRVTableItemInfo(reader.GetItem(i)) do
              Options := Options + [rvtoEditing];
        except
          on E: Exception do begin
            debugOnScreen(PChar('DoAfterPaste Table error: ' + E.Message));
            Continue;
          end;
        end;

        //remove checkpoint when pasting from bible view
        try
          cpd := reader.GetItemCheckpoint(i);
          if ( cpd<>nil ) then begin
            reader.GetCheckpointInfo(cpd, RVTag, name, re);
            if GlobalUtils.StrSameStartU('_TW_VLIDX', RVTag) then
              reader.RemoveCheckpointEd(i);
          end;
        except
          on E: Exception do begin
            debugOnScreen(PChar('DoAfterPaste Checkpoint error: ' + E.Message));
            Continue;
          end;
        end;
      end;//To make sure we are inside of range
    end;

  //if SHIFT is down, fix e-Sword style links: for pasting from eSword
  if GetKeyState(VK_SHIFT) and $ff00 <> 0 then begin
    if (minIndex >= 0) and (maxIndex >= minIndex) and (maxIndex < NewItemCount) then
    begin
      if not FixEswordLinksOnPasteRE.IsInit then
        FixEswordLinksOnPasteRE.Create(gc_AutoRefRegex, [roCompiled]);
      if maxIndex >= minIndex then begin
        RVIterateAllItems(reader.RVData, [], DetectVRefsOnPasteCB, 'DoAfterPaste minIndex to maxIndex', minIndex,
          maxIndex);
      end else begin
        // Fallback: use full range if calculation fails
        minIndex := 0;
        maxIndex := NewItemCount - 1;
        if maxIndex >= minIndex then begin
          RVIterateAllItems(reader.RVData, [], DetectVRefsOnPasteCB, 'DoAfterPaste minIndex to maxIndex', minIndex, maxIndex);
        end;
        debugOnScreen(PChar('DoAfterPaste: Used fallback maxIndex ' + IntToStr(maxIndex)));
      end;
      cmdAutoDetectVREFsClick(nil);
    end;
  end;

  reader.format; //<----------------------This doesn't seem to do anything
  reader.RVData.NormalizeDocument(0,True); //<-----------This triggers the error (I put it here normally it is in some other function and so happens later at various times depending on what a user does.
end;
Post Reply