Page 1 of 1

Traversing hyperlinks

Posted: Thu Mar 22, 2007 4:24 pm
by momsoft
Hi,

I have a document loaded in a TRichViewEditor (in ReadOnly mode) with many hyperlinks, both in text and in tables. I'd like to traverse the list of hyperlinks to replace text and replace the formatting of the hyperlinks:

Something like (in pseudocode):

Code: Select all

for i := 1 to Hyperlinks.Count do
  if Hyperlink.Tag = ATag then begin
    Hyperlink.BackColor := clNavy;
    Hyperlink.Color := clYellow;
  end;
and,

Code: Select all

for i := 1 to Hyperlinks.Count do
  if Hyperlink.Tag = ATag then 
    Hyperlink.Text := NewText;
Is this possible?

Thank you very much in advance,

- Manuel Onate

Posted: Fri Mar 23, 2007 12:19 pm
by Michael Pro
As it's said in help
ReadOnly
...
This property does not affect behavior of methods introduced in TRichView.
All RichView methods are still working.
This means, that you have to cycle through all items and make something like this

Code: Select all

if GetItemStyle(i) >= 0 then //Check for text style
  if (Style.TextStyles[GetItemStyle(StartNo)].Jump) and  (GetItemTag(i) = ATag) then //Check for assigned jump and for tags equality
    SetItemText(i, <newtext>);
This code had to work with text items correctly, but not with tables - actually, I didn't work with tables in RichView and didn't examine, how to get styles from it.
In case of changing hyperlink color it's more difficult - you need to create new text style with new color parameters, check if any equal style exists, add it, if not found and change item style in RVData. It could affect formatting of document - I did smth in my project, but it's not so simple.
By the way, you didn't wrote pseudocode in right way, because hyperlinks in text are also text items - and the main conception is to cycle through all items in RichView, checking styles of current item.

Posted: Tue Mar 27, 2007 6:30 pm
by Sergey Tkachenko

Code: Select all

{ Returns (and creates, if required) text style having all properties of
  the StyleNo-th style, but yellow on navy }
function GetNewLinkStyleNo(RVStyle: TRVStyle; StyleNo: Integer): Integer;
var TextStyle: TFontInfo;
begin
  TextStyle := TFontInfo.Create(nil);
  TextStyle.Assign(RVStyle.TextStyles[StyleNo]);
  TextStyle.Color := clYellow;
  TextStyle.BackColor := clNavy;
  TextStyle.HoverColor := clYellow;
  TextStyle.HoverBackColor := clNavy;
  Result := RVStyle.TextStyles.FindSuchStyle(StyleNo, TextStyle,
    RVAllFontInfoProperties);
  if Result<0 then begin
    RVStyle.TextStyles.Add;
    Result := RVStyle.TextStyles.Count-1;
    RVStyle.TextStyles[Result].Assign(TextStyle);
    RVStyle.TextStyles[Result].Standard := False;
  end;
  TextStyle.Free;
end;


procedure ChangeLinks(RVData: TCustomRVData);
var i,r,c, StyleNo: Integer;
    Table: TRVTableItemInfo;
begin
  for i := 0 to RVData.ItemCount-1 do begin
    StyleNo := RVData.GetItemStyle(i);
    if (StyleNo>=0) and RVData.GetRVStyle.TextStyles[StyleNo].Jump and
       (StrComp(PChar(RVData.GetItemTag(i)), 'test')=0) then begin
      RVData.SetItemTextA(i, 'New Text');
      RVData.GetItem(i).StyleNo := GetNewLinkStyleNo(RVData.GetRVStyle, StyleNo);
      end
    else if StyleNo=rvsTable then begin
      Table := TRVTableItemInfo(RVData.GetItem(i));
      for r := 0 to Table.RowCount-1 do
        for c := 0 to Table.ColCount-1 do
          if Table.Cells[r,c]<>nil then
            ChangeLinks(Table.Cells[r,c].GetRVData);
    end;
  end;
end;
Test:

Code: Select all

  with RichViewEdit1 do begin
    SetSelectionBounds(0, GetOffsBeforeItem(0), 0, GetOffsBeforeItem(0));
    ChangeLinks(RVData);
    ClearUndo;
    Format;
  end;
This code assumes that rvoTagsArePChars is included in RichViewEdit1.Options (tags are compared as strings). If tags are integers, change

Code: Select all

(StrComp(PChar(RVData.GetItemTag(i)), 'test')=0)
to

Code: Select all

(RVData.GetItemTag(i)=...)

Posted: Wed Apr 25, 2007 8:37 pm
by momsoft
Sorry for the delay in answering. I thought I had solved my needs by using code like the one below, and forgot to check the forum.

Code: Select all

procedure TfmMain.EnumMark(RVData: TCustomRVData; ItemNo: Integer; var UserData1: Integer;
          const UserData2: String; var ContinueEnum: Boolean);
// Mark all Jumps with Tag = UserData1 as Selected and the rest as Normal
var
  IsLink : Boolean;
  ATag,OldStyle,NewStyle,L,T : integer;
begin
  IsLink := RVData.GetItem(ItemNo).GetBoolValueEx(rvbpJump,RVData.GetRVStyle);
  if IsLink then begin
    ATag := RVData.GetItemTag(ItemNo) - 1; // Tags are 1-based
    OldStyle := RVData.GetItemStyle(ItemNo);
    if ATag = UserData1 then begin
      NewStyle := GetSelectedStyle(Styles,OldStyle)
    end else
      NewStyle := GetNormalStyle(Styles,OldStyle);
    RVData.GetItem(ItemNo).StyleNo := NewStyle;
  end;
end;

procedure TfmMain.EnumSelect(RVData: TCustomRVData; ItemNo: Integer; var UserData1: Integer;
          const UserData2: String; var ContinueEnum: Boolean);
// Scroll to the NumInstance Instance of UserData1 Field
var
  IsLink : Boolean;
  ATag,L,T : integer;
  s : String;
begin
  IsLink := RVData.GetItem(ItemNo).GetBoolValueEx(rvbpJump,RVData.GetRVStyle);
  if IsLink then begin
    ATag := RVData.GetItemTag(ItemNo) - 1; // Tags are 1-based
    If ATag = UserData1 then begin //
      Inc(CurrInstance);
      if CurrInstance = NumInstance then begin
        Preview.GetItemCoords(ItemNo,L,T);
        s := Preview.GetItemTextA(ItemNo);
        T := T - Preview.Height div 2; if T < 0 then T := 0;
        Preview.ScrollTo(T);
        // Select the item
        Preview.SetSelectionBounds(ItemNo,0,ItemNo,Length(s)+1);
        ContinueEnum := False;  // Stop the iteration
      end;
    end;
  end;
end;
EnumMark and EnumSelect are called with the EnumItems method of the RVData object of the RichView

GetSelectedStyle and GetNormalStyle are similar to your suggested GetNewLinkStyleNo function.

EnumMark works as intended.

EnumSelect works perfectly when the richview does not contain tables, but it fails when there are tables. The reason is that the GetItemCoords gives me the position of the item relative to the top of the cell rather than to the top of the document.

What do you suggest?

Posted: Thu Apr 26, 2007 6:42 am
by Sergey Tkachenko
ItemNo is an item index inside RVData parameter. If RVData is a table cell (or cell inplace editor's RVData), you cannot use ItemNo to access items in Preview.

Selecting:

Code: Select all

RVData := RVData.Edit;
TCustomRVFormattedData(RVData).SetSelectionBounds(ItemNo,
  RVData.GetOffsBeforeItem(ItemNo), ItemNo, RVData.GetOffsAfterItem(ItemNo));
As for scrolling:

Code: Select all

  var OL, OT: Integer;

  RVData.GetItemCoords(ItemNo, L, T);
  RVData.GetOriginEx(OL, OT);
  T := OT + T - Preview.Height div 2;
  if T < 0 then
    T := 0;
  Preview.ScrollTo(T);

Posted: Thu Apr 26, 2007 6:00 pm
by momsoft
Sergey,

Thank you very much for your prompt reply. The scrolling part works perfectly. The selection doesn't.

If I add the line: RVData := RVData.Edit; as per your suggestion, I get a List Index Out of Bounds exception in the function TCustomRVData.EnumItems when I try to select an item contained inside a table, the exception occurs in this line.

Result := RVData.GetRVData.EnumItems(Proc, UserData1, UserData2);

I hope this information helps you to determine the error.

Posted: Sat Apr 28, 2007 11:22 am
by Sergey Tkachenko
Well, really, it was not expected in EnumItems that some RVData is moved to the editing state.
So use cycle, as in my example above.

Posted: Wed May 02, 2007 9:12 am
by momsoft
Sergey,

Sorry. I don't understand you. What do you mean by 'use cycle'.

Thank you,

- Manuel Onate

Posted: Wed May 02, 2007 12:58 pm
by Sergey Tkachenko
I mean a procedure like ChangeLinks, see above in this topic.