Implement "Track changes" feature

General TRichView support forum. Please post your questions here
Post Reply
AB
Posts: 2
Joined: Fri Feb 08, 2008 1:39 pm

Implement "Track changes" feature

Post by AB »

For a multi-user software, I would like to have a "Track Changes" features, as in Word.

I tried to implement this in a very dirty way: every change is shown with an alternative style, colored.

It doesn't show deleted words or characters...

How can I implement this feature, without changing the TRichView core, or is this great feature in your to-do list?

Here is what I've done so far:
First I create a new Component with some new methods:

Code: Select all

  protected
    StyleModif: array[0..31] of byte; // just 256 boolean values 
    ModifItem: TCustomRVItemInfo;
    ModifItemOffset: integer;
  public
    // assign to a RichViewEdit for basic track changes:
    procedure ModifItemAction(Sender: TCustomRichView; ItemAction: TRVItemAction; Item: TCustomRVItemInfo;
      var Text: String; RVData: TCustomRVData);
    procedure ModifCaretMove(Sender: TObject);
    // track changes text styles: GetBit(@StyleModif,StyleNo)=true, BackColor=clYellow
    function StyleModifToNormal(StyleNo: integer): integer;
    function StyleNormalToModif(StyleNo: integer): integer;
    // Tag=integer(NewStr('Comment')) (must have rvoTagsArePChars in Options)
Then the implementation part, with dirty StyleNo changes:

Code: Select all

procedure TIct4RichViewEdit.ModifCaretMove(Sender: TObject);
var s,s2,s3: string;
    m: TRVTextItemInfo;
    i, e,b, o, L: integer;
    rv: TCustomRichViewEdit absolute Sender;
begin
  if (ModifItem=nil) or not (Sender is TCustomRichViewEdit) or
    not (ModifItem is TRVTextItemInfo) then exit;
  while (rv.InplaceEditor<>nil) and (rv.InplaceEditor is TCustomRichViewEdit) do
    rv := TCustomRichViewEdit(rv.InplaceEditor);
  m := TRVTextItemInfo(ModifItem);
  ModifItem := nil;
  i := rv.GetItemNo(m);
  if i<0 then exit;
  s := rv.GetItemTextA(i);
  L := length(s);
  e := ModifItemOffset;
  b := e;
  while (e<=L) and (s[e]<>' ') do inc(e); // word modified in s[b+1..e-1]
//    while (e<=L) and (s[e]=' ') do inc(e); / includes spaces after
  while (b>0) and (s[b]<>' ') do dec(b);
  if e=b then exit;
  dec(e);
  s2 := copy(s,b+1,e-b);
  s3 := copy(s,e+1,maxInt);
  o := rv.GetOffsBeforeItem(i);
  inc(b,o);
  inc(e,o);
  inc(o,ModifItemOffset-b+1); // ModifItemOffset will change with ApplyTextStyle
  if rv.UndoAction=rvutDelete then begin
    dec(o,2);
    if o<1 then o := 1;
  end;
  rv.SetSelectionBounds(i,b,i,e);
  rv.ApplyTextStyle( StyleNormalToModif(m.StyleNo));
  i := rv.CurItemNo;
  rv.SetSelectionBounds(i, o, i, o);
  rv.Invalidate;
end;

procedure TIct4RichViewEdit.ModifItemAction(Sender: TCustomRichView;
  ItemAction: TRVItemAction; Item: TCustomRVItemInfo; var Text: String;
  RVData: TCustomRVData);
begin
  case ItemAction of
    rviaTextModifying: begin
      if (ModifItem=Item) or (Item.StyleNo<0) or GetBit(@StyleModif,Item.StyleNo) or
        not Sender.InheritsFrom(TCustomRichViewEdit) then exit;
      ModifItem := Item;
      ModifItemOffset := TCustomRichViewEdit(Sender).OffsetInCurItem;
    end;
  end;
end;

function TIct4RichViewEdit.StyleModifToNormal(StyleNo: integer): integer;
var FontSource: TFontInfo;
begin
  FontSource := Style.TextStyles[StyleNo];
  if FontSource.BackColor<>clYellow then
    result := StyleNo else
    result := FontSource.BaseStyleNo;
end;

function TIct4RichViewEdit.StyleNormalToModif(StyleNo: integer): integer;
var FontSource, FontInfo: TFontInfo;
begin
  FontSource := Style.TextStyles[StyleNo];
  if FontSource.BackColor=clYellow then begin
    result := StyleNo;
    exit;
  end;
  FontInfo := TFontInfo.Create(nil);
  try
    FontInfo.Assign(FontSource);
    FontInfo.BackColor := clYellow;
    FontInfo.BaseStyleNo := StyleNo;
    result := Style.TextStyles.FindSuchStyle(StyleNo,FontInfo,RVAllFontInfoProperties);
    if result=-1 then begin
      with Style.TextStyles.Add do begin
        Assign(FontInfo);
        Standard := false;
      end;
      result := Style.TextStyles.Count-1;
      SetBit(@StyleModif,result);
    end;
  finally
    FontInfo.Free;
  end;
end;
The StyleModif just check whatever the Style is a "modifying" Style or not with this common and fast procedures:

Code: Select all

function GetBit(p: pointer; aIndex: integer): boolean;
asm
  bt      [p],aindex  // eax = self = bits
  sbb     eax,eax
  and     eax,1
end;

procedure SetBit(p: pointer; aIndex: integer);
asm
  bts [p],aIndex
end;
Sergey Tkachenko
Site Admin
Posts: 17565
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

Sorry, this feature is not planned in near future.
The simplest way to implement is using OnKeyDown, OnKeyPress, OnPaste to trap editing operations.
For example, the user pressed Backspace. You use OnKeyDown to prevent the default action, but apply the special style to the character to the left instead.
Unfortunately, I do not know what to do with modifying line breaks and with non-text items in this case.
AB
Posts: 2
Joined: Fri Feb 08, 2008 1:39 pm

Post by AB »

Thanks for the answer, and the 'On*' trick: I just made up something complicated.

If I manage to implement this feature, I will post here the code.
DeepBlue7911
Posts: 3
Joined: Sat Apr 28, 2012 12:24 pm

mark

Post by DeepBlue7911 »

mark
Post Reply