Imported Upstream version 3.2.2
[debian/gnuradio] / grc / gui / Element.py
1 """
2 Copyright 2007 Free Software Foundation, Inc.
3 This file is part of GNU Radio
4
5 GNU Radio Companion is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 GNU Radio Companion is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18 """
19
20 import Colors
21 import pygtk
22 pygtk.require('2.0')
23 import gtk
24 import pango
25 from Constants import LINE_SELECT_SENSITIVITY
26 from Constants import POSSIBLE_ROTATIONS
27
28 class Element(object):
29         """
30         GraphicalElement is the base class for all graphical elements.
31         It contains an X,Y coordinate, a list of rectangular areas that the element occupies,
32         and methods to detect selection of those areas.
33         """
34
35         def __init__(self, *args, **kwargs):
36                 """
37                 Make a new list of rectangular areas and lines, and set the coordinate and the rotation.
38                 """
39                 self.set_rotation(POSSIBLE_ROTATIONS[0])
40                 self.set_coordinate((0, 0))
41                 self.clear()
42                 self.set_highlighted(False)
43
44         def is_horizontal(self, rotation=None):
45                 """
46                 Is this element horizontal?
47                 If rotation is None, use this element's rotation.
48                 @param rotation the optional rotation
49                 @return true if rotation is horizontal
50                 """
51                 rotation = rotation or self.get_rotation()
52                 return rotation in (0, 180)
53
54         def is_vertical(self, rotation=None):
55                 """
56                 Is this element vertical?
57                 If rotation is None, use this element's rotation.
58                 @param rotation the optional rotation
59                 @return true if rotation is vertical
60                 """
61                 rotation = rotation or self.get_rotation()
62                 return rotation in (90, 270)
63
64         def draw(self, gc, window, border_color, bg_color):
65                 """
66                 Draw in the given window.
67                 @param gc the graphics context
68                 @param window the gtk window to draw on
69                 @param border_color the color for lines and rectangle borders
70                 @param bg_color the color for the inside of the rectangle
71                 """
72                 X,Y = self.get_coordinate()
73                 for (rX,rY),(W,H) in self.areas_dict[self.get_rotation()]:
74                         aX = X + rX
75                         aY = Y + rY
76                         gc.set_foreground(bg_color)
77                         window.draw_rectangle(gc, True, aX, aY, W, H)
78                         gc.set_foreground(border_color)
79                         window.draw_rectangle(gc, False, aX, aY, W, H)
80                 for (x1, y1),(x2, y2) in self.lines_dict[self.get_rotation()]:
81                         gc.set_foreground(border_color)
82                         window.draw_line(gc, X+x1, Y+y1, X+x2, Y+y2)
83
84         def rotate(self, rotation):
85                 """
86                 Rotate all of the areas by 90 degrees.
87                 @param rotation multiple of 90 degrees
88                 """
89                 self.set_rotation((self.get_rotation() + rotation)%360)
90
91         def clear(self):
92                 """Empty the lines and areas."""
93                 self.areas_dict = dict((rotation, list()) for rotation in POSSIBLE_ROTATIONS)
94                 self.lines_dict = dict((rotation, list()) for rotation in POSSIBLE_ROTATIONS)
95
96         def set_coordinate(self, coor):
97                 """
98                 Set the reference coordinate.
99                 @param coor the coordinate tuple (x,y)
100                 """
101                 self.coor = coor
102
103         def get_parent(self):
104                 """
105                 Get the parent of this element.
106                 @return the parent
107                 """
108                 return self.parent
109
110         def set_highlighted(self, highlighted):
111                 """
112                 Set the highlight status.
113                 @param highlighted true to enable highlighting
114                 """
115                 self.highlighted = highlighted
116
117         def is_highlighted(self):
118                 """
119                 Get the highlight status.
120                 @return true if highlighted
121                 """
122                 return self.highlighted
123
124         def get_coordinate(self):
125                 """Get the coordinate.
126                 @return the coordinate tuple (x,y)
127                 """
128                 return self.coor
129
130         def move(self, delta_coor):
131                 """
132                 Move the element by adding the delta_coor to the current coordinate.
133                 @param delta_coor (delta_x,delta_y) tuple
134                 """
135                 deltaX, deltaY = delta_coor
136                 X, Y = self.get_coordinate()
137                 self.set_coordinate((X+deltaX, Y+deltaY))
138
139         def add_area(self, rel_coor, area, rotation=None):
140                 """
141                 Add an area to the area list.
142                 An area is actually a coordinate relative to the main coordinate
143                 with a width/height pair relative to the area coordinate.
144                 A positive width is to the right of the coordinate.
145                 A positive height is above the coordinate.
146                 The area is associated with a rotation.
147                 If rotation is not specified, the element's current rotation is used.
148                 @param rel_coor (x,y) offset from this element's coordinate
149                 @param area (width,height) tuple
150                 @param rotation rotation in degrees
151                 """
152                 self.areas_dict[rotation or self.get_rotation()].append((rel_coor, area))
153
154         def add_line(self, rel_coor1, rel_coor2, rotation=None):
155                 """
156                 Add a line to the line list.
157                 A line is defined by 2 relative coordinates.
158                 Lines must be horizontal or vertical.
159                 The line is associated with a rotation.
160                 If rotation is not specified, the element's current rotation is used.
161                 @param rel_coor1 relative (x1,y1) tuple
162                 @param rel_coor2 relative (x2,y2) tuple
163                 @param rotation rotation in degrees
164                 """
165                 self.lines_dict[rotation or self.get_rotation()].append((rel_coor1, rel_coor2))
166
167         def what_is_selected(self, coor, coor_m=None):
168                 """
169                 One coordinate specified:
170                         Is this element selected at given coordinate?
171                         ie: is the coordinate encompassed by one of the areas or lines?
172                 Both coordinates specified:
173                         Is this element within the rectangular region defined by both coordinates?
174                         ie: do any area corners or line endpoints fall within the region?
175                 @param coor the selection coordinate, tuple x, y
176                 @param coor_m an additional selection coordinate.
177                 @return self if one of the areas/lines encompasses coor, else None.
178                 """
179                 #function to test if p is between a and b (inclusive)
180                 in_between = lambda p, a, b: p >= min(a, b) and p <= max(a, b)
181                 #relative coordinate
182                 x, y = [a-b for a,b in zip(coor, self.get_coordinate())]
183                 if coor_m:
184                         x_m, y_m = [a-b for a,b in zip(coor_m, self.get_coordinate())]
185                         #handle rectangular areas
186                         for (x1,y1), (w,h) in self.areas_dict[self.get_rotation()]:
187                                 if in_between(x1, x, x_m) and in_between(y1, y, y_m) or \
188                                         in_between(x1+w, x, x_m) and in_between(y1, y, y_m) or \
189                                         in_between(x1, x, x_m) and in_between(y1+h, y, y_m) or \
190                                         in_between(x1+w, x, x_m) and in_between(y1+h, y, y_m):
191                                         return self
192                         #handle horizontal or vertical lines
193                         for (x1, y1), (x2, y2) in self.lines_dict[self.get_rotation()]:
194                                 if in_between(x1, x, x_m) and in_between(y1, y, y_m) or \
195                                         in_between(x2, x, x_m) and in_between(y2, y, y_m):
196                                         return self
197                         return None
198                 else:
199                         #handle rectangular areas
200                         for (x1,y1), (w,h) in self.areas_dict[self.get_rotation()]:
201                                 if in_between(x, x1, x1+w) and in_between(y, y1, y1+h): return self
202                         #handle horizontal or vertical lines
203                         for (x1, y1), (x2, y2) in self.lines_dict[self.get_rotation()]:
204                                 if x1 == x2: x1, x2 = x1-LINE_SELECT_SENSITIVITY, x2+LINE_SELECT_SENSITIVITY
205                                 if y1 == y2: y1, y2 = y1-LINE_SELECT_SENSITIVITY, y2+LINE_SELECT_SENSITIVITY
206                                 if in_between(x, x1, x2) and in_between(y, y1, y2): return self
207                         return None
208
209         def get_rotation(self):
210                 """
211                 Get the rotation in degrees.
212                 @return the rotation
213                 """
214                 return self.rotation
215
216         def set_rotation(self, rotation):
217                 """
218                 Set the rotation in degrees.
219                 @param rotation the rotation"""
220                 if rotation not in POSSIBLE_ROTATIONS:
221                         raise Exception('"%s" is not one of the possible rotations: (%s)'%(rotation, POSSIBLE_ROTATIONS))
222                 self.rotation = rotation
223
224         def update(self):
225                 """Do nothing for the update. Dummy method."""
226                 pass