// Draws the group to the graphics instance.
        public override void DrawBitmap( CPaintbox paintbox, Graphics g, ArrayList alMap )
        {
            if( m_alMembers == null )
              {
            // Empty group
            return;
              }

              foreach( object obj in m_alMembers )
              {
            if( obj is CMiniTreeGroup )
            {
              if( ((CMiniTreeGroup)obj).m_mtiBoxLeft != null && ((CMiniTreeGroup)obj).m_mtiBoxRight != null )
              {
            // Draw crossbar
            float fCrossbarLeft = ((CMiniTreeGroup)obj).m_mtiBoxLeft.TeeRight;
            float fCrossbarRight = ((CMiniTreeGroup)obj).m_mtiBoxRight.TeeLeft;
            float fCrossbarLeftGap = ((CMiniTreeGroup)obj).m_mtiBoxLeft.Right;
            float fCrossbarRightGap = ((CMiniTreeGroup)obj).m_mtiBoxRight.Left;
            float fCrossbarY = (((CMiniTreeGroup)obj).m_mtiBoxLeft.TeeCentreVert + ((CMiniTreeGroup)obj).m_mtiBoxRight.TeeCentreVert) / 2f;
            switch( ((CMiniTreeGroup)obj).m_ecCrossbar )
            {
              case ECrossbar.eCB_Solid:
                g.DrawLine( paintbox.m_penConnector, fCrossbarLeft, fCrossbarY, fCrossbarRight, fCrossbarY );
                break;

              case ECrossbar.eCB_DottedLeft:
                g.DrawLine( paintbox.m_penConnectorDotted, fCrossbarLeft, fCrossbarY, fCrossbarRightGap, fCrossbarY );
                break;

              case ECrossbar.eCB_DottedRight:
                g.DrawLine( paintbox.m_penConnectorDotted, fCrossbarLeftGap, fCrossbarY, fCrossbarRight, fCrossbarY );
                break;

                default:
                    break;
            }

            if( ((CMiniTreeGroup)obj).m_uStalkedIndividuals > 0 )
            {
              // Draw down to individuals
              // Use y coord of first individual, assuming all are at the same y coord
              float fIndividualY = 0f;
              bool bHaveIndividuals = false;
              foreach( CMiniTreeObject groupObj in ((CMiniTreeGroup)obj).m_alMembers )
              {
                if( groupObj is CMiniTreeIndividual )
                {
                  fIndividualY = ((CMiniTreeIndividual)groupObj).Top;
                  bHaveIndividuals = true;
                  break;
                }
              }
              float fCrossbarCentre = (fCrossbarLeft+fCrossbarRight)/2f;
              if( bHaveIndividuals )
              {
                g.DrawLine( paintbox.m_penConnector, fCrossbarCentre, fCrossbarY, fCrossbarCentre, fIndividualY );

                // Connect individuals
                SizeF stalkMinMax = ((CMiniTreeGroup)obj).StalkMinMax;

                // Width irrelevant, using SizeF simply as a way to pass 2 floats:
                float fStalkMin = stalkMinMax.Width;

                // Height irrelevant, using SizeF simply as a way to pass 2 floats
                float fStalkMax = stalkMinMax.Height;

                if( fCrossbarCentre < fStalkMin )
                {
                  fStalkMin = fCrossbarCentre;
                }
                else if( fCrossbarCentre > fStalkMax )
                {
                  fStalkMax = fCrossbarCentre;
                }
                g.DrawLine( paintbox.m_penConnector, fStalkMin, fIndividualY, fStalkMax, fIndividualY );
              }
            }
              }

              ((CMiniTreeGroup)obj).DrawBitmap( paintbox, g, alMap );
            }
            else if( obj is CMiniTreeIndividual )
            {
              // Draw individual box
              ((CMiniTreeIndividual)obj).DrawBitmap( paintbox, g, alMap );
            }
              }
        }
 // Draws the element to the graphics instance.
 public abstract void DrawBitmap( CPaintbox paintbox, Graphics g, ArrayList alMap );
        // 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 ArrayList CreateMiniTree( CPaintbox paintbox, CIndividualRecord ir, string sFilename, int nTargetWidth, System.Drawing.Imaging.ImageFormat imageFormat )
        {
            // First calculate size required for tree, by iterating through individuals and building a data structure
            CMiniTreeGroup 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, System.Drawing.Imaging.PixelFormat.Format24bppRgb );
            Graphics g = Graphics.FromImage( bmp );
            Font f = paintbox.m_font;

            // Record what font windows actually used, in case it chose a different one
            MainForm.s_config.m_sTreeFontName = f.Name;
            MainForm.s_config.m_fTreeFontSize = 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();
            m_sizeTotal = 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)(m_sizeTotal.Width+1.0f);
            int nTotalHeight = (int)(m_sizeTotal.Height+1.0f);
            bmp = new Bitmap( nTotalWidth, nTotalHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb );
            g = Graphics.FromImage( bmp );

            // Do background fill
            if( MainForm.s_config.m_bFakeMiniTreeTransparency && paintbox.m_brushFakeTransparency != null )
            {
                g.FillRectangle( paintbox.m_brushFakeTransparency, 0, 0, nTotalWidth, nTotalHeight );
            }
            else if( imageFormat == ImageFormat.Gif && paintbox.m_brushBgGif != null )
            {
                g.FillRectangle( paintbox.m_brushBgGif, 0, 0, nTotalWidth, nTotalHeight );
            }

            ArrayList alMap = new ArrayList();
            mtgParent.DrawBitmap( paintbox, g, alMap );

            // Save the bitmap
            LogFile.TheLogFile.WriteLine( LogFile.DT_HTML, LogFile.EDebugLevel.eDL_Note, "Saving mini tree as " + sFilename );

            if( System.IO.File.Exists( sFilename ) )
            {
                // Delete any current file
                System.IO.File.SetAttributes( sFilename, System.IO.FileAttributes.Normal );
                System.IO.File.Delete( sFilename );
            }

            // Save using FileStream to try to avoid crash (only seen by customers)
            System.IO.FileStream fs = new System.IO.FileStream( sFilename, System.IO.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 && !MainForm.s_config.m_bFakeMiniTreeTransparency )
            {
                Image imageGif;
                ColorPalette colorpalette;
                imageGif = Image.FromFile( sFilename );
                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;

                    LogFile.TheLogFile.WriteLine( LogFile.DT_HTML, LogFile.EDebugLevel.eDL_Note, "Re-saving mini gif as " + sFilename );

                    imageGif.Save( sFilename, imageFormat );
                }
            }

            return alMap;
        }
        // Draws the actual box, and adds the region of the box to the image alMap list.
        public override void DrawBitmap( CPaintbox paintbox, Graphics g, ArrayList alImageMap )
        {
            SolidBrush solidbrushBg, solidbrushText;

            if( m_bConcealed )
            {
                solidbrushBg = paintbox.m_brushBoxConcealed;
            }
            else if( m_bHighlight )
            {
                solidbrushBg = paintbox.m_brushBoxHighlight;
            }
            else if( m_bShade )
            {
                solidbrushBg = paintbox.m_brushBoxShade;
            }
            else
            {
                solidbrushBg = paintbox.m_brushBox;
            }

            if( m_bLinkable )
            {
                solidbrushText = paintbox.m_brushTextLink;
            }
            else if( m_bConcealed )
            {
                solidbrushText = paintbox.m_brushTextConcealed;
            }
            else
            {
                solidbrushText = paintbox.m_brushText;
            }

            g.FillRectangle( solidbrushBg, m_x+MARGIN_HORIZ, m_y+MARGIN_VERT, m_sizeText.Width, m_sizeText.Height-1f );
            g.DrawRectangle( paintbox.m_penBox, m_x+MARGIN_HORIZ, m_y+MARGIN_VERT, m_sizeText.Width, m_sizeText.Height-1f );

            float fTextX = m_x+MARGIN_HORIZ+PADDING_HORIZ;
            float fTextY = m_y+MARGIN_VERT+PADDING_VERT;
            if( m_bConserveWidth )
            {
                g.DrawString( m_sFirstnames, paintbox.m_font, solidbrushText, fTextX + m_fFirstnamesPad, fTextY );
                fTextY += m_sizeFirstnames.Height;
                g.DrawString( m_sSurname, paintbox.m_font, solidbrushText, fTextX + m_fSurnamePad, fTextY );
                fTextY += m_sizeSurname.Height;
            }
            else
            {
                g.DrawString( Name, paintbox.m_font, solidbrushText, fTextX + m_fSurnamePad, fTextY );
                fTextY += m_sizeSurname.Height;
            }

            g.DrawString( m_sDate, paintbox.m_font, solidbrushText, fTextX + m_fDatePad, fTextY );

            if( m_bChild )
            {
                g.DrawLine( paintbox.m_penConnector, m_x+MARGIN_HORIZ+m_sizeText.Width/2f, m_y, m_x+MARGIN_HORIZ+m_sizeText.Width/2f, m_y + MARGIN_VERT/* -1f*/ );
            }

            if( m_ir != null )
            {
                alImageMap.Add( new CMiniTreeMap( Name, m_ir, m_bLinkable,
                    (int)(m_x+MARGIN_HORIZ), (int)(m_y+MARGIN_VERT),
                    (int)(m_x+MARGIN_HORIZ+m_sizeText.Width), (int)(m_y+MARGIN_VERT+m_sizeText.Height-1f) ) );
            }
        }
 // Constructor
 public CCreatorRecordIndividual( CGedcom gedcom, IProgressCallback progress, string sW3cfile, CIndividualRecord ir, CCreatorIndexIndividuals indiIndexCreator, CPaintbox paintbox )
     : base(gedcom, progress, sW3cfile)
 {
     m_ir = ir;
       m_indiIndexCreator = indiIndexCreator;
       m_paintbox = paintbox;
       m_htFirstFoundEvent = new Hashtable();
       m_sBirthdaySourceRefs = "";
       m_sDeathdaySourceRefs = "";
       m_sNameTitle = "";
       m_bUnknownName = false;
       m_sName = m_ir.Name;
       m_sNameSuffix = m_ir.NameSuffix;
       m_sFirstName = "";
       m_sSurname = "";
       m_sOccupation = "";
       m_bConcealed = m_ir.Visibility() == CIndividualRecord.EVisibility.Restricted;
       m_alEventList = new ArrayList();
       m_alAttributeList = new ArrayList();
       m_alReferenceList = new ArrayList();
       m_alOccupations = new ArrayList();
       m_sPreviousChildLink = "";
       m_sNextChildLink = "";
       m_alOtherNames = new ArrayList();
       m_qdateInferredBirthday = null;
       m_dateActualBirthday = null;
       m_qdateInferredDeathday = null;
       m_dateActualDeathday = null;
       m_alParents = new ArrayList();
 }
        // The heart of GEDmill is here.
        public void Create()
        {
            LogFile.TheLogFile.WriteLine( LogFile.DT_HTML, LogFile.EDebugLevel.Note, "CWebsite::Create()" );

              // 1 means the process was aborted, for signalling back to calling thread. 2 means file nError.
              CThreadError threaderror = new CThreadError( 1, "No error" );

              try
              {
            // The value to indicate in the progress bar to show how much of the website creation is complete.
            int nProgress = 0;

            // The maximum value of the progress bar, i.e. when website creation is fully complete.
            int nProgressMax =
            1  // Site-wide multimedia files
              + 1  // W3C Sticker
              + 1  // Background image
              + 1  // Style sheet
              + m_gedcom.CountIndividuals
              + 1  // Individuals Index
              + m_gedcom.CountSources
              + 1  // Front page
              + 1  // Help page
              + 1  // CD ROM (Doesn't matter here that CD ROM autorun might not be included.)
              + 1; // Scripts (Doesn't matter here that scripts might not be included.)

            // The paintbox with which to draw the mini tree
            CPaintbox paintbox = new CPaintbox(MainForm.s_config);
            paintbox.SetBackgroundImage(MainForm.s_config.m_sBackgroundImage);

            // Object to keep count of number of files created etc.
            CStats stats = new CStats();

            // Here goes....

            // Start the progress indicator.
            m_progressWindow.Begin( 0, nProgressMax );
            if (m_progressWindow.IsAborting)
            {
            return;
            }
            // Copy the images to use in place of non-pic multimedia files.
            m_progressWindow.SetText("Copying multimedia");
            CopyIcons();
            if (m_progressWindow.IsAborting)
            {
            return;
            } m_progressWindow.StepTo(++nProgress);

            // Copy the W3C sticker file.
            m_progressWindow.SetText("Copying W3C sticker");
            string sW3CFilename = "";
            if (MainForm.s_config.m_bIncludeValiditySticker)
            {
              sW3CFilename = CopyW3CSticker();
            }
            if (m_progressWindow.IsAborting)
            {
            return;
            } m_progressWindow.StepTo(++nProgress);

            // Create the index creator for use by the individuals records creator.
            CCreatorIndexIndividuals indiIndexCreator = new CCreatorIndexIndividuals( m_gedcom, m_progressWindow, sW3CFilename );

            // Copy the image for the background of the webpages.
            m_progressWindow.SetText("Copying background image");
            string sBackgroundImageFilename = CopyBackgroundImage();
            if (m_progressWindow.IsAborting)
            {
            return;
            }
            m_progressWindow.StepTo( ++nProgress );

            // Create the style sheet
            m_progressWindow.SetText("Creating style sheet");
            string cssFilename = String.Concat( MainForm.s_config.m_sOutputFolder, "\\", MainForm.s_config.m_sStylesheetFilename, ".css" );
            if( MainForm.s_config.m_sStylesheetFilename.Length>0 && (!MainForm.s_config.m_bPreserveStylesheet || !File.Exists( cssFilename ) ) )
            {
              CCreatorStylesheet csc = new CCreatorStylesheet( m_gedcom, m_progressWindow, sW3CFilename, cssFilename, sBackgroundImageFilename );
              csc.Create();
            }

            if (m_progressWindow.IsAborting)
            {
            return;
            }

            m_progressWindow.StepTo( ++nProgress );

            // Create the pages for the individual records.
            m_progressWindow.SetText("Creating individual pages");
            foreach (CIndividualRecord ir in m_gedcom.m_alIndividualRecords)
            {
              CCreatorRecordIndividual ipc = new CCreatorRecordIndividual( m_gedcom, m_progressWindow, sW3CFilename, ir, indiIndexCreator, paintbox );
              if (ipc.Create( stats ))
              {
            stats.m_unIndividuals++;
              }
              if (m_progressWindow.IsAborting)
              {
              return;
              }

              m_progressWindow.StepTo(++nProgress);
            }

            // Create the index for the individual records pages.
            m_progressWindow.SetText("Creating individuals index");
            indiIndexCreator.Create();
            if (m_progressWindow.IsAborting)
            {
            return;
            }

            m_progressWindow.StepTo(++nProgress);

            // Clear list of copied files, so that source images get copied afresh
            // and so get resized differently to any indi images based on the same file.
            CCreator.ClearCopiedFilesList();

            // Create the pages for the source records.
            m_progressWindow.SetText( "Creating source pages" );
            foreach( CSourceRecord sr in m_gedcom.m_alSourceRecords )
            {
              if( sr != null && sr.ContainsAnyInformation() )
              {
            CCreatorRecordSource spc = new CCreatorRecordSource( m_gedcom, m_progressWindow, sW3CFilename, sr );
            if( spc.Create( stats ) )
            {
              stats.m_unSources++;
            }
              }
              if (m_progressWindow.IsAborting)
              {
              return;
              }

              m_progressWindow.StepTo(++nProgress);
            }
            if (m_progressWindow.IsAborting)
            {
            return;
            }

            // Create the front page
            m_progressWindow.SetText("Creating front page");
            string front_page_filename = String.Concat(MainForm.s_config.m_sOutputFolder, "\\", MainForm.s_config.m_sFrontPageFilename, ".", MainForm.s_config.m_sHtmlExtension);
            if( MainForm.s_config.m_sFrontPageFilename.Length>0 && (!MainForm.s_config.m_bPreserveFrontPage || !File.Exists( front_page_filename ) ) )
            {
              CCreatorFrontPage fpc = new CCreatorFrontPage( m_gedcom, m_progressWindow, sW3CFilename, stats );
              fpc.Create();
            }
            m_progressWindow.StepTo( ++nProgress );
            if (m_progressWindow.IsAborting)
            {
            return;
            }

            // Create the help page
            m_progressWindow.SetText("Creating help page");
            string help_page_filename = String.Concat(MainForm.s_config.m_sOutputFolder, "\\", "help.", MainForm.s_config.m_sHtmlExtension);
            if( MainForm.s_config.m_bIncludeHelppage )
            {
              CCreatorHelppage hpc = new CCreatorHelppage( m_gedcom, m_progressWindow, sW3CFilename );
              hpc.Create();
            }
            m_progressWindow.StepTo( ++nProgress );
            if (m_progressWindow.IsAborting)
            {
            return;
            }

            // Copy the CD ROM autorun file
            m_progressWindow.SetText( "Creating CD-ROM files" );
            if( MainForm.s_config.m_bCreateCDROMFiles )
            {
              CreateCDROMFiles( m_gedcom );
            }
            if (m_progressWindow.IsAborting)
            {
            return;
            }

            m_progressWindow.StepTo(++nProgress);

            // Copy the Javascript
            m_progressWindow.SetText( "Creating Javascript file" );
            if( MainForm.s_config.m_bAllowMultipleImages ) // Currently (10Dec08) the only thing that uses javascript is the multiple images feature.
            {
              CreateJavascriptFiles( m_gedcom );
            }
            if (m_progressWindow.IsAborting)
            {
            return;
            }

            m_progressWindow.StepTo(++nProgress);

            // Done
            LogFile.TheLogFile.WriteLine( LogFile.DT_HTML, LogFile.EDebugLevel.Note, "Finished" );
            m_progressWindow.SetText( "Done" );
            threaderror.m_nError = 0;
            threaderror.m_sMessage = "";
              }
              catch( ArgumentException e )
              {
            LogFile.TheLogFile.WriteLine( LogFile.DT_HTML, LogFile.EDebugLevel.Error, "Caught Argument Exception : " + e.ToString() );

            threaderror.m_nError = 2; // 2 => abnormal abort.
            threaderror.m_sMessage = "";
              }
              catch( IOException e )
              {
            LogFile.TheLogFile.WriteLine( LogFile.DT_HTML, LogFile.EDebugLevel.Error, "Caught IO Exception : " + e.ToString() );

            threaderror.m_nError = 2; // 2 => abnormal abort.
            threaderror.m_sMessage = "";
              }
              catch( NullReferenceException e )
              {
            LogFile.TheLogFile.WriteLine( LogFile.DT_HTML, LogFile.EDebugLevel.Error, "Caught NullReference Exception : " + e.ToString() );

            threaderror.m_nError = 2; // 2 => abnormal abort.
            threaderror.m_sMessage = "";
              }
              catch( CHTMLException e )
              {
            threaderror.m_nError = 2; // 2 => abnormal abort.
            threaderror.m_sMessage = e.Message;
              }
              catch( Exception e )
              {
            LogFile.TheLogFile.WriteLine( LogFile.DT_HTML, LogFile.EDebugLevel.Error, "Caught Exception : " + e.ToString() );

            threaderror.m_nError = 2; // 2 => abnormal abort.
            threaderror.m_sMessage = "";
              }
              finally
              {
            LogFile.TheLogFile.WriteLine( LogFile.DT_HTML, LogFile.EDebugLevel.Note, "Thread ending..." );

            if( m_progressWindow != null )
            {
              m_progressWindow.End( threaderror );
            }
              }
        }