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