1/* 2* Copyright (c) 2018 (https://github.com/phase1geo/Minder) 3* 4* This program is free software; you can redistribute it and/or 5* modify it under the terms of the GNU General Public 6* License as published by the Free Software Foundation; either 7* version 2 of the License, or (at your option) any later version. 8* 9* This program is distributed in the hope that it will be useful, 10* but WITHOUT ANY WARRANTY; without even the implied warranty of 11* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12* General Public License for more details. 13* 14* You should have received a copy of the GNU General Public 15* License along with this program; if not, write to the 16* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 17* Boston, MA 02110-1301 USA 18* 19* Authored by: Trevor Williams <phase1geo@gmail.com> 20*/ 21 22public class ExportYed : Export { 23 24 /* Constructor */ 25 public ExportYed() { 26 base( "yed", _( "Yed" ), { ".graphml" }, true, false ); 27 } 28 29 /* Exports the given drawing area to the file of the given name */ 30 public override bool export( string fname, DrawArea da ) { 31 Xml.Doc* doc = new Xml.Doc( "1.0" ); 32 Xml.Node* root = new Xml.Node( null, "graphml" ); 33 root->new_prop( "xmlns", "http://graphml.graphdrawing.org/xmlns" ); 34 root->new_ns( "http://www.yworks.com/xml/yfiles-common/1.0/java", "java" ); 35 root->new_ns( "http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0", "sys" ); 36 root->new_ns( "http://www.yworks.com/xml/yfiles-common/markup/2.0", "x" ); 37 root->new_ns( "http://www.w3.org/2001/XMLSchema-instance", "xsi" ); 38 root->new_ns( "http://www.yworks.com/xml/yed/3", "yed" ); 39 Xml.Ns* yns = root->new_ns( "http://www.yworks.com/xml/graphml", "y" ); 40 41 root->add_child( new Xml.Node.comment( _( "Generated by Minder" ) + " " + Minder.version ) ); 42 43 export_keys( root ); 44 export_graphs( root, yns, da ); 45 doc->set_root_element( root ); 46 doc->save_format_file( fname, 1 ); 47 delete doc; 48 return( true ); 49 } 50 51 /* Returns a single key populated with the specified information */ 52 private Xml.Node* export_key_attr( string id, string for_item, string attr_name, string attr_type ) { 53 Xml.Node* n = new Xml.Node( null, "key" ); 54 n->new_prop( "id", id ); 55 n->new_prop( "for", for_item ); 56 n->new_prop( "attr.name", attr_name ); 57 n->new_prop( "attr.type", attr_type ); 58 return( n ); 59 } 60 61 /* Returns a single key populated with the specified information */ 62 private Xml.Node* export_key_yfiles( string id, string for_item, string yfiles_type ) { 63 Xml.Node* n = new Xml.Node( null, "key" ); 64 n->new_prop( "id", id ); 65 n->new_prop( "for", for_item ); 66 n->new_prop( "yfiles.type", yfiles_type ); 67 return( n ); 68 } 69 70 /* Adds all of the keys to the root node */ 71 private void export_keys( Xml.Node* root ) { 72 root->add_child( export_key_attr( "d5", "node", "description", "string" ) ); 73 root->add_child( export_key_yfiles( "d6", "node", "nodegraphics" ) ); 74 root->add_child( export_key_yfiles( "d7", "graphml", "resources" ) ); 75 root->add_child( export_key_attr( "d9", "edge", "description", "string" ) ); 76 root->add_child( export_key_yfiles( "d10", "edge", "edgegraphics" ) ); 77 } 78 79 /* Exports each tree as a separate graph */ 80 private void export_graphs( Xml.Node* root, Xml.Ns* yns, DrawArea da ) { 81 82 Xml.Node* graph = new Xml.Node( null, "graph" ); 83 graph->new_prop( "edgedefault", "directed" ); 84 graph->new_prop( "id", "G0" ); 85 root->add_child( graph ); 86 87 /* Add nodes */ 88 for( int i=0; i<da.get_nodes().length; i++ ) { 89 export_node_edge( graph, yns, da.get_nodes().index( i ), da.get_theme() ); 90 } 91 92 /* Add connections */ 93 export_connections( graph, yns, da.get_connections().connections, da.get_theme() ); 94 95 Xml.Node* d7 = new Xml.Node( null, "data" ); 96 d7->new_prop( "key", "d7" ); 97 98 Xml.Node* res = new Xml.Node( yns, "Resources" ); 99 d7->add_child( res ); 100 101 graph->add_child( d7 ); 102 103 } 104 105 private Xml.Node* export_node_shape( Node node, Theme theme, Xml.Ns* yns ) { 106 Xml.Node* shape = new Xml.Node( yns, "ShapeNode" ); 107 108 Xml.Node* geometry = new Xml.Node( yns, "Geometry" ); 109 geometry->new_prop( "height", (node.height - (node.style.node_margin * 2)).to_string() ); 110 geometry->new_prop( "width", (node.width - (node.style.node_margin * 2)).to_string() ); 111 geometry->new_prop( "x", (node.posx + node.style.node_margin).to_string() ); 112 geometry->new_prop( "y", (node.posy + node.style.node_margin).to_string() ); 113 shape->add_child( geometry ); 114 115 Xml.Node* fill = new Xml.Node( yns, "Fill" ); 116 fill->new_prop( "color", Utils.color_from_rgba( node.is_root() ? theme.get_color( "root_background" ) : (node.style.node_fill ? node.link_color : theme.get_color( "background" )) ) ); 117 fill->new_prop( "transparent", "false" ); 118 shape->add_child( fill ); 119 120 Xml.Node* bs = new Xml.Node( yns, "BorderStyle" ); 121 bs->new_prop( "color", Utils.color_from_rgba( node.link_color ) ); 122 bs->new_prop( "type", "line" ); // TBD 123 bs->new_prop( "width", node.style.node_borderwidth.to_string() ); 124 shape->add_child( bs ); 125 126 Xml.Node* lbl = new Xml.Node( yns, "NodeLabel" ); 127 lbl->new_prop( "alignment", "left" ); 128 lbl->new_prop( "autoSizePolicy", "content" ); 129 lbl->new_prop( "fontFamily", node.style.node_font.get_family() ); 130 lbl->new_prop( "fontSize", (node.style.node_font.get_size() / Pango.SCALE).to_string() ); 131 lbl->new_prop( "fontStyle", "plain" ); 132 lbl->new_prop( "hasBackgroundColor", "false" ); 133 lbl->new_prop( "hasLineColor", "false" ); 134 lbl->new_prop( "height", node.name.height.to_string() ); 135 lbl->new_prop( "horizontalTextPosition", "left" ); 136 lbl->new_prop( "iconTextGap", "4" ); 137 lbl->new_prop( "modelName", "custom" ); 138 lbl->new_prop( "textColor", Utils.color_from_rgba( node.is_root() ? theme.get_color( "root_foreground" ) : (node.style.node_fill ? theme.get_color( "background" ) : theme.get_color( "foreground" )) ) ); 139 lbl->new_prop( "verticalTextPosition", "top" ); 140 lbl->new_prop( "visible", node.folded ? "false" : "true" ); 141 lbl->new_prop( "width", node.name.width.to_string() ); 142 lbl->new_prop( "x", node.style.node_padding.to_string() ); 143 lbl->new_prop( "xml:space", "preserve" ); 144 lbl->new_prop( "y", node.style.node_padding.to_string() ); 145 lbl->add_content( node.name.get_wrapped_text() ); 146 shape->add_child( lbl ); 147 148 Xml.Node* model = new Xml.Node( yns, "LabelModel" ); 149 Xml.Node* smodel = new Xml.Node( yns, "SmartNodeLabelModel" ); 150 smodel->new_prop( "distance", "4.0" ); 151 model->add_child( smodel ); 152 lbl->add_child( model ); 153 154 Xml.Node* param = new Xml.Node( yns, "ModelParameter" ); 155 Xml.Node* sparam = new Xml.Node( yns, "SmartNodeLabelModelParameter" ); 156 sparam->new_prop( "labelRatioX", "0.0" ); 157 sparam->new_prop( "labelRatioY", "0.0" ); 158 sparam->new_prop( "nodeRatioX", "0.0" ); 159 sparam->new_prop( "nodeRatioY", "0.0" ); 160 sparam->new_prop( "offsetX", "0.0" ); 161 sparam->new_prop( "offsetY", "0.0" ); 162 sparam->new_prop( "upX", "0.0" ); 163 sparam->new_prop( "upY", "-1.0" ); 164 param->add_child( sparam ); 165 lbl->add_child( param ); 166 167 Xml.Node* s = new Xml.Node( yns, "Shape" ); 168 switch( node.style.node_border.name() ) { 169 case "rounded" : s->new_prop( "type", "roundrectangle" ); break; 170 case "squared" : s->new_prop( "type", "rectangle" ); break; 171 default : s->new_prop( "type", "rectangle" ); break; 172 } 173 shape->add_child( s ); 174 175 return( shape ); 176 177 } 178 179 private Xml.Node* export_node( Node node, Theme theme, Xml.Ns* yns ) { 180 Xml.Node* n = new Xml.Node( null, "node" ); 181 Xml.Node* d5 = new Xml.Node( null, "data" ); 182 Xml.Node* d6 = new Xml.Node( null, "data" ); 183 n->new_prop( "id", ("n" + node.id().to_string()) ); 184 d5->new_prop( "key", "d5" ); 185 if( node.note != "" ) { 186 d5->new_prop( "xml:space", "preserve" ); 187 d5->add_content( node.note ); 188 } 189 n->add_child( d5 ); 190 d6->new_prop( "key", "d6" ); 191 d6->add_child( export_node_shape( node, theme, yns ) ); 192 n->add_child( d6 ); 193 return( n ); 194 } 195 196 private Xml.Node* export_node_bezieredge( Node node, Xml.Ns* yns ) { 197 198 Xml.Node* be = new Xml.Node( yns, "BezierEdge" ); 199 200 Xml.Node* path = new Xml.Node( yns, "Path" ); 201 path->new_prop( "sx", "0.0" ); 202 path->new_prop( "sy", "0.0" ); 203 path->new_prop( "tx", "0.0" ); 204 path->new_prop( "ty", "0.0" ); 205 be->add_child( path ); 206 207 Xml.Node* ls = new Xml.Node( yns, "LineStyle" ); 208 ls->new_prop( "color", Utils.color_from_rgba( node.link_color ) ); 209 ls->new_prop( "type", "line" ); 210 ls->new_prop( "width", node.style.link_width.to_string() ); 211 be->add_child( ls ); 212 213 Xml.Node* arrow = new Xml.Node( yns, "Arrows" ); 214 arrow->new_prop( "source", "none" ); 215 arrow->new_prop( "target", "none" ); 216 be->add_child( arrow ); 217 218 return( be ); 219 220 } 221 222 /* Adds the node link as an edge */ 223 private Xml.Node* export_link( Node node, Xml.Ns* yns ) { 224 if( node.is_root() ) return( null ); 225 Xml.Node* e = new Xml.Node( null, "edge" ); 226 e->new_prop( "id", ("e" + node.id().to_string()) ); 227 e->new_prop( "source", ("n" + node.parent.id().to_string()) ); 228 e->new_prop( "target", ("n" + node.id().to_string()) ); 229 230 Xml.Node* d9 = new Xml.Node( null, "data" ); 231 d9->new_prop( "key", "d9" ); 232 e->add_child( d9 ); 233 234 Xml.Node* d10 = new Xml.Node( null, "data" ); 235 d10->new_prop( "key", "d10" ); 236 d10->add_child( export_node_bezieredge( node, yns ) ); 237 e->add_child( d10 ); 238 239 return( e ); 240 241 } 242 243 /* Adds a node along with its edge */ 244 private void export_node_edge( Xml.Node* graph, Xml.Ns* yns, Node node, Theme theme ) { 245 graph->add_child( export_node( node, theme, yns ) ); 246 if( !node.is_root() ) { 247 graph->add_child( export_link( node, yns ) ); 248 } 249 for( int i=0; i<node.children().length; i++ ) { 250 export_node_edge( graph, yns, node.children().index( i ), theme ); 251 } 252 } 253 254 /* Create connection */ 255 private void export_connections( Xml.Node* graph, Xml.Ns* yns, Array<Connection> conns, Theme theme ) { 256 257 for( int i=0; i<conns.length; i++ ) { 258 259 var conn = conns.index( i ); 260 261 Xml.Node* e = new Xml.Node( null, "edge" ); 262 e->new_prop( "id", ("c" + i.to_string()) ); 263 e->new_prop( "source", ("n" + conn.from_node.id().to_string()) ); 264 e->new_prop( "target", ("n" + conn.to_node.id().to_string()) ); 265 266 Xml.Node* d9 = new Xml.Node( null, "data" ); 267 d9->new_prop( "key", "d9" ); 268 e->add_child( d9 ); 269 270 Xml.Node* d10 = new Xml.Node( null, "data" ); 271 d10->new_prop( "key", "d10" ); 272 273 Xml.Node* be = new Xml.Node( yns, "BezierEdge" ); 274 275 Xml.Node* path = new Xml.Node( yns, "Path" ); 276 path->new_prop( "sx", "0.0" ); 277 path->new_prop( "sy", "0.0" ); 278 path->new_prop( "tx", "0.0" ); 279 path->new_prop( "ty", "0.0" ); 280 be->add_child( path ); 281 282 Xml.Node* ls = new Xml.Node( yns, "LineStyle" ); 283 ls->new_prop( "color", Utils.color_from_rgba( theme.get_color( "connection" ) ) ); 284 ls->new_prop( "type", (conn.style.connection_dash.name == "solid") ? "line" : "dashed" ); 285 ls->new_prop( "width", conn.style.connection_line_width.to_string() ); 286 be->add_child( ls ); 287 288 Xml.Node* arrow = new Xml.Node( yns, "Arrows" ); 289 var atype = conn.style.connection_arrow; 290 arrow->new_prop( "source", ((atype == "tofrom") || (atype == "both")) ? "standard" : "none" ); 291 arrow->new_prop( "target", ((atype == "fromto") || (atype == "both")) ? "standard" : "none" ); 292 be->add_child( arrow ); 293 294 if( conn.title != null ) { 295 296 Xml.Node* el = new Xml.Node( yns, "EdgeLabel" ); 297 el->new_prop( "alignment", "center" ); 298 el->new_prop( "fontFamily", conn.style.connection_font.get_family() ); 299 el->new_prop( "fontSize", (conn.style.connection_font.get_size() / Pango.SCALE).to_string() ); 300 el->new_prop( "hasBackgroundColor", "false" ); 301 el->new_prop( "hasLineColor", "true" ); 302 el->new_prop( "height", conn.title.height.to_string() ); 303 el->new_prop( "horizontalTextPosition", "center" ); 304 el->new_prop( "modelName", "custom" ); 305 el->new_prop( "preferredPlacement", "anywhere" ); 306 el->new_prop( "ratio", "0.5" ); 307 el->new_prop( "textColor", Utils.color_from_rgba( theme.get_color( "foreground" ) ) ); 308 el->new_prop( "verticalTextPosition", "top" ); 309 el->new_prop( "visible", "true" ); 310 el->new_prop( "width", conn.title.width.to_string() ); 311 el->new_prop( "x", conn.title.posx.to_string() ); 312 el->new_prop( "xml:space", "preserve" ); 313 el->new_prop( "y", conn.title.posy.to_string() ); 314 el->add_content( conn.title.get_wrapped_text() ); 315 316 Xml.Node* lm = new Xml.Node( yns, "LabelModel" ); 317 Xml.Node* slm = new Xml.Node( yns, "SmartEdgeLabelModel" ); 318 slm->new_prop( "autoRotationEnabled", "false" ); 319 slm->new_prop( "defaultAngle", "0.0" ); 320 slm->new_prop( "defaultDistance", "10.0" ); 321 lm->add_child( slm ); 322 el->add_child( lm ); 323 324 Xml.Node* mp = new Xml.Node( yns, "ModelParameter" ); 325 Xml.Node* smp = new Xml.Node( yns, "SmartEdgeLabelModelParameter" ); 326 smp->new_prop( "angle", "0.0" ); 327 smp->new_prop( "distance", "30.0" ); 328 smp->new_prop( "distanceToCenter", "true" ); 329 smp->new_prop( "position", "left" ); 330 smp->new_prop( "ratio", "0.5" ); 331 smp->new_prop( "segment", "1" ); 332 mp->add_child( smp ); 333 el->add_child( mp ); 334 335 Xml.Node* ppd = new Xml.Node( yns, "PreferredPlacementDescriptor" ); 336 ppd->new_prop( "angle", "0.0" ); 337 ppd->new_prop( "angleOffsetOnRightSide", "0" ); 338 ppd->new_prop( "angleReference", "absolute" ); 339 ppd->new_prop( "angleRotationOnRightSide", "co" ); 340 ppd->new_prop( "distance", "-1.0" ); 341 ppd->new_prop( "frozen", "true" ); 342 ppd->new_prop( "placement", "anywhere" ); 343 ppd->new_prop( "side", "anywhere" ); 344 ppd->new_prop( "sideReference", "relative_to_edge_flow" ); 345 el->add_child( ppd ); 346 347 be->add_child( el ); 348 } 349 350 d10->add_child( be ); 351 e->add_child( d10 ); 352 graph->add_child( e ); 353 354 } 355 356 } 357 358} 359