#include #include #include #include #include #include #include #include #include #include "lkb-chart.h" #include "embedded-view.h" #include #include "lkb-avm.h" #include "font-info.h" // parse chart visualization int init_chart_view(embedded_view_t *view, int id, lkb_chart *chart, char *title); void update_chart(embedded_view_t *view, int x1, int y1, int x2, int y2); void event_chart(embedded_view_t *view, event_t ev); void postscript_chart(embedded_view_t *view); void latex_chart(embedded_view_t *view); void close_chart_view(embedded_view_t *view); void mouse_chart(embedded_view_t *view, int x, int y); void render_chart(lkb_chart *chart, int *DY, int *DX, int display); void handle_edge_drag(embedded_view_t *view, lkb_edge *edge); embedded_view_t prototype_chart_view = { init: (int (*)(struct embedded_view_t*, ...))init_chart_view, update: update_chart, event: event_chart, postscript: postscript_chart, latex: latex_chart, close: close_chart_view, mouse_moved: mouse_chart, }; void retain_chart(int id); void release_chart(int id); int style_text(int x, int y, int dx, int dy, char *t, int bold, int fs); struct chart_view_data { lkb_edge *mouse_edge; int shown_from, shown_to; char *shown_type; int id; lkb_chart *chart; char *title; }; #define cvdata ((struct chart_view_data*)view->private) font_info chart_word_font = {"helvetica", 12, fsRoman, {0,0,0}, 0}; font_info chart_edge_font = {"helvetica", 12, fsRoman, {0,0,0}, 0}; font_info chart_bar_font = {"helvetica", 12, fsRoman, {0,0,0}, 0}; int chart_margin = 20; int init_chart_view(embedded_view_t *view, int id, lkb_chart *chart, char *title) { int now_chart_width, now_chart_height; view->private = malloc(sizeof(*cvdata)); cvdata->chart = chart; if(!cvdata->chart) { fprintf(stderr, "Error: tried to browse a NULL chart structure!\n"); return -1; } cvdata->id = id; cvdata->mouse_edge = 0; cvdata->shown_from = 0; cvdata->shown_to = -1; cvdata->shown_type = 0; cvdata->title = title; //printf("nedges %d\n", chart->nedges); render_chart(cvdata->chart, &now_chart_height, &now_chart_width, 0); now_chart_width += chart_margin*2; now_chart_height += chart_margin*2; view->width = now_chart_width; view->height = now_chart_height; retain_chart(id); return 0; } void close_chart_view(embedded_view_t *view) { release_chart(cvdata->id); free(cvdata); } int ncids, *cids, *refs; void retain_chart(int id) { int i; for(i=0;ichart, &now_chart_height, &now_chart_width, 1); now_chart_width += chart_margin*2; now_chart_height += chart_margin*2; set_embedded_view_size(view, now_chart_width, now_chart_height); gettimeofday(&tv2, 0); secs = tv2.tv_sec - tv1.tv_sec; secs += ((float)tv2.tv_usec - tv1.tv_usec) / 1000000; //fprintf(stderr, "display took %.5f seconds\n", secs); if(x2-x1 == view->vis_width && y2-y1 == view->vis_height); show_span(view, cvdata->shown_from, cvdata->shown_to, 1, 1, cvdata->shown_type); } void event_chart(embedded_view_t *view, event_t ev) { switch(ev.type) { case YZ_MOUSE_DOWN: handle_chart_click(view, ev.x, ev.y, ev.button); break; } } show_span(embedded_view_t *view, int from, int to, int force, int from_view, char *type) { int ox, oy, cx1, cy1, cx2, cy2; int i, x = 5, div; int barh, texty; lkb_edge *e; barh = chart_bar_font.size*1.3 - 1; texty = chart_bar_font.size*1.05; if(from==-1)from=to; if(to==from)to++; if(force || cvdata->shown_from != from || cvdata->shown_to != to || (type && !cvdata->shown_type) || (cvdata->shown_type && !type) || (type && strcmp(cvdata->shown_type, type))) { if(from_view) { yzPopClipRect4p(&cx1, &cy1, &cx2, &cy2); yzPopOrigin2p(&ox, &oy); } yzBufferMode(YZ_BUFFER_BACK); //yzSelectFont("system"); yzPenColor(50000, 50000, 50000); yzRect(0, 0, view->use_width, barh); yzPenColor(0,0,0); yzOutlineRect(0, 0, view->use_width, barh); use_font(&chart_bar_font); cvdata->shown_from = from; cvdata->shown_to = to; for(i=0;ichart->nedges;i++) { e = cvdata->chart->edges+i; if(e->id>=0 || e->fromfrom>=to) continue; yzText(x, texty, e->abbr); x += yzStringSize(e->abbr, strlen(e->abbr)) + 5; } if(cvdata->shown_type != type) { if(cvdata->shown_type)free(cvdata->shown_type); cvdata->shown_type = type?strdup(type):0; } div = 220; if(view->use_width-div < 100)div = view->use_width-100; if(type)yzText(view->use_width-div+4, texty, type); yzPenColor(0,0,0); yzLine(view->use_width-div, 0, view->use_width-div, barh); yzBufferMode(YZ_BUFFER_BOTH); yzUpdateBuffer(YZ_BUFFER_FRONT); if(from_view) { yzPushOrigin(ox, oy); yzPushClipRect(cx1, cy1, cx2, cy2); } } } lkb_edge *pick_edge(lkb_chart *chart, int x, int y) { int i; lkb_edge *e; for(i=0;inedges;i++) { e = chart->edges+i; if(e->x > x || e->y < y)continue; if(e->x+e->dx < x || e->y-e->dy > y)continue; if(e->filter>1)continue; return e; } return 0; } void pick_cell(lkb_chart *chart, int x, int y, int *F, int *T) { int i; lkb_chart_cell *c; for(i=0;incells;i++) { c = chart->cells+i; if(c->x > x || c->y > y)continue; if(c->x+c->dx < x || c->y+c->dy < y)continue; *F = c->from; *T = c->to; return; } *F = 0; *T = -1; } void deselect_all_edges(lkb_chart *chart) { int i; for(i=0;inedges;i++)chart->edges[i].bold = 0; } lkb_edge *lookup_edge(lkb_chart *chart, int id) { int i; for(i=0;inedges;i++) if(chart->edges[i].id == id) return chart->edges+i; return 0; } void visit_daughters(lkb_chart *chart, lkb_edge *e, int recurse, void (*visitter)(lkb_edge *e)) { int i; lkb_edge *ep; for(i=0;indaughters;i++) { ep = lookup_edge(chart, e->daughters[i]); if(!ep) { fprintf(stderr, "WARNING: no such daughter as %d\n", e->daughters[i]); continue; } visitter(ep); if(recurse)visit_daughters(chart, ep, recurse-1, visitter); } } void visit_parents(lkb_chart *chart, lkb_edge *e, int recurse, void (*visitter)(lkb_edge *e)) { int i, j; lkb_edge *ep; for(i=0;inedges;i++) { ep = chart->edges+i; for(j=0;jndaughters;j++) if(ep->daughters[j] == e->id) break; if(j==ep->ndaughters)continue; // not a parent visitter(ep); if(recurse)visit_parents(chart, ep, recurse-1, visitter); } } void embolden_family(lkb_chart *chart, lkb_edge *e, int bold, int recurse, int parents, int daughters) { void embolden(lkb_edge *ep) { ep->bold = bold; } if(parents)visit_parents(chart, e, recurse, embolden); if(daughters)visit_daughters(chart, e, recurse, embolden); } void embolden_daughters(lkb_chart *chart, lkb_edge *e, int bold, int recurse) { embolden_family(chart, e, bold, recurse, 0, 1); } void embolden_parents(lkb_chart *chart, lkb_edge *e, int bold, int recurse) { embolden_family(chart, e, bold, recurse, 1, 0); } void filter_family(lkb_chart *chart, lkb_edge *e, int incr, int recurse, int parents, int daughters) { void filter(lkb_edge *ep) { ep->filter += incr; if(ep->filter < 0)ep->filter = 0; } if(parents)visit_parents(chart, e, recurse, filter); if(daughters)visit_daughters(chart, e, recurse, filter); } void filter_daughters(lkb_chart *chart, lkb_edge *e, int incr, int recurse) { filter_family(chart, e, incr, recurse, 0, 1); } void filter_parents(lkb_chart *chart, lkb_edge *e, int incr, int recurse) { filter_family(chart, e, incr, recurse, 1, 0); } void linked_edges(lkb_chart *chart, lkb_edge *e, int recurse) { deselect_all_edges(chart); e->bold = 2; embolden_daughters(chart, e, 1, recurse); embolden_parents(chart, e, -1, recurse); chart->hold_selections = 1; } int do_edge_popup(embedded_view_t *view, lkb_edge *e) { window_t *par; lkb_chart *chart = cvdata->chart; menu_data_t *md; char biglabel[128]; int id, i, j, ret = 0; md = yzNewMenuData("Edge Options"); if(e->id>=0) { yzAddMenuDataItem(md, "Local AVM", 6); yzAddMenuDataItem(md, "Parse Tree", 7); } yzAddMenuDataItem(md, "Linked Edges", 4); if(!e->filter)yzAddMenuDataItem(md, "Filter", 1); else yzAddMenuDataItem(md, "Unfilter", 2); yzAddMenuDataItem(md, "Filter Others", 5); yzAddMenuDataItem(md, "Restrict", 9); yzAddMenuDataItem(md, "Nuclear Family", 3); if(e->id<0) // orthography { yzAddMenuDataItem(md, "All Lexemes", 99); for(i=0;inedges;i++) { //if(chart->edges[i].from != chart->edges[i].to)continue; //if(chart->edges[i].ndaughters != 1)continue; // should always be false if(chart->edges[i].ndaughters == 0)continue; for(j=0;jedges[i].ndaughters;j++) if(chart->edges[i].daughters[j] == e->id)break; if(j == chart->edges[i].ndaughters)continue; // this orth is some daughter of this edge, so the edge is lexical sprintf(biglabel, "Lex: %d %s", chart->edges[i].id, chart->edges[i].type); yzAddMenuDataItem(md, biglabel, 100+chart->edges[i].id); } } else { yzAddMenuDataItem(md, "Grammar Entry", 8); } par = yzGetSelectedWindow(); yzShowMenu(md, &id, e->x + par->origin_x - 5, e->y + par->origin_y - 11); if(id >= 100) { // lexeme filtering lexfilt: for(i=0;inedges;i++) { if(chart->edges[i].from != chart->edges[i].to)continue; if(chart->edges[i].ndaughters != 1)continue; // should always be false if(chart->edges[i].daughters[0] != e->id)continue; if(chart->edges[i].id != id-100 && id!=99) { if(chart->edges[i].filter==0) { chart->edges[i].filter += 1; filter_parents(chart, chart->edges+i, 2, -1); } } else { if(chart->edges[i].filter != 0) { chart->edges[i].filter = 0; filter_parents(chart, chart->edges+i, -2, -1); } } } return 1; } switch(id) { case 0: break; // cancelled default: printf("unknown item %d\n", id); break; case 1: e->filter += 1; filter_parents(chart, e, 2, -1); ret = 1; break; case 2: e->filter -= 1; filter_parents(chart, e, -2, -1); ret = 1; break; case 5: for(i=0;inedges;i++)if(chart->edges[i].from==e->from && chart->edges[i].to==e->to) { if(e==chart->edges+i) { if(e->filter){chart->edges[i].filter = 0; filter_parents(chart, chart->edges+i, -2, -1);} } else { if(!e->filter){chart->edges[i].filter += 1; filter_parents(chart, chart->edges+i, 2, -1);} } } break; case 3: case 4: linked_edges(cvdata->chart, e, (id==3)?0:-1); ret = 1; break; case 6: lkb_browse_tree(cvdata->id, e->key, "avm"); break; case 7: lkb_browse_tree(cvdata->id, e->key, "tree"); break; case 8: lkb_browse_tree(cvdata->id, e->key, "entity"); break; case 9: restrict_to_edge(cvdata->chart, e); ret = 1; break; case 99: goto lexfilt; } // XXX should free memory here return ret; } void handle_edge_drag(embedded_view_t *view, lkb_edge *edge) { event_t ev; window_t *w, *prevw = 0; int relx, rely, acc = 0; extern window_t *dropping_window; extern lkb_avm *dropping_avm; extern int drop_target_id; extern char drop_target_path[1024]; dropping_window = yzGetSelectedWindow(); dropping_avm = 0; do { ev = yzGetUnfilteredEvent(10); if(ev.type == YZ_MOUSE_DRAG) { w = yzLocateWindow(ev.x2, ev.y2, &relx, &rely); if(w != prevw) { if(prevw)notify_drag(prevw, "avm", -1, -1); prevw = w; } if(w)acc = notify_drag(w, "avm", relx, rely); } } while(ev.type != YZ_MOUSE_UP); if(prevw)notify_drag(prevw, "avm", -1, -1); // end the drag //fprintf(stderr, "note: edge %d should unify into dag %d path %s\n", // edge->key, drop_target_id, drop_target_path); #define ROOT_AVM_PATH PATH_DIV_STR if(prevw && acc) lkb_send_unify( edge->key, ROOT_AVM_PATH, drop_target_id, drop_target_path); } reset_filters(lkb_chart *chart) { int i; for(i=0;inedges;i++) chart->edges[i].filter = 0; } extern lkb_chart *duplicate_chart(lkb_chart *chart); fork_browser(embedded_view_t *view) { lkb_chart *nchart; window_t *my_window = yzGetSelectedWindow(); nchart = duplicate_chart(cvdata->chart); browse_chart(cvdata->id, nchart, cvdata->title); yzSelectWindow(my_window); } static int do_other_popup(embedded_view_t *view, int x, int y) { window_t *par; lkb_chart *chart = cvdata->chart; menu_data_t *md; int id, ret = 0; void latex_chart(embedded_view_t *view); void postscript_chart(embedded_view_t *view); md = yzNewMenuData("Chart Options"); yzAddMenuDataItem(md, "Reset Filters", 1); yzAddMenuDataItem(md, "Fork Browser", 2); yzAddMenuDataItem(md, "Output Postscript", 3); //yzAddMenuDataItem(md, "Output LaTeX", 4); yzAddMenuDataItem(md, "Close Window", 5); par = yzGetSelectedWindow(); yzShowMenu(md, &id, x + par->origin_x, y + par->origin_y); switch(id) { case 0: break; // cancelled default: printf("unknown item %d\n", id); break; case 1: reset_filters(chart); ret = 1; break; case 2: fork_browser(view); break; case 3: postscript_chart(view); break; case 4: latex_chart(view); break; case 5: sim_key('q'); break; } // XXX should dispose memory here return ret; } handle_chart_click(embedded_view_t *view, int x, int y, int button) { lkb_edge *e = pick_edge(cvdata->chart, x, y); event_t ev; int update = 0; #define DRAG_DIST 10 int dx = 0, dy = 0; if(!e) { if(button==1 && !yzQueryKey(YZ_KEYCODE_CONTROL)) { cvdata->chart->hold_selections = 0; deselect_all_edges(cvdata->chart); update = 1; } else update = do_other_popup(view, x, y); } else { if(button!=1 || yzQueryKey(YZ_KEYCODE_CONTROL)) update = do_edge_popup(view, e); else { // potentially a drag do { ev = yzGetUnfilteredEvent(1000); if(ev.type==YZ_MOUSE_DRAG)dx += ev.x2 - ev.x, dy += ev.y2 - ev.y; } while((dx*dx + dy*dy < DRAG_DIST*DRAG_DIST) && ev.type != YZ_MOUSE_UP); if(dx*dx + dy*dy < DRAG_DIST*DRAG_DIST) { if(e->key >= 0)lkb_browse_tree(cvdata->id, e->key, "avm"); } else handle_edge_drag(view, e); } } if(update) { yzBufferMode(YZ_BUFFER_BACK); update_chart(view, 0, 0, view->width, view->height); yzUpdateBuffer(YZ_BUFFER_FRONT); } } void mouse_chart(embedded_view_t *view, int x, int y) { lkb_edge *e = pick_edge(cvdata->chart, x, y); int hold = cvdata->chart->hold_selections; int from, to; char display[1024]; if(!e) { if(!hold)deselect_all_edges(cvdata->chart); pick_cell(cvdata->chart, x, y, &from, &to); show_span(view, from, to, 0, 0, 0); } else { if(!hold)deselect_all_edges(cvdata->chart); snprintf(display, 1023, "%d %s", e->id, e->type); show_span(view, e->from, e->to, 0, 0, display); if(!hold)e->bold = 2; if(!hold)embolden_daughters(cvdata->chart, e, 1, -1); if(!hold)embolden_parents(cvdata->chart, e, -1, -1); } if(e != cvdata->mouse_edge) { cvdata->mouse_edge = e; yzDrawScrollArea(view->scroll_area); } } void latex_chart(embedded_view_t *view) { } void postscript_chart(embedded_view_t *view) { window_t *chart_ps, *old; int dy, dx, fd; float scale; char message[1024], fname[768] = "chart-print.ps"; sprintf(fname, "/tmp/chart.%d.ps", cvdata->id); unlink(fname); fd = open(fname, O_WRONLY | O_CREAT, 0664); chart_ps = (window_t*)yzPostScriptWindow("Parse Chart", 555, 800, fd); old = yzSelectWindow(chart_ps); if(view->width > view->height) { scale = 660.0 / view->width; if((550.0 / view->height) < scale) scale = 660.0 / view->height; yzPostScriptSetLandscape(); } else { scale = 660.0 / view->height; if((550.0 / view->width) < scale) scale = 660.0 / view->width; } extern int yzPostScriptSetScale(float); yzPostScriptSetScale(scale); yzPushOrigin(25, 25); render_chart(cvdata->chart, &dx, &dy, 1); yzPopOrigin(); yzPostScriptEndPage(); close(fd); sprintf(message, "Printed chart to `%s' at scale %.1f%%\n", fname, 100*scale); console_add(message); yzSelectWindow(old); } // layout/rendering code void render_chart(lkb_chart *chart, int *DY, int *DX, int display) { int i, y = chart_margin, len, cell_dx = 40; lkb_edge *e; struct font_info *pfi = 0; // calculate the sizes of all the edge labels for(i=0;inedges;i++) { e = chart->edges+i; if(e->id<0){ if(pfi!=&chart_word_font)use_font(pfi = &chart_word_font); } else { if(pfi!=&chart_edge_font)use_font(pfi = &chart_edge_font); } e->dx = yzStringSize(e->label, strlen(e->label)); e->dy = pfi->size * 1.5; // determine visible maximum label size if(!e->filter && e->dx > cell_dx)cell_dx = e->dx; } cell_dx += 10; chart->ncells = 0; // draw normal rows for(len=chart->size;len>0;len--) y = render_chart_row(chart, len, cell_dx*chart->size, y, display); //// draw lexemes //y = render_chart_row(chart, 0, cell_dx*chart->size, y, display); // draw orthographies y = render_orths(chart, cell_dx*chart->size, y, display); *DY = y - chart_margin; *DX = chart->size * cell_dx; } int render_chart_row(lkb_chart *chart, int len, int total_dx, int y, int display) { int y0 = y, cell_x, i, j, ex, ey, first; int ncell = (len==0)?chart->size:(chart->size-len+1); float dx = (float)total_dx / ncell; lkb_edge *e; lkb_chart_cell *cell; y += chart_edge_font.size*1.5; for(i=0;i<=ncell;i++) { cell_x = (float)chart_margin + dx * i; ex = cell_x + 3, ey = y0 + chart_edge_font.size*1.15; first = 1; for(j=0;jnedges;j++) { e = chart->edges+j; if(!(len==1 && e->to==e->from && e->from == i) && e->to-e->from != len || e->from != i)continue; if(e->ndaughters==0)continue; if(e->filter>1)continue; if(!first) { //if(display)yzText(ex, ey, ","); ex += 11; } if(ex + e->dx > cell_x + dx - 3 && ex != cell_x + 3) { // this entry would overflow and it's not the only entry on the line ex = cell_x + 3; ey += chart_edge_font.size*1.5; } e->x = ex; e->y = ey + chart_edge_font.size*0.3; if(display) { use_font(&chart_edge_font); if(e->filter) { strike_out(e->x, e->y, e->dx, e->dy); yzPenColor(40000, 40000, 40000); } style_text(ex, ey, e->dx, e->dy, e->label, e->bold, chart_edge_font.size); if(e->filter)yzPenColor(0,0,0); } ex += e->dx; first = 0; } ey += chart_edge_font.size*0.4; if(ey > y)y = ey; } if(display) { yzPenColor(0,0,0); for(i=0;i<=ncell;i++) { cell_x = (float)chart_margin + dx * i; yzLine(cell_x, y0, cell_x, y); if(icells+chart->ncells++; cell->from = i; cell->to = i+len; cell->x = cell_x; cell->y = y0; cell->dx = dx; cell->dy = y-y0; } } // lines on top and bottom yzLine(chart_margin, y0, chart_margin+total_dx, y0); yzLine(chart_margin, y, chart_margin+total_dx, y); } return y; } render_orths(struct lkb_chart *chart, int total_dx, int y, int display) { int cell_dx = total_dx / chart->size; int i, x = chart_margin; struct lkb_edge *e; //printf("rendering orths y=%d cell_dx = %d display=%d\n", y, cell_dx, display); for(i=0;inedges;i++) { e = chart->edges+i; if(e->ndaughters != 0)continue; //printf("orth %s\n", e->label); e->x = x + cell_dx/2 - e->dx/2; e->y = y+chart_word_font.size*1.5; if(display) { use_font(&chart_word_font); if(e->filter) { strike_out(e->x, e->y, e->dx, e->dy); yzPenColor(40000, 40000, 40000); } //printf("orth %s at %d %d\n", e->label, x+cell_dx/2-e->dx/2, y+chart_word_font.size*1.15); style_text(x + cell_dx/2 - e->dx/2, y+chart_word_font.size*1.15, e->dx, e->dy, e->label, e->bold, chart_word_font.size); if(e->filter)yzPenColor(0,0,0); } x += cell_dx; } return y + chart_word_font.size*1.5; } style_text(int x, int y, int dx, int dy, char *t, int bold, int fs) { if(bold==1) // red bold { yzPenColor(60000, 0, 0); yzText(x, y, t); yzText(x+1, y, t); yzPenColor(0,0,0); } else if(bold==-1) // blue bold { yzPenColor(0, 0, 60000); yzText(x, y, t); yzText(x+1, y, t); yzPenColor(0,0,0); } else if(bold==2) // distinguished element { if(0) // inverse video { yzPenColor(0,0,0); yzRect(x-1, y+0.3*fs-dy, x+1+dx, y+0.25*fs); yzPenColor(65535, 65535, 65535); yzText(x, y, t); yzPenColor(0,0,0); } else // box around bold { yzOutlineRect(x-1, y+0.5*fs-dy, x+1+dx, y+0.16*fs); yzText(x, y, t); yzText(x+1, y, t); } } else yzText(x, y, t); } strike_out(int x, int y, int dx, int dy) { yzLine(x, y-8, x+dx, y-8); } restrict_to_edge(lkb_chart *chart, lkb_edge *e) { int i; for(i=0;inedges;i++) chart->edges[i].filter = 2; e->filter = 0; filter_family(chart, e, -2, -1, 1, 1); } void chart_special_event(embedded_view_t *view, int cid, char *what, int edge) { int i; lkb_edge *e; if(cvdata->id != cid)return; for(i=0;ichart->nedges;i++) if(cvdata->chart->edges[i].id == edge) break; if(i==cvdata->chart->nedges)return; e = cvdata->chart->edges+i; if(!strcasecmp(what, "highlight")) { linked_edges(cvdata->chart, e, -1); yzDrawScrollArea(view->scroll_area); } else if(!strcasecmp(what, "restrict")) { restrict_to_edge(cvdata->chart, e); yzDrawScrollArea(view->scroll_area); } }