Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJCTutorius <charlie@tutorius-dev.(none)>2009-11-06 01:13:26 (GMT)
committer JCTutorius <charlie@tutorius-dev.(none)>2009-11-06 01:13:26 (GMT)
commit29aea8f7d71d5584e011a6955999e37efe6343f9 (patch)
treeb2d59c925adf470d89f40458b5656219913bae31
parent7035c6e281332b1688c59877ac78516a0dd4635d (diff)
parent69e3f4598a8b1a4e3d20edcae524b05a8cc7f330 (diff)
Merge branch 'master' of gitorious@git.sugarlabs.org:tutorius/mainlinelp458452
-rw-r--r--tutorius/creator.py173
-rw-r--r--tutorius/dbustools.py15
-rw-r--r--tutorius/viewer.py119
3 files changed, 146 insertions, 161 deletions
diff --git a/tutorius/creator.py b/tutorius/creator.py
index c477056..f59f320 100644
--- a/tutorius/creator.py
+++ b/tutorius/creator.py
@@ -26,14 +26,15 @@ import gtk.glade
import gobject
from gettext import gettext as T
+import uuid
import os
-from sugar.graphics import icon
-import copy
+from sugar.graphics import icon, style
from . import overlayer, gtkutils, actions, vault, properties, addon
from . import filters
from .services import ObjectStore
-from .core import Tutorial, FiniteStateMachine, State
+from .core import State
+from .tutorial import Tutorial
from . import viewer
class Creator(object):
@@ -50,10 +51,11 @@ class Creator(object):
"""
self._activity = activity
if not tutorial:
- self._tutorial = FiniteStateMachine('Untitled')
- self._state = State(name='INIT')
- self._tutorial.add_state(self._state)
- self._state_counter = 1
+ self._tutorial = Tutorial('Untitled')
+ self._state = self._tutorial.add_state()
+ self._tutorial.update_transition(
+ transition_name=self._tutorial.INITIAL_TRANSITION_NAME,
+ new_state=self._state)
else:
self._tutorial = tutorial
# TODO load existing tutorial; unused yet
@@ -110,30 +112,17 @@ class Creator(object):
"""
Removes the first instance of specified action from the tutorial.
- @param action: the action object to remove from the tutorial
+ @param action: the action name
@returns: True if successful, otherwise False.
"""
- state = self._tutorial.get_state_by_name("INIT")
-
- while True:
- state_actions = state.get_action_list()
- for fsm_action in state_actions:
- if fsm_action is action:
- state.clear_actions()
- if state is self._state:
- fsm_action.exit_editmode()
- state_actions.remove(fsm_action)
- self.set_insertion_point(state.name)
- for keep_action in state_actions:
- state.add_action(keep_action)
- return True
-
- ev_list = state.get_event_filter_list()
- if ev_list:
- state = self._tutorial.get_state_by_name(ev_list[0][1])
- continue
-
+ action_obj = self._tutorial.get_action_dict(self._state)\
+ .get(action, None)
+ if not action_obj:
return False
+ action_obj.exit_editmode()
+ self._tutorial.delete_action(action)
+ self._overview.win.queue_draw()
+ return True
def delete_state(self):
"""
@@ -143,49 +132,25 @@ class Creator(object):
@returns: True if successful, otherwise False.
"""
- if not self._state.get_event_filter_list():
+ if self._state in (self._tutorial.INIT, self._tutorial.END):
# last state cannot be removed
return False
- state = self._tutorial.get_state_by_name("INIT")
- ev_list = state.get_event_filter_list()
- if state is self._state:
- next_state = self._tutorial.get_state_by_name(ev_list[0][1])
- self.set_insertion_point(next_state.name)
- self._tutorial.remove_state(state.name)
- self._tutorial.remove_state(next_state.name)
- next_state.name = "INIT"
- self._tutorial.add_state(next_state)
- return True
-
- # loop to repair links from deleted state
- while ev_list:
- next_state = self._tutorial.get_state_by_name(ev_list[0][1])
- if next_state is self._state:
- # the tutorial will flush the event filters. We'll need to
- # clear and re-add them.
- self._tutorial.remove_state(self._state.name)
- state.clear_event_filters()
- self._update_next_state(state, ev_list[0][0], next_state.get_event_filter_list()[0][1])
- for ev, next_state in ev_list:
- state.add_event_filter(ev, next_state)
-
- self.set_insertion_point(ev_list[0][1])
- return True
-
- state = next_state
- ev_list = state.get_event_filter_list()
- return False
+ remove_state = self._state
+ next_state = self._tutorial\
+ .get_following_states_dict(remove_state).keys()[0]
+ self.set_insertion_point(next_state)
+ return bool(self._tutorial.delete_state(remove_state))
def get_insertion_point(self):
- return self._state.name
+ return self._state
def set_insertion_point(self, state_name):
- for action in self._state.get_action_list():
+ for action in self._tutorial.get_action_dict(self._state).values():
action.exit_editmode()
- self._state = self._tutorial.get_state_by_name(state_name)
- self._overview.win.queue_draw()
- state_actions = self._state.get_action_list()
+
+ self._state = state_name
+ state_actions = self._tutorial.get_action_dict(self._state).values()
for action in state_actions:
action.enter_editmode()
action._drag._eventbox.connect_after(
@@ -196,6 +161,8 @@ class Creator(object):
else:
self._propedit.action = None
+ self._overview.win.queue_draw()
+
def _evfilt_cb(self, menuitem, event):
"""
@@ -249,7 +216,7 @@ class Creator(object):
action_type = self._propedit.actions_list[path][ToolBox.ICON_NAME]
action = addon.create(action_type)
action.enter_editmode()
- self._state.add_action(action)
+ self._tutorial.add_action(self._state, action)
# FIXME: replace following with event catching
action._drag._eventbox.connect_after(
"button-release-event", self._action_refresh_cb, action)
@@ -283,31 +250,24 @@ class Creator(object):
else:
raise NotImplementedError()
- event_filters = self._state.get_event_filter_list()
+ event_filters = self._tutorial.get_transition_dict(self._state)
+
+ # if not at the end of tutorial
if event_filters:
- # linearize tutorial by inserting state
- new_state = State(name=str(self._state_counter))
- self._state_counter += 1
- self._state.clear_event_filters()
- for evt_filt, next_state in event_filters:
- new_state.add_event_filter(evt_filt, next_state)
- self._update_next_state(self._state, event, new_state.name)
- next_state = new_state.name
- # blocks are shifted, full redraw is necessary
- self._overview.win.queue_draw()
+ old_transition = event_filters.keys()[0]
+ new_state = self._tutorial.add_state(event_filters[old_transition])
+ self._tutorial.update_transition(transition_name=old_transition,
+ new_state=new_state)
+
else:
# append empty state only if edit inserting at end of linearized
# tutorial.
- self._update_next_state(self._state, event, str(self._state_counter))
- next_state = str(self._state_counter)
- new_state = State(name=str(self._state_counter))
- self._state_counter += 1
+ new_state = self._tutorial.add_state()
+ self._tutorial.add_transition(self._state, (event, new_state))
- self._state.add_event_filter(event, next_state)
- self._tutorial.add_state(new_state)
self._overview.win.queue_draw()
- self.set_insertion_point(new_state.name)
+ self.set_insertion_point(new_state)
def _action_refresh_cb(self, widget, evt, action):
"""
@@ -324,24 +284,27 @@ class Creator(object):
self._overview.win.queue_draw()
- def _cleanup_cb(self, *args):
+ def _cleanup_cb(self, *args, **kwargs):
"""
Quit editing and cleanup interface artifacts.
+
+ @param force: force quitting without saving.
"""
# undo actions so they don't persist through step editing
- for action in self._state.get_action_list():
+ for action in self._tutorial.get_action_dict(self._state).values():
action.exit_editmode()
- dialog = gtk.MessageDialog(
- parent=self._activity,
- flags=gtk.DIALOG_MODAL,
- type=gtk.MESSAGE_QUESTION,
- buttons=gtk.BUTTONS_YES_NO,
- message_format=T('Do you want to save before stopping edition?'))
- do_save = dialog.run()
- dialog.destroy()
- if do_save == gtk.RESPONSE_YES:
- self.save()
+ if kwargs.get(force, False):
+ dialog = gtk.MessageDialog(
+ parent=self._activity,
+ flags=gtk.DIALOG_MODAL,
+ type=gtk.MESSAGE_QUESTION,
+ buttons=gtk.BUTTONS_YES_NO,
+ message_format=T('Do you want to save before stopping edition?'))
+ do_save = dialog.run()
+ dialog.destroy()
+ if do_save == gtk.RESPONSE_YES:
+ self.save()
# remove UI remains
self._hlmask.covered = None
@@ -354,20 +317,24 @@ class Creator(object):
del self._activity._creator
def save(self, widget=None):
- if not self.tuto:
- dlg = TextInputDialog(self._activity,
+ if not self._guid:
+ self._guid = str(uuid.uuid1())
+ dlg = TextInputDialog(parent=self._overview.win,
text=T("Enter a tutorial title."),
field=T("Title"))
tutorialName = ""
while not tutorialName: tutorialName = dlg.pop()
dlg.destroy()
-
- # prepare tutorial for serialization
- self.tuto = Tutorial(tutorialName, self._tutorial)
- bundle = vault.TutorialBundler(self._guid)
- self._guid = bundle.Guid
- bundle.write_metadata_file(self.tuto)
- bundle.write_fsm(self._tutorial)
+ self._metadata = {
+ vault.INI_GUID_PROPERTY: self._guid,
+ vault.INI_NAME_PROPERTY: tutorialName,
+ vault.INI_VERSION_PROPERTY: '1',
+ 'activities':{os.environ['SUGAR_BUNDLE_NAME']:
+ os.environ['SUGAR_BUNDLE_VERSION']
+ },
+ }
+
+ vault.Vault.saveTutorial(self._tutorial, self._metadata)
def launch(*args, **kwargs):
diff --git a/tutorius/dbustools.py b/tutorius/dbustools.py
index 5d70d7b..02acd3d 100644
--- a/tutorius/dbustools.py
+++ b/tutorius/dbustools.py
@@ -1,4 +1,6 @@
import logging
+import gobject
+
LOGGER = logging.getLogger("sugar.tutorius.dbustools")
def save_args(callable, *xargs, **xkwargs):
@@ -40,3 +42,16 @@ def remote_call(callable, args, return_cb=None, error_cb=None, block=False):
else:
callable(*args, reply_handler=reply_cb, error_handler=errhandler_cb)
+class Future(object):
+ def __init__(self):
+ self._value = None
+
+ def get(self):
+ context = gobject.MainLoop().get_context()
+ while self._value == None and context.iteration(True):
+ pass
+ return self._value
+
+ def _set(self, value):
+ self._value = value
+
diff --git a/tutorius/viewer.py b/tutorius/viewer.py
index 272558e..56428e1 100644
--- a/tutorius/viewer.py
+++ b/tutorius/viewer.py
@@ -18,9 +18,7 @@ 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 gtk
import cairo
from math import pi as PI
PI2 = PI/2
@@ -30,7 +28,6 @@ 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
@@ -66,7 +63,7 @@ class Viewer(object):
self.alloc = None
self.click_pos = None
self.drag_pos = None
- self.selection = []
+ self.selection = set()
self.win = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.win.set_size_request(400, 200)
@@ -81,7 +78,7 @@ class Viewer(object):
canvas = gtk.DrawingArea()
vbox.add_with_viewport(canvas)
canvas.set_app_paintable(True)
- canvas.connect_after("expose-event", self.on_viewer_expose, tutorial._states)
+ canvas.connect_after("expose-event", self.on_viewer_expose, tutorial)
canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK \
|gtk.gdk.BUTTON_MOTION_MASK \
|gtk.gdk.BUTTON_RELEASE_MASK \
@@ -99,10 +96,13 @@ class Viewer(object):
canvas.set_size_request(2048, 180) # FIXME
def destroy(self):
+ """
+ Destroy ui resources associated with this object.
+ """
self.win.destroy()
- def _paint_state(self, ctx, states):
+ def _paint_state(self, ctx, tutorial):
"""
Paints a tutorius fsm state in a cairo context.
Final context state will be shifted by the size of the graphics.
@@ -111,17 +111,13 @@ class Viewer(object):
block_max_height = self.alloc.height
new_insert_point = None
- cur_state = 'INIT'
+ state_name = tutorial.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:
+ while state_name:
new_app = 'Calculate'
if new_app != cur_app:
ctx.save()
@@ -132,16 +128,17 @@ class Viewer(object):
ctx.translate(BLOCK_PADDING, 0)
cur_app = new_app
- action_list = state.get_action_list()
+ action_list = tutorial.get_action_dict(state_name).items()
if action_list:
- local_height = (block_max_height - BLOCK_PADDING)/len(action_list) - BLOCK_PADDING
+ local_height = (block_max_height - BLOCK_PADDING)\
+ / len(action_list) - BLOCK_PADDING
ctx.save()
- for action in action_list:
+ for action_name, action in action_list:
origin = tuple(ctx.get_matrix())[-2:]
if self.click_pos and \
self.click_pos[0]-BLOCK_WIDTH<origin[0] and \
self.drag_pos[0]>origin[0]:
- self.selection.append(action)
+ self.selection.add((action_name, action))
self.render_action(ctx, block_width, local_height, action)
ctx.translate(0, local_height+BLOCK_PADDING)
@@ -150,7 +147,7 @@ class Viewer(object):
# insertion cursor painting made from two opposed triangles
# joined by a line.
- if state.name == self._creator.get_insertion_point():
+ if state_name == self._creator.get_insertion_point():
ctx.save()
bp2 = BLOCK_PADDING/2
ctx.move_to(-bp2, 0)
@@ -170,36 +167,34 @@ class Viewer(object):
ctx.restore()
- event_list = state.get_event_filter_list()
+ event_list = tutorial.get_transition_dict(state_name).items()
if event_list:
- local_height = (block_max_height - BLOCK_PADDING)/len(event_list) - BLOCK_PADDING
+ local_height = (block_max_height - BLOCK_PADDING)\
+ /len(event_list) - BLOCK_PADDING
ctx.save()
- for event, next_state in event_list:
+ for transition_name, transition in event_list:
origin = tuple(ctx.get_matrix())[-2:]
if self.click_pos and \
self.click_pos[0]-BLOCK_WIDTH<origin[0] and \
self.drag_pos[0]>origin[0]:
- self.selection.append(event)
- self.render_event(ctx, block_width, local_height, event)
+ self.selection.add((transition_name, transition))
+ self.render_event(ctx, block_width, local_height,
+ event=transition[0])
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][1]
-
if (not new_insert_point) and self.click_pos:
origin = tuple(ctx.get_matrix())[-2:]
if self.click_pos[0]<origin[0]:
- new_insert_point = state
+ new_insert_point = state_name
- if event_list:
- try:
- state = states[cur_state]
- except KeyError:
- break
+ if event_list and state_name != tutorial.END:
+ # TODO: use marked path, to avoid infinite loops on recursive
+ # tutorials.
+ next_states = tutorial.get_following_states_dict(state_name)
+ state_name = next_states.keys()[0]
yield True
else:
break
@@ -209,9 +204,9 @@ class Viewer(object):
if self.click_pos:
if not new_insert_point:
- new_insert_point = state
+ new_insert_point = state_name
- self._creator.set_insertion_point(new_insert_point.name)
+ self._creator.set_insertion_point(new_insert_point)
yield False
@@ -235,7 +230,7 @@ class Viewer(object):
def _render_app_hints(self, ctx, appname):
"""
- Fetches the icon of the app related to current states and renders it on a
+ Fetches the icon of the app related to current state and renders it on a
separator, between states.
"""
ctx.set_source_rgb(0.0, 0.0, 0.0)
@@ -248,11 +243,11 @@ class Viewer(object):
bundle_path = os.getenv("SUGAR_BUNDLE_PATH")
if bundle_path:
icon_path = activitybundle.ActivityBundle(bundle_path).get_icon()
- icon = rsvg.Handle(icon_path)
+ icon_rsvg = rsvg.Handle(icon_path)
ctx.save()
ctx.translate(-15, 0)
ctx.scale(0.5, 0.5)
- icon_surf = icon.render_cairo(ctx)
+ icon_rsvg.render_cairo(ctx)
ctx.restore()
@@ -278,9 +273,12 @@ class Viewer(object):
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(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)
@@ -295,7 +293,7 @@ class Viewer(object):
ctx.save()
ctx.translate(BLOCK_INNER_PAD, BLOCK_INNER_PAD)
ctx.scale(0.5, 0.5)
- icon_surf = rsvg_icon.render_cairo(ctx)
+ rsvg_icon.render_cairo(ctx)
ctx.restore()
@@ -340,29 +338,30 @@ class Viewer(object):
addon_name = addon.get_name_from_type(type(event))
# TODO use icon pool
- icon_name = addon.get_addon_meta(addon_name)['icon']
- rsvg_icon = rsvg.Handle(icon.get_icon_file_name(icon_name))
- ctx.save()
- ctx.translate(BLOCK_INNER_PAD, BLOCK_INNER_PAD)
- ctx.scale(0.5, 0.5)
- icon_surf = rsvg_icon.render_cairo(ctx)
+ if addon_name:
+ icon_name = addon.get_addon_meta(addon_name)['icon']
+ rsvg_icon = rsvg.Handle(icon.get_icon_file_name(icon_name))
+ ctx.save()
+ ctx.translate(BLOCK_INNER_PAD, BLOCK_INNER_PAD)
+ ctx.scale(0.5, 0.5)
+ rsvg_icon.render_cairo(ctx)
- ctx.restore()
+ ctx.restore()
ctx.translate(BLOCK_INNER_PAD, (height-SNAP_HEIGHT)/2)
self._render_snapshot(ctx, event)
ctx.restore()
- def on_viewer_expose(self, widget, evt, states):
+ def on_viewer_expose(self, widget, evt, tutorial):
"""
Expose signal handler for the viewer's DrawingArea.
- This loops through states and renders every action and transition of
+ This loops through tutorial and renders every action and transition of
the "happy path".
@param widget: the gtk.DrawingArea on which to draw
@param evt: the gtk.gdk.Event containing an "expose" event
- @param states: a tutorius FiniteStateMachine object to paint
+ @param tutorial: a tutorius FiniteStateMachine object to paint
"""
ctx = widget.window.cairo_create()
self.alloc = widget.get_allocation()
@@ -378,9 +377,12 @@ class Viewer(object):
ctx.clip()
ctx.paint()
+ # padding internal to the widget, to draw the first half of the
+ # activity app hint (the icon)
+ ctx.translate(20, 0)
ctx.translate(BLOCK_PADDING, BLOCK_PADDING)
- painter = self._paint_state(ctx, states)
+ painter = self._paint_state(ctx, tutorial)
while painter.next(): pass
if self.click_pos and self.drag_pos:
@@ -400,7 +402,7 @@ class Viewer(object):
self.drag_pos = self.click_pos = evt.get_coords()
widget.queue_draw()
- self.selection = []
+ self.selection.clear()
def _on_drag(self, widget, evt):
self.drag_pos = evt.get_coords()
@@ -413,11 +415,12 @@ class Viewer(object):
def _on_key_press(self, widget, evt):
if evt.keyval == gtk.keysyms.BackSpace:
# remove selection
- for selected in self.selection:
- if isinstance(selected, EventFilter):
- self._creator.delete_state()
+ for name, obj in self.selection:
+ if isinstance(obj, Action):
+ self._creator.delete_action(name)
else:
- self._creator.delete_action(selected)
+ self._creator.delete_state()
+ self.selection.clear()
widget.queue_draw()