#!/usr/bin/env python from __future__ import division import gtk from threading import Thread from math import pi, tan import src as mt from utils import BoundingBoxArea RED = 1, 0, 0 GREEN = 0, 1, 0 BLUE = 0, 0, 1 WHITE = 1, 1, 1 BLACK = 0, 0, 0 class Rectangle(mt.RectangularArea): def __init__(self, x, y, width, height, color=(1, 0, 0)): super(Rectangle, self).__init__(x, y, width, height) self.color = color self.on_drag(self.handle_drag) def handle_drag(self, g): tx, ty = g.get_translation() self.translate(tx, ty) refresh() def draw(self, cr): cr.rectangle(self.x, self.y, self.width, self.height) cr.set_source_rgb(*self.color) cr.fill() class Polygon(BoundingBoxArea): def __init__(self, x, y, points, color=BLUE, border_color=RED): super(Polygon, self).__init__(x, y, points) self.fill_color = color self.border_color = border_color self.on_drag(self.handle_drag) self.on_pinch(self.handle_pinch) self.on_rotate(self.handle_rotate) def handle_drag(self, g): tx, ty = g.get_translation() self.translate(tx, ty) refresh() def handle_pinch(self, g): cx, cy = g.get_position() self.scale_points(g.get_scale(), cx, cy) self.update_bounds() refresh() def handle_rotate(self, g): cx, cy = g.get_position() self.rotate_points(g.get_angle(), cx, cy) self.update_bounds() refresh() def draw(self, cr): # Draw bounding box if draw_bounding_boxes: cr.rectangle(self.x, self.y, self.width, self.height) cr.set_source_rgb(*self.border_color) cr.set_line_width(3) cr.stroke() # Fill polygon cr.translate(self.x, self.y) cr.new_path() for x, y in zip(*self.points): cr.line_to(x, y) cr.set_source_rgb(*self.fill_color) cr.fill() fullscreen = False draw_bounding_boxes = draw_touch_points = True W, H = mt.screen.screen_size def create_context_window(w, h, callback): def create_context(area, event): """Add Cairo context to GTK window and draw state.""" global cr cr = area.window.cairo_create() draw() def move_window(win, event): """Synchronize root multi-touch area with GTK window.""" root.set_position(*event.get_coords()) root.set_size(event.width, event.height) overlay.set_size(event.width, event.height) draw() def handle_key(win, event): """Handle key event. 'f' toggles fullscreen, 'q' exits the program, 'b' toggles bounding boxes, 'p' toggles touch points.""" if event.keyval >= 256: return key = chr(event.keyval) if key == 'f': global fullscreen (win.unfullscreen if fullscreen else win.fullscreen)() fullscreen = not fullscreen elif key == 'q': quit() elif key == 'b': global draw_bounding_boxes draw_bounding_boxes = not draw_bounding_boxes refresh() elif key == 'p': global draw_touch_points draw_touch_points = not draw_touch_points refresh() # Root area (will be synchronized with GTK window) global root, overlay root = mt.RectangularArea(0, 0, w, h) overlay = mt.RectangularArea(0, 0, w, h) # GTK window global window window = gtk.Window() window.set_title('Cairo test') window.connect('destroy', quit) window.connect('key-press-event', handle_key) window.connect('configure-event', move_window) window.connect('show', callback) if fullscreen: window.fullscreen() # Drawing area, needed by cairo context for drawing area = gtk.DrawingArea() area.set_size_request(w, h) area.connect('expose-event', create_context) window.add(area) area.show() window.show() def draw(*args): if not cr: return # Background cr.rectangle(0, 0, *root.get_size()) cr.set_source_rgb(*BLACK) cr.fill() # Drawable objects (use save and restore to allow transformations) for obj in draw_objects: cr.save() obj.draw(cr) cr.restore() if draw_touch_points: ox, oy = root.get_position() cr.set_source_rgb(*WHITE) for x, y in touch_points.itervalues(): x -= ox y -= oy cr.set_line_width(3) cr.arc(x, y, 20, 0, 2 * pi) cr.stroke() cr.set_line_width(1) cr.move_to(x - 8, y) cr.line_to(x + 8, y) cr.move_to(x, y - 8) cr.line_to(x, y + 8) cr.stroke() def refresh(): window.queue_draw() def quit(*args): gtk.main_quit() # Initialization window = cr = root = overlay = None draw_objects = [] touch_points = {} def triangle_height(width): return abs(.5 * width * tan(2 / 3 * pi)) def on_show(window): def root_dtap(g): print 'double tapped on root' root.on_double_tap(root_dtap) # Create blue rectangle x, y, w, h = 0, 0, 250, 150 rect = Polygon(x, y, [(0, 0), (0, h), (w, h), (w, 0)]) draw_objects.append(rect) root.add_area(rect) def rect_tap(g): print 'tapped on rectangle' rect.on_tap(rect_tap, propagate_up_event=False) # Create green triangle x, y, w = 400, 400, 200 h = triangle_height(w) triangle = Polygon(x, y, [(0, h), (w, h), (w / 2, 0)], color=GREEN) draw_objects.append(triangle) root.add_area(triangle) # Overlay catches basic events def handle_down(gesture): point = gesture.get_event().get_touch_object() touch_points[point.get_id()] = point.get_position() if draw_touch_points: refresh() def handle_up(gesture): point = gesture.get_event().get_touch_object() del touch_points[point.get_id()] if draw_touch_points: refresh() overlay.on_point_down(handle_down) overlay.on_point_move(handle_down) overlay.on_point_up(handle_up) root.add_area(overlay) if __name__ == '__main__': from parse_arguments import create_parser, parse_args # Parse arguments parser = create_parser() parser.add_argument('-f', '--fullscreen', action='store_true', default=False, help='run in fullscreen initially') args = parse_args(parser) fullscreen = args.fullscreen # Create a window with a Cairo context in it and a multi-touch area # syncronized with it create_context_window(640, 460, on_show) # Run multi-touch gesture server in separate thread driver = mt.create_driver(root) mt_thread = Thread(target=driver.start) mt_thread.daemon = True mt_thread.start() # Initialize threads in GTK so that the thread started above will work gtk.gdk.threads_init() # Start main loop in current thread gtk.main()