summaryrefslogtreecommitdiff
path: root/grc/gui/canvas/connection.py
diff options
context:
space:
mode:
authorSebastian Koslowski <koslowski@kit.edu>2017-01-10 17:54:02 +0100
committerSebastian Koslowski <koslowski@kit.edu>2017-01-11 21:33:25 +0100
commit502c63d341bd839f597a3d23708c72e2aa888bf1 (patch)
treefdaa588cde56739a5b63d10df12da6c24267e1d3 /grc/gui/canvas/connection.py
parentc52deb5f43cfd5a7083f3568719d88ac09ac838f (diff)
grc: gtk3: curved connections
Diffstat (limited to 'grc/gui/canvas/connection.py')
-rw-r--r--grc/gui/canvas/connection.py221
1 files changed, 113 insertions, 108 deletions
diff --git a/grc/gui/canvas/connection.py b/grc/gui/canvas/connection.py
index 14bd0c9280..1da63be389 100644
--- a/grc/gui/canvas/connection.py
+++ b/grc/gui/canvas/connection.py
@@ -17,13 +17,19 @@ 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
+from __future__ import absolute_import, division
-from .drawable import Drawable
-
-from .. import Colors, Utils
-from ..Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT, GR_MESSAGE_DOMAIN
+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
@@ -46,13 +52,17 @@ class Connection(CoreConnection, Drawable):
self._line_width_factor = 1.0
self._color = self._color2 = self._arrow_color = None
- self._sink_rot = self._source_rot = None
- self._sink_coor = self._source_coor = 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.get_connector_coordinate()
+ return self.source_port.connector_coordinate_absolute
@nop_write
@property
@@ -68,123 +78,96 @@ class Connection(CoreConnection, Drawable):
def create_shapes(self):
"""Pre-calculate relative coordinates."""
- self._sink_rot = None
- self._source_rot = None
- self._sink_coor = None
- self._source_coor = None
- #get the source coordinate
- try:
- connector_length = self.source_port.connector_length
- except:
- return # todo: why?
- self.x1, self.y1 = Utils.get_rotated_coordinate((connector_length, 0), self.source_port.rotation)
- #get the sink coordinate
- connector_length = self.sink_port.connector_length + CONNECTOR_ARROW_HEIGHT
- self.x2, self.y2 = Utils.get_rotated_coordinate((-connector_length, 0), self.sink_port.rotation)
- #build the arrow
- self._arrow_base = [
- (0, 0),
- Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE/2), self.sink_port.rotation),
- Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, CONNECTOR_ARROW_BASE/2), self.sink_port.rotation),
- ] if self.sink_block.state != 'bypassed' else []
- source_domain = self.source_port.domain
- sink_domain = self.sink_port.domain
+ 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
+ return colors.get_color(color_spec) if color_spec else colors.DEFAULT_DOMAIN_COLOR
- if source_domain == GR_MESSAGE_DOMAIN:
+ if source.domain == GR_MESSAGE_DOMAIN:
self._line_width_factor = 1.0
self._color = None
- self._color2 = Colors.CONNECTION_ENABLED_COLOR
+ self._color2 = colors.CONNECTION_ENABLED_COLOR
else:
- if source_domain != sink_domain:
+ if source.domain != sink.domain:
self._line_width_factor = 2.0
- self._color = get_domain_color(source_domain)
- self._color2 = get_domain_color(sink_domain)
- self._arrow_color = self._color2 if self.is_valid() else Colors.CONNECTION_ERROR_COLOR
- self._update_after_move()
-
- def _update_after_move(self):
- """Calculate coordinates."""
- source = self.source_port
- sink = self.sink_port
- source_dir = source.get_connector_direction()
- sink_dir = sink.get_connector_direction()
-
- x_pos, y_pos = self.coordinate
- x_start, y_start = source.get_connector_coordinate()
- x_end, y_end = sink.get_connector_coordinate()
-
- p3 = x3, y3 = x_end - x_pos, y_end - y_pos
- p2 = x2, y2 = self.x2 + x3, self.y2 + y3
- p1 = x1, y1 = self.x1, self.y1
- p0 = x_start - x_pos, y_start - y_pos
- self._arrow = [(x + x3, y + y3) for x, y in self._arrow_base]
-
- if abs(source_dir - sink.get_connector_direction()) == 180:
- # 2 possible point sets to create a 3-line connector
- mid_x, mid_y = (x1 + x2) / 2.0, (y1 + y2) / 2.0
- points = ((mid_x, y1), (mid_x, y2))
- alt = ((x1, mid_y), (x2, mid_y))
- # source connector -> points[0][0] should be in the direction of source (if possible)
- if Utils.get_angle_from_coordinates(p1, points[0]) != source_dir:
- points, alt = alt, points
- # points[0] -> sink connector should not be in the direction of sink
- if Utils.get_angle_from_coordinates(points[0], p2) == sink_dir:
- points, alt = alt, points
- # points[0] -> source connector should not be in the direction of source
- if Utils.get_angle_from_coordinates(points[0], p1) == source_dir:
- points, alt = alt, points
- # create 3-line connector
- i1, i2 = points
- self._line = [p0, p1, i1, i2, p2, p3]
- else:
- # 2 possible points to create a right-angled connector
- point, alt = [(x1, y2), (x2, y1)]
- # source connector -> point should be in the direction of source (if possible)
- if Utils.get_angle_from_coordinates(p1, point) != source_dir:
- point, alt = alt, point
- # point -> sink connector should not be in the direction of sink
- if Utils.get_angle_from_coordinates(point, p2) == sink_dir:
- point, alt = alt, point
- # point -> source connector should not be in the direction of source
- if Utils.get_angle_from_coordinates(point, p1) == source_dir:
- point, alt = alt, point
- # create right-angled connector
- self._line = [p0, p1, point, p2, p3]
- self.bounds_from_line(self._line)
+ self._color = get_domain_color(source.domain)
+ self._color2 = get_domain_color(sink.domain)
+
+ self._arrow_color = self._color2 if self.is_valid() else colors.CONNECTION_ERROR_COLOR
+ 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
- if self._sink_rot != sink.rotation or self._source_rot != source.rotation:
- self.create_shapes()
- self._sink_rot = sink.rotation
- self._source_rot = source.rotation
-
- elif self._sink_coor != sink.parent_block.coordinate or self._source_coor != source.parent_block.coordinate:
- self._update_after_move()
- self._sink_coor = sink.parent_block.coordinate
- self._source_coor = source.parent_block.coordinate
- # draw
+ 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, arrow_color = (
None if color is None else
- Colors.HIGHLIGHT_COLOR if self.highlighted else
- Colors.CONNECTION_DISABLED_COLOR if not self.enabled else color
+ colors.HIGHLIGHT_COLOR if self.highlighted else
+ colors.CONNECTION_DISABLED_COLOR if not self.enabled else color
for color in (self._color, self._color2, self._arrow_color)
)
cr.translate(*self.coordinate)
cr.set_line_width(self._line_width_factor * cr.get_line_width())
- for point in self._line:
- cr.line_to(*point)
+ 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)
@@ -199,10 +182,32 @@ class Connection(CoreConnection, Drawable):
else:
cr.new_path()
- if self._arrow:
- cr.set_source_rgba(*arrow_color)
- cr.move_to(*self._arrow[0])
- cr.line_to(*self._arrow[1])
- cr.line_to(*self._arrow[2])
- cr.close_path()
- cr.fill()
+ cr.move_to(*arrow_pos)
+ cr.set_source_rgba(*arrow_color)
+ 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
+ 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