// drawline.c // // This file draws the objects (people/events) in the timeline window. It draws them overtop of the scale. // // by John D. de Boer #include "twist.h" #include "twindow.h" extern GlobalOpts Opt; extern TimeGlobs Time; extern void *Sel; extern float TopRoom; extern int ORDER,CYCLE; extern void ***Order; //extern rect FlagRect; //bool Mono; // whether display/page is black & white float Centre, // horizontal coordinate of middle of screen BarRoom, // height of bar plus space between Scale; // pixels per day const int BIGWIND = 16384; static int BarHeight, // height of bar TextBase; // base line of text from top of bar //static bool Printing; // Utility static float Horz(const Day *D) { long JD; float L; if (!D) return 0; JD= (D->kind==timeStill) ? Time.jd2 : D->jd; L= Centre + ((JD - Time.jd) * Scale); return fbetween(L,-BIGWIND,BIGWIND); } static void FrameBar(window *W, rect R, bool Still) { if (!W) return; if (!Still) { framerect(W,R); return; } beginpath(W); moveto(W,right(R),top(R)); lineto(W,left(R),top(R)); lineto(W,left(R),bottom(R)); lineto(W,right(R),bottom(R)); stroke(W); } static void CalcBar(rect *R, float V, const Span *S) { if (!R || !S) return; setlbrt(R,Horz(&(S->start)),V - BarHeight,Horz(&(S->end)),V); //if (S->end.kind==timeStill || right(R)==left(R)) right(R)++; } static void DrawUncLines(window *W, rect Re, Span *S) { point C; float L,R,Horz; if (!W || !S) return; C=middle(Re); Horz= Scale * S->start.unc; if (Horz>1.0) { R=left(Re); L=fgtr(R - Horz,-BIGWIND); beginpath(W); moveto(W,R,C.y); lineto(W,L,C.y); move(W,0,-5); line(W,0,10); stroke(W); } Horz= Scale * S->end.unc; if (Horz>1 && S->end.kind!=timeInval) { L=right(Re); R=flsr(L + Horz,BIGWIND); beginpath(W); moveto(W,L,C.y); lineto(W,R,C.y); move(W,0,-5); line(W,0,10); stroke(W); } } void LineMapText(window *W, point P, const char *S, void *V) { if (Highlight(W,V)) boldtext(W,P.x,P.y,S,Opt.size); else drawtext(W,P.x,P.y,S); } // Tick marks for age/day counts static long YearTickScale(real Pix, long UNC) { if (Pix < 0.00075 || UNC>= 20000) return -1; if (Pix < 0.0015 || UNC>= 10000) return 200; if (Pix < 0.003 || UNC>= 5000) return 100; if (Pix < 0.0075 || UNC>= 2000) return 50; if (Pix < 0.015 || UNC>= 1000) return 20; if (Pix < 0.03 || UNC>= 500) return 10; if (Pix < 0.075 || UNC>= 200) return 5; if (Pix < 0.15 || UNC>= 100) return 2; return 1; } static long DayTickScale(real Pix, long UNC) { if (Pix < 0.01 || UNC>=2000) return -1; if (Pix < 0.02 || UNC>=1000) return 5000; if (Pix < 0.05 || UNC>= 500) return 2000; if (Pix < 0.1 || UNC>= 200) return 1000; if (Pix < 0.2 || UNC>= 100) return 500; if (Pix < 0.5 || UNC>= 50) return 200; if (Pix < 1.0 || UNC>= 20) return 100; if (Pix < 2.0 || UNC>= 10) return 50; if (Pix < 5.0 || UNC>= 5) return 20; if (Pix < 10.0 || UNC>= 2) return 10; return 5; } static void GoTickStyle(window *W, const colour *C) { int COLOUR; textsize(W,7); if (!W || !C) return; if (verydarkcolour(C)) COLOUR=lightgrey; else if (darkcolour(C)) COLOUR=white; else COLOUR=darkgrey; penindex(W,COLOUR); fillindex(W,COLOUR); } static void DrawYearTicks(window *W, const Span *S, rect R) { short Y1,M,D,Y2,Y; long JD; int i,YEARS; float H; Day Da; char *T; if (!S) return; YEARS=YearTickScale(Scale,S->start.unc); if (YEARS<0) return; jdtodate(S->start.jd,&Y1,&M,&D); jdtodate(S->end.jd,&Y2,NULL,NULL); for (i=YEARS;i < Y2 - Y1;i+=YEARS) { Y= Y1 + i; if (Y1<0 && Y>=0) Y++; JD=datetojd(Y,M,D); if (JD>=S->end.jd) return; Da.jd=JD; Da.kind=timeJD; H=Horz(&Da); if (H<1 || H>=right(W->page)) continue; beginpath(W); moveto(W,H,bottom(R) + 1); line(W,0,3); stroke(W); T=fig(i); textcentre(W,H,bottom(R) + 4,T); old(T); } } static void DrawDayTicks(window *W, const Span *S, rect R) { int i,DAYS; float H; Day D; char *T; if (!S) return; DAYS=DayTickScale(Scale,S->start.unc); if (DAYS<0) return; D=S->start; for (i=DAYS;i < S->end.jd - S->start.jd;i+=DAYS) { D.jd= S->start.jd + i; H=Horz(&D); if (H<1 || H>=right(W->page)) continue; beginpath(W); moveto(W,H,bottom(R) + 1); line(W,0,3); stroke(W); T=fig(i); textcentre(W,H,bottom(R) + 4,T); old(T); } } static void DrawTicks(window *W, const Span *S, rect R, const colour *C) { if (!S || S->start.kind!=timeJD) return; if (S->start.unc * Scale > 100.0) return; if (S->end.jd>Time.jd2) return; GoTickStyle(W,C); if (S->end.jd - S->start.jd > 731) DrawYearTicks(W,S,R); else DrawDayTicks(W,S,R); penindex(W,grey); fillindex(W,grey); textsize(W,Opt.size); } // Determining positions of people bars static bool PersonOffPage(window *W, Person *P) { Day *St,*En; float Allow; if (!W || !P) return 1; St=&(P->life.start); En=&(P->life.end); if (St->kind==timeInval) return 1; P->left=Horz(St); if (P->left>right(W->page)) return 1; if (En->kind==timeInval) return 1; P->right=Horz(En); Allow= W->printing ? (width(W->page) / 2) : 0; if (P->right < left(W->page) - Allow) return 1; return 0; } static bool ElidablePerson(Person *P) { Day *St,*En; long DUR; if (!P) return 0; St=&(P->life.start); En=&(P->life.end); if (!Opt.elide /*&& St->kind==timeJD && En->kind==timeJD*/) return 0; if (En->kind==timeStill) DUR= Time.jd2 - St->jd; else DUR= En->jd - St->jd; if (DUR > Time.dt * 3) return 1; return 0; } static void CalcPersonRect(Person *P, int PASS, float Vert) { if (!P) return; Vert= TopRoom - (Vert * BarRoom); if (PASS & 1) Vert-=2; P->vert=Vert; setlbrt(&(P->rect),P->left,Vert - BarHeight,P->right,Vert); if (/*P->life.end.kind==timeStill || */width(P->rect) < 1.0) P->rect.size.width=1.0; } // Drawing person bars static void PersonColour(Person *P, colour *C) { Place *Nat; if (!P || !C) return; //if (!P || Mono) { setcolour(C,white); return; } Nat=P->nation; if (!Nat) { setcolour(C,white); return; } *C=Nat->rgb; } static void FramePersonBar(window *W, Person *P) { FrameBar(W,P->rect,P->life.end.kind==timeStill); } static void FrameReign(window *W, Person *P, int i) { Span *S; rect R; S= P->reign + i; if (!W || S->start.kind==timeInval || S->end.kind==timeInval) return; CalcBar(&R,P->vert,S); penindex(W,orange); penwidth(W,2); if (W->printing) inset(&R,0.75); FrameBar(W,R,S->end.kind==timeStill); penwidth(W,1); if (Opt.unc) DrawUncLines(W,R,S); penindex(W,black); } static void DrawLineIcon(window *W, Person *P) { point Pt; if (!W || !P) return; Pt.x= P->left + 16; Pt.y= P->vert - Opt.size - 2; //if (Highlight(W,P)) OutlineIcon(PersonIcon(P),Pt); DrawIcon(W,PersonIcon(P),Pt,Opt.size / 10.0); } static void FillPersonBar(window *W, rect R, Place *Nat) { picture Flag; rect Src,Dest; float Width,Max; if (!W) return; if (!Opt.flag || !Nat || !Nat->flag) { fillrect(W,R); return; } Flag=Nat->flag; Src=picturerect(Flag); Width= height(R) * width(Src) / height(Src); Dest=R; Dest.size.width=Width; Max=width(R); if (Width>=Max) { drawpictureclip(W,Flag,Dest,R); return; } drawpicture(W,Flag,Dest); Flag=Nat->flag2; if (!Flag) return; Dest=R; setleft(&Dest,left(R) + Width - 1); drawpicture(W,Flag,Dest); } static point PersonTextPoint(Person *P) { point Pt; if (!P) { Pt.x=0; Pt.y=0; return Pt; } Pt.x= P->left + (Opt.ticon ? 16 : 3); Pt.y= P->vert - TextBase; return Pt; } static void DrawPersonBar(window *W, Person *P) { int i; colour C; point Pt; char *S; bool Dark,New; if (!W || !P) return; if (Opt.unc) { penindex(W,grey); DrawUncLines(W,P->rect,&(P->life)); } PersonColour(P,&C); Dark=darkcolour(&C); fillcolour(W,&C); FillPersonBar(W,P->rect,P->nation); if (Opt.ticks) DrawTicks(W,&(P->life),P->rect,&C); if (!Dark) { penindex(W,grey); FramePersonBar(W,P); } penindex(W,black); for (i=0;inrgn;i++) FrameReign(W,P,i); if (Highlight(W,P)) { penwidth(W,2); FramePersonBar(W,P); penwidth(W,1); } if (Opt.ticon) DrawLineIcon(W,P); S= (Scale<0.0055) ? P->surname : P->name; Pt=PersonTextPoint(P); if (Pt.xpage) && !W->printing) { Pt.x=0; S=cat(0,"...",S); New=1; } else New=0; //if (Highlight(W,P)) TextFace(bold); fillindex(W,black); LineMapText(W,Pt,S,P); if (Dark) { savegraphics(W); fillindex(W,white); cliprect(W,P->rect); LineMapText(W,Pt,S,P); restoregraphics(W); } //TextFace(0); if (New) old(S); } static void DrawDynastyLine(window *W, const Person *Parent, const Person *P) { float H,V1,V2; if (!W || !Parent || !P) return; H=left(P->rect); V1=top(Parent->rect); V2=top(P->rect); beginpath(W); moveto(W,H,V1); lineto(W,H,V2); stroke(W); } // Determining positions of event bars static bool EventOffPage(window *W, Event *E) { Day *St,*En; float Allow; if (!W || !E) return 1; St=&(E->date.start); En=&(E->date.end); if (St->kind==timeInval) return 1; E->left=Horz(St); if (E->left>right(W->page)) return 1; if (En->kind==timeJD) E->right=Horz(En); else E->right=E->left; Allow= W->printing ? (width(W->page) / 2) : 0; if (E->right < left(W->page) - Allow) return 1; return 0; } static bool ElidableEvent(Event *E) { Day *St,*En; long DUR,UNC; if (!E) return 0; St=&(E->date.start); En=&(E->date.end); if (St->kind!=timeJD || En->kind!=timeJD) return 0; DUR= En->jd - St->jd; if (Opt.elide && DUR >= 3 * Time.dt) return 1; UNC= St->unc + En->unc; if (DUR= 2 * Time.dt) return 1; return 0; } static void CalcEventRect(Event *E, int PASS, float Vert) { if (!E) return; E->vert= TopRoom - (Vert * BarRoom); if (PASS & 1) E->vert-=2; setlbrt(&(E->rect),E->left,E->vert - BarHeight,E->right,E->vert); //if (E->date.end.kind==timeStill) E->rect.right++; } // Drawing event bars static void FrameEventBar(window *W, Event *E, rect R) { if (!W || !E) return; if (Highlight(W,E)) { penwidth(W,2); penindex(W,black); } else penindex(W,grey); if (E->date.end.kind!=timeStill) framerect(W,R); else { beginpath(W); moveto(W,right(R),top(R)); lineto(W,left(R),top(R)); lineto(W,left(R),bottom(R)); lineto(W,right(R),bottom(R)); stroke(W); } penwidth(W,1); penindex(W,black); } static void DrawLineEventIcon(window *W, Event *E) { point Pt; if (!W || !E) return; Pt.x= E->left + 10; Pt.y= E->vert - (Opt.size / 2) - 8; //if (Highlight(W,E)) OutlineIcon(EventIcon(E),Pt); DrawIcon(W,EventIcon(E),Pt,Opt.size / 10.0); } static point EventTextPoint(Event *E) { point Pt; if (!E) { Pt.x=0; Pt.y=0; return Pt; } Pt.x= E->left + 12; Pt.y= E->vert - TextBase; return Pt; } static void DrawEventBar(window *W, Event *E) { colour C; point Pt; char *S; bool Dark,New; if (!W || !E) return; if (Opt.unc) { penindex(W,grey); DrawUncLines(W,E->rect,&(E->date)); } /*if (Mono) setcolour(&C,white); else*/ TypeColour(E,&C); Dark=darkcolour(&C); fillcolour(W,&C); fillrect(W,E->rect); if (Opt.ticks) DrawTicks(W,&(E->date),E->rect,&C); if (!Dark || Highlight(W,E)) FrameEventBar(W,E,E->rect); DrawLineEventIcon(W,E); S=E->name; Pt=EventTextPoint(E); if (Pt.xpage) && !W->printing) { Pt.x=0; S=cat(0,"...",S); New=1; } else New=0; fillindex(W,black); LineMapText(W,Pt,S,E); if (Dark) { savegraphics(W); fillindex(W,white); cliprect(W,E->rect); LineMapText(W,Pt,S,E); restoregraphics(W); } if (New) old(S); } // Drawing in time-line windows static int FIT, // number of bars that fit vertically in the window SELSERIAL; // SERIAL value for selection static const int MAXITEM = 500; // maximum number of items to draw before giving up static real Lap; static long LAPTIME; static real LapScale; static real LeftLap(long NEWTIME) { long SINCE; SINCE= NEWTIME - LAPTIME; return Lap * exp(-SINCE / LapScale); } static void DoCalcs(window *W) { int i,SERIAL,SHOWN; void *V; real LL,LapLimit; if (!W) return; FIT= (TopRoom - bottom(W->page)) / BarRoom; SHOWN=0; CYCLE=mod(CYCLE,FIT); SERIAL=CYCLE; LapScale= Time.dt / 2.5; LapLimit= 0.8 * FIT; Lap= 0.67 * LapLimit; LAPTIME=Time.jd1; for (i=0;i=MAXITEM && !W->printing) { P->seen=0; continue; } if (Opt.onmap && !SeenOnAMap(i)) { P->seen=0; continue; } if (ElidablePerson(P)) { P->seen=0; continue; } if (Opt.thin) { LL=LeftLap(P->sorttime); if (LL - P->inbnd.num > LapLimit && P!=Sel) { P->seen=0; continue; } Lap= LL + 1.0; LAPTIME=P->sorttime; } SERIAL++; if (PersonOffPage(W,P)) { P->seen=0; continue; } P->seen=1; CalcPersonRect(P,SERIAL / FIT,mod(SERIAL,FIT)); SHOWN++; } else if (VoidIsEvent(V)) { Event *E; E=(Event *)V; if (SHOWN>=MAXITEM && !W->printing) { E->seen=0; continue; } if (Opt.onmap && !SeenOnAMap(i)) { E->seen=0; continue; } if (ElidableEvent(E)) { E->seen=0; continue; } if (Opt.thin) { LL=LeftLap(E->sorttime); if (LL - E->inbnd.num > LapLimit && E!=Sel) { E->seen=0; continue; } Lap= LL + 1.0; LAPTIME=E->sorttime; } SERIAL++; if (EventOffPage(W,E)) { E->seen=0; continue; } E->seen=1; CalcEventRect(E,SERIAL / FIT,mod(SERIAL,FIT)); SHOWN++; } } } static void DrawDynastyLines(window *W) { int i; void *V; Person *P,*F,*M; if (!W) return; penwidth(W,2); penindex(W,darkgrey); for (i=0;iseen) continue; F=P->father; if (F && F->show && F->seen) { DrawDynastyLine(W,F,P); continue; } M=P->mother; if (M && M->show && M->seen) DrawDynastyLine(W,M,P); } penwidth(W,1); } static void DrawBars(window *W) { int i; void *V; Person *P; Event *E; if (Opt.dyn) DrawDynastyLines(W); for (i=0;iseen/* || W->printing*/) DrawPersonBar(W,P); } else if (VoidIsEvent(V)) { E=(Event *)V; if (E->seen /*|| W->printing*/) DrawEventBar(W,E); } } } static void SetDrawGlobs(window *W) { if (!W) return; textfont(W,"Geneva",Opt.size); BarRoom= Opt.size + 7; BarHeight= Opt.size + 4; TextBase= Opt.size - (Opt.size / 12) + 1; //Mono=monochrome(&Page); Scale= width(W->page) / (2.0 * Time.dt); } void DrawTimeLine(window *W) { point C; if (!W) return; SetControlValue(W->hsb,Time.year); C=middle(W->page); Centre=C.x; SetDrawGlobs(W); DrawTimeCoordinates(W,1,0); DoCalcs(W); DrawBars(W); } short TimeLinePages(window *W, rect R) { float P; if (!W) return 0; P= (Time.jd2 - Time.jd) / (float)(2.0 * Time.dt); return P + 1.5; } void PrintTimeLine(window *W, short PAGE) { point C; TimeGlobs SaveTime; if (!W) return; C=middle(W->page); Centre=C.x; SaveTime=Time; Time.jd+= PAGE * 2 * Time.dt; SetDrawGlobs(W); DrawTimeCoordinates(W,1,1); DoCalcs(W); DrawBars(W); Time=SaveTime; } bool LinePDFSize(window *W, rect *R) { if (!W) return 0; setrect(R,0,0,5184,648); // six feet by nine inches return 1; } void LineDrawPDF(window *W) { if (!W) return; DrawTimeLine(W); return;// Centre=234; // one half of (8.5 - 2 = 6.5 inches) SetDrawGlobs(W); Scale= Centre / (float)Time.dt; DrawTimeCoordinates(W,12,0); // 5184 / (2 * 234) = 11.1 DoCalcs(W); DrawBars(W); } // Clicking in time-line windows static bool StringHit(window *W, point Pt, point Q, const char *S) { float LEN; rect R; if (!W || !S) return 0; LEN=textwidth(W,S); if (Q.x<0) { Q.x=0; LEN+=textwidth(W,"..."); } setrect(&R,Q.x,Q.y,LEN,Opt.size); return pointinrect(Pt,R); } static bool PersonTimeLineHit(window *W, point Pt, Person *P) { point Q; if (!W || !P || !P->seen) return 0; if (pointinrect(Pt,P->rect)) return 1; Q=PersonTextPoint(P); //Pt.x-=Q.x; Pt.y-=Q.y; return StringHit(W,Pt,Q,P->name); } static bool EventTimeLineHit(window *W, point Pt, Event *E) { rect R; point Q; if (!W || !E || !E->seen) return 0; R=E->rect; setrect(&R,left(R) - 8,bottom(R),width(R) + 12,height(R) + 4); if (pointinrect(Pt,R)) return 1; Q=EventTextPoint(E); //Pt.x-=Q.x; Pt.y-=Q.y; return StringHit(W,Pt,Q,E->name); } static void *VoidTimeLineHit(window *W, point Pt) { int i; void *V; if (!W) return NULL; for (i= ORDER - 1;i>=0;i--) { V=(*Order)[i]; if (VoidIsPers(V) && PersonTimeLineHit(W,Pt,V)) return V; if (VoidIsEvent(V) && EventTimeLineHit(W,Pt,V)) return V; } return NULL; } void *TimeLineHit(mouseclick *M) { window *W; void *V; if (!M) return NULL; W=M->wind; if (!W) return NULL; textfont(W,"Geneva",Opt.size); V=VoidTimeLineHit(W,M->point); return V; } // Time focus static void TemporalFocus(Span *S) { long JD; if (!S || S->start.kind==timeInval) return; JD= S->start.jd; if (S->end.kind==timeJD) JD= (JD + S->end.jd) / 2; GoToJD(JD); } static void CycleFocus(void) { SELSERIAL-=CYCLE; CYCLE=mod((FIT / 2) - SELSERIAL,FIT); } static void PersonFocus(Person *P) { if (!P) return; TemporalFocus(&(P->life)); CycleFocus(); } static void EventFocus(Event *E) { if (!E) return; TemporalFocus(&(E->date)); CycleFocus(); } void TimeFocus(void) { if (!Sel) return; if (VoidIsPers(Sel)) PersonFocus(Sel); else if (VoidIsEvent(Sel)) EventFocus(Sel); }