/************************************************************************* * Copyright (c) 2011 AT&T Intellectual Property * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v10.html * * Contributors: Details at https://graphviz.org *************************************************************************/ #include #include #include #include "gui.h" #include #include #include #include "viewport.h" #include "frmobjectui.h" #include #include "gvprpipe.h" #include #include #include #include #include #include #include #include static int sel_node; static int sel_edge; static int sel_graph; static char *safestrdup(const char *src) { if (!src) return NULL; else return gv_strdup(src); } static int get_object_type(void) { if (gtk_toggle_button_get_active ((GtkToggleButton *) glade_xml_get_widget(xml, "attrRB0"))) return AGRAPH; if (gtk_toggle_button_get_active ((GtkToggleButton *) glade_xml_get_widget(xml, "attrRB1"))) return AGNODE; if (gtk_toggle_button_get_active ((GtkToggleButton *) glade_xml_get_widget(xml, "attrRB2"))) return AGEDGE; return -1; } static attr_t *new_attr(void) { return gv_alloc(sizeof(attr_t)); } static attr_t *new_attr_with_ref(Agsym_t * sym) { attr_t *a = new_attr(); a->name = gv_strdup(sym->name); switch (sym->kind) { case AGRAPH: a->objType[0] = 1; a->defValG = safestrdup(sym->defval); break; case AGNODE: a->objType[1] = 1; a->defValN = safestrdup(sym->defval); break; case AGEDGE: a->objType[2] = 1; a->defValE = safestrdup(sym->defval); break; default: UNREACHABLE(); } return a; } static attr_t *new_attr_ref(attr_t * refAttr) { attr_t *a = gv_alloc(sizeof(attr_t)); *a = *refAttr; a->defValG = safestrdup(refAttr->defValG); a->defValN = safestrdup(refAttr->defValN); a->defValE = safestrdup(refAttr->defValE); a->name = gv_strdup(refAttr->name); a->value = safestrdup(refAttr->value); return a; } static void reset_attr_list_widgets(attr_list * l) { int id; for (id = 0; id < MAX_FILTERED_ATTR_COUNT; id++) { gtk_label_set_text(l->fLabels[id], ""); } } // creates a new attr_list // attr_list is a basic stack implementation // with alphanumeric sorting functions // that uses quicksort static attr_list *attr_list_new(bool with_widgets) { int id; attr_list *l = gv_alloc(sizeof(attr_list)); /*create filter widgets */ if (with_widgets) { for (id = 0; id < MAX_FILTERED_ATTR_COUNT; id++) { l->fLabels[id] = (GtkLabel *) gtk_label_new(""); gtk_widget_add_events((GtkWidget *) l->fLabels[id], GDK_BUTTON_MOTION_MASK | GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL | GDK_VISIBILITY_NOTIFY_MASK); gtk_widget_show((GtkWidget *) l->fLabels[id]); Color_Widget_bg("blue", (GtkWidget *) l->fLabels[id]); gtk_fixed_put((GtkFixed *) glade_xml_get_widget(xml, "fixed6"), (GtkWidget *) l->fLabels[id], 10, 110 + id * 13); } } return l; } static int attr_compare_core(const void *key, const void *candidate) { const char *k = key; const attr_t *const *c = candidate; return strcasecmp(k, (*c)->name); } static int attr_compare(const attr_t **a, const attr_t **b) { return attr_compare_core((*a)->name, b); } static void attr_list_add(attr_list *l, attr_t *a) { if (!l || !a) return; attrs_append(&l->attributes, a); attrs_sort(&l->attributes, attr_compare); /*update indices */ for (size_t id = 0; id < attrs_size(&l->attributes); ++id) attrs_get(&l->attributes, id)->index = id; } static attr_data_type get_attr_data_type(char c) { switch (c) { case 'A': return attr_alpha; break; case 'F': return attr_float; break; case 'B': return attr_bool; break; case 'I': return attr_int; break; default: break; } return attr_alpha; } static void object_type_helper(strview_t a, int *t) { if (strview_str_eq(a, "GRAPH")) t[0] = 1; if (strview_str_eq(a, "CLUSTER")) t[0] = 1; if (strview_str_eq(a, "NODE")) t[1] = 1; if (strview_str_eq(a, "EDGE")) t[2] = 1; if (strview_str_eq(a, "ANY_ELEMENT")) { t[0] = 1; t[1] = 1; t[2] = 1; } } static void set_attr_object_type(const char *str, int *t) { strview_t a = strview(str, ' '); object_type_helper(a, t); while (true) { const char *start = a.data + a.size; if (!startswith(start, " or ")) { break; } start += strlen(" or "); const char *end = strstr(start, " or "); if (end == NULL) { a = strview(start, '\0'); } else { a = (strview_t){.data = start, .size = (size_t)(end - start)}; } object_type_helper(a, t); } } static attr_t *binarySearch(attr_list *l, const char *searchKey) { attrs_sync(&l->attributes); attr_t **attrp = bsearch(searchKey, attrs_front(&l->attributes), attrs_size(&l->attributes), sizeof(attr_t*), attr_compare_core); if (attrp != NULL) { return *attrp; } return NULL; } static attr_t *pBinarySearch(attr_list *l, const char *searchKey) { size_t low = 0; size_t high = attrs_size(&l->attributes) - 1; while (high != SIZE_MAX && low <= high) { size_t middle = (low + high) / 2; int res = strncasecmp(searchKey, attrs_get(&l->attributes, middle)->name, strlen(searchKey)); if (res == 0) { return attrs_get(&l->attributes, middle); } else if (res < 0) { high = middle - 1; } else { low = middle + 1; } } return NULL; } static void create_filtered_list(const char *prefix, attr_list *sl, attr_list *tl) { int res; attr_t *at; int objKind = get_object_type(); if (strlen(prefix) == 0) return; /*locate first occurrence */ at = pBinarySearch(sl, prefix); if (!at) return; res = 0; /*go backward to get the first */ while (at->index > 0 && res == 0) { at = attrs_get(&sl->attributes, at->index - 1); res = strncasecmp(prefix, at->name, strlen(prefix)); } res = 0; while (at->index < attrs_size(&sl->attributes) && res == 0) { at = attrs_get(&sl->attributes, at->index + 1); res = strncasecmp(prefix, at->name, strlen(prefix)); if (res == 0 && at->objType[objKind] == 1) attr_list_add(tl, new_attr_ref(at)); } } static void filter_attributes(const char *prefix, topview *t) { int tmp; attr_list *l = t->attributes; int objKind = get_object_type(); attr_list *fl = attr_list_new(false); reset_attr_list_widgets(l); create_filtered_list(prefix, l, fl); for (size_t ind = 0; ind < attrs_size(&fl->attributes); ++ind) { gtk_label_set_text(l->fLabels[ind], attrs_get(&fl->attributes, ind)->name); } Color_Widget_bg("white", glade_xml_get_widget(xml, "txtAttr")); if (attrs_is_empty(&fl->attributes)) Color_Widget_bg("red", glade_xml_get_widget(xml, "txtAttr")); /*a new attribute can be entered */ gtk_widget_show(glade_xml_get_widget(xml, "txtValue")); gtk_widget_show(glade_xml_get_widget(xml, "txtDefValue")); gtk_entry_set_text((GtkEntry *) glade_xml_get_widget(xml, "txtDefValue"), ""); gtk_entry_set_text((GtkEntry *) glade_xml_get_widget(xml, "txtValue"), ""); gtk_widget_set_sensitive(glade_xml_get_widget(xml, "txtDefValue"), 1); gtk_widget_show(glade_xml_get_widget(xml, "attrAddBtn")); gtk_widget_hide(glade_xml_get_widget(xml, "attrApplyBtn")); gtk_widget_hide(glade_xml_get_widget(xml, "attrApplyAllBtn")); gtk_widget_hide(glade_xml_get_widget(xml, "attrSearchBtn")); gtk_toggle_button_set_active((GtkToggleButton *) glade_xml_get_widget(xml, "attrProg"), 0); if (strlen(prefix) == 0) { gtk_widget_hide(glade_xml_get_widget(xml, "attrAddBtn")); gtk_widget_hide(glade_xml_get_widget(xml, "attrApplyBtn")); gtk_widget_hide(glade_xml_get_widget(xml, "attrApplyAllBtn")); gtk_widget_hide(glade_xml_get_widget(xml, "attrSearchBtn")); gtk_widget_hide(glade_xml_get_widget(xml, "attrAddBtn")); gtk_widget_hide(glade_xml_get_widget(xml, "txtValue")); gtk_widget_hide(glade_xml_get_widget(xml, "txtDefValue")); Color_Widget_bg("white", glade_xml_get_widget(xml, "txtAttr")); } for (size_t ind = 0; ind < attrs_size(&fl->attributes); ++ind) { if (strcasecmp(prefix, attrs_get(&fl->attributes, ind)->name) == 0) { // an existing attribute Color_Widget_bg("green", glade_xml_get_widget(xml, "txtAttr")); if (get_object_type() == AGRAPH) gtk_entry_set_text((GtkEntry *) glade_xml_get_widget(xml, "txtDefValue"), attrs_get(&fl->attributes, 0)->defValG); if (get_object_type() == AGNODE) gtk_entry_set_text((GtkEntry *) glade_xml_get_widget(xml, "txtDefValue"), attrs_get(&fl->attributes, 0)->defValN); if (get_object_type() == AGEDGE) gtk_entry_set_text((GtkEntry *) glade_xml_get_widget(xml, "txtDefValue"), attrs_get(&fl->attributes, 0)->defValE); gtk_widget_set_sensitive(glade_xml_get_widget (xml, "txtDefValue"), 0); gtk_widget_hide(glade_xml_get_widget(xml, "attrAddBtn")); gtk_widget_show(glade_xml_get_widget(xml, "attrApplyBtn")); gtk_widget_show(glade_xml_get_widget(xml, "attrApplyAllBtn")); gtk_widget_show(glade_xml_get_widget(xml, "attrSearchBtn")); gtk_toggle_button_set_active((GtkToggleButton *) glade_xml_get_widget(xml, "attrProg"), attrs_get(&fl->attributes, 0)->propagate); break; } } tmp = (objKind == AGNODE && sel_node) || (objKind == AGEDGE && sel_edge) || (objKind == AGRAPH && sel_graph); gtk_widget_set_sensitive(glade_xml_get_widget(xml, "attrApplyBtn"), tmp); } // attribute text changed call back _BB void on_txtAttr_changed(GtkWidget *widget, void *user_data) { (void)user_data; filter_attributes(gtk_entry_get_text((GtkEntry*)widget), view->Topview); } static void doApply(void) { char *attr_name; int prog; Agnode_t *v; Agedge_t *e; Agraph_t *g; int objKind; Agsym_t *sym; attr_t *attr; /*values to be applied to selected objects */ attr_name = (char *) gtk_entry_get_text((GtkEntry *) glade_xml_get_widget(xml, "txtAttr")); const char *def_val = gtk_entry_get_text((GtkEntry *) glade_xml_get_widget(xml, "txtDefValue")); const char *value = gtk_entry_get_text((GtkEntry *) glade_xml_get_widget(xml, "txtValue")); prog = gtk_toggle_button_get_active((GtkToggleButton *) glade_xml_get_widget(xml, "attrProg")); g = view->g[view->activeGraph]; objKind = get_object_type(); attr = binarySearch(view->Topview->attributes, attr_name); assert(attr); attr->propagate = prog; sym = agattr(g, objKind, attr_name, NULL); if (!sym) /*it shouldnt be null, just in case it is null */ sym = agattr(g, objKind, attr_name, def_val); /*graph */ if (objKind == AGRAPH) agset(g, attr_name, value); /*nodes */ else if (objKind == AGNODE) { for (v = agfstnode(g); v; v = agnxtnode(g, v)) { if (ND_selected(v)) agxset(v, sym, value); } } /*edges */ else if (objKind == AGEDGE) { for (v = agfstnode(g); v; v = agnxtnode(g, v)) { for (e = agfstout(g, v); e; e = agnxtout(g, e)) { if (ED_selected(e)) agxset(e, sym, value); } } } else fprintf(stderr, "on_attrApplyBtn_clicked: unknown object kind %d\n", objKind); } _BB void on_attrApplyBtn_clicked(GtkWidget *widget, void *user_data) { (void)widget; (void)user_data; doApply(); } _BB void on_attrRB0_clicked(GtkWidget *widget, void *user_data) { (void)widget; (void)user_data; filter_attributes(gtk_entry_get_text((GtkEntry*)glade_xml_get_widget(xml, "txtAttr")), view->Topview); } /* This is the action attached to the publish button on the attributes * window. What should happen? */ _BB void on_attrProg_toggled(GtkWidget *widget, void *user_data) { (void)widget; (void)user_data; /* FIX */ } _BB void on_attrAddBtn_clicked(GtkWidget *widget, void *user_data) { (void)widget; (void)user_data; char *attr_name; int objKind; topview *t; Agraph_t *g; attr_t *attr; objKind = get_object_type(); /*values to be applied to selected objects */ attr_name = (char *) gtk_entry_get_text((GtkEntry *) glade_xml_get_widget(xml, "txtAttr")); const char *defValue = gtk_entry_get_text((GtkEntry *) glade_xml_get_widget(xml, "txtDefValue")); g = view->g[view->activeGraph]; t = view->Topview; /*try to find first */ attr = binarySearch(t->attributes, attr_name); if (!attr) { attr = new_attr(); attr->index = 0; attr->name = safestrdup(attr_name); attr->type = attr_alpha; attr->value = gv_strdup(""); attr->widget = NULL; attr_list_add(t->attributes, attr); } attr->propagate = 0; if (objKind == AGRAPH) { agattr(g, AGRAPH, attr_name, defValue); attr->defValG = safestrdup(defValue); attr->objType[0] = 1; } /*nodes */ else if (objKind == AGNODE) { agattr(g, AGNODE, attr_name, defValue); attr->defValN = safestrdup(defValue); attr->objType[1] = 1; } else if (objKind == AGEDGE) { agattr(g, AGEDGE, attr_name, defValue); attr->defValE = safestrdup(defValue); attr->objType[2] = 1; } else fprintf(stderr, "on_attrAddBtn_clicked: unknown object kind %d\n", objKind); filter_attributes(attr_name, view->Topview); } attr_list *load_attr_list(Agraph_t * g) { attr_t *attr; attr_list *l; FILE *file; char buffer[BUFSIZ]; static char *smyrna_attrs; char *a; if (!smyrna_attrs) smyrna_attrs = smyrnaPath("attrs.txt"); g = view->g[view->activeGraph]; l = attr_list_new(true); file = fopen(smyrna_attrs, "r"); if (file != NULL) { for (size_t i = 0; fgets(buffer, sizeof(buffer), file) != NULL; ++i) { attr = new_attr(); a = strtok(buffer, ","); attr->index = i; attr->type = get_attr_data_type(a[0]); for (int idx = 0; (a = strtok(NULL, ",")); ++idx) { /*C,(0)color, (1)black, (2)EDGE Or NODE Or CLUSTER, (3)ALL_ENGINES */ switch (idx) { case 0: attr->name = gv_strdup(a); break; case 1: attr->defValG = gv_strdup(a); attr->defValN = gv_strdup(a); attr->defValE = gv_strdup(a); break; case 2: set_attr_object_type(a, attr->objType); break; default: break; } } attr_list_add(l, attr); } fclose(file); } for (Agsym_t *sym = NULL; (sym = agnxtattr(g, AGRAPH, sym)); ) { attr = binarySearch(l, sym->name); if (attr) attr->objType[0] = 1; else { attr = new_attr_with_ref(sym); attr_list_add(l, attr); } } for (Agsym_t *sym = NULL; (sym = agnxtattr(g, AGNODE, sym)); ) { attr = binarySearch(l, sym->name); if (attr) { attr->objType[1] = 1; } else { attr = new_attr_with_ref(sym); attr_list_add(l, attr); } } for (Agsym_t *sym = NULL; (sym = agnxtattr(g, AGEDGE, sym)); ) { attr = binarySearch(l, sym->name); if (attr) attr->objType[2] = 1; else { attr = new_attr_with_ref(sym); attr_list_add(l, attr); } } return l; } static void set_header_text(void) { int nodeCnt = 0; int edgeCnt = 0; char buf[512]; Agedge_t* ep; Agnode_t* v; Agraph_t* g; g = view->g[view->activeGraph]; for (v = agfstnode(g); v; v = agnxtnode(g, v)) { if (ND_selected(v)) nodeCnt++; for (ep = agfstout(g, v); ep; ep = agnxtout(g, ep)) { if (ND_selected(v)) edgeCnt++; } } sel_node = nodeCnt; sel_edge = edgeCnt; sel_graph = 1; snprintf(buf, sizeof(buf), "%d Nodes and %d edges selected", nodeCnt, edgeCnt); gtk_label_set_text((GtkLabel *) glade_xml_get_widget(xml, "label124"), buf); gtk_entry_set_text((GtkEntry *) glade_xml_get_widget(xml, "txtAttr"), ""); Color_Widget_bg("white", glade_xml_get_widget(xml, "fixed6")); } void showAttrsWidget(void) { gtk_widget_hide(glade_xml_get_widget(xml, "dlgSettings")); gtk_widget_show(glade_xml_get_widget(xml, "dlgSettings")); gtk_notebook_set_current_page((GtkNotebook *) glade_xml_get_widget(xml, "notebook3"), ATTR_NOTEBOOK_IDX); set_header_text(); filter_attributes("", view->Topview); } static void gvpr_select(const char *attrname, const char *regex_str, int objType) { char *bf2; int i, argc; agxbuf sf = {0}; if (objType == AGNODE) agxbprint(&sf, "N[%s==\"%s\"]{selected = \"1\"}", attrname, regex_str); else if (objType == AGEDGE) agxbprint(&sf, "E[%s==\"%s\"]{selected = \"1\"}", attrname, regex_str); bf2 = agxbdisown(&sf); argc = 1; if (*bf2 != '\0') argc++; char *argv[3] = {0}; size_t j = 0; argv[j++] = "smyrna"; argv[j++] = bf2; run_gvpr(view->g[view->activeGraph], j, argv); for (i = 1; i < argc; i++) free(argv[i]); set_header_text(); } _BB void on_attrSearchBtn_clicked(GtkWidget *widget, void *user_data) { (void)widget; (void)user_data; const char *attrname = gtk_entry_get_text((GtkEntry *) glade_xml_get_widget(xml, "txtAttr")); const char *regex_str = gtk_entry_get_text((GtkEntry *) glade_xml_get_widget(xml, "txtValue")); gvpr_select(attrname, regex_str, get_object_type()); }