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