testapp.py 7.2 KB

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