Example #1
0
 def init_lower_part(self):
     self.lower_part = QtWidgets.QGraphicsTextItem(self)
     self.lower_doc = LabelDocument()
     self.lower_part.setDocument(self.lower_doc)
     self.lower_part.setTextWidth(-1)
     if self._font:
         self.lower_part.setFont(self._font)
Example #2
0
 def init_lower_part(self):
     self.lower_part = QtWidgets.QGraphicsTextItem(self)
     self.lower_doc = LabelDocument()
     self.lower_part.setDocument(self.lower_doc)
     self.lower_part.setTextWidth(-1)
     if self._font:
         self.lower_part.setFont(self._font)
Example #3
0
 def __init__(self, parent=None):
     """ Give node as parent. Label asks it to produce text to show here """
     QtWidgets.QGraphicsItem.__init__(self, parent)
     self.editable_part = QtWidgets.QGraphicsTextItem(self)
     self.lower_part = None  # QtWidgets.QGraphicsTextItem(self)
     self._host = parent
     self.has_been_initialized = False
     self.top_y = 0
     self.upper_part_y = 0
     self.lower_part_y = 0
     self.bottom_y = 0
     self.draw_triangle = False
     self.triangle_height = 20
     self.triangle_width = 20
     self.triangle_y = 0
     self.width = 0
     self.height = 0
     self.template_width = 0
     self.x_offset = 0
     self.y_offset = 0
     self.text_align = CENTER_ALIGN
     self.label_shape = NORMAL
     self._font = None
     self.editable_html = ''
     self.lower_html = ''
     self.edited_field = ''
     self._quick_editing = False
     self._recursion_block = False
     self._last_blockpos = ()
     self._previous_values = None
     self.editable = {}
     self.prepare_template()  # !<----
     self.editable_doc = LabelDocument()
     self.lower_doc = None
     self._fresh_focus = False
     self.editable_part.setDocument(self.editable_doc)
     # not acceptin hover events is important, editing focus gets lost if other labels take
     # hover events. It is unclear why.
     self.setAcceptDrops(False)
     self.setAcceptHoverEvents(False)
     self.editable_doc.contentsChanged.connect(self.editable_doc_changed)
     self.editable_part.setTextWidth(-1)
     self.set_font(self._host.get_font())
Example #4
0
 def __init__(self, parent=None):
     """ Give node as parent. Label asks it to produce text to show here """
     QtWidgets.QGraphicsItem.__init__(self, parent)
     self.editable_part = QtWidgets.QGraphicsTextItem(self)
     self.lower_part = None # QtWidgets.QGraphicsTextItem(self)
     self._host = parent
     self.has_been_initialized = False
     self.top_y = 0
     self.upper_part_y = 0
     self.lower_part_y = 0
     self.bottom_y = 0
     self.triangle_is_present = False
     self.triangle_height = 20
     self.triangle_y = 0
     self.width = 0
     self.height = 0
     self.template_width = 0
     self.x_offset = 0
     self.y_offset = 0
     self.text_align = CENTER_ALIGN
     self.label_shape = NORMAL
     self._font = None
     self.html = ''
     self.lower_html = ''
     self.edited_field = ''
     self.editable_html = ''
     self._quick_editing = False
     self._recursion_block = False
     self._last_blockpos = ()
     self._previous_values = None
     self.editable = {}
     self.prepare_template()
     self.editable_doc = LabelDocument()
     self.lower_doc = None
     self._fresh_focus = False
     self.editable_part.setDocument(self.editable_doc)
     # not acceptin hover events is important, editing focus gets lost if other labels take
     # hover events. It is unclear why.
     self.setAcceptDrops(False)
     self.setAcceptHoverEvents(False)
     self.editable_doc.contentsChanged.connect(self.editable_doc_changed)
     self.editable_part.setTextWidth(-1)
     self.set_font(self._host.get_font())
Example #5
0
class Label(QtWidgets.QGraphicsItem):
    """ Labels are names of nodes. Node itself provides a template for what to show in label,
    label composes its document (html layout) for its contents based on that. """
    max_width = 400
    __qt_type_id__ = next_available_type_id()
    card_size = (60, 90)

    def __init__(self, parent=None):
        """ Give node as parent. Label asks it to produce text to show here """
        QtWidgets.QGraphicsItem.__init__(self, parent)
        self.editable_part = QtWidgets.QGraphicsTextItem(self)
        self.lower_part = None # QtWidgets.QGraphicsTextItem(self)
        self._host = parent
        self.has_been_initialized = False
        self.top_y = 0
        self.upper_part_y = 0
        self.lower_part_y = 0
        self.bottom_y = 0
        self.triangle_is_present = False
        self.triangle_height = 20
        self.triangle_y = 0
        self.width = 0
        self.height = 0
        self.template_width = 0
        self.x_offset = 0
        self.y_offset = 0
        self.text_align = CENTER_ALIGN
        self.label_shape = NORMAL
        self._font = None
        self.html = ''
        self.lower_html = ''
        self.edited_field = ''
        self.editable_html = ''
        self._quick_editing = False
        self._recursion_block = False
        self._last_blockpos = ()
        self._previous_values = None
        self.editable = {}
        self.prepare_template()
        self.editable_doc = LabelDocument()
        self.lower_doc = None
        self._fresh_focus = False
        self.editable_part.setDocument(self.editable_doc)
        # not acceptin hover events is important, editing focus gets lost if other labels take
        # hover events. It is unclear why.
        self.setAcceptDrops(False)
        self.setAcceptHoverEvents(False)
        self.editable_doc.contentsChanged.connect(self.editable_doc_changed)
        self.editable_part.setTextWidth(-1)
        self.set_font(self._host.get_font())

    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 __str__(self):
        return 'Label:' + self.html + self.lower_html

    def init_lower_part(self):
        self.lower_part = QtWidgets.QGraphicsTextItem(self)
        self.lower_doc = LabelDocument()
        self.lower_part.setDocument(self.lower_doc)
        self.lower_part.setTextWidth(-1)
        if self._font:
            self.lower_part.setFont(self._font)

    def remove_lower_part(self):
        self.lower_part.setParentItem(None)
        self.lower_part.setParent(None)
        self.lower_part = None
        self.lower_doc = None

    def set_font(self, font):
        self.editable_part.setFont(font)
        if self.lower_part:
            self.lower_part.setFont(font)
        self._font = font

    def update_font(self):
        self.set_font(self._host.get_font())

    def update_label(self, force_update=False):
        """ Asks for node/host to give text and update if changed """
        force_update = True
        self.has_been_initialized = True
        is_card = self.is_card()
        if self.text_align == LEFT_ALIGN:
            self.editable_doc.set_align(QtCore.Qt.AlignLeft)
        elif self.text_align == RIGHT_ALIGN:
            self.editable_doc.set_align(QtCore.Qt.AlignRight)
        else:
            self.editable_doc.set_align(QtCore.Qt.AlignHCenter)
        html, lower_html = self._host.compose_html_for_viewing()
        #if html in ['<', '>', '&']:
        #    html = escape(html)
        #if lower_html in ['<', '>', '&']:
        #    lower_html = escape(lower_html)

        if self.label_shape == SCOPEBOX:
            if not self._host.is_leaf(only_similar=True, only_visible=True):
                html = '<sub>' + html + '</sub>'
            if lower_html:
                html += lower_html.replace('<br/>', '')
        elif self.label_shape == BRACKETED:
            if not self._host.is_leaf(only_similar=True, only_visible=True):
                html = '[<sub>' + html + '</sub>'
            if lower_html:
                html += lower_html.replace('<br/>', '')
        if force_update or (self.label_shape, html, lower_html, is_card) != self._previous_values:
            if self.html != html:
                if is_card:
                    self.editable_doc.setTextWidth(self.card_size[0])
                else:
                    self.editable_doc.setTextWidth(-1)
                self.html = html
                self.editable_part.setHtml(html)
            ctrl.qdocument_parser.process(self.editable_doc)
            if lower_html and self.label_shape not in [g.SCOPEBOX, g.BRACKETED]:
                if not self.lower_part:
                    self.init_lower_part()
                if lower_html != self.lower_html:
                    self.lower_html = lower_html
                    if is_card:
                        self.lower_doc.setTextWidth(self.card_size[0])
                    else:
                        self.lower_doc.setTextWidth(-1)
                    self.lower_part.setHtml(self.lower_html)
                ctrl.qdocument_parser.process(self.lower_doc)
            else:
                self.lower_html = ''
                if self.lower_part:
                    self.remove_lower_part()
            self._previous_values = (self.label_shape, self.html, self.lower_html, is_card)
        self.resize_label()

    def is_card(self):
        return self.label_shape == CARD and \
               (inner_cards or
                self._host.triangle or
                self._host.is_leaf(only_similar=True, only_visible=True))

    def left_bracket_width(self):
        return self.width

    def right_bracket_width(self) -> int:
        if self.label_shape == BRACKETED:
            return 6
        elif self.label_shape == SCOPEBOX:
            return 2
        else:
            return 0

    def prepare_template(self):
        my_class = self._host.__class__
        if self._host.syntactic_object:
            synclass = self._host.syntactic_object.__class__
            syn_editable = getattr(synclass, 'editable', {})
            self.editable = combine_dicts(syn_editable, my_class.editable)
        else:
            self.editable = my_class.editable

    def is_empty(self) -> bool:
        """ Turning this node into label would result in an empty label.
        :return: bool
        """
        return not (self.html or self.lower_html)


    def cursor(self):
        return self.editable_part.textCursor()

    def char_format(self) -> QtGui.QTextCharFormat:
        return self.editable_part.textCursor().charFormat()

    def has_content(self) -> bool:
        return bool(self.html or self.lower_html)

    def get_top_y(self) -> int:
        return self.y_offset

    def get_lower_part_y(self) -> int:
        return self.y_offset + self.lower_part_y

    def release_editor_focus(self):
        self.set_quick_editing(False)

    def is_quick_editing(self) -> bool:
        return self._quick_editing

    def set_quick_editing(self, value):
        """  Toggle quick editing on and off for this label. Quick editing is toggled on when
        the label is clicked or navigated into with the keyboard. The label and its editor takes
        focus and many things behave differently while editing, so please keep make sure that
        quick editing is switched off properly, using this method.
        :param value:
        :return:
        """
        if value:
            if self._quick_editing:
                return
            self._quick_editing = True
            self._host.update_label_visibility()
            if ctrl.text_editor_focus:
                ctrl.release_editor_focus()
            ctrl.text_editor_focus = self
            self.editable_part.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction)
            self.prepareGeometryChange()
            if self.is_card():
                self.editable_doc.setTextWidth(self.card_size[0])
            else:
                self.editable_doc.setTextWidth(-1)
            self.edited_field, self.editable_html = self._host.compose_html_for_editing()
            self.editable_html = self.editable_html.replace('\n', '<br/>')
            self.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor))

            ctrl.ui.add_quick_edit_buttons_for(self._host, self.editable_doc)
            self.editable_part.setHtml(self.editable_html)
            self.editable_doc.cursorPositionChanged.connect(self.cursor_position_changed)

            self.resize_label()
            self.editable_part.setAcceptDrops(True)
            ctrl.graph_view.setFocus()
            self.editable_part.setFocus()
            self._fresh_focus = True

        elif self._quick_editing:
            if self.editable_doc.isModified():
                self.parse_document_to_field()
                self.editable_doc.setModified(False)
            ctrl.text_editor_focus = None
            self._quick_editing = False
            ctrl.ui.remove_quick_edit_buttons()
            self._host.update_label()
            self.editable_part.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
            self.editable_part.setAcceptDrops(False)
            self.editable_part.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
            self.editable_part.clearFocus()
            self._fresh_focus = False
            #if len(fields) == 0:
            #    ctrl.main.action_finished("Finished editing %s, no changes." % self._host,
            #                              undoable=False)
            #elif len(fields) == 1:
            #    ctrl.main.action_finished("Edited field %s in %s" % (fields[0], self._host))
            #else:
            #    ctrl.main.action_finished("Edited fields %s in %s" % (str(fields), self._host))


    def cursor_position_changed(self, cursor):
        if self._quick_editing:
            ctrl.ui.quick_edit_buttons.update_formats(cursor.charFormat())

    def parse_document_to_field(self):
        """ Parse edited QDocument into rows of INodes and into receptable
        field in host object

        :return:
        """
        parsed_parts = ctrl.qdocument_parser.process(self.editable_doc)
        my_editable = self.editable.get(self.edited_field, {})
        setter = my_editable.get('setter', '')
        if setter:
            setter_method = getattr(self._host, setter, None)
            if setter_method and callable(setter_method):
                setter_method(parsed_parts)
            else:
                print('missing setter!')
        else:
            setattr(self._host, self.edited_field, parsed_parts)

    def editable_doc_changed(self):
        if self._recursion_block or not (ctrl.forest.in_display and ctrl.forest.is_parsed):
            return
        w = self.width
        self._recursion_block = True
        self.resize_label()
        self._host.update_bounding_rect()
        if self.width != w and self.scene() == ctrl.graph_scene:
            ctrl.forest.draw()
        self._recursion_block = False

    def get_max_size_from_host(self):
        if self._host.resizable and self._host.user_size is not None:
            return self._host.user_size
        else:
            return 0, 0

    def resize_label(self):
        self.prepareGeometryChange()
        # Width
        user_width, user_height = self.get_max_size_from_host()
        if self.is_card():
            width = self.card_size[0]
        else:
            if self.lower_html:
                self.editable_part.setTextWidth(-1)
                self.lower_part.setTextWidth(-1)
                ideal_width = max((self.editable_doc.idealWidth(), self.lower_doc.idealWidth()))
            else:
                self.editable_part.setTextWidth(-1)
                ideal_width = self.editable_doc.idealWidth()

            if user_width and user_width < ideal_width:
                width = user_width
            elif self.template_width:
                width = self.template_width
            else:
                width = ideal_width
            if width < 20:
                width = 20
            elif width > Label.max_width:
                width = Label.max_width
        self.editable_part.setTextWidth(width)
        if self.lower_html:
            self.lower_part.setTextWidth(width)

        # Height
        self.triangle_is_present = self._host.triangle \
                                   and self.label_shape not in [g.SCOPEBOX, g.CARD, g.BRACKETED]
        if self.is_card():
            total_height = self.card_size[1]
        elif self.triangle_is_present:
            if self.editable_html:
                eh = self.editable_doc.size().height()
            else:
                eh = 0
            if self.lower_html:
                lh = self.lower_doc.size().height()
            else:
                lh = 0
            total_height = eh + self.triangle_height + lh
        else:
            total_height = self.editable_doc.size().height()
        half_height = total_height / 2.0
        self.top_y = -half_height
        self.bottom_y = half_height
        self.width = width
        self.height = total_height
        # middle line is 0
        if self.triangle_is_present:
            if self._host.is_leaf(only_visible=False, only_similar=True):
                # if triangled is leaf, put upper part below the triangle so it can be edited.
                self.upper_part_y = self.triangle_height
                self.triangle_y = 0
                self.lower_part_y = self.triangle_height
            else:
                # if triangled is not leaf, editing should target the upper part and leave
                # the combination of leaves alone
                self.upper_part_y = 0
                if self.editable_html:
                    self.triangle_y = self.editable_doc.size().height()
                else:
                    self.triangle_y = 0
                self.lower_part_y = self.triangle_y + self.triangle_height
        elif self.label_shape == g.CARD and self._host.triangle:
            # no lower part, no triangle
            self.upper_part_y = 0
            self.triangle_y = 0
            self.lower_part_y = self.editable_doc.size().height()
            # reduce font size until it fits
            if self.lower_part and self.lower_html:
                font = self.lower_part.font()
                use_point_size = font.pointSize() != -1
                if use_point_size:
                    fsize = font.pointSize()
                else:
                    fsize = font.pixelSize()
                attempts = 0
                while fsize > 5 and attempts < 10 and self.lower_part_y + \
                        self.lower_doc.size().height() > total_height:
                    fsize -= 1
                    if use_point_size:
                        font.setPointSize(fsize)
                    else:
                        font.setPixelSize(fsize)
                    self.lower_part.setFont(font)
                    attempts += 1
        else:
            # no lower part, no triangle
            self.upper_part_y = 0
            self.triangle_y = 0
            self.lower_part_y = 0

        self.x_offset = width / -2.0
        if self.is_card():
            self.y_offset = self.upper_part_y
        else:
            self.y_offset = -half_height

        self.setPos(self.x_offset, self.y_offset)
        self.editable_part.setPos(0, self.upper_part_y)
        if self.lower_html:
            self.lower_part.setPos(0, self.lower_part_y)

        # Update ui items around the label (or node hosting the label)
        ctrl.ui.update_position_for(self._host)

    def dropEvent(self, event):
        mim = event.mimeData()
        if mim.hasFormat("application/x-qabstractitemmodeldatalist"):
            event.accept()
            data = open_symbol_data(event.mimeData())
            if data and 'char' in data:
                self.editable_part.textCursor().insertText(data['char'])
                event.acceptProposedAction()
        elif mim.hasFormat("text/plain"):
            event.accept()
            event.acceptProposedAction()
            self.editable_part.dropEvent(event)
        else:
            self.editable_part.dropEvent(event)

    def boundingRect(self):
        if self.is_card():
            return QtCore.QRectF(0, 0, self.card_size[0], self.card_size[1])
        else:
            return QtCore.QRectF(self.x_offset, self.y_offset, self.width, self.height)

    def dragEnterEvent(self, event):
        """ Support dragging of items from their panel containers, e.g. symbols from symbol panel
        or new nodes from nodes panel.
        :param event:
        """
        data = event.mimeData()
        if data.hasFormat("application/x-qabstractitemmodeldatalist") or data.hasFormat(
                "text/plain"):
            event.acceptProposedAction()
            event.accept()
        else:
            QtWidgets.QGraphicsTextItem.dragEnterEvent(self, event)

    def paint(self, painter, option, widget):
        """ Painting is sensitive to mouse/selection issues, but usually with
        nodes it is the label of the node that needs complex painting
        :param painter:
        :param option:
        :param widget:
        """
        self.editable_part.setDefaultTextColor(self._host.contextual_color)
        if self.lower_part:
            self.lower_part.setDefaultTextColor(self._host.contextual_color)
        if self.triangle_is_present:
            br = self.boundingRect()
            #print(br, self.x(), self.y(), br.x(), br.y())
            left = 0
            center = self.width / 2
            right = self.width
            top = self.triangle_y
            bottom = top + self.triangle_height
            simple = False
            c = self._host.contextual_color
            painter.setPen(c)
            if simple:
                triangle = QtGui.QPainterPath()
                triangle.moveTo(center, top)
                triangle.lineTo(right, bottom)
                triangle.lineTo(left, bottom)
                triangle.lineTo(center, top)
                painter.drawPath(triangle)
            else:
                edge_type = self._host.edge_type()
                shape_name = ctrl.settings.get_edge_setting('shape_name', edge_type=edge_type)
                path_class = SHAPE_PRESETS[shape_name]
                path, lpath, foo, bar = path_class.path(start_point=(center, top),
                                                        end_point=(right, bottom),
                                                        alignment=g.RIGHT)
                fill = ctrl.settings.get_shape_setting('fill', edge_type=edge_type)
                if fill:
                    painter.fillPath(path, c)
                else:
                    painter.drawPath(path)
                painter.drawLine(left, bottom, right, bottom)
                path, lpath, foo, bar = path_class.path(start_point=(center, top),
                                                        end_point=(left, bottom),
                                                        alignment=g.LEFT)
                if fill:
                    painter.fillPath(path, c)
                else:
                    painter.drawPath(path)
Example #6
0
class Label(QtWidgets.QGraphicsItem):
    """ Labels are names of nodes. Node itself provides a template for what to show in label,
    label composes its document (html layout) for its contents based on that. """
    max_width = 400
    __qt_type_id__ = next_available_type_id()
    card_size = (60, 90)

    def __init__(self, parent=None):
        """ Give node as parent. Label asks it to produce text to show here """
        QtWidgets.QGraphicsItem.__init__(self, parent)
        self.editable_part = QtWidgets.QGraphicsTextItem(self)
        self.lower_part = None  # QtWidgets.QGraphicsTextItem(self)
        self._host = parent
        self.has_been_initialized = False
        self.top_y = 0
        self.upper_part_y = 0
        self.lower_part_y = 0
        self.bottom_y = 0
        self.draw_triangle = False
        self.triangle_height = 20
        self.triangle_width = 20
        self.triangle_y = 0
        self.width = 0
        self.height = 0
        self.template_width = 0
        self.x_offset = 0
        self.y_offset = 0
        self.text_align = CENTER_ALIGN
        self.label_shape = NORMAL
        self._font = None
        self.editable_html = ''
        self.lower_html = ''
        self.edited_field = ''
        self._quick_editing = False
        self._recursion_block = False
        self._last_blockpos = ()
        self._previous_values = None
        self.editable = {}
        self.prepare_template()  # !<----
        self.editable_doc = LabelDocument()
        self.lower_doc = None
        self._fresh_focus = False
        self.editable_part.setDocument(self.editable_doc)
        # not acceptin hover events is important, editing focus gets lost if other labels take
        # hover events. It is unclear why.
        self.setAcceptDrops(False)
        self.setAcceptHoverEvents(False)
        self.editable_doc.contentsChanged.connect(self.editable_doc_changed)
        self.editable_part.setTextWidth(-1)
        self.set_font(self._host.get_font())

    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 __str__(self):
        return 'Label:' + self.editable_html + self.lower_html

    def init_lower_part(self):
        self.lower_part = QtWidgets.QGraphicsTextItem(self)
        self.lower_doc = LabelDocument()
        self.lower_part.setDocument(self.lower_doc)
        self.lower_part.setTextWidth(-1)
        if self._font:
            self.lower_part.setFont(self._font)

    def remove_lower_part(self):
        self.lower_part.setParentItem(None)
        self.lower_part.setParent(None)
        self.lower_part = None
        self.lower_doc = None

    def set_font(self, font):
        self.editable_part.setFont(font)
        if self.lower_part:
            self.lower_part.setFont(font)
        self._font = font

    def update_font(self):
        self.set_font(self._host.get_font())

    def update_label(self, force_update=False):
        """ Asks for node/host to give text and update if changed """
        force_update = True
        self.has_been_initialized = True
        is_card = self.is_card()
        if self.text_align == LEFT_ALIGN:
            self.editable_doc.set_align(QtCore.Qt.AlignLeft)
        elif self.text_align == RIGHT_ALIGN:
            self.editable_doc.set_align(QtCore.Qt.AlignRight)
        else:
            self.editable_doc.set_align(QtCore.Qt.AlignHCenter)
        html, lower_html = self._host.compose_html_for_viewing()
        if html is None:
            print('problems ahead:')
            print(self._host, self._host.node_type,
                  self._host.syntactic_object)

        if self.label_shape == SCOPEBOX:
            if not self._host.is_leaf(only_similar=True, only_visible=True):
                html = '<sub>' + html + '</sub>'
            if lower_html:
                html += lower_html.replace('<br/>', '')
        elif self.label_shape == BRACKETED:
            if not self._host.is_leaf(only_similar=True, only_visible=True):
                html = '[<sub>' + html + '</sub>'
            if lower_html:
                html += lower_html.replace('<br/>', '')
        if force_update or (self.label_shape, html, lower_html,
                            is_card) != self._previous_values:
            if self.editable_html != html:
                self.editable_doc.blockSignals(True)
                if is_card:
                    self.editable_doc.setTextWidth(self.card_size[0])
                else:
                    self.editable_doc.setTextWidth(-1)
                self.editable_html = html
                self.editable_part.setHtml(html)
                self.editable_doc.blockSignals(False)

            ctrl.qdocument_parser.process(self.editable_doc)
            if lower_html and self.label_shape not in [
                    g.SCOPEBOX, g.BRACKETED
            ]:
                if not self.lower_part:
                    self.init_lower_part()
                if lower_html != self.lower_html:
                    self.lower_html = lower_html
                    if is_card:
                        self.lower_doc.setTextWidth(self.card_size[0])
                    else:
                        self.lower_doc.setTextWidth(-1)
                    self.lower_part.setHtml(self.lower_html)
                ctrl.qdocument_parser.process(self.lower_doc)
            else:
                self.lower_html = ''
                if self.lower_part:
                    self.remove_lower_part()
            self._previous_values = (self.label_shape, self.editable_html,
                                     self.lower_html, is_card)

        self.resize_label()

    def is_card(self):
        if self.label_shape != CARD:
            return False
        if inner_cards:
            return True
        elif self._host.is_triangle_host():
            return True
        elif self._host.is_leaf(only_similar=True, only_visible=True):
            return True
        return False

    def left_bracket_width(self):
        return self.width

    def right_bracket_width(self) -> int:
        if self.label_shape == BRACKETED:
            return 6
        elif self.label_shape == SCOPEBOX:
            return 2
        else:
            return 0

    def prepare_template(self):
        my_class = self._host.__class__
        if self._host.syntactic_object:
            synclass = self._host.syntactic_object.__class__
            syn_editable = getattr(synclass, 'editable', {})
            self.editable = combine_dicts(syn_editable, my_class.editable)
        else:
            self.editable = my_class.editable

    def is_empty(self) -> bool:
        """ Turning this node into label would result in an empty label.
        :return: bool
        """
        return not (self.editable_html or self.lower_html)

    def cursor(self):
        return self.editable_part.textCursor()

    def char_format(self) -> QtGui.QTextCharFormat:
        return self.editable_part.textCursor().charFormat()

    def has_content(self) -> bool:
        return bool(self.editable_html or self.lower_html)

    def get_top_y(self) -> int:
        return self.y_offset

    def get_lower_part_y(self) -> int:
        return self.y_offset + self.lower_part_y

    def release_editor_focus(self):
        self.set_quick_editing(False)

    def is_quick_editing(self) -> bool:
        return self._quick_editing

    def set_quick_editing(self, value):
        """  Toggle quick editing on and off for this label. Quick editing is toggled on when
        the label is clicked or navigated into with the keyboard. The label and its editor takes
        focus and many things behave differently while editing, so please keep make sure that
        quick editing is switched off properly, using this method.
        :param value:
        :return:
        """
        if value:
            if self._quick_editing:
                return
            self._quick_editing = True
            self._host.update_label_visibility()
            if ctrl.text_editor_focus:
                ctrl.release_editor_focus()
            ctrl.text_editor_focus = self
            self.editable_part.setTextInteractionFlags(
                QtCore.Qt.TextEditorInteraction)
            self.prepareGeometryChange()
            if self.is_card():
                self.editable_doc.setTextWidth(self.card_size[0])
            else:
                self.editable_doc.setTextWidth(-1)
            self.edited_field, self.editable_html = \
                self._host.compose_html_for_editing()
            self.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor))

            ctrl.ui.add_quick_edit_buttons_for(self._host, self.editable_doc)
            self.editable_part.setHtml(self.editable_html)
            self.editable_doc.cursorPositionChanged.connect(
                self.cursor_position_changed)

            self.resize_label()
            self.editable_part.setAcceptDrops(True)
            ctrl.graph_view.setFocus()
            self.editable_part.setFocus()
            self._fresh_focus = True

        elif self._quick_editing:
            if self.editable_doc.isModified():
                self.parse_document_to_field()
                self.editable_doc.setModified(False)
            ctrl.text_editor_focus = None
            self._quick_editing = False
            ctrl.ui.remove_quick_edit_buttons()
            self._host.update_label()
            self.editable_part.setCursor(
                QtGui.QCursor(QtCore.Qt.PointingHandCursor))
            self.editable_part.setAcceptDrops(False)
            self.editable_part.setTextInteractionFlags(
                QtCore.Qt.NoTextInteraction)
            self.editable_part.clearFocus()
            self._fresh_focus = False
            #if len(fields) == 0:
            #    ctrl.main.action_finished("Finished editing %s, no changes." % self._host,
            #                              undoable=False)
            #elif len(fields) == 1:
            #    ctrl.main.action_finished("Edited field %s in %s" % (fields[0], self._host))
            #else:
            #    ctrl.main.action_finished("Edited fields %s in %s" % (str(fields), self._host))

    def cursor_position_changed(self, cursor):
        if self._quick_editing:
            ctrl.ui.quick_edit_buttons.update_formats(cursor.charFormat())

    @time_me
    def parse_document_to_field(self):
        """ Parse edited QDocument into rows of INodes and into receptable
        field in host object

        :return:
        """
        parsed_parts = ctrl.qdocument_parser.process(self.editable_doc)
        # Parser should return INode or str, if there is nothing that needs INode in it.
        print('parsed_parts:', repr(parsed_parts))
        self._host.parse_edited_label(self.edited_field, parsed_parts)

    def editable_doc_changed(self):
        if self._recursion_block or not (ctrl.forest.in_display
                                         and ctrl.forest.is_parsed):
            return
        w = self.width
        self._recursion_block = True
        self.resize_label()
        self._host.update_bounding_rect()
        if self.width != w and self.scene() == ctrl.graph_scene:
            ctrl.forest.draw()
        self._recursion_block = False

    def get_max_size_from_host(self):
        if self._host.resizable and self._host.user_size is not None:
            return self._host.user_size
        else:
            return 0, 0

    def resize_label(self):
        self.prepareGeometryChange()
        triangle_host = self._host.is_triangle_host()
        if triangle_host:
            label_text = ctrl.settings.get('label_text_mode')
            self.draw_triangle = (label_text == g.NODE_LABELS or
                                  label_text == g.NODE_LABELS_FOR_LEAVES) and \
                self.label_shape not in [g.SCOPEBOX, g.CARD, g.BRACKETED]
        else:
            self.draw_triangle = False

        # ------------------- Width -------------------
        user_width, user_height = self.get_max_size_from_host()
        if self.is_card():
            width = self.card_size[0]
        else:
            if self.lower_html:
                self.editable_part.setTextWidth(-1)
                self.lower_part.setTextWidth(-1)
                ideal_width = max((self.editable_doc.idealWidth(),
                                   self.lower_doc.idealWidth()))
            elif triangle_host:
                self.editable_part.setTextWidth(-1)
                ideal_width = max(
                    (self.editable_doc.idealWidth(), self.triangle_width))
            else:
                self.editable_part.setTextWidth(-1)
                ideal_width = self.editable_doc.idealWidth()
            if user_width and user_width < ideal_width:
                width = user_width
            elif self.template_width:
                width = self.template_width
            else:
                width = ideal_width
            if width < 20:
                width = 20
            elif width > Label.max_width:
                width = Label.max_width
        self.editable_part.setTextWidth(width)
        if self.lower_html:
            self.lower_part.setTextWidth(width)

        # ------------------- Height -------------------
        if self.is_card():
            total_height = self.card_size[1]
        elif self.draw_triangle:
            if self.editable_html:
                eh = self.editable_doc.size().height()
            else:
                eh = 0
            if self.lower_html:
                lh = self.lower_doc.size().height()
            else:
                lh = 0
            total_height = eh + self.triangle_height + lh
        else:
            total_height = self.editable_doc.size().height()
        half_height = total_height / 2.0
        self.top_y = -half_height
        self.bottom_y = half_height
        self.width = width
        self.height = total_height
        # middle line is 0
        if self.draw_triangle:
            # if triangled is not leaf, editing should target the upper part and leave
            # the combination of leaves alone
            self.upper_part_y = 0
            if self.editable_html:
                self.triangle_y = self.editable_doc.size().height()
            else:
                self.triangle_y = 0
            self.lower_part_y = self.triangle_y + self.triangle_height
        elif self.label_shape == g.CARD:
            # no lower part, no triangle
            self.upper_part_y = 0
            self.triangle_y = 0
            self.lower_part_y = self.editable_doc.size().height()
            # reduce font size until it fits
            if self.lower_part and self.lower_html:
                font = self.lower_part.font()
                use_point_size = font.pointSize() != -1
                if use_point_size:
                    fsize = font.pointSize()
                else:
                    fsize = font.pixelSize()
                attempts = 0
                while fsize > 5 and attempts < 10 and self.lower_part_y + \
                        self.lower_doc.size().height() > total_height:
                    fsize -= 1
                    if use_point_size:
                        font.setPointSize(fsize)
                    else:
                        font.setPixelSize(fsize)
                    self.lower_part.setFont(font)
                    attempts += 1
        else:
            # no lower part, no triangle
            self.upper_part_y = 0
            self.triangle_y = 0
            self.lower_part_y = 0

        self.x_offset = width / -2.0
        if self.is_card():
            self.y_offset = self.upper_part_y
        else:
            self.y_offset = -half_height

        self.setPos(self.x_offset, self.y_offset)
        self.editable_part.setPos(0, self.upper_part_y)
        if self.lower_html:
            self.lower_part.setPos(0, self.lower_part_y)

        # Update ui items around the label (or node hosting the label)
        ctrl.ui.update_position_for(self._host)

    def dropEvent(self, event):
        mim = event.mimeData()
        if mim.hasFormat("application/x-qabstractitemmodeldatalist"):
            print('label dropEvent application/x-qabstractitemmodeldatalist')
            event.accept()
            data = open_symbol_data(event.mimeData())
            if data and 'char' in data:
                self.editable_part.textCursor().insertText(data['char'])
                event.acceptProposedAction()
        elif mim.hasFormat("text/plain"):
            print('label dropEvent text/plain')
            event.accept()
            event.acceptProposedAction()
            self.editable_part.dropEvent(event)
        else:
            print('label dropEvent something')
            self.editable_part.dropEvent(event)

    def boundingRect(self):
        if self.is_card():
            return QtCore.QRectF(0, 0, self.card_size[0], self.card_size[1])
        else:
            return QtCore.QRectF(self.x_offset, self.y_offset, self.width,
                                 self.height)

    def dragEnterEvent(self, event):
        """ Support dragging of items from their panel containers, e.g. symbols from symbol panel
        or new nodes from nodes panel.
        :param event:
        """
        data = event.mimeData()
        print('label dragEnterEvent ', data)
        if data.hasFormat("application/x-qabstractitemmodeldatalist"
                          ) or data.hasFormat("text/plain"):
            event.acceptProposedAction()
            event.accept()
        else:
            QtWidgets.QGraphicsTextItem.dragEnterEvent(self, event)

    def paint(self, painter, option, widget):
        """ Painting is sensitive to mouse/selection issues, but usually with
        nodes it is the label of the node that needs complex painting
        :param painter:
        :param option:
        :param widget:
        """
        self.editable_part.setDefaultTextColor(self._host.contextual_color)
        if self.lower_part:
            self.lower_part.setDefaultTextColor(self._host.contextual_color)
        if self.draw_triangle:
            left = 0
            center = self.width / 2
            right = self.width
            top = self.triangle_y
            bottom = top + self.triangle_height
            simple = False
            c = self._host.contextual_color
            painter.setPen(c)
            if simple:
                triangle = QtGui.QPainterPath()
                triangle.moveTo(center, top)
                triangle.lineTo(right, bottom)
                triangle.lineTo(left, bottom)
                triangle.lineTo(center, top)
                painter.drawPath(triangle)
            else:
                edge_type = self._host.edge_type()
                shape_name = ctrl.settings.get_edge_setting(
                    'shape_name', edge_type=edge_type)
                path_class = SHAPE_PRESETS[shape_name]
                path, lpath, foo, bar = path_class.path(start_point=(center,
                                                                     top),
                                                        end_point=(right,
                                                                   bottom),
                                                        alignment=g.RIGHT)
                fill = ctrl.settings.get_shape_setting('fill',
                                                       edge_type=edge_type)
                if fill:
                    painter.fillPath(path, c)
                else:
                    painter.drawPath(path)
                painter.drawLine(left, bottom, right, bottom)
                path, lpath, foo, bar = path_class.path(start_point=(center,
                                                                     top),
                                                        end_point=(left,
                                                                   bottom),
                                                        alignment=g.LEFT)
                if fill:
                    painter.fillPath(path, c)
                else:
                    painter.drawPath(path)