Modules | Files | Inheritance Tree | Inheritance Graph | Name Index | Config
File: Synopsis/UI/Qt/actionvis.py
    1| # $Id: actionvis.py,v 1.12 2002/11/11 14:28:41 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: actionvis.py,v $
   23| # Revision 1.12  2002/11/11 14:28:41  chalky
   24| # New Source Edit dialog, with Insert Rule wizard and new s/path/rules with
   25| # extra features.
   26| #
   27| # Revision 1.11  2002/11/02 06:36:19  chalky
   28| # Upgrade to Qt3
   29| #
   30| # Revision 1.10  2002/10/11 06:03:23  chalky
   31| # Use config from project
   32| #
   33| # Revision 1.9  2002/09/28 05:53:31  chalky
   34| # Refactored display into separate project and browser windows. Execute projects
   35| # in the background
   36| #
   37| # Revision 1.8  2002/08/23 04:36:35  chalky
   38| # Use icons! :)
   39| #
   40| # Revision 1.7  2002/06/22 07:03:27  chalky
   41| # Updates to GUI - better editing of Actions, and can now execute some.
   42| #
   43| # Revision 1.6  2002/06/12 12:58:33  chalky
   44| # Updates to display - hilite items and right click menus
   45| #
   46| # Revision 1.5  2002/04/26 01:21:14  chalky
   47| # Bugs and cleanups
   48| #
   49| # Revision 1.4  2001/11/09 08:06:59  chalky
   50| # More GUI fixes and stuff. Double click on Source Actions for a dialog.
   51| #
   52| # Revision 1.3  2001/11/07 05:58:21  chalky
   53| # Reorganised UI, opening a .syn file now builds a simple project to view it
   54| #
   55| # Revision 1.2  2001/11/06 08:47:11  chalky
   56| # Silly bug, arrows, channels are saved
   57| #
   58| # Revision 1.1  2001/11/05 06:52:11  chalky
   59| # Major backside ui changes
   60| #
   61| 
   62| 
   63| import sys, pickle, Synopsis, cStringIO, math
   64| from qt import *
   65| 
   66| # In later versions of python-qt aka PyQt this is in a separate module
   67| if not globals().has_key('QCanvasView'):
   68|     from qtcanvas import *
   69| 
   70| from Synopsis.Config import prefix
   71| from Synopsis.Core import AST, Util
   72| from Synopsis.Core.Action import *
   73| from Synopsis.Core.Project import *
   74| from Synopsis.Formatter.ASCII import ASCIIFormatter
   75| from Synopsis.Formatter.ClassTree import ClassTree
   76| 
   77| import actionwiz
   78| 
   79| class CanvasStrategy:
   80|     "An interface for a strategy to handle mouse events"
   81|     def __init__(self, canvas, view):
   82|         self.canvas = canvas
   83|         self.view = view
   84|     def reset(self): pass
   85|     def set(self): pass
   86|     def press(self, event): pass
   87|     def release(self, event): pass
   88|     def move(self, event): pass
   89|     def doubleClick(self, event): pass
   90|     def key(self, event):
   91|         if event.key() == Qt.Key_Escape:
   92|             self.view.window.setMode('select')
   93|         else:
   94|             event.ignore()
   95| 
   96| class SelectionStrategy (CanvasStrategy):
   97|     """The normal CanvasStrategy to handle mouse actions."""
   98| 
   99|     class MenuHandler:
  100|         """Wraps menu callbacks with an action or line object"""
  101|         def __init__(self, sel, obj):
  102|             self.sel = sel
  103|             self.obj = obj 
  104|         def on_properties(self):
  105|             self.sel.on_properties(self.obj)
  106|         def on_delete_action(self):
  107|             self.sel.on_delete_action(self.obj)
  108|         def on_delete_line(self):
  109|             self.sel.on_delete_line(self.obj)
  110|         def on_default_formatter(self):
  111|             self.sel.on_default_formatter(self.obj)
  112| 
  113|     def __init__(self, canvas, view):
  114|         CanvasStrategy.__init__(self, canvas, view)
  115|         self.__drag_action = None
  116|         self.__icon = None
  117|         self.__last = None
  118|         self.__hilite = None # the action or line to hilight
  119|         self.__normal_cursor = QCursor(Qt.ArrowCursor)
  120|         self.__moving_cursor = QCursor(Qt.BlankCursor)
  121|         self.__hint_cursor = QCursor(Qt.SizeAllCursor)
  122| 
  123|     def reset(self):
  124|         self.__drag_item = None
  125| 
  126|     def press(self, event):
  127|         action = self.canvas.get_action_at(event.x(), event.y())
  128|         if action and event.button() == Qt.LeftButton:
  129|             # Drag the action if left button pressed
  130|             self.__drag_action = action
  131|             self.__last = QPoint(event.pos())
  132|             self.view.setCursor(self.__moving_cursor)
  133|           return
  134|         if event.button() == Qt.RightButton:
  135|             if action and event.button() == Qt.RightButton:
  136|                # Show a menu for the right button
  137|                handler = self.MenuHandler(self, action)
  138|                menu = QPopupMenu(self.view)
  139|                menu.insertItem("Delete Action", handler.on_delete_action)
  140|                if isinstance(action, FormatAction):
  141|                    id = menu.insertItem("Default Formatter", handler.on_default_formatter)
  142|                    check = self.canvas.project.default_formatter() is action
  143|                    menu.setItemChecked(id, check)
  144|                menu.insertItem("Properties...", handler.on_properties)
  145|                menu.exec_loop(QCursor.pos())
  146|         return
  147|             line = self.canvas.get_line_at(event.x(), event.y())
  148|             if line:
  149|                # Show a menu for the right button
  150|                handler = self.MenuHandler(self, line)
  151|                menu = QPopupMenu(self.view)
  152|                menu.insertItem("Delete Channel", handler.on_delete_line)
  153|                menu.exec_loop(QCursor.pos())
  154|         return
  155|         
  156|     def release(self, event):
  157|         if self.__drag_action:
  158|             self.__drag_action = None
  159|             self.view.setCursor(self.__hint_cursor)
  160|         
  161|     def move(self, event):
  162|         # Drag an action if we're dragging
  163|         if self.__drag_action:
  164|             # Move the dragging action
  165|             dx = event.x() - self.__last.x()
  166|             dy = event.y() - self.__last.y()
  167|             self.view.actions.move_action_by(self.__drag_action, dx, dy)
  168|             self.__last = QPoint(event.pos())
  169|           return
  170|         # Hilite an action if mouse is over one
  171|         action = self.canvas.get_action_at(event.x(), event.y())
  172|         if action:
  173|             self.view.setCursor(self.__hint_cursor)
  174|             self.hilite(self.canvas.get_icon_for(action))
  175|           return
  176|         # Hilite a line if mouse is over one
  177|         line = self.canvas.get_line_at(event.x(), event.y())
  178|         if line:
  179|             self.hilite(line)
  180|         elif self.__hilite:
  181|             # Not doing anything..
  182|             self.hilite(None)
  183|             self.view.setCursor(self.__normal_cursor)
  184|     
  185|     def hilite(self, obj):
  186|         """Sets the currently hilited object. The object may be None, an Icon
  187|         or a Line"""
  188|         if obj is self.__hilite:
  189|           return
  190|         if self.__hilite:
  191|             self.__hilite.set_hilite(0)
  192|         self.__hilite = obj
  193|         if obj:
  194|             obj.set_hilite(1)
  195|         self.canvas.update()
  196|    
  197|     def doubleClick(self, event):
  198|         action = self.canvas.get_action_at(event.x(), event.y())
  199|         if action: self.on_properties(action)
  200|     
  201|     def key(self, event):
  202|         "Override default set-mode-to-select behaviour"
  203|         event.ignore()
  204| 
  205|     def on_delete_line(self, line):
  206|         print "Deleting line",line.source.name(),"->",line.dest.name()
  207|         button = QMessageBox.warning(self.view, "Confirm delete",\
  208|             'Delete line from "%s" to "%s"?'%(line.source.name(),
  209|             line.dest.name()), QMessageBox.Yes, QMessageBox.No)
  210|         if button is QMessageBox.No: return
  211|         self.canvas.actions.remove_channel(line.source, line.dest)
  212| 
  213|     def on_delete_action(self, action):
  214|         print "Deleting action",action.name()
  215|         button = QMessageBox.warning(self.view, "Confirm delete",\
  216|             'Delete action "%s"?'%(action.name(),),
  217|             QMessageBox.Yes, QMessageBox.No)
  218|         if button is QMessageBox.No: return
  219|         self.canvas.actions.remove_action(action)
  220| 
  221|     def on_default_formatter(self, action):
  222|         print "Changing default formatter:",action.name()
  223|         if self.canvas.project.default_formatter() is action:
  224|             self.canvas.project.set_default_formatter(None)
  225|         else:
  226|             self.canvas.project.set_default_formatter(action)
  227| 
  228|     def on_properties(self, action):
  229|         print action.name()
  230|         if isinstance(action, SourceAction):
  231|             editor = actionwiz.SourceActionEditor(self.view, self.canvas.project)
  232|             result = editor.edit(action)
  233|             print result
  234|           return
  235|         actionwiz.ActionDialog(self.view, action, self.canvas.project)
  236| 
  237| class ConnectStrategy (CanvasStrategy):
  238|     def __init__(self, canvas, view):
  239|         CanvasStrategy.__init__(self, canvas, view)
  240|         self.__normal_cursor = QCursor(Qt.ArrowCursor)
  241|         self.__hint_cursor = QCursor(Qt.UpArrowCursor)
  242|         self.__find_cursor = QCursor(Qt.SizeHorCursor)
  243|         self.templine = QCanvasLine(self.canvas)
  244|         self.templine.setPen(QPen(Qt.blue, 1, Qt.DotLine))
  245| 
  246|     def set(self):
  247|         self.source = None
  248|         self.templine.hide()
  249|         self.canvas.update()
  250|     def reset(self):
  251|         self.templine.hide()
  252|         self.canvas.update()
  253|     def move(self, event):
  254|         # Find action under cursor and change cursor type
  255|         if self.source:
  256|             self.templine.setPoints(self.source.x()+16, self.source.y()+16, event.x(), event.y())
  257|             self.canvas.update()
  258|         action = self.canvas.get_action_at(event.x(), event.y())
  259|         if action and action is not self.source:
  260|             if self.view.actions.is_valid_channel(self.source, action):
  261|                self.view.setCursor(self.__hint_cursor)
  262|         return
  263|         if self.source:
  264|             self.view.setCursor(self.__find_cursor)
  265|         else:
  266|             self.view.setCursor(self.__normal_cursor)
  267|     def press(self, event):
  268|         action = self.canvas.get_action_at(event.x(), event.y())
  269|         if not action: return
  270|         if not self.source: self.setSource(action, event)
  271|         elif action is not self.source:
  272|             if self.view.actions.is_valid_channel(self.source, action):
  273|                self.setDest(action)
  274|     def release(self, event):
  275|         action = self.canvas.get_action_at(event.x(), event.y())
  276|         if action and self.source and action is not self.source:
  277|             if self.view.actions.is_valid_channel(self.source, action):
  278|                self.setDest(action)
  279|     def setSource(self, action, event):
  280|         self.source = action
  281|         self.templine.setPoints(action.x()+16, action.y()+16, event.x(), event.y())
  282|         self.templine.show()
  283|         self.canvas.update()
  284|     def setDest(self, action):
  285|         self.templine.hide()
  286|         self.view.actions.add_channel(self.source, action)
  287|         self.source = None
  288|         self.view.window.setMode('select')
  289| 
  290| class AddActionStrategy (CanvasStrategy):
  291|     def __init__(self, canvas, view, action_type):
  292|         CanvasStrategy.__init__(self, canvas, view)
  293|         self.__drag_action = None
  294|         self.__normal_cursor = QCursor(Qt.ArrowCursor)
  295|         self.__moving_cursor = QCursor(Qt.BlankCursor)
  296|         self.__action_type = action_type
  297| 
  298|     def set(self):
  299|         action_class = getattr(Synopsis.Core.Action, '%sAction'%self.__action_type)
  300|         self.action = action_class(self.view.last_pos.x()-16,
  301|                            self.view.last_pos.y()-16, 
  302|                            "New %s action"%self.__action_type)
  303|         try:
  304|             # Run the wizard to set the type of the action
  305|             wizard = actionwiz.AddWizard(self.view, self.action, self.canvas.project)
  306|             if wizard.exec_loop() == QDialog.Rejected:
  307|                # Abort adding
  308|                self.action = None
  309|                self.view.window.setMode('select')
  310|          else:
  311|                # Wizard will create a new more derived action object
  312|                self.view.actions.add_action(wizard.action)
  313|                self.action = wizard.action
  314|                self.view.setCursor(self.__moving_cursor)
  315|         except Exception, msg:
  316|             print "An error occured in add wizard:\n", msg
  317|             self.action = None
  318|             self.view.window.setMode('select')
  319|             import traceback
  320|             traceback.print_exc()
  321| 
  322|     def reset(self):
  323|         if self.action:
  324|             self.view.actions.remove_action(self.action)
  325|         self.view.setCursor(self.__normal_cursor)
  326| 
  327|     def move(self, event):
  328|         self.view.actions.move_action(self.action, event.x()-16, event.y()-16)
  329| 
  330|     def release(self, event):
  331|         self.action = None
  332|         self.view.window.setMode('select')
  333| 
  334| class ActionPropertiesDialog (QDialog):
  335|     def __init__(self, parent, action):
  336|         QDialog.__init__(self, parent)
  337|         self.action = action
  338| 
  339| class ActionColorizer (ActionVisitor):
  340|     def __init__(self, action=None):
  341|         self.color = None
  342|         if action: action.accept(self)
  343|     def visitAction(self, action): self.color=Qt.black
  344|     def visitSource(self, action): self.color=Qt.magenta
  345|     def visitParser(self, action): self.color=Qt.red
  346|     def visitLinker(self, action): self.color=Qt.yellow
  347|     def visitCacher(self, action): self.color=Qt.green
  348|     def visitFormat(self, action): self.color=Qt.cyan
  349| 
  350| class ActionIcon (ActionVisitor):
  351|     def __init__(self, action=None):
  352|         self.icon = None
  353|         if action: action.accept(self)
  354|     def visitAction(self, action): pass #self.icon='syn-icon-action.png'
  355|     def visitSource(self, action): self.icon='syn-icon-c++.png'
  356|     def visitParser(self, action): self.icon='syn-icon-parse.png'
  357|     def visitLinker(self, action): self.icon='syn-icon-link.png'
  358|     def visitCacher(self, action): self.icon='syn-icon-cache.png'
  359|     def visitFormat(self, action): self.icon='syn-icon-html.png'
  360| 
  361| class Icon:
  362|     "Encapsulates the canvas display of an Action"
  363| 
  364|     def __init__(self, canvas, action):
  365|         self.action = action
  366|         self.canvas = canvas
  367|         self.icon = ActionIcon(action).icon
  368|         if self.icon:
  369|             icon = prefix+'/share/synopsis/'+self.icon
  370|             self.pixmap = QPixmap(icon)
  371|             self.array = QCanvasPixmapArray([self.pixmap])
  372|             self.img = QCanvasSprite(self.array, canvas)
  373|         else:
  374|             self.img = QCanvasRectangle(action.x(), action.y(), 32, 32, canvas)
  375|             self.brush = QBrush(ActionColorizer(action).color)
  376|             self.img.setBrush(self.brush)
  377|         self.width = self.img.width()
  378|         self.height = self.img.height()
  379|         self.img.setZ(1)
  380|         self.text = QCanvasText(action.name(), canvas)
  381|         self.text.setZ(1)
  382|         self.img.show()
  383|         self.text.show()
  384|         self.hilite = 0
  385|         self.update_pos()
  386|     def hide(self):
  387|         """Hides icon on canvas"""
  388|         # Note, items must be deleted, which will occur when GC happens.
  389|         # Until them, make them invisible
  390|         self.img.hide()
  391|         self.text.hide()
  392|     def set_hilite(self, yesno):
  393|         self.hilite = yesno
  394|         if not hasattr(self, 'brush'): return
  395|         if yesno:
  396|             colour = self.brush.color()
  397|             brush = QBrush(QColor(colour.red()*3/4, colour.green()*3/4,
  398|                colour.blue()*3/4))
  399|         else:
  400|             brush = self.brush
  401|         self.img.setBrush(brush)
  402|     def update_pos(self):
  403|         self.img.move(self.action.x(), self.action.y())
  404|         rect = self.text.boundingRect()
  405|         irect = self.img.boundingRect()
  406|         y = irect.bottom()
  407|         x = irect.center().x() - rect.width()/2
  408|         self.text.move(x, y)
  409| 
  410| class Line:
  411|     "Encapsulates the canvas display of a channel between two Actions"
  412|     def __init__(self, canvas, source, dest):
  413|         self.canvas = canvas
  414|         self.source = source
  415|         self.dest = dest
  416|         self.hilite = 0
  417|         self.line = QCanvasLine(canvas)
  418|         self.line.setPen(QPen(Qt.blue))
  419|         self.arrow = QCanvasPolygon(canvas)
  420|         self.arrow.setBrush(QBrush(Qt.blue))
  421|         self.update_pos()
  422|         self.line.show()
  423|         self.arrow.show()
  424|     def hide(self):
  425|         """Hide line on canvas"""
  426|         self.line.hide()
  427|         self.arrow.hide()
  428|     def set_hilite(self, yesno):
  429|         self.hilite = yesno
  430|         if yesno:
  431|             pen = QPen(Qt.blue, 2)
  432|         else:
  433|             pen = QPen(Qt.blue)
  434|         self.line.setPen(pen)
  435|         self.arrow.setPen(pen)
  436|         self.update_pos()
  437|     def update_pos(self):
  438|         source, dest = self.source, self.dest
  439|         src_action, dst_action = source.action, dest.action
  440|         # Find centers of the two icons
  441|         src_xrad, src_yrad = source.width/2, source.height/2
  442|         dst_xrad, dst_yrad = dest.width/2, dest.height/2
  443|         if src_xrad == 0: src_xrad, src_yrad = 10, 10
  444|         if dst_xrad == 0: dst_xrad, dst_yrad = 10, 10
  445|         x1, y1 = src_action.x()+src_xrad, src_action.y()+src_yrad
  446|         x2, y2 = dst_action.x()+dst_xrad, dst_action.y()+dst_yrad
  447|         # Calculate the distance
  448|         dx, dy = x2 - x1, y2 - y1
  449|         d = math.sqrt(dx*dx + dy*dy)
  450|         if d < 32:
  451|             # Don't draw lines that are too short (they will be behind the
  452|             # icons)
  453|             self.line.setPoints(x1, y1, x2, y2)
  454|             self.arrow.setPoints(QPointArray([]))
  455|           return
  456|         # Normalize direction vector
  457|         dx, dy = dx / d, dy / d
  458|         # Find the end-point for the arrow
  459|         if math.fabs(dx) * dst_yrad < math.fabs(dy) * dst_xrad:
  460|             if dy < 0:
  461|                # Bottom
  462|                x2 = x2 + dst_yrad * dx / dy
  463|                y2 = y2 + dst_yrad
  464|          else:
  465|         # Top
  466|                x2 = x2 - dst_yrad * dx / dy
  467|                y2 = y2 - dst_yrad
  468|         else:
  469|             if dx < 0:
  470|         # Left
  471|                y2 = y2 + dst_xrad * dy / dx
  472|                x2 = x2 + dst_xrad
  473|          else:
  474|         # Right
  475|                y2 = y2 - dst_xrad * dy / dx
  476|                x2 = x2 - dst_xrad
  477|         self.line.setPoints(x1, y1, x2, y2)
  478|         alen = 8 + self.hilite
  479|         awid = 5 + self.hilite
  480|         self.arrow.setPoints(QPointArray([
  481|             x2 + self.hilite*dx, y2 + self.hilite*dy,
  482|             x2 - alen*dx - awid*dy, y2 - alen*dy + awid*dx,
  483|             x2 - alen*dx + awid*dy, y2 - alen*dy - awid*dx]))
  484| 
  485| class ActionCanvas (QCanvas):
  486|     """Extends QCanvas to automatically fill and update the canvas when
  487|     notified of events by an ActionManager"""
  488|     def __init__(self, actions, parent, project):
  489|         QCanvas.__init__(self, parent)
  490|         self.actions = actions
  491|         self.project = project
  492|         self._item_to_action_map = {}
  493|         self._action_to_icon_map = {}
  494|         self._action_lines = {}
  495|         self._item_to_line_map = {}
  496| 
  497|         self.actions.add_listener(self)
  498| 
  499|     def get_action_at(self, x, y):
  500|         "Returns the Action (if any) at the given coordinates"
  501|         items = self.collisions(QPoint(x, y))
  502|         if items and self._item_to_action_map.has_key(items[0]):
  503|             return self._item_to_action_map[items[0]]
  504|     
  505|     def get_line_at(self, x, y):
  506|         """Returns the Actions forming a line which crosses x,y, as a
  507|         line object"""
  508|         items = self.collisions(QPoint(x, y))
  509|         if items and self._item_to_line_map.has_key(items[0]):
  510|             return self._item_to_line_map[items[0]]
  511|     
  512|     def get_icon_for(self, action):
  513|         return self._action_to_icon_map[action]
  514| 
  515|     def action_added(self, action):
  516|         "Callback from ActionManager. Adds an Icon for the new Action"
  517|         icon = Icon(self, action)
  518|         self._item_to_action_map[icon.img] = action
  519|         self._action_to_icon_map[action] = icon
  520|         self.update()
  521| 
  522|     def action_removed(self, action):
  523|         "Callback from ActionManager. Removes the Icon for the Action"
  524|         icon = self._action_to_icon_map[action]
  525|         del self._action_to_icon_map[action]
  526|         del self._item_to_action_map[icon.img]
  527|         icon.hide()
  528|         self.update()
  529| 
  530|     def action_moved(self, action):
  531|         "Callback from ActionManager. Moves the Icon to follow the Action"
  532|         icon = self._action_to_icon_map[action]
  533|         icon.update_pos()
  534|         if self._action_lines.has_key(action):
  535|             for line in self._action_lines[action]:
  536|                line.update_pos()
  537|         self.update()
  538| 
  539|     def channel_added(self, source, dest):
  540|         "Callback from ActionManager. Adds a channel between the given actions"
  541|         src_icon = self._action_to_icon_map[source]
  542|         dst_icon = self._action_to_icon_map[dest]
  543|         line = Line(self, src_icon, dst_icon)
  544|         self._action_lines.setdefault(source, []).append(line)
  545|         self._action_lines.setdefault(dest, []).append(line)
  546|         self._item_to_line_map[line.line] = line
  547|         self.update()
  548| 
  549|     def channel_removed(self, source, dest):
  550|         "Callback from ActionManager. Adds a channel between the given actions"
  551|         # NB: check source and dest separately to handle inconsistancies
  552|         # better, if they should arise
  553|         if self._action_lines[source]:
  554|             for line in self._action_lines[source]:
  555|                if line.dest is dest:
  556|                    self._action_lines[source].remove(line)
  557|                    if self._item_to_line_map.has_key(line.line):
  558|                       self._item_to_line_map[line.line].hide()
  559|                       del self._item_to_line_map[line.line]
  560|         if self._action_lines[dest]:
  561|             for line in self._action_lines[dest]:
  562|                if line.source is source:
  563|                    self._action_lines[dest].remove(line)
  564|                    if self._item_to_line_map.has_key(line.line):
  565|                       self._item_to_line_map[line.line].hide()
  566|                       del self._item_to_line_map[line.line]
  567|         self.update()
  568| 
  569|     def action_changed(self, action):
  570|         "Callback from ProjectActions. Indicates changes, including rename"
  571|         icon = self._action_to_icon_map[action]
  572|         icon.text.setText(action.name())
  573|         icon.update_pos()
  574|         self.update()
  575| 
  576| 
  577| class CanvasView (QCanvasView):
  578|     def __init__(self, canvas, parent):
  579|         QCanvasView.__init__(self, canvas, parent)
  580|         self.actions = parent.actions
  581|         self.window = parent
  582|         self.last_pos = QPoint(-50,-50)
  583|         self.viewport().setFocusPolicy(QWidget.ClickFocus)
  584|         self.__canvas = canvas
  585|         self.__strategies = {
  586|             'select' : SelectionStrategy(canvas, self),
  587|             'connect': ConnectStrategy(canvas, self),
  588|             'new_source' : AddActionStrategy(canvas, self, 'Source'),
  589|             'new_parse' : AddActionStrategy(canvas, self, 'Parser'),
  590|             'new_link' : AddActionStrategy(canvas, self, 'Linker'),
  591|             'new_cache' : AddActionStrategy(canvas, self, 'Cacher'),
  592|             'new_format' : AddActionStrategy(canvas, self, 'Format')
  593|         }
  594|         self.viewport().setMouseTracking(1)
  595|         
  596|         self.__strategy = self.__strategies['select']
  597|         self.connect(parent, PYSIGNAL('modeChanged(string)'), self.modeChanged)
  598|     def modeChanged(self, tool):
  599|         if self.__strategies.has_key(tool):
  600|             self.__strategy.reset()
  601|             self.__strategy = self.__strategies[tool]
  602|             self.__strategy.set()
  603|     def contentsMousePressEvent(self, event):
  604|         self.__strategy.press(event)
  605|     def contentsMouseReleaseEvent(self, event):
  606|         self.__strategy.release(event)
  607|     def contentsMouseMoveEvent(self, event):
  608|         self.last_pos = QPoint(event.pos())
  609|         self.__strategy.move(event)
  610|     def contentsMouseDoubleClickEvent(self, event):
  611|         self.__strategy.doubleClick(event)
  612|     def keyPressEvent(self, event):
  613|         self.__strategy.key(event)
  614|         
  615| 
  616| class CanvasWindow (QVBox):
  617|     def __init__(self, parent, main_window, project):
  618|         QVBox.__init__(self, parent)
  619|         self.setCaption("Canvas")
  620|         self.main_window = main_window
  621|         self.__activated = 0
  622| 
  623|         # Make the toolbar
  624|         self.buttons = QButtonGroup()
  625|         self.buttons.setExclusive(1)
  626|         self.tool = QToolBar("Canvas", self.main_window, self)
  627|         #self.tool.setHorizontalStretchable(0)
  628|         pixmap = QPixmap(16,16); pixmap.fill()
  629|         self.tool_sel = QToolButton(QIconSet(pixmap), "Select", "Select actions in the display", self.setSelect, self.tool)
  630|         self.tool_sel.setUsesTextLabel(1)
  631|         self.tool_sel.setToggleButton(1)
  632|         self.buttons.insert(self.tool_sel)
  633| 
  634|         pixmap = QPixmap(16,16); pixmap.fill(Qt.blue)
  635|         self.tool_con = QToolButton(QIconSet(pixmap), "Connect", "Connect two actions", self.setConnect, self.tool)
  636|         self.tool_con.setUsesTextLabel(1)
  637|         self.tool_con.setToggleButton(1)
  638|         self.buttons.insert(self.tool_con)
  639| 
  640|         #pixmap = QPixmap(16,16); pixmap.fill(Qt.red)
  641|         #self.tool_add = QToolButton(QIconSet(pixmap), "Add Action", "Add a new action to the project", self.newAction, self.tool)
  642|         #self.tool_add.setUsesTextLabel(1)
  643|         #self.tool_add.setToggleButton(1)
  644|         #self.buttons.insert(self.tool_add)
  645|         tools = (('Source','source','c++'), ('Parser','parse','parse'),
  646|                 ('Linker','link','link'), ('Cache','cache', 'cache'),
  647|                 ('Formatter','format', 'html'))
  648|         for name, short_name, icon in tools:
  649|             self._makeNewTool(name, short_name, icon)
  650| 
  651|         # Make the menu, to be inserted in the app menu upon window activation
  652|         self._file_menu = QPopupMenu(main_window.menuBar())
  653|         #self._file_menu.insertItem("New &Action", self.newAction, Qt.CTRL+Qt.Key_A)
  654|         self._file_menu.insertItem("&Save Project", self.saveProject, Qt.CTRL+Qt.Key_S)
  655|         self._file_menu.insertItem("Save Project &As...", self.saveProjectAs)
  656| 
  657| 
  658|         self.__tools = {
  659|             'select' : self.tool_sel,
  660|             'connect': self.tool_con,
  661|             'new_source' : self.tool_source,
  662|             'new_parse' : self.tool_parse,
  663|             'new_link' : self.tool_link,
  664|             'new_cache' : self.tool_cache,
  665|             'new_format' : self.tool_format
  666|         }
  667|         # Make the canvas
  668|         self.project = project
  669|         self.actions = self.project.actions()
  670|         self.canvas = ActionCanvas(self.actions, self, project)
  671|         self.canvas.resize(parent.width(), parent.height())
  672|         self.canvas_view = CanvasView(self.canvas, self)
  673| 
  674|         self.show()
  675| 
  676|         self.connect(parent.parentWidget(), SIGNAL('windowActivated(QWidget*)'), self.windowActivated)
  677| 
  678|         self.setMode('select')
  679|         self.activate()
  680| 
  681|     def _makeNewTool(self, name, short_name, icon_id):
  682|         icon = prefix+'/share/synopsis/syn-icon-%s.png'%icon_id
  683|         image = QImage(icon)
  684|         # Resize icon to correct size
  685|         image.smoothScale(16, 16, QImage.ScaleMin)
  686|         pixmap = QPixmap(image)
  687|         tool = QToolButton(QIconSet(pixmap), "New %s"%name,
  688|             "Add a new %s action to the project"%name, 
  689|             getattr(self, 'new%sAction'%name), self.tool)
  690|         tool.setUsesTextLabel(1)
  691|         tool.setToggleButton(1)
  692|         self.buttons.insert(tool)
  693|         # Store with appropriate name in self
  694|         setattr(self, 'tool_'+short_name, tool)
  695| 
  696|     def resizeEvent(self, ev):
  697|         QVBox.resizeEvent(self, ev)
  698|         self.canvas.resize(self.canvas_view.width(), self.canvas_view.height())
  699| 
  700|         
  701|     def setMode(self, mode):
  702|         self.mode = mode
  703|         self.__tools[mode].setOn(1)
  704| 
  705|         self.emit(PYSIGNAL('modeChanged(string)'), (self.mode,))
  706| 
  707|     def windowActivated(self, widget):
  708|         if self.__activated:
  709|             if widget is not self: self.deactivate()
  710|         elif widget is self: self.activate()
  711|     
  712|     def activate(self):
  713|         self.__activated = 1
  714|         self._menu_id = self.main_window.menuBar().insertItem('Canvas', self._file_menu)
  715| 
  716|     def deactivate(self):
  717|         self.__activated = 0
  718|         self.main_window.menuBar().removeItem(self._menu_id)
  719| 
  720|     def setSelect(self):
  721|         self.setMode('select')
  722| 
  723|     def setConnect(self):
  724|         self.setMode('connect')
  725| 
  726|     def newSourceAction(self):
  727|         self.setMode('new_source')
  728| 
  729|     def newParserAction(self):
  730|         self.setMode('new_parse')
  731| 
  732|     def newLinkerAction(self):
  733|         self.setMode('new_link')
  734| 
  735|     def newCacheAction(self):
  736|         self.setMode('new_cache')
  737| 
  738|     def newFormatterAction(self):
  739|         self.setMode('new_format')
  740| 
  741|     def saveProject(self):
  742|         if self.project.filename() is None:
  743|             filename = QFileDialog.getSaveFileName('project.synopsis', '*.synopsis',
  744|                self, 'ProjectSave', "Save Project...")
  745|             filename = str(filename)
  746|             if not filename:
  747|                # User clicked cancel
  748|         return
  749|             print "filename =",filename
  750|             self.project.set_filename(filename)
  751|         self.project.save()
  752|         
  753|     def saveProjectAs(self):
  754|         filename = QFileDialog.getSaveFileName('project.synopsis', '*.synopsis',
  755|             self, 'ProjectSave', "Save Project As...")
  756|         filename = str(filename)
  757|         if not filename:
  758|             # User clicked cancel
  759|           return
  760|         print "filename =",filename
  761|         self.project.set_filename(filename)
  762|         self.project.save()
  763|         
  764|