// drawgen.c // // This file draws the contents of the genealogy windows. It calls routines in gentree.c to calculate // where the boxes should go. // // by John D. de Boer #include "twist.h" #include "twindow.h" extern void *Sel; extern GlobalOpts Opt; // Utility static int JDToAstro(long JD) { short Y; jdtodate(JD,&Y,NULL,NULL); if (Y<0) Y++; return Y; } static void GenBox(Person *P, float Horz, float Top, float Margin, rect *R) { float Width; if (!P || !R) return; Width= gtr(P->gw1,P->gw2) + (2.0 * Margin); Horz-= Width / 2.0; setrect(R,Horz,Top - P->ght,Width,P->ght); } // Adjusting scrollbars when Total or Page changes static void CalcGenScrolls(window *W) { GenInfo *D; int MIN,MAX; if (!W) return; D=W->appdata; if (!D) return; MIN= left(D->total) - left(W->page); MAX= right(D->total) - right(W->page); setscrollfrac(W->hsb,MIN,MAX); MIN= top(D->total) - top(W->page); MAX= bottom(D->total) - bottom(W->page); setscrollfrac(W->vsb,-MIN,-MAX); } void ScrollGenToSubject(window *W) { int VAL; if (!W) return; // subject is drawn at (0,0) VAL= -(left(W->page) + right(W->page)) / 2.0; SetControlValue(W->hsb,VAL); VAL= -(top(W->page) + bottom(W->page)) / 2.0; SetControlValue(W->vsb,-VAL); } // Calculations to be done once when size is set, but not needed when scrolling occurs // This does all of the profile calculations, and sets D->total. static void TreeRect(const treeprofile *T, bool Up, genvars *G, rect *R) { int i; float L; if (!T || !G || !R) return; setrect(R,0,0,0,0); L= T->bottom - T->top; if (L<1) return; for (i=0;irow[i].left)); setright(R,gtr(right(*R),T->row[i].right)); } if (Up) settop(R,G->scale * G->slice * (L - 1)); else setbottom(R,G->scale * G->slice * (-L)); } static void CalcGenParameters(window *W, GenInfo *D) { if (!W || !D) return; D->var.size=Opt.size; textsize(W,D->var.size); if (D->compr) CGContextSetCharacterSpacing(W->q2d,-0.075 * Opt.size); //GetFontInfo(&(D->var.font)); D->var.font.ascent=Opt.size; D->var.font.descent= 0.25 * Opt.size; D->var.font.widMax= 0.8 * Opt.size; D->var.font.leading= 0.15 * Opt.size; D->var.line= D->var.font.ascent + D->var.font.descent + D->var.font.leading; D->var.scale= 0.21 * D->var.size; if (D->vert==0) D->var.scale*=0.77; if (D->vert==2) D->var.scale*=1.33; D->var.brh= (D->var.size / 2) - 1; if (D->vert==2) D->var.brh+=1; D->var.slice=3; D->var.margin= D->compr ? 2 : 4; D->var.space= D->compr ? 3 : 6; } void DoGenDrawCalcs(window *W) { GenInfo *D; Person *P; rect R; treeprofile *An,*De; if (!W) return; D=W->appdata; if (!D) return; P=D->pers; if (!P) return; if (P->nchild<0) MakeChildList(P); CalcGenParameters(W,D); setgenparameters(&(D->var)); resetgendone(); An=ancestorsprofile(W,P); resetgendone(); De=descendantsprofile(W,P); GenBox(P,0,0,D->var.margin,&(D->total)); if (An) { TreeRect(An,1,&(D->var),&R); D->total=rectunion(D->total,R); old(An); } if (De) { TreeRect(De,0,&(D->var),&R); D->total=rectunion(D->total,R); old(De); } inset(&(D->total),-4); D->recalc=0; } static void SetGenVars(window *W) { GenInfo *D; if (!W) return; D=W->appdata; if (!D) return; if (D->lastsize==Opt.size && !D->recalc) return; DoGenDrawCalcs(W); CalcGenScrolls(W); D->lastsize=Opt.size; } // Adjusting scrollbars when window size is changed void GenGrow(window *W) { SetGenVars(W); CalcGenScrolls(W); } // Global drawing variables static point Offset; // drawing offset static int ASTRO, // year of birth (astro.) of person whose genealogy is shown ROW,COL; // page row and column number, valid when printing static genvars GV; // drawing parameters static void SetDrawVars(window *W) { GenInfo *D; Person *P; if (!W) return; D=W->appdata; if (!D) return; P=D->pers; if (!P) return; GV=D->var; Offset=middle(W->page); if (W->hsb) Offset.x=-GetControlValue(W->hsb); if (W->vsb) Offset.y=+GetControlValue(W->vsb); ASTRO=JDToAstro(P->life.start.jd); } // Drawing utilities static int AstroToVert(int A) { return Offset.y + (GV.scale * (ASTRO - A)); } /* static int SliceToVert(int SERIAL) { return AstroToVert((GV.slice * SERIAL) - (GV.slice - 1)); } static void FrameGenTree(treeprofile *T, int H) { int i,L; if (!T) return; colour(red); L= T->bottom - T->top; moveto(W,H + T->row[0].right,SliceToVert(T->top)); for (i=0;irow[i].left,SliceToVert(T->top + i)); lineto(W,H + T->row[i].left,SliceToVert(T->top + i + 1)); } for (i= L - 1;i>=0;i--) { lineto(W,H + T->row[i].right,SliceToVert(T->top + i + 1)); lineto(W,H + T->row[i].right,SliceToVert(T->top + i)); } colour(black); }*/ // Drawing year lines static void DrawYearLine(window *W, int Y, bool Label, bool Century/*, bool Printing*/) { int A,V,ABSO,SPACE; char *S; bool LeftPage; LeftPage=(!W->printing || COL==0); A=Y; if (Y<1) A++; V=AstroToVert(A); ABSO=abso(Y); S=fig(ABSO); if (Y<0) S=cat(1,S," B.C."); SPACE= 5 + textwidth(W,S); penindex(W,Century ? lightbrown : beige); moveto(W,left(W->page) + (LeftPage ? SPACE : 0),V); lineto(W,right(W->page),V); stroke(W); if (!Label || !LeftPage) { old(S); return; } fillindex(W,chestnut); drawtextold(W,left(W->page) + 4,V - (Opt.size / 2.0),S); } static void DrawScale(window *W/*, bool Printing*/) { int i,Y1,Y2; Y1= ASTRO - ((top(W->page) - Offset.y) / GV.scale) - 10; Y1-=mod(Y1,10); if (Y1>=0) Y1++; Y2= ASTRO - ((bottom(W->page) - Offset.y) / GV.scale) + 10; for (i=Y1;i<=lsr(-10,Y2);i+=10) DrawYearLine(W,i,(i % 20)==0,(i % 100)==0/*,Printing*/); for (i=gtr(1,Y1);i<=Y2;i+=10) DrawYearLine(W,i,(i % 20)==1,(i % 100)==1/*,Printing*/); fillindex(W,black); penindex(W,black); } // Drawing trees static void ClearDrewUp(Person *P) { Person *M,*F; if (!P) return; P->gendrew=0; M=P->mother; if (M && M!=P) ClearDrewUp(M); F=P->father; if (F && F!=P) ClearDrewUp(F); } static void ClearDrewDown(Person *P) { int i; Person *C; if (!P) return; P->gendrew=0; for (i=0;inchild;i++) { C=(*(P->children))[i]; if (C && C!=P) ClearDrewDown(C); } } static void BoxColour(window *W, Person *P) { colour C; if (!P || !Opt.pink) setcolour(&C,white); else if (Female(P)) setrgb(&C,1.0,0.6,1.0); else setrgb(&C,0.5,0.75,1.0); fillcolour(W,&C); } static void DrawBox(window *W, Person *P, float H, float V) { GenInfo *D; rect R; float V1; if (!W || !P) return; D=W->appdata; if (!D) return; GenBox(P,H,V,GV.margin,&R); P->genrect=R; BoxColour(W,P); if (Opt.round && P->sex=='F') fillroundrect(W,R,0.6 * Opt.size); else fillrect(W,R); if (Highlight(W,P)) penwidth(W,2); if (Opt.round && P->sex=='F') frameroundrect(W,R,0.6 * Opt.size); else framerect(W,R); penwidth(W,1); V1= GV.font.leading + GV.font.ascent; fillindex(W,black); if (Opt.italic && P->sex=='F') textfont(W,"Helvetica Neue Italic",D->var.size); textcentre(W,H,top(R) - V1,P->gen1); textcentre(W,H,top(R) - V1 - GV.line,P->gen2); if (Opt.italic && P->sex=='F') textfont(W,"Helvetica Neue",D->var.size); } static void gendash(window *W) { float Len,Dash[2]; if (!W || !W->q2d) return; Len= Opt.size / 2.0; Dash[0]=Len; Dash[1]=Len; CGContextSetLineDash(W->q2d,Len / 2,Dash,2); } static void DrawFamilyTreeUp(window *W, Person *P, float Horz, float ChildVert, bool RadicalsOnly, int LineDown) { float V,L,R; Person *M,*F; if (!W || !P) return; L=0; R=0; // to avoid warnings V=AstroToVert(JDToAstro(P->life.start.jd)); //if (P==Sel) { treeprofile *T; resetgendone(); T=ancestorsprofile(P); FrameGenTree(T,Horz); old(T); }// if (P->gendrew) { if (RadicalsOnly) { savegraphics(W); beginpath(W); moveto(W,Horz,ChildVert); lineto(W,P->horzwas,V - P->ght - GV.brh); if (LineDown==2) gendash(W); stroke(W); restoregraphics(W); } return; } if (!RadicalsOnly) { if (LineDown) { savegraphics(W); beginpath(W); moveto(W,Horz,ChildVert); lineto(W,Horz,V - P->ght); if (LineDown==2) gendash(W); stroke(W); restoregraphics(W); } DrawBox(W,P,Horz,V); } P->horzwas=Horz; P->gendrew=1; F=P->father; if (F) { R= Horz + P->faright; DrawFamilyTreeUp(W,F,R,V + GV.brh,RadicalsOnly,P->fagrand ? 2 : 1); } M=P->mother; if (M) { L= Horz + P->moright; DrawFamilyTreeUp(W,M,L,V + GV.brh,RadicalsOnly,P->mogrand ? 2 : 1); } if (!RadicalsOnly && (M || F)) { beginpath(W); moveto(W,Horz,V); lineto(W,Horz,V + GV.brh); moveto(W,M ? L : R,V + GV.brh); lineto(W,F ? R : L,V + GV.brh); stroke(W); } } static void DrawFamilyTreeDown(window *W, Person *P, float Horz, int ParentVert, bool RadicalsOnly, int LineUp) { int i; float V,M,L,R; Person *C; bool Rule,Grand; if (!W || !P) return; L=0; R=0; // to avoid warnings V=AstroToVert(JDToAstro(P->life.start.jd)); //if (P==Sel) { treeprofile *T; resetgendone(); T=descendantsprofile(P); FrameGenTree(T,Horz); old(T); }// if (P->gendrew) { if (RadicalsOnly) { pencolour(W,black); // nec? savegraphics(W); beginpath(W); moveto(W,Horz,ParentVert); lineto(W,P->horzwas,V + GV.brh); if (LineUp==2) gendash(W); stroke(W); restoregraphics(W); } return; } if (!RadicalsOnly) { if (LineUp) { savegraphics(W); beginpath(W); moveto(W,Horz,ParentVert); lineto(W,Horz,V); if (LineUp==2) gendash(W); stroke(W); restoregraphics(W); } DrawBox(W,P,Horz,V); } P->horzwas=Horz; P->gendrew=1; Rule=0; for (i=0;inchild;i++) { C=(*(P->children))[i]; if (!C) continue; M= Horz + (Female(P) ? C->rightmo : C->rightfa); Grand=(*(P->chgrand))[i]; DrawFamilyTreeDown(W,C,M,V - P->ght - GV.brh,RadicalsOnly,Grand ? 2 : 1); if (!Rule) { L=R=M; Rule=1; } else R=M; } if (!RadicalsOnly && Rule) { beginpath(W); moveto(W,Horz,V - P->ght); lineto(W,Horz,V - P->ght - GV.brh); moveto(W,L,V - P->ght - GV.brh); lineto(W,R,V - P->ght - GV.brh); stroke(W); } } // Determining multi-page layout point Dst, // size of one page (not actually the physical paper, but the page Rect) Margin; // the margin necessary to centre a tree on multiple pages (left and top) int FitX, // how many printed pages the tree takes up (wide and high) FitY; //static long roundupdiv(long A, long B) { long L; L= A / B; if (A % B) L++; return L; } static void DeterminePageLayout(rect Tree, rect Page) { point Src; Dst.x=width(Page); Dst.y=height(Page); Src.x=width(Tree); Src.y=height(Tree); FitX= (Src.x / Dst.x) + 1.0; Margin.x= ((FitX * Dst.x) - Src.x) / 2.0; FitY= (Src.y / Dst.y) + 1.0; Margin.y= ((FitY * Dst.y) - Src.y) / 2.0; } // Entry points for drawing static void DrawFamilyTree(window *W/*, bool Printing*/) { GenInfo *D; Person *P; if (!W) return; D=W->appdata; if (!D) return; P=D->pers; if (!P) return; textfont(W,"Helvetica Neue",D->var.size); //textsize(W,D->var.size); if (D->compr) CGContextSetCharacterSpacing(W->q2d,-0.075 * Opt.size); DrawScale(W/*,Printing*/); ClearDrewUp(P); DrawFamilyTreeUp(W,P,Offset.x,0.0,1,0); ClearDrewUp(P); DrawFamilyTreeUp(W,P,Offset.x,0.0,0,0); ClearDrewDown(P); DrawFamilyTreeDown(W,P,Offset.x,0.0,1,0); ClearDrewDown(P); DrawFamilyTreeDown(W,P,Offset.x,0.0,0,0); } void DrawGen(window *W) { if (!W) return; SetGenVars(W); SetDrawVars(W); DrawFamilyTree(W); } short GenPages(window *W, rect R) { GenInfo *D; if (!W) return 0; D=W->appdata; if (!D) return 0; DeterminePageLayout(D->total,R); return FitX * FitY; } void PrintGen(window *W, short PAGE) { GenInfo *D; Person *P; if (!W) return; D=W->appdata; if (!D) return; P=D->pers; if (!P) return; DeterminePageLayout(D->total,W->page); ROW= FitY - (PAGE / FitX) - 1; COL= PAGE % FitX; Offset.x= left(W->page) + Margin.x - (left(D->total) + (COL * Dst.x)); Offset.y= bottom(W->page) + Margin.y - (bottom(D->total) + (ROW * Dst.y)); ASTRO=JDToAstro(P->life.start.jd); DrawFamilyTree(W); } bool GenPDFSize(window *W, rect *R) { GenInfo *D; if (!W || !R) return 0; D=W->appdata; if (!D) return 0; *R=D->total; return 1; } void GenPDFDraw(window *W) { GenInfo *D; if (!W) return; //DrawGen(W); return; // D=W->appdata; if (!D) return; SetGenVars(W); SetDrawVars(W); //Offset.x=0; Offset.y=0; Offset.x= left(W->page) + Margin.x - left(D->total); Offset.y= bottom(W->page) + Margin.y - bottom(D->total); //fillindex(W,white); fillrect(W,W->page); fillindex(W,black); DrawFamilyTree(W); } // Clicking in genealogy windows static bool BoxHit(window *W, Person *P, int H, int V, point Pt) { rect R; if (!W || !P) return 0; GenBox(P,H,V,GV.margin,&R); P->gendrew=1; return pointinrect(Pt,R); } static Person *FamilyTreeUpHit(window *W, Person *P, float Horz, point Pt) { float V; Person *M,*F,*H; if (!P || P->gendrew) return NULL; V=AstroToVert(JDToAstro(P->life.start.jd)); if (BoxHit(W,P,Horz,V,Pt)) return P; F=P->father; if (F) { H=FamilyTreeUpHit(W,F,Horz + P->faright,Pt); if (H) return H; } M=P->mother; if (M) { H=FamilyTreeUpHit(W,M,Horz + P->moright,Pt); if (H) return H; } return NULL; } static Person *FamilyTreeDownHit(window *W, Person *P, float Horz, point Pt) { int i; float V; Person *C,*H; if (!P || P->gendrew) return NULL; V=AstroToVert(JDToAstro(P->life.start.jd)); if (BoxHit(W,P,Horz,V,Pt)) return P; for (i=0;inchild;i++) { C=(*(P->children))[i]; if (!C) continue; H=FamilyTreeDownHit(W,C,Horz + (Female(P) ? C->rightmo : C->rightfa),Pt); if (H) return H; } return NULL; } Person *GenHit(mouseclick *M) { window *W; GenInfo *D; Person *P,*H; W=M->wind; if (!W) return NULL; D=W->appdata; if (!D) return NULL; P=D->pers; if (!P) return NULL; SetGenVars(W); SetDrawVars(W); ClearDrewUp(P); H=FamilyTreeUpHit(W,P,Offset.x,M->point); if (!H) { ClearDrewDown(P); H=FamilyTreeDownHit(W,P,Offset.x,M->point); } return H; } // Initialisation /* void InitGenDraw(void) { GV.slice=3; GV.size=10; GV.line=10; GV.margin=4; GV.scale=1.7; GV.space=6; GV.brh=3; } */