summaryrefslogtreecommitdiff
path: root/grc/gui/Port.py
blob: 8c4500f960372570ff3c6ff1a196f0b3dfa6c746 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
"""
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
import math
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, PangoCairo

from . import Actions, Colors, Utils
from .Constants import (
    PORT_SEPARATION, PORT_SPACING, CONNECTOR_EXTENSION_MINIMAL,
    CONNECTOR_EXTENSION_INCREMENT, PORT_LABEL_PADDING, PORT_MIN_WIDTH,
    PORT_LABEL_HIDDEN_WIDTH, PORT_FONT
)
from .Element import Element
from ..core.Constants import DEFAULT_DOMAIN, GR_MESSAGE_DOMAIN

from ..core.Port import Port as _Port


class Port(_Port, Element):
    """The graphical port."""

    def __init__(self, block, n, dir):
        """
        Port contructor.
        Create list of connector coordinates.
        """
        _Port.__init__(self, block, n, dir)
        Element.__init__(self)
        self.W = self.w = self.h = 0
        self.H = 20  # todo: fix
        self._connector_coordinate = (0, 0)
        self._connector_length = 0
        self._hovering = True
        self._force_label_unhidden = False
        self.layout = Gtk.DrawingArea().create_pango_layout('')
        self._bg_color = Colors.get_color(self.get_color())

    def create_shapes(self):
        """Create new areas and labels for the port."""
        Element.create_shapes(self)
        self._bg_color = Colors.get_color(self.get_color())
        if self.get_hide():
            return  # this port is hidden, no need to create shapes
        if self.get_domain() == GR_MESSAGE_DOMAIN:
            pass
        elif self.get_domain() != DEFAULT_DOMAIN:
            self.line_attributes[0] = 2
        #get current rotation
        rotation = self.get_rotation()
        #get all sibling ports
        ports = self.parent.get_sources_gui() \
            if self.is_source else self.parent.get_sinks_gui()
        ports = [p for p in ports if not p.get_hide()]
        #get the max width
        self.W = max([port.W for port in ports] + [PORT_MIN_WIDTH])
        W = self.W if not self._label_hidden() else PORT_LABEL_HIDDEN_WIDTH
        #get a numeric index for this port relative to its sibling ports
        try:
            index = ports.index(self)
        except:
            if hasattr(self, '_connector_length'):
                del self._connector_length
            return
        #reverse the order of ports for these rotations
        if rotation in (180, 270):
            index = len(ports)-index-1

        port_separation = PORT_SEPARATION \
            if not self.parent.has_busses[self.is_source] \
            else max([port.H for port in ports]) + PORT_SPACING

        offset = (self.parent.H - (len(ports)-1)*port_separation - self.H)/2
        #create areas and connector coordinates
        if (self.is_sink and rotation == 0) or (self.is_source and rotation == 180):
            x = -W
            y = port_separation*index+offset
            self.add_area((x, y), (W, self.H))
            self._connector_coordinate = (x-1, y+self.H/2)
        elif (self.is_source and rotation == 0) or (self.is_sink and rotation == 180):
            x = self.parent.W
            y = port_separation*index+offset
            self.add_area((x, y), (W, self.H))
            self._connector_coordinate = (x+1+W, y+self.H/2)
        elif (self.is_source and rotation == 90) or (self.is_sink and rotation == 270):
            y = -W
            x = port_separation*index+offset
            self.add_area((x, y), (self.H, W))
            self._connector_coordinate = (x+self.H/2, y-1)
        elif (self.is_sink and rotation == 90) or (self.is_source and rotation == 270):
            y = self.parent.W
            x = port_separation*index+offset
            self.add_area((x, y), (self.H, W))
            self._connector_coordinate = (x+self.H/2, y+1+W)
        #the connector length
        self._connector_length = CONNECTOR_EXTENSION_MINIMAL + CONNECTOR_EXTENSION_INCREMENT*index

    def create_labels(self):
        """Create the labels for the socket."""
        self.layout.set_markup("""<span foreground="black" font_desc="{font}">{name}</span>""".format(
            name=Utils.encode(self.get_name()), font=PORT_FONT
        ))

    def draw(self, widget, cr):
        """
        Draw the socket with a label.
        """
        border_color = (
            Colors.HIGHLIGHT_COLOR if self.is_highlighted() else
            Colors.MISSING_BLOCK_BORDER_COLOR if self.parent.is_dummy_block else
            Colors.BORDER_COLOR
        )
        Element.draw(self, widget, cr, border_color, self._bg_color)

        if not self._areas_list or self._label_hidden():
            return  # this port is either hidden (no areas) or folded (no label)
        X, Y = self.get_coordinate()
        (x, y), _ = self._areas_list[0]
        cr.set_source_rgb(*self._bg_color)
        cr.save()
        if self.is_horizontal():
            cr.translate(x + X + (self.W - self.w) / 2, y + Y + (self.H - self.h) / 2)
        elif self.is_vertical():
            cr.translate(x + X + (self.H - self.h) / 2, y + Y + (self.W - self.w) / 2)
            cr.rotate(-90 * math.pi / 180.)
            cr.translate(-self.w, 0)
        PangoCairo.update_layout(cr, self.layout)
        PangoCairo.show_layout(cr, self.layout)
        cr.restore()

    def get_connector_coordinate(self):
        """
        Get the coordinate where connections may attach to.

        Returns:
            the connector coordinate (x, y) tuple
        """
        x, y = self._connector_coordinate
        X, Y = self.get_coordinate()
        return (x + X, y + Y)

    def get_connector_direction(self):
        """
        Get the direction that the socket points: 0,90,180,270.
        This is the rotation degree if the socket is an output or
        the rotation degree + 180 if the socket is an input.

        Returns:
            the direction in degrees
        """
        if self.is_source: return self.get_rotation()
        elif self.is_sink: return (self.get_rotation() + 180)%360

    def get_connector_length(self):
        """
        Get the length of the connector.
        The connector length increases as the port index changes.

        Returns:
            the length in pixels
        """
        return self._connector_length

    def get_rotation(self):
        """
        Get the parent's rotation rather than self.

        Returns:
            the parent's rotation
        """
        return self.parent.get_rotation()

    def move(self, delta_coor):
        """
        Move the parent rather than self.

        Args:
            delta_corr: the (delta_x, delta_y) tuple
        """
        self.parent.move(delta_coor)

    def rotate(self, direction):
        """
        Rotate the parent rather than self.

        Args:
            direction: degrees to rotate
        """
        self.parent.rotate(direction)

    def get_coordinate(self):
        """
        Get the parent's coordinate rather than self.

        Returns:
            the parents coordinate
        """
        return self.parent.get_coordinate()

    def set_highlighted(self, highlight):
        """
        Set the parent highlight rather than self.

        Args:
            highlight: true to enable highlighting
        """
        self.parent.set_highlighted(highlight)

    def is_highlighted(self):
        """
        Get the parent's is highlight rather than self.

        Returns:
            the parent's highlighting status
        """
        return self.parent.is_highlighted()

    def _label_hidden(self):
        """
        Figure out if the label should be hidden

        Returns:
            true if the label should not be shown
        """
        return self._hovering and not self._force_label_unhidden and Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active()

    def force_label_unhidden(self, enable=True):
        """
        Disable showing the label on mouse-over for this port

        Args:
            enable: true to override the mouse-over behaviour
        """
        self._force_label_unhidden = enable

    def mouse_over(self):
        """
        Called from flow graph on mouse-over
        """
        self._hovering = False
        return Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active()  # only redraw if necessary

    def mouse_out(self):
        """
        Called from flow graph on mouse-out
        """
        self._hovering = True
        return Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active()  # only redraw if necessary