public MiniTreeGroup() { fMembers = null; fSize = new SizeF(0.0f, 0.0f); fParent = null; fIndividuals = 0; fStalkedIndividuals = 0; fBoxLeft = null; fBoxRight = null; fLastAddedObject = null; fCrossbar = ECrossbar.Solid; }
// Add a box for the individual to the specified group. private static MiniTreeIndividual AddToGroup(GDMIndividualRecord ir, MiniTreeGroup mtg) { MiniTreeIndividual mti = null; if (Exists(ir)) { CBoxText boxtext = new CBoxText(ir); mti = mtg.AddIndividual(ir, boxtext.FirstName, boxtext.Surname, boxtext.Date, true, false, false, boxtext.Concealed, true); } else { mti = mtg.AddIndividual(null, "", CConfig.Instance.UnknownName, " ", false, false, false, false, true); } return(mti); }
// Adds a CMiniTreeGroup to this group. // Informs neighbouring boxes about the group. public void AddGroup(MiniTreeGroup mtg) { if (mtg != null) { if (fMembers == null) { fMembers = new List <MiniTreeObject>(); } fMembers.Add(mtg); mtg.fParent = this; mtg.LeftObject = fLastAddedObject; if (fLastAddedObject != null) { fLastAddedObject.RightObject = mtg; } fLastAddedObject = mtg; } }
// Calculate size required for tree by iterating through individuals and building a data structure. protected MiniTreeGroup CreateDataStructure(GDMIndividualRecord irSubject) { // Add subject's frParents GDMFamilyRecord frParents = fTree.GetParentsFamily(irSubject); MiniTreeGroup mtgParents = new MiniTreeGroup(); MiniTreeIndividual mtiFather = null; if (frParents != null) { mtiFather = AddToGroup(fTree.GetPtrValue(frParents.Husband), mtgParents); } // Create a group for the subejct and their siblings. MiniTreeGroup mtgSiblings = new MiniTreeGroup(); // Keeps count of subject's siblings (including subject) int nSiblings = 0; // Keeps track of last added sibling, to hook up to next added sibling. MiniTreeIndividual mtiRightmostSibling = null; // Keeps track of last added child, to hook up to next added child. MiniTreeIndividual mtiRightmostChild = null; // For each sibling (including the subject) while (true) { GDMIndividualRecord irSibling = GetChild(frParents, nSiblings, irSubject); if (irSibling == null) { break; } if (irSibling == irSubject) { // Add spouses and children of subject, (and subject too, if we need to put wife after them.) MiniTreeGroup mtgOffspring = null; bool bAddedSubject = false; int nSpouses = 0; MiniTreeGroup.ECrossbar ecbCrossbar = MiniTreeGroup.ECrossbar.Solid; var indiFamilies = GMHelper.GetFamilyList(fTree, irSubject); foreach (GDMFamilyRecord famRec in indiFamilies) { GDMIndividualRecord irSpouse = fTree.GetSpouseBy(famRec, irSubject); if (famRec.Husband.XRef != irSubject.XRef) { mtiRightmostSibling = AddToGroup(irSpouse, mtgSiblings); // Subject is female so all but last husband have dotted bars ecbCrossbar = MiniTreeGroup.ECrossbar.DottedLeft; } else if (Exists(irSubject) && !bAddedSubject) { // Subject is male, so need to put them in now, before their children. // (Otherwise they get added as a regular sibling later) CBoxText boxtext = new CBoxText(irSubject); mtiRightmostSibling = mtgSiblings.AddIndividual(irSubject, boxtext.FirstName, boxtext.Surname, boxtext.Date, false, frParents != null, true, boxtext.Concealed, false); // To stop subject being added as regular sibling. bAddedSubject = true; } int nGrandchildren = 0; GDMIndividualRecord irGrandchild = null; // If we have already added an offspring box (from previous marriage) need connect this box to it as its right box. if (mtgOffspring != null) { mtgOffspring.RightBox = mtiRightmostSibling; } // Create a box for the offspring of this marriage mtgOffspring = new MiniTreeGroup(); // Set crossbar that joins subject to spouse according to whether this is subject's first spouse. mtgOffspring.fCrossbar = ecbCrossbar; // Add children by this spouse MiniTreeIndividual mtiChild = null; while ((irGrandchild = GetChild(famRec, nGrandchildren, null)) != null) { if (Exists(irGrandchild)) { CBoxText boxtext = new CBoxText(irGrandchild); mtiChild = mtgOffspring.AddIndividual(irGrandchild, boxtext.FirstName, boxtext.Surname, boxtext.Date, true, true, false, boxtext.Concealed, false); // Hook this up to any children by previous spouses. if (nGrandchildren == 0 && mtiRightmostChild != null) { mtiRightmostChild.RightObjectAlien = mtiChild; mtiChild.LeftObjectAlien = mtiRightmostChild; } } nGrandchildren++; } // If we added anything, record it as the right-most child ready to hook to children by next spouse. if (mtiChild != null) { mtiRightmostChild = mtiChild; } // Add the subjects children to the siblings group mtgSiblings.AddGroup(mtgOffspring); // Hook the offspring group to the previous sibling if (mtgOffspring != null) { mtgOffspring.LeftBox = mtiRightmostSibling; } // If subject is husband then we need to add their wife now. if (famRec.Husband.XRef == irSubject.XRef) { ecbCrossbar = MiniTreeGroup.ECrossbar.DottedRight; // Hook up to previous rightmost sibling and set this as new rightmost sibling. mtiRightmostSibling = AddToGroup(irSpouse, mtgSiblings); // Hook the wife up as box on right of offspring box. if (mtgOffspring != null) { mtgOffspring.RightBox = mtiRightmostSibling; } } nSpouses++; } if (!bAddedSubject) { CBoxText boxtext = new CBoxText(irSubject); MiniTreeIndividual mtiWife = mtgSiblings.AddIndividual(irSubject, boxtext.FirstName, boxtext.Surname, boxtext.Date, false, frParents != null, true, boxtext.Concealed, false); if (mtgOffspring != null) { mtgOffspring.fCrossbar = MiniTreeGroup.ECrossbar.Solid; mtgOffspring.RightBox = mtiWife; } } } else if (Exists(irSibling)) { // A sibling (not the subject). CBoxText boxtext = new CBoxText(irSibling); mtgSiblings.AddIndividual(irSibling, boxtext.FirstName, boxtext.Surname, boxtext.Date, true, frParents != null, true, boxtext.Concealed, false); } nSiblings++; } // Add siblings group after subject's father mtgParents.AddGroup(mtgSiblings); // Hook up to subject's father mtgSiblings.LeftBox = mtiFather; // Add subject's mother if (frParents != null) { MiniTreeIndividual mtiMother = AddToGroup(fTree.GetPtrValue(frParents.Wife), mtgParents); mtgSiblings.RightBox = mtiMother; } // Return the parents group (which contains the other family groups). return(mtgParents); }
// This is the main tree drawing method. // irSubject is the individual for whom the tree is based. // nTargeWidth is the width below which the layout is free to use up space to produce a nice tree. public List <MiniTreeMap> CreateMiniTree(Paintbox paintbox, GDMIndividualRecord ir, string fileName, int targetWidth, ImageFormat imageFormat) { // First calculate size required for tree, by iterating through individuals and building a data structure MiniTreeGroup mtgParent = CreateDataStructure(ir); // For each individual calculate size of box required for display using helper function // There must be a better way to get a graphics: Bitmap bmp = new Bitmap(1, 1, PixelFormat.Format24bppRgb); Graphics g = Graphics.FromImage(bmp); Font f = paintbox.Font; // Record what font windows actually used, in case it chose a different one CConfig.Instance.TreeFontName = f.Name; CConfig.Instance.TreeFontSize = f.Size; // Recursively calculate sizes of other groups mtgParent.CalculateSize(g, f); g.Dispose(); bmp.Dispose(); // Now calculate sizes of each row // Total width includes irSubject, their spouses and their siblings. // Total height is always three generations // Now calculate how best to position each generation // Calculate the width of each generation // There are three cases : frParents widest, siblings widest, children widest // Plus two aims : minimise total width, get offspring centred under frParents. // If nTargetWidth is exceeded simply because of number of individuals in one row, that // row's width becomes the new target width. // If nTargetWidth is exceeded otherwise, minimising total width becomes the priority mtgParent.CalculateLayout(0f, 0f); mtgParent.Compress(); RectangleF rect = mtgParent.GetExtent(); fSizeTotal = new SizeF(rect.Width, rect.Height); mtgParent.Translate(-rect.Left, -rect.Top); // Calculate offset for each row // Can't do this so create a new bitmap: bmp.Width = totalSize.Width; // Can't do this so create a new bitmap: bmp.Height = totalSize.Height; int nTotalWidth = (int)(fSizeTotal.Width + 1.0f); int nTotalHeight = (int)(fSizeTotal.Height + 1.0f); bmp = new Bitmap(nTotalWidth, nTotalHeight, PixelFormat.Format32bppArgb); g = Graphics.FromImage(bmp); // Do background fill if (CConfig.Instance.FakeMiniTreeTransparency && paintbox.BrushFakeTransparency != null) { g.FillRectangle(paintbox.BrushFakeTransparency, 0, 0, nTotalWidth, nTotalHeight); } else if (imageFormat == ImageFormat.Gif && paintbox.BrushBgGif != null) { g.FillRectangle(paintbox.BrushBgGif, 0, 0, nTotalWidth, nTotalHeight); } List <MiniTreeMap> alMap = new List <MiniTreeMap>(); mtgParent.DrawBitmap(paintbox, g, alMap); // Save the bitmap fLogger.WriteInfo("Saving mini tree as " + fileName); if (File.Exists(fileName)) { // Delete any current file File.SetAttributes(fileName, FileAttributes.Normal); File.Delete(fileName); } // Save using FileStream to try to avoid crash (only seen by customers) FileStream fs = new FileStream(fileName, FileMode.Create); bmp.Save(fs, imageFormat); fs.Close(); g.Dispose(); bmp.Dispose(); // For gifs we need to reload and set transparency colour if (imageFormat == ImageFormat.Gif && !CConfig.Instance.FakeMiniTreeTransparency) { Image imageGif; ColorPalette colorpalette; imageGif = Image.FromFile(fileName); colorpalette = imageGif.Palette; // Creates a new GIF image with a modified colour palette if (colorpalette != null) { // Create a new 8 bit per pixel image Bitmap bm = new Bitmap(imageGif.Width, imageGif.Height, PixelFormat.Format8bppIndexed); // Get it's palette ColorPalette colorpaletteNew = bm.Palette; // Copy all the entries from the old palette removing any transparency int n = 0; foreach (Color c in colorpalette.Entries) { colorpaletteNew.Entries[n++] = Color.FromArgb(255, c); } // Now to copy the actual bitmap data // Lock the source and destination bits BitmapData src = ((Bitmap)imageGif).LockBits(new Rectangle(0, 0, imageGif.Width, imageGif.Height), ImageLockMode.ReadOnly, imageGif.PixelFormat); BitmapData dst = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.WriteOnly, bm.PixelFormat); // Uses pointers so we need unsafe code. // The project is also compiled with /unsafe byte backColor = 0; unsafe { backColor = ((byte *)src.Scan0.ToPointer())[0]; // Assume transparent colour appears as first pixel. byte *src_ptr = ((byte *)src.Scan0.ToPointer()); byte *dst_ptr = ((byte *)dst.Scan0.ToPointer()); // May be useful: System.Runtime.InteropServices.Marshal.Copy(IntPtr source, byte[], destination, int start, int length) // May be useful: System.IO.MemoryStream ms = new System.IO.MemoryStream(src_ptr); int width = imageGif.Width; int src_stride = src.Stride - width; int dst_stride = dst.Stride - width; for (int y = 0; y < imageGif.Height; y++) { // Can't convert IntPtr to byte[]: Buffer.BlockCopy( src_ptr, 0, dst_ptr, 0, width ); int x = width; while (x-- > 0) { *dst_ptr++ = *src_ptr++; } src_ptr += src_stride; dst_ptr += dst_stride; } } // Set the newly selected transparency colorpaletteNew.Entries[(int)backColor] = Color.FromArgb(0, Color.Magenta); // Re-insert the palette bm.Palette = colorpaletteNew; // All done, unlock the bitmaps ((Bitmap)imageGif).UnlockBits(src); bm.UnlockBits(dst); imageGif.Dispose(); // Set the new image in place imageGif = bm; colorpalette = imageGif.Palette; fLogger.WriteInfo("Re-saving mini gif as " + fileName); imageGif.Save(fileName, imageFormat); } } return(alMap); }