testapp.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. #!/usr/bin/env python
  2. from __future__ import division
  3. import gtk
  4. from threading import Thread
  5. from math import pi, tan
  6. import src as mt
  7. from utils import BoundingBoxArea
  8. RED = 1, 0, 0
  9. GREEN = 0, 1, 0
  10. BLUE = 0, 0, 1
  11. WHITE = 1, 1, 1
  12. BLACK = 0, 0, 0
  13. class Rectangle(mt.RectangularArea):
  14. def __init__(self, x, y, width, height, color=(1, 0, 0)):
  15. super(Rectangle, self).__init__(x, y, width, height)
  16. self.color = color
  17. self.on_drag(self.handle_drag)
  18. def handle_drag(self, g):
  19. tx, ty = g.get_translation()
  20. self.translate(tx, ty)
  21. refresh()
  22. def draw(self, cr):
  23. cr.rectangle(self.x, self.y, self.width, self.height)
  24. cr.set_source_rgb(*self.color)
  25. cr.fill()
  26. class Polygon(BoundingBoxArea):
  27. def __init__(self, x, y, points, color=BLUE, border_color=RED):
  28. super(Polygon, self).__init__(x, y, points)
  29. self.fill_color = color
  30. self.border_color = border_color
  31. self.on_drag(self.handle_drag)
  32. self.on_pinch(self.handle_pinch)
  33. self.on_rotate(self.handle_rotate)
  34. def handle_drag(self, g):
  35. tx, ty = g.get_translation()
  36. self.translate(tx, ty)
  37. refresh()
  38. def handle_pinch(self, g):
  39. cx, cy = g.get_position()
  40. self.scale_points(g.get_scale(), cx, cy)
  41. self.update_bounds()
  42. refresh()
  43. def handle_rotate(self, g):
  44. cx, cy = g.get_position()
  45. self.rotate_points(g.get_angle(), cx, cy)
  46. self.update_bounds()
  47. refresh()
  48. def draw(self, cr):
  49. # Draw bounding box
  50. if draw_bounding_boxes:
  51. cr.rectangle(self.x, self.y, self.width, self.height)
  52. cr.set_source_rgb(*self.border_color)
  53. cr.set_line_width(3)
  54. cr.stroke()
  55. # Fill polygon
  56. cr.translate(self.x, self.y)
  57. cr.new_path()
  58. for x, y in zip(*self.points):
  59. cr.line_to(x, y)
  60. cr.set_source_rgb(*self.fill_color)
  61. cr.fill()
  62. fullscreen = False
  63. draw_bounding_boxes = draw_touch_points = True
  64. W, H = mt.screen.screen_size
  65. def create_context_window(w, h, callback):
  66. def create_context(area, event):
  67. """Add Cairo context to GTK window and draw state."""
  68. global cr
  69. cr = area.window.cairo_create()
  70. draw()
  71. def move_window(win, event):
  72. """Synchronize root multi-touch area with GTK window."""
  73. root.set_position(*event.get_coords())
  74. root.set_size(event.width, event.height)
  75. overlay.set_size(event.width, event.height)
  76. draw()
  77. def handle_key(win, event):
  78. """Handle key event. 'f' toggles fullscreen, 'q' exits the program, 'b'
  79. toggles bounding boxes, 'p' toggles touch points."""
  80. if event.keyval >= 256:
  81. return
  82. key = chr(event.keyval)
  83. if key == 'f':
  84. global fullscreen
  85. (win.unfullscreen if fullscreen else win.fullscreen)()
  86. fullscreen = not fullscreen
  87. elif key == 'q':
  88. quit()
  89. elif key == 'b':
  90. global draw_bounding_boxes
  91. draw_bounding_boxes = not draw_bounding_boxes
  92. refresh()
  93. elif key == 'p':
  94. global draw_touch_points
  95. draw_touch_points = not draw_touch_points
  96. refresh()
  97. # Root area (will be synchronized with GTK window)
  98. global root, overlay
  99. root = mt.RectangularArea(0, 0, w, h)
  100. overlay = mt.RectangularArea(0, 0, w, h)
  101. # GTK window
  102. global window
  103. window = gtk.Window()
  104. window.set_title('Cairo test')
  105. window.connect('destroy', quit)
  106. window.connect('key-press-event', handle_key)
  107. window.connect('configure-event', move_window)
  108. window.connect('show', callback)
  109. if fullscreen:
  110. window.fullscreen()
  111. # Drawing area, needed by cairo context for drawing
  112. area = gtk.DrawingArea()
  113. area.set_size_request(w, h)
  114. area.connect('expose-event', create_context)
  115. window.add(area)
  116. area.show()
  117. window.show()
  118. def draw(*args):
  119. if not cr:
  120. return
  121. # Background
  122. cr.rectangle(0, 0, *root.get_size())
  123. cr.set_source_rgb(*BLACK)
  124. cr.fill()
  125. # Drawable objects (use save and restore to allow transformations)
  126. for obj in draw_objects:
  127. cr.save()
  128. obj.draw(cr)
  129. cr.restore()
  130. if draw_touch_points:
  131. ox, oy = root.get_position()
  132. cr.set_source_rgb(*WHITE)
  133. for x, y in touch_points.itervalues():
  134. x -= ox
  135. y -= oy
  136. cr.set_line_width(3)
  137. cr.arc(x, y, 20, 0, 2 * pi)
  138. cr.stroke()
  139. cr.set_line_width(1)
  140. cr.move_to(x - 8, y)
  141. cr.line_to(x + 8, y)
  142. cr.move_to(x, y - 8)
  143. cr.line_to(x, y + 8)
  144. cr.stroke()
  145. def refresh():
  146. window.queue_draw()
  147. def quit(*args):
  148. gtk.main_quit()
  149. # Initialization
  150. window = cr = root = overlay = None
  151. draw_objects = []
  152. touch_points = {}
  153. def triangle_height(width):
  154. return abs(.5 * width * tan(2 / 3 * pi))
  155. def on_show(window):
  156. def root_dtap(g): print 'double tapped on root'
  157. root.on_double_tap(root_dtap)
  158. # Create blue rectangle
  159. x, y, w, h = 0, 0, 250, 150
  160. rect = Polygon(x, y, [(0, 0), (0, h), (w, h), (w, 0)])
  161. draw_objects.append(rect)
  162. root.add_area(rect)
  163. def rect_tap(g): print 'tapped on rectangle'
  164. rect.on_tap(rect_tap, propagate_up_event=False)
  165. # Create green triangle
  166. x, y, w = 400, 400, 200
  167. h = triangle_height(w)
  168. triangle = Polygon(x, y, [(0, h), (w, h), (w / 2, 0)], color=GREEN)
  169. draw_objects.append(triangle)
  170. root.add_area(triangle)
  171. # Overlay catches basic events
  172. def handle_down(gesture):
  173. point = gesture.get_event().get_touch_object()
  174. touch_points[point.get_id()] = point.get_position()
  175. if draw_touch_points:
  176. refresh()
  177. def handle_up(gesture):
  178. point = gesture.get_event().get_touch_object()
  179. del touch_points[point.get_id()]
  180. if draw_touch_points:
  181. refresh()
  182. overlay.on_point_down(handle_down)
  183. overlay.on_point_move(handle_down)
  184. overlay.on_point_up(handle_up)
  185. root.add_area(overlay)
  186. if __name__ == '__main__':
  187. from parse_arguments import create_parser, parse_args
  188. # Parse arguments
  189. parser = create_parser()
  190. parser.add_argument('-f', '--fullscreen', action='store_true', default=False,
  191. help='run in fullscreen initially')
  192. args = parse_args(parser)
  193. fullscreen = args.fullscreen
  194. # Create a window with a Cairo context in it and a multi-touch area
  195. # syncronized with it
  196. create_context_window(800, 600, on_show)
  197. # Run multi-touch gesture server in separate thread
  198. driver = mt.create_driver(root)
  199. mt_thread = Thread(target=driver.start)
  200. mt_thread.daemon = True
  201. mt_thread.start()
  202. # Initialize threads in GTK so that the thread started above will work
  203. gtk.gdk.threads_init()
  204. # Start main loop in current thread
  205. gtk.main()