1# #START_LICENSE###########################################################
2#
3#
4# This file is part of the Environment for Tree Exploration program
5# (ETE).  http://etetoolkit.org
6#
7# ETE is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# ETE is distributed in the hope that it will be useful, but WITHOUT
13# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
15# License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with ETE.  If not, see <http://www.gnu.org/licenses/>.
19#
20#
21#                     ABOUT THE ETE PACKAGE
22#                     =====================
23#
24# ETE is distributed under the GPL copyleft license (2008-2015).
25#
26# If you make use of ETE in published work, please cite:
27#
28# Jaime Huerta-Cepas, Joaquin Dopazo and Toni Gabaldon.
29# ETE: a python Environment for Tree Exploration. Jaime BMC
30# Bioinformatics 2010,:24doi:10.1186/1471-2105-11-24
31#
32# Note that extra references to the specific methods implemented in
33# the toolkit may be available in the documentation.
34#
35# More info at http://etetoolkit.org. Contact: huerta@embl.de
36#
37#
38# #END_LICENSE#############################################################
39import math
40import re
41import six
42
43from .qt import *
44from . import qt4_circular_render as crender
45from . import qt4_rect_render as rrender
46from .main import _leaf, NodeStyle, _FaceAreas, tracktime, TreeStyle
47from .node_gui_actions import _NodeActions as _ActionDelegator
48from .qt4_face_render import update_node_faces, _FaceGroupItem, _TextFaceItem
49from .templates import _DEFAULT_STYLE, apply_template
50from . import faces
51
52
53## | General scheme of node content
54## |==========================================================================================================================|
55## |                                                fullRegion                                                                |
56## |             nodeRegion                  |================================================================================|
57## |                                         |                                fullRegion                                     ||
58## |                                         |        nodeRegion                     |=======================================||
59## |                                         |                                       |         fullRegion                   |||
60## |                                         |                                       |         nodeRegion                   |||
61## |                                         |                         |             |branch_length | nodeSize | facesRegion|||
62## |                                         | branch_length | nodesize|faces-right  |=======================================||
63## |                                         |                         |(facesRegion)|=======================================||
64## |                                         |                                       |             fullRegion                ||
65## |                                         |                                       |             nodeRegion                ||
66## |  faces-top     |          |             |                                       | branch_length | nodeSize | facesRegion||
67## | branch_length  | NodeSize |faces-right  |                                       |=======================================||
68## |  faces-bottom  |          |(facesRegion)|================================================================================|
69## |                                         |=======================================|                                        |
70## |                                         |             fullRegion                |                                        |
71## |                                         |        nodeRegion                     |                                        |
72## |                                         | branch_length | nodeSize | facesRegion|                                        |
73## |                                         |=======================================|                                        |
74## |==========================================================================================================================|
75
76class _CircleItem(QGraphicsEllipseItem, _ActionDelegator):
77    def __init__(self, node):
78        self.node = node
79        d = node.img_style["size"]
80        QGraphicsEllipseItem.__init__(self, 0, 0, d, d)
81        _ActionDelegator.__init__(self)
82
83        self.setBrush(QBrush(QColor(self.node.img_style["fgcolor"])))
84        self.setPen(QPen(QColor(self.node.img_style["fgcolor"])))
85
86class _RectItem(QGraphicsRectItem, _ActionDelegator):
87    def __init__(self, node):
88        self.node = node
89        d = node.img_style["size"]
90        QGraphicsRectItem.__init__(self, 0, 0, d, d)
91        _ActionDelegator.__init__(self)
92        self.setBrush(QBrush(QColor(self.node.img_style["fgcolor"])))
93        self.setPen(QPen(QColor(self.node.img_style["fgcolor"])))
94
95class _SphereItem(QGraphicsEllipseItem, _ActionDelegator):
96    def __init__(self, node):
97        self.node = node
98        d = node.img_style["size"]
99        r = d/2.0
100        QGraphicsEllipseItem.__init__(self, 0, 0, d, d)
101        _ActionDelegator.__init__(self)
102        #self.setBrush(QBrush(QColor(self.node.img_style["fgcolor"])))
103        self.setPen(QPen(QColor(self.node.img_style["fgcolor"])))
104        gradient = QRadialGradient(r, r, r,(d)/3,(d)/3)
105        gradient.setColorAt(0.05, Qt.white);
106        gradient.setColorAt(1, QColor(self.node.img_style["fgcolor"]));
107        self.setBrush(QBrush(gradient))
108        # self.setPen(Qt.NoPen)
109
110class _EmptyItem(QGraphicsItem):
111    def __init__(self, parent=None):
112        QGraphicsItem.__init__(self)
113        self.setParentItem(parent)
114
115        # qt4.6+ Only
116        try:
117            self.setFlags(QGraphicsItem.ItemHasNoContents)
118        except:
119            pass
120
121    def boundingRect(self):
122        return QRectF(0,0,0,0)
123
124    def paint(self, *args, **kargs):
125        return
126
127class _TreeItem(QGraphicsRectItem):
128    def __init__(self, parent=None):
129        QGraphicsRectItem.__init__(self)
130        self.setParentItem(parent)
131        self.n2i = {}
132        self.n2f = {}
133
134class _NodeItem(_EmptyItem):
135    def __init__(self, node, parent):
136        _EmptyItem.__init__(self, parent)
137        self.node = node
138        self.nodeRegion = QRectF()
139        self.facesRegion = QRectF()
140        self.fullRegion = QRectF()
141        self.highlighted = False
142
143class _NodeLineItem(QGraphicsLineItem, _ActionDelegator):
144    def __init__(self, node, *args, **kargs):
145        self.node = node
146        QGraphicsLineItem.__init__(self, *args, **kargs)
147        _ActionDelegator.__init__(self)
148    def paint(self, painter, option, widget):
149        QGraphicsLineItem.paint(self, painter, option, widget)
150
151class _LineItem(QGraphicsLineItem):
152    def paint(self, painter, option, widget):
153        QGraphicsLineItem.paint(self, painter, option, widget)
154
155class _PointerItem(QGraphicsRectItem):
156    def __init__(self, parent=None):
157        QGraphicsRectItem.__init__(self,0,0,0,0, parent)
158        self.color = QColor("blue")
159        self._active = False
160        self.setBrush(QBrush(Qt.NoBrush))
161
162    def paint(self, p, option, widget):
163        p.setPen(self.color)
164        p.drawRect(self.rect())
165        return
166        # Draw info text
167        font = QFont("Arial",13)
168        text = "%d selected."  % len(self.get_selected_nodes())
169        textR = QFontMetrics(font).boundingRect(text)
170        if  self.rect().width() > textR.width() and \
171                self.rect().height() > textR.height()/2.0 and 0: # OJO !!!!
172            p.setPen(QPen(self.color))
173            p.setFont(QFont("Arial",13))
174            p.drawText(self.rect().bottomLeft().x(),self.rect().bottomLeft().y(),text)
175
176    def get_selected_nodes(self):
177        selPath = QPainterPath()
178        selPath.addRect(self.rect())
179        self.scene().setSelectionArea(selPath)
180        return [i.node for i in self.scene().selectedItems()]
181
182    def setActive(self,bool):
183        self._active = bool
184
185    def isActive(self):
186        return self._active
187
188class _TreeScene(QGraphicsScene):
189    def __init__(self):
190        QGraphicsScene.__init__(self)
191        self.view = None
192
193    def init_values(self, tree, img, n2i, n2f):
194        self.master_item = _EmptyItem()
195        self.tree = tree
196        self.n2i = n2i
197        self.n2f = n2f
198        self.img = img
199
200    def draw(self):
201        self.img._scale = None
202        tree_item, self.n2i, self.n2f = render(self.tree, self.img)
203        if self.master_item:
204            self.removeItem(self.master_item)
205        tree_item, n2i, n2f = render(self.tree, self.img)
206        self.init_values(self.tree, self.img, n2i, n2f)
207        self.addItem(self.master_item)
208        tree_item.setParentItem(self.master_item)
209        self.setSceneRect(tree_item.rect())
210
211#@tracktime
212def render(root_node, img, hide_root=False):
213    '''main render function. hide_root option is used when render
214    trees as Faces
215
216    '''
217    mode = img.mode
218    orientation = img.orientation
219
220    arc_span = img.arc_span
221
222    layout_fn = img._layout_handler
223
224    parent = _TreeItem()
225    n2i = parent.n2i # node to items
226    n2f = parent.n2f # node to faces
227
228    parent.bg_layer =  _EmptyItem(parent)
229    parent.tree_layer = _EmptyItem(parent)
230    parent.float_layer = _EmptyItem(parent)
231    parent.float_behind_layer = _EmptyItem(parent)
232
233    TREE_LAYERS = [parent.bg_layer, parent.tree_layer,
234                   parent.float_layer, parent.float_behind_layer]
235
236    parent.bg_layer.setZValue(0)
237    parent.tree_layer.setZValue(2)
238
239    parent.float_behind_layer.setZValue(1)
240    parent.float_layer.setZValue(3)
241
242    # This could be used to handle aligned faces in internal
243    # nodes.
244    virtual_leaves = 0
245
246    if img.show_branch_length:
247        bl_face = faces.AttrFace("dist", fsize=8, ftype="Arial", fgcolor="black", formatter = "%0.3g")
248    if img.show_branch_support:
249        su_face = faces.AttrFace("support", fsize=8, ftype="Arial", fgcolor="darkred", formatter = "%0.3g")
250    if img.show_leaf_name:
251        na_face = faces.AttrFace("name", fsize=10, ftype="Arial", fgcolor="black")
252
253    for n in root_node.traverse(is_leaf_fn=_leaf):
254        set_style(n, layout_fn)
255
256        if img.show_branch_length:
257            faces.add_face_to_node(bl_face, n, 0, position="branch-top")
258
259        if not _leaf(n) and img.show_branch_support:
260            faces.add_face_to_node(su_face, n, 0, position="branch-bottom")
261
262        if _leaf(n) and n.name and img.show_leaf_name:
263            faces.add_face_to_node(na_face, n, 0, position="branch-right")
264
265        if _leaf(n):# or len(n.img_style["_faces"]["aligned"]):
266            virtual_leaves += 1
267
268        update_node_faces(n, n2f, img)
269
270    rot_step = float(arc_span) / virtual_leaves
271    #rot_step = float(arc_span) / len([n for n in root_node.traverse() if _leaf(n)])
272
273    # Calculate optimal branch length
274    if img._scale is not None:
275        init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root)
276    elif img.scale is None:
277        # create items and calculate node dimensions skipping branch lengths
278        init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root)
279        if mode == 'r':
280            if img.optimal_scale_level == "full":
281                scales = [(i.widths[1]/n.dist) for n,i in six.iteritems(n2i) if n.dist]
282                img._scale = max(scales) if scales else 0.0
283            else:
284                farthest, dist = root_node.get_farthest_leaf(topology_only=img.force_topology)
285                img._scale = img.tree_width / dist if dist else 0.0
286            update_branch_lengths(root_node, n2i, n2f, img)
287        else:
288            img._scale = crender.calculate_optimal_scale(root_node, n2i, rot_step, img)
289            #print "OPTIMAL circular scale", img._scale
290            update_branch_lengths(root_node, n2i, n2f, img)
291            init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root)
292    else:
293        # create items and calculate node dimensions CONSIDERING branch lengths
294        img._scale = img.scale
295        init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root)
296
297    #print "USING scale", img._scale
298    # Draw node content
299    for node in root_node.traverse(is_leaf_fn=_leaf):
300        if node is not root_node or not hide_root:
301            render_node_content(node, n2i, n2f, img)
302
303    # Adjust content to rect or circular layout
304    mainRect = parent.rect()
305
306    if mode == "c":
307        tree_radius = crender.render_circular(root_node, n2i, rot_step)
308        mainRect.adjust(-tree_radius, -tree_radius, tree_radius, tree_radius)
309    else:
310        iwidth = n2i[root_node].fullRegion.width()
311        iheight = n2i[root_node].fullRegion.height()
312        mainRect.adjust(0, 0, iwidth, iheight)
313        tree_radius = iwidth
314
315    # Add extra layers: aligned faces, floating faces, node
316    # backgrounds, etc. The order by which the following methods are
317    # called IS IMPORTANT
318    render_floatings(n2i, n2f, img, parent.float_layer, parent.float_behind_layer)
319
320    aligned_region_width = render_aligned_faces(img, mainRect, parent.tree_layer, n2i, n2f)
321
322    render_backgrounds(img, mainRect, parent.bg_layer, n2i, n2f)
323
324    # rotate if necessary in circular images. flip and adjust if mirror orientation.
325    adjust_faces_to_tranformations(img, mainRect, n2i, n2f, TREE_LAYERS)
326
327    # Rotate main image if necessary
328    parent.setRect(mainRect)
329    parent.setPen(QPen(Qt.NoPen))
330
331    if img.rotation:
332        rect = parent.boundingRect()
333        x =  rect.x() + (rect.width()/2.0)
334        y =  rect.y() +  (rect.height()/2.0)
335        parent.setTransform(QTransform().translate(x, y).rotate(img.rotation).translate(-x, -y))
336
337    # Creates the main tree item that will act as frame for the whole image
338    frame = QGraphicsRectItem()
339    parent.setParentItem(frame)
340    mainRect = parent.mapToScene(mainRect).boundingRect()
341
342    mainRect.adjust(-img.margin_left, -img.margin_top, \
343                         img.margin_right, img.margin_bottom)
344
345    # Fix negative coordinates, so main item always starts at 0,0
346    topleft  = mainRect.topLeft()
347    _x = abs(topleft.x()) if topleft.x() < 0 else 0
348    _y = abs(topleft.y()) if topleft.y() < 0 else 0
349    if _x or _y:
350        parent.moveBy(_x, _y)
351        mainRect.adjust(_x, _y, _x, _y)
352
353    # Add extra components and adjust mainRect to them
354    add_legend(img, mainRect, frame)
355    add_title(img, mainRect, frame)
356    add_scale(img, mainRect, frame)
357    frame.setRect(mainRect)
358
359    # Draws a border around the tree
360    if not img.show_border:
361        frame.setPen(QPen(Qt.NoPen))
362    else:
363        frame.setPen(QPen(QColor("black")))
364
365    return frame, n2i, n2f
366
367def adjust_faces_to_tranformations(img, mainRect, n2i, n2f, tree_layers):
368    if img.mode == "c":
369        rotate_inverted_faces(n2i, n2f, img)
370    elif img.mode == "r" and img.orientation == 1:
371        for layer in tree_layers:
372            layer.setTransform(QTransform().translate(0, 0).scale(-1,1).translate(0, 0))
373            layer.moveBy(mainRect.width(),0)
374        for faceblock in six.itervalues(n2f):
375            for pos, fb in six.iteritems(faceblock):
376                fb.flip_hz()
377
378def add_legend(img, mainRect, parent):
379    if img.legend:
380        legend = _FaceGroupItem(img.legend, None)
381        legend.setup_grid()
382        legend.render()
383        lg_w, lg_h = legend.get_size()
384        dw = max(0, lg_w-mainRect.width())
385        legend.setParentItem(parent)
386        if img.legend_position == 1:
387            mainRect.adjust(0, -lg_h, dw, 0)
388            legend.setPos(mainRect.topLeft())
389        elif img.legend_position == 2:
390            mainRect.adjust(0, -lg_h, dw, 0)
391            pos = mainRect.topRight()
392            legend.setPos(pos.x()-lg_w, pos.y())
393        elif img.legend_position == 3:
394            legend.setPos(mainRect.bottomLeft())
395            mainRect.adjust(0, 0, dw, lg_h)
396        elif img.legend_position == 4:
397            pos = mainRect.bottomRight()
398            legend.setPos(pos.x()-lg_w, pos.y())
399            mainRect.adjust(0, 0, dw, lg_h)
400
401def add_title(img, mainRect, parent):
402    if img.title:
403        title = _FaceGroupItem(img.title, None)
404        title.setup_grid()
405        title.render()
406        lg_w, lg_h = title.get_size()
407        dw = max(0, lg_w-mainRect.width())
408        title.setParentItem(parent)
409        mainRect.adjust(0, -lg_h, dw, 0)
410        title.setPos(mainRect.topLeft())
411
412def add_legend(img, mainRect, parent):
413    if img.legend:
414        legend = _FaceGroupItem(img.legend, None)
415        legend.setup_grid()
416        legend.render()
417        lg_w, lg_h = legend.get_size()
418        dw = max(0, lg_w-mainRect.width())
419        legend.setParentItem(parent)
420        if img.legend_position == 1:
421            mainRect.adjust(0, -lg_h, dw, 0)
422            legend.setPos(mainRect.topLeft())
423        elif img.legend_position == 2:
424            mainRect.adjust(0, -lg_h, dw, 0)
425            pos = mainRect.topRight()
426            legend.setPos(pos.x()-lg_w, pos.y())
427        elif img.legend_position == 3:
428            legend.setPos(mainRect.bottomLeft())
429            mainRect.adjust(0, 0, dw, lg_h)
430        elif img.legend_position == 4:
431            pos = mainRect.bottomRight()
432            legend.setPos(pos.x()-lg_w, pos.y())
433            mainRect.adjust(0, 0, dw, lg_h)
434
435def add_scale(img, mainRect, parent):
436    if img.show_scale:
437        if img.scale_length is None:
438            length = 50.0
439        else:
440            length = img.scale_length * img._scale
441            if length > mainRect.width():
442                max_value = mainRect.width() / img._scale
443                raise ValueError('Custom scale bar length (TreeStyle.scale_length) is larger than the tree image'
444                                 'Use values between 0 and %g' %max_value)
445
446        scaleItem = _EmptyItem()
447        customPen = QPen(QColor("black"), 1)
448
449        if img.force_topology:
450            wtext = "Force topology is enabled!\nBranch lengths do not represent real values."
451            warning_text = QGraphicsSimpleTextItem(wtext)
452            warning_text.setFont(QFont("Arial", 8))
453            warning_text.setBrush( QBrush(QColor("darkred")))
454            warning_text.setPos(0, 32)
455            warning_text.setParentItem(scaleItem)
456        else:
457            line = QGraphicsLineItem(scaleItem)
458            line2 = QGraphicsLineItem(scaleItem)
459            line3 = QGraphicsLineItem(scaleItem)
460            line.setPen(customPen)
461            line2.setPen(customPen)
462            line3.setPen(customPen)
463
464            line.setLine(0, 5, length, 5)
465            line2.setLine(0, 0, 0, 10)
466            line3.setLine(length, 0, length, 10)
467            length_text = float(length) / img._scale if img._scale else 0.0
468            scale_text = "%g" %(length_text)
469            scale = QGraphicsSimpleTextItem(scale_text)
470            scale.setParentItem(scaleItem)
471            scale.setPos(0, 10)
472
473        scaleItem.setParentItem(parent)
474        dw = max(0, length - mainRect.width())
475        scaleItem.setPos(mainRect.bottomLeft())
476        scaleItem.moveBy(img.margin_left, 0)
477        mainRect.adjust(0, 0, 0, length)
478
479def rotate_inverted_faces(n2i, n2f, img):
480    for node, faceblock in six.iteritems(n2f):
481        item = n2i[node]
482        if item.rotation > 90 and item.rotation < 270:
483            for pos, fb in six.iteritems(faceblock):
484                fb.rotate(181)
485
486def render_backgrounds(img, mainRect, bg_layer, n2i, n2f):
487
488    if img.mode == "c":
489        max_r = mainRect.width()/2.0
490    else:
491        max_r = mainRect.width()
492
493    for node, item in six.iteritems(n2i):
494        if _leaf(node):
495            first_c = n2i[node]
496            last_c = n2i[node]
497        else:
498            first_c = n2i[node.children[0]]
499            last_c = n2i[node.children[-1]]
500
501        if img.mode == "c":
502            h = item.effective_height
503            angle_start = first_c.full_start
504            angle_end = last_c.full_end
505            parent_radius = getattr(n2i.get(node.up, None), "radius", 0)
506            base = parent_radius + item.nodeRegion.width()
507
508            # if node.img_style["node_bgcolor"].upper() != "#FFFFFF":
509            #     bg1 = crender._ArcItem()
510            #     r = math.sqrt(base**2 + h**2)
511            #     bg1.set_arc(0, 0, parent_radius, r, angle_start, angle_end)
512            #     bg1.setParentItem(item.content.bg)
513            #     bg1.setPen(QPen(QColor(node.img_style["node_bgcolor"])))
514            #     bg1.setBrush(QBrush(QColor(node.img_style["node_bgcolor"])))
515
516            # if node.img_style["faces_bgcolor"].upper() != "#FFFFFF":
517            #     bg2 = crender._ArcItem()
518            #     r = math.sqrt(base**2 + h**2)
519            #     bg2.set_arc(0, 0, parent_radius, item.radius, angle_start, angle_end)
520            #     bg2.setParentItem(item.content)
521            #     bg2.setPen(QPen(QColor(node.img_style["faces_bgcolor"])))
522            #     bg2.setBrush(QBrush(QColor(node.img_style["faces_bgcolor"])))
523
524            if node.img_style["bgcolor"].upper() != "#FFFFFF":
525                bg = crender._ArcItem()
526                bg.set_arc(0, 0, parent_radius, max_r, angle_start, angle_end)
527                bg.setPen(QPen(QColor(node.img_style["bgcolor"])))
528                bg.setBrush(QBrush(QColor(node.img_style["bgcolor"])))
529                bg.setParentItem(bg_layer)
530                bg.setZValue(item.zValue())
531
532        if img.mode == "r":
533            if node.img_style["bgcolor"].upper() != "#FFFFFF":
534                bg = QGraphicsRectItem()
535                pos = item.content.mapToScene(0, 0)
536                bg.setPos(pos.x(), pos.y())
537                bg.setRect(0, 0, max_r-pos.x(),  item.fullRegion.height())
538                bg.setPen(QPen(QColor(node.img_style["bgcolor"])))
539                bg.setBrush(QBrush(QColor(node.img_style["bgcolor"])))
540                bg.setParentItem(bg_layer)
541                bg.setZValue(item.zValue())
542
543def set_node_size(node, n2i, n2f, img):
544    scale = img._scale
545    min_separation = img.min_leaf_separation
546
547    item = n2i[node]
548    if img.force_topology:
549        branch_length = item.branch_length = 25
550    else:
551        branch_length = item.branch_length = float(node.dist * scale)
552
553    # Organize faces by groups
554    #faceblock = update_node_faces(node, n2f, img)
555    faceblock = n2f[node]
556    aligned_height = 0
557    if _leaf(node):
558        if img.mode == "r":
559            aligned_height = faceblock["aligned"].h
560        elif img.mode == "c":
561            # aligned faces in circular mode are adjusted afterwords. The
562            # min radius of the largest aligned faces will be calculated.
563            pass
564
565    # Total height required by the node. I cannot sum up the height of
566    # all elements, since the position of some of them are forced to
567    # be on top or at the bottom of branches. This fact can produce
568    # and unbalanced nodeRegion center. Here, I only need to know
569    # about the imbalance size to correct node height. The center will
570    # be calculated later according to the parent position.
571    top_half_h = ( (node.img_style["size"]/2.0) +
572                       node.img_style["hz_line_width"]/2.0 +
573                       faceblock["branch-top"].h )
574
575    bottom_half_h =( (node.img_style["size"]/2.0) +
576                       node.img_style["hz_line_width"]/2.0 +
577                       faceblock["branch-bottom"].h )
578
579    h1 = top_half_h + bottom_half_h
580    h2 = max(faceblock["branch-right"].h, \
581                 aligned_height, \
582                min_separation )
583    h = max(h1, h2)
584    imbalance = abs(top_half_h - bottom_half_h)
585    if imbalance > h2/2.0:
586        h += imbalance - (h2/2.0)
587
588    # This adds a vertical margin around the node elements
589    h += img.branch_vertical_margin
590
591    # Total width required by the node
592    w = sum([max(branch_length + node.img_style["size"],
593                 faceblock["branch-top"].w + node.img_style["size"],
594                 faceblock["branch-bottom"].w + node.img_style["size"],
595                 ),
596             faceblock["branch-right"].w]
597            )
598
599    # This breaks ultrametric tree visualization
600    #w += node.img_style["vt_line_width"]
601
602    # rightside faces region
603    item.facesRegion.setRect(0, 0, faceblock["branch-right"].w, h)
604
605    # Node region
606    item.nodeRegion.setRect(0, 0, w, h)
607
608    # This is the node total region covered by the node
609    item.fullRegion.setRect(0, 0, w, h)
610
611def render_node_content(node, n2i, n2f, img):
612    style = node.img_style
613    item = n2i[node]
614    item.content = _EmptyItem(item)
615
616    nodeR = item.nodeRegion
617    facesR = item.facesRegion
618    center = item.center
619    branch_length = item.branch_length
620
621    # Node points
622    ball_size = style["size"]
623
624
625    vlw = style["vt_line_width"] if not _leaf(node) and len(node.children) > 1 else 0.0
626
627    # face_start_x = nodeR.width() - facesR.width() - vlw
628    face_start_x = max(0, nodeR.width() - facesR.width() - vlw)
629    ball_start_x = face_start_x - ball_size
630
631    if ball_size:
632        if node.img_style["shape"] == "sphere":
633            node_ball = _SphereItem(node)
634        elif node.img_style["shape"] == "circle":
635            node_ball = _CircleItem(node)
636        elif node.img_style["shape"] == "square":
637            node_ball = _RectItem(node)
638
639        node_ball.setPos(ball_start_x, center-(ball_size/2.0))
640
641        #from qt4_gui import _BasicNodeActions
642        #node_ball.delegate = _BasicNodeActions()
643        #node_ball.setAcceptHoverEvents(True)
644        #node_ball.setCursor(Qt.PointingHandCursor)
645
646    else:
647        node_ball = None
648
649    # Branch line to parent
650    pen = QPen()
651    set_pen_style(pen, style["hz_line_type"])
652    pen.setColor(QColor(style["hz_line_color"]))
653    pen.setWidth(style["hz_line_width"])
654    pen.setCapStyle(Qt.FlatCap)
655    #pen.setCapStyle(Qt.RoundCap)
656    #pen.setCapStyle(Qt.SquareCap)
657    #pen.setJoinStyle(Qt.RoundJoin)
658    hz_line = _LineItem()
659    hz_line = _NodeLineItem(node)
660    hz_line.setPen(pen)
661
662    join_fix = 0
663    if img.mode == "c" and node.up and node.up.img_style["vt_line_width"]:
664        join_fix = node.up.img_style["vt_line_width"]
665        # fix_join_line = _LineItem()
666        # fix_join_line = _NodeLineItem(node)
667        # parent_style = node.up.img_style
668        # pen = QPen()
669        # pen.setColor(QColor(parent_style["vt_line_color"]))
670        # pen.setWidth(parent_style["hz_line_width"])
671        # pen.setCapStyle(Qt.FlatCap)
672        # fix_join_line.setPen(pen)
673        # fix_join_line.setLine(-join_fix, center, join_fix, center)
674        # fix_join_line.setParentItem(item.content)
675
676    hz_line.setLine(-join_fix, center, branch_length, center)
677
678    if img.complete_branch_lines_when_necessary:
679        extra_line = _LineItem(branch_length, center, ball_start_x, center)
680        pen = QPen()
681        item.extra_branch_line = extra_line
682        set_pen_style(pen, img.extra_branch_line_type)
683        pen.setColor(QColor(img.extra_branch_line_color))
684        pen.setCapStyle(Qt.FlatCap)
685        pen.setWidth(style["hz_line_width"])
686        extra_line.setPen(pen)
687    else:
688        extra_line = None
689
690    # Attach branch-right faces to child
691    fblock_r = n2f[node]["branch-right"]
692    fblock_r.render()
693    fblock_r.setPos(face_start_x, center-fblock_r.h/2.0)
694
695    # Attach branch-bottom faces to child
696    fblock_b = n2f[node]["branch-bottom"]
697    fblock_b.render()
698    fblock_b.setPos(item.widths[0], center + style["hz_line_width"]/2.0)
699
700    # Attach branch-top faces to child
701    fblock_t = n2f[node]["branch-top"]
702    fblock_t.render()
703    fblock_t.setPos(item.widths[0], center - fblock_t.h - style["hz_line_width"]/2.0)
704
705    # Vertical line
706    if not _leaf(node):
707        if img.mode == "c":
708            vt_line = QGraphicsPathItem()
709
710        elif img.mode == "r":
711            vt_line = _LineItem(item)
712            first_child = node.children[0]
713            last_child = node.children[-1]
714            first_child_part = n2i[node.children[0]]
715            last_child_part = n2i[node.children[-1]]
716            c1 = first_child_part.start_y + first_child_part.center
717            c2 = last_child_part.start_y + last_child_part.center
718            fx = nodeR.width() - (vlw/2.0)
719            if first_child.img_style["hz_line_width"] > 0:
720                c1 -= (first_child.img_style["hz_line_width"] / 2.0)
721            if last_child.img_style["hz_line_width"] > 0:
722                c2 += (last_child.img_style["hz_line_width"] / 2.0)
723            vt_line.setLine(fx, c1, fx, c2)
724
725        pen = QPen()
726        set_pen_style(pen, style["vt_line_type"])
727        pen.setColor(QColor(style["vt_line_color"]))
728        pen.setWidth(style["vt_line_width"])
729        pen.setCapStyle(Qt.FlatCap)
730        #pen.setCapStyle(Qt.RoundCap)
731        #pen.setCapStyle(Qt.SquareCap)
732        vt_line.setPen(pen)
733        item.vt_line = vt_line
734    else:
735        vt_line = None
736
737    item.bg = QGraphicsItemGroup()
738    item.movable_items = [] #QGraphicsItemGroup()
739    item.static_items = [] #QGraphicsItemGroup()
740
741    # Items fow which coordinates are exported in the image map
742    item.mapped_items = [node_ball, fblock_r, fblock_b, fblock_t]
743
744
745    for i in [vt_line, extra_line, hz_line]:
746        if i:
747            #item.static_items.addToGroup(i)
748            item.static_items.append(i)
749            i.setParentItem(item.content)
750    for i in [node_ball, fblock_r, fblock_b, fblock_t]:
751        if i:
752            #item.movable_items.addToGroup(i)
753            item.movable_items.append(i)
754            i.setParentItem(item.content)
755
756
757    #item.movable_items.setParentItem(item.content)
758    #item.static_items.setParentItem(item.content)
759
760def set_pen_style(pen, line_style):
761    if line_style == 0:
762        pen.setStyle(Qt.SolidLine)
763    elif line_style == 1:
764        pen.setStyle(Qt.DashLine)
765    elif line_style == 2:
766        pen.setStyle(Qt.DotLine)
767
768def set_style(n, layout_func):
769    #if not isinstance(getattr(n, "img_style", None), NodeStyle):
770    #    print "Style of", n.name ,"is None"
771    #    n.set_style()
772    #    n.img_style = NodeStyle()
773
774    n._temp_faces = _FaceAreas()
775
776    for func in layout_func:
777        func(n)
778
779def render_floatings(n2i, n2f, img, float_layer, float_behind_layer):
780    #floating_faces = [ [node, fb["float"]] for node, fb in n2f.iteritems() if "float" in fb]
781
782    for node, faces in six.iteritems(n2f):
783        face_set = [ [float_layer, faces.get("float", None)],
784                     [float_behind_layer, faces.get("float-behind",None)]]
785
786        for parent_layer,fb in face_set:
787            if not fb:
788                continue
789
790            item = n2i[node]
791            fb.setParentItem(parent_layer)
792
793            try:
794                xtra =  item.extra_branch_line.line().dx()
795            except AttributeError:
796                xtra = 0
797
798            if img.mode == "c":
799                # Floatings are positioned over branches
800                crender.rotate_and_displace(fb, item.rotation, fb.h, item.radius - item.nodeRegion.width() + xtra)
801                # Floatings are positioned starting from the node circle
802                #crender.rotate_and_displace(fb, item.rotation, fb.h, item.radius - item.nodeRegion.width())
803
804            elif img.mode == "r":
805                start = item.branch_length + xtra - fb.w #if fb.w < item.branch_length else 0.0
806                fb.setPos(item.content.mapToScene(start, item.center - (fb.h/2.0)))
807
808            z = item.zValue()
809            if not img.children_faces_on_top:
810                z = -z
811
812            fb.setZValue(z)
813            fb.update_columns_size()
814            fb.render()
815
816def render_aligned_faces(img, mainRect, parent, n2i, n2f):
817    # Prepares and renders aligned face headers. Used to later
818    # place aligned faces
819    aligned_faces = [ [node, fb["aligned"]] for node, fb in six.iteritems(n2f)\
820                          if fb["aligned"].column2faces and _leaf(node)]
821
822    # If no aligned faces, just return an offset of 0 pixels
823    if not aligned_faces:
824        return 0
825
826    # Load header and footer
827    if img.mode == "r":
828        tree_end_x = mainRect.width()
829
830        fb_head = _FaceGroupItem(img.aligned_header, None)
831        fb_head.setParentItem(parent)
832        fb_foot = _FaceGroupItem(img.aligned_foot, None)
833        fb_foot.setParentItem(parent)
834        surroundings = [[None,fb_foot], [None, fb_head]]
835        mainRect.adjust(0, -fb_head.h, 0, fb_foot.h)
836    else:
837        tree_end_x = mainRect.width()/2.0
838        surroundings = []
839
840    # Place aligned faces and calculates the max size of each
841    # column (needed to place column headers)
842    c2max_w = {}
843    maxh = 0
844    maxh_node = None
845    for node, fb in aligned_faces + surroundings:
846        if fb.h > maxh:
847            maxh = fb.h
848            maxh_node = node
849        for c, w in six.iteritems(fb.c2max_w):
850            c2max_w[c] = max(w, c2max_w.get(c,0))
851    extra_width = sum(c2max_w.values())
852
853    # If rect mode, render header and footer
854    if img.mode == "r":
855        if img.draw_aligned_faces_as_table:
856            fb_head.setup_grid(c2max_w)
857            fb_foot.setup_grid(c2max_w)
858
859        fb_head.render()
860        fb_head.setPos(tree_end_x, mainRect.top())
861        fb_foot.render()
862        fb_foot.setPos(tree_end_x, mainRect.bottom()-fb_foot.h)
863        if img.orientation == 1:
864            fb_head.flip_hz()
865            fb_foot.flip_hz()
866
867    # if no scale provided in circular mode, optimal scale is expected
868    # to provide the correct ending point to start drawing aligned
869    # faces.
870    elif img.mode == "c" and (img.scale or img._scale == 0) and not img.allow_face_overlap:
871        angle = n2i[maxh_node].angle_span
872        rad, off = crender.get_min_radius(1, maxh, angle, tree_end_x)
873        extra_width += rad - tree_end_x
874        tree_end_x = rad
875
876    # Place aligned faces
877    for node, fb in aligned_faces:
878        item = n2i[node]
879        item.mapped_items.append(fb)
880        if img.draw_aligned_faces_as_table:
881            if img.aligned_table_style == 0:
882                fb.setup_grid(c2max_w, as_grid=True)
883            elif img.aligned_table_style == 1:
884                fb.setup_grid(c2max_w, as_grid=False)
885
886        fb.render()
887        fb.setParentItem(item.content)
888        if img.mode == "c":
889            if node.up in n2i:
890                x = tree_end_x - n2i[node.up].radius
891            else:
892                x = tree_end_x
893            #fb.moveBy(tree_end_x, 0)
894        elif img.mode == "r":
895            x = item.mapFromScene(tree_end_x, 0).x()
896
897        fb.setPos(x, item.center-(fb.h/2.0))
898
899        if img.draw_guiding_lines and _leaf(node):
900            # -1 is to connect the two lines, otherwise there is a pixel in between
901            guide_line = _LineItem(item.nodeRegion.width()-1, item.center, x, item.center)
902            pen = QPen()
903            set_pen_style(pen, img.guiding_lines_type)
904            pen.setColor(QColor(img.guiding_lines_color))
905            pen.setCapStyle(Qt.FlatCap)
906            pen.setWidth(node.img_style["hz_line_width"])
907            guide_line.setPen(pen)
908            guide_line.setParentItem(item.content)
909
910    if img.mode == "c":
911        mainRect.adjust(-extra_width, -extra_width, extra_width, extra_width)
912    else:
913        mainRect.adjust(0, 0, extra_width, 0)
914    return extra_width
915
916def get_tree_img_map(n2i, x_scale=1, y_scale=1):
917    MOTIF_ITEMS = set([faces.QGraphicsTriangleItem,
918                       faces.QGraphicsEllipseItem,
919                       faces.QGraphicsDiamondItem,
920                       faces.QGraphicsRectItem,
921                       faces.QGraphicsRoundRectItem])
922    node_list = []
923    face_list = []
924    node_areas = {}
925    #nid = 0
926    for n, main_item in six.iteritems(n2i):
927        #n.add_feature("_nid", str(nid))
928        nid = n._nid
929
930        rect = main_item.mapToScene(main_item.fullRegion).boundingRect()
931        x1 = x_scale * rect.x()
932        y1 = y_scale * rect.y()
933        x2 = x_scale * (rect.x() + rect.width())
934        y2 = y_scale * (rect.y() + rect.height())
935        node_areas[nid] = [x1, y1, x2, y2]
936
937        for item in main_item.mapped_items:
938            if isinstance(item, _CircleItem) \
939                    or isinstance(item, _SphereItem) \
940                    or isinstance(item, _RectItem):
941                r = item.boundingRect()
942                rect = item.mapToScene(r).boundingRect()
943                x1 = x_scale * rect.x()
944                y1 = y_scale * rect.y()
945                x2 = x_scale * (rect.x() + rect.width())
946                y2 = y_scale * (rect.y() + rect.height())
947                node_list.append([x1, y1, x2, y2, nid, None])
948            elif isinstance(item, _FaceGroupItem):
949                if item.column2faces:
950                    for f in item.childItems():
951                        r = f.boundingRect()
952                        rect = f.mapToScene(r).boundingRect()
953                        x1 = x_scale * rect.x()
954                        y1 = y_scale * rect.y()
955                        x2 = x_scale * (rect.x() + rect.width())
956                        y2 = y_scale * (rect.y() + rect.height())
957                        if isinstance(f, _TextFaceItem):
958                            face_list.append([x1, y1, x2, y2, nid, str(getattr(f, "face_label", f.text()))])
959                        elif isinstance(f, faces.SeqMotifRectItem):
960                            #face_list.append([x1, y1, x2, y2, nid, str(getattr(f, "face_label", None))])
961                            for mf in f.childItems():
962                                r = mf.boundingRect()
963                                rect = mf.mapToScene(r).boundingRect()
964                                x1 = x_scale * rect.x()
965                                y1 = y_scale * rect.y()
966                                x2 = x_scale * (rect.x() + rect.width())
967                                y2 = y_scale * (rect.y() + rect.height())
968                                try:
969                                    label = "Motif:%s" %mf.childItems()[0].text
970                                except Exception:
971                                    label = ""
972                                face_list.append([x1, y1, x2, y2, nid, label])
973                        else:
974                            face_list.append([x1, y1, x2, y2, nid, getattr(f, "face_label", None)])
975        #nid += 1
976
977    return {"nodes": node_list, "faces": face_list, "node_areas": node_areas}
978
979#@tracktime
980def init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root):
981    # ::: Precalculate values :::
982    visited = set()
983    to_visit = []
984    to_visit.append(root_node)
985    last_rotation = img.arc_start
986    depth = 1
987    while to_visit:
988        node = to_visit[-1]
989        finished = True
990        if node not in n2i:
991            # Set style according to layout function
992            item = n2i[node] = _NodeItem(node, parent.tree_layer)
993            depth += 1
994
995            item.setZValue(depth)
996            if node is root_node and hide_root:
997                pass
998            else:
999                init_node_dimensions(node, item, n2f[node], img)
1000                #set_node_size(node, n2i, n2f, img)
1001
1002        if not _leaf(node):
1003            # visit children starting from left to right. Very
1004            #  important!! check all children[-1] and children[0]
1005            for c in reversed(node.children):
1006                if c not in visited:
1007                    to_visit.append(c)
1008                    finished = False
1009            # :: pre-order code here ::
1010        if not finished:
1011            continue
1012        else:
1013            to_visit.pop(-1)
1014            visited.add(node)
1015
1016        # :: Post-order visits. Leaves are already visited here ::
1017        if img.mode == "c":
1018            if _leaf(node):
1019                crender.init_circular_leaf_item(node, n2i, n2f, last_rotation, rot_step)
1020                last_rotation += rot_step
1021            else:
1022                crender.init_circular_node_item(node, n2i, n2f)
1023
1024        elif img.mode == "r":
1025            if _leaf(node):
1026                rrender.init_rect_leaf_item(node, n2i, n2f)
1027            else:
1028                rrender.init_rect_node_item(node, n2i, n2f)
1029
1030
1031def init_node_dimensions(node, item, faceblock, img):
1032    """Calculates width and height of all different subparts and faces
1033    of a given node. Branch lengths are not taken into account, so some
1034    dimensions must be adjusted after setting a valid scale.
1035    """
1036
1037    min_separation = img.min_leaf_separation
1038
1039    if _leaf(node):
1040        aligned_height = faceblock["aligned"].h
1041        aligned_width = faceblock["aligned"].w
1042    else:
1043        aligned_height = 0
1044        aligned_width = 0
1045
1046    ndist =  1.0 if img.force_topology else node.dist
1047    item.branch_length = (ndist * img._scale) if img._scale else 0
1048    ## Calculate dimensions of the different node regions
1049    ##
1050    ##
1051    ##                                |
1052    ##                                |        ------
1053    ##          b-top       --------- |        |    |
1054    ## xoff-------------- O |b-right| |        |alg |
1055    ##          b-bottom    --------- |        |    |
1056    ##                                |        ------
1057    ##                                |
1058    ##
1059    ##      0     1       2     3     4           5
1060    ##
1061
1062    item.xoff = 0.0
1063    # widths
1064    w1 = max(faceblock["branch-bottom"].w, faceblock["branch-top"].w)
1065    w0 = item.branch_length - w1 if item.branch_length > w1 else 0
1066    w2 = node.img_style["size"]
1067    w3 = faceblock["branch-right"].w
1068    w4 = node.img_style["vt_line_width"] if not _leaf(node) and len(node.children) > 1 else 0.0
1069    w5 = 0
1070    # heights
1071    h0 = node.img_style["hz_line_width"]
1072    h1 = node.img_style["hz_line_width"] + faceblock["branch-top"].h + faceblock["branch-bottom"].h
1073    h2 = node.img_style["size"]
1074    h3 = faceblock["branch-right"].h
1075    h4 = 0
1076    h5 = aligned_height
1077
1078    # This fixes the problem of miss-aligned branches in ultrametric trees. If
1079    # there is nothing between the hz line and the vt line, then I prevent
1080    # vt_line_width to add extra width to the node, so node distances are
1081    # preserved in the img.
1082    if w2 == 0 and w3 == 0:
1083        w4 = 0
1084
1085    # ignore face heights if requested
1086    if img.mode == "c" and img.allow_face_overlap:
1087        h1, h3, h5 = 0, 0, 0
1088
1089    item.heights = [h0, h1, h2, h3, h4, h5]
1090    item.widths = [w0, w1, w2, w3, w4, w5]
1091
1092    # Calculate total node size
1093    total_w = sum([w0, w1, w2, w3, w4, item.xoff]) # do not count aligned faces
1094
1095    if img.mode == "c":
1096        max_h = max(item.heights[:4] + [min_separation])
1097    elif img.mode == "r":
1098        max_h = max(item.heights + [min_separation])
1099
1100    max_h += img.branch_vertical_margin
1101
1102    # correct possible unbalanced block in branch faces
1103    h_imbalance = abs(faceblock["branch-top"].h - faceblock["branch-bottom"].h)
1104    if h_imbalance + h1 > max_h:
1105        max_h += h_imbalance
1106
1107    item.facesRegion.setRect(0, 0, w3, max_h)
1108    item.nodeRegion.setRect(0, 0, total_w, max_h)
1109    item.fullRegion.setRect(0, 0, total_w, max_h)
1110
1111def update_branch_lengths(tree, n2i, n2f, img):
1112    for node in tree.traverse("postorder", is_leaf_fn=_leaf):
1113        item = n2i[node]
1114        ndist = 1.0 if img.force_topology else node.dist
1115        item.branch_length = ndist * img._scale
1116        w0 = 0
1117
1118        if item.branch_length > item.widths[1]:
1119            w0 = item.widths[0] = item.branch_length - item.widths[1]
1120            item.nodeRegion.adjust(0, 0, w0, 0)
1121
1122        child_width = 0
1123        if not _leaf(node):
1124            for ch in node.children:
1125                child_width = max(child_width, n2i[ch].fullRegion.width())
1126                if w0 and img.mode == "r":
1127                    #n2i[ch].translate(w0, 0) # deprecated in qt4.8
1128                    n2i[ch].moveBy(w0, 0)
1129        item.fullRegion.setWidth(item.nodeRegion.width() + child_width)
1130
1131def init_tree_style(t, ts):
1132    custom_ts = True
1133    if not ts:
1134        custom_ts = False
1135        ts = TreeStyle()
1136
1137    if not ts.layout_fn:
1138        cl = t.__class__
1139        try:
1140            ts_template = _DEFAULT_STYLE[cl]
1141        except KeyError as e:
1142            pass
1143        else:
1144            if not custom_ts:
1145                apply_template(ts, ts_template)
1146            else:
1147                ts.layout_fn = ts_template.get("layout_fn", None)
1148
1149    return ts
1150
1151