Index: src/finfo.c ================================================================== --- src/finfo.c +++ src/finfo.c @@ -345,11 +345,12 @@ blob_appendf(&title, "History of "); hyperlinked_path(zFilename, &title, 0); @ <h2>%b(&title)</h2> blob_reset(&title); pGraph = graph_init(); - @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div> + @ <div id="canvas" style="position:relative;width:1px;height:1px;" + @ onclick="clickOnGraph(event)"></div> @ <table id="timelineTable" class="timelineTable"> while( db_step(&q)==SQLITE_ROW ){ const char *zDate = db_column_text(&q, 0); const char *zCom = db_column_text(&q, 1); const char *zUser = db_column_text(&q, 2); @@ -370,11 +371,12 @@ if( uBg ){ zBgClr = hash_color(zUser); }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){ zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr); } - gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr, zBgClr, 0); + gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr, zBgClr, + zUuid, 0); if( memcmp(zDate, zPrevDate, 10) ){ sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate); @ <tr><td> @ <div class="divider">%s(zPrevDate)</div> @ </td><td></td><td></td></tr> @@ -446,8 +448,8 @@ @ <div id="grbtm" style="width:%d(pGraph->mxRail*20+30)px;"></div> @ </td><td></td></tr> } } @ </table> - timeline_output_graph_javascript(pGraph, 0); + timeline_output_graph_javascript(pGraph, 0, 1); style_footer(); } Index: src/graph.c ================================================================== --- src/graph.c +++ src/graph.c @@ -34,10 +34,11 @@ int rid; /* The rid for the check-in */ i8 nParent; /* Number of parents */ int *aParent; /* Array of parents. 0 element is primary .*/ char *zBranch; /* Branch name */ char *zBgClr; /* Background Color */ + char zUuid[17]; /* Check-in for file ID */ GraphRow *pNext; /* Next row down in the list of all rows */ GraphRow *pPrev; /* Previous row */ int idx; /* Row index. First is 1. 0 used for "none" */ @@ -175,10 +176,11 @@ int rid, /* RID for the check-in */ int nParent, /* Number of parents */ int *aParent, /* Array of parents */ const char *zBranch, /* Branch for this check-in */ const char *zBgClr, /* Background color. NULL or "" for white. */ + const char *zUuid, /* SHA1 hash of the object being graphed */ int isLeaf /* True if this row is a leaf */ ){ GraphRow *pRow; int nByte; @@ -188,10 +190,12 @@ pRow = (GraphRow*)safeMalloc( nByte ); pRow->aParent = (int*)&pRow[1]; pRow->rid = rid; pRow->nParent = nParent; pRow->zBranch = persistBranchName(p, zBranch); + if( zUuid==0 ) zUuid = ""; + sqlite3_snprintf(sizeof(pRow->zUuid), pRow->zUuid, "%s", zUuid); pRow->isLeaf = isLeaf; memset(pRow->aiRiser, -1, sizeof(pRow->aiRiser)); if( zBgClr==0 || zBgClr[0]==0 ) zBgClr = "white"; pRow->zBgClr = persistBranchName(p, zBgClr); memcpy(pRow->aParent, aParent, sizeof(aParent[0])*nParent); Index: src/timeline.c ================================================================== --- src/timeline.c +++ src/timeline.c @@ -211,11 +211,12 @@ if( tmFlags & TIMELINE_GRAPH ){ pGraph = graph_init(); /* style is not moved to css, because this is ** a technical div for the timeline graph */ - @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div> + @ <div id="canvas" style="position:relative;width:1px;height:1px;" + @ onclick="clickOnGraph(event)"></div> } db_static_prepare(&qbranch, "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid", TAG_BRANCH ); @@ -311,11 +312,12 @@ db_bind_int(&qparent, ":rid", rid); while( db_step(&qparent)==SQLITE_ROW && nParent<32 ){ aParent[nParent++] = db_column_int(&qparent, 0); } db_reset(&qparent); - gidx = graph_add_row(pGraph, rid, nParent, aParent, zBr, zBgClr, isLeaf); + gidx = graph_add_row(pGraph, rid, nParent, aParent, zBr, zBgClr, + zUuid, isLeaf); db_reset(&qbranch); @ <div id="m%d(gidx)"></div> } @</td> if( zBgClr && zBgClr[0] ){ @@ -491,18 +493,22 @@ @ </td><td></td></tr> } } @ </table> if( fchngQueryInit ) db_finalize(&fchngQuery); - timeline_output_graph_javascript(pGraph, (tmFlags & TIMELINE_DISJOINT)!=0); + timeline_output_graph_javascript(pGraph, (tmFlags & TIMELINE_DISJOINT)!=0, 0); } /* ** Generate all of the necessary javascript to generate a timeline ** graph. */ -void timeline_output_graph_javascript(GraphContext *pGraph, int omitDescenders){ +void timeline_output_graph_javascript( + GraphContext *pGraph, /* The graph to be displayed */ + int omitDescenders, /* True to omit descenders */ + int fileDiff /* True for file diff. False for check-in diff */ +){ if( pGraph && pGraph->nErr==0 && pGraph->nRow>0 ){ GraphRow *pRow; int i; char cSep; @ <script type="text/JavaScript"> @@ -535,10 +541,11 @@ ** mi: "merge-in". An array of integer x-coordinates from which ** merge arrows should be drawn into this node. If the value is ** negative, then the x-coordinate is the absolute value of mi[] ** and a thin merge-arrow descender is drawn to the bottom of ** the screen. + ** h: The SHA1 hash of the object being graphed */ cgi_printf("var rowinfo = [\n"); for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){ int mo = pRow->mergeOut; if( mo<0 ){ @@ -576,11 +583,11 @@ cgi_printf("%c%d", cSep, mi); cSep = ','; } } if( cSep=='[' ) cgi_printf("["); - cgi_printf("]}%s", pRow->pNext ? ",\n" : "];\n"); + cgi_printf("],h:\"%s\"}%s", pRow->zUuid, pRow->pNext ? ",\n" : "];\n"); } cgi_printf("var nrail = %d\n", pGraph->mxRail+1); graph_free(pGraph); @ var canvasDiv = gebi("canvas"); #if 0 @@ -598,10 +605,11 @@ @ n.style.top = y0+"px"; @ n.style.width = w+"px"; @ n.style.height = h+"px"; @ n.style.backgroundColor = color; @ canvasDiv.appendChild(n); + @ return n; @ } @ function absoluteY(id){ @ var obj = gebi(id); @ if( !obj ) return; @ var top = 0; @@ -699,10 +707,12 @@ @ }else{ @ drawThinArrow(y0,mx,p.x-5); @ } @ } @ } + @ var selBox = null; + @ var selRow = null; @ function renderGraph(){ @ var canvasDiv = gebi("canvas"); @ while( canvasDiv.hasChildNodes() ){ @ canvasDiv.removeChild(canvasDiv.firstChild); @ } @@ -736,10 +746,41 @@ @ }; @ } #endif @ for(var i in rowinfo){ @ drawNode(rowinfo[i], left, btm); + @ } + @ if( selRow!=null ) clickOnRow(selRow); + @ } + @ function clickOnGraph(event){ + @ var x=event.clientX-absoluteX("canvas")+window.pageXOffset; + @ var y=event.clientY-absoluteY("canvas")+window.pageYOffset; + @ for(var i in rowinfo){ + @ p = rowinfo[i]; + @ if( p.y<y-10 ) continue; + @ if( p.y>y+10 ) break; + @ if( p.x>x-10 && p.x<x+10 ){ + @ clickOnRow(p); + @ break; + @ } + @ } + @ } + @ function clickOnRow(p){ + @ if( selRow==null ){ + @ selBox = drawBox("red",p.x-2,p.y-2,p.x+3,p.y+3); + @ selRow = p; + @ }else if( selRow==p ){ + @ var canvasDiv = gebi("canvas"); + @ canvasDiv.removeChild(selBox); + @ selBox = null; + @ selRow = null; + @ }else{ + if( fileDiff ){ + @ location.href="%R/fdiff?v1="+selRow.h+"&v2="+p.h; + }else{ + @ location.href="%R/vdiff?from="+selRow.h+"&to="+p.h; + } @ } @ } @ var lastId = "m"+rowinfo[rowinfo.length-1].id; @ var lastY = 0; @ function checkHeight(){