ete2.treeview.drawer

Package ete2 :: Package treeview :: Module drawer |
[hide private]
[frames] | no frames]

# Source Code for Module ete2.treeview.drawer

       1  __VERSION__="ete2-2.0rev95"
       2  # #START_LICENSE###########################################################
       3  #
       4  # Copyright (C) 2009 by Jaime Huerta Cepas. All rights reserved.
       5  # email: jhcepas@gmail.com
       6  #
       7  # This file is part of the Environment for Tree Exploration program (ETE).
       8  # http://ete.cgenomics.org
       9  #
      10  # ETE is free software: you can redistribute it and/or modify it
      11  # under the terms of the GNU General Public License as published by
      12  # the Free Software Foundation, either version 3 of the License, or
      13  # (at your option) any later version.
      14  #
      15  # ETE is distributed in the hope that it will be useful,
      16  # but WITHOUT ANY WARRANTY; without even the implied warranty of
      17  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      18  # GNU General Public License for more details.
      19  #
      20  # You should have received a copy of the GNU General Public License
      21  # along with ETE.  If not, see <http://www.gnu.org/licenses/>.
      22  #
      23  # #END_LICENSE#############################################################
      24
      25  from sys import stderr, stdout
      26  import time
      27  import re
      28  import math
      29  import random
      30  import types
      31  import copy
      32  import string
      33  import numpy
      34  try:
      35      from PyQt4  import QtCore, QtGui
      36      from PyQt4.QtGui import QPrinter
      37  except ImportError:
      38      import QtCore, QtGui
      39      from QtGui import QPrinter
      40  import layouts
      41  import _mainwindow, _search_dialog, _show_newick, _open_newick, _about
      42
      43  try:
      44      from PyQt4 import QtOpenGL
      45      #USE_GL = True
      46      USE_GL = False # Temporarily disabled
      47  except ImportError:
      48      USE_GL = False
      49
      50  from ete2 import Tree, PhyloTree, ClusterTree
      51
      52  __all__ = ["show_tree", "render_tree", "TreeImageProperties"]
      53
      54  DEBUG = 0
      55  _QApp = None
      56
      57  _MIN_NODE_STYLE = {
      58      "fgcolor": "#0030c1",
      59      "bgcolor": "#FFFFFF",
      60      "vt_line_color": "#000000",
      61      "hz_line_color": "#000000",
      62      "line_type": 0,
      63      "vlwidth": 1,
      64      "hlwidth": 1,
      65      "size":6,
      66      "shape": "sphere",
      67      "faces": None,
      68      "draw_descendants": 1,
      69      "ymargin": 0
      70  }
      71
      72 -class TreeImageProperties:
      73 -    def __init__(self):
      74          self.force_topology             = False
      75          self.draw_branch_length         = False
      76          self.align_leaf_faces           = False
      77          self.orientation                = 0
      78          self.style                      = 0 # 0 = Normal, 1 = Diagonal lines
      79          self.general_font_type          = "Verdana"
      80          self.branch_length_font_color   = "#222"
      81          self.branch_length_font_size    = 6
      82          self.branch_support_font_color  = "red"
      83          self.branch_support_font_size   = 9
      84          self.tree_width                 = 200  # This is used to scale
      85                                                 # the tree branches
      86          self.min_branch_separation      = 1
      87          self.search_node_bg = "#cccccc"
      88          self.search_node_fg = "#ff0000"
      89
      90 -def logger(level,*msg):
      91      """ Just to manage how to print messages """
      92      msg = map(str, msg)
      93      # critrical error
      94      if level == -1:
      95          print >>stderr,"* Error:     ", " ".join(msg)
      96      # warning
      97      elif level == 0:
      98          print >>stderr,"* Warning:   ", " ".join(msg)
      99      # info
     100      elif level == 1:
     101          if DEBUG == 1:
     102              print >>stdout,"* Info:      ", " ".join(msg)
     103      # debug
     104      elif level == 2:
     105          if DEBUG == 1:
     106              print >>stderr,"* Debug:     ", " ".join(msg)
     107      else:
     108          print >>stderr,"* ", " ".join(msg)
     109      return
     110
     111 -def show_tree(t, style=None, img_properties=None):
     112      """ Interactively shows a tree."""
     113      global _QApp
     114
     115      if not style:
     116          if t.__class__ == PhyloTree:
     117              style = "phylogeny"
     118          elif t.__class__ == ClusterTree:
     119              style = "large"
     120          else:
     121              style = "basic"
     122
     123      if not _QApp:
     124          _QApp = QtGui.QApplication(["ETE"])
     125
     126      scene  = _TreeScene()
     127      mainapp = _MainApp(scene)
     128
     129
     130      if not img_properties:
     131          img_properties = TreeImageProperties()
     132      scene.initialize_tree_scene(t, style, \
     133                                      tree_properties=img_properties)
     134      scene.draw()
     135
     136      mainapp.show()
     137      _QApp.exec_()
     138
     139 -def render_tree(t, imgName, w=None, h=None, style=None, \
     140                      img_properties = None, header=None):
     141      """ Render tree image into a PNG file."""
     142
     143      if not style:
     144          if t.__class__ == PhyloTree:
     145              style = "phylogeny"
     146          elif t.__class__ == ClusterTree:
     147              style = "large"
     148          else:
     149              style = "basic"
     150
     151
     152      global _QApp
     153      if not _QApp:
     154          _QApp = QtGui.QApplication(["ETE"])
     155
     156      scene  = _TreeScene()
     157      if not img_properties:
     158          img_properties = TreeImageProperties()
     159      scene.initialize_tree_scene(t, style,
     160                                  tree_properties=img_properties)
     161      scene.draw()
     162      scene.save(imgName, w=w, h=h, header=header)
     163
     164
     165  # #################
     166  # NON PUBLIC STUFF
     167  # #################
     168
     169
     170
     171
     172 -class NewickDialog(QtGui.QDialog):
     173 -    def __init__(self, node, *args):
     174          QtGui.QDialog.__init__(self, *args)
     175          self.node = node
     176
     177 -    def update_newick(self):
     178          f= int(self._conf.nwFormat.currentText())
     179          self._conf.features_list.selectAll()
     180          if self._conf.useAllFeatures.isChecked():
     181              features = []
     182          elif self._conf.features_list.count()==0:
     183              features = None
     184          else:
     185              features = set()
     186              for i in self._conf.features_list.selectedItems():
     187                  features.add(str(i.text()))
     188
     189          nw = self.node.write(format=f, features=features)
     190          self._conf.newickBox.setText(nw)
     191
     192 -    def add_feature(self):
     193          aName = str(self._conf.attrName.text()).strip()
     194          if aName != '':
     195              self._conf.features_list.addItem( aName)
     196              self.update_newick()
     197 -    def del_feature(self):
     198          r = self._conf.features_list.currentRow()
     199          self._conf.features_list.takeItem(r)
     200          self.update_newick()
     201
     202 -    def set_custom_features(self):
     203          state = self._conf.useAllFeatures.isChecked()
     204          self._conf.features_list.setDisabled(state)
     205          self._conf.attrName.setDisabled(state)
     206          self.update_newick()
     207
     208 -class _MainApp(QtGui.QMainWindow):
     209 -    def __init__(self, scene, *args):
     210          QtGui.QMainWindow.__init__(self, *args)
     211          self.scene = scene
     212          self.view  = _MainView(scene)
     213          scene.view = self.view
     214          _mainwindow.Ui_MainWindow().setupUi(self)
     215          scene.view = self.view
     216          self.view.centerOn(0,0)
     217          splitter = QtGui.QSplitter()
     218          splitter.addWidget(self.view)
     219          splitter.addWidget(scene.propertiesTable)
     220          self.setCentralWidget(splitter)
     221
     222          # I create a single dialog to keep the last search options
     223          self.searchDialog =  QtGui.QDialog()
     224          # Don't know if this is the best way to set up the dialog and
     225          # its variables
     226          self.searchDialog._conf = _search_dialog.Ui_Dialog()
     227          self.searchDialog._conf.setupUi(self.searchDialog)
     228
     229
     230      @QtCore.pyqtSignature("")
     231 -    def on_actionETE_triggered(self):
     232          try:
     233              __VERSION__
     234          except:
     235              __VERSION__= "developmnet branch"
     236
     237          d = QtGui.QDialog()
     238          d._conf = _about.Ui_About()
     239          d._conf.setupUi(d)
     240          d._conf.version.setText("Version: %s" %__VERSION__)
     241          d._conf.version.setAlignment(QtCore.Qt.AlignHCenter)
     242          d.exec_()
     243
     244      @QtCore.pyqtSignature("")
     245 -    def on_actionZoomOut_triggered(self):
     246          self.view.safe_scale(0.8,0.8)
     247
     248      @QtCore.pyqtSignature("")
     249 -    def on_actionZoomIn_triggered(self):
     250          self.view.safe_scale(1.2,1.2)
     251
     252      @QtCore.pyqtSignature("")
     253 -    def on_actionZoomInX_triggered(self):
     254          self.scene.props.tree_width += 20
     255          self.scene.draw()
     256
     257      @QtCore.pyqtSignature("")
     258 -    def on_actionZoomOutX_triggered(self):
     259          if self.scene.props.tree_width >20:
     260              self.scene.props.tree_width -= 20
     261              self.scene.draw()
     262
     263      @QtCore.pyqtSignature("")
     264 -    def on_actionZoomInY_triggered(self):
     265          if self.scene.props.min_branch_separation < \
     266                  self.scene.min_real_branch_separation:
     267              self.scene.props.min_branch_separation = \
     268                  self.scene.min_real_branch_separation
     269          self.scene.props.min_branch_separation += 5
     270          self.scene.draw()
     271
     272      @QtCore.pyqtSignature("")
     273 -    def on_actionZoomOutY_triggered(self):
     274          if self.scene.props.min_branch_separation > 5:
     275              self.scene.props.min_branch_separation -= 5
     276              self.scene.draw()
     277
     278      @QtCore.pyqtSignature("")
     279 -    def on_actionFit2tree_triggered(self):
     280          self.view.fitInView(self.scene.sceneRect(), QtCore.Qt.KeepAspectRatio)
     281
     282      @QtCore.pyqtSignature("")
     283 -    def on_actionFit2region_triggered(self):
     284          if self.scene.highlighter.isVisible():
     285              R = self.scene.highlighter.rect()
     286          else:
     287              R = self.scene.selector.rect()
     288          if R.width()>0 and R.height()>0:
     289
     290
     291              self.view.fitInView(R.x(), R.y(), R.width(),\
     292                                      R.height(), QtCore.Qt.KeepAspectRatio)
     293
     294      @QtCore.pyqtSignature("")
     295 -    def on_actionSearchNode_triggered(self):
     296          ok = self.searchDialog.exec_()
     297          if ok:
     298              setup = self.searchDialog._conf
     299              mType = setup.attrType.currentIndex()
     300              aName = str(setup.attrName.text())
     301              if mType >= 2 and mType <=6:
     302                  try:
     303                      aValue =  float(setup.attrValue.text())
     304                  except ValueError:
     305                      QtGui.QMessageBox.information(self, "!",\
     306                                                "A numeric value is expected")
     307                      return
     308              elif mType == 7:
     309                  aValue = re.compile(str(setup.attrValue.text()))
     310              elif mType == 0 or mType == 1:
     311                  aValue =  str(setup.attrValue.text())
     312
     313              if mType == 0 or mType == 2:
     314                  cmpFn = lambda x,y: x == y
     315              elif mType == 1:
     316                  cmpFn = lambda x,y: y in x
     317              elif mType == 3:
     318                  cmpFn = lambda x,y: x >= y
     319              elif mType == 4:
     320                  cmpFn = lambda x,y: x > y
     321              elif mType == 5:
     322                  cmpFn = lambda x,y: x <= y
     323              elif mType == 6:
     324                  cmpFn = lambda x,y: x < y
     325              elif mType == 7:
     326                  cmpFn = lambda x,y: re.search(y, x)
     327
     328              for n in self.scene.startNode.traverse():
     329                  if setup.leaves_only.isChecked() and not n.is_leaf():
     330                      continue
     331                  if hasattr(n, aName) \
     332                          and cmpFn(getattr(n, aName), aValue ):
     333                      self.scene.highlight_node(n)
     334
     335      @QtCore.pyqtSignature("")
     336 -    def on_actionClear_search_triggered(self):
     337          # This could be much more efficient
     338          for n in self.scene._highlighted_nodes.keys():
     339              self.scene.unhighlight_node(n)
     340
     341      @QtCore.pyqtSignature("")
     342 -    def on_actionBranchLength_triggered(self):
     343          self.scene.props.draw_branch_length ^= True
     344          self.scene.draw()
     345
     346      @QtCore.pyqtSignature("")
     347 -    def on_actionForceTopology_triggered(self):
     348          self.scene.props.force_topology ^= True
     349          self.scene.draw()
     350
     351      @QtCore.pyqtSignature("")
     352 -    def on_actionShow_newick_triggered(self):
     353          d = NewickDialog(self.scene.startNode)
     354          d._conf = _show_newick.Ui_Newick()
     355          d._conf.setupUi(d)
     356          d.update_newick()
     357          d.exec_()
     358
     359      @QtCore.pyqtSignature("")
     360 -    def on_actionChange_orientation_triggered(self):
     361          self.scene.props.orientation ^= 1
     362          self.scene.draw()
     363
     364      @QtCore.pyqtSignature("")
     365 -    def on_actionShow_phenogram_triggered(self):
     366          self.scene.props.style = 0
     367          self.scene.draw()
     368
     369      @QtCore.pyqtSignature("")
     370 -    def on_actionShowCladogram_triggered(self):
     371          self.scene.props.style = 1
     372          self.scene.draw()
     373
     374      @QtCore.pyqtSignature("")
     375 -    def on_actionOpen_triggered(self):
     376
     377          d = QtGui.QFileDialog()
     378          d._conf = _open_newick.Ui_OpenNewick()
     379          d._conf.setupUi(d)
     380          d.exec_()
     381          return
     382
     383
     384          fname = QtGui.QFileDialog.getOpenFileName(self ,"Open File",
     385                                                   "/home",
     386                                                   )
     387
     388
     389          try:
     390              t = Tree(str(fname))
     391          except Exception, e:
     392              print e
     393          else:
     394              self.scene.initialize_tree_scene(t, "basic", TreeImageProperties())
     395              self.scene.draw()
     396
     397      @QtCore.pyqtSignature("")
     398 -    def on_actionSave_newick_triggered(self):
     399          fname = QtGui.QFileDialog.getSaveFileName(self ,"Save File",
     400                                                   "/home",
     401                                                   "Newick (*.nh *.nhx *.nw )")
     402          nw = self.scene.startNode.write()
     403          try:
     404              OUT = open(fname,"w")
     405          except Exception, e:
     406              print e
     407          else:
     408              OUT.write(nw)
     409              OUT.close()
     410
     411      @QtCore.pyqtSignature("")
     412 -    def on_actionRenderPDF_triggered(self):
     413          F = QtGui.QFileDialog(self)
     414          if F.exec_():
     415              imgName = str(F.selectedFiles()[0])
     416              if not imgName.endswith(".pdf"):
     417                  imgName += ".pdf"
     418              self.scene.save(imgName)
     419
     420
     421      @QtCore.pyqtSignature("")
     422 -    def on_actionRender_selected_region_triggered(self):
     423          if not self.scene.selector.isVisible():
     424              return QtGui.QMessageBox.information(self, "!",\
     425                                                "You must select a region first")
     426
     427          F = QtGui.QFileDialog(self)
     428          if F.exec_():
     429              imgName = str(F.selectedFiles()[0])
     430              if not imgName.endswith(".pdf"):
     431                  imgName += ".pdf"
     432              self.scene.save(imgName, take_region=True)
     433
     434
     435      @QtCore.pyqtSignature("")
     436 -    def on_actionPaste_newick_triggered(self):
     437          text,ok = QtGui.QInputDialog.getText(self,\
     438                                                   "Paste Newick",\
     439                                                   "Newick:")
     440          if ok:
     441              try:
     442                  t = Tree(str(text))
     443              except Exception,e:
     444                  print e
     445              else:
     446                  self.scene.initialize_tree_scene(t,"basic", TreeImageProperties())
     447                  self.scene.draw()
     448
     449
     450  # This function should be reviewed. Probably there are better ways to
     451  # do de same, or at least less messy ways... So far this is what I
     452  # have :)
     453 -class _TableItem(QtGui.QItemDelegate):
     454 -    def __init__(self, parent=None):
     455          QtGui.QItemDelegate.__init__(self, parent)
     456          self.propdialog = parent
     457
     458 -    def paint(self, painter, option, index):
     459          self.propdialog.tableView.setRowHeight(index.row(), 18)
     460          QtGui.QItemDelegate.paint(self, painter, option, index)
     461
     462 -    def createEditor(self, parent, option, index):
     463          # Edit only values, not property names
     464          if index.column() != 1:
     465              logger(2, "NOT EDITABLE COLUMN")
     466              return None
     467
     468          originalValue = index.model().data(index)
     469          if not self.isSupportedType(originalValue.type()):
     470              logger(2, "data type not suppoerted for editting")
     471              return None
     472
     473          if re.search("^#[0-9ABCDEFabcdef]{6}$",str(originalValue.toString())):
     474              origc = QtGui.QColor(str(originalValue.toString()))
     475              color = QtGui.QColorDialog.getColor(origc)
     476              if color.isValid():
     477                  self.propdialog._edited_indexes.add( (index.row(), index.column()) )
     478                  index.model().setData(index,QtCore.QVariant(color.name()))
     479                  self.propdialog.apply_changes()
     480
     481              return None
     482          else:
     483              editField = QtGui.QLineEdit(parent)
     484              editField.setFrame(False)
     485              validator = QtGui.QRegExpValidator(QtCore.QRegExp(".+"), editField)
     486              editField.setValidator(validator)
     487              self.connect(editField, QtCore.SIGNAL("returnPressed()"),
     488                           self.commitAndCloseEditor)
     489              self.connect(editField, QtCore.SIGNAL("returnPressed()"),
     490                           self.propdialog.apply_changes)
     491              self.propdialog._edited_indexes.add( (index.row(), index.column()) )
     492              return editField
     493
     494 -    def setEditorData(self, editor, index):
     495          value = index.model().data(index)
     496          if editor is not None:
     497              editor.setText(self.displayText(value))
     498
     499 -    def isSupportedType(valueType):
     500          return True
     501
     502      isSupportedType = staticmethod(isSupportedType)
     503 -    def displayText(self,value):
     504          return value.toString()
     505
     506 -    def commitAndCloseEditor(self):
     507          editor = self.sender()
     508          self.emit(QtCore.SIGNAL("commitData(QWidget *)"), editor)
     509          self.emit(QtCore.SIGNAL("closeEditor(QWidget *)"), editor)
     510
     511 -class _PropModeChooser(QtGui.QWidget):
     512 -    def __init__(self,scene, *args):
     513          QtGui.QWidget.__init__(self,*args)
     514
     515 -class _PropertiesDialog(QtGui.QWidget):
     516 -    def __init__(self,scene, *args):
     517          QtGui.QWidget.__init__(self,*args)
     518          self.scene = scene
     519          self._mode = 0
     520          self.layout =  QtGui.QVBoxLayout()
     521          self.tableView = QtGui.QTableView()
     522          self.tableView.verticalHeader().setVisible(False)
     523          self.tableView.horizontalHeader().setVisible(False)
     524          self.tableView.setVerticalHeader(None)
     525          self.layout.addWidget(self.tableView)
     526          self.setLayout(self.layout)
     527          self.tableView.setGeometry ( 0, 0, 200,200 )
     528
     529
     530 -    def update_properties(self, node):
     531          self.node = node
     532          self._edited_indexes =  set([])
     533          self._style_indexes = set([])
     534          self._prop_indexes = set([])
     535
     536          self.get_prop_table(node)
     537
     538 -    def get_props_in_nodes(self, nodes):
     539          # sorts properties and faces of selected nodes
     540          self.prop2nodes = {}
     541          self.prop2values = {}
     542          self.style2nodes = {}
     543          self.style2values = {}
     544
     545          for n in nodes:
     546              for pname in n.features:
     547                  pvalue = getattr(n,pname)
     548                  if type(pvalue) == int or \
     549                     type(pvalue) == float or \
     550                     type(pvalue) == str :
     551                      self.prop2nodes.setdefault(pname,[]).append(n)
     552                      self.prop2values.setdefault(pname,[]).append(pvalue)
     553
     554              for pname,pvalue in n.img_style.iteritems():
     555                  if type(pvalue) == int or \
     556                     type(pvalue) == float or \
     557                     type(pvalue) == str :
     558                      self.style2nodes.setdefault(pname,[]).append(n)
     559                      self.style2values.setdefault(pname,[]).append(pvalue)
     560
     561 -    def get_prop_table(self, node):
     562          if self._mode == 0: # node
     563              self.get_props_in_nodes([node])
     564          elif self._mode == 1: # childs
     565              self.get_props_in_nodes(node.get_leaves())
     566          elif self._mode == 2: # partition
     567              self.get_props_in_nodes([node]+node.get_descendants())
     568
     569          total_props = len(self.prop2nodes) + len(self.style2nodes.keys())
     570          self.model = QtGui.QStandardItemModel(total_props, 2)
     571          # self.tableView = QtGui.QTableView()
     572          self.tableView.setModel(self.model)
     573          self.delegate = _TableItem(self)
     574          self.tableView.setItemDelegate(self.delegate)
     575
     576          row = 0
     577          for name,nodes in self.prop2nodes.iteritems():
     578              value= getattr(nodes[0],name)
     579
     580              index1 = self.model.index(row, 0, QtCore.QModelIndex())
     581              index2 = self.model.index(row, 1, QtCore.QModelIndex())
     582
     583              self.model.setData(index1, QtCore.QVariant(name))
     584              v = QtCore.QVariant(value)
     585              self.model.setData(index2, v)
     586
     587              self._prop_indexes.add( (index1, index2) )
     588              row +=1
     589
     590          for name in self.style2nodes.iterkeys():
     591              value= self.style2values[name][0]
     592
     593              index1 = self.model.index(row, 0, QtCore.QModelIndex())
     594              index2 = self.model.index(row, 1, QtCore.QModelIndex())
     595
     596              self.model.setData(index1, QtCore.QVariant(name))
     597              v = QtCore.QVariant(value)
     598              self.model.setData(index2, v)
     599              # Creates a variant element
     600              self._style_indexes.add( (index1, index2) )
     601              row +=1
     602          return
     603
     604 -    def apply_changes(self):
     605          # Apply changes on styles
     606          for i1, i2 in self._style_indexes:
     607              if (i2.row(), i2.column()) not in self._edited_indexes:
     608                  continue
     609              name = str(self.model.data(i1).toString())
     610              value = str(self.model.data(i2).toString())
     611              for n in self.style2nodes[name]:
     612                  typedvalue = type(n.img_style[name])(value)
     613                  try:
     614                      n.img_style[name] = typedvalue
     615                  except:
     616                      logger(-1, "Wrong format for attribute:", name)
     617                      break
     618
     619          # Apply changes on properties
     620          for i1, i2 in self._prop_indexes:
     621              if (i2.row(), i2.column()) not in self._edited_indexes:
     622                  continue
     623              name = str(self.model.data(i1).toString())
     624              value = str(self.model.data(i2).toString())
     625              for n in self.prop2nodes[name]:
     626                  try:
     627                      setattr(n, name, type(getattr(n,name))(value))
     628                  except Exception, e:
     629                      logger(-1, "Wrong format for attribute:", name)
     630                      print e
     631                      break
     632          self.update_properties(self.node)
     633          self.scene.draw()
     634          return
     635
     636 -class _TextItem(QtGui.QGraphicsSimpleTextItem):
     637      """ Manage faces on Scene"""
     638 -    def __init__(self,face,node,*args):
     639          QtGui.QGraphicsSimpleTextItem.__init__(self,*args)
     640          self.node = node
     641          self.face = face
     642
     643 -    def hoverEnterEvent (self,e):
     644  #       if self.scene().selector.isVisible():
     645  #           self.scene().mouseMoveEvent(e)
     646
     647          R = self.node.fullRegion.getRect()
     648          if self.scene().props.orientation == 0:
     649              width = self.scene().i_width-self.node._x
     650              self.scene().highlighter.setRect(QtCore.QRectF(self.node._x,self.node._y,width,R[3]))
     651          elif self.scene().props.orientation == 1:
     652              width = self.node._x-self.scene().i_width
     653              self.scene().highlighter.setRect(QtCore.QRectF(width,self.node._y,width,R[3]))
     654          self.scene().highlighter.setVisible(True)
     655
     656 -    def hoverLeaveEvent (self,e):
     657          self.scene().highlighter.setVisible(False)
     658
     659 -    def mousePressEvent(self,e):
     660          pass
     661
     662 -    def mouseReleaseEvent(self,e):
     663          logger(2,"released in scene", e.button)
     664          if e.button() == QtCore.Qt.RightButton:
     665              self.node._QtItem_.showActionPopup()
     666          elif e.button() == QtCore.Qt.LeftButton:
     667              self.scene().propertiesTable.update_properties(self.node)
     668
     669
     670 -class _FaceItem(QtGui.QGraphicsPixmapItem):
     671      """ Manage faces on Scene"""
     672 -    def __init__(self,face,node,*args):
     673          QtGui.QGraphicsPixmapItem.__init__(self,*args)
     674          self.node = node
     675          self.face = face
     676
     677 -    def hoverEnterEvent (self,e):
     678  #       if self.scene().selector.isVisible():
     679  #           self.scene().mouseMoveEvent(e)
     680
     681          R = self.node.fullRegion.getRect()
     682          if self.scene().props.orientation == 0:
     683              width = self.scene().i_width-self.node._x
     684              self.scene().highlighter.setRect(QtCore.QRectF(self.node._x,self.node._y,width,R[3]))
     685          elif self.scene().props.orientation == 1:
     686              width = self.node._x-self.scene().i_width
     687              self.scene().highlighter.setRect(QtCore.QRectF(width,self.node._y,width,R[3]))
     688          self.scene().highlighter.setVisible(True)
     689
     690 -    def hoverLeaveEvent (self,e):
     691          self.scene().highlighter.setVisible(False)
     692
     693 -    def mousePressEvent(self,e):
     694          pass
     695
     696 -    def mouseReleaseEvent(self,e):
     697          logger(2,"released in scene", e.button)
     698          if e.button() == QtCore.Qt.RightButton:
     699              self.node._QtItem_.showActionPopup()
     700          elif e.button() == QtCore.Qt.LeftButton:
     701              self.scene().propertiesTable.update_properties(self.node)
     702
     703
     704 -class _NodeItem(QtGui.QGraphicsRectItem):
     705 -    def __init__(self,node):
     706          self.node = node
     707          self.radius = node.img_style["size"]/2
     708          QtGui.QGraphicsRectItem.__init__(self,0,0,self.radius*2,self.radius*2)
     709
     710 -    def paint(self, p, option, widget):
     711          if self.node.img_style["shape"] == "sphere":
     712              r = self.radius
     713              gradient = QtGui.QRadialGradient(r, r, r,(r*2)/3,(r*2)/3)
     714              gradient.setColorAt(0.05, QtCore.Qt.white);
     715              gradient.setColorAt(0.9, QtGui.QColor(self.node.img_style["fgcolor"]));
     716              p.setBrush(QtGui.QBrush(gradient))
     717              p.setPen(QtCore.Qt.NoPen)
     718              p.drawEllipse(self.rect())
     719          elif self.node.img_style["shape"] == "square":
     720              p.fillRect(self.rect(),QtGui.QBrush(QtGui.QColor(self.node.img_style["fgcolor"])))
     721          elif self.node.img_style["shape"] == "circle":
     722              p.setBrush(QtGui.QBrush(QtGui.QColor(self.node.img_style["fgcolor"])))
     723              p.setPen(QtGui.QPen(QtGui.QColor(self.node.img_style["fgcolor"])))
     724              p.drawEllipse(self.rect())
     725
     726
     727 -    def hoverEnterEvent (self,e):
     728          R = self.node.fullRegion.getRect()
     729          if self.scene().props.orientation == 0:
     730              width = self.scene().i_width-self.node._x
     731              self.scene().highlighter.setRect(QtCore.QRectF(self.node._x,self.node._y,width,R[3]))
     732          elif self.scene().props.orientation == 1:
     733              width = self.node._x-self.scene().i_width
     734              self.scene().highlighter.setRect(QtCore.QRectF(width,self.node._y,width,R[3]))
     735          self.scene().highlighter.setVisible(True)
     736
     737 -    def hoverLeaveEvent (self,e):
     738          self.scene().highlighter.setVisible(False)
     739
     740 -    def mousePressEvent(self,e):
     741          logger(2,"Pressed in scene", e.button)
     742
     743 -    def mouseReleaseEvent(self,e):
     744          logger(2,"released in scene", e.button)
     745          if e.button() == QtCore.Qt.RightButton:
     746              self.showActionPopup()
     747          elif e.button() == QtCore.Qt.LeftButton:
     748              self.scene().propertiesTable.update_properties(self.node)
     749
     750
     751 -    def showActionPopup(self):
     752          contextMenu = QtGui.QMenu()
     753          if self.node.collapsed:
     754              contextMenu.addAction( "Expand"           , self.toggle_collapse)
     755          else:
     756              contextMenu.addAction( "Collapse"         , self.toggle_collapse)
     757
     758          contextMenu.addAction( "Set as outgroup"      , self.set_as_outgroup)
     759          contextMenu.addAction( "Swap branches"        , self.swap_branches)
     760          contextMenu.addAction( "Delete node"          , self.delete_node)
     761          contextMenu.addAction( "Delete partition"     , self.detach_node)
     762          contextMenu.addAction( "Add childs"           , self.add_childs)
     763          contextMenu.addAction( "Populate partition"   , self.populate_partition)
     764          if self.node.up is not None and\
     765                  self.scene().startNode == self.node:
     766              contextMenu.addAction( "Back to parent", self.back_to_parent_node)
     767          else:
     768              contextMenu.addAction( "Extract"              , self.set_start_node)
     769
     770          if self.scene().buffer_node:
     771              contextMenu.addAction( "Paste partition"  , self.paste_partition)
     772
     773          contextMenu.addAction( "Cut partition"        , self.cut_partition)
     774          contextMenu.addAction( "Show newick"        , self.show_newick)
     775          contextMenu.exec_(QtGui.QCursor.pos())
     776
     777 -    def show_newick(self):
     778          d = NewickDialog(self.node)
     779          d._conf = _show_newick.Ui_Newick()
     780          d._conf.setupUi(d)
     781          d.update_newick()
     782          d.exec_()
     783          return False
     784
     785 -    def delete_node(self):
     786          self.node.delete()
     787          self.scene().draw()
     788
     789 -    def detach_node(self):
     790          self.node.detach()
     791          self.scene().draw()
     792
     793 -    def swap_branches(self):
     794          self.node.swap_childs()
     795          self.scene().draw()
     796
     797 -    def add_childs(self):
     798          n,ok = QtGui.QInputDialog.getInteger(None,"Add childs","Number of childs to add:",1,1)
     799          if ok:
     800              for i in xrange(n):
     801                  ch = self.node.add_child()
     802              self.scene().set_style_from(self.scene().startNode,self.scene().layout_func)
     803
     804 -    def void(self):
     805          logger(0,"Not implemented yet")
     806          return True
     807
     808 -    def set_as_outgroup(self):
     809          self.scene().startNode.set_outgroup(self.node)
     810          self.scene().set_style_from(self.scene().startNode, self.scene().layout_func)
     811          self.scene().draw()
     812
     813 -    def toggle_collapse(self):
     814          self.node.collapsed ^= True
     815          self.scene().draw()
     816
     817 -    def cut_partition(self):
     818          self.scene().buffer_node = self.node
     819          self.node.detach()
     820          self.scene().draw()
     821
     822 -    def paste_partition(self):
     823          if self.scene().buffer_node:
     824              self.node.add_child(self.scene().buffer_node)
     825              self.scene().set_style_from(self.scene().startNode,self.scene().layout_func)
     826              self.scene().buffer_node= None
     827              self.scene().draw()
     828
     829 -    def populate_partition(self):
     830          n, ok = QtGui.QInputDialog.getInteger(None,"Populate partition","Number of nodes to add:",2,1)
     831          if ok:
     832              self.node.populate(n)
     833              self.scene().set_style_from(self.scene().startNode,self.scene().layout_func)
     834              self.scene().draw()
     835
     836 -    def set_start_node(self):
     837          self.scene().startNode = self.node
     838          self.scene().draw()
     839
     840 -    def back_to_parent_node(self):
     841          self.scene().startNode = self.node.up
     842          self.scene().draw()
     843
     844
     845 -class _SelectorItem(QtGui.QGraphicsRectItem):
     846 -    def __init__(self):
     847          self.Color = QtGui.QColor("blue")
     848          self._active = False
     849          QtGui.QGraphicsRectItem.__init__(self,0,0,0,0)
     850
     851 -    def paint(self, p, option, widget):
     852          p.setPen(self.Color)
     853          p.drawRect(self.rect().x(),self.rect().y(),self.rect().width(),self.rect().height())
     854          return
     855          # Draw info text
     856          font = QtGui.QFont("Arial",13)
     857          text = "%d selected."  % len(self.get_selected_nodes())
     858          textR = QtGui.QFontMetrics(font).boundingRect(text)
     859          if  self.rect().width() > textR.width() and \
     860                  self.rect().height() > textR.height()/2 and 0: # OJO !!!!
     861              p.setPen(QtGui.QPen(self.Color))
     862              p.setFont(QtGui.QFont("Arial",13))
     863              p.drawText(self.rect().bottomLeft().x(),self.rect().bottomLeft().y(),text)
     864
     865 -    def get_selected_nodes(self):
     866          selPath = QtGui.QPainterPath()
     867          selPath.addRect(self.rect())
     868          self.scene().setSelectionArea(selPath)
     869          return [i.node for i in self.scene().selectedItems()]
     870
     871 -    def setActive(self,bool):
     872          self._active = bool
     873
     874 -    def isActive(self):
     875          return self._active
     876
     877
     878 -class _HighlighterItem(QtGui.QGraphicsRectItem):
     879 -    def __init__(self):
     880          self.Color = QtGui.QColor("red")
     881          self._active = False
     882          QtGui.QGraphicsRectItem.__init__(self,0,0,0,0)
     883
     884 -    def paint(self, p, option, widget):
     885          p.setPen(self.Color)
     886          p.drawRect(self.rect().x(),self.rect().y(),self.rect().width(),self.rect().height())
     887          return
     888
     889
     890
     891 -class _MainView(QtGui.QGraphicsView):
     892 -    def __init__(self,*args):
     893          QtGui.QGraphicsView.__init__(self,*args)
     894
     895          #self.setViewportUpdateMode(QtGui.QGraphicsView.FullViewportUpdate)
     896          #self.setViewportUpdateMode(QtGui.QGraphicsView.MinimalViewportUpdate)
     897          #self.setOptimizationFlag (QtGui.QGraphicsView.DontAdjustForAntialiasing)
     898          #self.setOptimizationFlag (QtGui.QGraphicsView.DontSavePainterState)
     899
     900          if USE_GL:
     901              F = QtOpenGL.QGLFormat()
     902              F.setSampleBuffers(True)
     903              print F.sampleBuffers()
     904              self.setViewport(QtOpenGL.QGLWidget(F))
     905              self.setRenderHints(QtGui.QPainter.Antialiasing)
     906          else:
     907              self.setRenderHints(QtGui.QPainter.Antialiasing or QtGui.QPainter.SmoothPixmapTransform )
     908              self.setViewportUpdateMode(QtGui.QGraphicsView.SmartViewportUpdate)
     909
     910
     911 -    def resizeEvent(self, e):
     912          QtGui.QGraphicsView.resizeEvent(self, e)
     913          logger(2, "RESIZE VIEW!!")
     914
     915
     916 -    def safe_scale(self, xfactor, yfactor):
     917          self.setTransformationAnchor(self.AnchorUnderMouse)
     918
     919          xscale = self.matrix().m11()
     920          yscale = self.matrix().m22()
     921          srect = self.sceneRect()
     922
     923          if (xfactor>1 and xscale>200000) or \
     924                  (yfactor>1 and yscale>200000):
     925              QtGui.QMessageBox.information(self, "!",\
     926                                                "Hey! I'm not an electron microscope?")
     927              return
     928
     929          # Do not allow to reduce scale to a value producing height or with smaller than 20 pixels
     930          # No restrictions to zoomin
     931          if (yfactor<1 and  srect.width() * yscale < 20):
     932              pass
     933          elif (xfactor<1 and  srect.width() * xscale < 20):
     934              pass
     935          else:
     936              self.scale(xfactor, yfactor)
     937
     938
     939 -    def wheelEvent(self,e):
     940          factor =  (-e.delta() / 360.0)
     941
     942          # Ctrl+Shift -> Zoom in X
     943          if  (e.modifiers() & QtCore.Qt.ControlModifier) and (e.modifiers() & QtCore.Qt.ShiftModifier):
     944              self.safe_scale(1+factor, 1)
     945
     946          # Ctrl+Alt -> Zomm in Y
     947          elif  (e.modifiers() & QtCore.Qt.ControlModifier) and (e.modifiers() & QtCore.Qt.AltModifier):
     948              self.safe_scale(1,1+factor)
     949
     950          # Ctrl -> Zoom X,Y
     951          elif e.modifiers() & QtCore.Qt.ControlModifier:
     952              self.safe_scale(1-factor, 1-factor)
     953
     954          # Shift -> Horizontal scroll
     955          elif e.modifiers() &  QtCore.Qt.ShiftModifier:
     956              if e.delta()>0:
     957                  self.horizontalScrollBar().setValue(self.horizontalScrollBar().value()-20 )
     958              else:
     959                  self.horizontalScrollBar().setValue(self.horizontalScrollBar().value()+20 )
     960          # No modifiers ->  Vertival scroll
     961          else:
     962              if e.delta()>0:
     963                  self.verticalScrollBar().setValue(self.verticalScrollBar().value()-20 )
     964              else:
     965                  self.verticalScrollBar().setValue(self.verticalScrollBar().value()+20 )
     966
     967 -    def keyPressEvent(self,e):
     968          key = e.key()
     969          logger(1, "****** Pressed key: ", key, QtCore.Qt.LeftArrow)
     970          if key  == QtCore.Qt.Key_Left:
     971              self.horizontalScrollBar().setValue(self.horizontalScrollBar().value()-20 )
     972              self.update()
     973          elif key  == QtCore.Qt.Key_Right:
     974              self.horizontalScrollBar().setValue(self.horizontalScrollBar().value()+20 )
     975              self.update()
     976          elif key  == QtCore.Qt.Key_Up:
     977              self.verticalScrollBar().setValue(self.verticalScrollBar().value()-20 )
     978              self.update()
     979          elif key  == QtCore.Qt.Key_Down:
     980              self.verticalScrollBar().setValue(self.verticalScrollBar().value()+20 )
     981              self.update()
     982          QtGui.QGraphicsView.keyPressEvent(self,e)
     983
     984 -class _TreeScene(QtGui.QGraphicsScene):
     985 -    def __init__(self, rootnode=None, style=None, *args):
     986          QtGui.QGraphicsScene.__init__(self,*args)
     987
     988          self.view = None
     989          # Config variables
     990          self.buffer_node = None        # Used to copy and paste
     991          self.layout_func = None        # Layout function
     992          self.startNode   = rootnode    # Node to start drawing
     993          self.scale       = 0           # Tree branch scale used to draw
     994          self.max_w_aligned_face = 0    # Stores the max width of aligned faces
     995          self.min_real_branch_separation = 0
     996          self.selectors  = []
     997          self._highlighted_nodes = {}
     998
     999          # Qt items
    1000          self.selector = None
    1001          self.mainItem = None        # Qt Item which is parent of all other items
    1002          self.propertiesTable = _PropertiesDialog(self)
    1003
    1004 -    def initialize_tree_scene(self, tree, style, tree_properties):
    1005          self.tree        = tree        # Pointer to original tree
    1006          self.startNode   = tree        # Node to start drawing
    1007          self.max_w_aligned_face = 0    # Stores the max width of aligned faces
    1008
    1009          # Load image attributes
    1010          self.props = tree_properties
    1011
    1012          # Validates layout function
    1013          if type(style) == types.FunctionType or\
    1014                  type(style) == types.MethodType:
    1015              self.layout_func = style
    1016          else:
    1017              try:
    1018                  self.layout_func = getattr(layouts,style)
    1019              except:
    1020                  raise ValueError, "Required layout is not a function pointer nor a valid layout name."
    1021
    1022          # Set the scene background
    1023          self.setBackgroundBrush(QtGui.QColor("white"))
    1024
    1025          # Set nodes style
    1026          self.set_style_from(self.startNode,self.layout_func)
    1027
    1028          self.propertiesTable.update_properties(self.startNode)
    1029
    1030 -    def highlight_node(self, n):
    1031          self.unhighlight_node(n)
    1032          r = QtGui.QGraphicsRectItem(self.mainItem)
    1033          self._highlighted_nodes[n] = r
    1034
    1035          R = n.fullRegion.getRect()
    1036          width = self.i_width-n._x
    1037          r.setRect(QtCore.QRectF(n._x,n._y,width,R[3]))
    1038
    1039          #r.setRect(0,0, n.fullRegion.width(), n.fullRegion.height())
    1040
    1041          #r.setPos(n.scene_pos)
    1042          # Don't know yet why do I have to add 2 pixels :/
    1043          #r.moveBy(0,0)
    1044          r.setZValue(-1)
    1045          r.setPen(QtGui.QColor(self.props.search_node_fg))
    1046          r.setBrush(QtGui.QColor(self.props.search_node_bg))
    1047
    1048          # self.view.horizontalScrollBar().setValue(n._x)
    1049          # self.view.verticalScrollBar().setValue(n._y)
    1050
    1051 -    def unhighlight_node(self, n):
    1052          if n in self._highlighted_nodes and \
    1053                  self._highlighted_nodes[n] is not None:
    1054              self.removeItem(self._highlighted_nodes[n])
    1055              del self._highlighted_nodes[n]
    1056
    1057
    1058 -    def mousePressEvent(self,e):
    1059          logger(2, "Press en view")
    1060          self.selector.setRect(e.scenePos().x(),e.scenePos().y(),0,0)
    1061          self.selector.startPoint = QtCore.QPointF(e.scenePos().x(),e.scenePos().y())
    1062          self.selector.setActive(True)
    1063          self.selector.setVisible(True)
    1064          QtGui.QGraphicsScene.mousePressEvent(self,e)
    1065
    1066 -    def mouseReleaseEvent(self,e):
    1067          logger(2, "Release en view")
    1068          curr_pos = e.scenePos()
    1069          x = min(self.selector.startPoint.x(),curr_pos.x())
    1070          y = min(self.selector.startPoint.y(),curr_pos.y())
    1071          w = max(self.selector.startPoint.x(),curr_pos.x()) - x
    1072          h = max(self.selector.startPoint.y(),curr_pos.y()) - y
    1073          if self.selector.startPoint == curr_pos:
    1074              self.selector.setVisible(False)
    1075          else:
    1076              logger(2, self.selector.get_selected_nodes())
    1077          self.selector.setActive(False)
    1078          QtGui.QGraphicsScene.mouseReleaseEvent(self,e)
    1079
    1080 -    def mouseMoveEvent(self,e):
    1081
    1082          curr_pos = e.scenePos()
    1083          if self.selector.isActive():
    1084              x = min(self.selector.startPoint.x(),curr_pos.x())
    1085              y = min(self.selector.startPoint.y(),curr_pos.y())
    1086              w = max(self.selector.startPoint.x(),curr_pos.x()) - x
    1087              h = max(self.selector.startPoint.y(),curr_pos.y()) - y
    1088              self.selector.setRect(x,y,w,h)
    1089          QtGui.QGraphicsScene.mouseMoveEvent(self, e)
    1090
    1091 -    def mouseDoubleClickEvent(self,e):
    1092          logger(2, "Double click")
    1093          QtGui.QGraphicsScene.mouseDoubleClickEvent(self,e)
    1094
    1095 -    def save(self, imgName, w=None, h=None, header=None, \
    1096                   dpi=150, take_region=False):
    1097          ext = imgName.split(".")[-1].upper()
    1098
    1099
    1100          root = self.startNode
    1101          aspect_ratio = root.fullRegion.height() / root.fullRegion.width()
    1102
    1103          # auto adjust size
    1104          if w is None and h is None:
    1105              w = dpi * 6.4
    1106              h = w * aspect_ratio
    1107              if h>dpi * 11:
    1108                  h = dpi * 11
    1109                  w = h / aspect_ratio
    1110
    1111          elif h is None:
    1112              h = w * aspect_ratio
    1113          elif w is None:
    1114              w = h / aspect_ratio
    1115
    1116          if ext == "PDF" or ext == "PS":
    1117              format = QPrinter.PostScriptFormat if ext == "PS" else QPrinter.PdfFormat
    1118              printer = QPrinter(QPrinter.HighResolution)
    1119              printer.setResolution(dpi)
    1120              printer.setOutputFormat(format)
    1121              printer.setPageSize(QPrinter.A4)
    1122
    1123              pageTopLeft = printer.pageRect().topLeft()
    1124              paperTopLeft = printer.paperRect().topLeft()
    1125              # For PS -> problems with margins
    1126              # print paperTopLeft.x(), paperTopLeft.y()
    1127              # print pageTopLeft.x(), pageTopLeft.y()
    1128              # print  printer.paperRect().height(),  printer.pageRect().height()
    1129              topleft =  pageTopLeft - paperTopLeft
    1130
    1131              printer.setFullPage(True);
    1132              printer.setOutputFileName(imgName);
    1133              pp = QtGui.QPainter(printer)
    1134              if header:
    1135                  pp.setFont(QtGui.QFont("Verdana",12))
    1136                  pp.drawText(topleft.x(),20, header)
    1137                  targetRect =  QtCore.QRectF(topleft.x(), 20 + (topleft.y()*2), w, h)
    1138              else:
    1139                  targetRect =  QtCore.QRectF(topleft.x(), topleft.y()*2, w, h)
    1140
    1141              if take_region:
    1142                  self.selector.setVisible(False)
    1143                  self.render(pp, targetRect, self.selector.rect())
    1144                  self.selector.setVisible(True)
    1145              else:
    1146                  self.render(pp, targetRect, self.sceneRect())
    1147              pp.end()
    1148              return
    1149          else:
    1150              targetRect = QtCore.QRectF(0, 0, w, h)
    1151              ii= QtGui.QImage(w, \
    1152                                   h, \
    1153                                   QtGui.QImage.Format_ARGB32)
    1154              pp = QtGui.QPainter(ii)
    1155              pp.setRenderHint(QtGui.QPainter.Antialiasing )
    1156              pp.setRenderHint(QtGui.QPainter.TextAntialiasing)
    1157              pp.setRenderHint(QtGui.QPainter.SmoothPixmapTransform)
    1158              if take_region:
    1159                  self.selector.setVisible(False)
    1160                  self.render(pp, targetRect, self.selector.rect())
    1161                  self.selector.setVisible(True)
    1162              else:
    1163                  self.render(pp, targetRect, self.sceneRect())
    1164              pp.end()
    1165              ii.save(imgName)
    1166
    1167      # Undocummented and untested
    1168 -    def save_by_chunks(self,imgName="img.out",rect=None):
    1169          max_width = 10000
    1170          max_height = 10000
    1171          if imgName.endswith(".png"):
    1172              imgName = ''.join(imgName.split('.')[:-1])
    1173
    1174          if rect is None:
    1175              rect = self.sceneRect()
    1176
    1177          width = int(rect.width())
    1178          height = int(rect.height())
    1179
    1180          start_x = 0
    1181          missing_width = width
    1182          counter_column = 1
    1183          for w in xrange(start_x, width, max_width):
    1184              start_y = 0
    1185              missing_height = height
    1186              counter_row = 1
    1187              for h in xrange(start_y, height, max_height):
    1188                  chunk_width = min( missing_width, max_width )
    1189                  chunk_height = min( missing_height, max_height )
    1190                  temp_rect = QtCore.QRectF( rect.x()+w, \
    1191                                          rect.y()+h,
    1192                                          chunk_width, \
    1193                                          chunk_height    )
    1194                  if chunk_height<=0 or chunk_width <=0:
    1195                      break
    1196                  ii= QtGui.QImage(chunk_width, \
    1197                                       chunk_height, \
    1198                                       QtGui.QImage.Format_RGB32)
    1199                  pp = QtGui.QPainter(ii)
    1200                  targetRect = QtCore.QRectF(0, 0, temp_rect.width(), temp_rect.height())
    1201                  self.render(pp, targetRect, temp_rect)
    1202                  ii.saves("%s-%s_%s.png" %(imgName,counter_row,counter_column))
    1203                  counter_row += 1
    1204
    1205                  missing_height -= chunk_height
    1206                  pp.end()
    1207              counter_column += 1
    1208              missing_width -= chunk_width
    1209
    1210 -    def draw(self):
    1211          # Clean previous items from scene by removing the main parent
    1212          if self.mainItem:
    1213              self.removeItem(self.mainItem)
    1214
    1215          #Clean_highlighting rects
    1216          for n in self._highlighted_nodes:
    1217              self._highlighted_nodes[n] = None
    1218
    1219          # Recreates main parent and add it to scene
    1220          self.mainItem = QtGui.QGraphicsRectItem()
    1221          self.addItem(self.mainItem)
    1222          # Recreates selector item (used to zoom etc...)
    1223          self.selector = _SelectorItem()
    1224          self.selector.setParentItem(self.mainItem)
    1225          self.selector.setVisible(False)
    1226          self.selector.setZValue(2)
    1227
    1228          self.highlighter   = _HighlighterItem()
    1229          self.highlighter.setParentItem(self.mainItem)
    1230          self.highlighter.setVisible(False)
    1231          self.highlighter.setZValue(2)
    1232          self.min_real_branch_separation = 0
    1233
    1234          # Get branch scale
    1235          fnode, max_dist = self.startNode.get_farthest_leaf(topology_only=\
    1236              self.props.force_topology)
    1237
    1238          if max_dist>0:
    1239              self.scale =  self.props.tree_width / max_dist
    1240          else:
    1241              self.scale =  1
    1242
    1243          # Updates all nodes regions and their faces
    1244          t1 = time.time()
    1245          self.update_node_areas(self.startNode)
    1246          logger(2, "Ellapsed time for updating node areas:",time.time()-t1)
    1247          # Get picture dimensions
    1248          self.i_width  = self.startNode.fullRegion.width()
    1249          self.i_height = self.startNode.fullRegion.height()
    1250
    1251
    1252          logger(1, "IMAGE dimension=",self.i_width,"x",self.i_height)
    1253          # Render tree on scene
    1254          t2 = time.time()
    1255          self.render_node(self.startNode,0,0)
    1256          logger(2, "Time for rendering", time.time()-t2)
    1257
    1258          # size correcton for aligned faces
    1259          self.i_width += self.max_w_aligned_face
    1260          # New pos for tree when inverse orientation
    1261          if self.props.orientation == 1:
    1262              self.startNode._QtItem_.moveBy(self.max_w_aligned_face,0)
    1263
    1264          # Tree border
    1265          #border = self.addRect(0,0,self.i_width, self.i_height)
    1266          #border = self.addRect(0,0,self.i_width-self.max_w_aligned_face,self.i_height)
    1267          #border = self.addRect(0,0, self.sceneRect().width(), self.sceneRect().height())
    1268          #border.setParentItem(self.mainItem)
    1269          self.add_scale(1 ,self.i_height+4)
    1270
    1271          #Re-establish node marks
    1272          for n in self._highlighted_nodes:
    1273              self.highlight_node(n)
    1274
    1275          self.setSceneRect(-2,-2,self.i_width+4,self.i_height+50)
    1276          logger(2, "Number of items in scene:", len(self.items()))
    1277
    1278 -    def add_scale(self,x,y):
    1279          size = 50
    1280          customPen  = QtGui.QPen(QtGui.QColor("black"),1)
    1281
    1282          line = QtGui.QGraphicsLineItem(self.mainItem)
    1283          line2 = QtGui.QGraphicsLineItem(self.mainItem)
    1284          line3 = QtGui.QGraphicsLineItem(self.mainItem)
    1285          line.setPen(customPen)
    1286          line2.setPen(customPen)
    1287          line3.setPen(customPen)
    1288
    1289          line.setLine(x,y+20,size,y+20)
    1290          line2.setLine(x,y+15,x,y+25)
    1291          line3.setLine(size,y+15,size,y+25)
    1292
    1293          scale_text = "%0.2f" % float(size/ self.scale)
    1294          scale = QtGui.QGraphicsSimpleTextItem(scale_text)
    1295          scale.setParentItem(self.mainItem)
    1296          scale.setPos(x,y+20)
    1297
    1298          if self.props.force_topology:
    1299              wtext = "Force topology is enabled!\nBranch lengths does not represent original values."
    1300              warning_text = QtGui.QGraphicsSimpleTextItem(wtext)
    1301              warning_text.setFont( QtGui.QFont("Arial", 8))
    1302              warning_text.setBrush( QtGui.QBrush(QtGui.QColor("darkred")))
    1303              warning_text.setPos(x, y+32)
    1304              warning_text.setParentItem(self.mainItem)
    1305
    1306 -    def set_style_from(self,node,layout_func):
    1307          for n in [node]+node.get_descendants():
    1308              n.img_style = copy.copy(_MIN_NODE_STYLE)
    1309              n.img_style["faces"] = []
    1310              layout_func(n)
    1311
    1312 -    def update_node_areas(self,node):
    1313          """ This recursive function scans all nodes hunging from the given
    1314          root node and calculates the coordinates and room necessary to
    1315          draw a rectangular tree. IT reads the face content of each
    1316          node, which ones have to be drawn, and how much room they
    1317          use. """
    1318          child_rects = []
    1319          # First, go for childs
    1320          if not node.is_leaf() and node.img_style["draw_descendants"]==1:
    1321              for ch in node.children:
    1322                  # Recursivity
    1323                  rect = self.update_node_areas(ch)
    1324                  child_rects.append(rect)
    1325
    1326          # This is the node region covered by column faces and the
    1327          # horizontal line drawn using the branch length and scale.
    1328
    1329          if self.props.force_topology:
    1330              node.dist_xoffset = 60#self.scale
    1331          else:
    1332              node.dist_xoffset = float(node.dist * self.scale)
    1333
    1334          min_node_height = max(node.img_style["size"], node.img_style["hlwidth"]*2)
    1335          max_w         = 0
    1336          max_aligned_w = 0
    1337          max_h         = 0
    1338          # Each stack is drawn as a different column
    1339          for stack in node.img_style["faces"]:
    1340              stack_h = 0
    1341              stack_w = 0
    1342              aligned_f_w = 0
    1343              aligned_f_h = 0
    1344              for face in stack:
    1345                  f, aligned, pixmap = face
    1346                  if aligned and not node.is_leaf():
    1347                      continue
    1348                  # Sets the working node from which required info will
    1349                  # be retrived. Thus, same face can be used for many nodes.
    1350                  f.node = node
    1351                  # If pixmap face, updates image
    1352                  if f.type == "pixmap":
    1353                      f.update_pixmap() # using current working node
    1354                      face[2] = f.pixmap
    1355
    1356                  if node.is_leaf() and aligned:
    1357                      aligned_f_w = max(aligned_f_w, f._width()) #+ f.xmargin*2
    1358                      aligned_f_h += f._height() #+ f.ymargin * 2
    1359                  else:
    1360                      stack_w = max(stack_w, f._width()) #+ f.xmargin*2
    1361                      stack_h += f._height() #+ f.ymargin*2
    1362
    1363              max_aligned_w += aligned_f_w
    1364              max_w += stack_w
    1365              max_h = numpy.max([aligned_f_h, stack_h, max_h])
    1366          max_w +=1
    1367          # Updates the max width spend by aligned faces
    1368          if max_aligned_w > self.max_w_aligned_face:
    1369              self.max_w_aligned_face = max_aligned_w
    1370
    1371          # Dist and faces region
    1372          node.facesRegion = QtCore.QRectF(0,0,max_w,max_h)
    1373          w = node.dist_xoffset + max_w + node.img_style["size"]
    1374          h = max(max_h, min_node_height, self.props.min_branch_separation) + node.img_style["ymargin"]*2
    1375          if self.min_real_branch_separation < h:
    1376              self.min_real_branch_separation = h
    1377
    1378          node.nodeRegion = QtCore.QRectF(0,0,w,h)
    1379
    1380          # This piece of code fixes an old and annoying bug by which nodes with
    1381          # faces larger than the sum of child node region were badly
    1382          # drawn (badly centered and using space from other nodes)
    1383          if not node.is_leaf() and node.img_style["draw_descendants"] == 1:
    1384              max_child_w = 0
    1385              sum_child_h = 0
    1386              # y correction is used to fix cases in which the height of
    1387              # parent nodes is greater than the sum of child
    1388              # heights. Then, an y offset is calculated as the missing
    1389              # amount of pixels. This correction is used by the render
    1390              # algorithm to draw child 'y_correction" pixels bellow the
    1391              # expected position.
    1392              node._y_correction = 0
    1393              start2 = 0
    1394              for ch in node.children:
    1395                  # Updates the max width used by childs
    1396                  if ch.fullRegion.width()>max_child_w:
    1397                      max_child_w = ch.fullRegion.width()
    1398                  # Stores the 'y' center of the first child node
    1399                  if ch == node.children[0]:
    1400                      start1 = ch.__center
    1401                  # And the 'y' center of the last child node
    1402                  elif ch == node.children[-1]:
    1403                      start2 = sum_child_h + ch.__center
    1404                  sum_child_h += ch.fullRegion.height()
    1405
    1406              # Knowing the centers of first and last child nodes, we
    1407              # know the center of current node. This center splits the
    1408              # current node space into two unequal pieces: above and
    1409              # bellow.
    1410              above =  (start1 +((start2 - start1)/2.0))
    1411              bellow = sum_child_h - above
    1412              newh = 0
    1413
    1414              # Current node faces will be drawn centered to the node
    1415              # position, so half of the faces space should fit in the
    1416              # above region, and the other half in the bellow region.
    1417              # If not, the height of current node is increased to
    1418              # reserve the required space.
    1419              #
    1420              # The space is missing in the above region, an y offset is
    1421              # set to permit child nodes to be drawn a bit more down
    1422              # than expected.
    1423              if above < (h/2):
    1424                  newh = sum_child_h + ((h/2.0) - above)
    1425                  node._y_correction = ((h/2.0) - above)
    1426              if bellow < h/2:
    1427                  if newh >0:
    1428                      newh +=  ((h/2.0) - bellow)
    1429                  else:
    1430                      newh = sum_child_h + ((h/2) - bellow)
    1431
    1432              # If current node faces do not exceed the sum of child
    1433              # heights, then current node height is the sum of child
    1434              # heights
    1435              if newh == 0:
    1436                  newh = sum_child_h
    1437              h = newh
    1438              # Increases node width the max child width
    1439              w += max_child_w
    1440              # And stores current node center, which is the center
    1441              # calculated using the child node centers + the
    1442              # y_correction (if any)
    1443              node.__center = (start1 +((start2 - start1)/2.0)) + node._y_correction
    1444          else:
    1445              node.__center = h/2
    1446              node._y_correction = 0
    1447
    1448          # This is the node total region covered by the node
    1449          node.fullRegion = QtCore.QRectF(0,0,w,h)
    1450
    1451          # Sets the total room needed for this node
    1452          return node.fullRegion
    1453
    1454 -    def rotate_node(self,node,angle,x=None,y=None):
    1455          if x and y:
    1456              x = node.fullRegion.width()/2
    1457              y = node.fullRegion.height()/2
    1458              node._QtItem_.setTransform(QtGui.QTransform().translate(x, y).rotate(angle).translate(-x, -y));
    1459          else:
    1460              node._QtItem_.rotate(angle)
    1461
    1462 -    def render_node(self,node , x, y,level=0):
    1463          """ Traverse the tree structure and render each node using the
    1464          regions, sizes, and faces previously loaded. """
    1465
    1466          # Node's stuff
    1467          orientation = self.props.orientation
    1468          r = node.img_style["size"]/2
    1469          fh = node.facesRegion.width()
    1470
    1471          node._QtItem_ = _NodeItem(node)
    1472          node._QtItem_.setAcceptsHoverEvents(True)
    1473
    1474          # RIGHT TO LEFT
    1475          if orientation == 1:
    1476              if node == self.startNode:
    1477                  x = self.i_width-x
    1478
    1479          # Add main node QGItem. Each node item has as parent the
    1480          # parent node item
    1481          if node==self.startNode:
    1482              node._QtItem_.setParentItem(self.mainItem)
    1483              scene_pos = node._QtItem_.pos()
    1484              node.scene_pos = scene_pos
    1485
    1486          # node x,y starting positions
    1487          node._x = x
    1488          node._y = y
    1489
    1490          # colour rect as node background
    1491          if  node.img_style["bgcolor"].upper() != "#FFFFFF":
    1492              background = QtGui.QGraphicsRectItem(self.mainItem)
    1493              background.setZValue(-1000+level)
    1494              color = QtGui.QColor(node.img_style["bgcolor"])
    1495              background.setBrush(color)
    1496              background.setPen(color)
    1497              if orientation == 0:
    1498                  background.setRect(node._x,node._y,self.i_width-node._x+self.max_w_aligned_face,node.fullRegion.height())
    1499              elif orientation == 1:
    1500                  background.setRect(node._x-node.fullRegion.width(),node._y,self.i_width,node.fullRegion.height())
    1501          # Draw node and lines
    1502          if not node.is_leaf() and node.img_style["draw_descendants"]==1:
    1503              # Corrections ... say something else, don't you think?
    1504  #            node_height = 0
    1505  #            for ch in node.get_children():
    1506  #                node_height += ch.fullRegion.height()
    1507
    1508  #            if node.fullRegion.height() >= node_height:
    1509  #                y_correction = node.fullRegion.height() - node_height
    1510  #            else:
    1511  #               y_correction = 0
    1512
    1513  #           y_correction = node._y_correction
    1514              # recursivity: call render function for every child
    1515              next_y = y + node._y_correction#/2
    1516              for ch in node.get_children():
    1517                  dist_to_child = ch.dist * self.scale
    1518                  if orientation == 0:
    1519                      next_x = x+node.nodeRegion.width()
    1520                  elif orientation == 1:
    1521                      next_x = x-node.nodeRegion.width()
    1522
    1523                  self.render_node(ch,next_x, next_y,level+1)
    1524                  next_y += ch.fullRegion.height()
    1525
    1526              node._centered_y = ((node.children[0]._centered_y + node.children[-1]._centered_y)/2)
    1527              # Draw an internal node. Take global pos.
    1528
    1529              # Place node at the correct pos in Scene
    1530              ch._QtItem_.setParentItem(node._QtItem_)
    1531              if orientation == 0:
    1532                  node._QtItem_.setPos(x+node.dist_xoffset,node._centered_y-node.img_style["size"]/2)
    1533              elif orientation == 1:
    1534                  node._QtItem_.setPos(x-node.dist_xoffset-node.img_style["size"],node._centered_y-node.img_style["size"]/2)
    1535              for ch in node.children:
    1536                  scene_pos = ch._QtItem_.pos()
    1537                  ch.scene_pos = scene_pos
    1538                  ch._QtItem_.setParentItem(node._QtItem_)
    1539                  ch._QtItem_.setPos(node._QtItem_.mapFromScene(scene_pos) )
    1540
    1541              # Draws the startNode branch when it is not the absolute root
    1542              if node == self.startNode:
    1543                  y = node._QtItem_.pos().y()+ node.img_style["size"]/2
    1544                  self.add_branch(self.mainItem,0,y,node.dist_xoffset,y,node.dist,node.support, node.img_style["hz_line_color"], node.img_style["hlwidth"], node.img_style["line_type"])
    1545
    1546              # RECTANGULAR STYLE
    1547              if self.props.style == 0:
    1548                  vt_line = QtGui.QGraphicsLineItem(node._QtItem_)
    1549                  customPen = QtGui.QPen(QtGui.QBrush(QtGui.QColor(node.img_style["vt_line_color"])), node.img_style["vlwidth"])
    1550                  if node.img_style["line_type"]==1:
    1551                      customPen.setStyle(QtCore.Qt.DashLine)
    1552                  vt_line.setPen(customPen)
    1553
    1554                  ch1_y = node._QtItem_.mapFromScene(0,node.children[0]._centered_y).y()
    1555                  ch2_y = node._QtItem_.mapFromScene(0,node.children[-1]._centered_y).y()
    1556
    1557                  # Draw hz lines of childs
    1558                  for ch in node.children:
    1559                      ch_pos = node._QtItem_.mapFromScene(ch._x, ch._centered_y)
    1560                      if orientation == 0:
    1561                          self.add_branch(node._QtItem_,fh+r*2,ch_pos.y(),fh+r*2+ch.dist_xoffset ,ch_pos.y(),ch.dist, ch.support, ch.img_style["hz_line_color"], ch.img_style["hlwidth"], ch.img_style["line_type"])
    1562                      elif orientation == 1:
    1563                          self.add_branch(node._QtItem_,-fh,ch_pos.y(),-fh-ch.dist_xoffset ,ch_pos.y(),ch.dist,ch.support,ch.img_style["hz_line_color"], ch.img_style["hlwidth"], ch.img_style["line_type"])
    1564                  # Draw vertical line
    1565                  if orientation == 0:
    1566                      vt_line.setLine(fh+r*2,ch1_y,fh+(r*2),ch2_y)
    1567                  elif orientation == 1:
    1568                      vt_line.setLine(-fh,ch1_y,-fh,ch2_y)
    1569
    1570              # DIAGONAL STYLE
    1571              elif self.props.style == 1:
    1572                  # Draw lines from node to childs
    1573                  for ch in node.children:
    1574                      if orientation == 0:
    1575                          ch_x = ch._QtItem_.x()
    1576                          ch_y = ch._QtItem_.y()+ch.img_style["size"]/2
    1577                          self.add_branch(node._QtItem_,fh+node.img_style["size"],r,ch_x,ch_y,ch.dist,ch.support, ch.img_style["hz_line_color"], 1, ch.img_style["line_type"])
    1578                      elif orientation == 1:
    1579                          ch_x = ch._QtItem_.x()
    1580                          ch_y = ch._QtItem_.y()+ch.img_style["size"]/2
    1581                          self.add_branch(node._QtItem_,-fh,r,ch_x+(r*2),ch_y,ch.dist,ch.support, ch.img_style["hz_line_color"], 1, ch.img_style["line_type"])
    1582
    1583              self.add_faces(node,orientation)
    1584
    1585          else:
    1586              # Draw terminal node
    1587              node._centered_y = y+node.fullRegion.height()/2
    1588              if orientation == 0:
    1589                  node._QtItem_.setPos(x+node.dist_xoffset, node._centered_y-r)
    1590              elif orientation == 1:
    1591                  node._QtItem_.setPos(x-node.dist_xoffset-node.img_style["size"], node._centered_y-r)
    1592
    1593              self.add_faces(node,orientation)
    1594
    1595 -    def add_branch(self,parent_item,x1,y1,x2,y2,dist,support,color,width,line_type):
    1596          hz_line = QtGui.QGraphicsLineItem(parent_item)
    1597          customPen  = QtGui.QPen(QtGui.QBrush(QtGui.QColor(color)), width)
    1598          if line_type == 1:
    1599              customPen.setStyle(QtCore.Qt.DashLine)
    1600          hz_line.setPen(customPen)
    1601          hz_line.setLine(x1,y1,x2,y2)
    1602
    1603          if self.props.draw_branch_length:
    1604              distText = "%0.3f" % dist
    1605              if support is not None:
    1606                  distText += "(%0.2f)" % support
    1607              font = QtGui.QFont(self.props.general_font_type,self.props.branch_length_font_size)
    1608              rect = QtGui.QFontMetrics(font).boundingRect(0,0,0,0,QtCore.Qt.AlignTop,distText)
    1609              b_length = QtGui.QGraphicsSimpleTextItem(distText)
    1610              b_length.setFont(font)
    1611              b_length.setBrush(QtGui.QColor(self.props.branch_length_font_color))
    1612              b_length.setParentItem(hz_line)
    1613              b_length.setPos(x1,y1)
    1614              if y1 != y2:
    1615                  offset = 0
    1616                  angle = math.atan((y2-y1)/(x2-x1))*360/ (2*math.pi)
    1617                  if y1>y2:
    1618                      offset = rect.height()
    1619                  b_length.setTransform(QtGui.QTransform().translate(0, y1-offset).rotate(angle).translate(0,-y1));
    1620
    1621 -    def add_faces(self,node,orientation):
    1622          # sphere radius
    1623          r = node.img_style["size"]/2
    1624
    1625          # ...
    1626          if orientation==0:
    1627              aligned_start_x = node._QtItem_.mapFromScene(self.i_width,0).x()
    1628              start_x = node.img_style["size"]
    1629          elif orientation==1:
    1630              start_x = 0
    1631              aligned_start_x = node._QtItem_.mapFromScene(0,0).x()
    1632
    1633          for stack in node.img_style["faces"]:
    1634              # get each stack's height and width
    1635              stack_h         = 0
    1636              stack_w         = 0
    1637              aligned_stack_w = 0
    1638              aligned_stack_h = 0
    1639              # Extract height and width of al faces in this stack
    1640              # Get max width and cumulated height
    1641              for f, aligned, pixmap in stack:
    1642                  if aligned and not node.is_leaf():
    1643                      continue
    1644                  f.node = node
    1645                  if node.is_leaf() and aligned:
    1646                      aligned_stack_w = max(aligned_stack_w , f._width()) #+ f.xmargin*2
    1647                      aligned_stack_h += f._height() #+ f.ymargin*2
    1648                  else:
    1649                      stack_w = max(stack_w ,f._width() )  #+f.xmargin*2
    1650                      stack_h += f._height() #+f.ymargin*2
    1651
    1652              # Creates a GGraphicsItem object for each face
    1653              cumulated_y         = 0
    1654              cumulated_aligned_y = 0
    1655              for j, face in enumerate(stack):
    1656                  f, aligned, pixmap = face
    1657                  if aligned and not node.is_leaf():
    1658                      continue
    1659                  # Sets the face's working node
    1660                  f.node = node
    1661                  # add each face of this stack into the correct position.
    1662                  if node.is_leaf() and aligned:
    1663                      start_y = cumulated_aligned_y + (-aligned_stack_h/2)+r #+ f.ymargin
    1664                  else:
    1665                      start_y = cumulated_y - (stack_h/2) +r #+ f.ymargin
    1666
    1667                  # If face is text type, add it as an QGraphicsTextItem
    1668                  if f.type == "text":
    1669                      obj = _TextItem(f, node, f.get_text())
    1670                      obj.setFont(f.font)
    1671                      obj.setBrush(QtGui.QBrush(f.fgcolor))
    1672                      obj.setParentItem(node._QtItem_)
    1673                      obj.setAcceptsHoverEvents(True)
    1674                      # Marks the y starting point
    1675                      # start_y -= f._height()/2
    1676                      # obj2 = QtGui.QGraphicsEllipseItem(0,0,1,1)
    1677                      # obj2.setBrush(QtGui.QBrush(QtGui.QColor("red")))
    1678                      # obj2.setParentItem(obj)
    1679                  else:
    1680                      # Loads the pre-generated pixmap
    1681                      obj = _FaceItem(f, node, pixmap)
    1682                      obj.setAcceptsHoverEvents(True)
    1683                      obj.setParentItem(node._QtItem_)
    1684
    1685                  # If face is aligned and node is terminal, place face
    1686                  # at aligned margin
    1687                  if node.is_leaf() and aligned:
    1688                      # Set face position
    1689                      if orientation ==0:
    1690                          obj.setPos(aligned_start_x + f.xmargin, start_y)# + f.ymargin)
    1691                      elif orientation ==1:
    1692                          obj.setPos(aligned_start_x-f._width() - f.xmargin , start_y)# + f.ymargin)
    1693                      cumulated_aligned_y += f._height()# + f.ymargin*2
    1694                  # If face has to be draw within the node room
    1695                  else:
    1696                      # Set face position
    1697                      if orientation ==0:
    1698                          obj.setPos(start_x, start_y)
    1699                      elif orientation ==1:
    1700                          obj.setPos(start_x-f._width(),start_y)
    1701                      cumulated_y += f._height()
    1702
    1703              # next stack will start with this x_offset
    1704              if orientation == 0:
    1705                  start_x += stack_w
    1706                  aligned_start_x += aligned_stack_w
    1707              elif orientation ==1:
    1708                  start_x -= stack_w
    1709                  aligned_start_x -= aligned_start_x
    1710