Modules | Files | Inheritance Tree | Inheritance Graph | Name Index | Config
File: Synopsis/UI/Qt/igraph.py
    1| # $Id: igraph.py,v 1.3 2002/09/28 05:53:31 chalky Exp $
    2| #
    3| # This file is a part of Synopsis.
    4| # Copyright (C) 2000, 2001 Stefan Seefeld
    5| # Copyright (C) 2000, 2001 Stephen Davies
    6| #
    7| # Synopsis is free software; you can redistribute it and/or modify it
    8| # under the terms of the GNU General Public License as published by
    9| # the Free Software Foundation; either version 2 of the License, or
   10| # (at your option) any later version.
   11| #
   12| # This program is distributed in the hope that it will be useful,
   13| # but WITHOUT ANY WARRANTY; without even the implied warranty of
   14| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   15| # General Public License for more details.
   16| #
   17| # You should have received a copy of the GNU General Public License
   18| # along with this program; if not, write to the Free Software
   19| # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
   20| # 02111-1307, USA.
   21| #
   22| # $Log: igraph.py,v $
   23| # Revision 1.3  2002/09/28 05:53:31  chalky
   24| # Refactored display into separate project and browser windows. Execute projects
   25| # in the background
   26| #
   27| # Revision 1.2  2001/11/07 05:58:21  chalky
   28| # Reorganised UI, opening a .syn file now builds a simple project to view it
   29| #
   30| # Revision 1.1  2001/11/05 06:52:11  chalky
   31| # Major backside ui changes
   32| #
   33| 
   34| 
   35| import sys, pickle, Synopsis, cStringIO
   36| from qt import *
   37| 
   38| # In later versions of python-qt aka PyQt this is in a separate module
   39| if not globals().has_key('QCanvasView'):
   40|     from qtcanvas import *
   41| 
   42| from Synopsis.Core import AST, Util
   43| from Synopsis.Core.Action import *
   44| from Synopsis.Formatter.ASCII import ASCIIFormatter
   45| from Synopsis.Formatter.ClassTree import ClassTree
   46| 
   47| 
   48| class IGraphWindow (QCanvasView):
   49|     class Icon:
   50|         def __init__(self, canvas, classname, x, y):
   51|             self.classname = classname
   52|             name = Util.ccolonName(classname[-1:])
   53|             self.text = QCanvasText(name, canvas)
   54|             self.text.setZ(2)
   55|             self.text.show()
   56|             r = self.text.boundingRect()
   57|             self.border = QCanvasRectangle(x , y, r.width()+4,r.height()+4, canvas)
   58|             self.border.setBrush(QBrush(QColor(255,255,200)))
   59|             self.border.setZ(1)
   60|             self.border.show()
   61|             self.subs = []
   62|             self.supers = []
   63|             self.sub_lines = []
   64|             self.super_lines = []
   65|             self.move_to(x, y)
   66|         def move_to(self, x, y):
   67|             self.x = x
   68|             self.y = y
   69|             self.border.move(x, y)
   70|             self.text.move(x+2,y+2)
   71|             r = self.border.boundingRect()
   72|             self.mid_x = (r.left() + r.right())/2
   73|             self.max_x = r.right()
   74|             self.top_y = r.top()
   75|             self.mid_y = (r.top() + r.bottom())/2
   76|             self.max_y = r.bottom()
   77|             for sub in self.sub_lines: self.move_sub(sub)
   78|             for super in self.super_lines: self.move_super(super)
   79|         def width(self):
   80|             "returns the width of this icon"
   81|             return self.border.boundingRect().width()
   82|         def mid(self):
   83|             "Returns the x, y coords of the middle of this icon"
   84|             return self.mid_x, self.mid_y
   85|         def add_sub(self, sub, line):
   86|             self.subs.append(sub)
   87|             self.sub_lines.append(line)
   88|             self.move_sub(line)
   89|         def move_sub(self, line):
   90|             other = line.endPoint()
   91|             line.setPoints(self.mid_x, self.mid_y, other.x(), other.y())
   92|         def add_super(self, super, line):
   93|             self.supers.append(super)
   94|             self.super_lines.append(line)
   95|             self.move_super(line)
   96|         def move_super(self, line):
   97|             other = line.startPoint()
   98|             line.setPoints(other.x(), other.y(), self.mid_x, self.top_y)
   99|         def clear(self):
  100|             "Removes all references to other icons etc so they can be garbage collected"
  101|             del self.subs
  102|             del self.supers
  103|             del self.sub_lines
  104|             del self.super_lines
  105|             self.text.hide()
  106|             self.border.hide()
  107| 
  108| 
  109|     def __init__(self, parent, main_window, classTree):
  110|         self.__canvas = QCanvas()
  111|         self.main_window = main_window
  112|         self.classTree = classTree
  113|         QCanvasView.__init__(self, self.__canvas, parent)
  114|         self.canvas().resize(parent.width(), parent.height())
  115| 
  116|         # Empty icon list
  117|         self.icons = {}
  118| 
  119|     def set_class(self, classname):
  120|         self.mainclass = classname
  121|         graphs = self.classTree.graphs()
  122|         classes = None
  123|         for graph in graphs:
  124|             if classname in graph:
  125|                classes = graph
  126|         break
  127|         if not classes: return
  128| 
  129|         # Reset icons
  130|         for icon in self.icons.values():
  131|             icon.clear()
  132|         self.__canvas.setChanged(QRect(QPoint(0,0),self.__canvas.size()))
  133|         self.__canvas.update()
  134| 
  135|         # Create new icons
  136|         self.icons = {}
  137|         for clas in classes:
  138|             self.icons[clas]= self.Icon(self.__canvas, clas, 0,0)
  139|             if clas == classname:
  140|                self.icons[clas].text.setColor(Qt.red)
  141| 
  142|         # Now connect their supers and subs lists
  143|         for name, icon in self.icons.items():
  144|             for child in self.classTree.subclasses(name):
  145|                line = QCanvasLine(self.__canvas)
  146|                sub = self.icons[child]
  147|                icon.add_sub(sub, line)
  148|                sub.add_super(icon, line)
  149|                if name == classname or sub.classname == classname:
  150|                    line.setPen(QPen(Qt.red))
  151|                line.show()
  152|         
  153|         self.organize()
  154| 
  155|         self.__canvas.update()
  156| 
  157|         #self.show()
  158| 
  159|     def organize(self):
  160|         # find a root
  161|         roots = filter(lambda x: not x.supers, self.icons.values())
  162|         if not roots: return # this is an error
  163|         root = roots[0]
  164|         self._organized = {}
  165|         self._ranks = {}
  166|         self.rank_down(root)
  167|         self.space_ranks()
  168|         self.sort_ranks()
  169| 
  170|         self.un_offset()
  171| 
  172|     def un_offset(self):
  173|         """Moves the graph to the top-left corner of the display"""
  174|         # Find top-left of graph
  175|         min_x, min_y = 0, 0
  176|         for node in self.icons.values():
  177|             if node.x < min_x: min_x = node.x
  178|             if node.y < min_y: min_y = node.y
  179|         # Move to top-left of display
  180|         min_x, min_y = min_x - 10, min_y - 10
  181|         max_x, max_y = 0, 0
  182|         for node in self.icons.values():
  183|             node.move_to(node.x - min_x, node.y - min_y)
  184|             if node.max_x > max_x: max_x = node.max_x
  185|             if node.max_y > max_y: max_y = node.max_y
  186|         self.__canvas.resize(max_x+10, max_y+10)
  187| 
  188|     def rank_down(self, node):
  189|         self._organized[node] = None
  190|         if not self._ranks.has_key(node.y): self._ranks[node.y] = []
  191|         self._ranks[node.y].append(node)
  192|         min_y = node.y + 30
  193|         for sub in node.subs:
  194|             if self._organized.has_key(sub) and min_y < sub.y: continue
  195|             sub.move_to(sub.x, node.y + 30)
  196|             self.rank_down(sub)
  197| 
  198|         for super in node.supers:
  199|             if self._organized.has_key(super): continue
  200|             super.move_to(super.x, node.y - 30)
  201|             self.rank_down(super)
  202|     
  203|     def space_ranks(self):
  204|         "Spaces out the ranks vertically"
  205|         ranks = self._ranks.keys()
  206|         ranks.sort()
  207|         space = 0
  208|         for rank in ranks:
  209|             nodes = self._ranks[rank]
  210|             space = space + len(nodes)
  211|             new_rank = rank + space
  212| 
  213|             del self._ranks[rank]
  214|             self._ranks[new_rank] = nodes
  215| 
  216|             for node in nodes:
  217|                node.move_to(node.x, new_rank)
  218|         
  219|         
  220|     
  221|     def sort_ranks(self):
  222|         ranks = self._ranks.keys()
  223|         ranks.sort()
  224|         for rank in ranks:
  225|             nodes = self._ranks[rank]
  226|             done = {}
  227|             order = []
  228|             rank_x = -1e6
  229|             for node in nodes:
  230|                for super in node.supers:
  231|                    # Create a set of nodes that are childen of super at this rank
  232|                    set = filter(lambda n, y=rank: n.y==y, super.subs)
  233|                    # Eliminate already ordered nodes
  234|                    set = filter(lambda n, d=done: not d.has_key(n), set)
  235|                    # Sort the set relative to where their parents are
  236|                    summer = lambda sum, num: (sum[0]+num, sum[1]+1)
  237|                    def average(sum): return sum[1] and float(sum[0])/sum[1] or 0
  238|                    super_order = lambda s, ds=node.supers: ds.index(s)
  239|                    node_order = lambda n, summer=summer, super_order=super_order, average=average: \
  240|                       average(reduce(summer, map(super_order, n.supers), (0,0)))
  241|                    set.sort(lambda x,y,no=node_order: cmp(no(x), no(y)))
  242|                    order.extend(set)
  243|                    # Make the nodes as done
  244|                    for s in set: done[s] = 1
  245|                    # Calculate the width of the set
  246|                  set_width = 0
  247|                    for s in set: set_width = set_width + s.width() + 2
  248|                    # Move nodes to their new order
  249|                    x = super.mid()[0] - set_width/2
  250|                    if x < rank_x: x = rank_x
  251|                  for s in set:
  252|                       s.move_to(x, s.y)
  253|                       x = x + s.width() + 2
  254|                   rank_x = x + 2
  255|                if not node.supers:
  256|                    # Backup: no siblings to rank with so just use rank_x
  257|                    if rank_x < -1e5: rank_x = 0
  258|                    node.move_to(rank_x, node.y)
  259|                    rank_x = rank_x + node.width() + 2 + 5*len(node.subs)
  260|                    order.append(node)
  261|                   done[node] = 1
  262|             #print rank, map(lambda n:'%s:%d'%(n.classname[-1],n.x), order)
  263| 
  264|