[Example] Smart indent

Demos, code samples. Only questions related to the existing topics are allowed here.
Post Reply
Sergey Tkachenko
Site Admin
Posts: 17236
Joined: Sat Aug 27, 2005 10:28 am
Contact:

[Example] Smart indent

Post by Sergey Tkachenko »

Smart indent, programming style

When user presses Enter key, this code adds space characters at the beginning of new paragraph (as many spaces as at the beginning of the current paragraph).

Code: Select all

// Returns count of space characters at the beginning of the current paragraph
function GetLeadingSpacesCount(rve: TCustomRichViewEdit): Integer;
var StartItemNo, ItemNo, i: Integer;
    s: String;
begin
  rve := rve.TopLevelEditor;
  ItemNo := rve.CurItemNo;
  while not rve.IsParaStart(ItemNo) do
    dec(ItemNo);
  Result := 0;
  StartItemNo := ItemNo;
  while ItemNo<rve.ItemCount do begin
    if (ItemNo>StartItemNo) and rve.IsParaStart(ItemNo) then
      exit;
    if rve.GetItemStyle(ItemNo)<0 then
      exit;
    s := rve.GetItemText(ItemNo);
    for i := 1 to Length(s) do
      if s[i]=' ' then
        inc(Result)
      else
        exit;
    inc(ItemNo);
  end;
end;

// Returns string consisting of Count space characters
function GetSpaces(Count: Integer): String;
var i: Integer;
begin
  SetLength(Result, Count);
  for i := 1 to Count do
    Result[i] := ' ';
end;

// OnKeyDown, indenting
procedure TForm1.RichViewEdit1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key=VK_RETURN then begin
    RichViewEdit1.InsertText(#13+GetSpaces(GetLeadingSpacesCount(RichViewEdit1)));
    Key := 0;
  end;
end;
Possibility to improve: when pressing Enter in the middle of paragraph, calculate how many space characters are to the right of the caret (so they will be at the beginning of the new paragraph) and take them into account.

2008-Dec-11: Updated for compatibility with TRichView 11 and Delphi 2009
Last edited by Sergey Tkachenko on Thu Dec 11, 2008 3:54 pm, edited 2 times in total.
Sergey Tkachenko
Site Admin
Posts: 17236
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

Smart indent/unindent, MS Word style

This code changes FirstIndent and LeftIndent of paragraph, if Tab/Backspace is pressed when the caret is at the beginning of paragraph. First, it changes FirstIndent. Next, if FirstIndent is already changed, it changes LeftIndent.
This code changes indents only if the caret is at the beginning of non-empty paragraph.

When the caret is at the beginning of a bulleted/numbered paragraph, Tab increases the list level.

This code uses its own procedures for OnParaStyleConversion event. Of course, you can integrate this code in existing event handler instead. But in this way, it's easy to integrate this code in existing application, even if it uses RichViewActions.

Code: Select all

// Procedure for OnParaStyleConversion event.
// Changes paragraph's FirstIndent to UserData
procedure TForm3.ChangeFirstIndentConversion(Sender: TCustomRichViewEdit;
  StyleNo, UserData: Integer; AppliedToText: Boolean;
  var NewStyleNo: Integer);
var ParaStyle: TParaInfo;
begin
  ParaStyle := TParaInfo.Create(nil);
  ParaStyle.Assign(Sender.Style.ParaStyles[StyleNo]);
  ParaStyle.FirstIndent := UserData;
  ParaStyle.Standard := False;
  NewStyleNo := Sender.Style.ParaStyles.FindSuchStyle(StyleNo, ParaStyle,
    RVAllParaInfoProperties);
  if NewStyleNo<0 then begin
    Sender.Style.ParaStyles.Add.Assign(ParaStyle);
    NewStyleNo := Sender.Style.ParaStyles.Count-1;
  end;
  ParaStyle.Free;
end;

// Procedure for OnParaStyleConversion event.
// Changes paragraph's LeftIndent to UserData 
// If UserData=0, resets FirstIndent as well.
procedure TForm3.ChangeLeftIndentConversion(Sender: TCustomRichViewEdit;
  StyleNo, UserData: Integer; AppliedToText: Boolean;
  var NewStyleNo: Integer);
var ParaStyle: TParaInfo;
begin
  ParaStyle := TParaInfo.Create(nil);
  ParaStyle.Assign(Sender.Style.ParaStyles[StyleNo]);
  ParaStyle.LeftIndent := UserData;
  if ParaStyle.LeftIndent=0 then
    ParaStyle.FirstIndent := 0;
  ParaStyle.Standard := False;
  NewStyleNo := Sender.Style.ParaStyles.FindSuchStyle(StyleNo, ParaStyle,
    RVAllParaInfoProperties);
  if NewStyleNo<0 then begin
    Sender.Style.ParaStyles.Add.Assign(ParaStyle);
    NewStyleNo := Sender.Style.ParaStyles.Count-1;
  end;
  ParaStyle.Free;
end;
 
function TForm3.ChangeIndent(rve: TCustomRichViewEdit;
  Step, Max: Integer): Boolean;
var OldParaStyleConversion: TRVStyleConversionEvent;
    FirstIndent, LeftIndent: Integer;
    ListNo, ListLevel, StartFrom: Integer;
    Reset: Boolean;
begin
  Result := False;
  OldParaStyleConversion := rve.OnParaStyleConversion;
  try
    rve := rve.TopLevelEditor;
    if rve.SelectionExists then
      exit;
    if  (rve.OffsetInCurItem<=rve.GetOffsBeforeItem(rve.CurItemNo)) and
       (rve.CurItemNo>0) and (rve.GetItemStyle(rve.CurItemNo-1)=rvsListMarker) then begin
       // changing list level
       rve.GetListMarkerInfo(rve.CurItemNo, ListNo, ListLevel, StartFrom, Reset);
       if (ListNo>=0) and (ListNo<rve.Style.ListStyles.Count) then
         if (Step>0) and (ListLevel+1<rve.Style.ListStyles[ListNo].Levels.Count) then begin
           rve.ChangeListLevels(+1);
           Result := True;
           end
         else if (Step<0) and (ListLevel>0) then begin
           rve.ChangeListLevels(-1);
           Result := True;
         end;
       exit;
    end;

    if (rve.OffsetInCurItem>rve.GetOffsBeforeItem(rve.CurItemNo)) or
      not rve.IsParaStart(rve.CurItemNo) then
      exit; // not at the beginning of paragraph
    if (rve.OffsetInCurItem>=rve.GetOffsAfterItem(rve.CurItemNo)) and
      ((rve.CurItemNo=rve.ItemCount-1) or rve.IsParaStart(rve.CurItemNo)) then
      exit; // empty paragraph, exiting
    FirstIndent := rve.Style.ParaStyles[rve.GetItemPara(rve.CurItemNo)].FirstIndent;
    LeftIndent  := rve.Style.ParaStyles[rve.GetItemPara(rve.CurItemNo)].LeftIndent; 
    if Step>0 then begin 
      if FirstIndent=0 then begin
        rve.OnParaStyleConversion := ChangeFirstIndentConversion;
        rve.ApplyParaStyleConversion(Step); 
        Result := True; 
        end 
      else begin 
        inc(LeftIndent, Step); 
        if LeftIndent>Max then 
          LeftIndent := Max; 
        if LeftIndent>rve.Style.ParaStyles[rve.GetItemPara(rve.CurItemNo)].LeftIndent then begin 
          rve.OnParaStyleConversion := ChangeLeftIndentConversion; 
          rve.ApplyParaStyleConversion(LeftIndent); 
          Result := True; 
        end; 
      end 
      end
    else begin 
     if FirstIndent>0 then begin 
        rve.OnParaStyleConversion := ChangeFirstIndentConversion; 
        rve.ApplyParaStyleConversion(0); 
        Result := True; 
        end 
      else begin 
        inc(LeftIndent, Step); 
        if LeftIndent<0 then 
          LeftIndent := 0; 
        if (LeftIndent<rve.Style.ParaStyles[rve.GetItemPara(rve.CurItemNo)].LeftIndent) or 
           (FirstIndent<>0) then begin 
          rve.OnParaStyleConversion := ChangeLeftIndentConversion; 
          rve.ApplyParaStyleConversion(LeftIndent); 
          Result := True;
        end; 
      end; 
    end;
  finally
    rve.OnParaStyleConversion := OldParaStyleConversion;
  end;
end;
 
// OnKeyDown event
procedure TForm3.RichViewEdit1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if (Key=VK_BACK) and ChangeIndent(TCustomRichViewEdit(Sender), -48, 480) then
    Key := 0;
end;

// OnKeyPress event 
procedure TForm3.RichViewEdit1KeyPress(Sender: TObject; var Key: Char);
begin
  if (Key=#9) and ChangeIndent(TCustomRichViewEdit(Sender), +48, 480) then
    Key := #0;
end;
Upd: 2011-Jun-26: changing list levels on Tab
Upd: 2015-Jan-14: changing list levels on Backspace
Last edited by Sergey Tkachenko on Wed Jan 14, 2015 10:36 am, edited 3 times in total.
jonjon
Posts: 432
Joined: Sat Aug 27, 2005 4:19 pm

Post by jonjon »

Sergey,

I've added this code and it seems to be working fine however I'm missing the SHIFT-TAB feature, available in Word, to un-indent.
Can it be easily added to your sample ?

Thanks.
Sergey Tkachenko
Site Admin
Posts: 17236
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

This code unindents on BACKSPACE.
To add SHIFT+TAB, change

Code: Select all

// OnKeyPress event 
procedure TForm3.RichViewEdit1KeyPress(Sender: TObject; var Key: Char); 
var step: Integer;
begin 
  if Key=#9 then begin
    step := 48;
    if GetAsyncKeyState(VK_SHIFT)and$8000<>0 then
      step := -step;
    if ChangeIndent(TCustomRichViewEdit(Sender), step, 480) then 
      Key := #0; 
  end;
end;
jonjon
Posts: 432
Joined: Sat Aug 27, 2005 4:19 pm

Post by jonjon »

Thanks Sergey but I can't get it to work on 13.10: TAB is working fine, Shift-Tab/Delete isn't. It looks like rve.IsParaStart is always returning false.
Sergey Tkachenko
Site Admin
Posts: 17236
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

Sorry, I cannot reproduce this problem. I added TForm3.ChangeFirstIndentConversion and the code

Code: Select all

procedure TForm3.RichViewEdit1KeyPress(Sender: TObject; var Key: Char); 
var step: Integer; 
begin 
  if Key=#9 then begin 
    step := 48; 
    if GetAsyncKeyState(VK_SHIFT)and$8000<>0 then 
      step := -step; 
    if ChangeIndent(TCustomRichViewEdit(Sender), step, 480) then 
      Key := #0; 
  end; 
end;
in the ActionTest project, and it works on Tab and Shift+Tab as expected.

Note that this procedure works only in non-empty paragraphs, like in MS Word.
Rael Bauer
Posts: 36
Joined: Tue Aug 21, 2007 4:47 am

Post by Rael Bauer »

Sergey Tkachenko wrote:Sorry, I cannot reproduce this problem. I added TForm3.ChangeFirstIndentConversion and the code

Code: Select all

procedure TForm3.RichViewEdit1KeyPress(Sender: TObject; var Key: Char); 
var step: Integer; 
begin 
  if Key=#9 then begin 
    step := 48; 
    if GetAsyncKeyState(VK_SHIFT)and$8000<>0 then 
      step := -step; 
    if ChangeIndent(TCustomRichViewEdit(Sender), step, 480) then 
      Key := #0; 
  end; 
end;
in the ActionTest project, and it works on Tab and Shift+Tab as expected.
If this is to work for Shift+Tab (i.e. un-indent) then shouldn't ChangeIndent function include some code like:

Code: Select all

rve.ChangeListLevels(-1);
It seems the top part of the function is not handling both cases?
Sergey Tkachenko
Site Admin
Posts: 17236
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

You are right, I corrected it.
Post Reply