summaryrefslogtreecommitdiff
path: root/grc/gui/canvas/connection.py
diff options
context:
space:
mode:
Diffstat (limited to 'grc/gui/canvas/connection.py')
-rw-r--r--grc/gui/canvas/connection.py254
1 files changed, 254 insertions, 0 deletions
diff --git a/grc/gui/canvas/connection.py b/grc/gui/canvas/connection.py
new file mode 100644
index 0000000000..ff790503ef
--- /dev/null
+++ b/grc/gui/canvas/connection.py
@@ -0,0 +1,254 @@
+"""
+Copyright 2007, 2008, 2009 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+"""
+
+from __future__ import absolute_import, division
+
+from argparse import Namespace
+from math import pi
+
+from . import colors
+from .drawable import Drawable
+from .. import Utils
+from ..Constants import (
+ CONNECTOR_ARROW_BASE,
+ CONNECTOR_ARROW_HEIGHT,
+ GR_MESSAGE_DOMAIN,
+ LINE_SELECT_SENSITIVITY,
+)
+from ...core.Connection import Connection as CoreConnection
+from ...core.Element import nop_write
+
+
+class Connection(CoreConnection, Drawable):
+ """
+ A graphical connection for ports.
+ The connection has 2 parts, the arrow and the wire.
+ The coloring of the arrow and wire exposes the status of 3 states:
+ enabled/disabled, valid/invalid, highlighted/non-highlighted.
+ The wire coloring exposes the enabled and highlighted states.
+ The arrow coloring exposes the enabled and valid states.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(self.__class__, self).__init__(*args, **kwargs)
+ Drawable.__init__(self)
+
+ self._line = []
+ self._line_width_factor = 1.0
+ self._color1 = self._color2 = None
+
+ self._current_port_rotations = self._current_coordinates = None
+
+ self._rel_points = None # connection coordinates relative to sink/source
+ self._arrow_rotation = 0.0 # rotation of the arrow in radians
+ self._current_cr = None # for what_is_selected() of curved line
+ self._line_path = None
+
+ @nop_write
+ @property
+ def coordinate(self):
+ return self.source_port.connector_coordinate_absolute
+
+ @nop_write
+ @property
+ def rotation(self):
+ """
+ Get the 0 degree rotation.
+ Rotations are irrelevant in connection.
+
+ Returns:
+ 0
+ """
+ return 0
+
+ def create_shapes(self):
+ """Pre-calculate relative coordinates."""
+ source = self.source_port
+ sink = self.sink_port
+ rotate = Utils.get_rotated_coordinate
+
+ # first two components relative to source connector, rest relative to sink connector
+ self._rel_points = [
+ rotate((15, 0), source.rotation), # line from 0,0 to here, bezier curve start
+ rotate((50, 0), source.rotation), # bezier curve control point 1
+ rotate((-50, 0), sink.rotation), # bezier curve control point 2
+ rotate((-15, 0), sink.rotation), # bezier curve end
+ rotate((-CONNECTOR_ARROW_HEIGHT, 0), sink.rotation), # line to arrow head
+ ]
+ self._current_coordinates = None # triggers _make_path()
+
+ def get_domain_color(domain_name):
+ domain = self.parent_platform.domains.get(domain_name, {})
+ color_spec = domain.get('color')
+ return colors.get_color(color_spec) if color_spec else colors.DEFAULT_DOMAIN_COLOR
+
+ if source.domain == GR_MESSAGE_DOMAIN:
+ self._line_width_factor = 1.0
+ self._color1 = None
+ self._color2 = colors.CONNECTION_ENABLED_COLOR
+ else:
+ if source.domain != sink.domain:
+ self._line_width_factor = 2.0
+ self._color1 = get_domain_color(source.domain)
+ self._color2 = get_domain_color(sink.domain)
+
+ self._arrow_rotation = -sink.rotation / 180 * pi
+
+ if not self._bounding_points:
+ self._make_path() # no cr set --> only sets bounding_points for extent
+
+ def _make_path(self, cr=None):
+ x_pos, y_pos = self.coordinate # is source connector coordinate
+ # x_start, y_start = self.source_port.get_connector_coordinate()
+ x_end, y_end = self.sink_port.connector_coordinate_absolute
+
+ # sink connector relative to sink connector
+ x_e, y_e = x_end - x_pos, y_end - y_pos
+
+ # make rel_point all relative to source connector
+ p0 = 0, 0 # x_start - x_pos, y_start - y_pos
+ p1, p2, (dx_e1, dy_e1), (dx_e2, dy_e2), (dx_e3, dy_e3) = self._rel_points
+ p3 = x_e + dx_e1, y_e + dy_e1
+ p4 = x_e + dx_e2, y_e + dy_e2
+ p5 = x_e + dx_e3, y_e + dy_e3
+ self._bounding_points = p0, p1, p4, p5 # ignores curved part =(
+
+ if cr:
+ cr.move_to(*p0)
+ cr.line_to(*p1)
+ cr.curve_to(*(p2 + p3 + p4))
+ cr.line_to(*p5)
+ self._line_path = cr.copy_path()
+
+ def draw(self, cr):
+ """
+ Draw the connection.
+ """
+ self._current_cr = cr
+ sink = self.sink_port
+ source = self.source_port
+
+ # check for changes
+ port_rotations = (source.rotation, sink.rotation)
+ if self._current_port_rotations != port_rotations:
+ self.create_shapes() # triggers _make_path() call below
+ self._current_port_rotations = port_rotations
+
+ new_coordinates = (source.parent_block.coordinate, sink.parent_block.coordinate)
+ if self._current_coordinates != new_coordinates:
+ self._make_path(cr)
+ self._current_coordinates = new_coordinates
+
+ color1, color2 = (
+ None if color is None else
+ colors.HIGHLIGHT_COLOR if self.highlighted else
+ colors.CONNECTION_DISABLED_COLOR if not self.enabled else
+ colors.CONNECTION_ERROR_COLOR if not self.is_valid() else
+ color
+ for color in (self._color1, self._color2)
+ )
+
+ cr.translate(*self.coordinate)
+ cr.set_line_width(self._line_width_factor * cr.get_line_width())
+ cr.new_path()
+ cr.append_path(self._line_path)
+
+ arrow_pos = cr.get_current_point()
+
+ if color1: # not a message connection
+ cr.set_source_rgba(*color1)
+ cr.stroke_preserve()
+
+ if color1 != color2:
+ cr.save()
+ cr.set_dash([5.0, 5.0], 5.0 if color1 else 0.0)
+ cr.set_source_rgba(*color2)
+ cr.stroke()
+ cr.restore()
+ else:
+ cr.new_path()
+
+ cr.move_to(*arrow_pos)
+ cr.set_source_rgba(*color2)
+ cr.rotate(self._arrow_rotation)
+ cr.rel_move_to(CONNECTOR_ARROW_HEIGHT, 0)
+ cr.rel_line_to(-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE/2)
+ cr.rel_line_to(0, CONNECTOR_ARROW_BASE)
+ cr.close_path()
+ cr.fill()
+
+ def what_is_selected(self, coor, coor_m=None):
+ """
+ Returns:
+ self if one of the areas/lines encompasses coor, else None.
+ """
+ if coor_m:
+ return Drawable.what_is_selected(self, coor, coor_m)
+
+ x, y = [a - b for a, b in zip(coor, self.coordinate)]
+
+ cr = self._current_cr
+
+ if cr is None:
+ return
+ cr.save()
+ cr.new_path()
+ cr.append_path(self._line_path)
+ cr.set_line_width(cr.get_line_width() * LINE_SELECT_SENSITIVITY)
+ hit = cr.in_stroke(x, y)
+ cr.restore()
+
+ if hit:
+ return self
+
+
+class DummyCoreConnection(object):
+ def __init__(self, source_port, **kwargs):
+ self.parent_platform = source_port.parent_platform
+ self.source_port = source_port
+ self.sink_port = self._dummy_port = Namespace(
+ domain=source_port.domain,
+ rotation=0,
+ coordinate=(0, 0),
+ connector_coordinate_absolute=(0, 0),
+ connector_direction=0,
+ parent_block=Namespace(coordinate=(0, 0)),
+ )
+
+ self.enabled = True
+ self.highlighted = False,
+ self.is_valid = lambda: True
+ self.update(**kwargs)
+
+ def update(self, coordinate=None, rotation=None, sink_port=None):
+ dp = self._dummy_port
+ self.sink_port = sink_port if sink_port else dp
+ if coordinate:
+ dp.coordinate = coordinate
+ dp.connector_coordinate_absolute = coordinate
+ dp.parent_block.coordinate = coordinate
+ if rotation is not None:
+ dp.rotation = rotation
+ dp.connector_direction = (180 + rotation) % 360
+
+ @property
+ def has_real_sink(self):
+ return self.sink_port is not self._dummy_port
+
+DummyConnection = Connection.make_cls_with_base(DummyCoreConnection)