From a6e312c9f0e1bd690963191bb5331eaabb06b431 Mon Sep 17 00:00:00 2001 From: Simon Poirier Date: Sat, 24 Oct 2009 21:18:52 +0000 Subject: creator with viewer and non-linear editing. Conflicts: tutorius/creator.py --- (limited to 'tutorius/viewer.py') diff --git a/tutorius/viewer.py b/tutorius/viewer.py new file mode 100644 index 0000000..e0ae4a9 --- /dev/null +++ b/tutorius/viewer.py @@ -0,0 +1,406 @@ +# Copyright (C) 2009, Tutorius.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" +This module renders a widget containing a graphical representation +of a tutorial and acts as a creator proxy as it has some editing +functionality. +""" +import sys + +import gtk, gtk.gdk +import cairo +from math import pi as PI +PI2 = PI/2 + +import rsvg + +from sugar.bundle import activitybundle +from sugar.tutorius import addon +from sugar.graphics import icon +from sugar.tutorius.filters import EventFilter +from sugar.tutorius.actions import Action +import os + +# 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 + +class Viewer(object): + """ + Renders a tutorial as a sequence of blocks, each block representing either + an action or an event (transition). + + Current Viewer implementation lacks viewport management; + having many objects in a tutorial will not render properly. + """ + def __init__(self, tutorial, creator): + super(Viewer, self).__init__() + + self._tutorial = tutorial + self._creator = creator + self.alloc = None + self.click_pos = None + self.drag_pos = None + self.selection = [] + + self.win = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.win.set_size_request(400, 200) + self.win.set_gravity(gtk.gdk.GRAVITY_SOUTH_WEST) + self.win.show() + self.win.set_deletable(False) + self.win.move(0, 0) + + #vbox = gtk.VBox() + vbox = gtk.ScrolledWindow() + self.win.add(vbox) + + canvas = gtk.DrawingArea() + #vbox.pack_start(canvas) + vbox.add_with_viewport(canvas) # temp + canvas.set_app_paintable(True) + canvas.connect_after("expose-event", self.on_viewer_expose, tutorial._states) + canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK \ + |gtk.gdk.BUTTON_MOTION_MASK \ + |gtk.gdk.BUTTON_RELEASE_MASK \ + |gtk.gdk.KEY_PRESS_MASK) + canvas.connect('button-press-event', self._on_click) + # drag-select disabled, for now + #canvas.connect('motion-notify-event', self._on_drag) + canvas.connect('button-release-event', self._on_drag_end) + canvas.connect('key-press-event', self._on_key_press) + + canvas.set_flags(gtk.HAS_FOCUS|gtk.CAN_FOCUS) + canvas.grab_focus() + + #self.scroll = gtk.HScrollbar() + #vbox.pack_end(self.scroll, False) + + self.win.show_all() + canvas.set_size_request(2048, 180) # FIXME + + def destroy(self): + self.win.destroy() + + + def _paint_state(self, 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 = self.alloc.height + + new_insert_point = None + cur_state = 'INIT' + + # FIXME: get app when we have a model that supports it + cur_app = 'Calculate' + app_start = ctx.get_matrix() + try: + state = states[cur_state] + except KeyError: + state = None + + while state: + new_app = 'Calculate' + if new_app != cur_app: + ctx.save() + ctx.set_matrix(app_start) + self._render_app_hints(ctx, cur_app) + ctx.restore() + app_start = ctx.get_matrix() + ctx.translate(BLOCK_PADDING, 0) + cur_app = new_app + + action_list = state.get_action_list() + if action_list: + local_height = (block_max_height - BLOCK_PADDING)/len(action_list) - BLOCK_PADDING + ctx.save() + for action in action_list: + origin = tuple(ctx.get_matrix())[-2:] + if self.click_pos and \ + self.click_pos[0]-BLOCK_WIDTHorigin[0]: + self.selection.append(action) + self.render_action(ctx, block_width, local_height, action) + ctx.translate(0, local_height+BLOCK_PADDING) + + ctx.restore() + ctx.translate(BLOCK_WIDTH, 0) + + # insertion cursor painting made from two opposed triangles + # joined by a line. + if state.name == self._creator.get_insertion_point(): + ctx.save() + bp2 = BLOCK_PADDING/2 + ctx.move_to(-bp2, 0) + ctx.line_to(-BLOCK_PADDING-bp2, -BLOCK_PADDING) + ctx.line_to(bp2, -BLOCK_PADDING) + ctx.line_to(-bp2, 0) + + ctx.line_to(-bp2, block_max_height-2*BLOCK_PADDING) + ctx.line_to(bp2, block_max_height-BLOCK_PADDING) + ctx.line_to(-BLOCK_PADDING-bp2, block_max_height-BLOCK_PADDING) + ctx.line_to(-bp2, block_max_height-2*BLOCK_PADDING) + + ctx.line_to(-bp2, BLOCK_PADDING) + ctx.set_source_rgb(1.0, 1.0, 0.0) + ctx.stroke_preserve() + ctx.fill() + ctx.restore() + + + event_list = state.get_event_filter_list() + if event_list: + local_height = (block_max_height - BLOCK_PADDING)/len(event_list) - BLOCK_PADDING + ctx.save() + for event in event_list: + origin = tuple(ctx.get_matrix())[-2:] + if self.click_pos and \ + self.click_pos[0]-BLOCK_WIDTHorigin[0]: + self.selection.append(event) + self.render_event(ctx, block_width, local_height, event) + ctx.translate(0, local_height+BLOCK_PADDING) + + ctx.restore() + ctx.translate(BLOCK_WIDTH, 0) + + # FIXME point to next state in state, as it would highlight + # the "happy path". + cur_state = event_list[0].get_next_state() + + if (not new_insert_point) and self.click_pos: + origin = tuple(ctx.get_matrix())[-2:] + if self.click_pos[0]