1 /*
2  *  "GEDKeeper", the personal genealogical database editor.
3  *  Copyright (C) 2009-2020 by Sergey V. Zhdanovskih.
4  *
5  *  This file is part of "GEDKeeper".
6  *
7  *  This program is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 using System;
22 using BSLib;
23 using BSLib.DataViz.SmartGraph;
24 using BSLib.Design.Graphics;
25 using GDModel;
26 using GKCore.Import;
27 using GKCore.Options;
28 using GKCore.Types;
29 
30 using BSDColors = BSLib.Design.BSDConsts.Colors;
31 
32 namespace GKCore.Charts
33 {
34     /// <summary>
35     ///
36     /// </summary>
37     public class PersonModifyEventArgs : EventArgs
38     {
39         public TreeChartPerson Person { get; set; }
40 
PersonModifyEventArgs(TreeChartPerson person)41         public PersonModifyEventArgs(TreeChartPerson person)
42         {
43             Person = person;
44         }
45     }
46 
47     public enum PersonFlag
48     {
49         pfDivorced, pfIsDead, pfSelected, pfIsDup, pfSpouse,
50         pfDescByFather, pfDescByMother, // descending flags
51         pfAncWalk, pfDescWalk, // walk flags
52         pfHasInvAnc, pfHasInvDesc, // invisible flags
53         pfSpecialMark, // debug flag for special goals
54         pfOutsideKin, pfCanExpand, pfAdopted,
55     }
56 
57     /// <summary>
58     ///
59     /// </summary>
60     public class TreeChartPerson : BaseObject
61     {
62         private readonly TreeChartModel fModel;
63 
64         private string fBirthDate;
65         private string fBirthPlace;
66         private string fDeathDate;
67         private string fDeathPlace;
68         private EnumSet<PersonFlag> fFlags;
69         private string fName;
70         private string fNick;
71         private string fPatronymic;
72         private string fSurname;
73         private GDMIndividualRecord fRec;
74         private EnumSet<SpecialUserRef> fSigns;
75         private GDMSex fSex;
76         private PersonList fChilds;
77         private PersonList fSpouses;
78         private IImage fPortrait;
79         private int fPortraitWidth;
80 
81         // for rendering
82         private int fHeight;
83         private int fPtX;
84         private int fPtY;
85         private int fWidth;
86 
87         // without property controlling
88         public GDMFamilyRecord BaseFamily;
89         public TreeChartPerson BaseSpouse;
90         public float CertaintyAssessment;
91         public TreeChartPerson Father;
92         public TreeChartPerson Mother;
93         public TreeChartPerson Parent;
94         public int Generation;
95         public string Kinship;
96         public string[] Lines;
97         public Vertex Node;
98         public string PathDebug;
99         public ExtRect PortraitArea;
100         public string MarriageDate;
101         public bool IsCollapsed;
102         public bool IsVisible;
103         public IColor UserColor;
104         public int NameLines;
105 
106 
107         public int Height
108         {
109             get { return fHeight; }
110         }
111 
112         public bool IsDup
113         {
114             get {
115                 return fFlags.Contains(PersonFlag.pfIsDup);
116             }
117             set {
118                 if (value) {
119                     fFlags.Include(PersonFlag.pfIsDup);
120                 } else {
121                     fFlags.Exclude(PersonFlag.pfIsDup);
122                 }
123             }
124         }
125 
126         public IImage Portrait
127         {
128             get { return fPortrait; }
129         }
130 
131         public int PortraitWidth
132         {
133             get { return fPortraitWidth; }
134         }
135 
136         public int PtX
137         {
138             get { return fPtX; }
139             set { fPtX = value; }
140         }
141 
142         public int PtY
143         {
144             get { return fPtY; }
145             set { fPtY = value; }
146         }
147 
148         public GDMIndividualRecord Rec
149         {
150             get { return fRec; }
151         }
152 
153         public ExtRect Rect
154         {
155             get {
156                 ExtRect result;
157                 result.Left = fPtX - fWidth / 2;
158                 result.Right = result.Left + fWidth - 1;
159                 result.Top = fPtY;
160                 result.Bottom = result.Top + fHeight - 1;
161                 return result;
162             }
163         }
164 
165         public bool Selected
166         {
167             get {
168                 return fFlags.Contains(PersonFlag.pfSelected);
169             }
170             set {
171                 if (value) {
172                     fFlags.Include(PersonFlag.pfSelected);
173                 } else {
174                     fFlags.Exclude(PersonFlag.pfSelected);
175                 }
176             }
177         }
178 
179         public GDMSex Sex
180         {
181             get { return fSex; }
182             set { fSex = value; }
183         }
184 
185         public EnumSet<SpecialUserRef> Signs
186         {
187             get { return fSigns; }
188         }
189 
190         public int Width
191         {
192             get { return fWidth; }
193         }
194 
195 
TreeChartPerson(TreeChartModel model)196         public TreeChartPerson(TreeChartModel model)
197         {
198             fModel = model;
199             fFlags = EnumSet<PersonFlag>.Create();
200             fPortrait = null;
201             fSpouses = null;
202             fChilds = null;
203             UserColor = null;
204         }
205 
Dispose(bool disposing)206         protected override void Dispose(bool disposing)
207         {
208             if (disposing) {
209                 // don't dispose portrait - he's from cache!
210                 //if (fPortrait != null) fPortrait.Dispose();
211 
212                 if (fChilds != null) fChilds.Dispose();
213                 if (fSpouses != null) fSpouses.Dispose();
214             }
215             base.Dispose(disposing);
216         }
217 
HasFlag(PersonFlag flag)218         public bool HasFlag(PersonFlag flag)
219         {
220             return fFlags.Contains(flag);
221         }
222 
SetFlag(PersonFlag flag)223         public void SetFlag(PersonFlag flag)
224         {
225             fFlags.Include(flag);
226         }
227 
SetFlag(PersonFlag flag, bool value)228         public void SetFlag(PersonFlag flag, bool value)
229         {
230             if (value) {
231                 fFlags.Include(flag);
232             } else {
233                 fFlags.Exclude(flag);
234             }
235         }
236 
GetChild(int index)237         public TreeChartPerson GetChild(int index)
238         {
239             TreeChartPerson result = ((fChilds == null) ? null : fChilds[index]);
240             return result;
241         }
242 
GetChildsCount()243         public int GetChildsCount()
244         {
245             int result = ((fChilds == null) ? 0 : fChilds.Count);
246             return result;
247         }
248 
GetSpouse(int index)249         public TreeChartPerson GetSpouse(int index)
250         {
251             TreeChartPerson result = ((fSpouses == null) ? null : fSpouses[index]);
252             return result;
253         }
254 
GetSpousesCount()255         public int GetSpousesCount()
256         {
257             int result = ((fSpouses == null) ? 0 : fSpouses.Count);
258             return result;
259         }
260 
AddChild(TreeChartPerson child)261         public void AddChild(TreeChartPerson child)
262         {
263             if (child == null) return;
264 
265             if (fChilds == null) fChilds = new PersonList(false);
266 
267             fChilds.Add(child);
268         }
269 
AddSpouse(TreeChartPerson spouse)270         public void AddSpouse(TreeChartPerson spouse)
271         {
272             if (spouse == null) return;
273 
274             if (fSpouses == null) fSpouses = new PersonList(false);
275 
276             fSpouses.Add(spouse);
277         }
278 
BuildBy(GDMIndividualRecord iRec)279         public void BuildBy(GDMIndividualRecord iRec)
280         {
281             try {
282                 fRec = iRec;
283 
284                 if (iRec != null) {
285                     if (fModel.PreparedIndividuals.IndexOf(iRec.XRef) < 0) {
286                         fModel.PreparedIndividuals.Add(iRec.XRef);
287                     }
288 
289                     var parts = GKUtils.GetNameParts(fModel.Base.Context.Tree, iRec);
290                     fSurname = parts.Surname;
291                     fName = parts.Name;
292                     fPatronymic = parts.Patronymic;
293                     fNick = GKUtils.GetNickString(iRec);
294                     fSex = iRec.Sex;
295 
296                     TreeChartOptions options = fModel.Options;
297 
298                     var lifeDates = iRec.GetLifeDates();
299                     DateFormat dateFormat = (options.OnlyYears) ? DateFormat.dfYYYY : DateFormat.dfDD_MM_YYYY;
300 
301                     SetFlag(PersonFlag.pfIsDead, (lifeDates.DeathEvent != null));
302                     GlobalOptions glob = GlobalOptions.Instance;
303                     fBirthDate = GKUtils.GEDCOMEventToDateStr(lifeDates.BirthEvent, dateFormat, glob.ShowDatesSign);
304                     fDeathDate = GKUtils.GEDCOMEventToDateStr(lifeDates.DeathEvent, dateFormat, glob.ShowDatesSign);
305 
306                     if (!options.OnlyYears) {
307                         if (options.ShowPlaces) {
308                             fBirthPlace = GKUtils.GetPlaceStr(lifeDates.BirthEvent, false);
309                             if (!string.IsNullOrEmpty(fBirthPlace) && !options.SeparateDatesAndPlacesLines) {
310                                 if (!string.IsNullOrEmpty(fBirthDate)) {
311                                     fBirthDate += ", ";
312                                 }
313                                 fBirthDate += fBirthPlace;
314                             }
315 
316                             fDeathPlace = GKUtils.GetPlaceStr(lifeDates.DeathEvent, false);
317                             if (!string.IsNullOrEmpty(fDeathPlace) && !options.SeparateDatesAndPlacesLines) {
318                                 if (!string.IsNullOrEmpty(fDeathDate)) {
319                                     fDeathDate += ", ";
320                                 }
321                                 fDeathDate += fDeathPlace;
322                             }
323                         }
324 
325                         if (!string.IsNullOrEmpty(fBirthDate)) {
326                             fBirthDate = ImportUtils.STD_BIRTH_SIGN + " " + fBirthDate;
327                         }
328                         if (!string.IsNullOrEmpty(fDeathDate)) {
329                             fDeathDate = ImportUtils.STD_DEATH_SIGN + " " + fDeathDate;
330                         }
331                     }
332 
333                     fSigns = EnumSet<SpecialUserRef>.Create();
334                     if (options.SignsVisible && fRec.HasUserReferences) {
335                         int num = fRec.UserReferences.Count;
336                         for (int i = 0; i < num; i++) {
337                             string rs = fRec.UserReferences[i].StringValue;
338 
339                             for (var cps = SpecialUserRef.urRI_StGeorgeCross; cps <= SpecialUserRef.urLast; cps++) {
340                                 string sur = LangMan.LS(GKData.SpecialUserRefs[(int)cps].Title);
341                                 if (rs == sur) {
342                                     fSigns.Include(cps);
343                                 }
344                             }
345                         }
346                     }
347 
348                     if (options.PortraitsVisible) {
349                         try {
350                             fPortrait = PortraitsCache.Instance.GetImage(fModel.Base.Context, iRec);
351 
352                             if (fPortrait == null && options.DefaultPortraits) {
353                                 string resName = (fSex == GDMSex.svFemale) ? "pi_female_140.png" : "pi_male_140.png";
354                                 fPortrait = AppHost.GfxProvider.LoadResourceImage(resName, false);
355                             }
356                         } catch (MediaFileNotFoundException) {
357                             if (!fModel.HasMediaFail) {
358                                 AppHost.StdDialogs.ShowError(LangMan.LS(LSID.LSID_MediaFileNotLoaded));
359                                 fModel.HasMediaFail = true;
360                             }
361                         }
362                     }
363 
364                     CertaintyAssessment = iRec.GetCertaintyAssessment();
365                 } else {
366                     fSurname = "";
367                     fName = "< ? >";
368                     fPatronymic = "";
369                     fNick = "";
370                     fBirthDate = "";
371                     fBirthPlace = "";
372                     fDeathDate = "";
373                     fDeathPlace = "";
374                     SetFlag(PersonFlag.pfIsDead, false);
375                     fSex = GDMSex.svUnknown;
376                     fSigns = EnumSet<SpecialUserRef>.Create();
377 
378                     CertaintyAssessment = 0.0f;
379                 }
380             } catch (Exception ex) {
381                 Logger.WriteError("TreeChartPerson.BuildBy()", ex);
382                 throw;
383             }
384         }
385 
InitInfo(int lines)386         private void InitInfo(int lines)
387         {
388             Lines = new string[lines];
389 
390             try {
391                 TreeChartOptions options = fModel.Options;
392 
393                 // prepare
394                 string nameLine = fName;
395                 if (options.NickVisible && !string.IsNullOrEmpty(fNick)) {
396                     nameLine += " \"" + fNick + "\"";
397                 }
398 
399                 NameLines = 0;
400 
401                 // create lines
402                 int idx = 0;
403 
404                 if (options.FamilyVisible) {
405                     Lines[idx] = fSurname;
406                     NameLines++;
407                     idx++;
408                 }
409 
410                 if (!options.DiffLines) {
411                     Lines[idx] = nameLine + " " + fPatronymic; // attention: "Name" is combined property
412                     NameLines++;
413                     idx++;
414                 } else {
415                     Lines[idx] = nameLine;
416                     NameLines++;
417                     idx++;
418 
419                     Lines[idx] = fPatronymic;
420                     NameLines++;
421                     idx++;
422                 }
423 
424                 if (!options.OnlyYears) {
425                     if (options.BirthDateVisible) {
426                         Lines[idx] = fBirthDate;
427                         idx++;
428 
429                         if (options.SeparateDatesAndPlacesLines) {
430                             Lines[idx] = fBirthPlace;
431                             idx++;
432                         }
433                     }
434 
435                     if (options.DeathDateVisible) {
436                         Lines[idx] = fDeathDate;
437                         idx++;
438 
439                         if (options.SeparateDatesAndPlacesLines) {
440                             Lines[idx] = fDeathPlace;
441                             idx++;
442                         }
443                     }
444                 } else {
445                     string lifeYears = "[ ";
446                     lifeYears += (fBirthDate == "") ? "?" : fBirthDate;
447                     if (HasFlag(PersonFlag.pfIsDead)) {
448                         lifeYears += (fDeathDate == "") ? " - ?" : " - " + fDeathDate;
449                     }
450                     lifeYears += " ]";
451 
452                     Lines[idx] = lifeYears;
453                     idx++;
454                 }
455 
456                 if (options.Kinship) {
457                     Lines[idx] = Kinship;
458                     idx++;
459                 }
460 
461                 if (fModel.PathDebug) {
462                     Lines[idx] = PathDebug;
463                     //idx++;
464                 }
465             } catch (Exception ex) {
466                 Logger.WriteError("TreeChartPerson.InitInfo()", ex);
467             }
468         }
469 
DefineExpands()470         private void DefineExpands()
471         {
472             if (fFlags.Contains(PersonFlag.pfAncWalk) && fFlags.Contains(PersonFlag.pfDescWalk)
473                 && fFlags.Contains(PersonFlag.pfHasInvDesc))
474             {
475                 // it's hack
476                 fFlags.Exclude(PersonFlag.pfHasInvDesc);
477             }
478 
479             SetFlag(PersonFlag.pfCanExpand, fFlags.Contains(PersonFlag.pfHasInvAnc) || fFlags.Contains(PersonFlag.pfHasInvDesc));
480         }
481 
CalcBounds(int lines, ChartRenderer renderer)482         public void CalcBounds(int lines, ChartRenderer renderer)
483         {
484             try {
485                 TreeChartOptions options = fModel.Options;
486 
487                 InitInfo(lines);
488                 DefineExpands();
489 
490                 int bh = renderer.GetTextHeight(fModel.BoldFont);
491                 int th = renderer.GetTextHeight(fModel.DrawFont);
492 
493                 int maxwid = 0;
494                 int height = 0;
495                 for (int k = 0; k < lines; k++) {
496                     IFont font;
497                     if (options.BoldNames && k < NameLines) {
498                         height += bh;
499                         font = fModel.BoldFont;
500                     } else {
501                         height += th;
502                         font = fModel.DrawFont;
503                     }
504 
505                     int wt = renderer.GetTextWidth(Lines[k], font);
506                     if (maxwid < wt) maxwid = wt;
507                 }
508 
509                 int pad2side = (fModel.NodePadding * 2);
510 
511                 fWidth = pad2side + maxwid;
512                 fHeight = pad2side + height;
513 
514                 if (fPortrait != null) {
515                     ExtRect portRt = ExtRect.Create(0, 0, fHeight - 1, fHeight - 1);
516                     portRt.Inflate(-3, -3);
517 
518                     int rtW = portRt.GetWidth();
519                     int rtH = portRt.GetHeight();
520                     int imgW = fPortrait.Width;
521                     int imgH = fPortrait.Height;
522                     float ratio = GfxHelper.ZoomToFit(imgW, imgH, rtW, rtH);
523                     imgW = (int)Math.Round(imgW * ratio);
524                     imgH = (int)Math.Round(imgH * ratio);
525 
526                     PortraitArea = ExtRect.CreateBounds(portRt.Left, portRt.Top, imgW, imgH);
527                     fPortraitWidth = imgW;
528 
529                     fWidth += imgW;
530                 }
531             } catch (Exception ex) {
532                 Logger.WriteError("TreeChartPerson.CalcBounds()", ex);
533             }
534         }
535 
GetSelectedColor()536         public IColor GetSelectedColor()
537         {
538             int result;
539 
540             switch (fSex) {
541                 case GDMSex.svMale:
542                     result = BSDColors.Blue;
543                     break;
544 
545                 case GDMSex.svFemale:
546                     result = BSDColors.Red;
547                     break;
548 
549                 default:
550                     result = BSDColors.Black;
551                     break;
552             }
553 
554             return ChartRenderer.GetColor(result);
555         }
556 
GetFillColor(bool dead)557         public IColor GetFillColor(bool dead)
558         {
559             if (fFlags.Contains(PersonFlag.pfSpecialMark)) {
560                 return ChartRenderer.GetColor(BSDColors.Khaki);
561             }
562 
563             if (dead) {
564                 return ChartRenderer.GetColor(BSDColors.Black);
565             }
566 
567             if (IsDup) {
568                 return ChartRenderer.GetColor(BSDColors.Silver);
569             }
570 
571             if (UserColor != null) {
572                 return UserColor;
573             }
574 
575             bool divorced = HasFlag(PersonFlag.pfDivorced);
576 
577             IColor result;
578             TreeChartOptions options = fModel.Options;
579             switch (fSex) {
580                 case GDMSex.svMale:
581                     result = divorced ? options.UnHusbandColor : options.MaleColor;
582                     break;
583 
584                 case GDMSex.svFemale:
585                     result = divorced ? options.UnWifeColor : options.FemaleColor;
586                     break;
587 
588                 default:
589                     result = options.UnkSexColor;
590                     break;
591             }
592             return result;
593         }
594     }
595 }
596