00001 """
00002 Copyright 2007 Free Software Foundation, Inc.
00003 This file is part of GNU Radio
00004
00005 GNU Radio Companion is free software; you can redistribute it and/or
00006 modify it under the terms of the GNU General Public License
00007 as published by the Free Software Foundation; either version 2
00008 of the License, or (at your option) any later version.
00009
00010 GNU Radio Companion is distributed in the hope that it will be useful,
00011 but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00013 GNU General Public License for more details.
00014
00015 You should have received a copy of the GNU General Public License
00016 along with this program; if not, write to the Free Software
00017 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
00018 """
00019
00020
00021
00022
00023
00024 import Colors
00025 import pygtk
00026 pygtk.require('2.0')
00027 import gtk
00028 import pango
00029 from grc.Constants import *
00030
00031 class Element(object):
00032 """
00033 GraphicalElement is the base class for all graphical elements.
00034 It contains an X,Y coordinate, a list of rectangular areas that the element occupies,
00035 and methods to detect selection of those areas.
00036 """
00037
00038 def __init__(self, *args, **kwargs):
00039 """!
00040 Make a new list of rectangular areas and lines, and set the coordinate and the rotation.
00041 """
00042 self.set_rotation(POSSIBLE_ROTATIONS[0])
00043 self.set_coordinate((0, 0))
00044 self.clear()
00045 self.set_highlighted(False)
00046
00047 def is_horizontal(self, rotation=None):
00048 """!
00049 Is this element horizontal?
00050 If rotation is None, use this element's rotation.
00051 @param rotation the optional rotation
00052 @return true if rotation is horizontal
00053 """
00054 rotation = rotation or self.get_rotation()
00055 return rotation in (0, 180)
00056
00057 def is_vertical(self, rotation=None):
00058 """!
00059 Is this element vertical?
00060 If rotation is None, use this element's rotation.
00061 @param rotation the optional rotation
00062 @return true if rotation is vertical
00063 """
00064 rotation = rotation or self.get_rotation()
00065 return rotation in (90, 270)
00066
00067 def get_gc(self): return self._gc
00068
00069 def draw(self, window, BG_color=Colors.BG_COLOR, FG_color=Colors.FG_COLOR):
00070 """!
00071 Draw in the given window.
00072 @param window the gtk window to draw on
00073 @param BG_color the background color
00074 @param FG_color the foreground color
00075 """
00076 gc = self.get_parent().get_gc()
00077 self._gc = gc
00078 X,Y = self.get_coordinate()
00079 for (rX,rY),(W,H) in self.areas_dict[self.get_rotation()]:
00080 aX = X + rX
00081 aY = Y + rY
00082 gc.foreground = BG_color
00083 window.draw_rectangle(gc, True, aX, aY, W, H)
00084 gc.foreground = self.is_highlighted() and Colors.H_COLOR or FG_color
00085 window.draw_rectangle(gc, False, aX, aY, W, H)
00086 for (x1, y1),(x2, y2) in self.lines_dict[self.get_rotation()]:
00087 gc.foreground = self.is_highlighted() and Colors.H_COLOR or FG_color
00088 window.draw_line(gc, X+x1, Y+y1, X+x2, Y+y2)
00089
00090 def rotate(self, direction):
00091 """!
00092 Rotate all of the areas by 90 degrees.
00093 @param direction 90 or 270 degrees
00094 """
00095 self.set_rotation((self.get_rotation() + direction)%360)
00096
00097 def clear(self):
00098 """Empty the lines and areas."""
00099 self.areas_dict = dict((rotation, list()) for rotation in POSSIBLE_ROTATIONS)
00100 self.lines_dict = dict((rotation, list()) for rotation in POSSIBLE_ROTATIONS)
00101
00102 def set_coordinate(self, coor):
00103 """!
00104 Set the reference coordinate.
00105 @param coor the coordinate tuple (x,y)
00106 """
00107 self.coor = coor
00108
00109 def get_parent(self):
00110 """!
00111 Get the parent of this element.
00112 @return the parent
00113 """
00114 return self.parent
00115
00116 def set_highlighted(self, highlighted):
00117 """!
00118 Set the highlight status.
00119 @param highlighted true to enable highlighting
00120 """
00121 self.highlighted = highlighted
00122
00123 def is_highlighted(self):
00124 """!
00125 Get the highlight status.
00126 @return true if highlighted
00127 """
00128 return self.highlighted
00129
00130 def get_coordinate(self):
00131 """!Get the coordinate.
00132 @return the coordinate tuple (x,y)
00133 """
00134 return self.coor
00135
00136 def move(self, delta_coor):
00137 """!
00138 Move the element by adding the delta_coor to the current coordinate.
00139 @param delta_coor (delta_x,delta_y) tuple
00140 """
00141 deltaX, deltaY = delta_coor
00142 X, Y = self.get_coordinate()
00143 self.set_coordinate((X+deltaX, Y+deltaY))
00144
00145 def add_area(self, rel_coor, area, rotation=None):
00146 """!
00147 Add an area to the area list.
00148 An area is actually a coordinate relative to the main coordinate
00149 with a width/height pair relative to the area coordinate.
00150 A positive width is to the right of the coordinate.
00151 A positive height is above the coordinate.
00152 The area is associated with a rotation.
00153 If rotation is not specified, the element's current rotation is used.
00154 @param rel_coor (x,y) offset from this element's coordinate
00155 @param area (width,height) tuple
00156 @param rotation rotation in degrees
00157 """
00158 self.areas_dict[rotation or self.get_rotation()].append((rel_coor, area))
00159
00160 def add_line(self, rel_coor1, rel_coor2, rotation=None):
00161 """!
00162 Add a line to the line list.
00163 A line is defined by 2 relative coordinates.
00164 Lines must be horizontal or vertical.
00165 The line is associated with a rotation.
00166 If rotation is not specified, the element's current rotation is used.
00167 @param rel_coor1 relative (x1,y1) tuple
00168 @param rel_coor2 relative (x2,y2) tuple
00169 @param rotation rotation in degrees
00170 """
00171 self.lines_dict[rotation or self.get_rotation()].append((rel_coor1, rel_coor2))
00172
00173 def what_is_selected(self, coor, coor_m=None):
00174 """!
00175 One coordinate specified:
00176 Is this element selected at given coordinate?
00177 ie: is the coordinate encompassed by one of the areas or lines?
00178 Both coordinates specified:
00179 Is this element within the rectangular region defined by both coordinates?
00180 ie: do any area corners or line endpoints fall within the region?
00181 @param coor the selection coordinate, tuple x, y
00182 @param coor_m an additional selection coordinate.
00183 @return self if one of the areas/lines encompasses coor, else None.
00184 """
00185
00186 in_between = lambda p, a, b: p >= min(a, b) and p <= max(a, b)
00187
00188 x, y = [a-b for a,b in zip(coor, self.get_coordinate())]
00189 if coor_m:
00190 x_m, y_m = [a-b for a,b in zip(coor_m, self.get_coordinate())]
00191
00192 for (x1,y1), (w,h) in self.areas_dict[self.get_rotation()]:
00193 if in_between(x1, x, x_m) and in_between(y1, y, y_m) or \
00194 in_between(x1+w, x, x_m) and in_between(y1, y, y_m) or \
00195 in_between(x1, x, x_m) and in_between(y1+h, y, y_m) or \
00196 in_between(x1+w, x, x_m) and in_between(y1+h, y, y_m):
00197 return self
00198
00199 for (x1, y1), (x2, y2) in self.lines_dict[self.get_rotation()]:
00200 if in_between(x1, x, x_m) and in_between(y1, y, y_m) or \
00201 in_between(x2, x, x_m) and in_between(y2, y, y_m):
00202 return self
00203 return None
00204 else:
00205
00206 for (x1,y1), (w,h) in self.areas_dict[self.get_rotation()]:
00207 if in_between(x, x1, x1+w) and in_between(y, y1, y1+h): return self
00208
00209 for (x1, y1), (x2, y2) in self.lines_dict[self.get_rotation()]:
00210 if x1 == x2: x1, x2 = x1-CONNECTION_SELECT_SENSITIVITY, x2+CONNECTION_SELECT_SENSITIVITY
00211 if y1 == y2: y1, y2 = y1-CONNECTION_SELECT_SENSITIVITY, y2+CONNECTION_SELECT_SENSITIVITY
00212 if in_between(x, x1, x2) and in_between(y, y1, y2): return self
00213 return None
00214
00215 def get_rotation(self):
00216 """!
00217 Get the rotation in degrees.
00218 @return the rotation
00219 """
00220 return self.rotation
00221
00222 def set_rotation(self, rotation):
00223 """!
00224 Set the rotation in degrees.
00225 @param rotation the rotation"""
00226 if rotation not in POSSIBLE_ROTATIONS:
00227 raise Exception('"%s" is not one of the possible rotations: (%s)'%(rotation,POSSIBLE_ROTATIONS))
00228 self.rotation = rotation
00229
00230 def update(self):
00231 """Do nothing for the update. Dummy method."""
00232 pass
00233
00234