testapp.py 7.2 KB

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