Get line types in code

General TRichView support forum. Please post your questions here
Post Reply
standay
Posts: 256
Joined: Fri Jun 18, 2021 3:07 pm

Get line types in code

Post by standay »

Hi Sergey,

I've been working on this for a long time and just can't seem to get it to work beyond a very simple setup. What I want is to be able to figure out the type of lines in my paragraphs. I'm using that to determine what I display in my line numbers gutter. I'll put an image in to illustrate:

Lines.png
Lines.png (11.69 KiB) Viewed 4426 times
Line 1 simple setup I can do with the following code, and this is all I'm using at the present time:

Code: Select all

  memo.RVData.ExpandToPara( DItem.ItemNo, DItem.ItemNo, i, X ); //i is para start item, X is para end item
  if (memo.GetLineNo(i,0) <> memo.GetLineNo(DItem.ItemNo, DItem.Offs)) then //only way I can keep it from overwriting normal numbers
    PBox.Canvas.TextOut( r.Left , r.Top, #$21B5) //anything other than normal para
  else
    PBox.Canvas.TextOut( r.Left , r.Top, IntToStr(LineNumber)); //normal para
This works OK but is very basic. I either show a return symbol if the line is not a normal para, or the line number if the para is normal. This code works no matter what text format or image I have the caret on. And it works whether the line is empty or not.

How can I determine if the caret is in a line that ends with a hard return (lines 2 - 4 above), is word wrapped (lines 5 - 7), is empty (lines 8 and 10), or a line that is the last line in a word wrapped para (line 7)?

Note that I loop through visible drawitems, not regular items when I parse the text. This needs to work even if there is formatted text and/or images within the text. Those 2 things have given me a lot of trouble. I can sometimes get code to work but as soon as I select a formatted word or image, it stops working.

Thanks Sergey. Any ideas appreciated!

Stan
Sergey Tkachenko
Site Admin
Posts: 17289
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Re: Get line types in code

Post by Sergey Tkachenko »

memo.RVData.DrawItems is a collection of drawitems.
Each item of this collection has properties:
- ItemNo - index of the corresponding item. So you can determine if this item is a text item or not (memo.GetItemStyle(ItemNo)>=0). For text items, you can get their text
- Offs - for text items, the first character of the item's text belonging to this drawitem (from 1)
- TextLength - for text item, the number of characters belonging to this drawitem.

Using this information, you can determine:
- if this is the first drawitem of an item,
- if this is the last drawitem of an item
- if this is an an empty drawitem.

Knowing the corresponding ItemNo, you can determine:
- if this is the last item (ItemNo = memo.ItemCount - 1)
- if this item is the first paragraph item (memo.IsParaStart(ItemNo)), the same for the next item (if it exists)
- if this item is the first item after a Shit+Enter line break in the paragraoh (memo.IsFromNewLine(ItemNo) but not memo.IsParaStart(ItemNo)), the same for the next item (if it exists)

I think this is enough to answer to the questions that you asked.
standay
Posts: 256
Joined: Fri Jun 18, 2021 3:07 pm

Re: Get line types in code

Post by standay »

Hi Sergey,

This helped confirm what I've observed, thanks. So, continuing with what I had before, I came up with the code below. It's working but I do have a new question based on it.

The code depends on knowing which line a given drawitem is on. This is because we can have more than 1 drawitem on a line. I've kept track of this using GetLineNo calls. It does not seem to impact speed even in big files, but I was concerned about that.

So, is there another or better way to do that? I can use GetLineVerticalBoundsOrigEx and then compare LineTops, but I wasn't sure that would be any better, or if I'm not approaching it correctly.

At least now it's working! Thanks Sergey

Stan

Code: Select all

    CurIndText := '';

    memo.RVData.ExpandToPara( DItem.ItemNo, DItem.ItemNo, FirstParaItem, LastParaItem);
    CurrentLN := memo.GetLineNo( DItem.ItemNo , DItem.Offs );
    ParaStartLN := memo.GetLineNo(FirstParaItem, memo.GetOffsBeforeItem(FirstParaItem));
    ParaEndLN := memo.GetLineNo(LastParaItem, memo.GetOffsAfterItem(LastParaItem));

    //Para end, pilcrow indicator char:
    if (CurrentLN = ParaEndLN) and
      (ParaStartLN <> ParaEndLN) then 
        CurIndText := #$00B6; //Simple unicode pilcrow symbol

    //Between para start line and para end line, i.e., "word wrapped"
    //lines or hard return lines before the end of the paragraph,
    //return indicator char:
    if (CurrentLN <> ParaEndLN) and
      (ParaStartLN <> ParaEndLN) then
        CurIndText := #$21B5; //Simple unicode return symbol

    //Normal para line, line number indicator:
    if memo.IsParaStart(FirstParaItem) and (ParaStartLN = CurrentLN) then
      CurIndText := IntToStr(LineNumber);
Image41.png
Image41.png (8.42 KiB) Viewed 4380 times
standay
Posts: 256
Joined: Fri Jun 18, 2021 3:07 pm

Re: Get line types in code

Post by standay »

Hi Sergey,

I was using this:

Code: Select all

   //ParaCurLN := memo.GetLineNo( DItem.ItemNo , DItem.Offs );
    //ParaStartLN := memo.GetLineNo(FirstParaItem, memo.GetOffsBeforeItem(FirstParaItem));
    //ParaEndLN := memo.GetLineNo(LastParaItem, memo.GetOffsAfterItem(LastParaItem));
But I switched to the functions below. The advantage (I think) is that I loop only through a paragraph at a time and there are no more GetLineNo calls. These seem to be working as well as the old code. I call it like this:

Code: Select all

    //Get the first drawitem of the current paragraph. Since it's always the
    //first item on that line, no further processing is needed (unlike the
    //current and last lines):
    ParaStartLN := GetDrawItemNo(memo,FirstParaItem,OffsBefore);

    //Get the first item on the current line. Since we can have more than 1
    //drawitem on a line, we need to be sure to get the beginning drawitem
    //since using drawitems further along the line will give the wrong value.
    //Note: ParaCurLN and ParaCaretLN are *NOT* always the same:
    TempDItem := GetFirstDrawItemOnLine(memo,DItem);
    ParaCurLN := GetDrawItemNo(memo,TempDItem.ItemNo,TempDItem.Offs);

    //Get last para item drawitem number:
    i := GetDrawItemNo(memo,LastParaItem,OffsAfter);
    //Make that number a temp drawitem to manipulate:
    TempDItem := memo.RVData.DrawItems[i];
    //Get the first item on the last line. We can have the same issue here like
    //we have with the current line having more than 1 drawitem on the line
    //returning the wrong value:
    TempDItem := GetFirstDrawItemOnLine(memo,TempDItem);
    ParaEndLN := GetDrawItemNo(memo,TempDItem.ItemNo,OffsAfter);

    //Note: ParaCurLN and ParaCaretLN are *NOT* always the same:
    TempDItem := GetFirstDrawItemOnLine(memo,CaretDrawItem);
    ParaCaretLN := GetDrawItemNo(memo,TempDItem.ItemNo,TempDItem.Offs);
Then I can still use the same code to evaluate what type of line it is (some of that code is in a previous message above).

Maybe this will help someone else too.

Stan

Code: Select all

function TForm1.GetDrawItemNo(rv: TCustomRichViewEdit; ItemNo, ItemOffs: integer): integer;
var
  DItemOffs: integer;
begin
  //Yes, turns out ItemOffs CAN be 0, but never negative...
  if ItemOffs >= 0 then
    rv.RVData.Item2DrawItem(ItemNo,ItemOffs,result,DItemOffs);
  if ItemOffs = OffsBefore then //const: OffsBefore = -1
    rv.RVData.Item2FirstDrawItem(ItemNo,result);
  if ItemOffs = OffsAfter then  //const: OffsAfter = -2
    rv.RVData.Item2LastDrawItem(ItemNo,result);
end;

function TForm1.GetFirstDrawItemOnLine(rv: TCustomRichViewEdit;
  DItemSource: TRVDrawLineInfo): TRVDrawLineInfo;
var
  TempDItem: TRVDrawLineInfo;
  DItemNo: integer;
begin

  TempDItem := DItemSource;
  DItemNo := GetDrawItemNo(rv,TempDItem.ItemNo,TempDItem.Offs);

  while not TempDItem.FromNewLine do
  begin
    dec(DItemNo);
    TempDItem := rv.RVData.DrawItems[DItemNo];
  end;

  Result := TempDItem;

end;
Post Reply