
{*******************************************************}
{                                                       }
{       RichView                                        }
{       RVChangeCharCase - procedure for changing       }
{       character case of the selected text             }
{                                                       }
{       Copyright (c) Sergey Tkachenko                  }
{       svt@trichview.com                               }
{       http://www.trichview.com                        }
{                                                       }
{*******************************************************}

unit RVCharCase;

{$I RV_Defs.inc}



interface
uses
  {$IFDEF RICHVIEWDEF2009}AnsiStrings,{$ENDIF}
  Windows, SysUtils,
  RVStyle, RVEdit, RVUni, RVItem, RVTypes;

{
  Limitations:
    - requires WinNT/2k/XP for Unicode text (otherwise, skips Unicode)
    - for non-Unicode text, works correctly only for the main language of Windows
    - does not work with selected tables/cells
}

type
  TRVCharCase = (rvccLowerCase, rvccUpperCase, rvccTitleWord);

// Changes char case
procedure RVChangeCharCase(rve: TCustomRichViewEdit; CharCase: TRVCharCase);
// Returns information about selected text
procedure RVGetCharCase(rve: TCustomRichViewEdit;
  var AllUpperCase, AllLowerCase: Boolean);

implementation

{------------------------------------------------------------------------------}

function ChangeCharCase(const prevs, s: TRVRawByteString; TextStyle: TFontInfo;
  CharCase: TRVCharCase): TRVRawByteString;
var unitext: PRVWordArray;
    i: Integer;
    Change: Boolean;
begin
  if Length(s)=0 then begin
    Result := s;
    exit;
  end;
  case CharCase of
    rvccLowerCase:
      if TextStyle.Unicode then
        if RVNT then begin
          SetString(Result, PRVAnsiChar(s), Length(s));
          CharLowerBuffW(Pointer(Result), Length(s) div 2);
          end
        else
          Result := s
      else
       Result := {$IFDEF RVUNICODESTR}AnsiStrings.{$ENDIF}AnsiLowerCase(s);
    rvccUpperCase:
      if TextStyle.Unicode then
        if RVNT then begin
          SetString(Result, PRVAnsiChar(s), Length(s));
          CharUpperBuffW(Pointer(Result), Length(s) div 2);
          end
        else
          Result := s
      else
       Result := {$IFDEF RVUNICODESTR}AnsiStrings.{$ENDIF}AnsiUpperCase(s);
    rvccTitleWord:
      if TextStyle.Unicode then
        if RVNT then begin
          SetString(Result, PRVAnsiChar(s), Length(s));
          CharLowerBuffW(Pointer(Result), Length(s) div 2);
          unitext := PRVWordArray(PRVAnsiChar(Result));
          Change := True;
          if prevs<>'' then
            Change := not IsCharAlphaNumericW(
              TRVUnicodeChar(PWordArray(PRVAnsiChar(prevs))[Length(prevs) div 2-1]));
          for i := 0 to Length(s) div 2 -1 do
            if not IsCharAlphaNumericW(TRVUnicodeChar(unitext[i])) then
              Change := True
            else if Change then begin
              CharUpperBuffW(Pointer(@(unitext[i])), 1);
              Change := False;
            end;
          end
        else
          Result := s
      else begin
        Result := {$IFDEF RVUNICODESTR}AnsiStrings.{$ENDIF}AnsiLowerCase(s);
        Change := True;
        if prevs<>'' then
          Change := not IsCharAlphaNumericA(prevs[Length(prevs)]);
        for i := 1 to Length(Result) do
          if not IsCharAlphaNumericA(Result[i]) then
            Change := True
          else if Change then begin
            CharUpperBuffA(Pointer(@(Result[i])), 1);
            Change := False;
          end;
      end;
  end;
end;

procedure RVChangeCharCase(rve: TCustomRichViewEdit; CharCase: TRVCharCase);
var i, ItemNo1, ItemNo2, Offs1, Offs2: Integer;
    AItemNo1, AItemNo2, AOffs1, AOffs2: Integer;
    TextStyles: TFontInfos;
    ItemOptions: TRVItemOptions;
    s, s1, s2: TRVRawByteString;
begin
  TextStyles := rve.Style.TextStyles;
  rve := rve.TopLevelEditor;
  rve.BeginUndoGroup(rvutModifyItem);
  rve.SetUndoGroupMode(True);
  rve.TopLevelEditor.BeginUpdate;
  try
    rve.GetSelectionBounds(ItemNo1, Offs1, ItemNo2, Offs2, True);
    rve.GetSelectionBounds(AItemNo1, AOffs1, AItemNo2, AOffs2, False);
    if ItemNo2<>ItemNo1 then begin
      if rve.GetItemStyle(ItemNo1)>=0 then begin
        ItemOptions := rve.GetItem(ItemNo1).ItemOptions;
        s  := rve.GetItemTextR(ItemNo1);
        s1 := RVU_Copy(s, 1, Offs1-1, ItemOptions);
        s2 := RVU_Copy(s, Offs1, Length(s), ItemOptions);
        if s2<>'' then begin
          s2 := ChangeCharCase(s1, s2, TextStyles[rve.GetItemStyle(ItemNo1)], CharCase);
          rve.SetItemTextEdR(ItemNo1, s1+s2);
        end;
      end;
      for i := ItemNo1+1 to ItemNo2-1 do
        if rve.GetItemStyle(i)>=0 then begin
          s  := rve.GetItemTextR(i);
          s := ChangeCharCase('', s, TextStyles[rve.GetItemStyle(i)], CharCase);
          rve.SetItemTextEdR(i, s);
        end;
      if rve.GetItemStyle(ItemNo2)>=0 then begin
        ItemOptions := rve.GetItem(ItemNo2).ItemOptions;
        s  := rve.GetItemTextR(ItemNo2);
        s1 := RVU_Copy(s, 1, Offs2-1, ItemOptions);
        s2 := RVU_Copy(s, Offs2, Length(s), ItemOptions);
        if s1<>'' then begin
          s1 := ChangeCharCase('', s1, TextStyles[rve.GetItemStyle(ItemNo2)], CharCase);
          rve.SetItemTextEdR(ItemNo2, s1+s2);
        end;
      end
      end
    else begin
      ItemOptions := rve.GetItem(ItemNo1).ItemOptions;
      s  := rve.GetItemTextR(ItemNo1);
      s1 := RVU_Copy(s, 1, Offs1-1, ItemOptions);
      s2 := RVU_Copy(s, Offs2, Length(s), ItemOptions);
      s  := RVU_Copy(s, Offs1, Offs2-Offs1, ItemOptions);
      s := ChangeCharCase(s1, s, TextStyles[rve.GetItemStyle(ItemNo1)], CharCase);
      rve.SetItemTextEdR(ItemNo1, s1+s+s2);
    end;
  finally
    rve.SetUndoGroupMode(False);
    rve.SetSelectionBounds(AItemNo1, AOffs1, AItemNo2, AOffs2);
    rve.TopLevelEditor.EndUpdate;
  end;
end;
{------------------------------------------------------------------------------}
// Assigns AllUpperCase to false, if there are lower-case characters
// Assigns AllLowerCase to false, if there are upper-case characters
// Returns (AllUpperCase or AllLowerCase)
function GetCharCase(const s: TRVRawByteString; Unicode: Boolean;
  var AllUpperCase, AllLowerCase: Boolean): Boolean;
var unitext: PRVWordArray;
    i: Integer;
    Upper, Lower: Boolean;
    ch: TRVAnsiChar;
    wch: TRVUnicodeChar;
begin
  if (Length(s)=0) or (Unicode and not RVNT) then begin
    Result := (AllUpperCase or AllLowerCase);
    exit;
  end;
  Upper := False;
  Lower := False;
  if not Unicode then
    for i := 1 to Length(s) do begin
      ch := s[i];
      if IsCharAlphaA(ch) then begin
        if IsCharLowerA(ch) then
          Lower := True;
        if IsCharUpperA(ch) then
          Upper := True;
        if Upper and Lower then
          break;
      end;
    end
  else begin
    unitext := PRVWordArray(PRVAnsiChar(s));
    for i := 0 to (Length(s) div 2)-1 do begin
      wch := TRVUnicodeChar(unitext[i]);
      if IsCharAlphaW(wch) then begin
        if IsCharLowerW(wch) then
          Lower := True;
        if IsCharUpperW(wch) then
          Upper := True;
        if Upper and Lower then
          break;
      end
    end;
  end;
  if Upper then
    AllLowerCase := False;
  if Lower then
    AllUpperCase := False;
  Result := (AllUpperCase or AllLowerCase);
end;
{------------------------------------------------------------------------------}
procedure RVGetCharCase(rve: TCustomRichViewEdit;
  var AllUpperCase, AllLowerCase: Boolean);
var i, ItemNo1, ItemNo2, Offs1, Offs2: Integer;
    TextStyles: TFontInfos;
    ItemOptions: TRVItemOptions;
    s: TRVRawByteString;
begin
  TextStyles := rve.Style.TextStyles;
  rve := rve.TopLevelEditor;
  AllUpperCase := True;
  AllLowerCase := True;
  rve.GetSelectionBounds(ItemNo1, Offs1, ItemNo2, Offs2, True);
  if ItemNo2<>ItemNo1 then begin
    if rve.GetItemStyle(ItemNo1)>=0 then begin
      ItemOptions := rve.GetItem(ItemNo1).ItemOptions;
      s  := rve.GetItemTextR(ItemNo1);
      s := RVU_Copy(s, Offs1, Length(s), ItemOptions);
      GetCharCase(s, rvioUnicode in ItemOptions, AllUpperCase, AllLowerCase);
    end;
    if (AllUpperCase or AllLowerCase) then
      for i := ItemNo1+1 to ItemNo2-1 do
        if rve.GetItemStyle(i)>=0 then begin
          s  := rve.GetItemTextR(i);
          if not GetCharCase(s, TextStyles[rve.GetItemStyle(i)].Unicode, AllUpperCase, AllLowerCase) then
           break;
        end;
    if (AllUpperCase or AllLowerCase) and (rve.GetItemStyle(ItemNo2)>=0) then begin
      ItemOptions := rve.GetItem(ItemNo2).ItemOptions;
      s  := rve.GetItemTextR(ItemNo2);
      s := RVU_Copy(s, 1, Offs2-1, ItemOptions);
      GetCharCase(s, rvioUnicode in ItemOptions, AllUpperCase, AllLowerCase)
    end;
    end
  else begin
    ItemOptions := rve.GetItem(ItemNo1).ItemOptions;
    s  := rve.GetItemTextR(ItemNo1);
    s  := RVU_Copy(s, Offs1, Offs2-Offs1, ItemOptions);
    GetCharCase(s, rvioUnicode in ItemOptions, AllUpperCase, AllLowerCase)
  end;
end;

end.
