<commit_before>'''
AssociationItem -- Graphical representation of an association.
'''
# vim:sw=4:et

# TODO: for Association.postload(): in some cases where the association ends
# are connected to the same Class, the head_end property is connected to the
# tail end and visa versa.

from __future__ import generators

import gobject
import pango
import diacanvas
import diacanvas.shape
import diacanvas.geometry
import gaphor
import gaphor.UML as UML
from gaphor.diagram import initialize_item

from diagramitem import DiagramItem
from relationship import RelationshipItem

class AssociationItem(RelationshipItem, diacanvas.CanvasGroupable, diacanvas.CanvasEditable):
    """AssociationItem represents associations. 
    An AssociationItem has two AssociationEnd items. Each AssociationEnd item
    represents a Property (with Property.association == my association).
    """
    __gproperties__ = {
        'head':         (gobject.TYPE_OBJECT, 'head',
                         'AssociationEnd held by the head end of the association',
                         gobject.PARAM_READABLE),
        'tail':         (gobject.TYPE_OBJECT, 'tail',
                         'AssociationEnd held by the tail end of the association',
                         gobject.PARAM_READABLE),
        'head-subject': (gobject.TYPE_PYOBJECT, 'head-subject',
                         'subject held by the head end of the association',
                         gobject.PARAM_READWRITE),
        'tail-subject': (gobject.TYPE_PYOBJECT, 'tail-subject',
                         'subject held by the tail end of the association',
                         gobject.PARAM_READWRITE),
    }

    FONT='sans bold 10'

    association_popup_menu = (
        'separator',
        'AssociationShowDirection',
        'AssociationInvertDirection',
        'separator',
        'Side _A', (
            'Head_isNavigable',
            'separator',
            'Head_AggregationNone',
            'Head_AggregationShared',
            'Head_AggregationComposite'),
        'Side _B', (
            'Tail_isNavigable',
            'separator',
            'Tail_AggregationNone',
            'Tail_AggregationShared',
            'Tail_AggregationComposite')
    )

    def __init__(self, id=None):
        RelationshipItem.__init__(self, id)

        # AssociationEnds are really inseperable from the AssociationItem.
        # We give them the same id as the association item.
        self._head_end = AssociationEnd()
        self._head_end.set_child_of(self)
        self._tail_end = AssociationEnd()
        self._tail_end.set_child_of(self)

        self._label = diacanvas.shape.Text()
        self._label.set_font_description(pango.FontDescription(AssociationItem.FONT))
        #self._label.set_alignment(pango.ALIGN_CENTER)
        self._label.set_markup(False)
        #self._label.set_max_width(100)
        #self._label.set_max_height(100)

        # Direction depends on the ends that hold the ownedEnd attributes.
        self._dir = diacanvas.shape.Path()
        self._dir.set_line_width(2.0)
        self._dir.line(((10, 0), (10, 10), (0, 5)))
        self._dir.set_fill_color(diacanvas.color(0,0,0))
        self._dir.set_fill(diacanvas.shape.FILL_SOLID)
        self._dir.set_cyclic(True)

    def save (self, save_func):
        RelationshipItem.save(self, save_func)
        if self._head_end.subject:
            save_func('head_subject', self._head_end.subject)
        if self._tail_end.subject:
            save_func('tail_subject', self._tail_end.subject)

    def load (self, name, value):
        # end_head and end_tail were used in an older Gaphor version
        if name in ( 'head_end', 'head_subject' ):
            #type(self._head_end).subject.load(self._head_end, value)
            self._head_end.load('subject', value)
        elif name in ( 'tail_end', 'tail_subject' ):
            #type(self._tail_end).subject.load(self._tail_end, value)
            self._tail_end.load('subject', value)
        else:
            RelationshipItem.load(self, name, value)

    def postload(self):
        RelationshipItem.postload(self)
        self._head_end.postload()
        self._tail_end.postload()

    def do_set_property (self, pspec, value):
        if pspec.name == 'head-subject':
            self._head_end.subject = value
        elif pspec.name == 'tail-subject':
            self._tail_end.subject = value
        else:
            RelationshipItem.do_set_property(self, pspec, value)

    def do_get_property(self, pspec):
        if pspec.name == 'head':
            return self._head_end
        if pspec.name == 'tail':
            return self._tail_end
        elif pspec.name == 'head-subject':
            return self._head_end.subject
        elif pspec.name == 'tail-subject':
            return self._tail_end.subject
        else:
            return RelationshipItem.do_get_property(self, pspec)

    head_end = property(lambda self: self._head_end)

    tail_end = property(lambda self: self._tail_end)

    def get_popup_menu(self):
        if self.subject:
            return self.popup_menu + self.association_popup_menu
        else:
            return self.popup_menu

    def on_subject_notify(self, pspec, notifiers=()):
        RelationshipItem.on_subject_notify(self, pspec,
                                           notifiers + ('name', 'ownedEnd',))

    def on_subject_notify__name(self, subject, pspec):
        log.debug('Association name = %s' % (self.subject and self.subject.name))
        if self.subject:
            self._label.set_text(self.subject.name or '')
        else:
            self._label.set_text('')
        self.request_update()

    def on_subject_notify__ownedEnd(self, subject, pspec):
        self.request_update()

    def update_label(self, p1, p2):
        w, h = self._label.to_pango_layout(True).get_pixel_size()

        x = p1[0] > p2[0] and w + 2 or -2
        x = (p1[0] + p2[0]) / 2.0 - x
        y = p1[1] <= p2[1] and h or 0
        y = (p1[1] + p2[1]) / 2.0 - y

	self._label.set_pos((x, y))
        log.debug('label pos = (%d, %d)' % (x, y))
	#return x, y, max(x + 10, x + w), max(y + 10, y + h)
	return x, y, x + w, y + h

    def update_dir(self, p1, p2):
        #self._dir.line(((10, 0), (10, 10), (0, 5)))
        w, h = 12, 10

        x = p1[0] < p2[0] and w + 2 or -2
        #x = 12
        x = (p1[0] + p2[0]) / 2.0 - x
        y = p1[1] >= p2[1] and h or 0
        #y = 12
        y = (p1[1] + p2[1]) / 2.0 - y
        from math import atan, pi, sin, cos
        
        try:
            angle = atan((p1[1] - p2[1]) / (p1[0] - p2[0])) #/ pi * 180.0
        except ZeroDivisionError:
            angle = 0

        if p1[0] < p2[0]: angle += pi
        if p1[1] < p2[1]: angle += pi * 2
        log.debug('rotation angle is %s' % (angle/pi * 180.0))

        sin_angle = sin(angle)
        cos_angle = cos(angle)

        def r(a, b):
            # TODO: Check libart for rotation algorithm.
            #return (cos_angle * a + x, \
            #        sin_angle * b + y)
            return (cos_angle * a - sin_angle * b + x, \
                    sin_angle * a + cos_angle * b + y)

	#self._dir.set_pos((x, y))
        #log.debug('label pos = (%d, %d)' % (x, y))
	#return x, y, max(x + 10, x + w), max(y + 10, y + h)
        #self._dir.line(((10 + x, 0 + y), (10 + x, 10 + y), (0 + x, 5 + y)))
        self._dir.line((r(w, 0), r(w, h), r(0, h/2)))
        self._dir.set_cyclic(True)

	return x, y, x + w, y + h
<commit_msg>*** empty log message ***<commit_after>'''
AssociationItem -- Graphical representation of an association.
'''
# vim:sw=4:et

# TODO: for Association.postload(): in some cases where the association ends
# are connected to the same Class, the head_end property is connected to the
# tail end and visa versa.

from __future__ import generators

import gobject
import pango
import diacanvas
import diacanvas.shape
import diacanvas.geometry
import gaphor
import gaphor.UML as UML
from gaphor.diagram import initialize_item

from diagramitem import DiagramItem
from relationship import RelationshipItem

class AssociationItem(RelationshipItem, diacanvas.CanvasGroupable, diacanvas.CanvasEditable):
    """AssociationItem represents associations. 
    An AssociationItem has two AssociationEnd items. Each AssociationEnd item
    represents a Property (with Property.association == my association).
    """
    __gproperties__ = {
        'head':         (gobject.TYPE_OBJECT, 'head',
                         'AssociationEnd held by the head end of the association',
                         gobject.PARAM_READABLE),
        'tail':         (gobject.TYPE_OBJECT, 'tail',
                         'AssociationEnd held by the tail end of the association',
                         gobject.PARAM_READABLE),
        'head-subject': (gobject.TYPE_PYOBJECT, 'head-subject',
                         'subject held by the head end of the association',
                         gobject.PARAM_READWRITE),
        'tail-subject': (gobject.TYPE_PYOBJECT, 'tail-subject',
                         'subject held by the tail end of the association',
                         gobject.PARAM_READWRITE),
    }

    FONT='sans bold 10'

    association_popup_menu = (
        'separator',
        'AssociationShowDirection',
        'AssociationInvertDirection',
        'separator',
        'Side _A', (
            'Head_isNavigable',
            'separator',
            'Head_AggregationNone',
            'Head_AggregationShared',
            'Head_AggregationComposite'),
        'Side _B', (
            'Tail_isNavigable',
            'separator',
            'Tail_AggregationNone',
            'Tail_AggregationShared',
            'Tail_AggregationComposite')
    )

    def __init__(self, id=None):
        RelationshipItem.__init__(self, id)

        # AssociationEnds are really inseperable from the AssociationItem.
        # We give them the same id as the association item.
        self._head_end = AssociationEnd()
        self._head_end.set_child_of(self)
        self._tail_end = AssociationEnd()
        self._tail_end.set_child_of(self)

        self._label = diacanvas.shape.Text()
        self._label.set_font_description(pango.FontDescription(AssociationItem.FONT))
        #self._label.set_alignment(pango.ALIGN_CENTER)
        self._label.set_markup(False)
        #self._label.set_max_width(100)
        #self._label.set_max_height(100)

        # Direction depends on the ends that hold the ownedEnd attributes.
        self._dir = diacanvas.shape.Path()
        self._dir.set_line_width(2.0)
        self._dir.line(((10, 0), (10, 10), (0, 5)))
        self._dir.set_fill_color(diacanvas.color(0,0,0))
        self._dir.set_fill(diacanvas.shape.FILL_SOLID)
        self._dir.set_cyclic(True)

    def save (self, save_func):
        RelationshipItem.save(self, save_func)
        if self._head_end.subject:
            save_func('head_subject', self._head_end.subject)
        if self._tail_end.subject:
            save_func('tail_subject', self._tail_end.subject)

    def load (self, name, value):
        # end_head and end_tail were used in an older Gaphor version
        if name in ( 'head_end', 'head_subject' ):
            #type(self._head_end).subject.load(self._head_end, value)
            self._head_end.load('subject', value)
        elif name in ( 'tail_end', 'tail_subject' ):
            #type(self._tail_end).subject.load(self._tail_end, value)
            self._tail_end.load('subject', value)
        else:
            RelationshipItem.load(self, name, value)

    def postload(self):
        RelationshipItem.postload(self)
        self._head_end.postload()
        self._tail_end.postload()

    def do_set_property (self, pspec, value):
        if pspec.name == 'head-subject':
            self._head_end.subject = value
        elif pspec.name == 'tail-subject':
            self._tail_end.subject = value
        else:
            RelationshipItem.do_set_property(self, pspec, value)

    def do_get_property(self, pspec):
        if pspec.name == 'head':
            return self._head_end
        if pspec.name == 'tail':
            return self._tail_end
        elif pspec.name == 'head-subject':
            return self._head_end.subject
        elif pspec.name == 'tail-subject':
            return self._tail_end.subject
        else:
            return RelationshipItem.do_get_property(self, pspec)

    head_end = property(lambda self: self._head_end)

    tail_end = property(lambda self: self._tail_end)

    def get_popup_menu(self):
        if self.subject:
            return self.popup_menu + self.association_popup_menu
        else:
            return self.popup_menu

    def on_subject_notify(self, pspec, notifiers=()):
        RelationshipItem.on_subject_notify(self, pspec,
                                           notifiers + ('name', 'ownedEnd',))

    def on_subject_notify__name(self, subject, pspec):
        log.debug('Association name = %s' % (self.subject and self.subject.name))
        if self.subject:
            self._label.set_text(self.subject.name or '')
        else:
            self._label.set_text('')
        self.request_update()

    def on_subject_notify__ownedEnd(self, subject, pspec):
        self.request_update()

    def update_label(self, p1, p2):
        w, h = self._label.to_pango_layout(True).get_pixel_size()

        x = p1[0] > p2[0] and w + 2 or -2
        x = (p1[0] + p2[0]) / 2.0 - x
        y = p1[1] <= p2[1] and h or 0
        y = (p1[1] + p2[1]) / 2.0 - y

	self._label.set_pos((x, y))
        log.debug('label pos = (%d, %d)' % (x, y))
	#return x, y, max(x + 10, x + w), max(y + 10, y + h)
	return x, y, x + w, y + h

    def update_dir(self, p1, p2):
        #self._dir.line(((10, 0), (10, 10), (0, 5)))
        w, h = 12, 10

        x = p1[0] < p2[0] and w + 2 or -2
        #x = 12
        x = (p1[0] + p2[0]) / 2.0 - x
        y = p1[1] >= p2[1] and h or 0
        #y = 12
        y = (p1[1] + p2[1]) / 2.0 - y
        from math import atan, pi, sin, cos
        
        try:
            angle = atan((p1[1] - p2[1]) / (p1[0] - p2[0])) #/ pi * 180.0
        except ZeroDivisionError:
            angle = pi + pi/2

        if p1[0] < p2[0]: angle += pi
        if p1[1] < p2[1]: angle += pi * 2
        log.debug('rotation angle is %s' % (angle/pi * 180.0))

        sin_angle = sin(angle)
        cos_angle = cos(angle)

        def r(a, b):
            # TODO: Check libart for rotation algorithm.
            #return (cos_angle * a + x, \
            #        sin_angle * b + y)
            return (cos_angle * a - sin_angle * b + x, \
                    sin_angle * a + cos_angle * b + y)

	#self._dir.set_pos((x, y))
        #log.debug('label pos = (%d, %d)' % (x, y))
	#return x, y, max(x + 10, x + w), max(y + 10, y + h)
        #self._dir.line(((10 + x, 0 + y), (10 + x, 10 + y), (0 + x, 5 + y)))
        self._dir.line((r(w, 0), r(w, h), r(0, h/2)))
        self._dir.set_cyclic(True)

	return x, y, x + w, y + h

{'fraction_non_alphanumeric': 0.11903178614177418, 'fraction_numerical': 0.018087511637185794, 'mean_word_length': 2.118830360846122, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '943956', 'n_tokens_mistral': 4952, 'n_tokens_neox': 4742, 'n_words': 1179}