1 /*
2  *  "GEDKeeper", the personal genealogical database editor.
3  *  Copyright (C) 2009-2021 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 System.Collections.Generic;
23 using System.Text.RegularExpressions;
24 using BSLib;
25 using BSLib.Design.Graphics;
26 using GDModel;
27 using GDModel.Providers.GEDCOM;
28 using GKCore.Interfaces;
29 using GKCore.Kinships;
30 using GKCore.Options;
31 using GKCore.Search;
32 using GKCore.Types;
33 
34 using BSDColors = BSLib.Design.BSDConsts.Colors;
35 
36 namespace GKCore.Charts
37 {
PersonModifyEventHandler(object sender, PersonModifyEventArgs eArgs)38     public delegate void PersonModifyEventHandler(object sender, PersonModifyEventArgs eArgs);
39 
RootChangedEventHandler(object sender, TreeChartPerson person)40     public delegate void RootChangedEventHandler(object sender, TreeChartPerson person);
41 
InfoRequestEventHandler(object sender, TreeChartPerson person)42     public delegate void InfoRequestEventHandler(object sender, TreeChartPerson person);
43 
44     /// <summary>
45     ///
46     /// </summary>
47     public class PersonList : ExtList<TreeChartPerson>
48     {
PersonList(bool ownsObjects)49         public PersonList(bool ownsObjects) : base(ownsObjects)
50         {
51         }
52     }
53 
54     public enum TreeChartKind
55     {
56         ckAncestors,
57         ckDescendants,
58         ckBoth
59     }
60 
61     /// <summary>
62     ///
63     /// </summary>
64     public class TreeChartModel : ChartModel
65     {
66         public const int DEF_MARGINS = 24;
67         public const int DEF_SPOUSE_DISTANCE = 10;
68         public const int DEF_BRANCH_DISTANCE = 40;
69         public const int DEF_LEVEL_DISTANCE = 46;
70         public const float HIGHLIGHTED_VAL = 0.1f;
71 
72         // Specifies the interior spacing of a node.
73         public const int DEF_PERSON_NODE_PADDING = 10;
74 
75         private readonly ChartFilter fFilter;
76         private readonly PersonList fPersons;
77         private readonly IList<string> fPreparedFamilies;
78         private readonly IList<string> fPreparedIndividuals;
79 
80         private IBaseWindow fBase;
81         private IFont fBoldFont;
82         private int fBranchDistance;
83         private bool fCertaintyIndex;
84         private IPen fDecorativeLinePen;
85         private int fDepthLimitAncestors;
86         private int fDepthLimitDescendants;
87         private IPen fDottedLinePen;
88         private IPen fDottedDecorativeLinePen;
89         private IFont fDrawFont;
90         private int[] fEdges;
91         private IImage fExpPic;
92         private IImage fInfoPic;
93         private IImage fPersExpPic;
94         private GKVarCache<GDMIndividualRecord, bool> fFilterData;
95         private KinshipsGraph fGraph;
96         private bool fHasMediaFail;
97         private TreeChartPerson fHighlightedPerson;
98         private TreeChartKind fKind;
99         private TreeChartPerson fKinRoot;
100         private ITreeLayout fLayout;
101         private int fLevelDistance;
102         private IPen fLinePen;
103         private int fMargins;
104         private int fNodePadding;
105         private TreeChartOptions fOptions;
106         private bool fPathDebug;
107         private TreeChartPerson fRoot;
108         private float fScale;
109         private IImage[] fSignsPic;
110         private IBrush fSolidBlack;
111         private int fSpouseDistance;
112         private GDMTree fTree;
113         private ExtRect fTreeBounds;
114         private ExtRect fVisibleArea;
115 
116 
117         public IBaseWindow Base
118         {
119             get { return fBase; }
120             set {
121                 fBase = value;
122                 fGraph = new KinshipsGraph(fBase.Context);
123                 fTree = fBase.Context.Tree;
124             }
125         }
126 
127         public IFont BoldFont
128         {
129             get { return fBoldFont; }
130             set { fBoldFont = value; }
131         }
132 
133         public int BranchDistance
134         {
135             get { return fBranchDistance; }
136             set { fBranchDistance = value; }
137         }
138 
139         public bool CertaintyIndex
140         {
141             get { return fCertaintyIndex; }
142             set { fCertaintyIndex = value; }
143         }
144 
145         public int DepthLimitAncestors
146         {
147             get { return fDepthLimitAncestors; }
148             set { fDepthLimitAncestors = value; }
149         }
150 
151         public int DepthLimitDescendants
152         {
153             get { return fDepthLimitDescendants; }
154             set { fDepthLimitDescendants = value; }
155         }
156 
157         public IFont DrawFont
158         {
159             get { return fDrawFont; }
160             set { fDrawFont = value; }
161         }
162 
163         public ChartFilter Filter
164         {
165             get { return fFilter; }
166         }
167 
168         internal bool HasMediaFail
169         {
170             get { return fHasMediaFail; }
171             set { fHasMediaFail = value; }
172         }
173 
174         public TreeChartPerson HighlightedPerson
175         {
176             get { return fHighlightedPerson; }
177             set { fHighlightedPerson = value; }
178         }
179 
180         public TreeChartKind Kind
181         {
182             get { return fKind; }
183             set { fKind = value; }
184         }
185 
186         public TreeChartPerson KinRoot
187         {
188             get { return fKinRoot; }
189             set { fKinRoot = value; }
190         }
191 
192         public ITreeLayout Layout
193         {
194             get { return fLayout; }
195             set { fLayout = value; }
196         }
197 
198         public int Margins
199         {
200             get { return fMargins; }
201             set { fMargins = value; }
202         }
203 
204         public int NodePadding
205         {
206             get { return fNodePadding; }
207         }
208 
209         public TreeChartOptions Options
210         {
211             get { return fOptions; }
212             set { fOptions = value; }
213         }
214 
215         public bool PathDebug
216         {
217             get { return fPathDebug; }
218             set { fPathDebug = value; }
219         }
220 
221         public PersonList Persons
222         {
223             get { return fPersons; }
224         }
225 
226         public IList<string> PreparedIndividuals
227         {
228             get { return fPreparedIndividuals; }
229         }
230 
231         public TreeChartPerson Root
232         {
233             get { return fRoot; }
234         }
235 
236         public float Scale
237         {
238             get {
239                 return fScale;
240             }
241             set {
242                 if (value < 0.5f) value = 0.5f;
243                 if (value > 1.5f) value = 1.5f;
244 
245                 fScale = value;
246             }
247         }
248 
249         public ExtRect VisibleArea
250         {
251             get { return fVisibleArea; }
252             set { fVisibleArea = value; }
253         }
254 
255 
TreeChartModel()256         public TreeChartModel()
257         {
258             fDepthLimitAncestors = -1;
259             fDepthLimitDescendants = -1;
260             fEdges = new int[256];
261             fFilter = new ChartFilter();
262             fFilterData = new GKVarCache<GDMIndividualRecord, bool>();
263             fGraph = null;
264             fPersons = new PersonList(true);
265             fPreparedFamilies = new List<string>();
266             fPreparedIndividuals = new List<string>();
267             fScale = 1.0f;
268 
269             fBranchDistance = DEF_BRANCH_DISTANCE;
270             fLevelDistance = DEF_LEVEL_DISTANCE;
271             fSpouseDistance = DEF_SPOUSE_DISTANCE;
272             fMargins = DEF_MARGINS;
273         }
274 
Dispose(bool disposing)275         protected override void Dispose(bool disposing)
276         {
277             if (disposing) {
278                 if (fGraph != null) fGraph.Dispose();
279                 fFilter.Dispose();
280                 fPersons.Dispose();
281 
282                 DoneGraphics();
283                 DoneSigns();
284                 if (fBoldFont != null) fBoldFont.Dispose();
285                 if (fDrawFont != null) fDrawFont.Dispose();
286             }
287             base.Dispose(disposing);
288         }
289 
PrepareImage(string name, bool makeTransp)290         private static IImage PrepareImage(string name, bool makeTransp)
291         {
292             if (name == null) return null;
293 
294             try {
295                 var result = AppHost.GfxProvider.LoadResourceImage(name, makeTransp);
296                 return result;
297             } catch (Exception ex) {
298                 Logger.WriteError("TreeChartModel.PrepareImage()", ex);
299                 return null;
300             }
301         }
302 
InitSigns()303         private void InitSigns()
304         {
305             try {
306                 var signsPic = new IImage[9];
307                 signsPic[0] = PrepareImage("tg_george_cross.gif", true);
308                 signsPic[1] = PrepareImage("tg_soldier.gif", true);
309                 signsPic[2] = PrepareImage("tg_soldier_fall.gif", true);
310                 signsPic[3] = PrepareImage("tg_veteran_rear.gif", true);
311                 signsPic[4] = PrepareImage("tg_barbed_wire.gif", true);
312                 signsPic[5] = PrepareImage("tg_islam_sym.gif", false);
313                 signsPic[6] = PrepareImage("tg_latin_cross.gif", false);
314                 signsPic[7] = PrepareImage("tg_orthodox_cross.gif", false);
315                 signsPic[8] = PrepareImage("tg_oldritual_cross.gif", false);
316                 fSignsPic = signsPic;
317 
318                 fExpPic = PrepareImage("btn_expand.gif", true);
319                 fPersExpPic = PrepareImage("btn_expand2.gif", true);
320                 fInfoPic = PrepareImage("btn_info.gif", true);
321             } catch (Exception ex) {
322                 Logger.WriteError("TreeChartModel.InitSigns()", ex);
323             }
324         }
325 
DoneSigns()326         private void DoneSigns()
327         {
328             // dummy
329         }
330 
Assign(TreeChartModel sourceModel)331         public void Assign(TreeChartModel sourceModel)
332         {
333             Base = sourceModel.fBase;
334 
335             fBoldFont = sourceModel.fBoldFont;
336             fBranchDistance = sourceModel.fBranchDistance;
337             fCertaintyIndex = sourceModel.fCertaintyIndex;
338             fDecorativeLinePen = sourceModel.fDecorativeLinePen;
339             fDepthLimitAncestors = sourceModel.fDepthLimitAncestors;
340             fDepthLimitDescendants= sourceModel.fDepthLimitDescendants;
341             fDottedLinePen = sourceModel.fDottedLinePen;
342             fDottedDecorativeLinePen = sourceModel.fDottedDecorativeLinePen;
343             fDrawFont = sourceModel.fDrawFont;
344             fExpPic = sourceModel.fExpPic;
345             fPersExpPic = sourceModel.fPersExpPic;
346             fInfoPic = sourceModel.fInfoPic;
347             //fKind = sourceModel.fKind;
348             //fKinRoot = sourceModel.fKinRoot;
349             fLevelDistance = sourceModel.fLevelDistance;
350             fLinePen = sourceModel.fLinePen;
351             fMargins = sourceModel.fMargins;
352             fNodePadding = sourceModel.fNodePadding;
353             fOptions = sourceModel.fOptions;
354             //fRoot = sourceModel.fRoot;
355             fScale = sourceModel.fScale;
356             fSignsPic = sourceModel.fSignsPic;
357             fSolidBlack = sourceModel.fSolidBlack;
358             fSpouseDistance = sourceModel.fSpouseDistance;
359             fTree = sourceModel.fTree;
360         }
361 
GenChart(GDMIndividualRecord iRec, TreeChartKind kind, bool rootCenter)362         public void GenChart(GDMIndividualRecord iRec, TreeChartKind kind, bool rootCenter)
363         {
364             fKind = kind;
365             fPersons.Clear();
366             fGraph.Clear();
367             DoFilter(iRec);
368             fRoot = null;
369             fPreparedIndividuals.Clear();
370 
371             switch (fKind) {
372                 case TreeChartKind.ckAncestors:
373                     fPreparedFamilies.Clear();
374                     fRoot = DoAncestorsStep(null, iRec, 1, false);
375                     break;
376 
377                 case TreeChartKind.ckDescendants:
378                     fPreparedFamilies.Clear();
379                     fRoot = DoDescendantsStep(null, iRec, 1);
380                     break;
381 
382                 case TreeChartKind.ckBoth:
383                     fPreparedFamilies.Clear();
384                     fRoot = DoAncestorsStep(null, iRec, 1, false);
385                     fPreparedFamilies.Clear();
386                     DoDescendantsStep(null, iRec, 1);
387                     break;
388             }
389 
390             fKinRoot = fRoot;
391         }
392 
393         #region Tree walking
394 
AddDescPerson(TreeChartPerson parent, GDMIndividualRecord iRec, bool outsideKin, int generation)395         private TreeChartPerson AddDescPerson(TreeChartPerson parent, GDMIndividualRecord iRec, bool outsideKin, int generation)
396         {
397             try
398             {
399                 TreeChartPerson result;
400 
401                 if (fRoot != null && fRoot.Rec == iRec) {
402                     result = fRoot;
403                     result.Parent = parent;
404                 } else {
405                     result = CreatePerson(iRec, generation);
406                     result.Parent = parent;
407 
408                     if (!outsideKin && parent != null) {
409                         parent.AddChild(result);
410                     }
411                 }
412 
413                 result.SetFlag(PersonFlag.pfOutsideKin, outsideKin);
414                 result.SetFlag(PersonFlag.pfDescWalk);
415 
416                 return result;
417             }
418             catch (Exception ex)
419             {
420                 Logger.WriteError("TreeChartModel.AddDescPerson()", ex);
421                 throw;
422             }
423         }
424 
CreatePerson(GDMIndividualRecord iRec, int generation, bool prevSearch = false)425         private TreeChartPerson CreatePerson(GDMIndividualRecord iRec, int generation, bool prevSearch = false)
426         {
427             // search root or previous added ancestors
428             TreeChartPerson result = (!prevSearch) ? null : FindPersonByRec(iRec);
429 
430             if (result == null) {
431                 result = new TreeChartPerson(this);
432                 result.BuildBy(iRec);
433                 result.Generation = generation;
434                 fPersons.Add(result);
435 
436                 if (fOptions.Kinship && iRec != null) {
437                     result.Node = fGraph.AddIndividual(iRec);
438                 }
439             }
440 
441             return result;
442         }
443 
DoAncestorsStep(TreeChartPerson aChild, GDMIndividualRecord aPerson, int generation, bool dupFlag)444         private TreeChartPerson DoAncestorsStep(TreeChartPerson aChild, GDMIndividualRecord aPerson, int generation, bool dupFlag)
445         {
446             try {
447                 TreeChartPerson result = null;
448 
449                 if (aPerson != null) {
450                     result = CreatePerson(aPerson, generation);
451                     result.SetFlag(PersonFlag.pfAncWalk);
452 
453                     if (aChild != null) {
454                         result.AddChild(aChild);
455                     }
456 
457                     if ((fDepthLimitAncestors <= -1 || generation != fDepthLimitAncestors) && aPerson.ChildToFamilyLinks.Count > 0 && !dupFlag) {
458                         GDMChildToFamilyLink childLink = aPerson.ChildToFamilyLinks[0];
459                         result.SetFlag(PersonFlag.pfAdopted, (childLink.PedigreeLinkageType == GDMPedigreeLinkageType.plAdopted));
460                         GDMFamilyRecord family = fTree.GetPtrValue(childLink);
461 
462                         bool isDup = (fPreparedFamilies.IndexOf(family.XRef) >= 0);
463                         if (!isDup) fPreparedFamilies.Add(family.XRef);
464 
465                         if (fBase.Context.IsRecordAccess(family.Restriction)) {
466                             GDMIndividualRecord iFather = fTree.GetPtrValue(family.Husband);
467                             GDMIndividualRecord iMother = fTree.GetPtrValue(family.Wife);
468 
469                             bool divorced = (family.Status == GDMMarriageStatus.MarrDivorced);
470 
471                             if (iFather != null && fBase.Context.IsRecordAccess(iFather.Restriction)) {
472                                 result.Father = DoAncestorsStep(result, iFather, generation + 1, isDup);
473                                 if (result.Father != null) {
474                                     result.Father.SetFlag(PersonFlag.pfDivorced, divorced);
475                                     result.Father.IsDup = isDup;
476                                     if (fOptions.Kinship) {
477                                         fGraph.AddRelation(result.Node, result.Father.Node, RelationKind.rkParent, RelationKind.rkChild);
478                                     }
479                                 }
480                             } else {
481                                 result.Father = null;
482                             }
483 
484                             if (iMother != null && fBase.Context.IsRecordAccess(iMother.Restriction)) {
485                                 result.Mother = DoAncestorsStep(result, iMother, generation + 1, isDup);
486                                 if (result.Mother != null) {
487                                     result.Mother.SetFlag(PersonFlag.pfDivorced, divorced);
488                                     result.Mother.IsDup = isDup;
489                                     if (fOptions.Kinship) {
490                                         fGraph.AddRelation(result.Node, result.Mother.Node, RelationKind.rkParent, RelationKind.rkChild);
491                                     }
492                                 }
493                             } else {
494                                 result.Mother = null;
495                             }
496 
497                             if (result.Father != null && result.Mother != null && fOptions.Kinship) {
498                                 fGraph.AddRelation(result.Father.Node, result.Mother.Node, RelationKind.rkSpouse, RelationKind.rkSpouse);
499                             }
500 
501                             if (fOptions.MarriagesDates) {
502                                 DateFormat dateFormat = (fOptions.OnlyYears) ? DateFormat.dfYYYY : DateFormat.dfDD_MM_YYYY;
503                                 //DateFormat dateFormat = DateFormat.dfYYYY;
504                                 GlobalOptions glob = GlobalOptions.Instance;
505                                 var marDate = GKUtils.GetMarriageDateStr(family, dateFormat, glob.ShowDatesSign);
506                                 if (!string.IsNullOrEmpty(marDate)) {
507                                     if (result.Father != null) {
508                                         result.Father.MarriageDate = marDate;
509                                     } else if (result.Mother != null) {
510                                         result.Mother.MarriageDate = marDate;
511                                     }
512                                 }
513                             }
514                         }
515                     } else {
516                         if (aPerson.ChildToFamilyLinks.Count > 0) {
517                             result.SetFlag(PersonFlag.pfHasInvAnc);
518                         }
519                     }
520 
521                     if (fTree.GetTotalChildrenCount(aPerson) > 1 || aPerson.SpouseToFamilyLinks.Count > 1) {
522                         result.SetFlag(PersonFlag.pfHasInvDesc);
523                     }
524                 }
525 
526                 return result;
527             } catch (Exception ex) {
528                 Logger.WriteError("TreeChartModel.DoAncestorsStep()", ex);
529                 throw;
530             }
531         }
532 
CheckDescendantFilter(GDMIndividualRecord person, int level)533         private bool CheckDescendantFilter(GDMIndividualRecord person, int level)
534         {
535             bool result = true;
536 
537             switch (fFilter.SourceMode)
538             {
539                 case FilterGroupMode.All:
540                     break;
541 
542                 case FilterGroupMode.None:
543                     if (person.HasSourceCitations) {
544                         result = false;
545                     }
546                     break;
547 
548                 case FilterGroupMode.Any:
549                     if (!person.HasSourceCitations) {
550                         result = false;
551                     }
552                     break;
553 
554                 case FilterGroupMode.Selected:
555                     GDMSourceRecord filterSource;
556                     if (string.IsNullOrEmpty(fFilter.SourceRef)) {
557                         filterSource = null;
558                     } else {
559                         filterSource = fTree.XRefIndex_Find(fFilter.SourceRef) as GDMSourceRecord;
560                     }
561                     if (person.IndexOfSource(filterSource) < 0) {
562                         result = false;
563                     }
564                     break;
565             }
566 
567             if ((fFilter.BranchCut != ChartFilter.BranchCutType.None) && (!fFilterData[person])) {
568                 result = false;
569             }
570 
571             return result;
572         }
573 
DoDescendantsStep(TreeChartPerson parent, GDMIndividualRecord person, int level)574         private TreeChartPerson DoDescendantsStep(TreeChartPerson parent, GDMIndividualRecord person, int level)
575         {
576             try
577             {
578                 TreeChartPerson result = null;
579                 if (person == null) return result;
580 
581                 int spousesNum = person.SpouseToFamilyLinks.Count;
582 
583                 // if the person have more than one families - to hide unknown spouses it is impossible
584                 bool skipUnkSpouses = fOptions.HideUnknownSpouses && spousesNum < 2;
585 
586                 bool skipChildless = fOptions.ChildlessExclude && fBase.Context.IsChildless(person);
587 
588                 if (!skipChildless || level <= 1 || spousesNum != 0)
589                 {
590                     if (!CheckDescendantFilter(person, level))
591                         return null;
592 
593                     result = AddDescPerson(parent, person, false, level);
594 
595                     for (int i = 0; i < spousesNum; i++)
596                     {
597                         GDMFamilyRecord family = fTree.GetPtrValue(person.SpouseToFamilyLinks[i]);
598 
599                         // protection against invalid third-party files
600                         if (family == null) {
601                             Logger.WriteError("TreeChartModel.DoDescendantsStep(): null pointer to family");
602                             continue;
603                         }
604 
605                         bool isDup = (fPreparedFamilies.IndexOf(family.XRef) >= 0);
606                         if (!isDup) fPreparedFamilies.Add(family.XRef);
607 
608                         if (!fBase.Context.IsRecordAccess(family.Restriction)) continue;
609 
610                         TreeChartPerson resParent = null;
611                         TreeChartPerson ft = null;
612                         TreeChartPerson mt = null;
613                         PersonFlag descFlag = PersonFlag.pfDescByFather;
614                         bool invalidSpouse = false;
615                         bool skipUnk = false;
616 
617                         switch (person.Sex) {
618                             case GDMSex.svFemale:
619                                 {
620                                     GDMIndividualRecord sp = fTree.GetPtrValue(family.Husband);
621                                     skipUnk = skipUnkSpouses && (sp == null);
622 
623                                     if (!skipUnk) {
624                                         resParent = AddDescPerson(null, sp, true, level);
625                                         resParent.Sex = GDMSex.svMale;
626                                         resParent.SetFlag(PersonFlag.pfSpouse);
627 
628                                         ft = resParent;
629                                         ft.IsDup = isDup;
630 
631                                         mt = result;
632                                         mt.IsDup = isDup;
633 
634                                         descFlag = PersonFlag.pfDescByFather;
635                                     } else {
636                                         resParent = null;
637                                         mt = result;
638                                         mt.IsDup = isDup;
639                                         descFlag = PersonFlag.pfDescByMother;
640                                     }
641                                     break;
642                                 }
643 
644                             case GDMSex.svMale:
645                                 {
646                                     GDMIndividualRecord sp = fTree.GetPtrValue(family.Wife);
647                                     skipUnk = skipUnkSpouses && (sp == null);
648 
649                                     if (!skipUnk) {
650                                         resParent = AddDescPerson(null, sp, true, level);
651                                         resParent.Sex = GDMSex.svFemale;
652                                         resParent.SetFlag(PersonFlag.pfSpouse);
653 
654                                         ft = result;
655                                         ft.IsDup = isDup;
656 
657                                         mt = resParent;
658                                         mt.IsDup = isDup;
659 
660                                         descFlag = PersonFlag.pfDescByMother;
661                                     } else {
662                                         resParent = null;
663                                         ft = result;
664                                         ft.IsDup = isDup;
665                                         descFlag = PersonFlag.pfDescByFather;
666                                     }
667                                     break;
668                                 }
669 
670                             default:
671                                 invalidSpouse = true;
672                                 Logger.WriteError("TreeChartModel.DoDescendantsStep(): sex of spouse is undetermined");
673                                 break;
674                         }
675 
676                         if (invalidSpouse) {
677                             continue;
678                         }
679 
680                         if (resParent != null) {
681                             if (fOptions.Kinship) {
682                                 fGraph.AddRelation(result.Node, resParent.Node, RelationKind.rkSpouse, RelationKind.rkSpouse);
683                             }
684 
685                             result.AddSpouse(resParent);
686                             resParent.BaseSpouse = result;
687                             resParent.BaseFamily = family;
688 
689                             if (resParent.Rec != null) {
690                                 if (fOptions.MarriagesDates) {
691                                     //DateFormat dateFormat = (fOptions.OnlyYears) ? DateFormat.dfYYYY : DateFormat.dfDD_MM_YYYY;
692                                     DateFormat dateFormat = DateFormat.dfYYYY;
693                                     GlobalOptions glob = GlobalOptions.Instance;
694                                     var marDate = GKUtils.GetMarriageDateStr(family, dateFormat, glob.ShowDatesSign);
695                                     resParent.MarriageDate = marDate;
696                                 }
697 
698                                 if (resParent.Rec.ChildToFamilyLinks.Count > 0) {
699                                     resParent.SetFlag(PersonFlag.pfHasInvAnc);
700                                 }
701 
702                                 if (resParent.Rec.SpouseToFamilyLinks.Count > 1) {
703                                     resParent.SetFlag(PersonFlag.pfHasInvDesc);
704                                 }
705                             }
706                         } else {
707                             resParent = result;
708                         }
709 
710                         if ((fDepthLimitDescendants <= -1 || level != fDepthLimitDescendants) && (!isDup))
711                         {
712                             int num2 = family.Children.Count;
713                             for (int j = 0; j < num2; j++)
714                             {
715                                 var childRec = fTree.GetPtrValue(family.Children[j]);
716 
717                                 // protection against invalid third-party files
718                                 if (childRec == null) {
719                                     Logger.WriteError("TreeChartModel.DoDescendantsStep(): null pointer to child");
720                                     continue;
721                                 }
722 
723                                 if (!fBase.Context.IsRecordAccess(childRec.Restriction)) continue;
724 
725                                 TreeChartPerson child = DoDescendantsStep(resParent, childRec, level + 1);
726                                 if (child == null) continue;
727 
728                                 child.Father = ft;
729                                 child.Mother = mt;
730                                 child.SetFlag(descFlag);
731 
732                                 GDMChildToFamilyLink childLink = childRec.FindChildToFamilyLink(family);
733                                 if (childLink != null) {
734                                     child.SetFlag(PersonFlag.pfAdopted, (childLink.PedigreeLinkageType == GDMPedigreeLinkageType.plAdopted));
735                                 }
736 
737                                 if (fOptions.Kinship) {
738                                     if (ft != null) {
739                                         fGraph.AddRelation(child.Node, ft.Node, RelationKind.rkParent, RelationKind.rkChild);
740                                     }
741                                     if (mt != null) {
742                                         fGraph.AddRelation(child.Node, mt.Node, RelationKind.rkParent, RelationKind.rkChild);
743                                     }
744                                 }
745                             }
746                         } else {
747                             if (family.Children.Count > 0) {
748                                 if (ft != null) {
749                                     ft.SetFlag(PersonFlag.pfHasInvDesc);
750                                 }
751                                 if (mt != null) {
752                                     mt.SetFlag(PersonFlag.pfHasInvDesc);
753                                 }
754                             }
755                         }
756                     }
757                 }
758 
759                 return result;
760             }
761             catch (Exception ex)
762             {
763                 Logger.WriteError("TreeChartModel.DoDescendantsStep()", ex);
764                 throw;
765             }
766         }
767 
768         #endregion
769 
770         #region Kinships
771 
FindRelationship(TreeChartPerson target)772         private void FindRelationship(TreeChartPerson target)
773         {
774             if (target == null) return;
775 
776             if (target.Node == null || target.Rec == null) {
777                 target.Kinship = "";
778             } else {
779                 string kinship = fGraph.GetRelationship(target.Rec, false, GlobalOptions.Instance.ShortKinshipForm);
780                 if (kinship == "?") {
781                     kinship = "-";
782                 }
783                 target.Kinship = "[" + kinship + "]";
784 
785                 if (fPathDebug) {
786                     target.PathDebug = fGraph.IndividualsPath;
787                 }
788             }
789         }
790 
791         #endregion
792 
793         #region Sizes and adjustment routines
794 
ToggleCollapse(TreeChartPerson person)795         public void ToggleCollapse(TreeChartPerson person)
796         {
797             if (person != null) {
798                 person.IsCollapsed = !person.IsCollapsed;
799 
800                 if (person.GetSpousesCount() > 0) {
801                     int num = person.GetSpousesCount();
802                     for (int i = 0; i < num; i++) {
803                         TreeChartPerson sp = person.GetSpouse(i);
804                         sp.IsCollapsed = person.IsCollapsed;
805                     }
806                 }
807             }
808         }
809 
InitInfoSize()810         private int InitInfoSize()
811         {
812             int lines = 0;
813 
814             if (fOptions.FamilyVisible) {
815                 lines++;
816             }
817 
818             if (!fOptions.DiffLines) {
819                 lines++;
820             } else {
821                 lines++;
822                 lines++;
823             }
824 
825             if (!fOptions.OnlyYears) {
826                 if (fOptions.BirthDateVisible) {
827                     lines++;
828                     if (fOptions.SeparateDatesAndPlacesLines) {
829                         lines++;
830                     }
831                 }
832                 if (fOptions.DeathDateVisible) {
833                     lines++;
834                     if (fOptions.SeparateDatesAndPlacesLines) {
835                         lines++;
836                     }
837                 }
838             } else {
839                 lines++;
840             }
841 
842             if (fOptions.Kinship) {
843                 lines++;
844             }
845 
846             if (fPathDebug) {
847                 lines++;
848             }
849 
850             return lines;
851         }
852 
Predef()853         private void Predef()
854         {
855             fBranchDistance = (int)Math.Round(fOptions.BranchDistance * fScale);
856             fLevelDistance = (int)Math.Round(fOptions.LevelDistance * fScale);
857             fMargins = (int)Math.Round(fOptions.Margins * fScale);
858             fNodePadding = (int)(DEF_PERSON_NODE_PADDING * fScale);
859             fSpouseDistance = (int)Math.Round(fOptions.SpouseDistance * fScale);
860         }
861 
RecalcChart(bool noRedraw = false)862         public void RecalcChart(bool noRedraw = false)
863         {
864             float fsz = (float)Math.Round(fOptions.DefFontSize * fScale);
865             fBoldFont = AppHost.GfxProvider.CreateFont(fOptions.DefFontName, fsz, true);
866             fDrawFont = AppHost.GfxProvider.CreateFont(fOptions.DefFontName, fsz, false);
867 
868             Predef();
869 
870             if (fOptions.Kinship && fKinRoot != null) {
871                 fGraph.SetTreeRoot(fKinRoot.Rec);
872             }
873 
874             int lines = InitInfoSize();
875 
876             int num = fPersons.Count;
877             for (int i = 0; i < num; i++) {
878                 TreeChartPerson p = fPersons[i];
879 
880                 if (fOptions.Kinship) {
881                     FindRelationship(p);
882                 }
883 
884                 p.IsVisible = false;
885                 p.CalcBounds(lines, fRenderer);
886             }
887 
888             switch (fKind) {
889                 case TreeChartKind.ckAncestors:
890                     RecalcAncestorsChart();
891                     break;
892 
893                 case TreeChartKind.ckDescendants:
894                     RecalcDescendantsChart(true);
895                     break;
896 
897                 case TreeChartKind.ckBoth:
898                     RecalcAncestorsChart();
899                     RecalcDescendantsChart(false);
900                     break;
901             }
902 
903             // search bounds
904             fTreeBounds = ExtRect.Create(int.MaxValue, int.MaxValue, 0, 0);
905             int num2 = fPersons.Count;
906             for (int i = 0; i < num2; i++) {
907                 TreeChartPerson p = fPersons[i];
908                 if (p.IsVisible) {
909                     AdjustTreeBounds(p);
910                 }
911             }
912 
913             // adjust bounds
914             int offsetX = 0 + fMargins - fTreeBounds.Left;
915             int offsetY = 0 + fMargins - fTreeBounds.Top;
916             fTreeBounds = ExtRect.Create(int.MaxValue, int.MaxValue, 0, 0);
917             for (int i = 0; i < num2; i++) {
918                 TreeChartPerson p = fPersons[i];
919                 if (p.IsVisible) {
920                     p.PtX += offsetX;
921                     p.PtY += offsetY;
922                     AdjustTreeBounds(p);
923                 }
924             }
925 
926             fImageHeight = fTreeBounds.GetHeight() + fMargins * 2;
927             fImageWidth = fTreeBounds.GetWidth() + fMargins * 2;
928         }
929 
AdjustTreeBounds(TreeChartPerson person)930         private void AdjustTreeBounds(TreeChartPerson person)
931         {
932             if (person == null) return;
933             ExtRect prt = person.Rect;
934 
935             if (fTreeBounds.Left > prt.Left) fTreeBounds.Left = prt.Left;
936             if (fTreeBounds.Top > prt.Top) fTreeBounds.Top = prt.Top;
937             if (fTreeBounds.Right < prt.Right) fTreeBounds.Right = prt.Right;
938             if (fTreeBounds.Bottom < prt.Bottom) fTreeBounds.Bottom = prt.Bottom;
939         }
940 
ShiftAnc(TreeChartPerson person, int offset)941         private void ShiftAnc(TreeChartPerson person, int offset)
942         {
943             TreeChartPerson pp = person;
944             if (pp == null) return;
945 
946             do
947             {
948                 pp.PtX += offset;
949                 fEdges[pp.Generation] = pp.Rect.Right;
950 
951                 pp = (pp.GetChildsCount() < 1) ? null : pp.GetChild(0);
952             }
953             while (pp != null);
954         }
955 
RecalcAnc(ExtList<TreeChartPerson> prev, TreeChartPerson person, int ptX, int ptY)956         private void RecalcAnc(ExtList<TreeChartPerson> prev, TreeChartPerson person, int ptX, int ptY)
957         {
958             if (person == null) return;
959 
960             person.PtX = ptX;
961             person.PtY = ptY;
962 
963             int gen = person.Generation;
964 
965             int offset = (fEdges[gen] > 0) ? fBranchDistance : fMargins;
966             int bound = fEdges[gen] + offset;
967             if (person.Rect.Left <= bound) {
968                 ShiftAnc(person, bound - person.Rect.Left);
969             }
970 
971             fEdges[gen] = person.Rect.Right;
972 
973             prev.Add(person);
974             if (person.Rect.Top < 0) {
975                 offset = 0 - person.Rect.Top + fMargins;
976                 int num = prev.Count;
977                 for (int i = 0; i < num; i++) {
978                     prev[i].PtY += offset;
979                 }
980             }
981 
982             person.IsVisible = true;
983 
984             if (person.IsCollapsed) {
985                 return;
986             }
987 
988             if (person.Father != null && person.Mother != null) {
989                 RecalcAnc(prev, person.Father, person.PtX - (fSpouseDistance + person.Father.Width / 2), NextGenY(person, true));
990                 RecalcAnc(prev, person.Mother, person.PtX + (fSpouseDistance + person.Mother.Width / 2), NextGenY(person, true));
991 
992                 person.PtX = (person.Father.PtX + person.Mother.PtX) / 2;
993                 fEdges[gen] = person.Rect.Right;
994             } else {
995                 TreeChartPerson anc = null;
996                 if (person.Father != null) {
997                     anc = person.Father;
998                 } else if (person.Mother != null) {
999                     anc = person.Mother;
1000                 }
1001 
1002                 if (anc != null) {
1003                     RecalcAnc(prev, anc, person.PtX, NextGenY(person, true));
1004                 }
1005             }
1006         }
1007 
NextGenY(TreeChartPerson person, bool ancestors)1008         private int NextGenY(TreeChartPerson person, bool ancestors)
1009         {
1010             int sign = (ancestors) ? -1 : +1;
1011             int sign2 = (!fOptions.InvertedTree) ? +1 : -1;
1012             int offset = (fLevelDistance + person.Height) * sign * sign2;
1013             return person.PtY + offset;
1014         }
1015 
RecalcAncestorsChart()1016         private void RecalcAncestorsChart()
1017         {
1018             Array.Clear(fEdges, 0, fEdges.Length);
1019 
1020             var prev = new ExtList<TreeChartPerson>();
1021             try {
1022                 RecalcAnc(prev, fRoot, fMargins, fMargins);
1023             } finally {
1024                 prev.Dispose();
1025             }
1026         }
1027 
ShiftDesc(TreeChartPerson person, int offset, bool isSingle, bool verify = false)1028         private bool ShiftDesc(TreeChartPerson person, int offset, bool isSingle, bool verify = false)
1029         {
1030             if (person == null) return true;
1031 
1032             if (person == fRoot) {
1033                 isSingle = false;
1034             }
1035 
1036             // fix #189
1037             if (verify && (person.Rect.Left + offset < fEdges[person.Generation] + fBranchDistance)) {
1038                 return false;
1039             }
1040 
1041             bool res = true;
1042             if (person.BaseSpouse != null && (person.BaseSpouse.Sex == GDMSex.svFemale || person.BaseSpouse.GetSpousesCount() == 1)) {
1043                 res = ShiftDesc(person.BaseSpouse, offset, isSingle, verify);
1044                 if (!res) return false;
1045             } else {
1046                 if (!isSingle) {
1047                     res = ShiftDesc(person.Father, offset, false, verify);
1048                     if (!res) return false;
1049 
1050                     res = ShiftDesc(person.Mother, offset, false, verify);
1051                     if (!res) return false;
1052                 } else {
1053                     if (person.HasFlag(PersonFlag.pfDescByFather)) {
1054                         res = ShiftDesc(person.Father, offset, true, verify);
1055                         if (!res) return false;
1056                     } else if (person.HasFlag(PersonFlag.pfDescByMother)) {
1057                         res = ShiftDesc(person.Mother, offset, true, verify);
1058                         if (!res) return false;
1059                     }
1060                 }
1061             }
1062 
1063             person.PtX += offset;
1064             return true;
1065         }
1066 
RecalcDescChilds(TreeChartPerson person)1067         private void RecalcDescChilds(TreeChartPerson person)
1068         {
1069             if (person.IsCollapsed) {
1070                 return;
1071             }
1072 
1073             int childrenCount = person.GetChildsCount();
1074             if (childrenCount == 0) return;
1075 
1076             bool alignPair = person.BaseSpouse != null && person.BaseSpouse.GetSpousesCount() == 1;
1077             int centX = 0;
1078 
1079             if (alignPair) {
1080                 switch (person.Sex) {
1081                     case GDMSex.svMale:
1082                         centX = (person.Rect.Right + person.BaseSpouse.Rect.Left) / 2;
1083                         break;
1084 
1085                     case GDMSex.svFemale:
1086                         centX = (person.BaseSpouse.Rect.Right + person.Rect.Left) / 2;
1087                         break;
1088                 }
1089             } else {
1090                 centX = person.PtX;
1091             }
1092 
1093             int childrenWidth = 0;
1094             for (int i = 0; i < childrenCount; i++) {
1095                 childrenWidth += person.GetChild(i).Width;
1096             }
1097             if (childrenCount > 1) {
1098                 childrenWidth += (childrenCount - 1) * fBranchDistance;
1099             }
1100 
1101             int curX = centX - childrenWidth / 2;
1102             int curY = NextGenY(person, false);
1103 
1104             for (int i = 0; i < childrenCount; i++) {
1105                 TreeChartPerson child = person.GetChild(i);
1106                 RecalcDesc(child, curX + child.Width / 2, curY, true);
1107                 curX = child.Rect.Right + fBranchDistance;
1108             }
1109 
1110             curX = person.GetChild(0).PtX;
1111             if (childrenCount > 1) {
1112                 curX += (person.GetChild(childrenCount - 1).PtX - curX) / 2;
1113             }
1114 
1115             // This code is designed to align parents in the center of the location of children (across width),
1116             // because in the process of drawing children, various kinds of displacement are formed,
1117             // and the initial arrangement of the parents can be very laterally,
1118             // after the formation of a complete tree of their descendants.
1119             // However, this may be a problem (reason of #189) in the case if a shift initiated from descendants,
1120             // must be performed to the left with an overlay on an already formed side branch.
1121 
1122             if (!fOptions.AutoAlign) {
1123                 return;
1124             }
1125 
1126             if (alignPair) {
1127                 int offset;
1128                 switch (person.Sex) {
1129                     case GDMSex.svMale:
1130                         // fix #189
1131                         offset = curX - (fBranchDistance + person.Width) / 2 + 1 - person.PtX;
1132                         if (person.Rect.Left + offset < fEdges[person.Generation]) {
1133                             return;
1134                         }
1135 
1136                         ShiftDesc(person, curX - (fBranchDistance + person.Width) / 2 + 1 - person.PtX, true);
1137                         ShiftDesc(person.BaseSpouse, curX + (fBranchDistance + person.BaseSpouse.Width) / 2 - person.BaseSpouse.PtX, true);
1138                         break;
1139 
1140                     case GDMSex.svFemale:
1141                         // fix #189
1142                         offset = curX - (fBranchDistance + person.BaseSpouse.Width) / 2 + 1 - person.BaseSpouse.PtX;
1143                         if (person.BaseSpouse.Rect.Left + offset < fEdges[person.BaseSpouse.Generation]) {
1144                             return;
1145                         }
1146 
1147                         ShiftDesc(person, curX + (fBranchDistance + person.Width) / 2 - person.PtX, true);
1148                         ShiftDesc(person.BaseSpouse, curX - (fBranchDistance + person.BaseSpouse.Width) / 2 + 1 - person.BaseSpouse.PtX, true);
1149                         break;
1150                 }
1151             } else {
1152                 ShiftDesc(person, curX - person.PtX, true, true);
1153             }
1154         }
1155 
RecalcDesc(TreeChartPerson person, int ptX, int ptY, bool predef)1156         private void RecalcDesc(TreeChartPerson person, int ptX, int ptY, bool predef)
1157         {
1158             if (person == null) return;
1159 
1160             int gen = person.Generation;
1161             if (predef) {
1162                 person.PtX = ptX;
1163                 person.PtY = ptY;
1164             }
1165 
1166             int offset = (fEdges[gen] > 0) ? fBranchDistance : fMargins;
1167             int bound = fEdges[gen] + offset;
1168             if (person.Rect.Left < bound) {
1169                 ShiftDesc(person, bound - person.Rect.Left, true);
1170             }
1171 
1172             if (person.Sex == GDMSex.svMale) {
1173                 RecalcDescChilds(person);
1174                 fEdges[gen] = person.Rect.Right;
1175             }
1176 
1177             person.IsVisible = true;
1178 
1179             int spousesCount = person.GetSpousesCount();
1180             if (spousesCount > 0) {
1181                 TreeChartPerson prev = person;
1182                 for (int i = 0; i < spousesCount; i++) {
1183                     TreeChartPerson sp = person.GetSpouse(i);
1184                     int spOffset = (fBranchDistance + sp.Width / 2);
1185                     int spX = 0;
1186 
1187                     switch (person.Sex) {
1188                         case GDMSex.svMale:
1189                             spX = prev.Rect.Right + spOffset;
1190                             break;
1191 
1192                         case GDMSex.svFemale:
1193                             spX = prev.Rect.Left - spOffset;
1194                             break;
1195                     }
1196 
1197                     RecalcDesc(sp, spX, person.PtY, true);
1198 
1199                     // spouses arranged from first to last from left to right
1200                     // therefore for several wifes of one man, the previous node is the previous wife
1201                     // however, for several husbands of one woman, the previous node is a woman
1202                     if (sp.Sex != GDMSex.svMale) {
1203                         prev = sp;
1204                     }
1205                 }
1206             }
1207 
1208             if (person.Sex == GDMSex.svFemale) {
1209                 RecalcDescChilds(person);
1210                 fEdges[gen] = person.Rect.Right;
1211             }
1212 
1213             // FIXME: Temporary hack: if this person does not specify a particular sex,
1214             // then breaks the normal sequence of formation of coordinates.
1215             if (person.Sex == GDMSex.svUnknown || person.Sex == GDMSex.svIntersex) {
1216                 fEdges[gen] = person.Rect.Right;
1217             }
1218 
1219             // Fix of long-distance displacement of male nodes in the presence of more than
1220             // one marriage and a large tree of descendants from the first wife
1221             if (person.Sex == GDMSex.svMale && spousesCount >= 2) {
1222                 var firstWife = person.GetSpouse(0);
1223                 if (firstWife.GetChildsCount() > 0) {
1224                     int d = firstWife.Rect.Left - person.Rect.Right;
1225                     if (d > fBranchDistance * 1.5f) {
1226                         //person.SetFlag(PersonFlag.pfSpecialMark);
1227                         offset = (d - fBranchDistance);
1228                         ShiftDesc(person, offset, true);
1229                     }
1230                 }
1231             }
1232         }
1233 
RecalcDescendantsChart(bool predef)1234         private void RecalcDescendantsChart(bool predef)
1235         {
1236             Array.Clear(fEdges, 0, fEdges.Length);
1237             RecalcDesc(fRoot, fMargins, fMargins, predef);
1238         }
1239 
1240         #endregion
1241 
1242         #region Filtering and search
1243 
DoFilter(GDMIndividualRecord root)1244         public void DoFilter(GDMIndividualRecord root)
1245         {
1246             if (root == null)
1247                 throw new ArgumentNullException("root");
1248 
1249             if (fFilter.BranchCut == ChartFilter.BranchCutType.None) return;
1250 
1251             fFilterData.Clear();
1252             DoDescendantsFilter(root);
1253             fFilterData[root] = true;
1254         }
1255 
DoDescendantsFilter(GDMIndividualRecord person)1256         private bool DoDescendantsFilter(GDMIndividualRecord person)
1257         {
1258             bool result = false;
1259             if (person == null) return result;
1260 
1261             ChartFilter.BranchCutType branchCut = fFilter.BranchCut;
1262             switch (branchCut) {
1263                 case ChartFilter.BranchCutType.Years:
1264                     int birthYear = person.GetChronologicalYear(GEDCOMTagName.BIRT);
1265                     result = (birthYear != 0 && birthYear >= fFilter.BranchYear);
1266                     break;
1267 
1268                 case ChartFilter.BranchCutType.Persons:
1269                     result = (fFilter.BranchPersons.IndexOf(person.XRef + ";") >= 0);
1270                     break;
1271             }
1272 
1273             int num = person.SpouseToFamilyLinks.Count;
1274             for (int i = 0; i < num; i++) {
1275                 GDMFamilyRecord family = fTree.GetPtrValue(person.SpouseToFamilyLinks[i]);
1276 
1277                 int num2 = family.Children.Count;
1278                 for (int j = 0; j < num2; j++) {
1279                     GDMIndividualRecord child = fTree.GetPtrValue(family.Children[j]);
1280                     bool resChild = DoDescendantsFilter(child);
1281                     result |= resChild;
1282                 }
1283             }
1284 
1285             fFilterData[person] = result;
1286             return result;
1287         }
1288 
FindAll(string searchPattern)1289         public IList<ISearchResult> FindAll(string searchPattern)
1290         {
1291             var result = new List<ISearchResult>();
1292 
1293             Regex regex = GKUtils.InitMaskRegex(searchPattern);
1294 
1295             int num = fPersons.Count;
1296             for (int i = 0; i < num; i++) {
1297                 TreeChartPerson person = fPersons[i];
1298                 GDMIndividualRecord iRec = person.Rec;
1299                 if (iRec == null) continue;
1300 
1301                 string fullname = GKUtils.GetNameString(iRec, true, false);
1302                 if (GKUtils.MatchesRegex(fullname, regex)) {
1303                     result.Add(new SearchResult(iRec));
1304                 }
1305             }
1306 
1307             return result;
1308         }
1309 
1310         #endregion
1311 
1312         #region Navigation
1313 
FindPersonByRec(GDMIndividualRecord iRec)1314         public TreeChartPerson FindPersonByRec(GDMIndividualRecord iRec)
1315         {
1316             if (iRec != null) {
1317                 int num = fPersons.Count;
1318                 for (int i = 0; i < num; i++) {
1319                     TreeChartPerson p = fPersons[i];
1320                     if (p.Rec == iRec) {
1321                         return p;
1322                     }
1323                 }
1324             }
1325 
1326             return null;
1327         }
1328 
FindPersonByCoords(int aX, int aY)1329         public TreeChartPerson FindPersonByCoords(int aX, int aY)
1330         {
1331             TreeChartPerson result = null;
1332 
1333             aX -= fOffsetX;
1334             aY -= fOffsetY;
1335             int num = fPersons.Count;
1336             for (int i = 0; i < num; i++) {
1337                 TreeChartPerson p = fPersons[i];
1338                 if (p.Rect.Contains(aX, aY)) {
1339                     result = p;
1340                     break;
1341                 }
1342             }
1343 
1344             return result;
1345         }
1346 
1347         #endregion
1348 
1349         #region Drawing routines
1350 
SetRenderer(ChartRenderer renderer)1351         public override void SetRenderer(ChartRenderer renderer)
1352         {
1353             base.SetRenderer(renderer);
1354 
1355             InitSigns();
1356             InitGraphics();
1357         }
1358 
InitGraphics()1359         public void InitGraphics()
1360         {
1361             DoneGraphics();
1362 
1363             fLinePen = fRenderer.CreatePen(ChartRenderer.GetColor(BSDColors.Black), 1f);
1364             fDottedLinePen = fRenderer.CreatePen(ChartRenderer.GetColor(BSDColors.Black), 1f, new float[] {4.0F, 2.0F});
1365             fDecorativeLinePen = fRenderer.CreatePen(ChartRenderer.GetColor(BSDColors.Silver), 1f);
1366             fDottedDecorativeLinePen = fRenderer.CreatePen(ChartRenderer.GetColor(BSDColors.Silver), 1f, new float[] {4.0F, 2.0F});
1367             fSolidBlack = fRenderer.CreateSolidBrush(ChartRenderer.GetColor(BSDColors.Black));
1368         }
1369 
DoneGraphics()1370         private void DoneGraphics()
1371         {
1372             if (fLinePen != null) fLinePen.Dispose();
1373             if (fDecorativeLinePen != null) fDecorativeLinePen.Dispose();
1374             if (fSolidBlack != null) fSolidBlack.Dispose();
1375         }
1376 
GetExpanderRect(ExtRect personRect)1377         public static ExtRect GetExpanderRect(ExtRect personRect)
1378         {
1379             ExtRect expRt = ExtRect.Create(personRect.Left, personRect.Top - 18, personRect.Left + 16 - 1, personRect.Top - 2);
1380             return expRt;
1381         }
1382 
GetInfoRect(ExtRect personRect)1383         public static ExtRect GetInfoRect(ExtRect personRect)
1384         {
1385             ExtRect expRt = ExtRect.Create(personRect.Right - 16, personRect.Top - 18, personRect.Right, personRect.Top - 2);
1386             return expRt;
1387         }
1388 
GetPersonExpandRect(ExtRect personRect)1389         public static ExtRect GetPersonExpandRect(ExtRect personRect)
1390         {
1391             int x = personRect.Left + (personRect.GetWidth() - 16) / 2;
1392             ExtRect expRt = ExtRect.Create(x, personRect.Top - 18, x + 16 - 1, personRect.Top - 2);
1393             return expRt;
1394         }
1395 
IsPersonVisible(ExtRect pnRect)1396         private bool IsPersonVisible(ExtRect pnRect)
1397         {
1398             return fVisibleArea.IntersectsWith(pnRect);
1399         }
1400 
IsLineVisible(int x1, int y1, int x2, int y2)1401         private bool IsLineVisible(int x1, int y1, int x2, int y2)
1402         {
1403             if (fVisibleArea.GetWidth() <= 0 || fVisibleArea.GetHeight() <= 0) {
1404                 return false;
1405             }
1406 
1407             var rangeX = new Range<int>(fVisibleArea.Left, fVisibleArea.Right);
1408             var rangeY = new Range<int>(fVisibleArea.Top, fVisibleArea.Bottom);
1409 
1410             if (x2 < x1) {
1411                 int tmp = x1;
1412                 x1 = x2;
1413                 x2 = tmp;
1414             }
1415 
1416             if (y2 < y1) {
1417                 int tmp = y1;
1418                 y1 = y2;
1419                 y2 = tmp;
1420             }
1421 
1422             return rangeX.IsOverlapped(new Range<int>(x1, x2)) && rangeY.IsOverlapped(new Range<int>(y1, y2));
1423         }
1424 
DrawLine(int x1, int y1, int x2, int y2)1425         private void DrawLine(int x1, int y1, int x2, int y2)
1426         {
1427             DrawLine(x1, y1, x2, y2, fLinePen, fDecorativeLinePen);
1428         }
1429 
DrawLine(int x1, int y1, int x2, int y2, IPen linePen, IPen decorativeLinePen)1430         private void DrawLine(int x1, int y1, int x2, int y2, IPen linePen, IPen decorativeLinePen)
1431         {
1432             if (!IsLineVisible(x1, y1, x2, y2)) return;
1433 
1434             int sX = fOffsetX + x1;
1435             int sX2 = fOffsetX + x2;
1436             int sY = fOffsetY + y1;
1437             int sY2 = fOffsetY + y2;
1438             fRenderer.DrawLine(linePen, sX, sY, sX2, sY2);
1439 
1440             if (fOptions.Decorative) {
1441                 if (sX == sX2) {
1442                     fRenderer.DrawLine(decorativeLinePen, sX + 1, sY + 1, sX2 + 1, sY2 - 1);
1443                 } else if (sY == sY2) {
1444                     fRenderer.DrawLine(decorativeLinePen, sX + 1, sY + 1, sX2 + 0, sY2 + 1);
1445                 }
1446             }
1447         }
1448 
DrawBorder(IPen xpen, ExtRect rt, bool dead, TreeChartPerson person)1449         private void DrawBorder(IPen xpen, ExtRect rt, bool dead, TreeChartPerson person)
1450         {
1451             IColor bColor = person.GetFillColor(dead);
1452             if (fHighlightedPerson == person) {
1453                 bColor = bColor.Lighter(HIGHLIGHTED_VAL);
1454             }
1455 
1456             if (person.Sex == GDMSex.svFemale) {
1457                 fRenderer.DrawRoundedRectangle(xpen, bColor, rt.Left, rt.Top, rt.GetWidth(), rt.GetHeight(), 6);
1458             } else {
1459                 fRenderer.DrawRectangle(xpen, bColor, rt.Left, rt.Top, rt.GetWidth(), rt.GetHeight());
1460             }
1461         }
1462 
DrawPerson(TreeChartPerson person, ChartDrawMode drawMode)1463         private void DrawPerson(TreeChartPerson person, ChartDrawMode drawMode)
1464         {
1465             try {
1466                 ExtRect prt = person.Rect;
1467                 if (drawMode == ChartDrawMode.dmInteractive && !IsPersonVisible(prt))
1468                     return;
1469 
1470                 prt.Offset(fOffsetX, fOffsetY);
1471 
1472                 if (person.HasFlag(PersonFlag.pfIsDead)) {
1473                     ExtRect dt = prt.GetOffset(-2, -2);
1474                     DrawBorder(null, dt, true, person);
1475                 }
1476 
1477                 IPen xpen = null;
1478                 try {
1479                     if (drawMode == ChartDrawMode.dmInteractive && person.Selected) {
1480                         IColor penColor = person.GetSelectedColor();
1481                         xpen = fRenderer.CreatePen(penColor, 2.0f);
1482                     } else {
1483                         xpen = fRenderer.CreatePen(ChartRenderer.GetColor(BSDColors.Black), 1.0f);
1484                     }
1485 
1486                     DrawBorder(xpen, prt, false, person);
1487                 } finally {
1488                     if (xpen != null)
1489                         xpen.Dispose();
1490                 }
1491 
1492                 ExtRect brt = prt;
1493                 if (person.Portrait != null) {
1494                     ExtRect portRt = person.PortraitArea.GetOffset(prt.Left, prt.Top);
1495                     fRenderer.DrawImage(person.Portrait, portRt.Left, portRt.Top,
1496                                         portRt.GetWidth(), portRt.GetHeight());
1497 
1498                     prt.Left += person.PortraitWidth;
1499                 }
1500 
1501                 int bh = fRenderer.GetTextHeight(fBoldFont);
1502                 int th = fRenderer.GetTextHeight(fDrawFont);
1503                 int ry = prt.Top + fNodePadding;
1504 
1505                 int lines = person.Lines.Length;
1506                 for (int k = 0; k < lines; k++) {
1507                     string line = person.Lines[k];
1508 
1509                     int lh;
1510                     IFont font;
1511                     if (fOptions.BoldNames && k < person.NameLines) {
1512                         lh = bh;
1513                         font = fBoldFont;
1514                     } else {
1515                         lh = th;
1516                         font = fDrawFont;
1517                     }
1518 
1519                     int stw = fRenderer.GetTextWidth(line, font);
1520                     int rx = prt.Left + ((prt.Right - prt.Left + 1) - stw) / 2;
1521 
1522                     fRenderer.DrawString(line, font, fSolidBlack, rx, ry);
1523 
1524                     ry += lh;
1525                 }
1526 
1527                 if (fOptions.SignsVisible && !person.Signs.IsEmpty()) {
1528                     int i = 0;
1529                     for (var cps = SpecialUserRef.urRI_StGeorgeCross; cps <= SpecialUserRef.urLast; cps++) {
1530                         if (!person.Signs.Contains(cps)) continue;
1531 
1532                         IImage pic = fSignsPic[(int)cps];
1533                         fRenderer.DrawImage(pic, brt.Right, brt.Top - 21 + i * pic.Height);
1534                         i++;
1535                     }
1536                 }
1537 
1538                 // only interactive mode
1539                 if (drawMode == ChartDrawMode.dmInteractive) {
1540                     if (person.HasFlag(PersonFlag.pfCanExpand)) {
1541                         ExtRect expRt = GetExpanderRect(brt);
1542                         fRenderer.DrawImage(fExpPic, expRt.Left, expRt.Top);
1543                     }
1544 
1545                     if (person.IsCollapsed) {
1546                         ExtRect expRt = GetPersonExpandRect(brt);
1547                         fRenderer.DrawImage(fPersExpPic, expRt.Left, expRt.Top);
1548                     }
1549 
1550                     if (person.Selected) {
1551                         ExtRect infoRt = GetInfoRect(brt);
1552                         fRenderer.DrawImage(fInfoPic, infoRt.Left, infoRt.Top);
1553                     }
1554 
1555                     // draw CI only for existing individuals
1556                     if (fCertaintyIndex && person.Rec != null) {
1557                         string cas = string.Format("{0:0.00}", person.CertaintyAssessment);
1558                         fRenderer.DrawString(cas, fDrawFont, fSolidBlack, brt.Left, brt.Bottom);
1559                     }
1560                 }
1561             } catch (Exception ex) {
1562                 Logger.WriteError("TreeChartModel.DrawPerson()", ex);
1563             }
1564         }
1565 
IsDottedLines(TreeChartPerson person)1566         private bool IsDottedLines(TreeChartPerson person)
1567         {
1568             return (person != null && person.HasFlag(PersonFlag.pfAdopted) && fOptions.DottedLinesOfAdoptedChildren);
1569         }
1570 
DrawAncestors(TreeChartPerson person, ChartDrawMode drawMode)1571         private void DrawAncestors(TreeChartPerson person, ChartDrawMode drawMode)
1572         {
1573             if (person.IsCollapsed || (person.Father == null && person.Mother == null)) {
1574                 return;
1575             }
1576 
1577             Draw(person.Father, TreeChartKind.ckAncestors, drawMode);
1578             Draw(person.Mother, TreeChartKind.ckAncestors, drawMode);
1579 
1580             int crY, parY;
1581             if (!fOptions.InvertedTree) {
1582                 crY = person.PtY - fLevelDistance / 2;
1583                 parY = (person.Father != null) ? person.Father.PtY + person.Father.Height : person.Mother.PtY + person.Mother.Height;
1584             } else {
1585                 crY = person.PtY + person.Height + fLevelDistance / 2;
1586                 parY = (person.Father != null) ? person.Father.PtY : person.Mother.PtY;
1587             }
1588 
1589             bool dotted = IsDottedLines(person);
1590             var linePen = (!dotted) ? fLinePen : fDottedLinePen;
1591             var decorativeLinePen = (!dotted) ? fDecorativeLinePen : fDottedDecorativeLinePen;
1592 
1593             DrawLine(person.PtX, crY, person.PtX, person.PtY, linePen, decorativeLinePen); // v
1594 
1595             string marrDate = null;
1596 
1597             if (person.Father != null) {
1598                 DrawLine(person.Father.PtX, crY, person.PtX, crY, linePen, decorativeLinePen); // h
1599                 DrawLine(person.Father.PtX, parY, person.Father.PtX, crY, linePen, decorativeLinePen); // v
1600 
1601                 if (!string.IsNullOrEmpty(person.Father.MarriageDate) && marrDate == null) {
1602                     marrDate = person.Father.MarriageDate;
1603                 }
1604             }
1605 
1606             if (person.Mother != null) {
1607                 DrawLine(person.PtX, crY, person.Mother.PtX, crY, linePen, decorativeLinePen); // h
1608                 DrawLine(person.Mother.PtX, parY, person.Mother.PtX, crY, linePen, decorativeLinePen); // v
1609 
1610                 if (!string.IsNullOrEmpty(person.Mother.MarriageDate) && marrDate == null) {
1611                     marrDate = person.Mother.MarriageDate;
1612                 }
1613             }
1614 
1615             if (!string.IsNullOrEmpty(marrDate)) {
1616                 int q = (!fOptions.InvertedTree) ? 1 : 2;
1617                 DrawText(marrDate, person.PtX, crY, q);
1618             }
1619         }
1620 
DrawText(string text, float x, float y, int quad = 2)1621         private void DrawText(string text, float x, float y, int quad = 2)
1622         {
1623             // quadrant clockwise from 00 hours
1624             x += fOffsetX;
1625             y += fOffsetY;
1626             ExtSizeF tsz = fRenderer.GetTextSize(text, fDrawFont);
1627 
1628             switch (quad) {
1629                 case 1:
1630                     y -= tsz.Height;
1631                     break;
1632                 case 2:
1633                     break;
1634                 case 3:
1635                     x -= tsz.Width;
1636                     break;
1637                 case 4:
1638                     x -= tsz.Width;
1639                     y -= tsz.Height;
1640                     break;
1641             }
1642 
1643             fRenderer.DrawString(text, fDrawFont, fSolidBlack, x, y);
1644         }
1645 
DrawDescendants(TreeChartPerson person, ChartDrawMode drawMode)1646         private void DrawDescendants(TreeChartPerson person, ChartDrawMode drawMode)
1647         {
1648             int spousesCount = person.GetSpousesCount();
1649             int childrenCount = person.GetChildsCount();
1650 
1651             // draw lines of spouses
1652             int spbOfs = (person.Height - 10) / (spousesCount + 1);
1653             int spbBeg = person.PtY + (person.Height - spbOfs * (spousesCount - 1)) / 2;
1654             switch (person.Sex) {
1655                 case GDMSex.svMale:
1656                     for (int i = 0; i < spousesCount; i++) {
1657                         TreeChartPerson spouse = person.GetSpouse(i);
1658 
1659                         int spbV = spbBeg + spbOfs * i;
1660                         DrawLine(person.Rect.Right + 1, spbV, spouse.Rect.Left, spbV); // h
1661 
1662                         if (!string.IsNullOrEmpty(spouse.MarriageDate)) {
1663                             int q = (!fOptions.InvertedTree) ? 4 : 3;
1664                             DrawText(spouse.MarriageDate, spouse.Rect.Left, spbV, q);
1665                         }
1666                     }
1667                     break;
1668 
1669                 case GDMSex.svFemale:
1670                     for (int i = 0; i < spousesCount; i++) {
1671                         TreeChartPerson spouse = person.GetSpouse(i);
1672 
1673                         int spbV = spbBeg + spbOfs * i;
1674                         DrawLine(spouse.Rect.Right + 1, spbV, person.Rect.Left, spbV); // h
1675 
1676                         if (!string.IsNullOrEmpty(spouse.MarriageDate)) {
1677                             int q = (!fOptions.InvertedTree) ? 1 : 2;
1678                             DrawText(spouse.MarriageDate, spouse.Rect.Right + 1, spbV, q);
1679                         }
1680                     }
1681                     break;
1682             }
1683 
1684             // draw spouses
1685             for (int i = 0; i < spousesCount; i++) {
1686                 Draw(person.GetSpouse(i), TreeChartKind.ckDescendants, drawMode);
1687             }
1688 
1689             // draw lines of children
1690             if (!person.IsCollapsed && childrenCount != 0) {
1691                 // draw children
1692                 for (int i = 0; i < childrenCount; i++) {
1693                     Draw(person.GetChild(i), TreeChartKind.ckDescendants, drawMode);
1694                 }
1695 
1696                 int crY;
1697                 if (!fOptions.InvertedTree) {
1698                     crY = person.PtY + person.Height + fLevelDistance / 2;
1699                 } else {
1700                     crY = person.PtY - fLevelDistance / 2;
1701                 }
1702 
1703                 int cx = 0;
1704                 if (person.BaseSpouse == null || (person.BaseSpouse.GetSpousesCount() > 1)) {
1705                     cx = person.PtX;
1706                     spbBeg = person.PtY + person.Height - 1;
1707                 } else {
1708                     switch (person.Sex) {
1709                         case GDMSex.svMale:
1710                             cx = (person.Rect.Right + person.BaseSpouse.Rect.Left) / 2;
1711                             break;
1712 
1713                         case GDMSex.svFemale:
1714                             cx = (person.BaseSpouse.Rect.Right + person.Rect.Left) / 2;
1715                             break;
1716                     }
1717 
1718                     spbBeg -= spbOfs / 2;
1719                 }
1720 
1721                 DrawLine(cx, spbBeg, cx, crY); // v
1722 
1723                 TreeChartPerson child0 = person.GetChild(0);
1724                 int chY = (!fOptions.InvertedTree) ? child0.PtY : child0.PtY + child0.Height;
1725 
1726                 for (int i = 0; i < childrenCount; i++) {
1727                     TreeChartPerson child = person.GetChild(i);
1728 
1729                     bool dotted = IsDottedLines(child);
1730                     var linePen = (!dotted) ? fLinePen : fDottedLinePen;
1731                     var decorativeLinePen = (!dotted) ? fDecorativeLinePen : fDottedDecorativeLinePen;
1732 
1733                     if (childrenCount > 1) {
1734                         int jX;
1735                         if (i < childrenCount / 2) {
1736                             jX = Math.Min(cx, person.GetChild(i + 1).PtX);
1737                         } else {
1738                             jX = Math.Max(cx, person.GetChild(i - 1).PtX);
1739                         }
1740 
1741                         DrawLine(child.PtX, crY, jX, crY, linePen, decorativeLinePen); // h
1742                     }
1743 
1744                     DrawLine(child.PtX, crY, child.PtX, chY, linePen, decorativeLinePen); // v
1745                 }
1746             }
1747         }
1748 
Draw(ChartDrawMode drawMode)1749         public void Draw(ChartDrawMode drawMode)
1750         {
1751             InitGraphics();
1752             Draw(fRoot, fKind, drawMode);
1753         }
1754 
Draw(TreeChartPerson person, TreeChartKind dirKind, ChartDrawMode drawMode)1755         private void Draw(TreeChartPerson person, TreeChartKind dirKind, ChartDrawMode drawMode)
1756         {
1757             if (person == null) return;
1758 
1759             switch (dirKind)
1760             {
1761                 case TreeChartKind.ckAncestors:
1762                     DrawAncestors(person, drawMode);
1763                     break;
1764 
1765                 case TreeChartKind.ckDescendants:
1766                     DrawDescendants(person, drawMode);
1767                     break;
1768 
1769                 case TreeChartKind.ckBoth:
1770                     if (person == fRoot || dirKind == TreeChartKind.ckAncestors) DrawAncestors(person, drawMode);
1771                     if (person == fRoot || dirKind == TreeChartKind.ckDescendants) DrawDescendants(person, drawMode);
1772                     break;
1773             }
1774 
1775             DrawPerson(person, drawMode);
1776         }
1777 
InternalDraw(ChartDrawMode drawMode, BackgroundMode background, int fSPX, int fSPY)1778         private void InternalDraw(ChartDrawMode drawMode, BackgroundMode background, int fSPX, int fSPY)
1779         {
1780             // dummy
1781         }
1782 
1783         #endregion
1784 
CheckTreeChartSize(GDMTree tree, GDMIndividualRecord iRec, TreeChartKind chartKind)1785         public static bool CheckTreeChartSize(GDMTree tree, GDMIndividualRecord iRec, TreeChartKind chartKind)
1786         {
1787             bool result = true;
1788             if (!GlobalOptions.Instance.CheckTreeSize) return result;
1789 
1790             if (chartKind == TreeChartKind.ckAncestors || chartKind == TreeChartKind.ckBoth) {
1791                 int ancCount = GKUtils.GetAncestorsCount(tree, iRec);
1792                 if (ancCount > 2048) {
1793                     AppHost.StdDialogs.ShowMessage(string.Format(LangMan.LS(LSID.LSID_AncestorsNumberIsInvalid), ancCount.ToString()));
1794                     return false;
1795                 }
1796             }
1797 
1798             if (chartKind >= TreeChartKind.ckDescendants && chartKind <= TreeChartKind.ckBoth) {
1799                 int descCount = GKUtils.GetDescendantsCount(tree, iRec);
1800                 if (descCount > 2048) {
1801                     AppHost.StdDialogs.ShowMessage(string.Format(LangMan.LS(LSID.LSID_DescendantsNumberIsInvalid), descCount.ToString()));
1802                     result = false;
1803                 }
1804             }
1805 
1806             return result;
1807         }
1808     }
1809 }
1810