Capitalize first letter of a sentence

General TRichView support forum. Please post your questions here
Post Reply
pgkammath
Posts: 36
Joined: Fri Nov 24, 2017 6:16 am

Capitalize first letter of a sentence

Post by pgkammath »

Is there a way to automatically capitalize the first letter of a sentence as we go on typing. ie. the first letter after the full stop and space.
thanks in advance
Ceprotec
Posts: 259
Joined: Thu Oct 28, 2010 6:09 pm
Contact:

Re: Capitalize first letter of a sentence

Post by Ceprotec »

pgkammath wrote: Thu Nov 08, 2018 9:21 am Is there a way to automatically capitalize the first letter of a sentence as we go on typing. ie. the first letter after the full stop and space.
thanks in advance
Sergey Tkachenko wrote: Sat Oct 29, 2016 3:21 pm Sorry for the delay.

Two main approaches are possible:
1) like in MS Word, a word is capitalized after it is finished
2) like in many other applications, a word is capitalized immediately on typing.

(1) is more complicated to implement, so I chose (2). This procedure is already not simple, so I do not want to complicate it even more.
The code is below. Actually, I think I'll create a new component, TRVKeyboardManager, that can be linked to TRichViewEdit or TSRichViewEdit and processes things like auto-capitalization, auto making opening/closing quotes, replacing - to –, etc.

The code is below.
This code requires Delphi 2009 or newer.

Code: Select all

{$I RV_Defs.inc}
uses Character;

type
  TRVCapitalizeState = (rvcsWord, rvcsWhitespace);

function IsEndOfSentencePunctuation(Ch: Char): Boolean;
begin
  case Ord(Ch) of
    $00021, $0002E, $0003F, $00589, $0061F, $006D4,
    $00700..$00702, $007F9, $00964, $00965, $0104A, $0104B,
    $01362, $01367, $01368, $0166E, $01803, $01809,
    $01944, $01945, $01AA8..$01AAB, $01B5A, $01B5B,
    $01B5E, $01B5F, $01C3B, $01C3C, $01C7E, $01C7F,
    $0203C, $0203D, $02047..$02049, $02E2E, $03002,
    $0A4FF, $0A60E, $0A60F, $0A6F3, $0A6F7,
    $0A876, $0A877, $0A8CE, $0A8CF, $0A92F,
    $0A9C8, $0A9C9, $0AA5D..$0AA5F, $0AAF0, $0AAF1,
    $0ABEB, $0FE52, $0FE56, $0FE57, $0FF01, $0FF0E,
    $0FF1F, $0FF61{, $11047, $11048, $110BE, $110BF,
    $110C0, $110C1, $11141..$11143, $111C5, $111C6}:
      Result := True;
    else
      Result := False;
  end;
end;

function RvIsLetterOrDigit(Ch: Char): Boolean; inline;
begin
  {$IFDEF RICHVIEWDEFXE4}
  Result := Ch.IsLetterOrDigit
  {$ELSE}
  Result := Character.IsLetterOrDigit(Ch);
  {$ENDIF}
end;

function RvIsWhiteSpace(Ch: Char): Boolean; inline;
begin
  {$IFDEF RICHVIEWDEFXE4}
  Result := Ch.IsWhiteSpace
  {$ELSE}
  Result := Character.IsWhiteSpace(Ch);
  {$ENDIF}
end;

function RvIsLower(Ch: Char): Boolean; inline;
begin
  {$IFDEF RICHVIEWDEFXE4}
  Result := Ch.IsLower
  {$ELSE}
  Result := Character.IsLower(Ch);
  {$ENDIF}
end;

function RvToUpper(Ch: Char): Char; inline;
begin
  {$IFDEF RICHVIEWDEFXE4}
  Result := Ch.ToUpper
  {$ELSE}
  Result := Character.ToUpper(Ch);
  {$ENDIF}
end;


function ShouldCapitalizeWord(rve: TCustomRichViewEdit;
  CapitalizeFirstCellChar, CapitalizeLists: Boolean): Boolean;
var
  ItemNo, Offs: Integer;
  S: String;
  State: TRVCapitalizeState;
  WhiteSpaceLength: Integer;

  function FinishItem: Boolean; // result: continue?
  begin
    Result := not rve.IsFromNewLine(ItemNo);
  end;

  function StartItem(ChangeOffs: Boolean): Boolean; // result: continue?
  begin
    Result := ItemNo >= 0;
    if not Result then
      exit;
    if rve.GetItemStyle(ItemNo) < 0 then
    begin
      Result := (rve.GetItemStyle(ItemNo) = rvsTab) and (State = rvcsWhitespace);
      if Result then
        inc(WhiteSpaceLength);
    end
    else
    begin
      Result := not rve.Style.TextStyles[rve.GetItemStyle(ItemNo)].IsSymbolCharset;
      if Result then
      begin
        S := rve.GetItemText(ItemNo);
        if ChangeOffs then
          Offs := Length(S);
      end;
    end;
  end;

begin
  Result := False;
  rve := rve.TopLevelEditor;
  if rve.SelectionExists then
    exit;
  ItemNo := rve.CurItemNo;
  Offs   := rve.OffsetInCurItem;
  if (Offs <= rve.GetOffsBeforeItem(ItemNo)) then
    if rve.IsFromNewLine(ItemNo) then
    begin
      Result := True;
      if not CapitalizeFirstCellChar and (rve.RVData.GetSourceRVData is TRVTableCellData) and (ItemNo = 0) then
        Result := False;
      exit;
    end
    else
    begin
      dec(ItemNo);
      if ItemNo < 0 then
        exit
      else
        Offs := rve.GetOffsAfterItem(ItemNo);
    end;
  dec(Offs);
  State := rvcsWhitespace;
  WhiteSpaceLength := 0;
  if not StartItem(False) then
  begin
    if CapitalizeLists and (ItemNo >=0) and
      (rve.GetItemStyle(ItemNo) = rvsListMarker) then
      Result := True;
    exit;
  end;
  while True do
  begin
    if (rve.GetItemStyle(ItemNo) >= 0) and (Offs > 0) then
    begin
      case State of
        rvcsWhitespace:
          if RvIsWhiteSpace(S[Offs]) then
            inc(WhiteSpaceLength)
          else if IsEndOfSentencePunctuation(S[Offs]) then
          begin
            if WhiteSpaceLength = 0 then
              exit;
            State := rvcsWord;
          end
          else
            exit;
        rvcsWord:
          begin
            Result := RvIsLetterOrDigit(S[Offs]);
            exit;
          end;
      end;
      dec(Offs);
    end
    else
    begin
      if not FinishItem then
        exit;
      dec(ItemNo);
      if not StartItem(True) then
        exit;
    end;
  end;
end;

procedure InsertReplacedChar(const Ch1, Ch2: Char; rve: TCustomRichViewEdit);
var
  ItemNo, Offs: Integer;
begin
  rve := rve.TopLevelEditor;
  ItemNo := rve.CurItemNo;
  Offs   := rve.OffsetInCurItem;
  rve.InsertText(Ch1);
  rve.BeginUndoGroup(rvutInsert);
  rve.SetUndoGroupMode(True);
  rve.SetSelectionBounds(ItemNo, Offs, rve.CurItemNo, rve.OffsetInCurItem);
  rve.DeleteSelection;
  rve.InsertText(Ch2);
  rve.SetUndoGroupMode(False);
end;

function CapitalizeWord(rve: TCustomRichViewEdit; var Key: Char;
  CapitalizeFirstCellChar, CapitalizeLists: Boolean): Boolean;
begin
  if RvIsLower(Key) and ShouldCapitalizeWord(rve, True, True) then
  begin
    InsertReplacedChar(Key, RVToUpper(Key), rve);
    Key := #0;
    Result := True;
  end
  else
    Result := False;
end;
The main function is CapitalizeWord. It has parameters:
- CapitalizeFirstCellChar: capitalize the first character in table cells
- CapitalizeLists: capitalize the first character in bulleted and numbered paragraphs.

Call it in OnKeyPress:

Code: Select all

procedure TForm3.RichViewEdit1KeyPress(Sender: TObject; var Key: Char);
begin
   ...
   CapitalizeWord(RichViewEdit1, Key, True, True);
end;
pgkammath
Posts: 36
Joined: Fri Nov 24, 2017 6:16 am

Re: Capitalize first letter of a sentence

Post by pgkammath »

Hi sergey,
Sorry, i forgot to mention the Delphi version. I want this to be done in Delphi-7 actually. So is there a way for that.

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

Re: Capitalize first letter of a sentence

Post by Sergey Tkachenko »

Here is the version for Delphi 5, 6, 7 and 2007.

Code: Select all

uses RVUni, RVTypes;


type
  TRVCapitalizeState = (rvcsWord, rvcsWhitespace);

function IsEndOfSentencePunctuation(Ch: TRVUnicodeChar): Boolean;
begin
  case Ord(Ch) of
    $00021, $0002E, $0003F, $00589, $0061F, $006D4,
    $00700..$00702, $007F9, $00964, $00965, $0104A, $0104B,
    $01362, $01367, $01368, $0166E, $01803, $01809,
    $01944, $01945, $01AA8..$01AAB, $01B5A, $01B5B,
    $01B5E, $01B5F, $01C3B, $01C3C, $01C7E, $01C7F,
    $0203C, $0203D, $02047..$02049, $02E2E, $03002,
    $0A4FF, $0A60E, $0A60F, $0A6F3, $0A6F7,
    $0A876, $0A877, $0A8CE, $0A8CF, $0A92F,
    $0A9C8, $0A9C9, $0AA5D..$0AA5F, $0AAF0, $0AAF1,
    $0ABEB, $0FE52, $0FE56, $0FE57, $0FF01, $0FF0E,
    $0FF1F, $0FF61{, $11047, $11048, $110BE, $110BF,
    $110C0, $110C1, $11141..$11143, $111C5, $111C6}:
      Result := True;
    else
      Result := False;
  end;
end;

function RvIsLetterOrDigit(Ch: TRVUnicodeChar): Boolean;
begin
  Result := IsCharAlphaNumericW(Ch);
end;

function RvIsWhiteSpace(Ch: TRVUnicodeChar): Boolean; 
begin
  case Ord(Ch) of
    $0020, $0009..$000D, $00A0, $0085, $2028, $2029,
    $1680, $2000..$200A, $202F, $205F, $3000:
      Result := True
    else
      Result := False;
  end;
end;

function RvIsLower(Ch: TRVUnicodeChar): Boolean; overload;
begin
  Result := IsCharLowerW(Ch)
end;

function RvToUpper(Ch: TRVUnicodeChar): TRVUnicodeChar;
var
  S: TRVUnicodeString;
begin
  S := Ch;
  CharUpperW(PRVUnicodeChar(S));
  Result := S[1];
end;


function ShouldCapitalizeWord(rve: TCustomRichViewEdit;
  CapitalizeFirstCellChar, CapitalizeLists: Boolean): Boolean;
var
  ItemNo, Offs: Integer;
  S: TRVUnicodeString;
  State: TRVCapitalizeState;
  WhiteSpaceLength: Integer;

  function FinishItem: Boolean; // result: continue?
  begin
    Result := not rve.IsFromNewLine(ItemNo);
  end;

  function StartItem(ChangeOffs: Boolean): Boolean; // result: continue?
  begin
    Result := ItemNo >= 0;
    if not Result then
      exit;
    if rve.GetItemStyle(ItemNo) < 0 then
    begin
      Result := (rve.GetItemStyle(ItemNo) = rvsTab) and (State = rvcsWhitespace);
      if Result then
        inc(WhiteSpaceLength);
    end
    else
    begin
      Result := not rve.Style.TextStyles[rve.GetItemStyle(ItemNo)].IsSymbolCharset;
      if Result then
      begin
        S := rve.GetItemTextW(ItemNo);
        if ChangeOffs then
          Offs := Length(S);
      end;
    end;
  end;

begin
  Result := False;
  rve := rve.TopLevelEditor;
  if rve.SelectionExists then
    exit;
  ItemNo := rve.CurItemNo;
  Offs   := rve.OffsetInCurItem;
  if (Offs <= rve.GetOffsBeforeItem(ItemNo)) then
    if rve.IsFromNewLine(ItemNo) then
    begin
      Result := True;
      if not CapitalizeFirstCellChar and (rve.RVData.GetSourceRVData is TRVTableCellData) and (ItemNo = 0) then
        Result := False;
      exit;
    end
    else
    begin
      dec(ItemNo);
      if ItemNo < 0 then
        exit
      else
        Offs := rve.GetOffsAfterItem(ItemNo);
    end;
  dec(Offs);
  State := rvcsWhitespace;
  WhiteSpaceLength := 0;
  if not StartItem(False) then
  begin
    if CapitalizeLists and (ItemNo >=0) and
      (rve.GetItemStyle(ItemNo) = rvsListMarker) then
      Result := True;
    exit;
  end;
  while True do
  begin
    if (rve.GetItemStyle(ItemNo) >= 0) and (Offs > 0) then
    begin
      case State of
        rvcsWhitespace:
          if RvIsWhiteSpace(S[Offs]) then
            inc(WhiteSpaceLength)
          else if IsEndOfSentencePunctuation(S[Offs]) then
          begin
            if WhiteSpaceLength = 0 then
              exit;
            State := rvcsWord;
          end
          else
            exit;
        rvcsWord:
          begin
            Result := RvIsLetterOrDigit(S[Offs]);
            exit;
          end;
      end;
      dec(Offs);
    end
    else
    begin
      if not FinishItem then
        exit;
      dec(ItemNo);
      if not StartItem(True) then
        exit;
    end;
  end;
end;

procedure InsertReplacedChar(const Ch1, Ch2: TRVUnicodeChar; rve: TCustomRichViewEdit);
var
  ItemNo, Offs: Integer;
begin
  rve := rve.TopLevelEditor;
  ItemNo := rve.CurItemNo;
  Offs   := rve.OffsetInCurItem;
  rve.InsertTextW(Ch1);
  rve.BeginUndoGroup(rvutInsert);
  rve.SetUndoGroupMode(True);
  rve.SetSelectionBounds(ItemNo, Offs, rve.CurItemNo, rve.OffsetInCurItem);
  rve.DeleteSelection;
  rve.InsertTextW(Ch2);
  rve.SetUndoGroupMode(False);
end;

function CapitalizeWord(rve: TCustomRichViewEdit; var Key: Char;
  CodePage: Cardinal; CapitalizeFirstCellChar, CapitalizeLists: Boolean): Boolean;
var
  S: TRVUnicodeString;
begin
  Result := False;
  S := RVU_AnsiToUnicode(CodePage, Key);
  if Length(S) <> 1 then
    exit;
  if RvIsLower(S[1]) and ShouldCapitalizeWord(rve, True, True) then
  begin
    InsertReplacedChar(S[1], RVToUpper(S[1]), rve);
    Key := #0;
    Result := True;
  end;
end;

How to use:

Code: Select all

procedure TForm3.RichViewEdit1KeyPress(Sender: TObject; var Key: Char);
begin
  ...
  CapitalizeWord(RichViewEdit1, Key, RVU_GetKeyboardCodePage,  True, True);
end;
pgkammath
Posts: 36
Joined: Fri Nov 24, 2017 6:16 am

Re: Capitalize first letter of a sentence

Post by pgkammath »

Thanks for the help. I will implement and revert.
Post Reply