Diff
Not logged in

Differences From:

File src/timeline.c part of check-in [9be1b00392] - Rename the "vinfo" page to "ci" and the "vedit" page to "ci_edit". by drh on 2009-01-25 20:13:46. [view]

To:

File src/timeline.c part of check-in [7a2c37063a] - merge trunk into creole branch by bob on 2009-09-22 07:49:39. Also file src/timeline.c part of check-in [eb1db585a5] - Fix a C++-ism in timeline.c. by drh on 2009-09-14 16:56:29. [view]

@@ -55,9 +55,9 @@
   char zShortUuid[UUID_SIZE+1];
   sprintf(zShortUuid, "%.10s", zUuid);
   if( g.okHistory ){
     @ <a onmouseover='%s(zIn)("m%d(id)")' onmouseout='%s(zOut)("m%d(id)")'
-    @    href="%s(g.zBaseURL)/ci/%s(zUuid)">[%s(zShortUuid)]</a>
+    @    href="%s(g.zBaseURL)/vinfo/%s(zUuid)">[%s(zShortUuid)]</a>
   }else{
     @ <b onmouseover='%s(zIn)("m%d(id)")' onmouseout='%s(zOut)("m%d(id)")'>
     @ [%s(zShortUuid)]</b>
   }
@@ -72,8 +72,38 @@
       @ <a href="%s(g.zBaseURL)/diff?v2=%s(zV1)">[diff]</a>
     }else{
       @ <a href="%s(g.zBaseURL)/diff?v1=%s(zV1)&v2=%s(zV2)">[diff]</a>
     }
+  }
+}
+
+/*
+** Generate a hyperlink to a date & time.
+*/
+void hyperlink_to_date(const char *zDate, const char *zSuffix){
+  if( zSuffix==0 ) zSuffix = "";
+  if( g.okHistory ){
+    @ <a href="%s(g.zTop)/timeline?c=%T(zDate)">%s(zDate)</a>%s(zSuffix)
+  }else{
+    @ %s(zDate)%s(zSuffix)
+  }
+}
+
+/*
+** Generate a hyperlink to a user.  This will link to a timeline showing
+** events by that user.  If the date+time is specified, then the timeline
+** is centered on that date+time.
+*/
+void hyperlink_to_user(const char *zU, const char *zD, const char *zSuf){
+  if( zSuf==0 ) zSuf = "";
+  if( g.okHistory ){
+    if( zD && zD[0] ){
+      @ <a href="%s(g.zTop)/timeline?c=%T(zD)&u=%T(zU)">%h(zU)</a>%s(zSuf)
+    }else{
+      @ <a href="%s(g.zTop)/timeline?u=%T(zU)">%h(zU)</a>%s(zSuf)
+    }
+  }else{
+    @ %s(zU)
   }
 }
 
 /*
@@ -103,8 +133,9 @@
 */
 #if INTERFACE
 #define TIMELINE_ARTID    0x0001  /* Show artifact IDs on non-check-in lines */
 #define TIMELINE_LEAFONLY 0x0002  /* Show "Leaf", but not "Merge", "Fork" etc */
+#define TIMELINE_BRIEF    0x0004  /* Combine adjacent elements of same object */
 #endif
 
 /*
 ** Output a timeline in the web format given a query.  The query
@@ -118,10 +149,12 @@
 **    5.  Number of non-merge children
 **    6.  Number of parents
 **    7.  True if is a leaf
 **    8.  background color
-**    9.  type ("ci", "w")
+**    9.  type ("ci", "w", "t")
 **   10.  list of symbolic tags.
+**   11.  tagid for ticket or wiki
+**   12.  Short comment to user for repeated tickets and wiki
 */
 void www_print_timeline(
   Stmt *pQuery,          /* Query to implement the timeline */
   int tmFlags,           /* Flags controlling display behavior */
@@ -129,11 +162,13 @@
 ){
   int wikiFlags;
   int mxWikiLen;
   Blob comment;
+  int prevTagid = 0;
+  int suppressCnt = 0;
   char zPrevDate[20];
-  zPrevDate[0] = 0;
-
+
+  zPrevDate[0] = 0;
   mxWikiLen = db_get_int("timeline-max-comment", 0);
   if( db_get_boolean("timeline-block-markup", 0) ){
     wikiFlags = WIKI_INLINE;
   }else{
@@ -156,8 +191,31 @@
     const char *zDate = db_column_text(pQuery, 2);
     const char *zType = db_column_text(pQuery, 9);
     const char *zUser = db_column_text(pQuery, 4);
     const char *zTagList = db_column_text(pQuery, 10);
+    int tagid = db_column_int(pQuery, 11);
+    int commentColumn = 3;    /* Column containing comment text */
+    if( tagid ){
+      if( tagid==prevTagid ){
+        if( tmFlags & TIMELINE_BRIEF ){
+          suppressCnt++;
+          continue;
+        }else{
+          commentColumn = 12;
+        }
+      }
+    }
+    prevTagid = tagid;
+    if( suppressCnt ){
+      @ <tr><td><td><td>
+      @ <small><i>... %d(suppressCnt) similar
+      @ event%s(suppressCnt>1?"s":"") omitted.</i></small></tr>
+      suppressCnt = 0;
+    }
+    if( strcmp(zType,"div")==0 ){
+      @ <tr><td colspan=3><hr></td></tr>
+      continue;
+    }
     db_multi_exec("INSERT OR IGNORE INTO seen VALUES(%d)", rid);
     if( memcmp(zDate, zPrevDate, 10) ){
       sprintf(zPrevDate, "%.10s", zDate);
       @ <tr><td colspan=3>
@@ -206,9 +264,9 @@
       }
     }else if( (tmFlags & TIMELINE_ARTID)!=0 ){
       hyperlink_to_uuid(zUuid);
     }
-    db_column_blob(pQuery, 3, &comment);
+    db_column_blob(pQuery, commentColumn, &comment);
     if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
       Blob truncated;
       blob_zero(&truncated);
       blob_append(&truncated, blob_buffer(&comment), mxWikiLen);
@@ -247,9 +305,11 @@
     @   nparent INTEGER,
     @   isleaf BOOLEAN,
     @   bgcolor TEXT,
     @   etype TEXT,
-    @   taglist TEXT
+    @   taglist TEXT,
+    @   tagid INTEGER,
+    @   short TEXT
     @ )
   ;
   db_multi_exec(zSql);
 }
@@ -278,9 +338,11 @@
     @   bgcolor,
     @   event.type,
     @   (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref
     @     WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
-    @       AND tagxref.rid=blob.rid AND tagxref.tagtype>0)
+    @       AND tagxref.rid=blob.rid AND tagxref.tagtype>0),
+    @   tagid,
+    @   brief
     @  FROM event JOIN blob
     @ WHERE blob.rid=event.objid
   ;
   if( zBase==0 ){
@@ -302,15 +364,36 @@
   style_submenu_element(zMenuName, zMenuName, "%s",
                         url_render(pUrl, zParam, zValue, zRemove, 0));
 }
 
+
+/*
+** zDate is a localtime date.  Insert records into the
+** "timeline" table to cause <hr> to be inserted before and after
+** entries of that date.
+*/
+static void timeline_add_dividers(const char *zDate){
+  db_multi_exec(
+    "INSERT INTO timeline(rid,timestamp,etype)"
+    "VALUES(-1,datetime(%Q,'-1 second') || '.9','div')",
+    zDate
+  );
+  db_multi_exec(
+    "INSERT INTO timeline(rid,timestamp,etype)"
+    "VALUES(-2,datetime(%Q) || '.1','div')",
+     zDate
+  );
+}
+
+
 /*
 ** WEBPAGE: timeline
 **
 ** Query parameters:
 **
 **    a=TIMESTAMP    after this date
 **    b=TIMESTAMP    before this date.
+**    c=TIMESTAMP    "circa" this date.
 **    n=COUNT        number of events in output
 **    p=RID          artifact RID and up to COUNT parents and ancestors
 **    d=RID          artifact RID and up to COUNT descendants
 **    t=TAGID        show only check-ins with the given tagid
@@ -335,11 +418,13 @@
   const char *zUser = P("u");        /* All entries by this user if not NULL */
   const char *zType = PD("y","all"); /* Type of events.  All if NULL */
   const char *zAfter = P("a");       /* Events after this time */
   const char *zBefore = P("b");      /* Events before this time */
+  const char *zCirca = P("c");       /* Events near this time */
   const char *zTagName = P("t");     /* Show events with this tag */
   HQuery url;                        /* URL for various branch links */
   int tagid;                         /* Tag ID */
+  int tmFlags;                       /* Timeline flags */
 
   /* To view the timeline, must have permission to read project data.
   */
   login_check_credentials();
@@ -347,8 +432,13 @@
   if( zTagName ){
     tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTagName);
   }else{
     tagid = 0;
+  }
+  if( zType[0]=='a' ){
+    tmFlags = TIMELINE_BRIEF;
+  }else{
+    tmFlags = 0;
   }
 
   style_header("Timeline");
   login_anonymous_available();
@@ -361,9 +451,12 @@
     /* If p= or d= is present, ignore all other parameters other than n= */
     char *zUuid;
     int np, nd;
 
-    if( p_rid && d_rid && p_rid!=d_rid ) p_rid = d_rid;
+    if( p_rid && d_rid ){
+      if( p_rid!=d_rid ) p_rid = d_rid;
+      if( P("n")==0 ) nEntry = 10;
+    }
     db_multi_exec(
        "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
     );
     zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d",
@@ -370,30 +463,40 @@
                          p_rid ? p_rid : d_rid);
     blob_appendf(&sql, " AND event.objid IN ok");
     nd = 0;
     if( d_rid ){
-      compute_descendants(d_rid, nEntry);
+      compute_descendants(d_rid, nEntry+1);
       nd = db_int(0, "SELECT count(*)-1 FROM ok");
       if( nd>0 ){
         db_multi_exec("%s", blob_str(&sql));
         blob_appendf(&desc, "%d descendants", nd);
       }
+      timeline_add_dividers(
+        db_text("1","SELECT datetime(mtime,'localtime') FROM event"
+                    " WHERE objid=%d", d_rid)
+      );
       db_multi_exec("DELETE FROM ok");
     }
     if( p_rid ){
-      compute_ancestors(p_rid, nEntry);
+      compute_ancestors(p_rid, nEntry+1);
       np = db_int(0, "SELECT count(*)-1 FROM ok");
       if( np>0 ){
         if( nd>0 ) blob_appendf(&desc, " and ");
         blob_appendf(&desc, "%d ancestors", np);
         db_multi_exec("%s", blob_str(&sql));
       }
+      if( d_rid==0 ){
+        timeline_add_dividers(
+          db_text("1","SELECT datetime(mtime,'localtime') FROM event"
+                      " WHERE objid=%d", p_rid)
+        );
+      }
     }
     if( g.okHistory ){
       blob_appendf(&desc, " of <a href='%s/info/%s'>[%.10s]</a>",
                    g.zBaseURL, zUuid, zUuid);
     }else{
-      blob_appendf(&desc, " of [%.10s]", zUuid);
+      blob_appendf(&desc, " of check-in [%.10s]", zUuid);
     }
   }else{
     int n;
     const char *zEType = "event";
@@ -443,8 +546,30 @@
         url_add_parameter(&url, "b", zBefore);
        }else{
         zBefore = 0;
       }
+    }else if( zCirca ){
+      while( isspace(zCirca[0]) ){ zCirca++; }
+      if( zCirca[0] ){
+        double rCirca = db_double(0.0, "SELECT julianday(%Q, 'utc')", zCirca);
+        Blob sql2;
+        blob_init(&sql2, blob_str(&sql), -1);
+        blob_appendf(&sql2,
+            " AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d",
+            rCirca, (nEntry+1)/2
+        );
+        db_multi_exec("%s", blob_str(&sql2));
+        blob_reset(&sql2);
+        blob_appendf(&sql,
+            " AND event.mtime>=%f ORDER BY event.mtime ASC",
+            rCirca
+        );
+        nEntry -= (nEntry+1)/2;
+        timeline_add_dividers(zCirca);
+        url_add_parameter(&url, "c", zCirca);
+      }else{
+        zCirca = 0;
+      }
     }else{
       blob_appendf(&sql, " ORDER BY event.mtime DESC");
     }
     blob_appendf(&sql, " LIMIT %d", nEntry);
@@ -453,9 +578,9 @@
     n = db_int(0, "SELECT count(*) FROM timeline");
     if( n<nEntry && zAfter ){
       cgi_redirect(url_render(&url, "a", 0, "b", 0));
     }
-    if( zAfter==0 && zBefore==0 ){
+    if( zAfter==0 && zBefore==0 && zCirca==0 ){
       blob_appendf(&desc, "%d most recent %ss", n, zEType);
     }else{
       blob_appendf(&desc, "%d %ss", n, zEType);
     }
@@ -468,8 +593,10 @@
     if( zAfter ){
       blob_appendf(&desc, " occurring on or after %h.<br>", zAfter);
     }else if( zBefore ){
       blob_appendf(&desc, " occurring on or before %h.<br>", zBefore);
+    }else if( zCirca ){
+      blob_appendf(&desc, " occurring around %h.<br>", zCirca);
     }
     if( g.okHistory ){
       if( zAfter || n==nEntry ){
         zDate = db_text(0, "SELECT min(timestamp) FROM timeline");
@@ -505,9 +632,9 @@
   blob_zero(&sql);
   db_prepare(&q, "SELECT * FROM timeline ORDER BY timestamp DESC");
   @ <h2>%b(&desc)</h2>
   blob_reset(&desc);
-  www_print_timeline(&q, 0, 0);
+  www_print_timeline(&q, tmFlags, 0);
   db_finalize(&q);
 
   @ <script>
   @ var parentof = new Object();