// Properties:
// rvoTagsArePChars is added in Options
// rvoCtrlJumps is added in EditorOptions
// "Allow adding styles dynamically" in the "Settings" in the context menu

/*
  This demo shows:
  - how to create document with hyperlinks;
  - how to make hyperlink from the selected text (hyperlink is blue and underlined);
  - how to insert new hyperlinks (when the selection is empty)
  - how to remove hyperlinks from the selected text (if user entered empty hyperlink target);
  - how to close hyperlinks when user presses Space, Tab or Enter key;
  - how to get hypertext style (having all properties of normal style, but
    blue, underlined and hypertext), and vice versa.
*/

//---------------------------------------------------------------------------
#include <vcl\vcl.h>
#include <shellapi.h>
#pragma hdrstop

#include "Unit1.h"
#include "Unit2.h"
//---------------------------------------------------------------------------
#pragma link "RVEdit"
#pragma link "RichView"
#pragma link "RVScroll"
#pragma link "RVStyle"
#pragma resource "*.dfm"

// Constants for calling RichViewEdit1->OnStyleConversion
#define CONVERT_TO_HYPERTEXT    1
#define CONVERT_TO_NONHYPERTEXT 2


TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
  RichViewEdit1->Clear();
  RichViewEdit1->AddNLWTag("Select text and click the button. ",0,0,0);
  RichViewEdit1->AddNLWTag("Hyperlink example", GetHypertextStyleNo(0), -1,
    (int)(StrNew(L"http://www.trichview.com")));
  RichViewEdit1->Format();
}
//---------------------------------------------------------------------------
// This function sets tags of all selected items.
//  If Target is an empty string, it sets tags = 0, otherwise tags are pointers
//  to dynamically allocated copies of Target.
void TForm1::SetTargetToSelection(const UnicodeString Target)
{
  // Important: when working with the selection item indices, always use
  //  TopLevelEditor.
  TCustomRichViewEdit* rve = RichViewEdit1->TopLevelEditor;
  // Receiving the range of selected items
  int StartNo, EndNo, StartOffs, EndOffs;
  rve->GetSelectionBounds(StartNo, StartOffs, EndNo, EndOffs, true);
  // If nothing is selected, exiting
  if (StartNo<0)
    return;
  // May be the outermost items are not included in the selection? In this case,
  // excluding them
  if (StartOffs >= rve->GetOffsAfterItem(StartNo))
    StartNo++;
  if (EndOffs <= rve->GetOffsBeforeItem(EndNo))
    EndNo--;
  // Changing tags of the selected items
  rve->BeginUndoGroup(rvutTag);
  rve->SetUndoGroupMode(true);
  int i;
  if (Target!="")
    for (i = StartNo; i<= EndNo; i++)
      rve->SetItemTagEd(i, (int)StrNew(Target.w_str()));
  else
    for (i = StartNo; i<= EndNo; i++)
      rve->SetItemTagEd(i, 0);
  rve->SetUndoGroupMode(False);
}
//----------------------------------------------------------------------------
// Returns the first non-empty tag of the selected items
UnicodeString TForm1::GetTargetFromSelection()
{
  TCustomRichViewEdit* rve = RichViewEdit1->TopLevelEditor;
  // Receiving the range of selected items
  int StartNo, EndNo, StartOffs, EndOffs;
  rve->GetSelectionBounds(StartNo, StartOffs, EndNo, EndOffs, true);
  if (StartNo<0)
    return "";
  if (StartOffs >= rve->GetOffsAfterItem(StartNo))
    StartNo++;
  if (EndOffs <= rve->GetOffsBeforeItem(EndNo))
    EndNo--;
  // Finding the first selected item with non-empty tag
  for (int i = StartNo; i <= EndNo; i++)
    if (rve->GetItemTag(i))
      return (wchar_t*)(rve->GetItemTag(i));
  return "";
}
//----------------------------------------------------------------------------
// Expand the selection: if hyperlink is selected partially, selects it completely
void TForm1::ExpandSelectionToHyperlink()
{
  TCustomRichViewEdit* rve = RichViewEdit1->TopLevelEditor;
  // Receiving a range of selected items
  int StartNo, EndNo, StartOffs, EndOffs;  
  rve->GetSelectionBounds(StartNo, StartOffs, EndNo, EndOffs, true);
  // If no selection exists, using caret position
  if (StartNo<0)
  {
    StartNo = rve->CurItemNo;
    StartOffs = rve->OffsetInCurItem;
    EndNo = StartNo;
    EndOffs = StartOffs;
  }
  if (StartOffs >= rve->GetOffsAfterItem(StartNo))
  {
    StartNo++;
    if (StartNo==rve->ItemCount)
      return;
  }
  if (EndOffs <= rve->GetOffsBeforeItem(EndNo))
  {
    EndNo--;
    if (EndNo<0)
      return;
  }
  // Expanding the selection to the whole items, if necessary
  if (rve->GetItemStyle(StartNo)>=0 &&
      RVStyle1->TextStyles->Items[rve->GetItemStyle(StartNo)]->Jump)
    StartOffs = rve->GetOffsBeforeItem(StartNo);
  if (rve->GetItemStyle(EndNo)>=0 &&
      RVStyle1->TextStyles->Items[rve->GetItemStyle(EndNo)]->Jump)
    EndOffs = rve->GetOffsAfterItem(EndNo);
  rve->SetSelectionBounds(StartNo, StartOffs, EndNo, EndOffs);
  rve->Invalidate();
}
//---------------------------------------------------------------------------
// Clicking "Create Hyperlink" button. If user enters non-empty Target, making
//  the selection hypertext. If user enters empty Target, making the selection
//  non-hypertext. Then assigning Target to tags of the selected items.
void __fastcall TForm1::SpeedButton1Click(TObject *Sender)
{
  ExpandSelectionToHyperlink();
  UnicodeString Target = GetTargetFromSelection();
  Form2->Edit1->Text = Target;
  if (Form2->ShowModal() == mrOk)
  {
    Target = Form2->Edit1->Text;
    if (RichViewEdit1->SelectionExists())
    {
      if (Target == "")
        RichViewEdit1->ApplyStyleConversion(CONVERT_TO_NONHYPERTEXT);
      else
        RichViewEdit1->ApplyStyleConversion(CONVERT_TO_HYPERTEXT);
      SetTargetToSelection(Target);
    }
    else
    {
      if (Target == "")
        Beep();
      else
      {
        RichViewEdit1->CurTextStyleNo = GetHypertextStyleNo(RichViewEdit1->CurTextStyleNo);
        RichViewEdit1->InsertStringWTag("New link", (int)StrNew(Target.w_str()));
      }
    }
  }
}
//---------------------------------------------------------------------------
// Clicking hyperlink.
void __fastcall TForm1::RichViewEdit1Jump(TObject *Sender, int id)
{
  TCustomRVFormattedData* RVData;
  int ItemNo;
  RichViewEdit1->GetJumpPointLocation(id, RVData, ItemNo);
  UnicodeString URL = (wchar_t*)RVData->GetItemTag(ItemNo);
  ShellExecute(0, L"open", URL.w_str(), NULL, NULL, SW_SHOW);
}
//---------------------------------------------------------------------------
// Switching readonly/editing mode. In editing mode, hypertext works only
//  if holding Ctrl key.
void __fastcall TForm1::CheckBox1Click(TObject *Sender)
{
  RichViewEdit1->ReadOnly = CheckBox1->Checked;
  if (CheckBox1->Checked)
    RichViewEdit1->Color = clBtnFace;
  else
    RichViewEdit1->Color = clWindow;
  SpeedButton1->Enabled = ! CheckBox1->Checked;
  RichViewEdit1->SetFocus();
}
//---------------------------------------------------------------------------
// Returns the index of text style having the same properties as
//  RVStyle1.TextStyles[StyleNo], but blue, underlined and hypertext.
//  If such text style does not exist, creates it and returns its index.
int TForm1::GetHypertextStyleNo(int StyleNo)
{
  TFontInfo* fi = new TFontInfo(NULL);
  fi->Assign(RVStyle1->TextStyles->Items[StyleNo]);
  fi->Color = clBlue;
  fi->Style << fsUnderline;
  fi->Jump  = true;
  int Index = RVStyle1->TextStyles->FindSuchStyle(StyleNo, fi, RVAllFontInfoProperties);
  if (Index<0)
  {
    RVStyle1->TextStyles->Add();
    Index = RVStyle1->TextStyles->Count-1;
    RVStyle1->TextStyles->Items[Index]->Assign(fi);
    RVStyle1->TextStyles->Items[Index]->Standard = false;
  }
  return Index;
}
//----------------------------------------------------------------------------
// Returns the index of text style having the same properties as
//  RVStyle1.TextStyles[StyleNo], but with normal color, not underlined and
//  not hypertext.
//  If such text style does not exist, creates it and returns its index.
int TForm1::GetNonHypertextStyleNo(int StyleNo)
{
  TFontInfo* fi = new TFontInfo(NULL);
  fi->Assign(RVStyle1->TextStyles->Items[StyleNo]);
  fi->Color = clWindowText;
  fi->Style >> fsUnderline;
  fi->Jump  = false;
  int Index = RVStyle1->TextStyles->FindSuchStyle(StyleNo, fi, RVAllFontInfoProperties);
  if (Index<0)
  {
    RVStyle1->TextStyles->Add();
    Index = RVStyle1->TextStyles->Count-1;
    RVStyle1->TextStyles->Items[Index]->Assign(fi);
    RVStyle1->TextStyles->Items[Index]->Standard = false;
  }
  return Index;
}
//---------------------------------------------------------------------------
// This event is called for all selected text items when you call
//  ApplyStyleConversion, see SpeedButton1Click
void __fastcall TForm1::RichViewEdit1StyleConversion(
      TCustomRichViewEdit *Sender, int StyleNo, int UserData,
      bool AppliedToText, int &NewStyleNo)
{
  switch (UserData)
  {
    case CONVERT_TO_HYPERTEXT:
      NewStyleNo = GetHypertextStyleNo(StyleNo);
      break;
    case CONVERT_TO_NONHYPERTEXT:
      NewStyleNo = GetNonHypertextStyleNo(StyleNo);
      break;
  }
}
//---------------------------------------------------------------------------
// If the caret is at the end of hyperlink, and there is no selection,
//  switching the current text style to non-hypertext
void TForm1::TerminateHyperlink()
{
  TCustomRichViewEdit* rve = RichViewEdit1->TopLevelEditor;
  if (rve->CurTextStyleNo==rve->CurItemStyle &&
    RVStyle1->TextStyles->Items[rve->CurTextStyleNo]->Jump &&
     ! rve->SelectionExists() &&
     rve->OffsetInCurItem>=rve->GetOffsAfterItem(rve->CurItemNo))
    rve->CurTextStyleNo = GetNonHypertextStyleNo(rve->CurTextStyleNo);
}
//---------------------------------------------------------------------------
// Closing hypelinks when user presses Space, Tab, or Enter
void __fastcall TForm1::RichViewEdit1KeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
  if ((Key == VK_SPACE || Key == VK_TAB || Key == VK_RETURN) &&
       ! RichViewEdit1->ReadOnly)
    TerminateHyperlink();
}
