[Example] Typing opening and closing quotes

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

[Example] Typing opening and closing quotes

Post by Sergey Tkachenko » Mon Dec 23, 2013 5:56 pm

The example automatically changes typed " and ' characters to the appropriate opening or closing quote.
The result depends on the keyboard language:
“ ” (example: English)
„ “ (example: German)
„ ” (example: Polish)
« » (example: Russian)
» « (Danish)
” ” (Finnish, Swedish)

I based this work on the following tables:
http://en.wikipedia.org/wiki/Internatio ... tion_marks
http://technet.microsoft.com/en-us/libr ... 25682.aspx
If this procedure chooses an incorrect (or not traditional) quotes for your language, please report to richviewgmailcom or to this topic.

How it works.
In OnKeyPress event, we check for ' and " characters. If one of these characters is typed, we find an appropriate replacement using GetQuote().
Of course, we can assign this replacement character to Key parameter of OnKeyPress, but this is not a good solution, because undo will simple delete this character. Instead, we assign this character to a global variable ReplaceChar, and perform a replacement in OnChange event. In this way, the first undo will revert a quote replacement, the second undo will delete a typed character.

Code: Select all

uses RVUni;

{$IFnDEF UNICODE}
const LDQuo = #147;
const RDQuo = #148;
const LAQuo = #171;
const RAQuo = #187;
const LSQuo = #145;
const RSQuo = #146;
const BDQuo = #132;
{$ELSE}
const LDQuo = #$201C;
const RDQuo = #$201D;
const LAQuo = #$00AB;
const RAQuo = #$00BB;
const LSQuo = #$2018;
const RSQuo = #$2019;
const BDQuo = #$201E;
{$ENDIF}

function GetQuote(rve: TCustomRichViewEdit; Ch: Char): Char;
var ItemNo1, ItemNo2, Offs1, Offs2: Integer;
    DelimBefore, DelimAfter: Boolean;
    {..........................................}
    function IsDelim(ItemNo, Index: Integer): Boolean;
    var
      s : String;
      {$IFnDEF UNICODE}
      CodePage: Cardinal;
      {$ENDIF}
    begin
      s := rve.GetItemText(ItemNo);
      if (Index>Length(s)) or (Index<0) then begin
        Result := True;
        exit;
      end;
      {$IFnDEF UNICODE}
      CodePage := rve.RVData.GetItemCodePage(ItemNo);
      {$ENDIF}
      Result :=
        {$IFDEF UNICODE}
        rve.RVData.IsDelimiterW(s[Index])
        {$ELSE}
        rve.RVData.IsDelimiterA(s[Index], CodePage)
      {$ENDIF}
    end;
    {..........................................}
    function DoGetQuote(Opening: Boolean): Char;
    var Lang: Cardinal;
    begin
      Lang := RVU_GetKeyboardLanguage;
      case Ch of
      '"':
        case Lang of
        // Albanian, Bulgarian, Czech, Estonian, Georgian,
        // German,
        // Icelandic, Lithuanian, Macedonian,
        // Slovak, Slovene, Sorbian
        $041c, $0402, $0405, $0425, $0437,
        $0c07, $0407, $1407, $1007,
        $040f, $0427, $042f,
        $041b, $0424, $082e:
          if Opening then
            Result := BDQuo
          else
            Result := LDQuo;
        // Croatian, Hungarian, Polish, Romanian,
        // Serbian,
        $041a, $101a, $040e, $0415, $0418,
        $1c1a, $181a, $301a, $2c1a, $281a, $241a, $0c1a, $081a:
          if Opening then
            Result := BDQuo
          else
            Result := RDQuo;
        // Armenian, Azerbaijani, Basque, Belarusian, Catalan,
        // French,
        // German-Swiss,
        // Greek, Italian, Latvian, Norwegian
        // Persian, Portuguese, Russian,
        // Spanish,
        // Ukrainian
        $042b, $082c, $042c, $042d, $0423, $0403,
        $080c, $0c0c, $040c, $140c, $180c, $100c,
        $0807,
        $0408, $0410, $0810, $0426, $0414, $0814,
        $0429, $0416, $0816, $0419,
        $2c0a, $200a, $400a, $340a, $240a, $140a, $1c0a, $300a, $440a, $100a,
        $480a, $080a, $4c0a, $180a, $3c0a, $280a, $500a, $0c0a, $040a, $540a, $380a,
        $0422:
          if Opening then
            Result := LAQuo
          else
            Result := RAQuo;
        // Danish
        $0406:
          if Opening then
            Result := RAQuo
          else
            Result := LAQuo;
        // Finnish, Swedish
        $040b, $081d, $041d:
          Result := RDQuo;
        else
          if Opening then
            Result := LDQuo
          else
            Result := RDQuo;
        end;
      '''':
        case Lang of
        // Finnish, Swedish
        $040b, $081d, $041d:
          Result := RSQuo;
        else
          if Opening then
            Result := LSQuo
          else
            Result := RSQuo
        end
      else
        Result := Ch;
      end;
    end;
    {..........................................}
begin
  // Returing the original character by default
  Result := Ch;
  rve := rve.TopLevelEditor;
  if rve.SelectionExists then
    exit;
  // Finding the position before (ItemNo1, Offs1) and after (ItemNo2, Offs2) the insertion point
  ItemNo1 := rve.CurItemNo;
  Offs1 := rve.OffsetInCurItem;
  if Offs1<=rve.GetOffsBeforeItem(ItemNo1) then begin
    ItemNo2 := ItemNo1;
    Offs2 := Offs1;
    dec(ItemNo1);
    if ItemNo1>=0 then
      if not rve.IsFromNewLine(ItemNo1) then
        Offs1 := rve.GetOffsAfterItem(ItemNo1)
      else begin
        ItemNo1 := -1;
        Offs1 := -1;
      end
    else
      Offs1 := -1;
    end
  else if Offs1>=rve.GetOffsAfterItem(ItemNo1) then begin
    ItemNo2 := ItemNo1+1;
    if ItemNo2<rve.ItemCount then
      if not rve.IsFromNewLine(ItemNo2) then
        Offs2 := rve.GetOffsBeforeItem(ItemNo2)
      else begin
        ItemNo2 := rve.ItemCount;
        Offs2 := -1;
      end
    else
      Offs2 := -1;
    end
  else begin
    ItemNo2 := ItemNo1;
    Offs2 := Offs1;
  end;
  // Is a delimiter (or nothing, or non-text) before the insertion point?
  if (ItemNo1<0) or (rve.GetItemStyle(ItemNo1)<0) or
    (rve.Style.TextStyles[rve.GetItemStyle(ItemNo1)].Charset=SYMBOL_CHARSET) then
    DelimBefore := True
  else
    DelimBefore := IsDelim(ItemNo1, Offs1-1);
  // Is a delimiter (or nothing, or non-text) after the insertion point?
  if (ItemNo2>=rve.ItemCount) or (rve.GetItemStyle(ItemNo2)<0) or
    (rve.Style.TextStyles[rve.GetItemStyle(ItemNo2)].Charset=SYMBOL_CHARSET) then
    DelimAfter := True
  else
    DelimAfter := IsDelim(ItemNo2, Offs2);
  // Choosing a quote
  if DelimBefore and not DelimAfter then
    Result := DoGetQuote(True)
  else if not DelimBefore and DelimAfter then
    Result := DoGetQuote(False);
end;

// this variable can be moved to a form
const ReplaceChar: Char = #0; 

procedure TForm3.RichViewEdit1KeyPress(Sender: TObject; var Key: Char);
begin
  ReplaceChar := #0;
  if (Key='"') or (Key='''') then begin
    ReplaceChar := GetQuote(RichViewEdit1, Key);
    if ReplaceChar=Key then
      ReplaceChar := #0;
  end;
end;

procedure TForm3.RichViewEdit1Change(Sender: TObject);
var rve: TCustomRichViewEdit;
    ItemNo, Offs: Integer;
    Ch: Char;
begin
  if ReplaceChar<>#0 then begin
    Ch := ReplaceChar;
    ReplaceChar := #0;
    rve := RichViewEdit1.TopLevelEditor;
    ItemNo := rve.CurItemNo;
    Offs := rve.OffsetInCurItem;
    rve.SetSelectionBounds(ItemNo, Offs-1, ItemNo, Offs);
    rve.InsertText(Ch);
  end;
end;

DavidRM
Posts: 90
Joined: Mon Aug 29, 2005 5:18 pm
Location: Tulsa, OK
Contact:

Post by DavidRM » Fri Dec 12, 2014 8:34 pm

I was unable to get this code to work as expected. I essentially just copied-and-pasted it in. The results are very inconsistent.

My own implementation boils down to this logic:

Code: Select all

when user types a " or '
  if this is the beginning of a new paragraph, or if the previous character is whitespace
    replace with an open/left quote
  else
    replace with a close/right quote
From my experimenting with MS Word 2003, they use pretty much the same simple logic.

-David

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

Post by Sergey Tkachenko » Fri Dec 19, 2014 9:36 pm

In this case, you can change the code fragment

Code: Select all

 if DelimBefore and not DelimAfter then 
    Result := DoGetQuote(True) 
  else if not DelimBefore and DelimAfter then 
    Result := DoGetQuote(False); 
to

Code: Select all

Result := DoGetQuote(DelimBefore) 

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest