00001 """
00002 Software License Agreement (BSD License)
00003
00004 Copyright (c) 2008, Willow Garage, Inc.
00005 All rights reserved.
00006
00007 Redistribution and use in source and binary forms, with or without
00008 modification, are permitted provided that the following conditions
00009 are met:
00010
00011 * Redistributions of source code must retain the above copyright
00012 notice, this list of conditions and the following disclaimer.
00013 * Redistributions in binary form must reproduce the above
00014 copyright notice, this list of conditions and the following
00015 disclaimer in the documentation and/or other materials provided
00016 with the distribution.
00017 * Neither the name of Willow Garage, Inc. nor the names of its
00018 contributors may be used to endorse or promote products derived
00019 from this software without specific prior written permission.
00020
00021 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032 POSSIBILITY OF SUCH DAMAGE
00033
00034 Note: This is modified version by Cogniteam
00035 """
00036
00037 from python_qt_binding.QtCore import QPointF, QRectF
00038 from python_qt_binding.QtGui import QColor
00039 from pydot import *
00040
00041 POINTS_PER_INCH = 72
00042
00043
00044
00045 def get_unquoted(item, name):
00046 value = item.get(name)
00047 if value is None:
00048 return None
00049 try:
00050 return value.strip('"\n"')
00051 except AttributeError:
00052
00053 return value
00054
00055
00056
00057 def clean_line_separator(attribute):
00058 return attribute.replace('\\', '')
00059
00060
00061 LABEL_HEIGHT = 30
00062
00063
00064
00065 class DotToQtGenerator():
00066
00067 def __init__(self, factory):
00068 self._factory = factory
00069
00070 def get_node_item_for_node(self, node):
00071 """
00072 returns a pyqt NodeItem object, or None in case of error or invisible style
00073 """
00074
00075 attr = {}
00076 for name in node.get_attributes().iterkeys():
00077 value = get_unquoted(node, name)
00078 attr[name] = value
00079 obj_dic = node.__getattribute__("obj_dict")
00080 for name in obj_dic:
00081 if name not in ['attributes', 'parent_graph'] and obj_dic[name] is not None:
00082 attr[name] = get_unquoted(obj_dic, name)
00083 node.attr = attr
00084
00085 if 'style' in node.attr:
00086 if node.attr['style'] == 'invis':
00087 return None
00088
00089 color = QColor(node.attr['color']) if 'color' in node.attr else None
00090 name = None
00091 if 'label' in node.attr:
00092 name = node.attr['label']
00093 elif 'name' in node.attr:
00094 name = node.attr['name']
00095 else:
00096 print("Error, no label defined for node with attr: %s" % node.attr)
00097 return None
00098
00099 if name is None:
00100
00101 print("Error, label is None for node %s, pygraphviz version may be too old." % node)
00102 else:
00103 name = name.decode('string_escape')
00104
00105
00106 bb_width = len(name) / 5
00107 if 'width' in node.attr:
00108 bb_width = node.attr['width']
00109
00110 bb_height = 1.0
00111 if 'width' in node.attr:
00112 bb_height = node.attr['height']
00113
00114 pos = (0, 0)
00115 if 'pos' in node.attr:
00116 pos = node.attr['pos'].split(',')
00117
00118 bounding_box = QRectF(0, 0, POINTS_PER_INCH * float(bb_width) - 1.0, POINTS_PER_INCH * float(bb_height) - 1.0)
00119
00120 bounding_box.moveCenter(QPointF(float(pos[0]), -float(pos[1])))
00121
00122 pen_width = self._factory.pen_width()
00123 if 'penwidth' in node.attr:
00124 pen_width = float(node.attr['penwidth'])
00125
00126 url = node.attr['URL'] if 'URL' in node.attr else 'N/A'
00127 node_item = self._factory.create_node(bounding_box=bounding_box,
00128 shape=node.attr.get('shape', 'box'),
00129 label=name,
00130 url=url,
00131 penwidth=pen_width)
00132 return node_item
00133
00134 def add_edge_item_for_edge(self, edge, nodes, edges):
00135 """
00136 adds EdgeItem by data in edge to edges
00137 :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
00138 """
00139
00140 attr = {}
00141 for name in edge.get_attributes().iterkeys():
00142 value = get_unquoted(edge, name)
00143 attr[name] = value
00144 edge.attr = attr
00145
00146
00147 edge.attr['pos'] = clean_line_separator(edge.attr['pos'])
00148
00149 if 'style' in edge.attr:
00150 if edge.attr['style'] == 'invis':
00151 return
00152
00153 label = edge.attr.get('label', None)
00154 label_pos = edge.attr.get('lp', None)
00155 label_center = None
00156 if label_pos is not None:
00157 label_pos = label_pos.split(',')
00158 label_center = QPointF(float(label_pos[0]), -float(label_pos[1]))
00159
00160
00161 source_node = edge.get_source() if hasattr(edge, 'get_source') else edge[0]
00162
00163 destination_node = str(edge.get_destination() if hasattr(edge, 'get_destination') else edge[1])
00164
00165
00166 edge_pos = (0, 0)
00167 if 'pos' in edge.attr:
00168 edge_pos = edge.attr['pos']
00169 if label is not None:
00170 label = label.decode('string_escape')
00171
00172 edge_item = self._factory.create_edge(spline=edge_pos,
00173 label=label,
00174 label_center=label_center,
00175 from_node=nodes[source_node],
00176 to_node=nodes[destination_node])
00177
00178 if self._factory.same_label_siblings():
00179 if label is None:
00180
00181 label = "%s_%s" % (source_node, destination_node)
00182
00183 if label in edges:
00184 for sibling in edges[label]:
00185 edge_item.add_sibling_edge(sibling)
00186 sibling.add_sibling_edge(edge_item)
00187
00188 if label not in edges:
00189 edges[label] = []
00190 edges[label].append(edge_item)
00191
00192 def get_graph_edges(self, graph, nodes, edges):
00193 for subgraph in graph.get_subgraph_list():
00194 self.get_graph_edges(subgraph, nodes, edges)
00195 for edge in subgraph.get_edge_list():
00196 self.add_edge_item_for_edge(edge, nodes, edges)
00197
00198 for edge in graph.get_edge_list():
00199 self.add_edge_item_for_edge(edge, nodes, edges)
00200
00201 def get_cluster_node(self, node):
00202
00203 attr = {}
00204 for name in node.get_attributes().iterkeys():
00205 value = get_unquoted(node, name)
00206 attr[name] = value
00207 obj_dic = node.__getattribute__("obj_dict")
00208 for name in obj_dic:
00209 if name not in ['nodes', 'attributes', 'parent_graph'] and obj_dic[name] is not None:
00210 attr[name] = get_unquoted(obj_dic, name)
00211 elif name == 'nodes':
00212 for key in obj_dic['nodes']['graph'][0]['attributes']:
00213 attr[key] = get_unquoted(obj_dic['nodes']['graph'][0]['attributes'], key)
00214 node.attr = attr
00215
00216 name = None
00217 if 'label' in node.attr:
00218 name = node.attr['label']
00219 elif 'name' in node.attr:
00220 name = node.attr['name']
00221 else:
00222 print("Error, no label defined for node with attr: %s" % node.attr)
00223 return None
00224 if name is None:
00225
00226 print("Error, label is None for node %s, pygraphviz version may be too old." % node)
00227
00228 bb_width = node.attr['width']
00229 bb_height = node.attr['height']
00230
00231 bounding_box = QRectF(0, 0, POINTS_PER_INCH * float(bb_width) - 1.0, POINTS_PER_INCH * float(bb_height) - 1.0)
00232
00233 pos = (0, 0)
00234 if 'pos' in node.attr:
00235 pos = node.attr['pos'].split(',')
00236 bounding_box.moveCenter(QPointF(float(pos[0]), -float(pos[1])))
00237
00238 label_pos = QPointF(bounding_box.center().x(), bounding_box.top() + LABEL_HEIGHT / 2)
00239
00240 color = QColor(node.attr['color']) if 'color' in node.attr else None
00241
00242 url = node.attr['URL'] if 'URL' in node.attr else 'N/A'
00243 label = node.attr['label'] if 'label' in node.attr else name
00244
00245 graph_node_item = self._factory.create_node(bounding_box=bounding_box,
00246 shape='box',
00247 label=label,
00248 label_pos=label_pos,
00249 url=url,
00250 cluster=True)
00251
00252 return graph_node_item
00253
00254 def get_subgraph_nodes(self, graph, nodes):
00255
00256 attr = {}
00257 for name in graph.get_attributes().iterkeys():
00258 value = get_unquoted(graph, name)
00259 attr[name] = value
00260 obj_dic = graph.__getattribute__("obj_dict")
00261 for name in obj_dic:
00262 if name not in ['nodes', 'attributes', 'parent_graph'] and obj_dic[name] is not None:
00263 attr[name] = get_unquoted(obj_dic, name)
00264 elif name == 'nodes':
00265 for key in obj_dic['nodes']['graph'][0]['attributes']:
00266 attr[key] = get_unquoted(obj_dic['nodes']['graph'][0]['attributes'], key)
00267 graph.attr = attr
00268
00269 bb = graph.attr['bb'].strip('"').split(',')
00270 if len(bb) < 4:
00271
00272 return None
00273 bounding_box = QRectF(0, 0, float(bb[2]) - float(bb[0]), float(bb[3]) - float(bb[1]))
00274 if 'lp' in graph.attr:
00275 label_pos = graph.attr['lp'].strip('"').split(',')
00276 else:
00277 label_pos = (float(bb[0]) + (float(bb[2]) - float(bb[0])) / 2,
00278 float(bb[1]) + (float(bb[3]) - float(bb[1])) - LABEL_HEIGHT / 2)
00279 bounding_box.moveCenter(QPointF(float(bb[0]) + (float(bb[2]) - float(bb[0])) / 2,
00280 -float(bb[1]) - (float(bb[3]) - float(bb[1])) / 2))
00281 name = graph.attr.get('label', '')
00282
00283 color = QColor(graph.attr['color']) if 'color' in graph.attr else None
00284
00285 url = graph.attr['URL'] if 'URL' in graph.attr else 'N/A'
00286
00287 graph_node_item = self._factory.create_node(bounding_box=bounding_box,
00288 shape='box',
00289 label=name,
00290 label_pos=QPointF(float(label_pos[0]), -float(label_pos[1])),
00291 url=url,
00292 cluster=True)
00293
00294 for subgraph in graph.get_subgraph_list():
00295 subgraph_node_item = self.get_subgraph_nodes(subgraph, nodes)
00296
00297 if subgraph_node_item is None:
00298 continue
00299
00300 nodes[subgraph.get_name()] = subgraph_node_item
00301 for node in subgraph.get_node_list():
00302
00303 if node.get_name() in ('graph', 'node', 'empty'):
00304 continue
00305 nodes[node.get_name()] = self.get_node_item_for_node(node)
00306 for node in graph.get_node_list():
00307
00308 if node.get_name() in ('graph', 'node', 'empty'):
00309 continue
00310 nodes[node.get_name()] = self.get_node_item_for_node(node)
00311
00312 return graph_node_item
00313
00314 def get_graph_nodes(self, graph, nodes):
00315
00316 for subgraph in graph.get_subgraph_list():
00317 subgraph_node_item = self.get_subgraph_nodes(subgraph, nodes)
00318
00319 if subgraph_node_item is None:
00320 continue
00321
00322 nodes[subgraph.get_name()] = subgraph_node_item
00323 for node in subgraph.get_node_list():
00324
00325 if node.get_name() in ('graph', 'node', 'empty'):
00326 continue
00327 nodes[node.get_name()] = self.get_node_item_for_node(node)
00328 for node in graph.get_node_list():
00329
00330 if node.get_name() in ('graph', 'node', 'empty'):
00331 continue
00332 nodes[node.get_name()] = self.get_node_item_for_node(node)
00333
00334 def dotcode_to_qt_items(self, dotcode):
00335 """
00336 takes dotcode, runs layout, and creates qt items based on the dot layout.
00337 returns two dicts, one mapping node names to Node_Item, one mapping edge names to lists of Edge_Item
00338 :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
00339 """
00340
00341 if dotcode is None:
00342 return {}, {}
00343
00344 graph = graph_from_dot_data(dotcode.encode("ascii", "ignore"))
00345
00346 nodes = {}
00347 edges = {}
00348
00349 self.get_graph_nodes(graph, nodes)
00350 self.get_graph_edges(graph, nodes, edges)
00351
00352 return nodes, edges