#include <helianthus.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#define MAXREC 1000
#define MARGINX 30.0
#define MARGINY 15.0
#define MARGINLINE0 30.0
#define MARGINLINE1 40.0
#define BASELINE 22.0
#define PADDING 15.0
#define SEPARATOR 15.0
#define TEXTWIDTH 300.0
typedef struct {
char text[128];
double w;
double h;
} Date;
typedef struct {
char text[1024];
double w;
double h;
} Text;
typedef struct {
char text[128];
int index;
int begin;
int end;
double w;
double y0;
double y1;
} Part;
typedef struct {
double x, w;
} Column;
typedef struct {
Date date;
Text text;
Part part;
} BaseRecord;
typedef struct {
Date *date;
Text *text;
Part *part;
double y;
} Record;
BaseRecord recordsSrc[MAXREC];
Record records[MAXREC];
Part *parts[MAXREC];
Date *dates[MAXREC];
Column columns[MAXREC];
int partCount;
int dateCount;
int recCount;
int colCount;
double datesWidth;
double fullWidth, fullHeight;
double dx, dy;
double mx, my;
double zm = 1;
int curRec;
double curFade;
void updateLayout();
double calcWidth(char *b, char *e) {
char c = *e;
*e = 0;
TextLayout tl = createTextLayout(b);
*e = c;
double w = textLayoutGetWidth(tl);
textLayoutDestroy(tl);
return w;
}
void wordWrap(char *buf, double maxWidth) {
int len = strlen(buf);
if (calcWidth(buf, buf + len) <= maxWidth) return;
char *last = NULL;
for(char *p = buf + len - 1; p > buf; --p) {
if (isspace(*p)) {
last = p;
if (calcWidth(buf, p) < maxWidth)
{ *p = '\n'; wordWrap(p+1, maxWidth); return; }
}
}
if (!last) return;
*last = '\n';
wordWrap(last+1, maxWidth);
}
void load(const char *filename) {
curRec = 0;
curFade = 0;
recCount = 0;
partCount = 0;
dateCount = 0;
colCount = 0;
datesWidth = 0;
memset(recordsSrc, 0, sizeof(recordsSrc));
memset(records, 0, sizeof(records));
memset(parts, 0, sizeof(parts));
memset(dates, 0, sizeof(dates));
memset(columns, 0, sizeof(columns));
FILE *f = fopen(filename, "r");
if (!f) {
printf("cannot open file for read: %s\n", filename);
return;
}
int mode = 0;
BaseRecord *rs = recordsSrc;
char *p = rs->date.text;
char *e = p + sizeof(rs->date.text);
while(1) {
int c = fgetc(f);
if (c <= 0 || c >= 256) break;
if (c == '\t') {
if (mode == 0) {
mode = 1; p = rs->text.text; e = p + sizeof(rs->text.text);
} else
if (mode == 1) {
mode = 2; p = rs->part.text; e = p + sizeof(rs->part.text);
} else
if (mode == 2) {
mode = 3;
}
} else
if (c == '\n') {
if (rs->date.text[0] || rs->text.text[0] || rs->part.text[0]) {
++recCount; ++rs;
if (recCount >= MAXREC) break;
}
mode = 0; p = rs->date.text; e = p + sizeof(rs->date.text);
} else
if (c != '\r' && p+1 < e) {
*p++ = c;
}
}
fclose(f);
if (recCount < MAXREC)
if (rs->date.text[0] || rs->text.text[0] || rs->part.text[0])
++recCount;
for(int i = 0; i < recCount; ++i) {
Record *r = &records[i];
BaseRecord *rs = &recordsSrc[i];
wordWrap(rs->text.text, TEXTWIDTH);
for(int j = 0; j <= i; ++j) {
BaseRecord *rrs = &recordsSrc[j];
if (!r->date && !strcmp(rs->date.text, rrs->date.text)) r->date = &rrs->date;
if (!r->text && !strcmp(rs->text.text, rrs->text.text)) r->text = &rrs->text;
if (!r->part && !strcmp(rs->part.text, rrs->part.text)) r->part = &rrs->part;
}
if (r->part == &rs->part)
parts[partCount++] = r->part;
if (r->date == &rs->date)
dates[dateCount++] = r->date;
TextLayout tl = createTextLayout(r->text->text);
r->text->w = textLayoutGetWidth(tl);
r->text->h = textLayoutGetHeight(tl);
textLayoutDestroy(tl);
tl = createTextLayout(r->date->text);
r->date->w = textLayoutGetWidth(tl);
r->date->h = textLayoutGetHeight(tl);
textLayoutDestroy(tl);
if (r->part->w < r->text->w)
r->part->w = r->text->w;
if (datesWidth < r->date->w)
datesWidth = r->date->w;
}
updateLayout();
}
void writeUnwrapped(const char *buf, FILE *f) {
while(*buf) {
fputc(*buf == '\n' ? ' ' : *buf, f);
++buf;
}
}
void save(const char *filename) {
FILE *f = fopen(filename, "w");
if (!f) {
printf("cannot open file for write: %s\n", filename);
return;
}
for(int i = 0; i < recCount; ++i) {
Record *r = &records[i];
writeUnwrapped(r->date->text, f); fputc('\t', f);
writeUnwrapped(r->text->text, f); fputc('\t', f);
writeUnwrapped(r->part->text, f); fputc('\n', f);
}
fclose(f);
printf("saved into: %s\n", filename);
}
void updateLayout() {
colCount = 0;
for(int i = 0; i < partCount; ++i) {
parts[i]->index = -1;
columns[i].w = 0;
}
double y = MARGINY + PADDING;
for(int i = 0; i < recCount; ++i) {
Record *r = &records[i];
Part *p = r->part;
if (p->index < 0) {
int idx = 0;
while(p->text[idx] == '~') ++idx;
while(1) {
int found = TRUE;
for(int j = i+1; j < recCount; ++j)
if (records[j].part->index == idx)
found = FALSE;
if (found) break;
++idx;
}
p->index = idx;
p->begin = i;
p->y0 = y;
if (colCount <= idx) colCount = idx + 1;
if (columns[idx].w < p->w) columns[idx].w = p->w;
}
if (i > 0) {
if (p == records[i-1].part) {
y += SEPARATOR;
} else {
for(int j = i-1; j >= 0; --j) {
if (records[j].part->index == p->index) {
double py = records[j].part->y1 + 2*PADDING + MARGINY;
if (y < py) y = py;
}
}
}
}
r->y = y;
p->end = i + 1;
if (p->begin == i) p->y0 = r->y;
p->y1 = y + r->text->h;
y = p->y1;
}
double x = MARGINX + PADDING + datesWidth + PADDING + MARGINLINE0 + MARGINLINE1 + MARGINX;
for(int i = 0; i < colCount; ++i) {
columns[i].x = x;
x += columns[i].w + 2*PADDING + MARGINX;
}
fullWidth = x;
fullHeight = y + PADDING + MARGINY;
}
void drawRect(double x, double y, double w, double h) {
noFill();
stroke(COLOR_WHITE);
strokeWidth(4);
rectRounded( x - PADDING, y - PADDING, w + 2*PADDING, h + 2*PADDING, PADDING );
fill(colorByRGBA(1, 1, 1, 0.75));
stroke(COLOR_BLUE);
strokeWidth(2);
rectRounded( x - PADDING, y - PADDING, w + 2*PADDING, h + 2*PADDING, PADDING );
}
void drawRecords() {
saveState();
strokeWidth(2);
stroke(COLOR_BLUE);
double mr = PADDING/4.0;
// lines
double xl = MARGINX + PADDING + datesWidth + PADDING + MARGINLINE0;
double xd = xl - MARGINLINE0;
double h = 0;
if (recCount > 0)
h = records[recCount-1].y + records[recCount-1].text->h + MARGINY + PADDING;
line(xl, 0, xl, h);
for(int i = 0; i < recCount; ++i) {
Record *r = &records[i];
double x = columns[ r->part->index ].x;
double y = r->y + BASELINE;
line(xd, y, x, y);
}
// text rects
for(int i = 0; i < partCount; ++i) {
Part *p = parts[i];
Column *c = &columns[p->index];
drawRect(c->x + PADDING, p->y0, p->w, p->y1 - p->y0);
}
// circles
fill(COLOR_WHITE);
for(int i = 0; i < recCount; ++i) {
Record *r = &records[i];
double x = columns[ r->part->index ].x;
double y = r->y + BASELINE;
double mrr = i == curRec ? mr + curFade*mr : mr;
circle(xl, y, mrr);
circle(x, y, mrr);
}
// texts
noFill();
stroke(COLOR_BLACK);
for(int i = 0; i < recCount; ++i) {
Record *r = &records[i];
Part *p = r->part;
Date *d = r->date;
double x = columns[ p->index ].x + PADDING;
double y = r->y;
text(x, y, r->text->text);
text(xd - PADDING - d->w, y, d->text);
}
restoreState();
}
void saveImage(const char *filename) {
double s = 1;
Framebuffer fb = createFramebuffer((int)(fullWidth*s) + 1, (int)(fullHeight*s) + 1);
saveState();
target(fb);
clear();
zoom(s);
drawRecords();
if (viewportSave(filename))
printf("image saved into: %s\n", filename);
restoreState();
framebufferDestroy(fb);
}
void init() {
if (fileExists("data/output/history/ancient-dates.txt"))
load("data/output/history/ancient-dates.txt");
else
load("data/history/ancient-dates.txt");
dx = -fullWidth/2;
dy = -450;
}
void draw() {
double w = windowGetWidth();
double h = windowGetHeight();
double pmx = mx, pmy = my;
mx = mouseX(), my = mouseY();
if (mouseDown("left")) {
dx += (mx - pmx)/zm;
dy += (my - pmy)/zm;
} else
if (mouseDown("right")) {
zm *= pow(2.0, -(my - pmy)/500.0);
}
if (keyEventGetCount(KEYEVENT_KEY_DOWN))
curFade = 1;
if (keyWentDown("up") && curRec > 0) {
if (keyDown("any ctrl")) {
Record r;
memcpy(&r, &records[curRec], sizeof(r));
memcpy(&records[curRec], &records[curRec-1], sizeof(r));
memcpy(&records[curRec-1], &r, sizeof(r));
updateLayout();
}
--curRec;
} else
if (keyWentDown("down") && curRec < recCount-1) {
if (keyDown("any ctrl")) {
Record r;
memcpy(&r, &records[curRec], sizeof(r));
memcpy(&records[curRec], &records[curRec+1], sizeof(r));
memcpy(&records[curRec+1], &r, sizeof(r));
updateLayout();
}
++curRec;
} else
if (keyWentDown("s")) {
save("data/output/history/ancient-dates.txt");
} else
if (keyWentDown("i")) {
curFade = 0;
saveImage("data/output/history/ancient-dates.png");
}
saveState();
translate(w/2, h/2);
zoom(zm);
translate(dx, dy);
drawRecords();
restoreState();
curFade -= windowGetFrameTime()*2;
if (curFade < 0) curFade = 0;
}
int main() {
windowSetResizable(TRUE);
windowSetVariableFrameRate();
windowSetInit(&init);
windowSetDraw(&draw);
windowRun();
return 0;
}