/* Dia -- an diagram creation/manipulation program * Copyright (C) 1998 Alexander Larsson * * SADT diagram support Copyright(C) 2000 Cyrille Chepelov * This file has been forked (sigh!) from Alex's zigzagline.c. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include "config.h" #include "intl.h" #include "object.h" #include "neworth_conn.h" #include "connectionpoint.h" #include "render.h" #include "attributes.h" #include "widgets.h" #include "message.h" #include "color.h" #include "lazyprops.h" #include "sadt.h" #include "pixmaps/arrow.xpm" #define ARROW_CORNER_RADIUS .75 #define ARROW_LINE_WIDTH 0.10 #define ARROW_HEAD_LENGTH .8 #define ARROW_HEAD_WIDTH .8 #define ARROW_HEAD_TYPE ARROW_FILLED_TRIANGLE #define ARROW_COLOR color_black #define ARROW_DOT_LOFFSET .4 #define ARROW_DOT_WOFFSET .5 #define ARROW_DOT_RADIUS .25 #define ARROW_PARENS_WOFFSET .5 #define ARROW_PARENS_LOFFSET .55 #define ARROW_PARENS_LENGTH 1.0 #define HANDLE_MIDDLE HANDLE_CUSTOM1 typedef enum { SADT_ARROW_NORMAL, SADT_ARROW_IMPORTED, SADT_ARROW_IMPLIED, SADT_ARROW_DOTTED, SADT_ARROW_DISABLED } Sadtarrow_style; typedef struct _SadtarrowPropertiesDialog SadtarrowPropertiesDialog; typedef struct _SadtarrowDefaultsDialog SadtarrowDefaultsDialog; typedef struct _SadtarrowState SadtarrowState; struct _SadtarrowState { ObjectState obj_state; Sadtarrow_style style; gboolean autogray; }; typedef struct _Sadtarrow { NewOrthConn orth; Sadtarrow_style style; gboolean autogray; } Sadtarrow; struct _SadtarrowPropertiesDialog { AttributeDialog dialog; Sadtarrow *parent; EnumAttribute style; BoolAttribute autogray; }; typedef struct _SadtarrowDefaults { Sadtarrow_style style; gboolean autogray; } SadtarrowDefaults; struct _SadtarrowDefaultsDialog { AttributeDialog dialog; SadtarrowDefaults *parent; EnumAttribute style; BoolAttribute autogray; }; static SadtarrowPropertiesDialog *sadtarrow_properties_dialog; static SadtarrowDefaultsDialog *sadtarrow_defaults_dialog; static SadtarrowDefaults sadtarrow_defaults; static void sadtarrow_move_handle(Sadtarrow *sadtarrow, Handle *handle, Point *to, HandleMoveReason reason, ModifierKeys modifiers); static void sadtarrow_move(Sadtarrow *sadtarrow, Point *to); static void sadtarrow_select(Sadtarrow *sadtarrow, Point *clicked_point, Renderer *interactive_renderer); static void sadtarrow_draw(Sadtarrow *sadtarrow, Renderer *renderer); static Object *sadtarrow_create(Point *startpoint, void *user_data, Handle **handle1, Handle **handle2); static real sadtarrow_distance_from(Sadtarrow *sadtarrow, Point *point); static void sadtarrow_update_data(Sadtarrow *sadtarrow); static void sadtarrow_destroy(Sadtarrow *sadtarrow); static Object *sadtarrow_copy(Sadtarrow *sadtarrow); static PROPDLG_TYPE sadtarrow_get_properties(Sadtarrow *sadtarrow); static ObjectChange *sadtarrow_apply_properties(Sadtarrow *sadtarrow); static DiaMenu *sadtarrow_get_object_menu(Sadtarrow *sadtarrow, Point *clickedpoint); static SadtarrowState *sadtarrow_get_state(Sadtarrow *sadtarrow); static void sadtarrow_set_state(Sadtarrow *sadtarrow, SadtarrowState *state); static void sadtarrow_save(Sadtarrow *sadtarrow, ObjectNode obj_node, const char *filename); static Object *sadtarrow_load(ObjectNode obj_node, int version, const char *filename); static PROPDLG_TYPE sadtarrow_get_defaults(); static void sadtarrow_apply_defaults(); static ObjectTypeOps sadtarrow_type_ops = { (CreateFunc)sadtarrow_create, /* create */ (LoadFunc) sadtarrow_load, /* load */ (SaveFunc) sadtarrow_save, /* save */ (GetDefaultsFunc) sadtarrow_get_defaults, (ApplyDefaultsFunc) sadtarrow_apply_defaults }; ObjectType sadtarrow_type = { "SADT - arrow", /* name */ 0, /* version */ (char **) arrow_xpm, /* pixmap */ &sadtarrow_type_ops /* ops */ }; static ObjectOps sadtarrow_ops = { (DestroyFunc) sadtarrow_destroy, (DrawFunc) sadtarrow_draw, (DistanceFunc) sadtarrow_distance_from, (SelectFunc) sadtarrow_select, (CopyFunc) sadtarrow_copy, (MoveFunc) sadtarrow_move, (MoveHandleFunc) sadtarrow_move_handle, (GetPropertiesFunc) sadtarrow_get_properties, (ApplyPropertiesFunc) sadtarrow_apply_properties, (ObjectMenuFunc) sadtarrow_get_object_menu }; static ObjectChange * sadtarrow_apply_properties(Sadtarrow *sadtarrow) { ObjectState *old_state; SadtarrowPropertiesDialog *dlg = sadtarrow_properties_dialog; PROPDLG_SANITY_CHECK(dlg,sadtarrow); old_state = (ObjectState *)sadtarrow_get_state(sadtarrow); PROPDLG_APPLY_ENUM(dlg,style); PROPDLG_APPLY_BOOL(dlg,autogray); sadtarrow_update_data(sadtarrow); return new_object_state_change((Object *)sadtarrow, old_state, (GetStateFunc)sadtarrow_get_state, (SetStateFunc)sadtarrow_set_state); } PropDlgEnumEntry flow_style[] = { { N_("Normal"),SADT_ARROW_NORMAL,NULL }, { N_("Import resource (not shown upstairs)"),SADT_ARROW_IMPORTED,NULL }, { N_("Imply resource (not shown downstairs)"),SADT_ARROW_IMPLIED, NULL }, { N_("Dotted arrow"),SADT_ARROW_DOTTED, NULL }, { N_("disable arrow heads"),SADT_ARROW_DISABLED, NULL }, { NULL}}; static PROPDLG_TYPE sadtarrow_get_properties(Sadtarrow *sadtarrow) { SadtarrowPropertiesDialog *dlg = sadtarrow_properties_dialog; PROPDLG_CREATE(dlg,sadtarrow); PROPDLG_SHOW_ENUM(dlg,style,_("Flow style:"),flow_style); PROPDLG_SHOW_BOOL(dlg,autogray,_("Automatically gray vertical flows:")); PROPDLG_READY(dlg); sadtarrow_properties_dialog = dlg; PROPDLG_RETURN(dlg); } static void init_default_values() { static int defaults_initialized = 0; if (!defaults_initialized) { sadtarrow_defaults.style = SADT_ARROW_NORMAL; sadtarrow_defaults.autogray = TRUE; defaults_initialized = 1; } } static void sadtarrow_apply_defaults() { SadtarrowDefaultsDialog *dlg = sadtarrow_defaults_dialog; PROPDLG_APPLY_ENUM(dlg,style); PROPDLG_APPLY_BOOL(dlg,autogray); } static PROPDLG_TYPE sadtarrow_get_defaults() { SadtarrowDefaultsDialog *dlg = sadtarrow_defaults_dialog; init_default_values(); PROPDLG_CREATE(dlg, &sadtarrow_defaults); PROPDLG_SHOW_ENUM(dlg,style,_("Flow style:"),flow_style); PROPDLG_SHOW_BOOL(dlg,autogray,_("Automatically gray vertical flows:")); PROPDLG_READY(dlg); sadtarrow_defaults_dialog = dlg; PROPDLG_RETURN(dlg); } static real sadtarrow_distance_from(Sadtarrow *sadtarrow, Point *point) { NewOrthConn *orth = &sadtarrow->orth; return neworthconn_distance_from(orth, point, ARROW_LINE_WIDTH); } static void sadtarrow_select(Sadtarrow *sadtarrow, Point *clicked_point, Renderer *interactive_renderer) { neworthconn_update_data(&sadtarrow->orth); } static void sadtarrow_move_handle(Sadtarrow *sadtarrow, Handle *handle, Point *to, HandleMoveReason reason, ModifierKeys modifiers) { assert(sadtarrow!=NULL); assert(handle!=NULL); assert(to!=NULL); neworthconn_move_handle(&sadtarrow->orth, handle, to, reason); sadtarrow_update_data(sadtarrow); } static void sadtarrow_move(Sadtarrow *sadtarrow, Point *to) { neworthconn_move(&sadtarrow->orth, to); sadtarrow_update_data(sadtarrow); } static void draw_arrowhead(Renderer *renderer, Point *end, Point *vect, Color *col); static void draw_dot(Renderer *renderer, Point *end, Point *vect, Color *col); static void draw_parenthesis(Renderer *renderer, Point *end, Point *vect, Color *col); #define GBASE .45 #define GMULT .55 static void sadtarrow_draw(Sadtarrow *sadtarrow, Renderer *renderer) { NewOrthConn *orth = &sadtarrow->orth; Point *points; int n; int i; Point *p; real zzr; Color col; points = &orth->points[0]; n = orth->numpoints; renderer->ops->set_linewidth(renderer, ARROW_LINE_WIDTH); renderer->ops->set_linestyle(renderer, LINESTYLE_SOLID); renderer->ops->set_linecaps(renderer, LINECAPS_BUTT); col = ARROW_COLOR; if (sadtarrow->autogray && (orth->orientation[0] == VERTICAL) && (orth->orientation[orth->numpoints-2] == VERTICAL)) { col.red = GBASE + (GMULT*col.red); col.green = GBASE + (GMULT*col.green); col.blue = GBASE + (GMULT*col.blue); } /* Now we draw what's in essence a rounded-corner zig-zag line */ p = points; zzr = ARROW_CORNER_RADIUS; /* This code would welcome some optimisations. In the meantime, it sorta works. */ for (i=0; i<(n-2); i++,p++) { real len1,len2; real rr,cosa,sina,cosb,sinb,/*cosg,*/sing,alpha,beta,/*gamma,*/ca,cb; Point v1,v2,vca,vcb; Point X,Y,m1,m2,M,C,A,B; rr = zzr; len1 = distance_point_point(p,p+1); if ((len1/2) < rr) rr = len1/2; len2 = distance_point_point(p+1,p+2); if ((len2/2) < rr) rr = len2/2; M.x = (*(p+1)).x; X.x = p->x; Y.x = (*(p+2)).x; M.y = (*(p+1)).y; X.y = p->y; Y.y = (*(p+2)).y; m1.x = (i==0)?(p->x):((p->x + M.x) / 2); m1.y = (i==0)?(p->y):((p->y + M.y) / 2); m2.x = (i==(n-3))?(Y.x):((M.x + Y.x) / 2); m2.y = (i==(n-3))?(Y.y):((M.y + Y.y) / 2); if (rr < .01) { /* too small corner : we do it a bit faster */ renderer->ops->draw_line(renderer,&m1,&M,&col); renderer->ops->draw_line(renderer,&M,&m2,&col); } else { /* full rendering. We know len1 and len2 are nonzero. */ v1.x = (M.x - X.x) / len1; v1.y = (M.y - X.y) / len1; v2.x = (Y.x - M.x) / len2; v2.y = (Y.y - M.y) / len2; A.x = M.x - (v1.x * rr); A.y = M.y - (v1.y * rr); renderer->ops->draw_line(renderer,&m1,&A,&col); B.x = M.x + (v2.x * rr); B.y = M.y + (v2.y * rr); renderer->ops->draw_line(renderer,&B,&m2,&col); C.x = A.x + (v2.x * rr); /* (or B.x - v1.x*rr) */ C.y = A.y + (v2.y * rr); vca.x = A.x - C.x; vca.y = -(A.y - C.y); /* @#$! coordinate system */ vcb.x = B.x - C.x; vcb.y = -(B.y - C.y); ca = distance_point_point(&C,&A); cb = distance_point_point(&C,&B); if ((ca > 1E-7) && (cb > 1E-7)) { cosa = vca.x / ca; sina = vca.y / ca; cosb = vcb.x / cb; sinb = vcb.y / cb; /*cosg = ((M.x-X.x)*(Y.x-M.x) + (M.y-X.y)*(Y.y-M.y))/(len1*len2);*/ sing = (-(M.x-X.x)*(Y.y-M.y) + (Y.x-M.x)*(M.y-X.y)) / (len1*len2); alpha = acos(cosa) * 180.0 / M_PI; if (sina < 0.0) alpha = -alpha; beta = acos(cosb) * 180.0 / M_PI ; if (sinb < 0.0) beta = -beta; /* we'll keep gamma in radians, since we're only interested in its sign */ /* after all, we don't even need to compute cos(gamma), and we can use sin(gamma) to extract the sign of gamma itself. */ if (alpha < 0.0) alpha += 360.0; if (beta < 0.0) beta += 360.0; if (sing /* gamma*/ < 0) { /* if this happens, we swap alpha and beta, in order to draw the other portion of circle */ real tau; tau = beta; beta=alpha; alpha = tau; } renderer->ops->draw_arc(renderer,&C,rr*2,rr*2,alpha,beta, &col); } } } /* depending on the exact arrow type, we'll draw different gizmos (arrow heads, dots, parenthesis) at different places. */ /* XXX : chop off a little (.1) the starting and ending segments, so that their ends don't show up in front of the arrow heads ? Ugh. */ switch (sadtarrow->style) { case SADT_ARROW_NORMAL: draw_arrowhead(renderer,&points[n-1], &points[n-2],&col); break; case SADT_ARROW_IMPORTED: draw_arrowhead(renderer,&points[n-1], &points[n-2],&col); draw_parenthesis(renderer,&points[0],&points[1],&col); break; case SADT_ARROW_IMPLIED: draw_arrowhead(renderer,&points[n-1], &points[n-2],&col); draw_parenthesis(renderer,&points[n-1],&points[n-2],&col); break; case SADT_ARROW_DOTTED: draw_arrowhead(renderer,&points[n-1], &points[n-2],&col); draw_arrowhead(renderer,&points[0], &points[1],&col); draw_dot(renderer,&points[n-1], &points[n-2],&col); draw_dot(renderer,&points[0], &points[1],&col); break; case SADT_ARROW_DISABLED: /* No arrow heads */ break; } } static void draw_arrowhead(Renderer *renderer, Point *end, Point *vect, Color *col) { arrow_draw(renderer, ARROW_HEAD_TYPE, end, vect, ARROW_HEAD_LENGTH, ARROW_HEAD_WIDTH, ARROW_LINE_WIDTH, col, &color_white); } static void draw_dot(Renderer *renderer, Point *end, Point *vect, Color *col) { Point vv,vp,vt,pt; real vlen; vv = *end; point_sub(&vv,vect); vlen = distance_point_point(vect,end); if (vlen < 1E-7) return; point_scale(&vv,1/vlen); vp.y = vv.x; vp.x = -vv.y; pt = *end; vt = vp; point_scale(&vt,ARROW_DOT_WOFFSET); point_add(&pt,&vt); vt = vv; point_scale(&vt,-ARROW_DOT_LOFFSET); point_add(&pt,&vt); renderer->ops->set_fillstyle(renderer,FILLSTYLE_SOLID); renderer->ops->fill_ellipse(renderer,&pt, ARROW_DOT_RADIUS,ARROW_DOT_RADIUS, col); } static void draw_parenthesis(Renderer *renderer, Point *end, Point *vect, Color *col) { Point vv,vp,vt1,vt2; BezPoint curve1[2]; BezPoint curve2[2]; real vlen; vv = *end; point_sub(&vv,vect); vlen = distance_point_point(vect,end); if (vlen < 1E-7) return; point_scale(&vv,1/vlen); vp.y = vv.x; vp.x = -vv.y; curve1[0].type = curve2[0].type = BEZ_MOVE_TO; curve1[0].p1 = curve2[0].p1 = *end; vt1 = vv; point_scale(&vt1,-ARROW_PARENS_LOFFSET - (.5*ARROW_PARENS_LENGTH)); point_add(&curve1[0].p1,&vt1); point_add(&curve2[0].p1,&vt1); /* gcc, work for me, please. */ vt2 = vp; point_scale(&vt2,ARROW_PARENS_WOFFSET); point_add(&curve1[0].p1,&vt2); point_sub(&curve2[0].p1,&vt2); vt2 = vp; vt1 = vv; point_scale(&vt1,2.0*ARROW_PARENS_LENGTH / 6.0); point_scale(&vt2,ARROW_PARENS_LENGTH / 6.0); curve1[1].type = curve2[1].type = BEZ_CURVE_TO; curve1[1].p1 = curve1[0].p1; curve2[1].p1 = curve2[0].p1; point_add(&curve1[1].p1,&vt1); point_add(&curve2[1].p1,&vt1); point_add(&curve1[1].p1,&vt2); point_sub(&curve2[1].p1,&vt2); curve1[1].p2 = curve1[1].p1; curve2[1].p2 = curve2[1].p1; point_add(&curve1[1].p2,&vt1); point_add(&curve2[1].p2,&vt1); curve1[1].p3 = curve1[1].p2; curve2[1].p3 = curve2[1].p2; point_add(&curve1[1].p3,&vt1); point_add(&curve2[1].p3,&vt1); point_sub(&curve1[1].p3,&vt2); point_add(&curve2[1].p3,&vt2); renderer->ops->draw_bezier(renderer,curve1,2,col); renderer->ops->draw_bezier(renderer,curve2,2,col); } static Object * sadtarrow_create(Point *startpoint, void *user_data, Handle **handle1, Handle **handle2) { Sadtarrow *sadtarrow; NewOrthConn *orth; Object *obj; init_default_values(); sadtarrow = g_malloc0(sizeof(Sadtarrow)); orth = &sadtarrow->orth; obj = (Object *) sadtarrow; obj->type = &sadtarrow_type; obj->ops = &sadtarrow_ops; neworthconn_init(orth, startpoint); sadtarrow_update_data(sadtarrow); sadtarrow->style = sadtarrow_defaults.style; sadtarrow->autogray = sadtarrow_defaults.autogray; *handle1 = orth->handles[0]; *handle2 = orth->handles[orth->numpoints-2]; return (Object *)sadtarrow; } static void sadtarrow_destroy(Sadtarrow *sadtarrow) { neworthconn_destroy(&sadtarrow->orth); } static Object * sadtarrow_copy(Sadtarrow *sadtarrow) { Sadtarrow *newsadtarrow; NewOrthConn *orth, *neworth; Object *newobj; orth = &sadtarrow->orth; newsadtarrow = g_malloc0(sizeof(Sadtarrow)); neworth = &newsadtarrow->orth; newobj = (Object *) newsadtarrow; neworthconn_copy(orth, neworth); newsadtarrow->style = sadtarrow->style; newsadtarrow->autogray = sadtarrow->autogray; return (Object *)newsadtarrow; } static SadtarrowState * sadtarrow_get_state(Sadtarrow *sadtarrow) { SadtarrowState *state = g_new(SadtarrowState, 1); state->obj_state.free = NULL; state->style = sadtarrow->style; state->autogray = sadtarrow->autogray; return state; } static void sadtarrow_set_state(Sadtarrow *sadtarrow, SadtarrowState *state) { sadtarrow->style = state->style; sadtarrow->autogray = state->autogray; g_free(state); sadtarrow_update_data(sadtarrow); } static void sadtarrow_update_data(Sadtarrow *sadtarrow) { real arrow_width; NewOrthConn *orth = &sadtarrow->orth; Object *obj = (Object *) sadtarrow; neworthconn_update_data(&sadtarrow->orth); neworthconn_update_boundingbox(orth); /* fix boundingbox for line_width: */ obj->bounding_box.top -= ARROW_LINE_WIDTH/2; obj->bounding_box.left -= ARROW_LINE_WIDTH/2; obj->bounding_box.bottom += ARROW_LINE_WIDTH/2; obj->bounding_box.right += ARROW_LINE_WIDTH/2; /* Fix boundingbox for arrowheads (gcc should optimise most of this away) */ arrow_width = MAX(ARROW_HEAD_WIDTH,ARROW_HEAD_LENGTH); if (arrow_width > 0.0) { obj->bounding_box.top -= arrow_width; obj->bounding_box.left -= arrow_width; obj->bounding_box.bottom += arrow_width; obj->bounding_box.right += arrow_width; } } static ObjectChange * sadtarrow_add_segment_callback(Object *obj, Point *clicked, gpointer data) { ObjectChange *change; change = neworthconn_add_segment((NewOrthConn *)obj, clicked); sadtarrow_update_data((Sadtarrow *)obj); return change; } static ObjectChange * sadtarrow_delete_segment_callback(Object *obj, Point *clicked, gpointer data) { ObjectChange *change; change = neworthconn_delete_segment((NewOrthConn *)obj, clicked); sadtarrow_update_data((Sadtarrow *)obj); return change; } static DiaMenuItem object_menu_items[] = { { N_("Add segment"), sadtarrow_add_segment_callback, NULL, 1 }, { N_("Delete segment"), sadtarrow_delete_segment_callback, NULL, 1 }, }; static DiaMenu object_menu = { N_("SADT Arrow"), sizeof(object_menu_items)/sizeof(DiaMenuItem), object_menu_items, NULL }; static DiaMenu * sadtarrow_get_object_menu(Sadtarrow *sadtarrow, Point *clickedpoint) { NewOrthConn *orth; orth = &sadtarrow->orth; /* Set entries sensitive/selected etc here */ object_menu_items[0].active = neworthconn_can_add_segment(orth, clickedpoint); object_menu_items[1].active = neworthconn_can_delete_segment(orth, clickedpoint); return &object_menu; } static void sadtarrow_save(Sadtarrow *sadtarrow, ObjectNode obj_node, const char *filename) { neworthconn_save(&sadtarrow->orth, obj_node); save_enum(obj_node,"arrow_style",sadtarrow->style); save_boolean(obj_node,"autogray",sadtarrow->autogray); } static Object * sadtarrow_load(ObjectNode obj_node, int version, const char *filename) { Sadtarrow *sadtarrow; NewOrthConn *orth; Object *obj; init_default_values(); sadtarrow = g_malloc0(sizeof(Sadtarrow)); orth = &sadtarrow->orth; obj = (Object *) sadtarrow; obj->type = &sadtarrow_type; obj->ops = &sadtarrow_ops; neworthconn_load(orth, obj_node); sadtarrow->style = load_enum(obj_node,"arrow_style",SADT_ARROW_NORMAL); sadtarrow->autogray = load_boolean(obj_node,"autogray",TRUE); sadtarrow_update_data(sadtarrow); return (Object *)sadtarrow; }