From 09b2ea3369df967309f030f9196c2f9861bc1b2c Mon Sep 17 00:00:00 2001 From: Simon Poirier Date: Sun, 27 Sep 2009 19:08:24 +0000 Subject: -t option to test runner. added prototype viewer under tests --- (limited to 'tests') diff --git a/tests/skip b/tests/skip index 2061a09..882d14d 100644 --- a/tests/skip +++ b/tests/skip @@ -1,2 +1,3 @@ run-tests.py overlaytests.py +viewer.py diff --git a/tests/viewer.py b/tests/viewer.py new file mode 100755 index 0000000..6c564bf --- /dev/null +++ b/tests/viewer.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python +import sys +import os +SUGAR_PREFIX = os.getenv('SUGAR_PREFIX') +sys.path.append(SUGAR_PREFIX+'/lib/python2.6/site-packages') + +import gtk, gtk.gdk +import cairo +from math import pi as PI +PI2 = PI/2 + +import rsvg + +from sugar.bundle import activitybundle +ACTIVITY_PATH = '%s/share/sugar/activities/'%SUGAR_PREFIX +#ACTIVITY_PATH = os.getenv('SUGAR_BUNDLE_PATH') + +# FIXME ideally, apps scale correctly and we should use proportional positions +X_WIDTH = 800 +X_HEIGHT = 600 +ACTION_WIDTH = 100 +ACTION_HEIGHT = 70 + +# block look +BLOCK_PADDING = 5 +BLOCK_WIDTH = 100 +BLOCK_CORNERS = 10 +BLOCK_INNER_PAD = 10 + +SNAP_WIDTH = BLOCK_WIDTH - BLOCK_PADDING - BLOCK_INNER_PAD*2 +SNAP_HEIGHT = SNAP_WIDTH*X_HEIGHT/X_WIDTH +SNAP_SCALE = float(SNAP_WIDTH)/X_WIDTH + +# load data structure +states = dict( + INIT=dict( + next='state1', + app='Browse', + actions=[dict(name='message', pos=[20,5])], + events=[{'name':'click', 'pos':(200,200)}], + ), + state1=dict( + next='state2', + app='Browse', + actions=[dict(name='mock')], + events=[{'name':'click', 'pos':(200,100)},{'pos':(200,200), 'name':'keyboard'}], + ), + state2=dict( + next='state3', + app='Write', + actions=[dict(name='flash', pos=[2,5]), dict(name='text', pos=[400,500])], + events=[{'name':'keyboard'}], + ), + state3=dict( + next='state4', + app='Write', + actions=[dict(name='message', pos=[2,5]), dict(name='hilight', pos=[600,100]), dict(name='message', pos=[300,500])], + events=[{'name':'click'}], + ), + state4=dict( + app='Browse', + next=None, + actions=[dict(name='message', pos=[200,5])], + events=[{'name':'scratch&smell'}], + ), +) + +def _paint_state(ctx, states): + """ + Paints a tutorius fsm state in a cairo context. + Final context state will be shifted by the size of the graphics. + """ + block_width = BLOCK_WIDTH - BLOCK_PADDING + block_max_height = alloc.height + + cur_state = 'INIT' + cur_app = states[cur_state]['app'] + app_start = ctx.get_matrix() + while cur_state: + state = states[cur_state] + new_app = state['app'] + if new_app != cur_app: + ctx.save() + ctx.set_matrix(app_start) + _render_app_hints(ctx, cur_app) + ctx.restore() + app_start = ctx.get_matrix() + ctx.translate(BLOCK_PADDING, 0) + cur_app = new_app + + local_height = (block_max_height - BLOCK_PADDING)/len(state['actions']) - BLOCK_PADDING + ctx.save() + for action in state['actions']: + origin = tuple(ctx.get_matrix())[-2:] + if click_pos and \ + click_pos[0]-BLOCK_WIDTHorigin[0]: + selection.append(action) + render_action(ctx, block_width, local_height, action) + ctx.translate(0, local_height+BLOCK_PADDING) + + ctx.restore() + ctx.translate(BLOCK_WIDTH, 0) + local_height = (block_max_height - BLOCK_PADDING)/len(state['events']) - BLOCK_PADDING + ctx.save() + for event in state['events']: + origin = tuple(ctx.get_matrix())[-2:] + if click_pos and \ + click_pos[0]-BLOCK_WIDTHorigin[0]: + selection.append(event) + render_event(ctx, block_width, local_height, event) + ctx.translate(0, local_height+BLOCK_PADDING) + + ctx.restore() + ctx.translate(BLOCK_WIDTH, 0) + + # FIXME use special notation for happy path, not chaining + cur_state = state['next'] + yield True + + ctx.set_matrix(app_start) + _render_app_hints(ctx, cur_app) + + yield False + +alloc = None +click_pos = None +drag_pos = None +selection = [] + +def _render_snapshot(ctx, elem): + ctx.set_source_rgba(1.0, 1.0, 1.0, 0.5) + ctx.rectangle(0, 0, SNAP_WIDTH, SNAP_HEIGHT) + ctx.fill_preserve() + ctx.stroke() + + pos = elem.get('pos') + if pos: + # FIXME this size approximation is fine, but I believe we could + # do better. + ctx.scale(SNAP_SCALE, SNAP_SCALE) + ctx.rectangle(pos[0], pos[1], ACTION_WIDTH, ACTION_HEIGHT) + ctx.fill_preserve() + ctx.stroke() + +def _render_app_hints(ctx, appname): + ctx.set_source_rgb(0.0, 0.0, 0.0) + ctx.set_dash((1,1,0,0), 1) + ctx.move_to(0, 0) + ctx.line_to(0, alloc.height) + ctx.stroke() + ctx.set_dash(tuple(), 1) + + icon_path = activitybundle.ActivityBundle( + os.path.join(ACTIVITY_PATH, appname+'.activity')).get_icon() + icon = rsvg.Handle(icon_path) + ctx.save() + ctx.translate(-15, 0) + ctx.scale(0.5, 0.5) + icon_surf = icon.render_cairo(ctx) + ctx.restore() + + +def render_action(ctx, width, height, action): + ctx.save() + inner_width = width-(BLOCK_CORNERS<<1) + inner_height = height-(BLOCK_CORNERS<<1) + + paint_border = ctx.rel_line_to + filling = cairo.LinearGradient(0, 0, 0, inner_height) + if action not in selection: + filling.add_color_stop_rgb(0.0, 0.7, 0.7, 1.0) + filling.add_color_stop_rgb(1.0, 0.1, 0.1, 0.8) + else: + filling.add_color_stop_rgb(0.0, 0.4, 0.4, 0.8) + filling.add_color_stop_rgb(1.0, 0.0, 0.0, 0.5) + tracing = cairo.LinearGradient(0, 0, 0, inner_height) + tracing.add_color_stop_rgb(0.0, 1.0, 1.0, 1.0) + tracing.add_color_stop_rgb(1.0, 0.2, 0.2, 0.2) + + ctx.move_to(BLOCK_CORNERS, 0) + paint_border(inner_width, 0) + ctx.arc(inner_width+BLOCK_CORNERS, BLOCK_CORNERS, BLOCK_CORNERS, -PI2, 0.0) + ctx.arc(inner_width+BLOCK_CORNERS, inner_height+BLOCK_CORNERS, BLOCK_CORNERS, 0.0, PI2) + ctx.arc(BLOCK_CORNERS, inner_height+BLOCK_CORNERS, BLOCK_CORNERS, PI2, PI) + ctx.arc(BLOCK_CORNERS, BLOCK_CORNERS, BLOCK_CORNERS, -PI, -PI2) + + ctx.set_source(tracing) + ctx.stroke_preserve() + ctx.set_source(filling) + ctx.fill() + + ctx.save() + ctx.set_source_rgb(0, 0, 0) + ctx.move_to(BLOCK_INNER_PAD, BLOCK_INNER_PAD) + ctx.show_text(action['name']) + ctx.stroke() + ctx.restore() + + ctx.translate(BLOCK_INNER_PAD, (height-SNAP_HEIGHT)/2) + _render_snapshot(ctx, action) + + ctx.restore() + +def render_event(ctx, width, height, event): + ctx.save() + inner_width = width-(BLOCK_CORNERS<<1) + inner_height = height-(BLOCK_CORNERS<<1) + + filling = cairo.LinearGradient(0, 0, 0, inner_height) + if event not in selection: + filling.add_color_stop_rgb(0.0, 1.0, 0.8, 0.6) + filling.add_color_stop_rgb(1.0, 1.0, 0.6, 0.2) + else: + filling.add_color_stop_rgb(0.0, 0.8, 0.6, 0.4) + filling.add_color_stop_rgb(1.0, 0.6, 0.4, 0.1) + tracing = cairo.LinearGradient(0, 0, 0, inner_height) + tracing.add_color_stop_rgb(0.0, 1.0, 1.0, 1.0) + tracing.add_color_stop_rgb(1.0, 0.3, 0.3, 0.3) + + ctx.move_to(BLOCK_CORNERS, 0) + ctx.rel_line_to(inner_width, 0) + ctx.rel_line_to(BLOCK_CORNERS, BLOCK_CORNERS) + ctx.rel_line_to(0, inner_height) + ctx.rel_line_to(-BLOCK_CORNERS, BLOCK_CORNERS) + ctx.rel_line_to(-inner_width, 0) + ctx.rel_line_to(-BLOCK_CORNERS, -BLOCK_CORNERS) + ctx.rel_line_to(0, -inner_height) + ctx.close_path() + + ctx.set_source(tracing) + ctx.stroke_preserve() + ctx.set_source(filling) + ctx.fill() + + ctx.set_source_rgb(0, 0, 0) + ctx.move_to(BLOCK_INNER_PAD, BLOCK_INNER_PAD) + ctx.show_text(event['name']) + ctx.stroke() + + ctx.translate(BLOCK_INNER_PAD, (height-SNAP_HEIGHT)/2) + _render_snapshot(ctx, event) + + ctx.restore() + +def on_viewer_expose(widget, evt): + ctx = widget.window.cairo_create() + global alloc + alloc = widget.get_allocation() + ctx.set_source_pixmap(widget.window, + widget.allocation.x, + widget.allocation.y) + + #draw no more than our expose event intersects our child + region = gtk.gdk.region_rectangle(widget.allocation) + r = gtk.gdk.region_rectangle(evt.area) + region.intersect(r) + ctx.region (region) + ctx.clip() + ctx.paint() + + ctx.translate(BLOCK_PADDING, BLOCK_PADDING) + + painter = _paint_state(ctx, states) + while painter.next(): pass + + if click_pos and drag_pos: + ctx.set_matrix(cairo.Matrix()) + ctx.rectangle(click_pos[0], click_pos[1], \ + drag_pos[0]-click_pos[0], drag_pos[1]-click_pos[1]) + ctx.set_source_rgba(0, 0, 1, 0.5) + ctx.fill_preserve() + ctx.stroke() + + return False + +def _on_click(widget, evt): + # the rendering pipeline will work out the click validation process + global selection, click_pos, drag_pos + drag_pos = None + drag_pos = click_pos = evt.get_coords() + widget.queue_draw() + + widget.drag_source_set(gtk.gdk.BUTTON1_MASK, (('view', gtk.TARGET_SAME_APP, 42),), gtk.gdk.ACTION_DEFAULT) + widget.drag_source_set_icon_pixbuf(gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 100, 200).get_from_drawable(widget.window, widget.window.get_colormap(), int(click_pos[0]), 0, 0,0, 100,200)) + + selection = [] + +def _on_drag(widget, evt): + global selection, click_pos, drag_pos + drag_pos = evt.get_coords() + widget.queue_draw() + + +#if __name__=='__main__': +if True: + win = gtk.Window(gtk.WINDOW_TOPLEVEL) + win.set_size_request(1000, 200) + win.connect("destroy", gtk.mainquit) + + canvas = gtk.DrawingArea() + win.add(canvas) + canvas.set_app_paintable(True) + canvas.connect_after("expose-event", on_viewer_expose) + canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK|gtk.gdk.BUTTON_MOTION_MASK) + canvas.connect('button-press-event', _on_click) + canvas.connect('motion-notify-event', _on_drag) + + win.show_all() + gtk.main() + + + -- cgit v0.9.1