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