Changeset 109


Ignore:
Timestamp:
Feb 14, 2022, 5:43:30 PM (2 years ago)
Author:
chronos
Message:
  • Added: Synced edit of full name and name parts.
Location:
trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/Forms/UFormContact.lfm

    r108 r109  
    1818    Top = 8
    1919    Width = 1009
    20     ActivePage = TabSheetGeneral
     20    ActivePage = TabSheetHome
    2121    Anchors = [akTop, akLeft, akRight, akBottom]
    2222    ParentFont = False
    23     TabIndex = 0
     23    TabIndex = 1
    2424    TabOrder = 0
    2525    object TabSheetGeneral: TTabSheet
     
    462462        Anchors = [akTop, akLeft, akRight]
    463463        Caption = 'Address'
    464         ClientHeight = 285
     464        ClientHeight = 259
    465465        ClientWidth = 895
    466466        TabOrder = 6
  • trunk/Forms/UFormContact.pas

    r108 r109  
    735735
    736736procedure TFormContact.EditFullNameChange(Sender: TObject);
    737 var
    738 
    739   Before, First, Middle, Last, After: string;
    740 begin
    741   Contact.FullNameToNameParts(EditFullName.Text, Before, First, Middle, Last, After);
    742   UpdateEditNoOnChange(EditTitleBefore, Before);
    743   UpdateEditNoOnChange(EditFirstName, First);
    744   UpdateEditNoOnChange(EditMiddleName, Middle);
    745   UpdateEditNoOnChange(EditLastName, Last);
    746   UpdateEditNoOnChange(EditTitleAfter, After);
    747   UpdateInterface;
     737begin
     738  with TNameDetails.Create do
     739  try
     740    Prefix := EditTitleBefore.Text;
     741    First := EditFirstName.Text;
     742    Middle := EditMiddleName.Text;
     743    Last := EditLastName.Text;
     744    Suffix := EditTitleAfter.Text;
     745    Split(EditFullName.Text);
     746    UpdateEditNoOnChange(EditTitleBefore, Prefix);
     747    UpdateEditNoOnChange(EditFirstName, First);
     748    UpdateEditNoOnChange(EditMiddleName, Middle);
     749    UpdateEditNoOnChange(EditLastName, Last);
     750    UpdateEditNoOnChange(EditTitleAfter, Suffix);
     751    UpdateInterface;
     752  finally
     753    Free;
     754  end;
    748755end;
    749756
    750757procedure TFormContact.NamePartChange(Sender: TObject);
    751758begin
    752   UpdateEditNoOnChange(EditFullName, Contact.NamePartsToFullName(EditTitleBefore.Text,
    753     EditFirstName.Text, EditMiddleName.Text, EditLastName.Text, EditTitleAfter.Text));
     759  with TNameDetails.Create do
     760  try
     761    Prefix := EditTitleBefore.Text;
     762    First := EditFirstName.Text;
     763    Middle := EditMiddleName.Text;
     764    Last := EditLastName.Text;
     765    Suffix := EditTitleAfter.Text;
     766    UpdateEditNoOnChange(EditFullName, GetCombined);
     767  finally
     768    Free;
     769  end;
    754770end;
    755771
  • trunk/Forms/UFormProperty.pas

    r103 r109  
    124124    Groups.StrictDelimiter := True;
    125125    Groups.DelimitedText := EditAttributes.Text;
     126    GroupsArray := Default(TStringArray);
    126127    SetLength(GroupsArray, Groups.Count);
    127128    for I := 0 to Groups.Count - 1 do
  • trunk/Languages/vCardStudio.cs.po

    r108 r109  
    182182#: tformcontact.label1.caption
    183183msgid "First name:"
    184 msgstr "Křestní jméno:"
     184msgstr "První jméno:"
    185185
    186186#: tformcontact.label10.caption
     
    273273msgctxt "tformcontact.label28.caption"
    274274msgid "Street:"
    275 msgstr "Adresa:"
     275msgstr "Ulice:"
    276276
    277277#: tformcontact.label29.caption
     
    387387#: tformcontact.label5.caption
    388388msgid "Last name"
    389 msgstr "Příjmení"
     389msgstr "Poslední jméno"
    390390
    391391#: tformcontact.label50.caption
     
    630630msgctxt "tformcontacts.listview1.columns[1].caption"
    631631msgid "First name"
    632 msgstr "Křestní jméno"
     632msgstr "První jméno"
    633633
    634634#: tformcontacts.listview1.columns[2].caption
     
    640640msgctxt "tformcontacts.listview1.columns[3].caption"
    641641msgid "Last Name"
    642 msgstr "Příjmení"
     642msgstr "Poslední jméno"
    643643
    644644#: tformcontacts.listview1.columns[4].caption
     
    10181018#: ucontact.sfirstname
    10191019msgid "First Name"
    1020 msgstr "Křestní jméno"
     1020msgstr "První Jméno"
    10211021
    10221022#: ucontact.sfullname
     
    11171117msgctxt "ucontact.slastname"
    11181118msgid "Last Name"
    1119 msgstr "Příjmení"
     1119msgstr "Poslední Jméno"
    11201120
    11211121#: ucontact.slasttimecontacted
  • trunk/Packages/Common/UCommon.pas

    r108 r109  
    304304    end else Break;
    305305  until False;
    306   SetLength(Result, Length(Result) + 1);
    307   Result[High(Result)] := Data;
     306  if Data <> '' then begin
     307    SetLength(Result, Length(Result) + 1);
     308    Result[High(Result)] := Data;
     309  end;
    308310end;
    309311
  • trunk/UContact.pas

    r108 r109  
    99
    1010type
     11  TNamePartKind = (npNone, npPrefix, npFirst, npMiddle, npLast, npSuffix);
     12
     13  TNamePart = record
     14    Index: Integer;
     15    Text: string;
     16    PartKind: TNamePartKind;
     17    NamePart: ^TNamePart;
     18    Previous: ^TNamePart;
     19    Next: ^TNamePart;
     20  end;
     21
     22  TNameParts = array of TNamePart;
     23
     24  { TNameDetails }
     25
     26  TNameDetails = class
     27  private
     28    function GetAsNameParts: TNameParts;
     29    function GetDetail(NamePartKind: TNamePartKind): string;
     30    function IsSuffix(Text: string): Boolean;
     31  public
     32    Prefix: string;
     33    First: string;
     34    Middle: string;
     35    Last: string;
     36    Suffix: string;
     37    procedure Split(FullName: string);
     38    function GetCombined: string;
     39  end;
     40
    1141  TContactsFile = class;
    1242
     
    141171    function GetProperty(Field: TContactField): TContactProperty; overload;
    142172    function GetProperty(FieldIndex: TContactFieldIndex): TContactProperty; overload;
    143     procedure FullNameToNameParts(FullName: string; out Before, First, Middle,
    144       Last, After: string);
    145     function NamePartsToFullName(Before, First, Middle, Last, After: string): string;
    146173    procedure Assign(Source: TContact);
    147174    function UpdateFrom(Source: TContact): Boolean;
     
    330357end;
    331358
     359function StartsWith(Text, What: string): Boolean;
     360begin
     361  Result := Copy(Text, 1, Length(Text)) = What;
     362end;
     363
    332364function EndsWith(Text, What: string): Boolean;
    333365begin
     
    408440  end;
    409441  SetLength(Result, O - 1);
     442end;
     443
     444{ TNameDetails }
     445
     446function IsNumber(Text: string): Boolean;
     447var
     448  Value: Integer;
     449begin
     450  Result := TryStrToInt(Text, Value);
     451end;
     452
     453function IsRomanNumber(Text: string): Boolean;
     454var
     455  I: Integer;
     456begin
     457  Result := True;
     458  for I := 1 to Length(Text) do
     459    if not (Text[I] in ['I', 'V', 'X', 'L', 'C', 'D', 'M']) then begin
     460      Result := False;
     461      Break;
     462    end;
     463end;
     464
     465procedure SearchPart(var NameParts: TNameParts; var NamePart: TNamePart);
     466var
     467  I: Integer;
     468begin
     469  for I := 0 to Length(NameParts) - 1 do begin
     470    if (NameParts[I].PartKind = npNone) and (NameParts[I].Text = NamePart.Text) then begin
     471      NameParts[I].PartKind := NamePart.PartKind;
     472      NameParts[I].NamePart := @NamePart;
     473      NamePart.NamePart := @NameParts[I];
     474      Break;
     475    end;
     476  end;
     477end;
     478
     479procedure SearchPartBackward(var NameParts: TNameParts; var NamePart: TNamePart);
     480var
     481  I: Integer;
     482begin
     483  for I := Length(NameParts) - 1 downto 0 do begin
     484    if (NameParts[I].PartKind = npNone) and (NameParts[I].Text = NamePart.Text) then begin
     485      NameParts[I].PartKind := NamePart.PartKind;
     486      NameParts[I].NamePart := @NamePart;
     487      NamePart.NamePart := @NameParts[I];
     488      Break;
     489    end;
     490  end;
     491end;
     492
     493function UsedInNameParts(NamePart: TNamePartKind; NameParts: TNameParts): Boolean;
     494var
     495  I: Integer;
     496begin
     497  I := 0;
     498  while (I < Length(NameParts)) and (NameParts[I].PartKind <> NamePart) do Inc(I);
     499  Result := I < Length(NameParts);
     500end;
     501
     502function TNameDetails.GetAsNameParts: TNameParts;
     503var
     504  I: Integer;
     505  K: TNamePartKind;
     506  Parts: TStringArray;
     507begin
     508  Result := Default(TNameParts);
     509  for K := Low(TNamePartKind) to High(TNamePartKind) do begin
     510    if GetDetail(K) <> '' then begin
     511      Parts := Explode(' ', GetDetail(K));
     512      for I := 0 to Length(Parts) - 1 do begin
     513        SetLength(Result, Length(Result) + 1);
     514        Result[Length(Result) - 1].Text := Parts[I];
     515        Result[Length(Result) - 1].PartKind := K;
     516        Result[Length(Result) - 1].Index := Length(Result) - 1;
     517      end;
     518    end;
     519  end;
     520
     521  // Update previous and next links
     522  for I := 0 to Length(Result) - 1 do begin
     523    if I > 0 then
     524      Result[I].Previous := @Result[I - 1];
     525    if (I + 1) < Length(Result) then
     526      Result[I].Next := @Result[I + 1];
     527  end;
     528end;
     529
     530function TNameDetails.GetDetail(NamePartKind: TNamePartKind): string;
     531begin
     532  case NamePartKind of
     533    npPrefix: Result := Prefix;
     534    npFirst: Result := First;
     535    npMiddle: Result := Middle;
     536    npLast: Result := Last;
     537    npSuffix: Result := Suffix;
     538  end;
     539end;
     540
     541function TNameDetails.IsSuffix(Text: string): Boolean;
     542begin
     543  Result := (Pos('.', Text) > 0) or IsNumber(Text) or
     544    IsRomanNumber(Text);
     545end;
     546
     547procedure TNameDetails.Split(FullName: string);
     548var
     549  Parts: TStringArray;
     550  NewNameParts: TNameParts;
     551  OldNameParts: TNameParts;
     552  I: Integer;
     553  J: Integer;
     554  Text: string;
     555  NextKind: TNamePartKind;
     556begin
     557  OldNameParts := GetAsNameParts;
     558
     559  Text := FullName;
     560  while Pos('  ', FullName) > 0 do
     561    FullName := StringReplace(FullName, '  ', ' ', [rfReplaceAll]);
     562  Text := Trim(Text);
     563
     564  Parts := Explode(' ', Text);
     565  NewNameParts := Default(TNameParts);
     566  SetLength(NewNameParts, Length(Parts));
     567  for I := 0 to Length(NewNameParts) - 1 do begin
     568    NewNameParts[I].Index := I;
     569    NewNameParts[I].PartKind := npNone;
     570    NewNameParts[I].Text := Parts[I];
     571    if I > 0 then
     572      NewNameParts[I].Previous := @NewNameParts[I - 1];
     573    if (I + 1) < Length(NewNameParts) then
     574      NewNameParts[I].Next := @NewNameParts[I + 1];
     575  end;
     576
     577  // Match existing parts
     578  for I := 0 to Length(OldNameParts) - 1 do begin
     579    if OldNameParts[I].Text <> '' then
     580      SearchPart(NewNameParts, OldNameParts[I]);
     581  end;
     582
     583  // Check incorrect matches
     584  for I := 0 to Length(OldNameParts) - 1 do begin
     585    for J := I + 1 to Length(OldNameParts) - 1 do
     586      if Assigned(OldNameParts[I].NamePart) and Assigned(OldNameParts[J].NamePart) and
     587      (OldNameParts[I].NamePart^.Index >= OldNameParts[J].NamePart^.Index) then begin
     588        if Abs(I - OldNameParts[I].NamePart^.Index) >
     589        Abs(J - OldNameParts[J].NamePart^.Index) then begin
     590          OldNameParts[I].NamePart^.PartKind := npNone;
     591          OldNameParts[I].NamePart^.NamePart := nil;
     592          OldNameParts[I].NamePart := nil;
     593        end else begin
     594          OldNameParts[J].NamePart^.PartKind := npNone;
     595          OldNameParts[J].NamePart^.NamePart := nil;
     596          OldNameParts[J].NamePart := nil;
     597        end;
     598      end;
     599  end;
     600  for I := Length(OldNameParts) - 1 downto 0 do begin
     601    for J := I - 1 downto 0 do
     602      if Assigned(OldNameParts[I].NamePart) and Assigned(OldNameParts[J].NamePart) and
     603      (OldNameParts[I].NamePart^.Index <= OldNameParts[J].NamePart^.Index) then begin
     604        if Abs(I - OldNameParts[I].NamePart^.Index) >
     605        Abs(J - OldNameParts[J].NamePart^.Index) then begin
     606          OldNameParts[I].NamePart^.PartKind := npNone;
     607          OldNameParts[I].NamePart^.NamePart := nil;
     608          OldNameParts[I].NamePart := nil;
     609        end else begin
     610          OldNameParts[J].NamePart^.PartKind := npNone;
     611          OldNameParts[J].NamePart^.NamePart := nil;
     612          OldNameParts[J].NamePart := nil;
     613        end;
     614      end;
     615  end;
     616
     617  // Match existing parts backqards
     618  for I := Length(OldNameParts) - 1 downto 0 do begin
     619    if (OldNameParts[I].Text <> '') and not Assigned(OldNameParts[I].NamePart) then
     620      SearchPartBackward(NewNameParts, OldNameParts[I]);
     621  end;
     622
     623  // Match uncertain parts
     624  for I := 0 to Length(OldNameParts) - 1 do
     625    if not Assigned(OldNameParts[I].NamePart) then begin
     626      if Assigned(OldNameParts[I].Next) and
     627      Assigned(OldNameParts[I].Next^.NamePart) and
     628      Assigned(OldNameParts[I].Next^.NamePart^.Previous) and
     629      (OldNameParts[I].Next^.NamePart^.Previous^.PartKind = npNone) then begin
     630        OldNameParts[I].NamePart := OldNameParts[I].Next^.NamePart^.Previous;
     631        OldNameParts[I].Next^.NamePart^.Previous^.NamePart := @OldNameParts[I];
     632        OldNameParts[I].Next^.NamePart^.Previous^.PartKind := OldNameParts[I].PartKind;
     633      end else
     634      if Assigned(OldNameParts[I].Previous) and
     635      Assigned(OldNameParts[I].Previous^.NamePart) and
     636      Assigned(OldNameParts[I].Previous^.NamePart^.Next) and
     637      (OldNameParts[I].Previous^.NamePart^.Next^.PartKind = npNone) then begin
     638        OldNameParts[I].NamePart := OldNameParts[I].Previous^.NamePart^.Next;
     639        OldNameParts[I].Previous^.NamePart^.Next^.NamePart := @OldNameParts[I];
     640        OldNameParts[I].Previous^.NamePart^.Next^.PartKind := OldNameParts[I].PartKind;
     641      end;
     642    end;
     643
     644  // Mark new unknown parts according existing parts
     645  for I := Length(Parts) - 1 downto 0 do
     646    if (NewNameParts[I].PartKind = npNone) and
     647    Assigned(NewNameParts[I].Next) and
     648    (NewNameParts[I].Next^.PartKind <> npNone) then begin
     649      if (NewNameParts[I].Next^.PartKind = npFirst) and
     650      EndsWith(NewNameParts[I].Text, '.') then begin
     651        NewNameParts[I].PartKind := npPrefix;
     652      end else NewNameParts[I].PartKind := NewNameParts[I].Next^.PartKind;
     653    end;
     654
     655  // Mark unknown parts according to neighbouring parts
     656  for I := 0 to Length(Parts) - 1 do
     657    if (NewNameParts[I].PartKind = npNone) and
     658    Assigned(NewNameParts[I].Previous) and
     659    (NewNameParts[I].Previous^.PartKind <> npNone) then begin
     660      if (NewNameParts[I].Previous^.PartKind in [npLast, npMiddle]) and
     661      IsSuffix(NewNameParts[I].Text) then begin
     662        NewNameParts[I].PartKind := npSuffix;
     663      end else
     664      if (NewNameParts[I].Previous^.PartKind = npFirst) and
     665      (Last = '') then begin
     666        NewNameParts[I].PartKind := npLast;
     667      end else
     668      if (NewNameParts[I].Previous^.PartKind = npLast) and
     669      (Middle = '') then begin
     670        NewNameParts[I].PartKind := npLast;
     671        NewNameParts[I].Previous^.PartKind := npMiddle;
     672      end else
     673      if (NewNameParts[I].Previous^.PartKind = npPrefix) then begin
     674        NewNameParts[I].PartKind := npFirst;
     675      end else
     676        NewNameParts[I].PartKind := NewNameParts[I].Previous^.PartKind;
     677    end;
     678
     679  // Mark remaining unknown parts based on defined filling sequence
     680  NextKind := npFirst;
     681  for I := 0 to Length(Parts) - 1 do
     682    if NewNameParts[I].PartKind = npNone then begin
     683      if EndsWith(NewNameParts[I].Text, '.') and (NextKind = npFirst) then begin
     684        NewNameParts[I].PartKind := npPrefix;
     685      end else
     686      if (NextKind = npMiddle) and IsSuffix(NewNameParts[I].Text) then begin
     687        NewNameParts[I].PartKind := npSuffix;
     688        NextKind := npSuffix;
     689      end else
     690      if NextKind = npMiddle then begin
     691        NewNameParts[I].Previous^.PartKind := npMiddle;
     692        NewNameParts[I].PartKind := npLast;
     693      end else begin
     694        NewNameParts[I].PartKind := NextKind;
     695        if NextKind = npFirst then NextKind := npLast
     696        else if NextKind = npLast then NextKind := npMiddle;
     697      end;
     698    end;
     699
     700  // Combine multiple parts to base parts
     701  Prefix := '';
     702  First := '';
     703  Middle := '';
     704  Last := '';
     705  Suffix := '';
     706  for I := 0 to Length(Parts) - 1 do
     707    case NewNameParts[I].PartKind of
     708      npPrefix: Prefix := Trim(Prefix + ' ' + Parts[I]);
     709      npFirst: First := Trim(First + ' ' + Parts[I]);
     710      npMiddle: Middle := Trim(Middle + ' ' + Parts[I]);
     711      npLast: Last := Trim(Last + ' ' + Parts[I]);
     712      npSuffix: Suffix := Trim(Suffix + ' ' + Parts[I]);
     713    end;
     714
     715{
     716  // Title Prefix
     717  while (Length(Parts) > 0) and EndsWith(Parts[0], '.') do begin
     718    Prefix := Trim(Prefix + ' ' + Parts[0]);
     719    Delete(Parts, 0, 1);
     720  end;
     721
     722  // Title Suffix
     723  if ProcessAfter then
     724  for I := 0 to High(Parts) do
     725    if (Pos('.', Parts[I]) > 0) or IsNumber(Parts[I]) or IsRomanNumber(Parts[I]) then begin
     726      for J := I to High(Parts) do
     727        Suffix := Trim(Suffix + ' ' + Parts[J]);
     728      SetLength(Parts, I);
     729      Break;
     730    end;
     731
     732  if Length(Parts) = 0 then begin
     733  end else
     734  if Length(Parts) = 1 then begin
     735    First := Parts[0];
     736  end else
     737  if Length(Parts) = 2 then begin
     738    First := Parts[0];
     739    Last := Parts[1];
     740  end else begin
     741    First := Parts[0];
     742    for I := 0 to Length(Parts) - 3 do
     743      Middle := Trim(Middle + ' ' + Parts[I + 1]);
     744    Last := Parts[High(Parts)];
     745  end;}
     746end;
     747
     748function TNameDetails.GetCombined: string;
     749begin
     750  Result := '';
     751  if Prefix <> '' then Result := Result + ' ' + Prefix;
     752  if First <> '' then Result := Result + ' ' + First;
     753  if Middle <> '' then Result := Result + ' ' + Middle;
     754  if Last <> '' then Result := Result + ' ' + Last;
     755  if Suffix <> '' then Result := Result + ' ' + Suffix;
     756  Result := Trim(Result);
    410757end;
    411758
     
    11191466end;
    11201467
    1121 function IsNumber(Text: string): Boolean;
    1122 var
    1123   Value: Integer;
    1124 begin
    1125   Result := TryStrToInt(Text, Value);
    1126 end;
    1127 
    1128 function IsRomanNumber(Text: string): Boolean;
    1129 var
    1130   I: Integer;
    1131 begin
    1132   Result := True;
    1133   for I := 1 to Length(Text) do
    1134     if not (Text[I] in ['I', 'V', 'X', 'L', 'C', 'D', 'M']) then begin
    1135       Result := False;
    1136       Break;
    1137     end;
    1138 end;
    1139 
    1140 procedure TContact.FullNameToNameParts(FullName: string; out Before, First,
    1141   Middle, Last, After: string);
    1142 var
    1143   Parts: TStringArray;
    1144   I, J: Integer;
    1145 begin
    1146   Before := '';
    1147   First := '';
    1148   Middle := '';
    1149   Last := '';
    1150   After := '';
    1151   while Pos('  ', FullName) > 0 do
    1152     FullName := StringReplace(FullName, '  ', ' ', [rfReplaceAll]);
    1153   Parts := Explode(' ', Trim(FullName));
    1154 
    1155   // Title before
    1156   while (Length(Parts) > 0) and EndsWith(Parts[0], '.') do begin
    1157     Before := Trim(Before + ' ' + Parts[0]);
    1158     Delete(Parts, 0, 1);
    1159   end;
    1160 
    1161   // Title after
    1162   for I := 0 to High(Parts) do
    1163     if (Pos('.', Parts[I]) > 0) or IsNumber(Parts[I]) or IsRomanNumber(Parts[I]) then begin
    1164       for J := I to High(Parts) do
    1165         After := Trim(After + ' ' + Parts[J]);
    1166       SetLength(Parts, I);
    1167       Break;
    1168     end;
    1169 
    1170   if Length(Parts) = 0 then begin
    1171   end else
    1172   if Length(Parts) = 1 then begin
    1173     First := Parts[0];
    1174   end else
    1175   if Length(Parts) = 2 then begin
    1176     First := Parts[0];
    1177     Last := Parts[1];
    1178   end else begin
    1179     First := Parts[0];
    1180     for I := 0 to Length(Parts) - 3 do
    1181       Middle := Trim(Middle + ' ' + Parts[I + 1]);
    1182     Last := Parts[High(Parts)];
    1183   end;
    1184 end;
    1185 
    1186 function TContact.NamePartsToFullName(Before, First, Middle, Last, After: string
    1187   ): string;
    1188 begin
    1189   Result := '';
    1190   if Before <> '' then Result := Result + ' ' + Before;
    1191   if First <> '' then Result := Result + ' ' + First;
    1192   if Middle <> '' then Result := Result + ' ' + Middle;
    1193   if Last <> '' then Result := Result + ' ' + Last;
    1194   if After <> '' then Result := Result + ' ' + After;
    1195   Result := Trim(Result);
    1196 end;
    1197 
    11981468procedure TContact.Assign(Source: TContact);
    11991469begin
Note: See TracChangeset for help on using the changeset viewer.