Using a DOT graph as a basis for tree GUI
Question: I want to use a graph that is generated by DOT (pyDot in python) as the basis
for an interactive Tree-structured GUI in which each of the nodes in the Tree
could be widgets.

The tree will basically be a binary Morse Code tree which start at the top
node and navigate down the tree to their desired letter and select it. The
node they want to select should be highlightable, and the contents (letters)
of should be able to be changed based on user input.

Basically I want the nodes to be turned into full scale objects with tunable
parameters that change as the interface is used. Can anyone point me in the
right direction in order to do this?


Answer: I started with the demo code at:
<http://wiki.wxpython.org/AnotherTutorial#wx.TreeCtrl>. I've added the
build_tree, _build_tree_helper and build_conn_dict methods. The key methods of
interest from the dot_parser library are edge.get_source() and
edge.get_destination() which are used to make the "connection" dictionary.

The dot graph is stored in the dot_data variable. Importantly, the dot graph
**must not** loop; that is, it must be a spanning tree otherwise the
_build_tree_helper method will loop infinitely (and it doesn't make sense in a
TreeControl).

I also had to patch dot_parser according to
<https://github.com/nlhepler/pydot-py3/issues/1#issuecomment-15999052> to get
it to work.

    
    
    import wx
    
    from dot_parser import parse_dot_data
    
    class MyFrame(wx.Frame):
        def __init__(self, parent, id, title, **kwargs):
            self.parsed_dot = kwargs.pop("parsed_dot", None)
            wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(450, 350))
    
            hbox = wx.BoxSizer(wx.HORIZONTAL)
            vbox = wx.BoxSizer(wx.VERTICAL)
            panel1 = wx.Panel(self, -1)
            panel2 = wx.Panel(self, -1)
    
            self.tree = wx.TreeCtrl(panel1, 1, wx.DefaultPosition, (-1,-1), wx.TR_HAS_BUTTONS | wx.TR_LINES_AT_ROOT )
            self.build_tree(self.tree)
            self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged, id=1)
            self.display = wx.StaticText(panel2, -1, "",(10,10), style=wx.ALIGN_CENTRE)
            vbox.Add(self.tree, 1, wx.EXPAND)
            hbox.Add(panel1, 1, wx.EXPAND)
            hbox.Add(panel2, 1, wx.EXPAND)
            panel1.SetSizer(vbox)
            self.SetSizer(hbox)
            self.Centre()
    
        def build_conn_dict(self):
            conn_dict = {}
            if(self.parsed_dot):
                for edge in self.parsed_dot.get_edges():
                    conn_dict.setdefault(edge.get_source(), []).append(edge.get_destination())
            return conn_dict
    
        def build_tree(self, tree):
            if(self.parsed_dot):
                conn_dict = self.build_conn_dict()
                outs = set(conn_dict.keys())
                ins = reduce(lambda x, y: x | set(y), conn_dict.values(), set([]))
                roots = list(outs - ins)
                roots = dict([(root, tree.AddRoot(root)) for root in roots])
                self._build_tree_helper(tree, conn_dict, roots)
    
        def _build_tree_helper(self, tree, conn_dict = {}, roots = {}):
            new_roots = {}
            for root in roots:
                if(conn_dict.has_key(root)):
                    for sub_root in conn_dict[root]:
                        new_roots[sub_root] = tree.AppendItem(roots[root], sub_root)
            if(new_roots):
                self._build_tree_helper(tree, conn_dict, new_roots)
    
        def OnSelChanged(self, event):
            item =  event.GetItem()
            self.display.SetLabel(self.tree.GetItemText(item))
            child_text = self.tree.GetItemText(item)
            parent_text = ""
            try:
                parent = self.tree.GetItemParent(item)
                parent_text = self.tree.GetItemText(parent)
            except wx._core.PyAssertionError:
                pass
            print "child: %s, parent: %s" % (child_text, parent_text)
    
    class MyApp(wx.App):
        def OnInit(self):
            dot_data = \
            '''
            graph ""
            {
                    label="(% (EXP (% (X) (% (X) (X)))) (EXP (SIN (X))))"
                    n039 ;
                    n039 [label="%"] ;
                    n039 -> n040 ;
                    n040 [label="EXP"] ;
                    n040 -> n041 ;
                    n041 [label="%"] ;
                    n041 -> n042 ;
                    n042 [label="X"] ;
                    n041 -> n043 ;
                    n043 [label="%"] ;
                    n043 -> n044 ;
                    n044 [label="X"] ;
                    n043 -> n045 ;
                    n045 [label="X"] ;
                    n039 -> n046 ;
                    n046 [label="EXP"] ;
                    n046 -> n047 ;
                    n047 [label="SIN"] ;
                    n047 -> n048 ;
                    n048 [label="X"] ;
            }
            '''        
            parsed_dot = parse_dot_data(dot_data)
            frame = MyFrame(None, -1, "treectrl.py", parsed_dot = parsed_dot)
            frame.Show(True)
            self.SetTopWindow(frame)
            return True
    
    app = MyApp(0)
    app.MainLoop()
    

