Esempio n. 1
0
class StartArrowTouchArea(AddBelowTouchArea):

    __qt_type_id__ = next_available_type_id()

    def contextual_color(self):
        if self._hovering:
            return ctrl.cm.hovering(ctrl.cm.ui())
        else:
            return ctrl.cm.ui()

    def boundingRect(self):
        """
        :return:
        """
        return QtCore.QRectF(-10, -5, 20, 15)

    def paint(self, painter, option, widget):
        """

        :param painter:
        :param option:
        :param widget:
        :raise:
        """
        if ctrl.pressed is self:
            pass
        c = self.contextual_color()
        painter.setPen(c)
        draw_plus(painter, -5, 5)
        #painter.setBrush(c)
        draw_arrow_shape_from_points(painter, -2, 0, 8, 7, c)
        if self._hovering:
            painter.drawRoundedRect(self.boundingRect(), 4, 4)
Esempio n. 2
0
class RightAddTop(JointedTouchArea):

    __qt_type_id__ = next_available_type_id()

    def __init__(self, host, action):
        super().__init__(host, action)
        self._align_left = False
        self.update_end_points()

    def paint(self, painter, option, widget):
        """

        :param painter:
        :param option:
        :param widget:
        :raise:
        """
        if ctrl.pressed is self:
            pass
        c = self.contextual_color()
        painter.setPen(c)
        if self._fill_path:
            painter.fillPath(self._path, c)
        else:
            painter.drawPath(self._path)
        if self._hovering:
            painter.save()
            painter.setBrush(ctrl.cm.ui())
            painter.rotate(-160)
            draw_leaf(painter, 0, end_spot_size / 2, end_spot_size)
            painter.restore()
            draw_plus(painter, 14, 0)
Esempio n. 3
0
class RemoveTriangleTouchArea(AddBelowTouchArea):

    __qt_type_id__ = next_available_type_id()

    def update_end_points(self):
        x, y = self.host.centered_scene_position
        lbr = self.host.label_object.boundingRect()
        y += self.host.label_object.triangle_y + lbr.top() + end_spot_size + 2
        self.end_point = x, y
        self.start_point = self.end_point
        self.setPos(x, y)

    def paint(self, painter, option, widget):
        """

        :param painter:
        :param option:
        :param widget:
        :raise:
        """
        if ctrl.pressed is self:
            pass
        c = self.contextual_color()
        painter.setPen(c)
        #draw_triangle(painter, 0, 0)
        draw_x(painter, 0, 0, end_spot_size / 2)
        if self._hovering:
            p = QtGui.QPen(c)
            p.setWidth(2)
            painter.setPen(p)
            draw_x(painter, 0, 0, end_spot_size)
Esempio n. 4
0
class AddBelowTouchArea(TouchArea):

    __qt_type_id__ = next_available_type_id()

    def update_end_points(self):
        x, y = self.host.centered_scene_position
        y += self.host.height / 2 + end_spot_size
        self.end_point = x, y
        self.start_point = self.end_point
        self.setPos(self.end_point[0], self.end_point[1])

    def paint(self, painter, option, widget):
        """

        :param painter:
        :param option:
        :param widget:
        :raise:
        """
        if ctrl.pressed is self:
            pass
        c = self.contextual_color()
        if self._hovering:
            painter.setBrush(ctrl.cm.ui_tr())
        else:
            painter.setBrush(qt_prefs.no_brush)
        painter.setPen(c)
        draw_tailed_leaf(painter, 0, 0, end_spot_size)
        if self._hovering:
            painter.setPen(c)
            draw_plus(painter, 4, 0)
Esempio n. 5
0
class BranchingTouchArea(TouchArea):
    """ TouchArea that connects to edges and has /-shape. Used to add/merge
    nodes in middle of the trees. """

    __qt_type_id__ = next_available_type_id()
    spot_size = end_spot_size * 0.5

    def boundingRect(self):
        """


        :return:
        """
        if not self.end_point:
            self.update_end_points()
            assert self.end_point
        # Bounding rect that includes the tail and end spot ellipse
        ex, ey = 0, 0
        sx, sy = sub_xy(self.start_point, self.end_point)
        ss2 = BranchingTouchArea.spot_size * 2
        ss = BranchingTouchArea.spot_size
        if sx < ex:
            w = max((ex - sx + ss, ss2))
            x = min((sx, ex - ss))
        else:
            w = max((sx - ex + ss, ss2))
            x = ex - ss
        if sy < ey:
            h = max((ey - sy + ss, ss2))
            y = min((sy, ey - ss))
        else:
            h = max((sy - ey + ss, ss2))
            y = ey - ss
        r = QtCore.QRectF(x, y, w, h)
        return r

    def drop(self, dropped_node):
        """
        Connect dropped node to host of this TouchArea.
        Connection depends on which merge area this is:
        top left, top right, left, right
        :param dropped_node:
        """
        if isinstance(dropped_node, str):
            dropped_node = self.make_node_from_string(dropped_node)
        if not dropped_node:
            return
        assert (self.host.start and self.host.end)
        adjustment = self.host.end.adjustment
        # host is an edge
        ctrl.free_drawing.insert_node_between(dropped_node, self.host.start,
                                              self.host.end, self._align_left,
                                              self.start_point)

        for node in ctrl.dragged_set:
            node.adjustment = adjustment
        ctrl.forest.forest_edited()
        return 'moved node %s to sibling of %s' % (dropped_node, self.host)
Esempio n. 6
0
class RightAddSibling(BranchingTouchArea):
    """ TouchArea that connects to edges and has /-shape. Used to add/merge
    nodes in middle of the trees. """
    __qt_type_id__ = next_available_type_id()

    def __init__(self, host, action):
        super().__init__(host, action)
        self._align_left = False
        self.update_end_points()

    def drag(self, event):
        self._dragging = True
        self.update_end_points(end_point=to_tuple(event.scenePos()))

    def update_end_points(self, end_point=None):
        """

        :param end_point: End point can be given or it can be calculated.
        """
        e = self.host
        sx, sy = to_tuple(e.get_point_at(0.4))
        self.start_point = sx, sy
        if end_point:
            self.end_point = end_point
        else:
            d = e.get_angle_at(0.4)
            d += 60  # 75
            angle = math.radians(-d)
            dx = math.cos(angle)
            dy = math.sin(angle)
            l = 12
            x = sx + dx * l
            y = sy + dy * l
            self.end_point = x, y
        self.setPos(self.end_point[0], self.end_point[1])

    def paint(self, painter, option, widget):
        """

        :param painter:
        :param option:
        :param widget:
        :raise:
        """
        if ctrl.pressed is self:
            pass
        c = self.contextual_color()
        painter.setPen(c)
        painter.drawLine(*sub_xy(self.start_point, self.end_point), 0, 0)
        if self._hovering:
            painter.save()
            painter.setBrush(ctrl.cm.ui())
            painter.rotate(-160)
            draw_leaf(painter, 0, BranchingTouchArea.spot_size / 2,
                      BranchingTouchArea.spot_size)
            painter.restore()
            draw_plus(painter, 14, 0)
Esempio n. 7
0
class LeftAddChild(BranchingTouchArea):
    """ TouchArea that adds children to nodes and has /-shape. Used to
    add nodes to leaf nodes."""
    __qt_type_id__ = next_available_type_id()

    def __init__(self, host, action):
        super().__init__(host, action)
        self._align_left = True

    def update_end_points(self, end_point=None):
        """

        :param end_point: End point can be given or it can be calculated.
        """
        shape_name = ctrl.settings.get_edge_setting(
            'shape_name', edge_type=g.CONSTITUENT_EDGE)
        self._fill_path = ctrl.settings.get_shape_setting(
            'fill', edge_type=g.CONSTITUENT_EDGE)
        sx, sy = self.host.magnet(7)
        self.start_point = sx, sy
        if end_point:
            self.end_point = end_point
        else:
            ex = sx - 20  # 75
            ey = sy + 10
            self.end_point = ex, ey
        self.setPos(self.end_point[0], self.end_point[1])
        rel_sp = sub_xy(self.start_point, self.end_point)
        adjust = []
        self._path = SHAPE_PRESETS[shape_name].path(rel_sp, (0, 0),
                                                    alignment=g.LEFT,
                                                    curve_adjustment=adjust)[0]

    def paint(self, painter, option, widget):
        """

        :param painter:
        :param option:
        :param widget:
        :raise:
        """
        if ctrl.pressed is self:
            pass
        c = self.contextual_color()
        painter.setPen(c)
        if self._fill_path:
            painter.fillPath(self._path, c)
        else:
            painter.drawPath(self._path)
        if self._hovering:
            painter.save()
            painter.setBrush(ctrl.cm.ui())
            painter.rotate(20)
            draw_leaf(painter, 0, end_spot_size / 2, end_spot_size)
            painter.restore()
            draw_plus(painter, 4, 0)
Esempio n. 8
0
class MarkerStartPoint(QtWidgets.QGraphicsItem):
    __qt_type_id__ = next_available_type_id()

    def __init__(self, parent):
        QtWidgets.QGraphicsItem.__init__(self, parent)
        self.setCursor(QtCore.Qt.CrossCursor)
        self.setAcceptHoverEvents(True)

    def type(self):
        """ Qt's type identifier, custom QGraphicsItems should have different type ids if events
        need to differentiate between them. These are set when the program starts.
        :return:
        """
        return self.__qt_type_id__

    def paint(self, painter, options, QWidget_widget=None):
        if prefs.touch:
            p = QtGui.QPen(ctrl.cm.ui_tr())
            p.setWidth(2)
            painter.setPen(p)
            painter.drawEllipse(-6, -6, 12, 12)
        else:
            p = QtGui.QPen(ctrl.cm.ui())
            p.setWidthF(0.5)
            painter.setPen(p)
            painter.drawRect(-2, -2, 4, 4)

    def boundingRect(self):
        if prefs.touch:
            return QtCore.QRectF(-6, -6, 12, 12)
        else:
            return QtCore.QRectF(-2, -2, 4, 4)

    def mouseMoveEvent(self, event):
        if ctrl.pressed is self:
            if ctrl.dragged_set or (event.buttonDownScenePos(
                    QtCore.Qt.LeftButton) - event.scenePos()).manhattanLength() > 6:
                self.drag(event)
                ctrl.graph_scene.dragging_over(event.scenePos())

    def mouseReleaseEvent(self, event):
        if ctrl.pressed is self:
            ctrl.release(self)
            if ctrl.dragged_set:
                ctrl.graph_scene.kill_dragging()
            return None  # this mouseRelease is now consumed
        super().mouseReleaseEvent(event)

    def drag(self, event):
        pi = self.parentItem()
        if pi:
            pi.set_dragged(True)
            pi.update_position(event.scenePos())

    def drop_to(self, x, y, recipient=None):
        pass
Esempio n. 9
0
class ProjectionVisual(QtWidgets.QGraphicsItem):
    """ Transparent overlay to show which nodes belong to one projection
    """
    __qt_type_id__ = next_available_type_id()

    def __init__(self, data):
        super().__init__()
        self.d = data
        self.color = ctrl.cm.get(self.d.color_tr_id)
        #self.show()

    def type(self):
        """ Qt's type identifier, custom QGraphicsItems should have different type ids if events
        need to differentiate between them. These are set when the program starts.
        :return:
        """
        return self.__qt_type_id__

    def boundingRect(self):
        br = QtCore.QRectF()
        for chain in self.d.chains:
            for node in chain:
                br |= node.sceneBoundingRect()
        return br

    def paint(self, painter, style, QWidget_widget=None):
        painter.setBrush(self.color)
        painter.setPen(QtCore.Qt.NoPen)
        for chain in self.d.chains:
            vis_chain = [x for x in chain if x.is_visible()]
            if len(vis_chain) < 2:
                continue
            forward = []
            back = []
            sx = 0
            sy = 0
            start_x, start_y = vis_chain[0].current_scene_position
            # shape will be one continuous filled polygon, so when we iterate
            # through nodes it needs to go through we make list of positions for
            # polygon going there (forward) and for its return trip (back).
            for node in vis_chain:
                sx, sy = node.current_scene_position
                # r = node.sceneBoundingRect()
                # p.addEllipse(r)
                forward.append((sx - 5, sy))
                back.append((sx + 5, sy))
            back.reverse()
            p = QtGui.QPainterPath(QtCore.QPointF(start_x, start_y + 5))
            for x, y in forward + [(sx, sy - 5)] + back:
                p.lineTo(x, y)
            painter.fillPath(p, self.color)
Esempio n. 10
0
class DeleteArrowTouchArea(TouchArea):

    __qt_type_id__ = next_available_type_id()

    def paint(self, painter, option, widget):
        """
        :param painter:
        :param option:
        :param widget:
        :raise:
        """
        if ctrl.pressed is self:
            pass
        painter.setPen(self.contextual_color())
        draw_x(painter, 0, 0, end_spot_size)
Esempio n. 11
0
class ConnectGlossTouchArea(AddBelowTouchArea):

    __qt_type_id__ = next_available_type_id()

    def __init__(self, host, action):
        super().__init__(host, action)
        self.set_tip("Add gloss for node")

    def drop(self, dropped_node):
        """
        :param dropped_node:
        """
        if isinstance(dropped_node, str):
            dropped_node = self.make_node_from_string(dropped_node)
        if not dropped_node:
            return
        ctrl.free_drawing.add_gloss_to_node(dropped_node, self.host)
        ctrl.forest.forest_edited()
        return 'added gloss %s to %s' % (dropped_node, self.host)
Esempio n. 12
0
class AddTriangleTouchArea(AddBelowTouchArea):

    __qt_type_id__ = next_available_type_id()

    def paint(self, painter, option, widget):
        """

        :param painter:
        :param option:
        :param widget:
        :raise:
        """
        if ctrl.pressed is self:
            pass
        c = self.contextual_color()
        painter.setPen(c)
        draw_triangle(painter, 0, 0)
        if self._hovering:
            painter.setPen(c)
            draw_triangle(painter, 0, -5, 6)
Esempio n. 13
0
class GlowRing(QtWidgets.QGraphicsEllipseItem):
    """ Decoration for radial menus """
    __qt_type_id__ = next_available_type_id()

    def __init__(self, parent, radius=40):
        QtWidgets.QGraphicsEllipseItem.__init__(self, QtCore.QRectF(0, 0, 0, 0), parent)
        pen = QtGui.QPen(ctrl.cm.ui())
        pen.setWidth(4)
        self.setPen(pen)
        glow = QtWidgets.QGraphicsBlurEffect(parent)
        glow.setBlurRadius(7)
        glow.setEnabled(True)
        self.setGraphicsEffect(glow)
        self._radius = 0
        self._max_radius = radius
        self._step_size = radius / 6.0

    def type(self):
        """ Qt's type identifier, custom QGraphicsItems should have different type ids if events
        need to differentiate between them. These are set when the program starts.
        :return:
        """
        return self.__qt_type_id__

    def grow(self):
        """


        """
        self._radius += self._step_size
        self.setRect(-self._radius, -self._radius, 2 * self._radius, 2 * self._radius)

    def shrink(self):
        """


        """
        self.radius -= self.step_size
        self.setRect(-self.radius, -self.radius, 2 * self.radius, 2 * self.radius)
Esempio n. 14
0
class ActivityMarker(QtWidgets.QGraphicsRectItem, UIGraphicsItem):
    """ Blinky thing to announce that computing is going on. """
    __qt_type_id__ = next_available_type_id()

    def __init__(self, role=0, ui_key=None):
        QtWidgets.QGraphicsRectItem.__init__(self, QtCore.QRectF(
            0, 0, 4, 4))  # , scene = parent)
        UIGraphicsItem.__init__(self, ui_key=ui_key)
        self.role = int(role)
        self.setZValue(100)
        self.setBrush(ctrl.cm.get('accent%s' % str(self.role + 1)))
        self.setPen(qt_prefs.no_pen)  # QtCore.Qt.NoPen
        self.setPos(5 + self.role * 10, 5)

    def type(self):
        """ Qt's type identifier, custom QGraphicsItems should have different type ids if events
        need to differentiate between them. These are set when the program starts.
        :return:
        """
        return self.__qt_type_id__

    def update_position(self):
        """ stay always in initial position """
        pass
Esempio n. 15
0
class AddConstituentTouchArea(TouchArea):

    __qt_type_id__ = next_available_type_id()

    def click(self, event=None):
        """
        :param event:
        """
        self._dragging = False
        if self._drag_hint:
            return False
        self.host.open_embed()
        return True

    def drop(self, dropped_node):
        """
        Connect dropped node to host of this TouchArea.
        Connection depends on which merge area this is:
        top left, top right, left, right
        :param dropped_node:
        """
        if isinstance(dropped_node, str):
            self.make_node_from_string(dropped_node)
        ctrl.forest.forest_edited()
Esempio n. 16
0
class ChildTouchArea(TouchArea):
    """ TouchArea that adds children to nodes and has /-shape. Used to
    add nodes to leaf nodes. """
    __qt_type_id__ = next_available_type_id()

    def __init__(self, host, action):
        super().__init__(host, action)
        self.update_end_points()

    def boundingRect(self):
        """


        :return:
        """
        if not self.end_point:
            self.update_end_points()
            assert self.end_point
        # Bounding rect that includes the tail and end spot ellipse
        ex, ey = 0, 0
        sx, sy = sub_xy(self.start_point, self.end_point)
        e2 = end_spot_size * 2
        if sx < ex:
            w = max((ex - sx + end_spot_size, e2))
            x = min((sx, ex - end_spot_size))
        else:
            w = max((sx - ex + end_spot_size, e2))
            x = ex - end_spot_size
        if sy < ey:
            h = max((ey - sy + end_spot_size, e2))
            y = min((sy, ey - end_spot_size))
        else:
            h = max((sy - ey + end_spot_size, e2))
            y = ey - end_spot_size
        r = QtCore.QRectF(x, y, w, h)
        return r

    def drag(self, event):
        self._dragging = True
        self.update_end_points(end_point=to_tuple(event.scenePos()))

    def drop(self, dropped_node):
        """
        Connect dropped node to host of this TouchArea.
        Connection depends on which merge area this is:
        top left, top right, left, right
        :param dropped_node:
        """
        if isinstance(dropped_node, str):
            dropped_node = self.make_node_from_string(dropped_node)
        if not dropped_node:
            return
        # host is an edge
        ctrl.free_drawing.insert_node_between(dropped_node, self.host.start,
                                              self.host.end, self._align_left,
                                              self.start_point)
        for node in ctrl.dragged_set:
            node.adjustment = self.host.end.adjustment
        message = 'moved node %s to sibling of %s' % (dropped_node, self.host)
        ctrl.forest.forest_edited()
        return message
Esempio n. 17
0
class ConstituentNode(Node):
    """ ConstituentNode is enriched with few elements that have no syntactic meaning but help with
     reading the trees aliases, indices and glosses.
    """
    __qt_type_id__ = next_available_type_id()
    display_name = ('Constituent', 'Constituents')
    display = True
    width = 20
    height = 20
    is_constituent = True
    node_type = g.CONSTITUENT_NODE
    wraps = 'constituent'
    editable = {
    }  # Uses custom ConstituentNodeEmbed instead of template-based NodeEditEmbed

    default_style = {
        'plain': {
            'color_id': 'content1',
            'font_id': g.MAIN_FONT,
            'font-size': 10
        },
        'fancy': {
            'color_id': 'content1',
            'font_id': g.MAIN_FONT,
            'font-size': 10
        }
    }

    default_edge = g.CONSTITUENT_EDGE

    # Touch areas are UI elements that scale with the trees: they can be
    # temporary shapes suggesting to drag or click here to create the
    # suggested shape.

    # touch_areas_when_dragging and touch_areas_when_selected use the same
    # format.

    # 'condition': there are some general conditions implemented in UIManager,
    # but condition can refer to method defined for node instance. When used
    # for when-dragging checks, the method will be called with two parameters
    # 'dragged_type' and 'dragged_host'.
    # 'place': there are some general places defined in UIManager. The most
    # important is 'edge_up': in this case touch areas are associated with
    # edges going up. When left empty, touch area is associated with the node.

    touch_areas_when_dragging = {
        g.LEFT_ADD_TOP: {
            'condition':
            ['is_top_node', 'dragging_constituent', 'free_drawing_mode']
        },
        g.RIGHT_ADD_TOP: {
            'condition':
            ['is_top_node', 'dragging_constituent', 'free_drawing_mode']
        },
        g.LEFT_ADD_SIBLING: {
            'place': 'edge_up',
            'condition': ['dragging_constituent', 'free_drawing_mode']
        },
        g.RIGHT_ADD_SIBLING: {
            'place': 'edge_up',
            'condition': ['dragging_constituent', 'free_drawing_mode']
        },
        g.TOUCH_CONNECT_COMMENT: {
            'condition': 'dragging_comment'
        },
        g.TOUCH_CONNECT_FEATURE: {
            'condition': ['dragging_feature', 'free_drawing_mode']
        },
        g.TOUCH_CONNECT_GLOSS: {
            'condition': 'dragging_gloss'
        }
    }

    touch_areas_when_selected = {
        g.LEFT_ADD_TOP: {
            'condition': ['is_top_node', 'free_drawing_mode'],
            'action': 'add_top_left'
        },
        g.RIGHT_ADD_TOP: {
            'condition': ['is_top_node', 'free_drawing_mode'],
            'action': 'add_top_right'
        },
        g.MERGE_TO_TOP: {
            'condition': ['not:is_top_node', 'free_drawing_mode'],
            'action': 'merge_to_top'
        },
        g.INNER_ADD_SIBLING_LEFT: {
            'condition': ['inner_add_sibling', 'free_drawing_mode'],
            'place': 'edge_up',
            'action': 'inner_add_sibling_left'
        },
        g.INNER_ADD_SIBLING_RIGHT: {
            'condition': ['inner_add_sibling', 'free_drawing_mode'],
            'place': 'edge_up',
            'action': 'inner_add_sibling_right'
        },
        g.UNARY_ADD_CHILD_LEFT: {
            'condition': ['has_one_child', 'free_drawing_mode'],
            'action': 'unary_add_child_left'
        },
        g.UNARY_ADD_CHILD_RIGHT: {
            'condition': ['has_one_child', 'free_drawing_mode'],
            'action': 'unary_add_child_right'
        },
        g.LEAF_ADD_SIBLING_LEFT: {
            'condition': ['is_leaf', 'free_drawing_mode'],
            'action': 'leaf_add_sibling_left'
        },
        g.LEAF_ADD_SIBLING_RIGHT: {
            'condition': ['is_leaf', 'free_drawing_mode'],
            'action': 'leaf_add_sibling_right'
        },
        g.ADD_TRIANGLE: {
            'condition': 'can_have_triangle',
            'action': 'add_triangle'
        },
        g.REMOVE_TRIANGLE: {
            'condition': 'is_triangle_host',
            'action': 'remove_triangle'
        }
    }

    buttons_when_selected = {
        g.REMOVE_MERGER: {
            'condition': ['is_unnecessary_merger', 'free_drawing_mode'],
            'action': 'remove_merger'
        },
        g.NODE_EDITOR_BUTTON: {
            'action': 'toggle_node_edit_embed'
        },
        g.REMOVE_NODE: {
            'condition': ['not:is_unnecessary_merger', 'free_drawing_mode'],
            'action': 'remove_node'
        },
        #g.QUICK_EDIT_LABEL: {}, # 'condition': 'is_quick_editing'
    }

    def __init__(self, label=''):
        """ Most of the initiation is inherited from Node """
        Node.__init__(self)
        self.heads = []

        # ### Projection -- see also preferences that govern if these are used
        self.can_project = True
        self.projecting_to = set()

        self.index = ''
        self.label = label
        self.autolabel = ''
        self.gloss = ''

        self.is_trace = False
        self.merge_order = 0
        self.select_order = 0
        self.in_projections = []

        # ### Cycle index stores the order when node was originally merged to structure.
        # going up in trees, cycle index should go up too

    def after_init(self):
        """ After_init is called in 2nd step in process of creating objects:
        1st wave creates the objects and calls __init__, and then iterates through and sets the
        values. 2nd wave calls after_inits for all created objects. Now they can properly refer
        to each other and know their values.
        :return: None
        """
        self.update_gloss()
        self.update_label_shape()
        self.update_label()
        self.update_visibility()
        self.update_status_tip()
        self.announce_creation()
        if prefs.glow_effect:
            self.toggle_halo(True)
        ctrl.forest.store(self)

    @staticmethod
    def create_synobj(label, forest):
        """ ConstituentNodes are wrappers for Constituents. Exact
        implementation/class of constituent is defined in ctrl.
        :return:
        """
        if not label:
            label = forest.get_first_free_constituent_name()
        c = ctrl.syntax.Constituent(label)
        c.after_init()
        return c

    def load_values_from_parsernode(self, parsernode):
        """ Update constituentnode with values from parsernode
        :param parsernode:
        :return:
        """
        def remove_dot_label(inode, row_n):
            for i, part in enumerate(list(inode.parts)):
                if isinstance(part, str):
                    if part.startswith('.'):
                        inode.parts[i] = part[1:]
                    return True
                elif isinstance(part,
                                ICommandNode) and part.command == 'qroof':
                    self.triangle_stack = [self]
                    continue
                else:
                    return remove_dot_label(part, row_n)

        if parsernode.index:
            self.index = parsernode.index
        rows = parsernode.label_rows
        # Remove dotlabel

        for i, row in enumerate(list(rows)):
            if isinstance(row, str):
                if row.startswith('.'):
                    rows[i] = row[1:]
                break
            stop = remove_dot_label(row, i)
            if stop:
                break
        # △

        self.label = join_lines(rows)
        # now as rows are in one INode / string, we can extract the triangle part and put it to
        # end. It is different to qtree's way of handling triangles, but much simpler for us in
        # long run.
        triangle_part = extract_triangle(self.label, remove_from_original=True)
        if triangle_part:
            assert isinstance(self.label, ITextNode)

            self.label.parts.append('\n')
            self.label.parts.append(triangle_part)
        if self.index:
            base = as_html(self.label)
            if base.strip().startswith('t<sub>'):
                self.is_trace = True

    def get_syntactic_label(self):
        if self.syntactic_object:
            return self.syntactic_object.label

    # Other properties

    @property
    def gloss_node(self):
        """
        :return:
        """
        gs = self.get_children(visible=True, of_type=g.GLOSS_EDGE)
        if gs:
            return gs[0]

    def has_ordered_children(self):
        if self.syntactic_object:
            return getattr(self.syntactic_object, 'is_ordered', False)
        return True

    def update_label_shape(self):
        self.label_object.label_shape = ctrl.settings.get('label_shape')

    def should_show_gloss_in_label(self) -> bool:
        return ctrl.settings.get('lock_glosses_to_label') == 1

    def update_status_tip(self) -> None:
        """ Hovering status tip """

        if self.label:
            label = f'Label: "{as_text(self.label)}" '
        else:
            label = ''
        syn_label = self.get_syn_label()
        if syn_label:
            syn_label = f' Constituent: "{as_text(syn_label)}" '
        else:
            syn_label = ''
        if self.index:
            index = f' Index: "{self.index}"'
        else:
            index = ''

        if self.is_trace:
            name = "Trace"
        elif self.is_leaf():
            name = "Leaf "
        # elif self.is_top_node():
        #    name = "Set %s" % self.set_string() # "Root constituent"
        else:
            #name = f"Set {self.set_string()}"
            name = "Set "
        if self.use_adjustment:
            adjustment = f' w. adjustment ({self.adjustment[0]:.1f}, {self.adjustment[1]:.1f})'
        else:
            adjustment = ''
        heads = ', '.join([as_text(x.label) for x in self.heads])
        self.status_tip = f"{name} ({label}{syn_label}{index} pos: ({self.current_scene_position[0]:.1f}, " \
                          f"{self.current_scene_position[1]:.1f}){adjustment} head: {heads})"

    def short_str(self):
        label = as_text(self.label)
        if label:
            lines = label.splitlines()
            if len(lines) > 3:
                label = f'{lines[0]} ...\n{lines[-1]}'
        syn_label = as_text(self.get_syn_label())
        if label and syn_label:
            return f'{label} ({syn_label})'
        else:
            return label or syn_label or "no label"

    def set_string(self):
        """ This can be surprisingly expensive to calculate
        :return: 
        """
        if self.syntactic_object and hasattr(self.syntactic_object,
                                             'set_string'):
            return self.syntactic_object.set_string()
        else:
            return self._set_string()

    def _set_string(self):
        parts = []
        for child in self.get_children(similar=True, visible=False):
            parts.append(str(child._set_string()))
        if parts:
            return '{%s}' % ', '.join(parts)
        else:
            return self.label

    def __str__(self):
        label = as_text(self.label, single_line=True)
        syn_label = as_text(self.get_syn_label(), single_line=True)
        if label and syn_label:
            return f'CN {label} ({syn_label})'
        else:
            return f'CN {label or syn_label or "no label"}'

    def get_syn_label(self):
        if self.syntactic_object:
            return self.syntactic_object.label
        return ''

    def compose_html_for_viewing(self, peek_into_synobj=True):
        """ This method builds the html to display in label. For convenience, syntactic objects
        can override this (going against the containment logic) by having their own
        'compose_html_for_viewing' -method. This is so that it is easier to create custom
        implementations for constituents without requiring custom constituentnodes.

        Note that synobj's compose_html_for_viewing receives the node object as parameter,
        so you can replicate the behavior below and add your own to it.

        :param peek_into_synobj: allow syntactic object to override this method. If synobj in turn
        needs the result from this implementation (e.g. to append something to it), you have to
        turn this off to avoid infinite loop. See example plugins.
        :return:
        """

        # Allow custom syntactic objects to override this
        if peek_into_synobj and hasattr(self.syntactic_object,
                                        'compose_html_for_viewing'):
            return self.syntactic_object.compose_html_for_viewing(self)

        html = []

        label_text_mode = ctrl.settings.get('label_text_mode')
        l = ''
        if label_text_mode == g.NODE_LABELS:
            if self.label:
                l = self.label
            elif self.syntactic_object:
                l = self.syntactic_object.label
        elif label_text_mode == g.NODE_LABELS_FOR_LEAVES:
            if self.label:
                l = self.label
            elif self.syntactic_object and self.is_leaf(only_similar=True,
                                                        only_visible=False):
                l = self.syntactic_object.label
        elif label_text_mode == g.SYN_LABELS:
            if self.syntactic_object:
                l = self.syntactic_object.label
        elif label_text_mode == g.SYN_LABELS_FOR_LEAVES:
            if self.syntactic_object and self.is_leaf(only_similar=True,
                                                      only_visible=False):
                l = self.syntactic_object.label
        elif label_text_mode == g.SECONDARY_LABELS:
            if self.syntactic_object:
                l = self.syntactic_object.get_secondary_label()
        elif label_text_mode == g.XBAR_LABELS:
            l = self.get_autolabel()
        separate_triangle = bool(self.is_cosmetic_triangle()
                                 and self.triangle_stack[-1] is self)
        l_html = as_html(l,
                         omit_triangle=separate_triangle,
                         include_index=self.index)
        if l_html:
            html.append(l_html)

        if self.gloss and self.should_show_gloss_in_label():
            if html:
                html.append('<br/>')
            html.append(as_html(self.gloss))
        if html and html[-1] == '<br/>':
            html.pop()

        # Lower part
        lower_html = ''
        if separate_triangle:
            qroof_content = extract_triangle(l)
            if qroof_content:
                lower_html = as_html(qroof_content)
        return ''.join(html), lower_html

    def compose_html_for_editing(self):
        """ This is used to build the html when quickediting a label. It should reduce the label
        into just one field value that is allowed to be edited, in constituentnode this is
        either label or synobj's label. This can be overridden in syntactic object by having
        'compose_html_for_editing' -method there. The method returns a tuple,
          (field_name, setter, html).
        :return:
        """

        # Allow custom syntactic objects to override this
        if self.syntactic_object and hasattr(self.syntactic_object,
                                             'compose_html_for_editing'):
            return self.syntactic_object.compose_html_for_editing(self)
        label_text_mode = ctrl.settings.get('label_text_mode')
        if label_text_mode == g.NODE_LABELS or label_text_mode == g.NODE_LABELS_FOR_LEAVES:
            if self.label:
                if self.triangle_stack:
                    lower_part = extract_triangle(self.label)
                    return 'node label', as_html(self.label, omit_triangle=True) + \
                           '<br/>' + as_html(lower_part or '')
                else:
                    return 'node label', as_html(self.label)
            elif self.syntactic_object:
                return 'syntactic label', as_html(self.syntactic_object.label)
            else:
                return '', '', ''
        elif label_text_mode == g.SYN_LABELS or label_text_mode == g.SYN_LABELS_FOR_LEAVES:
            if self.syntactic_object:
                return 'syntactic label', as_html(self.syntactic_object.label)
            else:
                return '', ''

    def parse_edited_label(self, label_name, value):
        success = False
        if self.syntactic_object and hasattr(self.syntactic_object,
                                             'parse_edited_label'):
            success = self.syntactic_object.parse_edited_label(
                label_name, value)
        if not success:
            if label_name == 'node label':
                self.poke('label')
                self.label = value
                return True
            elif label_name == 'syntactic label':
                self.syntactic_object.label = value
                return True
            elif label_name == 'index':
                self.index = value
        return False

    def as_bracket_string(self):
        """ returns a simple bracket string representation """
        if self.label:
            children = list(self.get_children(similar=True, visible=False))
            if children:
                return '[.%s %s ]' % \
                       (self.label, ' '.join((c.as_bracket_string() for c in children)))
            else:
                return str(self.label)
        else:
            inside = ' '.join(
                (x.as_bracket_string()
                 for x in self.get_children(similar=True, visible=False)))
            if inside:
                return '[ ' + inside + ' ]'
            elif self.syntactic_object:
                return str(self.syntactic_object)
            else:
                return '-'

    def get_attribute_nodes(self, label_key=''):
        """

        :param label_k ey:
        :return:
        """
        atts = [
            x.end for x in self.edges_down if x.edge_type == g.ATTRIBUTE_EDGE
        ]
        if label_key:
            for a in atts:
                if a.attribute_label == label_key:
                    return a
        else:
            return atts

    def get_autolabel(self):
        return self.autolabel

    def is_unnecessary_merger(self):
        """ This merge can be removed, if it has only one child
        :return:
        """
        return len(list(self.get_children(similar=True, visible=False))) == 1

    # Conditions ##########################
    # These are called from templates with getattr, and may appear unused for IDE's analysis.
    # Check their real usage with string search before removing these.

    def inner_add_sibling(self):
        """ Node has child and it is not unary child. There are no other reasons preventing
        adding siblings
        :return: bool
        """
        return self.get_children(similar=True,
                                 visible=False) and not self.is_unary()

    def has_one_child(self):
        return len(self.get_children(similar=True, visible=False)) == 1

    def can_be_projection_of_another_node(self):
        """ Node can be projection from other nodes if it has other nodes
        below it.
        It may be necessary to move this check to syntactic level at some
        point.
        :return:
        """
        if ctrl.settings.get('use_projection'):
            if self.is_leaf(only_similar=True, only_visible=False):
                return False
            else:
                return True
        else:
            return False

    def set_heads(self, head):
        """ Set projecting head to be Node, list of Nodes or empty. Notice that this doesn't
        affect syntactic objects.
        :param head:
        :return:
        """
        if isinstance(head, list):
            self.heads = list(head)
        elif isinstance(head, Node):
            self.heads = [head]
        elif not head:
            self.heads = []
        else:
            raise ValueError

    def synobj_to_node(self):
        """ Update node's values from its synobj. Subclasses implement this.
        :return:
        """
        self.synheads_to_heads()

    def synheads_to_heads(self):
        """ Make sure that node's heads reflect synobjs heads.
        :return:
        """
        self.heads = []
        if self.syntactic_object:
            synlabel = self.syntactic_object.label
            parts = self.syntactic_object.parts
            if len(parts) == 0:
                self.heads = [self]
            if len(parts) == 1:
                if parts[0].label == synlabel:
                    self.heads = [ctrl.forest.get_node(parts[0])]
                else:
                    self.heads = [self]
            elif len(parts) == 2:
                if parts[0].label == synlabel:
                    self.heads = [ctrl.forest.get_node(parts[0])]
                elif parts[1].label == synlabel:
                    self.heads = [ctrl.forest.get_node(parts[1])]
                elif synlabel == f"({parts[0].label}, {parts[1].label})":
                    self.heads = [
                        ctrl.forest.get_node(parts[0]),
                        ctrl.forest.get_node(parts[1])
                    ]
                elif synlabel == f"({parts[1].label}, {parts[0].label})":
                    self.heads = [
                        ctrl.forest.get_node(parts[1]),
                        ctrl.forest.get_node(parts[0])
                    ]

    @property
    def contextual_color(self):
        """ Drawing color that is sensitive to node's state
        :return: QColor
        """

        if ctrl.is_selected(self):
            base = ctrl.cm.selection()
        elif self.in_projections:
            base = ctrl.cm.get(self.in_projections[0].color_id)
        else:
            base = self.color
        if self.drag_data:
            return ctrl.cm.lighter(base)
        elif ctrl.pressed is self:
            return ctrl.cm.active(base)
        elif self._hovering:
            return ctrl.cm.hovering(base)
        else:
            return base

    # ### Features #########################################

    def update_gloss(self, value=None):
        """


        """
        if not self.syntactic_object:
            return
        syn_gloss = self.gloss
        gloss_node = self.gloss_node
        if not ctrl.undo_disabled:
            if gloss_node and not syn_gloss:
                ctrl.free_drawing.delete_node(gloss_node)
            elif syn_gloss and not gloss_node:
                ctrl.free_drawing.create_gloss_node(host=self)
            elif syn_gloss and gloss_node:
                gloss_node.update_label()

    # ### Labels #############################################
    # things to do with traces:
    # if renamed and index is removed/changed, update chains
    # if moved, update chains
    # if copied, make sure that copy isn't in chain
    # if deleted, update chains
    # any other operations?

    def is_empty_node(self):
        """ Empty nodes can be used as placeholders and deleted or replaced without structural
        worries """
        return (not (self.syntactic_object or self.label
                     or self.index)) and self.is_leaf()

    # ## Indexes and chains ###################################

    def is_chain_head(self):
        """


        :return:
        """
        if self.index:
            return not (self.is_leaf() and self.label == 't')
        return False

    ### UI support

    def dragging_constituent(self):
        """ Check if the currently dragged item is constituent and can connect with me
        :return:
        """
        return self.is_dragging_this_type(g.CONSTITUENT_NODE)

    def dragging_feature(self):
        """ Check if the currently dragged item is feature and can connect with me
        :return:
        """
        return self.is_dragging_this_type(g.FEATURE_NODE)

    def dragging_gloss(self):
        """ Check if the currently dragged item is gloss and can connect with me
        :return:
        """
        return self.is_dragging_this_type(g.GLOSS_NODE)

    def dragging_comment(self):
        """ Check if the currently dragged item is comment and can connect with me
        :return:
        """
        return self.is_dragging_this_type(g.COMMENT_NODE)

    # ### Features #########################################

    def get_features(self):
        """ Returns FeatureNodes """
        return self.get_children(visible=True, of_type=g.FEATURE_EDGE)

    def get_features_as_string(self):
        """
        :return:
        """
        features = [f.syntactic_object for f in self.get_features()]
        feature_strings = [str(f) for f in features]
        return ', '.join(feature_strings)

    # ### Checks for callable actions ####

    def can_top_merge(self):
        """
        :return:
        """
        top = self.get_top_node()
        return self is not top and self not in top.get_children(similar=True,
                                                                visible=False)

    # ### Dragging #####################################################################

    # ## Most of this is implemented in Node

    def prepare_children_for_dragging(self, scene_pos):
        """ Implement this if structure is supposed to drag with the node
        :return:
        """
        children = ctrl.forest.list_nodes_once(self)

        for tree in self.trees:
            dragged_index = tree.sorted_constituents.index(self)
            for i, node in enumerate(tree.sorted_constituents):
                if node is not self and i > dragged_index and node in children:
                    node.start_dragging_tracking(host=False,
                                                 scene_pos=scene_pos)

    #################################

    # ### Parents & Children ####################################################

    def is_projecting_to(self, other):
        """

        :param other:
        """
        pass

    #
    # def paint(self, painter, option, widget=None):
    #     """ Painting is sensitive to mouse/selection issues, but usually with
    #     :param painter:
    #     :param option:
    #     :param widget:
    #     nodes it is the label of the node that needs complex painting """
    #     super().paint(painter, option, widget=widget)

    # ############## #
    #                #
    #  Save support  #
    #                #
    # ############## #

    label = SavedField("label")
    index = SavedField("index")
    gloss = SavedField("gloss", if_changed=update_gloss)
    heads = SavedField("heads")
Esempio n. 18
0
class JointedTouchArea(TouchArea):
    """ TouchArea that connects to nodes and has ^-shape. Used to add nodes
    to top of the trees. """
    __qt_type_id__ = next_available_type_id()

    def boundingRect(self):
        """


        :return:
        """
        if not self.end_point:
            self.update_end_points()
            assert self.end_point
        # Bounding rect that includes the tail and end spot ellipse
        rel_sp = sub_xy(self.start_point, self.end_point)
        sx, sy = rel_sp
        ex, ey = 0, 0
        e2 = end_spot_size * 2
        if sx < ex:
            w = max((ex - sx + end_spot_size, e2))
            x = min((sx, ex - end_spot_size))
        else:
            w = max((sx - ex + end_spot_size, e2))
            x = ex - end_spot_size
        if sy < ey:
            h = max((ey - sy + end_spot_size, e2))
            y = min((sy, ey - end_spot_size))
        else:
            h = max((sy - ey + end_spot_size, e2))
            y = ey - end_spot_size
        r = QtCore.QRectF(x, y, w, h)
        return r.united(self._path.controlPointRect())

    def shape(self):
        """ Shape is used for collisions and it shouldn't go over the originating node. So use
        only the last half, starting from the "knee" of the shape.
        :return:
        """
        path = QtGui.QPainterPath()
        # Bounding rect that includes the tail and end spot ellipse
        rel_sp = sub_xy(self.start_point, self.end_point)
        sx, sy = rel_sp
        sx /= 2.0
        ex, ey = 0, 0
        e2 = end_spot_size * 2
        if sx < ex:
            w = max((ex - sx + end_spot_size, e2))
            x = min((sx, ex - end_spot_size))
        else:
            w = max((sx - ex + end_spot_size, e2))
            x = ex - end_spot_size
        if sy < ey:
            h = max((ey - sy + end_spot_size, e2))
            y = min((sy, ey - end_spot_size))
        else:
            h = max((sy - ey + end_spot_size, e2))
            y = ey - end_spot_size
        r = QtCore.QRectF(x, y, w, h)
        path.addRect(r)
        return path

    def drag(self, event):
        self._dragging = True
        self.update_end_points(end_point=to_tuple(event.scenePos()))

    def update_end_points(self, end_point=None):
        """

        :param end_point: End point can be given or it can be calculated.
        """
        shape_name = ctrl.settings.get_edge_setting(
            'shape_name', edge_type=g.CONSTITUENT_EDGE)
        self._fill_path = ctrl.settings.get_shape_setting(
            'fill', edge_type=g.CONSTITUENT_EDGE)
        sx, sy = self.host.magnet(2)
        self.start_point = sx, sy
        hw_ratio = float(prefs.edge_height -
                         (ConstituentNode.height / 2)) / (prefs.edge_width
                                                          or 1)
        if not end_point:
            good_width = max((prefs.edge_width * 2,
                              self.host.width / 2 + ConstituentNode.width))
            if self._align_left:
                self.end_point = sx - good_width, sy
            else:
                self.end_point = sx + good_width, sy
        self.setPos(self.end_point[0], self.end_point[1])
        rel_sp = sub_xy(self.start_point, self.end_point)
        sx, sy = rel_sp
        ex, ey = 0, 0
        line_middle_point = sx / 2.0, sy - hw_ratio * abs(sx)
        adjust = []
        if self._align_left:
            self._path = SHAPE_PRESETS[shape_name].path(
                line_middle_point, (sx, sy),
                alignment=g.RIGHT,
                curve_adjustment=adjust)[0]
            self._path.moveTo(sx, sy)
            path2 = SHAPE_PRESETS[shape_name].path(line_middle_point, (ex, ey),
                                                   alignment=g.LEFT,
                                                   curve_adjustment=adjust)[0]
        else:
            self._path = SHAPE_PRESETS[shape_name].path(
                line_middle_point, (ex, ey),
                alignment=g.RIGHT,
                curve_adjustment=adjust)[0]
            self._path.moveTo(ex, ey)
            path2 = SHAPE_PRESETS[shape_name].path(line_middle_point, (sx, sy),
                                                   alignment=g.LEFT,
                                                   curve_adjustment=adjust)[0]
        self._path |= path2

    def drop(self, dropped_node):
        """
        Connect dropped node to host of this TouchArea.
        Connection depends on which merge area this is:
        top left, top right, left, right
        :param dropped_node:
        """
        if isinstance(dropped_node, str):
            dropped_node = self.make_node_from_string(dropped_node)
        if not dropped_node:
            return
        # host is a node
        assert isinstance(self.host, Node)
        ctrl.free_drawing.merge_to_top(self.host,
                                       dropped_node,
                                       merge_to_left=self._align_left,
                                       pos=self.start_point)
        for node in ctrl.dragged_set:
            node.adjustment = self.host.adjustment
        ctrl.forest.forest_edited()
        return 'moved node %s to sibling of %s' % (dropped_node, self.host)
Esempio n. 19
0
class RightAddInnerSibling(RightAddSibling):
    __qt_type_id__ = next_available_type_id()
Esempio n. 20
0
class CommentNode(Node):
    """ Node to display comments, annotations etc. syntactically inert information """
    __qt_type_id__ = next_available_type_id()
    width = 20
    height = 20
    node_type = g.COMMENT_NODE
    display_name = ('Comment', 'Comments')
    display = True
    is_syntactic = False
    can_be_in_groups = False
    editable = {
        'text':
        dict(name='',
             prefill='comment',
             tooltip='freeform text, invisible for processing',
             input_type='expandingtext')
    }

    default_style = {
        'fancy': {
            'color_id': 'accent4',
            'font_id': g.MAIN_FONT,
            'font-size': 14
        },
        'plain': {
            'color_id': 'accent4',
            'font_id': g.MAIN_FONT,
            'font-size': 14
        }
    }

    default_edge = g.COMMENT_EDGE

    touch_areas_when_dragging = {
        g.DELETE_ARROW: {
            'condition': 'dragging_my_arrow'
        },
        g.TOUCH_CONNECT_COMMENT: {
            'condition': 'dragging_comment'
        },
    }

    touch_areas_when_selected = {
        g.DELETE_ARROW: {
            'condition': 'has_arrow',
            'action': 'delete_arrow'
        },
        g.ADD_ARROW: {
            'action': 'start_arrow_from_node'
        }
    }

    def __init__(self, label='comment'):
        self.image_object = None
        Node.__init__(self)
        if not label:
            label = 'comment'
        self.resizable = True
        self.label = label
        self.physics_x = False
        self.physics_y = False
        self.image_path = None
        self.image = None
        self.pos_relative_to_host = -50, -50
        self.preferred_host = None

    def after_init(self):
        Node.after_init(self)
        if self.user_size:
            w, h = self.user_size
            self.set_user_size(w, h)

    @property
    def hosts(self):
        """ A comment can be associated with nodes. The association uses the general connect/disconnect mechanism, but
        'hosts' is a shortcut to get the nodes.
        :return: list of Nodes
        """
        return self.get_parents(visible=False, of_type=g.COMMENT_EDGE)

    @property
    def text(self):
        """ The text of the comment. Uses the generic node.label as storage.
        :return: str or ITextNode
        """
        return self.label

    @text.setter
    def text(self, value):
        """ The text of the comment. Uses the generic node.label as storage.
        :param value: str or ITextNode
        """
        self.label = value

    def has_arrow(self):
        return bool(self.edges_down)

    def set_image_path(self, pixmap_path):
        if pixmap_path and (self.image_path != pixmap_path or not self.image):
            self.image_path = pixmap_path
            self.image = QtGui.QPixmap()
            success = self.image.load(pixmap_path)
            if success:
                if self.image_object:
                    print('removing old image object')
                    self.image_object.hide()
                    self.image_object.setParentItem(None)
                self.image_object = QtWidgets.QGraphicsPixmapItem(
                    self.image, self)
                self.image_object.setPos(self.image.width() / -2,
                                         self.image.height() / -2)
                self.text = ''
                self.update_label_visibility()
                self.update_bounding_rect()
        else:
            self.image = None
            if self.image_object:
                print('removing old image object 2')
                self.image_object.hide()
                self.image_object.setParentItem(None)
            self.image_object = None

    def set_user_size(self, width, height):
        if width < 1 or height < 1:
            return
        self.user_size = (width, height)
        if self.image_object:
            scaled = self.image.scaled(width, height,
                                       QtCore.Qt.KeepAspectRatio,
                                       QtCore.Qt.SmoothTransformation)
            self.image_object.prepareGeometryChange()
            self.image_object.setPixmap(scaled)
            self.image_object.setPos(-scaled.width() / 2, -scaled.height() / 2)
            # Update ui items around the label (or node hosting the label)
            ctrl.ui.update_position_for(self)

        elif self.label_object:
            self.label_object.resize_label()

    def dragging_my_arrow(self):
        return True

    def __str__(self):
        return 'comment: %s' % self.text

    def update_bounding_rect(self):
        """


        :return:
        """
        if self.image_object:
            my_class = self.__class__
            if self.user_size is None:
                user_width, user_height = 0, 0
            else:
                user_width, user_height = self.user_size

            lbr = self.image_object.boundingRect()
            lbw = lbr.width()
            lbh = lbr.height()
            lbx = self.image_object.x()
            lby = self.image_object.y()
            self.label_rect = QtCore.QRectF(lbx, lby, lbw, lbh)
            self.width = max((lbw, my_class.width, user_width))
            self.height = max((lbh, my_class.height, user_height))
            y = self.height / -2
            x = self.width / -2
            self.inner_rect = QtCore.QRectF(x, y, self.width, self.height)
            w4 = (self.width - 2) / 4.0
            w2 = (self.width - 2) / 2.0
            h2 = (self.height - 2) / 2.0

            self._magnets = [(-w2, -h2), (-w4, -h2), (0, -h2), (w4, -h2),
                             (w2, -h2), (-w2, 0), (w2, 0), (-w2, h2),
                             (-w4, h2), (0, h2), (w4, h2), (w2, h2)]
            if ctrl.ui.selection_group and self in ctrl.ui.selection_group.selection:
                ctrl.ui.selection_group.update_shape()

            return self.inner_rect

        else:
            return super().update_bounding_rect()

    def paint(self, painter, option, widget=None):
        """ Painting is sensitive to mouse/selection issues, but usually with
        :param painter:
        :param option:
        :param widget:
        nodes it is the label of the node that needs complex painting """
        if self.drag_data:
            p = QtGui.QPen(self.contextual_color)
            #b = QtGui.QBrush(ctrl.cm.paper())
            #p.setColor(ctrl.cm.hover())
            p.setWidth(1)
            painter.setPen(p)
            #painter.setBrush(self.drag_data.background)
            painter.drawRect(self.inner_rect)
            painter.setBrush(QtCore.Qt.NoBrush)

        elif self._hovering:
            p = QtGui.QPen(self.contextual_color)
            #p.setColor(ctrl.cm.hover())
            p.setWidth(1)
            painter.setPen(p)
            painter.drawRect(self.inner_rect)
        elif ctrl.pressed is self or ctrl.is_selected(self):
            p = QtGui.QPen(self.contextual_color)
            p.setWidth(1)
            painter.setPen(p)
            painter.drawRect(self.inner_rect)
        elif self.has_empty_label() and self.node_alone():
            p = QtGui.QPen(self.contextual_color)
            p.setStyle(QtCore.Qt.DotLine)
            p.setWidth(1)
            painter.setPen(p)
            painter.drawRect(self.inner_rect)

    def move(self, md):
        """ Override usual movement if comment is connected to some node. If so, try to keep the
        given position relative to that node.
        :return:
        """
        if self.preferred_host:
            x, y = self.preferred_host.current_scene_position
            dx, dy = self.pos_relative_to_host
            self.current_position = self.scene_position_to_tree_position(
                (x + dx, y + dy))
            return False, False
        else:
            return super().move(md)

    def drop_to(self, x, y, recipient=None):
        """

        :param recipient:
        :param x:
        :param y:
        :return: action finished -message (str)
        """
        message = super().drop_to(x, y, recipient=recipient)
        if self.preferred_host:
            x, y = self.preferred_host.current_scene_position
            mx, my = self.current_scene_position
            self.pos_relative_to_host = mx - x, my - y
            message = "Adjusted comment to be at %s, %s relative to '%s'" % (
                mx - x, my - y, self.preferred_host)
        return message

    def on_connect(self, other):
        print('on_connect called, hosts:', self.hosts)
        if other in self.hosts:
            self.preferred_host = other
            x, y = other.current_scene_position
            mx, my = self.current_scene_position
            self.pos_relative_to_host = mx - x, my - y

    def on_disconnect(self, other):
        print('on_disconnect called')
        if other is self.preferred_host:
            for item in self.hosts:
                if item != other:
                    self.preferred_host = item
                    x, y = item.current_scene_position
                    mx, my = self.current_scene_position
                    self.pos_relative_to_host = mx - x, my - y
                    return
            self.preferred_host = None

    def can_connect_with(self, other):
        return other not in self.hosts

    def dragging_comment(self):
        """ Check if the currently dragged item is comment and can connect with me
        :return:
        """
        return self.is_dragging_this_type(g.COMMENT_NODE)

    # ############## #
    #                #
    #  Save support  #
    #                #
    # ############## #

    image_path = SavedField("image_path", if_changed=set_image_path)
Esempio n. 21
0
class LeftAddInnerSibling(LeftAddSibling):
    __qt_type_id__ = next_available_type_id()
Esempio n. 22
0
class LeftAddLeafSibling(LeftAddChild):
    __qt_type_id__ = next_available_type_id()
Esempio n. 23
0
class TouchArea(UIGraphicsItem, QtWidgets.QGraphicsObject):
    """ Mouse sensitive areas connected to either nodes or edges between
    them. """
    __qt_type_id__ = next_available_type_id()
    clicked = QtCore.pyqtSignal()

    def __init__(self, host, action):
        """
        :param ConstituentNode host:
        :param string action:
        """
        UIGraphicsItem.__init__(self, host=host)
        QtWidgets.QGraphicsObject.__init__(self)
        self._dragging = False
        self._path = None
        self.start_point = None
        self.end_point = None
        self.z_value = 160
        self.setZValue(self.z_value)
        self.status_tip = ""
        # Drawing flags defaults
        self._fill_path = False
        self._align_left = False
        self._below_node = False
        self.focusable = True
        self._visible = True
        self._hovering = False
        self._drag_hint = False
        self.setAcceptHoverEvents(True)
        self.setAcceptDrops(True)
        self.update_end_points()
        self.action = action
        self.setFlag(QtWidgets.QGraphicsObject.ItemIsSelectable)
        self.setCursor(QtCore.Qt.PointingHandCursor)
        if action:
            action.connect_element(self)
        if action and action.tip:
            self.set_tip(action.tip_with_shortcut())

    def type(self):
        """ Qt's type identifier, custom QGraphicsItems should have different type ids if events
        need to differentiate between them. These are set when the program starts.
        :return:
        """
        return self.__qt_type_id__

    def is_visible(self):
        """


        :return:
        """
        return self._visible

    def set_tip(self, tip):
        self.status_tip = tip
        if ctrl.main.use_tooltips:
            self.setToolTip(self.status_tip)

    def contextual_color(self):
        if self._hovering:
            return ctrl.cm.hovering(ctrl.cm.ui())
        else:
            return ctrl.cm.ui()
            #return ctrl.cm.ui_tr()

    def boundingRect(self):
        """


        :return:
        """
        if not self.end_point:
            self.update_end_points()
            assert self.end_point
        # Just the bounding rect of end spot ellipse
        return QtCore.QRectF(-end_spot_size, -end_spot_size,
                             end_spot_size + end_spot_size,
                             end_spot_size + end_spot_size)

    def sensitive_area(self):
        """


        :return:
        """
        return self.boundingRect()

    def update_position(self):
        """


        """
        pass
        # if not self in ctrl.dragged:
        self.update_end_points()

    def drag(self, event):
        self._dragging = True
        ep = to_tuple(event.scenePos())
        self.end_point = ep
        self.start_point = ep
        self.setPos(ep[0], ep[1])
        self._path = None

    def update_end_points(self):
        """ """
        if not self.host:
            return
        if isinstance(self.host, Edge):
            x, y = self.host.end_point
            self.end_point = x, y
        else:
            x, y = self.host.current_scene_position
        self.end_point = x, y
        self.start_point = self.end_point
        self.setPos(self.end_point[0], self.end_point[1])
        self._path = None

    def __repr__(self):
        return '<toucharea %s>' % self.ui_type

    def remove(self):
        """ remove item from the scene but otherwise keep it intact """
        sc = self.scene()
        if sc:
            sc.removeItem(self)

    def make_node_from_string(self, string):
        """ Try to create a node from given string
        :param string: str
        :return:
        """
        command_identifier, *args = string.split(':')
        if command_identifier == 'kataja' and args:
            command, *args = args
            if command == "new_node":
                node_type = args[0]
                try:
                    node_type = int(node_type)
                except TypeError:
                    pass
                if hasattr(self.host, 'current_position'):
                    x, y = self.host.current_scene_position
                elif hasattr(self.host, 'start_point'):
                    x, y = self.host.start_point
                else:
                    return
                if hasattr(self.host, 'height'):
                    h = self.host.height
                else:
                    h = 0
                pos = QtCore.QPointF(x, y + h)
                return ctrl.free_drawing.create_node(pos=pos,
                                                     node_type=node_type)
            else:
                print('received unknown command:', command, args)
        else:
            print('received just some string: ', string)

    def mousePressEvent(self, event):
        ctrl.press(self)
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if ctrl.pressed is self:
            if ctrl.dragged_set or (
                    event.buttonDownScenePos(QtCore.Qt.LeftButton) -
                    event.scenePos()).manhattanLength() > 6:
                self.drag(event)
                ctrl.graph_scene.dragging_over(event.scenePos())

    def mouseReleaseEvent(self, event):
        if ctrl.pressed is self:
            ctrl.release(self)
            if ctrl.dragged_set:
                self._dragging = False
                ctrl.graph_scene.kill_dragging()
                ctrl.ui.update_selections(
                )  # drag operation may have changed visible affordances
                ctrl.main.action_finished()  # @UndefinedVariable
            else:  # This is regular click on 'pressed' object

                self.click(event)
                self.update()
            return None  # this mouseRelease is now consumed
        super().mouseReleaseEvent(event)

    def click(self, event=None):
        """
        :type event: QMouseEvent
         """
        self._dragging = False
        if self._drag_hint:
            return False
        ctrl.deselect_objects()
        self.clicked.emit()
        return True

    # self, N, R, merge_to_left, new_node_pos, merger_node_pos):

    def calculate_if_can_merge(self, dragged, top, node_list):
        """

        :param dragged:
        :param top:
        :param node_list:
        :return:
        """
        host = self.host
        if host is ctrl.dragged_focus:
            return False
        elif host in ctrl.dragged_set:
            return False
        elif host is ctrl.pressed:
            return False
        return True

    def dragged_over_by(self, dragged):
        """

        :param dragged:
        """
        if ctrl.drag_hovering_on is self:
            self.hovering = True
            return True
        elif self.accepts_drops(dragged):
            ctrl.set_drag_hovering(self)
            return True
        else:
            return False

    def accepts_drops(self, dragged):
        """

        :param dragged:
        :return:
        """
        return self.calculate_if_can_merge(dragged, None, None)

    @property
    def hovering(self):
        """


        :return:
        """
        return self._hovering

    @hovering.setter
    def hovering(self, value):
        """

        :param value:
        """
        if value and not self._hovering:
            self._hovering = True
            ctrl.set_status(self.status_tip)
            self.setZValue(1000)

        elif (not value) and self._hovering:
            self._hovering = False
            ctrl.remove_status(self.status_tip)
            self.setZValue(self.z_value)

        self.update()

    def hoverEnterEvent(self, event):
        """

        :param event:
        """
        if (not self._hovering) and not ctrl.pressed:
            self.hovering = True
        QtWidgets.QGraphicsObject.hoverEnterEvent(self, event)

    def hoverLeaveEvent(self, event):
        """

        :param event:
        """
        if self._hovering:
            self.hovering = False
        QtWidgets.QGraphicsObject.hoverLeaveEvent(self, event)

    def dragEnterEvent(self, event):
        self.dragged_over_by(event.mimeData().text())
        event.accept()
        QtWidgets.QGraphicsObject.dragEnterEvent(self, event)

    def dragLeaveEvent(self, event):
        self.hovering = False
        event.accept()
        QtWidgets.QGraphicsObject.dragLeaveEvent(self, event)

    def dropEvent(self, event):
        self.hovering = False
        event.accept()
        message = self.drop(event.mimeData().text())
        QtWidgets.QGraphicsObject.dropEvent(self, event)
        ctrl.main.action_finished(message)
Esempio n. 24
0
class MergeToTop(BranchingTouchArea):
    """ TouchArea that connects to nodes and has \-shape.  """
    __qt_type_id__ = next_available_type_id()

    def __init__(self, host, action):
        super().__init__(host, action)
        self._align_left = True

    def update_end_points(self, end_point=None):
        """

        :param end_point: End point can be given or it can be calculated.
        """

        sx, sy = self.host.magnet(0)
        self.start_point = sx, sy
        if end_point:
            self.end_point = end_point
        else:
            ex = sx - 20  # 75
            ey = sy - 10
            self.end_point = ex, ey
        self.setPos(self.end_point[0], self.end_point[1])

    def paint(self, painter, option, widget):
        """

        :param painter:
        :param option:
        :param widget:
        :raise:
        """
        if ctrl.pressed is self:
            pass
        c = self.contextual_color()
        painter.setPen(c)
        dx = self.start_point[0] - self.end_point[0]
        dy = self.start_point[1] - self.end_point[1]
        l = QtCore.QLineF(dx, dy, 0, 0)

        if self._hovering:
            if len(self.host.trees) != 1:
                painter.drawLine(l)
                painter.save()
                painter.setBrush(ctrl.cm.ui())
                painter.rotate(20)
                draw_leaf(painter, 0, end_spot_size / 2, end_spot_size)
                painter.restore()
                draw_plus(painter, 4, 0)
                return
            else:
                top = list(self.host.trees)[0].top
                lmx, lmy = top.magnet(5)
                scene_point = QtCore.QPointF(lmx, lmy)
                end_point = self.mapFromScene(scene_point)
                path = QtGui.QPainterPath(QtCore.QPointF(dx, dy))
                path.quadTo(QtCore.QPointF(end_point.x() - 200, end_point.y()),
                            end_point)
                painter.drawPath(path)
                l = QtCore.QLineF(
                    QtCore.QPointF(end_point.x() - 200, end_point.y()),
                    end_point)
        else:
            painter.drawLine(l)

        l2x = l.p2().x()
        l2 = l.p2()
        l2y = l.p2().y()
        head_size = 8.0
        back = head_size / -2
        # Draw the arrows if there's enough room.
        ll = l.length()
        if ll >= 1 and ll + back > 0:
            angle = math.acos(l.dx() / ll)  # acos has to be <= 1.0
        else:
            return
        prop = back / ll
        if l.dy() >= 0:
            angle = (math.pi * 2.0) - angle
        destArrowP1 = QtCore.QPointF(
            (math.sin(angle - math.pi / 3) * head_size) + l2x,
            (math.cos(angle - math.pi / 3) * head_size) + l2y)
        destArrowP2 = QtCore.QPointF(
            (math.sin(angle - math.pi + math.pi / 3) * head_size) + l2x,
            (math.cos(angle - math.pi + math.pi / 3) * head_size) + l2y)
        l2c = QtCore.QPointF(l.dx() * prop + l2x, l.dy() * prop + l2y)
        painter.setBrush(c)
        painter.drawPolygon(
            QtGui.QPolygonF([l2, destArrowP1, l2c, destArrowP2]))

    def drop(self, dropped_node):
        """
        Connect dropped node to host of this TouchArea.
        Connection depends on which merge area this is:
        top left, top right, left, right
        :param dropped_node:
        """
        pass

    def accepts_drops(self, dragged):
        return False
Esempio n. 25
0
class Tree(Movable):
    """ Container for nodes that form a single trees. It allows operations that affect
    all nodes in one trees, e.g. translation of position.
    :param top:
    """
    __qt_type_id__ = next_available_type_id()
    display_name = ('Tree', 'Trees')

    def __init__(self, top=None, numeration=False):
        Movable.__init__(self)
        self.top = top
        if is_constituent(top):
            self.sorted_constituents = [top]
        else:
            self.sorted_constituents = []
        if top:
            self.sorted_nodes = [top]
        else:
            self.sorted_nodes = []
        self.numeration = numeration
        self.current_position = 100, 100
        self.drag_data = None
        self.tree_changed = True
        self._cached_bounding_rect = None
        self.setZValue(100)

    def __repr__(self):
        if self.numeration:
            suffix = " (numeration)"
        else:
            suffix = ''
        return "Tree '%s' and %s nodes.%s" % (self.top, len(
            self.sorted_nodes), suffix)

    def __contains__(self, item):
        return item in self.sorted_nodes

    def after_init(self):
        self.recalculate_top()
        self.update_items()
        self.announce_creation()

    def after_model_update(self, updated_fields, transition_type):
        """ Compute derived effects of updated values in sensible order.
        :param updated_fields: field keys of updates
        :param transition_type: 0:edit, 1:CREATED, -1:DELETED
        :return: None
        """
        super().after_model_update(updated_fields, transition_type)
        if transition_type != g.DELETED:
            self.update_items()

    def rebuild(self):
        self.recalculate_top()
        self.update_items()

    def recalculate_top(self):
        """ Verify that self.top is the topmost element of the trees. Doesn't handle consequences,
        e.g. it may now be that there are two identical trees at the top and doesn't update the
        constituent and node lists.
        :return: new top
        """
        passed = set()

        def walk_to_top(node):
            """ Recursive walk upwards
            :param node:
            :return:
            """
            passed.add(node)
            for parent in node.get_parents(similar=False, visible=False):
                if parent not in passed:
                    return walk_to_top(parent)
            return node

        if self.top:  # hopefully it is a short walk
            self.top = walk_to_top(self.top)
        elif self.sorted_nodes:  # take the long way if something strange has happened to top
            self.top = walk_to_top(self.sorted_nodes[-1])
        else:
            self.top = None  # hopefully this trees gets deleted.
        return self.top

    def is_empty(self):
        """ Empty trees should be deleted when found
        :return:
        """
        return bool(self.top)

    def is_valid(self):
        """ If trees top has parents, the trees needs to be recalculated or it is otherwise unusable
          before fixed.
        :return:
        """
        return not self.top.get_parents(similar=False, visible=False)

    def add_node(self, node):
        """ Add this node to given trees and possibly set it as parent for this graphicsitem.
        :param node: Node
        :return:
        """
        node.poke('trees')
        node.trees.add(self)
        node.update_graphics_parent()

    def remove_node(self, node, recursive_down=False):
        """ Remove node from trees and remove the (graphicsitem) parenthood-relation.
        :param node: Node
        :param recursive_down: bool -- do recursively remove child nodes from tree too
        :return:
        """
        if self in node.trees:
            node.poke('trees')
            node.trees.remove(self)
            node.update_graphics_parent()
        if recursive_down:
            for child in node.get_children(similar=False, visible=False):
                legit = False
                for parent in child.get_parents(similar=False, visible=False):
                    if self in parent.trees:
                        legit = True
                if not legit:
                    self.remove_node(child, recursive_down=True)

    def add_to_numeration(self, node):
        def add_children(node):
            if node not in self.sorted_nodes:
                self.sorted_nodes.append(node)
                if self not in node.trees:
                    self.add_node(node)
                for child in node.get_children(similar=False, visible=False):
                    if child:  # undoing object creation may cause missing edge ends
                        add_children(child)

        add_children(node)

    @time_me
    def update_items(self):
        """ Check that all children of top item are included in this trees and create the sorted
        lists of items. Make sure there is a top item before calling this!
        :return:
        """
        if self.numeration:
            to_be_removed = set()
            for item in self.sorted_nodes:
                for tree in item.trees:
                    if tree is not self:
                        to_be_removed.add(item)
                        break
            for item in to_be_removed:
                self.remove_node(item)
            return
        sorted_constituents = []
        sorted_nodes = []
        used = set()

        def add_children(node):
            """ Add node to this trees.
            :param node:
            :return:
            """
            if node not in used:
                used.add(node)
                if is_constituent(node):
                    sorted_constituents.append(node)
                sorted_nodes.append(node)
                if self not in node.trees:
                    self.add_node(node)
                for child in node.get_children(similar=False, visible=False):
                    if child:  # undoing object creation may cause missing edge ends
                        add_children(child)

        old_nodes = set(self.sorted_nodes)

        if is_constituent(self.top):
            add_children(self.top)

        self.sorted_constituents = sorted_constituents
        for i, item in enumerate(self.sorted_constituents):
            item.z_value = 10 + i
            item.setZValue(item.z_value)
        self.sorted_nodes = sorted_nodes

        to_be_removed = old_nodes - set(sorted_nodes)
        for item in to_be_removed:
            self.remove_node(item)

    def is_higher_in_tree(self, node_a, node_b):
        """ Compare two nodes, if node_a is higher, return True. Return False
        if not.
            Return None if nodes are not in the same trees -- cannot compare.
            (Be careful with the result,
            handle None and False differently.)
        :param node_a:
        :param node_b:
        :return:
        """
        if node_a in self and node_b in self:
            return self.sorted_nodes.index(node_a) < self.sorted_nodes.index(
                node_b)
        else:
            return None

    def start_dragging_tracking(self, host=False, scene_pos=None):
        """ Add this *Tree* to entourage of dragged nodes. These nodes will
        maintain their relative position to drag pointer while dragging.
        :return: None
        """
        self.drag_data = TreeDragData(self,
                                      is_host=host,
                                      mousedown_scene_pos=scene_pos)

    def boundingRect(self):
        if self.tree_changed or not self._cached_bounding_rect:
            if not self.sorted_nodes:
                return QtCore.QRectF()
            min_x, min_y = 10000, 10000
            max_x, max_y = -10000, -10000
            for node in self.sorted_nodes:
                if node.is_visible() and not node.locked_to_node:
                    nbr = node.future_children_bounding_rect()
                    if node.physics_x or node.physics_y:
                        x, y = node.x(), node.y()
                    else:
                        x, y = node.target_position
                    x1, y1, x2, y2 = nbr.getCoords()
                    x1 += x
                    y1 += y
                    x2 += x
                    y2 += y
                    if x1 < min_x:
                        min_x = x1
                    if x2 > max_x:
                        max_x = x2
                    if y1 < min_y:
                        min_y = y1
                    if y2 > max_y:
                        max_y = y2
            self._cached_bounding_rect = QtCore.QRectF(min_x, min_y,
                                                       max_x - min_x,
                                                       max_y - min_y)
            self.tree_changed = False
            return self._cached_bounding_rect
        else:
            return self._cached_bounding_rect

    def current_scene_bounding_rect(self):
        if not self.sorted_nodes:
            return QtCore.QRectF()
        min_x, min_y = 10000, 10000
        max_x, max_y = -10000, -10000
        for node in self.sorted_nodes:
            if node.is_visible():
                nbr = node.sceneBoundingRect()
                x1, y1, x2, y2 = nbr.getCoords()
                if x1 < min_x:
                    min_x = x1
                if x2 > max_x:
                    max_x = x2
                if y1 < min_y:
                    min_y = y1
                if y2 > max_y:
                    max_y = y2
        return QtCore.QRectF(min_x, min_y, max_x - min_x, max_y - min_y)

    # def normalize_positions(self):
    #     print('tree normalising positions')
    #     tx, ty = self.top.target_position
    #     for node in self.sorted_constituents:
    #         nx, ny = node.target_position
    #         node.move_to(nx - tx, ny - ty)

    def paint(self, painter, QStyleOptionGraphicsItem, QWidget_widget=None):
        if self.numeration:  # or True:
            br = self.boundingRect()
            painter.drawRect(br)
            #painter.drawText(br.topLeft() + QtCore.QPointF(2, 10), str(self))

    top = SavedField("top")
Esempio n. 26
0
class RightAddUnaryChild(RightAddChild):
    __qt_type_id__ = next_available_type_id()
Esempio n. 27
0
class LeftAddUnaryChild(LeftAddChild):
    __qt_type_id__ = next_available_type_id()
Esempio n. 28
0
class UIEmbed(UIWidget, QtWidgets.QWidget):
    """ UIEmbeds are UI elements that are drawn on the main graphics view: they
    are contextual panels that need more UI-capabilities like focus, selection
    and text editing than would be practical to do with GraphicsItems.
    The benefits of UIEmbeds are that these do not scale with graphicsitems,
    and these are styled automatically from palette, as long as they get
    updated properly. These are also not counted in GraphicsScene, so these
    won't cause additional code there.

    UIEmbed implements the basic functions of all embeds: showing them,
    updating their positions, close buttons, updating colors. The approach is
    similar to UIPanels.

    :param parent:
    :param ui_manager:
    """
    __qt_type_id__ = next_available_type_id()
    unique = True

    def __init__(self, parent, host, text):
        UIWidget.__init__(self, host)
        QtWidgets.QWidget.__init__(self, parent)
        self._palette = None
        self.update_colors()
        self._drag_diff = None
        self.moved_by_hand = False

        self.top_row_layout = QtWidgets.QHBoxLayout()
        #close_button = QtWidgets.QPushButton("x")
        close_button = PanelButton(pixmap=qt_prefs.close_icon,
                                   tooltip='Close',
                                   parent=self,
                                   size=12,
                                   color_key='content1')
        close_button.setMaximumWidth(16)
        self.ui_manager.connect_element_to_action(close_button, 'close_embed')
        self.top_row_layout.addWidget(close_button)
        self.top_row_layout.setAlignment(QtCore.Qt.AlignLeft)
        self.top_row_layout.addSpacing(8)
        self.top_title = QtWidgets.QLabel(text)
        self.top_row_layout.addWidget(self.top_title)
        self.assumed_width = 300
        self.assumed_height = 100
        self._magnet = QtCore.QPoint(0, 0), 1
        # Effect will be disabled if QTextEdit is used.
        self.setAutoFillBackground(True)
        self.setBackgroundRole(QtGui.QPalette.Window)
        self.hide()
        # Remember to add top_row_layout to your layout

    def type(self):
        """ Qt's type identifier, custom QGraphicsItems should have different type ids if events
        need to differentiate between them. These are set when the program starts.
        :return:
        """
        return self.__qt_type_id__

    @property
    def graphic_item(self):
        """ if this _Widget_ has UI graphic items associated in scene, e.g. target reticles or so.
        :return:
        """
        return None

    def update_embed(self, focus_point=None):
        self.update_colors()
        self.update_fields()
        if focus_point:
            self.update_position(focus_point=focus_point)

    def update_size(self):
        self.setFixedSize(self.layout().minimumSize())

    def update_fields(self):
        """ Subclasses implement this if there are elements to update
        :return:
        """
        pass

    def margin_x(self):
        """ Margin around embed edit provides some empty space so that the focus point of embed
        is not occluded by the embed. e.g. if focus point=(0,0), with margins=(6,6) embed's top
        left corner would be at (6, 6) and point (0, 0) would still be visible.
        :return:
        """
        return 6

    def margin_y(self):
        """ Margin around embed edit provides some empty space so that the focus point of embed
        is not occluded by the embed. e.g. if focus point=(0,0), with margins=(6,6) embed's top
        left corner would be at (6, 6) and point (0, 0) would still be visible.
        :return:
        """
        return 6

    def update_position(self, focus_point=None):
        """ Position embedded editor to graphics view. The idea is that the embed edit shouldn't
        occlude the focus point (object being edited or the point on screen where new object will
        be created, but it should try to fit as whole into the graphics view. So this method
        computes which corner or edge of editor should be placed closest to focus point and moves
        the embed accordingly.
        :param focus_point:
        :return:
        """
        if self.moved_by_hand:
            return

        if not focus_point:
            if self.host:
                focus_point = self.host.scenePos()
            else:
                return
        self.update_size()
        ew = prefs.edge_width
        eh = prefs.edge_height
        view = ctrl.graph_view
        my_rect = self.geometry()
        w = my_rect.width()
        h = my_rect.height()
        view_rect = view.geometry()
        if self.host:
            scene_br = self.host.sceneBoundingRect()
            scene_br.adjust(-ew, -eh, ew, eh)
        elif focus_point:
            scene_br = QtCore.QRectF(-ew, -eh, ew * 2, eh * 2)
            scene_br.moveCenter(focus_point.toPoint())
        node_rect = view.mapFromScene(scene_br).boundingRect()

        # do nothing if we already have a good enough position. For user it is better if the
        # panel stays in place than if it jumps around.
        if view_rect.contains(
                my_rect, proper=True) and not node_rect.intersects(my_rect):
            return

        ncy = node_rect.center().y()

        if node_rect.right() + w < view_rect.right():
            if node_rect.right() + w + 50 < view_rect.right():
                x = node_rect.right() + 50
            else:
                x = view_rect.right() - w
        else:
            if node_rect.left() - w - 50 > 4:
                x = node_rect.left() - w - 50
            else:
                x = 4
        if ncy - h / 2 > 25 and \
            ncy + h / 2 < view_rect.height():
            y = ncy - h / 2
        else:
            y = h / 2 - node_rect.height() / 2

        self.move(x, y)
        self.updateGeometry()

    def magnet(self):
        return self._magnet

    def update_colors(self):
        key = None
        if self.host and hasattr(self.host, 'get_color_id'):
            key = self.host.get_color_id()
        if key:
            self._palette = ctrl.cm.get_accent_palette(key)
            self.setPalette(self._palette)

    def wake_up(self):
        self.fade_in()
        self.raise_()
        self.focus_to_main()

    def focus_to_main(self):
        pass

    def mousePressEvent(self, event):
        self._drag_diff = event.pos()

    def mouseMoveEvent(self, event):
        self.move(self.mapToParent(event.pos()) - self._drag_diff)
        self.moved_by_hand = True
        QtWidgets.QWidget.mouseMoveEvent(self, event)

    def resizeEvent(self, event):
        QtWidgets.QWidget.resizeEvent(self, event)

    def event(self, e):
        if e.type() == QtCore.QEvent.PaletteChange:
            self.update_colors()
        return QtWidgets.QWidget.event(self, e)
Esempio n. 29
0
class ControlPoint(UIGraphicsItem, QtWidgets.QGraphicsItem):
    """

    """
    __qt_type_id__ = next_available_type_id()

    def __init__(self, edge, index=-1, role=''):
        UIGraphicsItem.__init__(self, host=edge, role=role)
        QtWidgets.QGraphicsItem.__init__(self)
        print('creating control_point, role is: ', role)
        if prefs.touch:
            self._wh = 12
            self._xy = -6
            self.round = True
            self.setCursor(Qt.PointingHandCursor)
        else:
            self._wh = 4
            self._xy = -2
            self.round = True
            self.setCursor(Qt.CrossCursor)
        self._index = index
        self.focusable = True
        self.pressed = False
        self._hovering = False
        self.being_dragged = False
        self.setAcceptHoverEvents(True)
        self.setFlag(QtWidgets.QGraphicsObject.ItemIsMovable)
        self.setFlag(QtWidgets.QGraphicsObject.ItemIsSelectable)

        self.setZValue(52)
        self._compute_position()
        self.status_tip = ""
        if self.role == g.START_POINT:
            self.status_tip = "Drag to move the starting point"
        elif self.role == g.END_POINT:
            self.status_tip = "Drag to move the ending point"
        elif self.role == g.CURVE_ADJUSTMENT:
            self.status_tip = "Drag to adjust the curvature of this line"
        elif self.role == g.LABEL_START:
            self.status_tip = "Drag along the line to adjust the anchor point of label"
            if prefs.touch:
                self._wh = 6
                self._xy = -3

        if ctrl.main.use_tooltips:
            self.setToolTip(self.status_tip)
        self.show()

    def type(self):
        """ Qt's type identifier, custom QGraphicsItems should have different type ids if events
        need to differentiate between them. These are set when the program starts.
        :return:
        """
        return self.__qt_type_id__

    def show(self):
        """ Assign as a watcher if necessary and make visible
        :return: None
        """
        if self.role == g.LABEL_START:
            ctrl.add_watcher(self, 'edge_label')

    def hide(self):
        """ Remove from watchers' list when control point is hidden
        :return: None
        """
        if self.role == g.LABEL_START:
            ctrl.remove_from_watch(self)

    def _compute_position(self):
        """
        :return:
        """
        if self.role == g.CURVE_ADJUSTMENT and self._index < len(
                self.host.adjusted_control_points):
            p = Pf(*self.host.adjusted_control_points[self._index])
        elif self.role == g.START_POINT:
            p = Pf(self.host.start_point[0], self.host.start_point[1])
        elif self.role == g.END_POINT:
            p = Pf(self.host.end_point[0], self.host.end_point[1])
        elif self.role == g.LABEL_START and self.host.label_item:
            c = self.host.label_item.get_label_start_pos()
            p = Pf(c.x(), c.y())
        else:
            return False
        self.setPos(p)
        return True

    def boundingRect(self):
        """


        :return:
        """
        return QtCore.QRectF(self._xy, self._xy, self._wh, self._wh)

    def update_position(self):
        """


        """
        ok = self._compute_position()
        self.update()
        return ok

    def _compute_adjust(self):
        x, y = to_tuple(self.pos())
        assert (self._index != -1)
        p = self.host.control_points[self._index]
        return int(x - p[0]), int(y - p[1])
        # print 'computed curve_adjustment:', self.curve_adjustment

    def _compute_adjust_from_pos(self, scene_pos):
        x, y = to_tuple(scene_pos)
        assert (self._index != -1)
        cx, cy = self.host.control_points[self._index]
        x_adjust = int(x - cx)
        y_adjust = int(y - cy)
        if self._index == 0:
            sx, sy = self.host.start_point
        else:
            sx, sy = self.host.end_point
        sx_to_cx = cx - sx
        sy_to_cy = cy - sy
        line_rad = math.atan2(sy_to_cy, sx_to_cx)
        line_dist = math.hypot(sx_to_cx, sy_to_cy)
        adj_rad = math.atan2(y_adjust, x_adjust)
        adj_dist = math.hypot(x_adjust, y_adjust)
        if line_dist != 0:
            relative_dist = adj_dist / line_dist
        else:
            relative_dist = adj_dist
        relative_rad = adj_rad - line_rad
        return relative_dist, relative_rad

    def click(self, event=None):
        """ Clicking a control point usually does nothing. These are more for dragging.
        :param event: some kind of mouse event
        :return: bool
        """
        pass
        return True  # consumes click

    def drag(self, event):
        """ Dragging a control point at least requires to update its coordinates and announcing the
        host object that things are a'changing. How this will be announced depends on control
        point's _role_.
        :param event: some kind of mouse event
        :return: None
        """
        scenepos = event.scenePos()
        if self.role == g.LABEL_START:
            d, point = self.host.get_closest_path_point(scenepos)
            self.host.label_item.label_start = d
        else:
            self.setPos(scenepos)
        if self.role == g.CURVE_ADJUSTMENT:
            rdist, rrad = self._compute_adjust_from_pos(scenepos)
            self.host.adjust_control_point(self._index, rdist, rrad)
        elif self.role == g.START_POINT:
            self.host.set_start_point(event.scenePos())
            self.host.make_path()
            self.host.update()
        elif self.role == g.END_POINT:
            self.host.set_end_point(event.scenePos())
            self.host.make_path()
            self.host.update()
        self.being_dragged = True

    def drop_to(self, x, y, recipient=None):
        """ Dragging ends, possibly by dropping over another object.
        :param x: scene x coordinate
        :param y: scene y coordinate
        :param recipient: object that receives the dropped _control point_
        """
        if recipient:
            # recipient.accept_drop(self)
            if self.role == g.START_POINT:
                self.host.connect_start_to(recipient)
            elif self.role == g.END_POINT:
                self.host.connect_end_to(recipient)

    def mousePressEvent(self, event):
        ctrl.press(self)
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if ctrl.pressed is self:
            if self.being_dragged or (
                    event.buttonDownScenePos(QtCore.Qt.LeftButton) -
                    event.scenePos()).manhattanLength() > 6:
                self.drag(event)
                ctrl.graph_scene.dragging_over(event.scenePos())

    def mouseReleaseEvent(self, event):
        if ctrl.pressed is self:
            ctrl.release(self)
            if self.being_dragged:
                x, y = to_tuple(event.scenePos())
                self.drop_to(x, y, recipient=ctrl.drag_hovering_on)
                self.being_dragged = False
                ctrl.graph_scene.kill_dragging()
                ctrl.ui.update_selections(
                )  # drag operation may have changed visible affordances
                ctrl.main.action_finished()  # @UndefinedVariable
            return None  # this mouseRelease is now consumed
        super().mouseReleaseEvent(event)

    def hoverEnterEvent(self, event):
        """ Trigger and update hover effects.
        :param event: somekind of qt mouse event?
        """
        self._hovering = True
        ctrl.set_status(self.status_tip)
        QtWidgets.QGraphicsItem.hoverEnterEvent(self, event)

    def hoverLeaveEvent(self, event):
        """ Remove hover effects for this piece.
        :param event: somekind of qt mouse event?
        """
        self._hovering = False
        ctrl.remove_status(self.status_tip)
        QtWidgets.QGraphicsItem.hoverLeaveEvent(self, event)

    def paint(self, painter, option, widget=None):
        """

        :param painter:
        :param option:
        :param widget:
        """
        cm = ctrl.cm
        if self.round:
            if self.pressed:
                p = QtGui.QPen(cm.active(cm.ui_tr()))
            elif self._hovering:
                p = QtGui.QPen(cm.hovering(cm.ui_tr()))
            else:
                p = QtGui.QPen(cm.ui_tr())

            if self.role == g.START_POINT or self.role == g.END_POINT:
                p.setWidth(4)
                painter.setPen(p)
                painter.drawEllipse(self._xy, self._xy, self._wh, self._wh)
            elif self.role == g.LABEL_START:
                p.setWidth(1)
                painter.setPen(p)
                painter.drawRect(self._xy, self._xy, self._wh, self._wh)
            else:
                p.setWidth(2)
                painter.setPen(p)
                painter.drawEllipse(self._xy, self._xy, self._wh, self._wh)
            #
            # if self.pressed:
            # painter.setBrush(cm.active(cm.ui_tr()))
            # elif self._hovering:
            # painter.setBrush(cm.hovering(cm.ui_tr()))
            # else:
            # painter.setBrush(cm.ui_tr())
            # painter.setPen(qt_prefs.no_pen)
            # painter.drawEllipse(self._xy, self._xy, self._wh, self._wh)
        else:
            if self.pressed:
                pen = cm.active(cm.selection())
            elif self._hovering:
                pen = cm.hovering(cm.selection())
            else:
                pen = cm.ui()
            painter.setPen(pen)
            painter.drawRect(self._xy, self._xy, self._wh, self._wh)

    def watch_alerted(self, obj, signal, field_name, value):
        print(obj, signal, field_name, value)
Esempio n. 30
0
class RightAddLeafSibling(RightAddChild):
    __qt_type_id__ = next_available_type_id()