import math import gtk import gtk.gtkgl from OpenGL.GL import * from Line import Line from Circle import Circle class SketchWidget: def __init__(self, sketch): self.sketch = sketch self.view_center = (0, 0) self.view_width = 4.0 self.size = (1, 1) self.panning = False # Configuration parameters self.line_width = 0.8 self.axis_width = 1.8 self.axis_length = 50 try: # try double-buffered self.glconfig = gtk.gdkgl.Config(mode = (gtk.gdkgl.MODE_RGB | gtk.gdkgl.MODE_DOUBLE | gtk.gdkgl.MODE_DEPTH)) except gtk.gdkgl.NoMatches: # try single-buffered self.glconfig = gtk.gdkgl.Config(mode = (gtk.gdkgl.MODE_RGB | gtk.gdkgl.MODE_DEPTH)) self.widget = gtk.gtkgl.DrawingArea(self.glconfig) self.widget.connect_after('realize', self.init) self.widget.connect('configure_event', self.reshape) self.widget.connect('expose_event', self.draw) self.widget.connect('button-press-event', self.button_press_event) self.widget.connect('button-release-event', self.button_release_event) self.widget.connect('motion-notify-event', self.motion_event) self.widget.connect('scroll-event', self.scroll_event) self.widget.add_events(gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.SCROLL_MASK) def init(self, glarea): # get GLContext and GLDrawable glcontext = glarea.get_gl_context() gldrawable = glarea.get_gl_drawable() # GL calls if not gldrawable.gl_begin(glcontext): return # glEnable(GL_BLEND) # glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) # glEnable(GL_POLYGON_SMOOTH) # glEnable(GL_LINE_SMOOTH) gldrawable.gl_end() def reshape(self, glarea, event): # get GLContext and GLDrawable glcontext = glarea.get_gl_context() gldrawable = glarea.get_gl_drawable() # GL calls if not gldrawable.gl_begin(glcontext): return x, y, width, height = glarea.get_allocation() self.size = (width, height) glViewport(0, 0, width, height) glMatrixMode(GL_PROJECTION) glLoadIdentity() glOrtho(0, width, 0, height, 1.0, -1.0) glMatrixMode(GL_MODELVIEW) glLoadIdentity() gldrawable.gl_end() return True def draw(self, glarea, event): # get GLContext and GLDrawable glcontext = glarea.get_gl_context() gldrawable = glarea.get_gl_drawable() # GL calls if not gldrawable.gl_begin(glcontext): return glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) self.drawAxes() for shape in self.sketch: if isinstance(shape, Line): self.drawLine(shape, self.line_width) elif isinstance(shape, Circle): self.drawCircle(shape, self.line_width) if gldrawable.is_double_buffered(): gldrawable.swap_buffers() else: glFlush() gldrawable.gl_end() return True def ptToScreenPt(self, pt): return ((pt[0] - self.view_center[0]) / self.view_width * self.size[0] + self.size[0] / 2, (pt[1] - self.view_center[1]) / (self.view_width * self.size[1] / self.size[0]) * self.size[1] + self.size[1] / 2) def distToScreenDist(self, dist): return dist / self.view_width * self.size[0] def screenPtToPt(self, pt): return ((pt[0] - self.size[0]) / self.size[0] * self.view_width + self.view_center[0] / 2, (pt[1] - self.size[1]) / self.size[1] * (self.view_width * self.size[1] / self.size[0]) + self.view_center[1] / 2) def screenDistToDist(self, dist): return dist * self.size[0] / self.view_width def drawLine(self, shape, size): pt0 = self.ptToScreenPt(shape.getPt(0)) pt1 = self.ptToScreenPt(shape.getPt(1)) self.drawFilledLine(pt0[0], pt0[1], pt1[0], pt1[1], size) def drawCircle(self, shape, size): center = self.ptToScreenPt(shape.getCenter()) rad = self.distToScreenDist(shape.getRadius()) if rad < 30: steps = 12 elif rad < 50: steps = 16 elif rad < 100: steps = 24 elif rad < 200: steps = 36 elif rad < 400: steps = 48 else: steps = 64 step = 2 * math.pi / steps for i in range(steps + 1): angle = i * step next_angle = (i + 1) * step x0 = center[0] + rad * math.cos(angle) y0 = center[1] + rad * math.sin(angle) x1 = center[0] + rad * math.cos(next_angle) y1 = center[1] + rad * math.sin(next_angle) self.drawFilledLineUncapped(x0, y0, x1, y1, size) self.drawFilledCircle(x0, y0, size, 8) def drawFilledLine(self, x0, y0, x1, y1, size): self.drawFilledLineUncapped(x0, y0, x1, y1, size) self.drawFilledCircle(x0, y0, size, 16) self.drawFilledCircle(x1, y1, size, 16) def drawFilledLineUncapped(self, x0, y0, x1, y1, size): glBegin(GL_QUADS) angle = math.atan2(y1 - y0, x1 - x0) ninety = math.pi / 2 x_left = size * math.cos(angle + ninety) y_left = size * math.sin(angle + ninety) x_right = size * math.cos(angle - ninety) y_right = size * math.sin(angle - ninety) glVertex(x0 + x_left, y0 + y_left) glVertex(x0 + x_right, y0 + y_right) glVertex(x1 + x_right, y1 + y_right) glVertex(x1 + x_left, y1 + y_left) glEnd() def drawFilledCircle(self, x, y, radius, steps): glBegin(GL_TRIANGLE_FAN) glVertex(x, y) for i in range(steps + 1): angle = i * 2 * math.pi / steps glVertex(x + radius * math.cos(angle), y + radius * math.sin(angle)) glEnd() def drawAxes(self): glPushAttrib(GL_CURRENT_BIT) glColor(1.0, 0.4, 0.4, 1.0) cx, cy = self.ptToScreenPt((0, 0)) self.drawFilledLine(cx - self.axis_length, cy, cx + self.axis_length, cy, self.axis_width) self.drawFilledLine(cx, cy - self.axis_length, cx, cy + self.axis_length, self.axis_width) glPopAttrib() def button_press_event(self, widget, event, data = None): if event.button == 2: self.panning = True self.panning_start = (event.x, event.y) def button_release_event(self, widget, event, data = None): if event.button == 2: self.panning = False def motion_event(self, widget, event, data = None): if self.panning: start_pt = self.screenPtToPt(self.panning_start) this_pt = self.screenPtToPt((event.x, event.y)) self.view_center = (self.view_center[0] + this_pt[0] - start_pt[0], self.view_center[1] + this_pt[1] - start_pt[1]) self.panning_start = (event.x, event.y) self.widget.queue_draw_area(0, 0, self.size[0], self.size[1]) def scroll_event(self, widget, event, data = None): if event.direction == gtk.gdk.SCROLL_UP: print 'scroll up at (%d, %d)' % (event.x, event.y) elif event.direction == gtk.gdk.SCROLL_DOWN: print 'scroll down at (%d, %d)' % (event.x, event.y)