Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorerick <erick@sugar-dev-erick.(none)>2009-12-05 00:19:19 (GMT)
committer erick <erick@sugar-dev-erick.(none)>2009-12-05 00:19:19 (GMT)
commitdbf88e0f12fe015467ecf89d95a2ba4303d9f73a (patch)
tree875d5a0f321128db76de01faca006af84b72cbd4
parentcda15f1fffcca24286867a9064d30dec662e796b (diff)
parent9a44da4488a0ff00150eb5cb114f74ba560b96a6 (diff)
Merge branch 'master' into frame_integration
-rw-r--r--Workshop.activity/MANIFEST5
-rw-r--r--Workshop.activity/Rating.py144
-rw-r--r--Workshop.activity/TutorialStoreCategories.py24
-rw-r--r--Workshop.activity/TutorialStoreDetails.py26
-rw-r--r--Workshop.activity/TutorialStoreHome.py187
-rw-r--r--Workshop.activity/TutorialStoreResults.py120
-rw-r--r--Workshop.activity/TutorialStoreSearch.py38
-rw-r--r--Workshop.activity/TutorialStoreSuggestion.py80
-rwxr-xr-xWorkshop.activity/TutoriusActivity.py120
-rw-r--r--Workshop.activity/Workshop.py479
-rw-r--r--Workshop.activity/WorkshopController.py201
-rw-r--r--Workshop.activity/WorkshopModel.py503
-rw-r--r--Workshop.activity/activity/activity.info8
-rw-r--r--Workshop.activity/activity/someicon.svg21
-rw-r--r--Workshop.activity/arrow_back.pngbin0 -> 310 bytes
-rw-r--r--Workshop.activity/arrow_next.pngbin0 -> 312 bytes
-rw-r--r--Workshop.activity/dialogs.py346
-rw-r--r--Workshop.activity/full_star.pngbin0 -> 1031 bytes
-rw-r--r--Workshop.activity/grayed_star.pngbin0 -> 930 bytes
-rw-r--r--Workshop.activity/half_star.pngbin0 -> 890 bytes
-rw-r--r--Workshop.activity/icon.svg21
-rwxr-xr-xWorkshop.activity/setup.py3
-rw-r--r--addons/bubblemessage.py2
-rw-r--r--addons/bubblemessagewimg.py2
-rw-r--r--addons/gtkwidgeteventfilter.py1
-rw-r--r--addons/gtkwidgettypefilter.py3
-rw-r--r--data/ui/creator.glade50
-rwxr-xr-xsetup.py14
-rwxr-xr-xsrc/extensions/tutoriusremote.py134
-rw-r--r--tests/probetests.py25
-rw-r--r--tests/propertiestests.py12
-rw-r--r--tutorius/TProbe.py185
-rw-r--r--tutorius/actions.py46
-rw-r--r--tutorius/creator.py422
-rw-r--r--tutorius/overlayer.py4
-rw-r--r--tutorius/properties.py35
-rw-r--r--tutorius/service.py3
-rw-r--r--tutorius/store.py2
-rw-r--r--tutorius/translator.py6
-rw-r--r--tutorius/viewer.py3
40 files changed, 3020 insertions, 255 deletions
diff --git a/Workshop.activity/MANIFEST b/Workshop.activity/MANIFEST
new file mode 100644
index 0000000..c5c6d42
--- /dev/null
+++ b/Workshop.activity/MANIFEST
@@ -0,0 +1,5 @@
+TutoriusActivity.py
+activity/someicon.svg
+activity/activity.info
+setup.py
+MANIFEST
diff --git a/Workshop.activity/Rating.py b/Workshop.activity/Rating.py
new file mode 100644
index 0000000..a13e5a2
--- /dev/null
+++ b/Workshop.activity/Rating.py
@@ -0,0 +1,144 @@
+import gtk
+from gtk import gdk
+import logging
+
+class Rating(gtk.Widget):
+ """
+ Controls that display the rating of a tutorial using colored stars
+ """
+ def __init__(self,tutorial,controller, rating=0,editable = False):
+ """
+ Constructor
+
+ @param the controller to link the view with
+ @param tutorial The tutorial for which this rating is
+ @param rating The rating to show
+ @param editable True if the rating may be edited
+ """
+ gtk.Widget.__init__(self)
+
+ self.tutorial = tutorial
+ self.controller = controller
+ self.editable = editable
+ self.rating = rating
+
+ #star size is 24 pixels by 24 pixels
+ self.image_length = 24
+
+ def do_realize(self):
+ self.set_flags(self.flags() | gtk.REALIZED)
+
+ self.window = gtk.gdk.Window(
+ self.get_parent_window(),
+ width=self.allocation.width,
+ height=self.allocation.height,
+ window_type=gdk.WINDOW_CHILD,
+ wclass=gdk.INPUT_OUTPUT,
+ event_mask=self.get_events() | gtk.gdk.EXPOSURE_MASK
+ | gtk.gdk.BUTTON_PRESS_MASK)
+
+ self.window.set_user_data(self)
+
+ self.style.attach(self.window)
+
+ self.style.set_background(self.window, gtk.STATE_NORMAL)
+ self.window.move_resize(*self.allocation)
+
+ #load the stars
+ pixbuf = gtk.gdk.pixbuf_new_from_file('full_star.png')
+ self.full_star,mask = pixbuf.render_pixmap_and_mask()
+
+ pixbuf = gtk.gdk.pixbuf_new_from_file('half_star.png')
+ self.half_star,mask = pixbuf.render_pixmap_and_mask()
+
+ image = gtk.Image()
+ pixbuf = gtk.gdk.pixbuf_new_from_file('grayed_star.png')
+ self.empty_star,mask =pixbuf.render_pixmap_and_mask()
+
+ self.gc = self.style.fg_gc[gtk.STATE_NORMAL]
+
+ def do_unrealize(self):
+ self.window.destroy()
+
+ def do_size_request(self, requisition):
+ requisition.height = self.image_length
+ requisition.width = (self.image_length * 5)
+
+ def do_size_allocate(self, allocation):
+ self.allocation = allocation
+ if self.flags() & gtk.REALIZED:
+ self.window.move_resize(*allocation)
+
+ def do_expose_event(self, event):
+ """
+ The widget is drawn here
+ """
+ value = self.rating
+ stars = [0,0,0,0,0]
+ if value > 0:
+ for x in range(5):
+ if value -1 > 0:
+ stars[x]=1
+ elif value -1 == -0.5:
+ stars[x] = 0.5
+ break
+ else:
+ stars[x]=1
+ break
+ value -= 1
+
+ for x in range(0,5):
+ if stars[x] == 0:
+ self.window.draw_drawable(self.gc, self.empty_star, 0, 0
+ , x*self.image_length
+ , 0,-1, -1)
+ elif stars[x] == 0.5:
+ self.window.draw_drawable(self.gc, self.half_star, 0, 0
+ , x*self.image_length
+ , 0,-1, -1)
+ elif stars[x] == 1:
+ self.window.draw_drawable(self.gc, self.full_star, 0, 0
+ , x*self.image_length
+ , 0,-1, -1)
+
+ def do_button_press_event(self, event):
+ """When the button is pressed"""
+
+ # make sure it was the first button
+ if self.editable:
+ if event.button == 1:
+ #check for new stars
+ self.check_for_new_stars(event.x)
+
+ return True
+
+ def check_for_new_stars(self, xPos):
+ """
+ Computes the star number based on where the click was
+ """
+
+ new_stars = int(xPos / self.image_length)
+ half_star = xPos % self.image_length
+
+ logging.info("xpos: %d, new_stars: %d, half_star: %d",xPos,new_stars,half_star)
+ if half_star > self.image_length/2:
+ new_stars +=1
+ else:
+ new_stars = new_stars+0.5
+ logging.info("rating: %f",new_stars)
+ self.controller.rate_tutorial(self.tutorial,new_stars)
+
+ self.set_value(new_stars)
+
+ def set_value(self, value):
+ """
+ Sets the value and force a redraw
+ """
+
+ if (value >= 0):
+ self.rating = value
+ #check for the maximum
+ if (self.rating > 5):
+ self.rating = 5
+ # redraw the widget
+ self.queue_draw() \ No newline at end of file
diff --git a/Workshop.activity/TutorialStoreCategories.py b/Workshop.activity/TutorialStoreCategories.py
new file mode 100644
index 0000000..c321d66
--- /dev/null
+++ b/Workshop.activity/TutorialStoreCategories.py
@@ -0,0 +1,24 @@
+import sys, os
+import gtk
+
+class TutorialStoreCategories(gtk.Frame):
+
+ def __init__(self,controller):
+ gtk.Frame.__init__(self,'Categories')
+ self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0,0,0))
+
+ self.controller = controller
+ self.categorie_box = gtk.VBox(False, 5)
+
+ self.add(self.categorie_box)
+ self.categorie_box.show()
+
+ def set_categories(self,categories):
+ for child in self.categorie_box.get_children():
+ self.categorie_box.remove(child)
+
+ for category in categories:
+ link = gtk.LinkButton("",category)
+ self.categorie_box.pack_start(link,False,False,10)
+ link.connect('clicked',self.controller.get_tutorials_by_category,category)
+ link.show()
diff --git a/Workshop.activity/TutorialStoreDetails.py b/Workshop.activity/TutorialStoreDetails.py
new file mode 100644
index 0000000..83c5366
--- /dev/null
+++ b/Workshop.activity/TutorialStoreDetails.py
@@ -0,0 +1,26 @@
+import sys, os
+import gtk
+from Workshop import WorkshopDetail
+
+class TutorialStoreDetails(WorkshopDetail):
+
+ def __init__(self,tutorial,controller):
+ WorkshopDetail.__init__(self,tutorial,controller)
+
+ download_button = gtk.Button('Download')
+ infos_button = gtk.Button('Informations')
+ #comp_button = gtk.Button('Compatibility')
+
+ last_row = gtk.HBox(False, 15)
+ last_row.pack_start(download_button,False,False)
+ last_row.pack_start(infos_button,False)
+ #last_row.pack_start(comp_button,False)
+
+
+ self.pack_end(last_row,False,False)
+
+ last_row.show_all()
+
+ download_button.connect('clicked',self.controller.download_tutorial,tutorial)
+ infos_button.connect('clicked',self.controller.display_infos,tutorial)
+ self.back_button.connect('clicked',self.controller.back_pressed,None)
diff --git a/Workshop.activity/TutorialStoreHome.py b/Workshop.activity/TutorialStoreHome.py
new file mode 100644
index 0000000..a9051e7
--- /dev/null
+++ b/Workshop.activity/TutorialStoreHome.py
@@ -0,0 +1,187 @@
+import logging
+import TutorialStoreCategories
+import TutorialStoreSearch
+import TutorialStoreSuggestion
+from TutorialStoreDetails import TutorialStoreDetails
+from TutorialStoreResults import TutorialStoreResults
+from WorkshopController import StoreController
+from dialogs import StoreInformationDialog
+
+import sys, os
+import gtk
+
+
+class TutorialStore(gtk.Alignment):
+ """
+ Main container for the Tutorial Store part of the workshop
+ """
+ def __init__(self,model):
+ gtk.Alignment.__init__(self,0.0,0.0,1.0,1.0)
+
+ self.controller = StoreController(self,model)
+ model.set_store_view(self)
+
+ self.store_home = TutorialStoreHome(self.controller)
+
+ self.add(self.store_home)
+ self.store_home.show()
+
+ self.controller.get_categories()
+ self.controller.get_popular()
+ self.controller.get_also_like()
+
+ def set_categories(self,categories):
+ self.store_home.set_categories(categories)
+
+ def set_tutorial_list(self,tutorial_list):
+ self.store_home.set_tutorial_list(tutorial_list)
+
+ def show_details(self,tutorial):
+ self.store_home.show_details(tutorial)
+
+ def set_popular(self,tutorials):
+ self.store_home.set_popular(tutorials)
+
+ def set_also_like(self,tutorials):
+ self.store_home.set_also_like(tutorials)
+
+ def display_infos(self,tutorial):
+ self.store_home.display_infos(tutorial)
+
+ def set_button_sensitive(self,back,next):
+ self.store_home.set_button_sensitive(back,next)
+
+ def show_search_result(self):
+ self.store_home.show_search_result()
+
+class TutorialStoreHome(gtk.HBox):
+ def __init__(self,controller):
+ gtk.HBox.__init__(self,False,5)
+ self.controller = controller
+
+ self.categories = TutorialStoreCategories.TutorialStoreCategories(self.controller)
+
+ self.search = TutorialStoreSearch.TutorialStoreSearch(self.controller)
+
+ self.suggestion = TutorialStoreSuggestion.TutorialStoreSuggestion(controller)
+
+ self.center_container = gtk.Alignment(0.0,0.0,1.0,1.0)
+ self.search_result = TutorialStoreResults(self.controller)
+
+
+ self.center_container.add(self.suggestion)
+
+ tut_store_home_base = gtk.VBox(False, 5)
+ tut_store_home_base.pack_start(self.search, False, False,10)
+ sep = gtk.HSeparator()
+ tut_store_home_base.pack_start(sep, False, False)
+ tut_store_home_base.pack_start(self.center_container,True,True)
+
+
+ self.pack_start(self.categories, True, True, 5)
+ self.pack_start(tut_store_home_base, True, True, 5)
+ #self.pack_start(tut_store_home_base, True, True, 5)
+
+ #tut_store_suggestion.show()
+ self.categories.show()
+ tut_store_home_base.show()
+ self.search.show()
+ sep.show()
+ self.center_container.show_all()
+ self.suggestion.show_all()
+
+ def set_categories(self,categories):
+ self.categories.set_categories(categories)
+ self.search.set_categories(categories)
+
+ def display_infos(self,tutorial):
+ dialog = StoreInformationDialog(tutorial)
+ dialog.run()
+ dialog.destroy()
+
+ def set_popular(self,tutorials):
+ self.suggestion.set_popular(tutorials)
+
+ def set_also_like(self,tutorials):
+ self.suggestion.set_also_like(tutorials)
+
+ def set_tutorial_list(self,tutorial_list):
+ self.search_result.set_tutorial_list(tutorial_list)
+ for child in self.center_container:
+ self.center_container.remove(child)
+
+ self.center_container.add(self.search_result)
+ self.search_result.show()
+
+ def show_details(self,tutorial):
+ self.details = TutorialStoreDetails(tutorial,self.controller)
+ for child in self.center_container:
+ self.center_container.remove(child)
+
+ self.center_container.add(self.details)
+ self.details.show()
+
+ def show_search_result(self):
+ for child in self.center_container:
+ self.center_container.remove(child)
+
+ self.center_container.add(self.search_result)
+ self.search_result.show()
+
+
+ def get_results_widget(self):
+
+ self.search = TutorialStoreSearch.TutorialStoreSearch()
+ tutorial_store_search = self.search.tutorial_store_search
+
+ self.results = TutorialStoreResults.TutorialStoreResults()
+ tutorial_store_results = self.results.tutorial_store_results
+
+ self.categories = TutorialStoreCategories.TutorialStoreCategories()
+ categories_frame = self.categories.categorie_box_frame
+
+ tut_store_home_base = gtk.VBox(False, 5)
+ tut_store_home_base.pack_start(tutorial_store_search, False, False, 25)
+ tut_store_home_base.pack_start(tutorial_store_results, False, False, 0)
+
+ self.tutorial_store_home = gtk.HBox(False, 5)
+ self.tutorial_store_home.pack_start(categories_frame, True, True, 5)
+ self.tutorial_store_home.pack_start(tut_store_home_base, True, True, 5)
+
+ tut_store_home_base.show()
+ tutorial_store_search.show()
+ tutorial_store_results.show()
+ categories_frame.show()
+ self.tutorial_store_home.show()
+
+ return self.tutorial_store_home
+
+ def get_details_widget(self):
+
+ self.search = TutorialStoreSearch.TutorialStoreSearch()
+ tutorial_store_search = self.search.tutorial_store_search
+
+ self.details = TutorialStoreDetails.TutorialStoreDetails()
+ tutorial_store_details = self.details.tutorial_store_details
+
+ self.categories = TutorialStoreCategories.TutorialStoreCategories()
+ categories_frame = self.categories.categorie_box_frame
+
+ tut_store_home_base = gtk.VBox(False, 5)
+ tut_store_home_base.pack_start(tutorial_store_search, False, False, 25)
+ tut_store_home_base.pack_start(tutorial_store_details, False, False, 0)
+
+ self.tutorial_store_home = gtk.HBox(False, 5)
+ self.tutorial_store_home.pack_start(categories_frame, True, True, 5)
+ self.tutorial_store_home.pack_start(tut_store_home_base, True, True, 5)
+
+ tut_store_home_base.show()
+ tutorial_store_search.show()
+ tutorial_store_details.show()
+ categories_frame.show()
+ self.tutorial_store_home.show()
+
+ return self.tutorial_store_home
+
+ def set_button_sensitive(self,back,next):
+ self.search_result.set_button_sensitive(back,next)
diff --git a/Workshop.activity/TutorialStoreResults.py b/Workshop.activity/TutorialStoreResults.py
new file mode 100644
index 0000000..3a7f78d
--- /dev/null
+++ b/Workshop.activity/TutorialStoreResults.py
@@ -0,0 +1,120 @@
+import sys, os
+import gtk
+from Workshop import WorkshopListItem
+from Rating import Rating
+import operator
+import logging
+
+
+class TutorialStoreResults(gtk.VBox):
+ def __init__(self,controller):
+ """Constructor
+
+ @param controller The controller to attach the view to
+ """
+ gtk.VBox.__init__(self,False,10)
+
+ back_image = gtk.Image()
+ back_image.set_from_file('arrow_back.png')
+ self.back_button = gtk.Button("Prev")
+ self.back_button.set_image(back_image)
+
+ next_image = gtk.Image()
+ next_image.set_from_file('arrow_next.png')
+ self.next_button = gtk.Button("Next")
+ self.next_button.set_image(next_image)
+
+ self.arrow_box = gtk.HBox()
+ self.arrow_box.pack_start(self.back_button,False,False)
+ self.arrow_box.pack_end(self.next_button,False,False)
+ self.back_button.set_sensitive(False)
+
+ self.controller = controller
+ self.tutorial_list = []
+
+ #by default tutorials are sorted by name
+ self.sorting_key = 'Name'
+
+ self.set_border_width(10)
+
+ #create the list item container with a scroll bar if necessary
+ self.main_container = gtk.ScrolledWindow()
+ self.main_container.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
+
+ self.list_container= gtk.VBox()
+
+ self.main_container.add_with_viewport(self.list_container)
+ self.pack_start(self.main_container)
+ self.pack_end(self.arrow_box,False,False)
+
+ self.back_button.connect("clicked",self.controller.prev_page,None)
+ self.next_button.connect("clicked",self.controller.next_page,None)
+
+ #Show the components
+ self.list_container.show()
+ self.main_container.show()
+ self.arrow_box.show_all()
+
+ def set_button_sensitive(self,back,next):
+ self.back_button.set_sensitive(back)
+ self.next_button.set_sensitive(next)
+
+ def change_sorting(self,sorting):
+ """
+ Changes the property by which tutorial are sorted
+
+ @param sorting The property by which tutorials will be sorted
+ """
+ logging.info("Change_sorting was called")
+ self.sorting_key = sorting
+ self.sort_tutorial()
+
+ def sort_tutorial(self):
+ """
+ Sorts the tutorials
+ """
+ #if tutorials are sorted by rating they are in the reverse order
+ self.tutorial_list.sort(key=operator.attrgetter(self.sorting_key.lower()))
+ self.refresh_tutorial_display()
+
+ def set_tutorial_list(self,tutorial_list):
+ """
+ Set the list of tutorial to display
+
+ @param tutorial_list the tutorial list
+ """
+ self.tutorial_list = tutorial_list
+ self.sort_tutorial()
+
+ def refresh_tutorial_display(self):
+ """
+ Refresh the tutorial content by deleting every item and recreating them
+ """
+ #delete every tutorial list item
+ for child in self.list_container.get_children():
+ self.list_container.remove(child)
+
+ #Creates and add a new item for every tutorial
+ for tuto in self.tutorial_list:
+ item = TutorialStoreListItem(tuto,self.controller)
+ self.list_container.pack_start(item)
+ item.show()
+ if self.tutorial_list[-1] != tuto:
+ sep = gtk.HSeparator()
+ self.list_container.pack_start(sep)
+ sep.show()
+
+class TutorialStoreListItem(WorkshopListItem):
+ def __init__(self,tutorial,controller):
+ WorkshopListItem.__init__(self,tutorial,controller)
+
+ self.last_row = gtk.HBox(False,15)
+ self.btn_detail = gtk.Button('Details')
+ self.last_row.pack_end(self.btn_detail,False,False)
+
+ self.table.attach(self.last_row,1,3,2,3,yoptions = 0)
+
+ self.last_row.show_all()
+
+ #connect the buttons
+ self.btn_detail.connect("clicked",self.controller.show_details,self.tutorial) \ No newline at end of file
diff --git a/Workshop.activity/TutorialStoreSearch.py b/Workshop.activity/TutorialStoreSearch.py
new file mode 100644
index 0000000..4303a07
--- /dev/null
+++ b/Workshop.activity/TutorialStoreSearch.py
@@ -0,0 +1,38 @@
+import sys, os
+import gtk
+
+class TutorialStoreSearch(gtk.HBox):
+
+ def __init__(self,controller):
+ gtk.HBox.__init__(self,False, 5)
+ self.controller = controller
+ search_label = gtk.Label('Search :')
+ self.search_box = gtk.Entry(400)
+ in_label = gtk.Label('in')
+ self.search_combobox = gtk.combo_box_new_text()
+ self.search_button = gtk.Button('Search')
+
+ self.pack_start(search_label, True, True, 5)
+ self.pack_start(self.search_box, True, True, 5)
+ self.pack_start(in_label, True, True, 5)
+ self.pack_start(self.search_combobox, True, True, 5)
+ self.pack_start(self.search_button, True, True, 5)
+
+ search_label.show()
+ self.search_box.show()
+ in_label.show()
+ self.search_combobox.show()
+ self.search_button.show()
+
+ self.search_button.connect("clicked",self.controller.search_store,{'keyword':self.search_box,
+ 'category':self.search_combobox})
+
+ def set_categories(self,categories):
+ self.search_combobox.set_active(0)
+ while self.search_combobox.get_active_text() is not None:
+ self.search_combobox.remove_text(0)
+ self.search_combobox.set_active(0)
+
+ for category in categories:
+ self.search_combobox.append_text(category)
+ \ No newline at end of file
diff --git a/Workshop.activity/TutorialStoreSuggestion.py b/Workshop.activity/TutorialStoreSuggestion.py
new file mode 100644
index 0000000..45fc1c3
--- /dev/null
+++ b/Workshop.activity/TutorialStoreSuggestion.py
@@ -0,0 +1,80 @@
+import sys, os
+import gtk
+
+class TutorialStoreSuggestion(gtk.HBox):
+
+ def __init__(self,controller):
+ gtk.HBox.__init__(self,homogeneous=True, spacing=5)
+ self.controller = controller
+
+ self.popular = gtk.VBox(homogeneous=True, spacing=0)
+ self.popular_frame = gtk.Frame('Most Popular')
+
+ #top_five.pack_start(tutorial1_frame, expand=True, fill=True, padding=0)
+ #top_five.pack_start(tutorial2_frame, expand=True, fill=True, padding=0)
+ #top_five.pack_start(tutorial3_frame, expand=True, fill=True, padding=0)
+
+ self.also_like = gtk.VBox(homogeneous=True, spacing=0)
+ self.also_like_frame = gtk.Frame('You might also like :')
+
+ #also_like.pack_start(tutorial4_frame, expand=True, fill=True, padding=0)
+ #also_like.pack_start(tutorial5_frame, expand=True, fill=True, padding=0)
+ #also_like.pack_start(tutorial6_frame, expand=True, fill=True, padding=0)
+
+ self.popular_frame.add(self.popular)
+ self.also_like_frame.add(self.also_like)
+
+ self.popular.show()
+ self.popular_frame.show()
+ self.also_like.show()
+ self.also_like_frame.show()
+
+ self.pack_start(self.popular_frame)
+ self.pack_start(self.also_like_frame)
+
+ def set_popular(self,tutorials):
+ for child in self.popular.get_children():
+ self.popular.remove(child)
+ if len(tutorials)> 3:
+ for x in range(0,3):
+ item = SuggestionListItem(self.controller,tutorials[x])
+ self.popular.pack_start(item)
+ item.show()
+ else:
+ for x in tutorials:
+ item = SuggestionListItem(self.controller,x)
+ self.popular.pack_start(item)
+ item.show()
+
+ def set_also_like(self,tutorials):
+ for child in self.also_like.get_children():
+ self.also_like.remove(child)
+ if len(tutorials)> 3:
+ for x in range(0,3):
+ item = SuggestionListItem(self.controller,tutorials[x])
+ self.also_like.pack_start(item)
+ item.show()
+ else:
+ for x in tutorials:
+ item = SuggestionListItem(self.controller,x)
+ self.also_like.pack_start(item)
+ item.show()
+
+class SuggestionListItem(gtk.Frame):
+ def __init__(self,controller,tutorial):
+ gtk.Frame.__init__(self)
+
+ self.container = gtk.HBox()
+
+ self.label = gtk.Label(tutorial.name)
+ self.icon = gtk.Image()
+ self.icon.set_from_file('icon.svg')
+ self.button = gtk.Button("More")
+
+ self.container.pack_start(icon5, expand=True, fill=True, padding=4)
+ self.container.pack_start(label5, expand=True, fill=True, padding=0)
+ self.container.pack_start(more_button5, expand=False, fill=False, padding=5)
+ self.add(self.container)
+ #tutorial5_frame.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0,0,0))
+
+ self.container.show_all() \ No newline at end of file
diff --git a/Workshop.activity/TutoriusActivity.py b/Workshop.activity/TutoriusActivity.py
new file mode 100755
index 0000000..f2f3adc
--- /dev/null
+++ b/Workshop.activity/TutoriusActivity.py
@@ -0,0 +1,120 @@
+# 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
+from sugar.activity import activity
+from TutorialStoreHome import TutorialStore
+from Workshop import WorkshopView
+from WorkshopModel import WorkshopModel
+import logging
+
+import sys, os
+import gtk
+from dialogs import LoginDialog
+
+class TutoriusActivity(activity.Activity):
+
+ def callback(self, widget, button_string):
+
+ if button_string == 'search_button':
+ self.right_container.remove(self.tutorial_store_home.tutorial_store_home)
+
+ results_widget = self.tutorial_store_home.get_results_widget()
+
+ self.right_container.pack_start(results_widget)
+
+ results_widget.show()
+ self.right_container.show()
+
+ elif button_string == 'more_button':
+ self.right_container.remove(self.tutorial_store_home.tutorial_store_home)
+
+ details_widget = self.tutorial_store_home.get_details_widget()
+
+ self.right_container.pack_start(details_widget)
+
+ details_widget.show()
+ self.right_container.show()
+
+ def __init__(self,handle):
+ activity.Activity.__init__(self,handle)
+ gtk.gdk.threads_init()
+ toolbox = activity.ActivityToolbox(self)
+ self.set_toolbox(toolbox)
+ toolbox.show()
+
+ self.table = gtk.VPaned()
+ self.table.set_position(500)
+ self.left_container = gtk.HBox()
+ btn1 = gtk.Button("My tutorials")
+ btn2 = gtk.Button("Tutorial Store")
+
+ self.left_container.pack_start(btn1,expand=False)
+ self.left_container.pack_start(btn2,expand=False)
+
+ #tutorial_store_search_button = self.tutorial_store_home.get_search_button()
+ #tutorial_store_search_button.connect("clicked", self.callback, 'search_button')
+
+ #tutorial_store_more_button = self.tutorial_store_home.get_more_button()
+ #tutorial_store_more_button.connect("clicked", self.callback, 'more_button')
+
+ self.right_container = gtk.VBox()
+ #self.right_container.pack_start(self.tutorial_store_home.tutorial_store_home)
+
+ self.model = WorkshopModel()
+
+ self.workshop_my_tutorial = WorkshopView(self.model)
+ self.workshop_store = TutorialStore(self.model)
+ self.model.set_workshop_view(self.workshop_my_tutorial)
+ self.model.set_store_view(self.workshop_store)
+
+ self.model.query(None)
+
+ self.table.add2(self.left_container)
+ self.table.add1(self.workshop_my_tutorial)
+ self.set_canvas(self.table)
+
+ self.workshop_store.show()
+ self.workshop_my_tutorial.show()
+ self.table.show_all()
+
+
+ btn1.connect("clicked",self.display_my_tutorial,None)
+ btn2.connect("clicked",self.display_store,None)
+
+ def display_store(self,widget,data):
+ """
+ Display the Tutorial store view
+
+ @param widget The widget that made the call
+ @param data parameter not used
+ """
+ if self.table.get_child1() == self.workshop_my_tutorial:
+ self.table.remove(self.workshop_my_tutorial)
+ if self.table.get_child1() is None:
+ logging.info("here store")
+ self.table.add1(self.workshop_store)
+
+ def display_my_tutorial(self,widget,data):
+ """
+ Display the My Tutorial view
+
+ @param widget The widget that made the call
+ @param data parameter not used
+ """
+ if self.table.get_child1() == self.workshop_store:
+ self.table.remove(self.workshop_store)
+ if self.table.get_child1() is None:
+ logging.info("here my")
+ self.table.add1(self.workshop_my_tutorial)
diff --git a/Workshop.activity/Workshop.py b/Workshop.activity/Workshop.py
new file mode 100644
index 0000000..857bf8c
--- /dev/null
+++ b/Workshop.activity/Workshop.py
@@ -0,0 +1,479 @@
+# 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
+import gtk
+import gobject
+
+from Rating import Rating
+from WorkshopModel import WorkshopModel
+from WorkshopController import WorkshopController
+from dialogs import InfoDialog
+import operator
+import logging
+
+class WorkshopView(gtk.Alignment):
+ """
+ Main container for the Workshop Mytutorial Part
+ """
+ def __init__(self,model):
+ """
+ Constructor
+ """
+ gtk.Alignment.__init__(self,0.0,0.0,1.0,1.0)
+
+ #Register Rating as a gobject
+ gobject.type_register(Rating)
+
+ #create the model and the controller
+ self.controller = WorkshopController(self,model)
+
+ #Create the main view
+ self.mainView = WorkshopMain(self.controller)
+ self.detailView = None
+
+ #display the main view
+ self.add(self.mainView)
+ self.mainView.show()
+
+
+
+ def set_categories(self,categories):
+ self.categories = categories
+
+ def set_tutorial_list(self,tutorial_list):
+ """
+ Set the list of tutorial to display in the main View
+ Refresh the View
+
+ @param tutorial_list the list of tutorial
+ """
+ self.mainView.set_tutorial_list(tutorial_list)
+
+
+ def change_sorting(self,sorting_key):
+ """
+ Sort the list of tutorial base on the sorting_key
+
+ @param sorting_key the tutorial metadata to use to sort the tutorials
+ """
+ self.mainView.change_sorting(sorting_key)
+
+ def display_detail(self,tutorial):
+ """
+ Displays the detail view of a tutorial
+
+ @param tutorial the tutorial to display
+ """
+ #hide the main view
+ self.mainView.hide()
+ self.remove(self.mainView)
+
+ #create the detail view and show it
+ self.detailView = MyTutorialDetail(tutorial,self.controller)
+ self.add(self.detailView)
+ self.detailView.show()
+
+ def display_main_view(self):
+ """
+ Displays the main view of the Workshop
+ """
+ #hide the detail view
+ self.detailView.hide()
+ self.remove(self.detailView)
+
+ #display the main view
+ self.add(self.mainView)
+ self.mainView.show()
+
+ def display_info_dialog(self,tutorial):
+ """
+ Displays the infos dialog on a tutorial
+
+ @param tutorial the tutorial to edit
+ """
+ infoDialog = InfoDialog(tutorial,self.controller,self.categories)
+ infoDialog.run()
+ infoDialog.destroy()
+
+
+ def refresh_content(self):
+ """
+ Refresh the data displayed
+ """
+ #refresh the tutorial list
+ self.mainView.refresh_tutorial_display()
+
+ #refresh the detail view
+ if self.detailView is not None:
+ self.detailView.refresh_content()
+
+class WorkshopMain(gtk.VBox):
+ """
+ Contains the main view for the Workshop My tutorial
+ """
+ def __init__(self,controller):
+ """Constructor
+
+ @param controller The controller to attach the view to
+ """
+ gtk.VBox.__init__(self,False,10)
+
+ self.controller = controller
+ self.tutorial_list = []
+
+ #by default tutorials are sorted by name
+ self.sorting_key = 'Name'
+
+ self.set_border_width(10)
+
+ #The searchbar is displayed at the top
+ self.search_bar = SearchBar(self.controller)
+ self.pack_start(self.search_bar,False,False)
+
+ #Add a separator after the search bar
+ sep = gtk.HSeparator()
+ self.pack_start(sep,False,False)
+
+ #create the list item container with a scroll bar if necessary
+ self.main_container = gtk.ScrolledWindow()
+ self.main_container.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
+
+ self.list_container= gtk.VBox()
+
+ self.main_container.add_with_viewport(self.list_container)
+ self.pack_start(self.main_container)
+
+ #Show the components
+ self.search_bar.show()
+ self.list_container.show()
+ self.main_container.show()
+ sep.show()
+
+
+ def change_sorting(self,sorting):
+ """
+ Changes the property by which tutorial are sorted
+
+ @param sorting The property by which tutorials will be sorted
+ """
+
+ self.sorting_key = sorting
+ self.sort_tutorial()
+
+ def sort_tutorial(self):
+ """
+ Sorts the tutorials
+ """
+
+ #if tutorials are sorted by rating they are in the reverse order
+ self.tutorial_list.sort(key=operator.attrgetter(self.sorting_key.lower()))
+ self.refresh_tutorial_display()
+
+ def set_tutorial_list(self,tutorial_list):
+ """
+ Set the list of tutorial to display
+
+ @param tutorial_list the tutorial list
+ """
+ self.tutorial_list = tutorial_list
+ self.sort_tutorial()
+
+ def refresh_tutorial_display(self):
+ """
+ Refresh the tutorial content by deleting every item and recreating them
+ """
+ #delete every tutorial list item
+ for child in self.list_container.get_children():
+ self.list_container.remove(child)
+
+ #Creates and add a new item for every tutorial
+ for tuto in self.tutorial_list:
+ item = MyTutorialListItem(tuto,self.controller)
+ self.list_container.pack_start(item)
+ item.show()
+ if self.tutorial_list[-1] != tuto:
+ sep = gtk.HSeparator()
+ self.list_container.pack_start(sep)
+ sep.show()
+
+class SearchBar(gtk.HBox):
+ """
+ The search bar control for the Workshop My tutorial
+ """
+ def __init__(self,controller):
+ """
+ Constructor
+
+ @param controller The controller to link the view to
+ """
+ gtk.HBox.__init__(self,False,10)
+
+ self.set_border_width(5)
+ self.controller = controller
+
+ #creating and configuring the controls
+ self.search_entry = gtk.Entry(400)
+
+ self.search_button = gtk.Button("Go")
+
+ self.separator = gtk.VSeparator()
+
+ self.sort_label = gtk.Label("Sort by")
+ self.sort_combo = gtk.combo_box_new_text()
+ self.sort_combo.insert_text(0,"Name")
+ self.sort_combo.insert_text(1,"Rating")
+ self.sort_combo.set_active(0)
+
+ self.selected_sorting = self.sort_combo.get_active_text()
+
+ #Adding the controls to the view
+ self.pack_start(self.search_entry,padding=5)
+ self.pack_start(self.search_button,False,False,padding=10)
+ self.pack_start(self.separator,False,False,padding=10)
+ self.pack_start(self.sort_label,False,False,padding=5)
+ self.pack_start(self.sort_combo,)
+
+ #showing the controls
+ self.search_entry.show()
+ self.search_button.show()
+ self.separator.show()
+ self.sort_label.show()
+ self.sort_combo.show()
+
+ #connecting the events
+ self.search_button.connect("clicked",self.controller.tutorial_query,self.search_entry)
+ self.sort_combo.connect("changed",self.controller.sort_selection_changed,None)
+
+class WorkshopDetail(gtk.VBox):
+ def __init__(self,tutorial,controller):
+ """
+ Constructor
+
+ @param tutorial The tutorial to display
+ @param controller The controller to link the view with
+ """
+
+ #Used for string formatting
+ self.title_text = '<span size="xx-large">%(title)s</span>'
+ self.author_text = '<span size="large">by %(author)s</span>'
+ self.desc_text = 'Description: %(description)s'
+
+ self.controller = controller
+ self.tutorial = tutorial
+
+ gtk.VBox.__init__(self,False,10)
+ self.set_border_width(10)
+
+ #The first row contains the back button
+ first_row = gtk.HBox(False)
+ back_image = gtk.Image()
+ back_image.set_from_file('arrow_back.png')
+ self.back_button = gtk.Button("Back")
+ self.back_button.set_image(back_image)
+
+ first_row.pack_start(self.back_button,False,False)
+
+ #The second row contains the activity icon, the title label,
+ #the author label and the star rating
+ icon = gtk.Image()
+ icon.set_from_file('icon.svg')
+
+ label_holder = gtk.VBox(False,10)
+
+ self.title_label = gtk.Label("")
+ self.author_label = gtk.Label("")
+
+ #Add a small offsert for author's label alignement because it's cute
+ self.author_label.set_alignment(0.05,0.5)
+ self.title_label.set_alignment(0.0,0.5)
+
+ label_holder.pack_start(self.title_label)
+ label_holder.pack_start(self.author_label)
+
+ self.rating = Rating(tutorial,controller,rating = tutorial.rating)
+
+ second_row = gtk.HBox(False)
+ second_row.pack_start(icon,False,False)
+ second_row.pack_start(label_holder)
+ second_row.pack_end(self.rating,False,False)
+
+ #The middle of the screen contains an area for the description
+ self.desc_view = gtk.TextView()
+ self.desc_buff = gtk.TextBuffer()
+ self.desc_buff.set_text(tutorial.description)
+ self.desc_view.set_buffer(self.desc_buff)
+ self.desc_view.set_editable(False)
+ self.desc_view.set_wrap_mode(gtk.WRAP_WORD)
+ self.desc_view.set_cursor_visible(False)
+ self.desc_view.connect("realize",self.realize_cb,None)
+ self.desc_view.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("gray") )
+
+ #The description view contains all the extra space
+ self.pack_start(first_row,False,False)
+ self.pack_start(second_row,False,False)
+ self.pack_start(self.desc_view)
+
+ #show everything
+ self.back_button.show()
+ first_row.show()
+ self.title_label.show()
+ self.author_label.show()
+ self.rating.show()
+ label_holder.show()
+ second_row.show()
+ icon.show()
+ self.desc_view.show()
+
+ #set some text with markup
+ self.title_label.set_markup(self.title_text % {"title":tutorial.name})
+ self.author_label.set_markup(self.author_text % {"author":tutorial.author})
+
+ def realize_cb(self,widget,data=None):
+ """
+ This fucntion changes the cursor over the description view
+ So we see an arrow and not the insert text cursor
+ """
+ widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
+
+ def refresh_content(self):
+ """
+ Refresh the labels' text based on the tutorial object
+ """
+ self.title_label.set_markup(self.title_text % {"title":self.tutorial.name})
+ self.author_label.set_markup(self.author_text % {"author":self.tutorial.author})
+ self.desc_buff.set_text(self.tutorial.description)
+
+class WorkshopListItem(gtk.Alignment):
+ """
+ A list item containing the details of a tutorial
+ """
+ def __init__(self,tutorial,controller):
+ """
+ Constructor
+
+ @param controller The controller to link the view to
+ """
+ #logging.info(tutorial.updated_metadata)
+ gtk.Alignment.__init__(self,0.0,0.0,1.0,1.0)
+ self.tutorial = tutorial
+ self.controller = controller
+ self.set_border_width(10)
+
+ #The table will contain everything else
+ self.table = gtk.Table(3,3,False)
+ self.table.set_row_spacing(1,10)
+
+ #Create the controls
+ self.lbl_title = gtk.Label('')
+ self.lbl_title.set_alignment(0.0,0.5)
+ self.title_text = '<span size="xx-large">%(title)s</span>'
+ self.lbl_title.set_markup(self.title_text % {'title':tutorial.name})
+
+ self.lbl_desc = gtk.Label(tutorial.description)
+ self.lbl_desc.set_line_wrap(True)
+ self.lbl_desc.set_alignment(0.0,0.5)
+
+ self.icon = gtk.Image()
+ self.icon.set_from_file('icon.svg')
+
+ self.rating = Rating(tutorial,controller,tutorial.rating, True)
+
+ #Add the controls to the table
+ self.table.attach(self.icon,0,1,0,1,0,0)
+ self.table.attach(self.lbl_title,1,2,0,1,yoptions=0)
+ self.table.attach(self.lbl_desc,1,2,1,2,xoptions=gtk.FILL,yoptions=gtk.EXPAND)
+ self.table.attach(self.rating,2,3,0,2,0,0)
+
+ #show everything
+ self.table.show()
+ self.icon.show()
+ self.lbl_title.show()
+ self.lbl_desc.show()
+ self.rating.show()
+
+ self.add(self.table)
+
+
+class MyTutorialListItem(WorkshopListItem):
+ def __init__(self,tutorial,controller):
+ WorkshopListItem.__init__(self,tutorial,controller)
+
+ self.last_row = gtk.HBox(False,15)
+ self.btn_launch = gtk.Button('Launch')
+ self.btn_detail = gtk.Button('Details')
+ self.last_row.pack_start(self.btn_launch,False,False)
+ self.last_row.pack_end(self.btn_detail,False,False)
+
+ self.table.attach(self.last_row,1,3,2,3,yoptions = 0)
+
+ self.last_row.show_all()
+
+ #connect the buttons
+ self.btn_detail.connect("clicked",self.controller.show_details,self.tutorial)
+ self.btn_launch.connect("clicked",self.controller.launch_tutorial,self.tutorial)
+
+
+class MyTutorialDetail(WorkshopDetail):
+ def __init__(self,tutorial,controller):
+ WorkshopDetail.__init__(self,tutorial,controller)
+
+ #The bottom of the screen contains the button(fourth and fifth row
+ self.launch_button = gtk.Button('Launch')
+ self.launch_button.get_child().set_markup(self.title_text %{"title":"Launch"})
+ self.edit_button = gtk.Button('Edit')
+ self.edit_button.get_child().set_markup(self.title_text %{"title":"Edit"})
+ self.update_button = gtk.Button('Update')
+ self.update_button.get_child().set_markup(self.title_text %{"title":"Update"})
+ self.info_button = gtk.Button('Infos')
+ self.info_button.get_child().set_markup(self.title_text %{"title":"Infos"})
+ self.delete_button = gtk.Button('Delete')
+ self.delete_button.get_child().set_markup(self.title_text %{"title":"Delete"})
+
+ fourth_row = gtk.HBox(False,15)
+ fourth_row.pack_start(self.launch_button,False,False)
+ fourth_row.pack_start(self.edit_button,False,False)
+ fourth_row.pack_start(self.update_button,False,False)
+ fourth_row.pack_start(self.info_button,False,False)
+ fourth_row.pack_end(self.delete_button,False,False)
+
+
+ self.publish_button = gtk.Button('')
+ self.publish_button.get_child().set_markup(self.title_text %{"title":"Publish"})
+ self.unpublish_button = gtk.Button('')
+ self.unpublish_button.get_child().set_markup(self.title_text %{"title":"Unpublish"})
+
+ fifth_row = gtk.HBox(False,15)
+ fifth_row.pack_start(self.publish_button,False,False)
+ fifth_row.pack_start(self.unpublish_button,False,False)
+
+ self.pack_end(fifth_row,False,False)
+ self.pack_end(fourth_row,False,False)
+
+ fifth_row.show_all()
+ fourth_row.show_all()
+
+ #connect the clicked events of the buttons
+ self.back_button.connect("clicked",self.controller.back_pressed,None)
+ self.publish_button.connect("clicked",self.controller.publish_tutorial,self.tutorial)
+ self.unpublish_button.connect("clicked",self.controller.unpublish_tutorial,self.tutorial)
+ self.launch_button.connect("clicked",self.controller.launch_tutorial,self.tutorial)
+ self.edit_button.connect("clicked",self.controller.edit_tutorial,self.tutorial)
+ self.update_button.connect("clicked",self.controller.update_tutorial,self.tutorial)
+ self.info_button.connect("clicked",self.controller.info_tutorial,self.tutorial)
+ self.delete_button.connect("clicked",self.controller.delete_tutorial,self.tutorial)
+
+ self.controller.get_categories()
+
+
diff --git a/Workshop.activity/WorkshopController.py b/Workshop.activity/WorkshopController.py
new file mode 100644
index 0000000..84b5999
--- /dev/null
+++ b/Workshop.activity/WorkshopController.py
@@ -0,0 +1,201 @@
+"""
+WorkshopController
+
+This module handles user action from the workshop view
+
+"""
+import logging
+from WorkshopModel import Tutorial
+class WorkshopController():
+ def __init__(self,view,model):
+ self.view = view
+ self.model = model
+
+ def tutorial_query(self,widget,keyword):
+ """
+ Handles query from the view
+
+ @param widget the widget that sent the query
+ @param keyword the keyword for the query, empty to get all tutorials
+ """
+ self.model.query([keyword.get_text()])
+
+ def sort_selection_changed(self,widget,data):
+ """
+ Handles selection changes in the sorting selection
+
+ @param widget the widget that sent the query
+ @param sort the property to use to sort tutorial
+ """
+ sorting = widget.get_active_text()
+ self.view.change_sorting(sorting)
+
+
+ def launch_tutorial(self,widget,tutorial):
+ """
+ Handles start tutorial action
+
+ @param widget the widget that triggered the action
+ @param tutorial the tutorial to launch
+ """
+ self.model.launch_tutorial(tutorial)
+
+ def show_details(self,widget,tutorial):
+ """
+ show the details for a tutorial
+
+ @param widget the widget that made the call
+ @param tutorial the tutorial to
+ """
+ self.view.display_detail(tutorial)
+
+ def back_pressed(self,widget,data):
+ """
+ When in detail view, go back to general view
+
+ @param widget the widget that made the call
+ @param data not used
+ """
+ self.view.display_main_view()
+
+ def rate_tutorial(self,tutorial,rating):
+ """
+ Change the rating for a tutorial
+
+ @param tutorial The tutorial to rate
+ @param rating The new rating
+ """
+ self.model.rate_tutorial(tutorial,rating)
+
+ def edit_tutorial(self,widget,tutorial):
+ """
+ Edit the tutorial in the detail view
+
+ @param widget the widget that made the call
+ @param tutorial the tutorial to edit
+ """
+ self.model.edit_tutorial(tutorial)
+
+ def update_tutorial(self,widget,tutorial):
+ """
+ Need to know what this do
+ """
+ pass
+
+ def info_tutorial(self,widget,tutorial):
+ """
+ Edit the infos about the tutorial
+
+ @param widget the widget that made the call
+ @param tutorial the tutorial to edit
+ """
+ self.view.display_info_dialog(tutorial)
+
+ def save_tutorial_info(self,tutorial):
+ """
+ Save the metadata of a tutorial
+
+ @param tutorial The tutorial to update containing the new metadata
+ """
+ self.model.save_metadata(tutorial)
+
+ def delete_tutorial(self,widget,tutorial):
+ """
+ Delete a tutorial
+
+ @param widget the widget that made the call
+ @param tutorial the tutorial to delete
+ """
+ self.model.delete_tutorial(tutorial)
+
+ def publish_tutorial(self,widget,tutorial):
+ """
+ Publish a tutorial
+
+ @param widget the widget that made the call
+ @param tutorial the tutorial to publish
+ """
+ self.model.publish_tutorial(tutorial)
+
+ def unpublish_tutorial(self,widget,tutorial):
+ """
+ Unpublish a tutorial
+
+ @param widget the widget that made the call
+ @param tutorial the tutorial to unpublish
+ """
+ self.model.unpublish_tutorial(tutorial)
+
+
+ def get_categories(self):
+ self.model.get_categories_for_workshop()
+
+class StoreController():
+ def __init__(self,view,model):
+ self.view = view
+ self.model = model
+ self.last_call_search = None
+ self.last_call_keyword = None
+ self.last_call_cat = None
+
+ def show_details(self,widget,tutorial):
+ self.view.show_details(tutorial)
+
+ def get_categories(self):
+ self.last_call_search = False
+ self.model.get_categories()
+
+ def search_store(self,widget,data):
+ cat = data["category"].get_active_text()
+ keyword = data["keyword"].get_text()
+ if cat is None or cat == "":
+ cat = 'all'
+ self.model.search_store(keyword,cat)
+ self.last_call_search = True
+ self.last_call_keyword = keyword
+ self.last_call_cat = cat
+ self.last_call_page = 1
+
+ def get_tutorials_by_category(self,widget,category):
+ self.model.get_tutorials_by_category(category)
+ self.last_call_search = False
+ self.last_call_cat = category
+ self.last_call_page = 1
+
+ def download_tutorial(self,widget,tutorial):
+ self.model.download_tutorial(tutorial)
+
+ def get_also_like(self):
+ self.model.get_also_like()
+
+ def get_popular(self):
+ self.model.get_popular()
+
+ def display_infos(self,widget,tutorial):
+ self.view.display_infos(tutorial)
+
+ def next_page(self,widget,data):
+ self.last_call_page = self.last_call_page + 1
+ if self.last_call_search:
+ self.model.search_store(self.last_call_keyword,self.last_call_cat,page = self.last_call_page )
+ else:
+ self.model.get_tutorials_by_category(self.last_call_cat,page = self.last_call_page )
+
+ def prev_page(self,widget,data):
+ self.last_call_page = self.last_call_page - 1
+ if self.last_call_search:
+ self.model.search_store(self.last_call_keyword,self.last_call_cat,page = self.last_call_page )
+ else:
+ self.model.get_tutorials_by_category(self.last_call_cat,page = self.last_call_page )
+
+ def back_pressed(self,widget,data):
+ self.view.show_search_result()
+
+ def rate_tutorial(self,tutorial,rating):
+ """
+ Change the rating for a tutorial
+
+ @param tutorial The tutorial to rate
+ @param rating The new rating
+ """
+ self.model.rate_tutorial(tutorial,rating)
diff --git a/Workshop.activity/WorkshopModel.py b/Workshop.activity/WorkshopModel.py
new file mode 100644
index 0000000..c52ab5b
--- /dev/null
+++ b/Workshop.activity/WorkshopModel.py
@@ -0,0 +1,503 @@
+# 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
+"""
+WorkshopModel
+
+This module is the model of the Workshop Activity
+"""
+
+
+from sugar.tutorius.vault import *
+from sugar.tutorius.store import *
+from dialogs import LoginDialog,WaitingDialog,ErrorDialog
+import gtk
+import threading
+from copy import deepcopy
+import gobject
+import sys,traceback
+import logging
+
+class Login(object):
+ def __init__(self,proxy,login_view):
+ self.proxy = proxy
+ self.login_view = login_view
+
+ def __call__(self,f):
+ def wrapper(*args):
+ self.model = self.login_view.get_model()
+ self.login_view = LoginDialog()
+ self.login_view.set_model(self.model)
+
+ if self.proxy.get_session_id() == None:
+ result = self.login_view.run()
+ self.login_view.destroy()
+ if result == gtk.RESPONSE_ACCEPT:
+ f(*args)
+ else:
+ f(*args)
+ return wrapper
+
+
+class WorkshopModel():
+
+ store_proxy = StoreProxy("http://bobthebuilder.mine.nu/tutorius/en-US/tutorius")
+ login_view = LoginDialog()
+
+ def __init__(self):
+ self.login_view.set_model(self)
+ self.store_page_number = 1
+
+ def set_workshop_view(self,view):
+ self.workshop_view = view
+
+ def set_store_view(self,view):
+ self.store_view = view
+
+ def login(self,username,password):
+ """
+ Log a user in the store
+
+ @param username The username of the user
+ @param password The password of the user
+ @return True if login succeeded False otherwise
+ """
+ return self.store_proxy.login(username,password)
+
+ def register(self,username,password,email):
+ """
+ Register a new user to the store
+
+ @param username The username of the new user
+ @param password The password of the user
+ @param email The email of the user
+ @return True if user is registered, False otherwise
+ """
+ return self.store_proxy.register_new_user({'nickname':username,'password':password,'email':email})
+
+ def query(self,keyword):
+ """
+ Query the vault for tutorial that are linked with the keyword
+ Update the currently managed tutorials and notifies the view that the managed tutorials have changed
+
+ @param keyword the keyword for the query
+ """
+ if keyword is None or keyword == []:
+ vault_return = Vault.query()
+ else:
+ vault_return = Vault.query(keyword=keyword)
+
+ tutorial_list = []
+ for tuto in vault_return:
+ tutorial_list.append(Tutorial(tuto))
+
+ self.workshop_view.set_tutorial_list(tutorial_list)
+
+ def delete_tutorial(self,tutorial):
+ """
+ Delete a tutorial and updated the currently managed tutorials
+ Notifies the view that the manages tutorials have changed
+
+ @param tutorial the tutorial to delete
+ """
+ Vault.deleteTutorial(tutorial.id)
+ self.query(None)
+ self.workshop_view.display_main_view()
+
+ def update_tutorial_infos(self,tutorial, new_infos):
+ """
+ Updates the metadata on a tutorial and updates the currently managed tutorials
+ Notifies the view that the managed tutorials have changed
+
+ @param tutorial the tutorial to update
+ @param new_infos a dictionnary of the new informations i.e. {"title":"tut1","author":"Max Power"}
+ """
+ Vault.update_metadata(tutorial.id, tutorial.updated_metadata)
+
+ @Login(store_proxy,login_view)
+ def publish_tutorial(self,tutorial):
+ """
+ Publishes a tutorial
+
+ Details to come
+ """
+
+ archive = Vault.get_tutorial_archive(tutorial.id)
+ metadata = self.tutorial_to_store_metadata(tutorial)
+ if not tutorial.published_state:
+
+ remote_id = self.store_proxy.publish(archive,metadata)
+ else:
+ remote_id = self.store_proxy.update_published_tutorial(archive,metadata,tutorial.remote_id)
+
+
+ if remote_id == -1:
+ dialog = ErrorDialog("An error occured while publishing the tutorial")
+ dialog.run()
+ dialog.destroy()
+ return
+
+ tutorial.remote_id = remote_id
+ tutorial.published_state = True
+
+ metadata = self.tutorial_to_vault_metadata(tutorial)
+ Vault.update_metadata(tutorial.id,metadata)
+
+ @Login(store_proxy,login_view)
+ def unpublish_tutorial(self,tutorial):
+ """
+ Unpublishes a tutorial
+
+ @param tutorial The tutorial to unpublish
+ """
+ logging.info(tutorial.remote_id)
+ self.store_proxy.unpublish(tutorial.remote_id)
+
+ def launch_tutorial(self,tutorial):
+ """
+ Lauches a tutorial
+
+ @param tutorial The tutorial to launch
+ """
+ pass
+
+ @Login(store_proxy,login_view)
+ def rate_tutorial(self,tutorial,rating):
+ """
+ Rate the tutorial
+
+ @param tutorial The tutorial to rate
+ @param rating The new rating for the tutorial
+ """
+ tutorial.rating = rating
+ logging.info(tutorial.updated_metadata)
+ logging.info(tutorial.remote_id)
+ if tutorial.remote_id is not None and int(tutorial.remote_id) > 0:
+ logging.info("this is here")
+ self.store_proxy.rate(rating,tutorial.remote_id)
+ self.workshop_view.refresh_content()
+
+ def edit_tutorial(self,tutorial):
+ """
+ Edit a tutorial
+
+ @param tutorial The tutorial to edit
+ """
+ pass
+
+ def save_metadata(self,tutorial):
+ """
+ Save the metadata of a tutorial
+
+ @param tutorial The tutorial to udpate containing the new metadata
+ """
+ metadata = self.tutorial_to_vault_metadata(tutorial)
+ Vault.update_metadata(tutorial.id,metadata)
+ self.workshop_view.refresh_content()
+
+ def get_categories_for_workshop(self):
+ """
+ Get all categories for selecting one in the workshop
+ """
+ self.categories = self.store_proxy.get_categories()
+ result = []
+ for category in self.categories:
+ result.append(category["name"])
+
+ self.workshop_view.set_categories(result)
+
+
+#Function related to the store
+
+ def get_categories(self):
+ """
+ Get all categories of tutorial from the store
+ """
+ self.categories = self.store_proxy.get_categories()
+ result = ['all']
+ for category in self.categories:
+ result.append(category["name"])
+
+ self.store_view.set_categories(result)
+
+ def search_store(self,keyword,category,page=1):
+ """
+ Search the store for specific tutorial
+
+ @param keyword The keyword to search for
+ @param category The category to search tutorial for
+ @param page The page result to return
+ """
+ tut_list = self.store_proxy.search(keyword,category,page)
+ show_next = False
+ show_prev = True
+ if page == 1:
+ show_prev = False
+ if len(tut_list) == 10:
+ next_list = self.store_proxy.search(keyword,category,page+1)
+ if len(next_list) > 0:
+ show_next = True
+ tutorials = []
+ for tut in tut_list:
+ tutorials.append(Tutorial(tut))
+ self.store_view.set_tutorial_list(tutorials)
+ self.store_view.set_button_sensitive(show_prev,show_next)
+
+ def get_tutorials_by_category(self,category,page=1):
+ """
+ Get all the tutorial in a specified category
+
+ @param category The category to search
+ @param The page result to return
+ """
+ for cat in self.categories:
+ if cat["name"] == category:
+ category = cat["id"]
+ break
+ tut_list = self.store_proxy.get_tutorials(category,page)
+ show_next = False
+ show_prev = True
+ if page == 1:
+ show_prev = False
+ if len(tut_list) == 10:
+ next_list = self.store_proxy.get_tutorials(category,page+1)
+ if len(next_list) > 0:
+ show_next = True
+ tutorials = []
+ for tut in tut_list:
+ tutorials.append(Tutorial(tut))
+ self.store_view.set_tutorial_list(tutorials)
+ self.store_view.set_button_sensitive(show_prev,show_next)
+
+ def download_tutorial(self,tutorial):
+ """
+ Download a tutorial from the store
+
+ @param tutorial The tutorial to download
+ """
+ thread = threading.Thread(target = self.background_download,kwargs={"tutorial":tutorial})
+ self.dialog = WaitingDialog(thread.start,{})
+ thread.start()
+ self.dialog.run()
+ self.dialog.destroy()
+
+
+ def background_download(self,tutorial):
+ try:
+ downloaded = self.store_proxy.download_tutorial(int(tutorial.remote_id))
+ temp_file = open('temp.zip','w')
+ temp_file.write(downloaded.read())
+ temp_file.close()
+ Vault.installTutorials(os.getcwd(),'temp.zip')
+ temp_list = Vault.query(str(tutorial.name))
+ logging.info(temp_list)
+ for tut in temp_list:
+ tuto = Tutorial(tut)
+ if tutorial.name == tuto.name:
+ tuto.remote_id = tutorial.remote_id
+ Vault.update_metadata(tuto.id,tuto.updated_metadata)
+ except:
+ traceback.print_exc()
+ finally:
+ gobject.idle_add(self.dialog.response,0)
+
+ def get_popular(self):
+ tutorials = self.store_proxy.list('Popular')
+ result = []
+ for tutorial in tutorials:
+ result.append(Tutorial(tutorial))
+ self.store_view.set_popular(result)
+
+ def get_also_like(self):
+ tutorials = self.store_proxy.list('Recommended')
+ result = []
+ for tutorial in tutorials:
+ result.append(Tutorial(tutorial))
+ self.store_view.set_also_like(result)
+
+
+
+ def tutorial_to_store_metadata(self,tutorial):
+ metadata = {}
+ metadata['guid'] = tutorial.id
+ metadata['name'] = tutorial.name
+ metadata['version'] = str(tutorial.version)
+ metadata['summary'] = tutorial.description
+ metadata['homepage'] = "http://www.tutorius.org"
+ metadata['description'] = tutorial.description
+ metadata['filename'] = tutorial.name +".zip"
+ for category in self.categories:
+ if category["name"] == tutorial.category:
+ metadata['cat1'] = category["id"]
+ logging.info(metadata)
+ return metadata
+
+ def tutorial_to_vault_metadata(self,tutorial):
+ metadata = {}
+ for key in tutorial.updated_metadata.keys():
+ if key == 'activities':
+ metadata[key] = tutorial.updated_metadata[key]
+ else:
+ metadata[key] = str(tutorial.updated_metadata[key])
+ return metadata
+
+class Tutorial(object):
+ """
+ Wrapper for tutorial metadata
+ """
+ def __init__(self,metadata_dict):
+ object.__init__(self)
+ self.__original_dict = metadata_dict
+ self.__update_dict = {}
+ for key in self.__original_dict.keys():
+ self.__update_dict[key] = self.__original_dict[key]
+
+ if 'name' in self.__original_dict:
+ self.__name = self.__original_dict['name']
+ else:
+ self.__name = ""
+
+ if 'version' in self.__original_dict:
+ if self.__original_dict['version'] == '':
+ self.__version = 0
+ else:
+ self.__version = int(self.__original_dict['version'])
+ else:
+ self.__version = 0
+
+ if 'description' in self.__original_dict:
+ self.__description = self.__original_dict['description']
+ else:
+ self.__description = ""
+
+ if 'author' in self.__original_dict:
+ self.__author = self.__original_dict['author']
+ else:
+ self.__author = ""
+
+ if 'rating' in self.__original_dict:
+ if self.__original_dict['rating'] == '':
+ self.__rating = 0
+ else:
+ self.__rating = float(self.__original_dict['rating'])
+ else:
+ self.__rating = 0
+
+ if 'category' in self.__original_dict:
+ self.__category = self.__original_dict['category']
+ else:
+ self.__category = ""
+
+ if 'publish_state' in self.__original_dict:
+ #I'm sorry for this
+ temp = self.__original_dict['publish_state']
+ temp = temp.lower()
+ if temp == 'false':
+ self.__published_state = False
+ elif temp == 'true':
+ self.__published_state = True
+ else:
+ self.__published_state = None
+ else:
+ self.__published_state = None
+
+ if 'guid' in self.__original_dict:
+ self.__id = self.__original_dict['guid']
+ else:
+ self.__id = ""
+
+ if 'id' in self.__original_dict:
+ self.__remote_id = self.__original_dict['id']
+ else:
+ self.__remote_id = ""
+
+
+ def get_name(self):
+ return self.__name
+
+ def set_name(self,name):
+ self.__name = name
+ self.__update_dict['name'] = name
+
+ def get_version(self):
+ return self.__version
+
+ def set_version(self,version):
+ self.__version = version
+ self.__update_dict['version'] = version
+
+ def get_description(self):
+ return self.__description
+
+ def set_description(self,description):
+ self.__description = description
+ self.__update_dict['description'] = description
+
+ def get_author(self):
+ return self.__author
+
+ def set_author(self,author):
+ self.__author = author
+ self.__update_dict['author'] = author
+
+ def get_rating(self):
+ return self.__rating
+
+ def set_rating(self,rating):
+ self.__rating = rating
+ self.__update_dict['rating'] = rating
+
+ def get_category(self):
+ return self.__category
+
+ def set_category(self,category):
+ self.__category = category
+ self.__update_dict['category'] = category
+
+ def get_published_state(self):
+ return self.__published_state
+
+ def set_published_state(self,published_state):
+ self.__published_state = published_state
+ self.__update_dict['published_state'] = published_state
+
+ def get_id(self):
+ return self.__id
+
+ def set_id(self,id):
+ self.__id = id
+ self.__update_dict['guid'] = id
+
+ def get_remote_id(self):
+ return self.__remote_id
+
+ def set_remote_id(self,id):
+ self.__remote_id = id
+ self.__update_dict['id'] = id
+
+ def get_updated_metadata(self):
+ return self.__update_dict
+
+ name = property(get_name,set_name)
+ version = property(get_version, set_version)
+ description = property(get_description,set_description)
+ author = property(get_author,set_author)
+ rating = property(get_rating,set_rating)
+ category = property(get_category,set_category)
+ published_state = property(get_published_state,set_published_state)
+ id = property(get_id,set_id)
+ remote_id = property(get_remote_id, set_remote_id)
+ updated_metadata = property(get_updated_metadata)
+
diff --git a/Workshop.activity/activity/activity.info b/Workshop.activity/activity/activity.info
new file mode 100644
index 0000000..02dbee1
--- /dev/null
+++ b/Workshop.activity/activity/activity.info
@@ -0,0 +1,8 @@
+[Activity]
+name = Tutorius
+bundle_id = org.laptop.TutoriusActivity
+class = TutoriusActivity.TutoriusActivity
+icon = someicon
+activity_version = 1
+host_version = 1
+show_launcher = yes
diff --git a/Workshop.activity/activity/someicon.svg b/Workshop.activity/activity/someicon.svg
new file mode 100644
index 0000000..bb28f04
--- /dev/null
+++ b/Workshop.activity/activity/someicon.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" ?><!-- Created with Inkscape (http://www.inkscape.org/) --><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#000000">
+ <!ENTITY fill_color "#ffffff">
+]><svg height="55px" id="svg2393" inkscape:output_extension="org.inkscape.output.svg.inkscape" inkscape:version="0.47pre1 r21720" sodipodi:docname="tutortool.svg" sodipodi:version="0.32" version="1.1" width="55px" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg">
+ <defs id="defs2395">
+ <inkscape:perspective id="perspective2401" inkscape:persp3d-origin="16 : 10.666667 : 1" inkscape:vp_x="0 : 16 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_z="32 : 16 : 1" sodipodi:type="inkscape:persp3d"/>
+ </defs>
+ <sodipodi:namedview bordercolor="#666666" borderopacity="1.0" id="base" inkscape:current-layer="layer1" inkscape:cx="3.7661233" inkscape:cy="33.132055" inkscape:document-units="px" inkscape:grid-bbox="true" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:window-height="675" inkscape:window-maximized="0" inkscape:window-width="1057" inkscape:window-x="108" inkscape:window-y="45" inkscape:zoom="3.9590209" pagecolor="#ffffff" showgrid="true"/>
+ <metadata id="metadata2398">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1" inkscape:groupmode="layer" inkscape:label="Layer 1" transform="translate(0,23)">
+ <path d="m 38.01548,1.5478747 c 0,7.1837999 -7.3667,13.0141283 -16.443525,13.0141283 -2.269208,0 -8.124729,3.152936 -13.9608513,4.252763 C 13.382177,14.110994 11.434521,11.926642 9.9463815,10.748864 6.9701032,8.3933076 5.1284282,5.1397735 5.1284282,1.5478747 c 0,-7.1837994 7.3666998,-13.0141297 16.4435268,-13.0141297 9.076825,0 16.443525,5.8303303 16.443525,13.0141297 z" id="path2403" sodipodi:nodetypes="cscsssc" style="fill:&fill_color;;fill-opacity:1;fill-rule:nonzero;stroke:&stroke_color;;stroke-width:1.96931934;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
+ <path d="m 50.150276,6.4721386 c 0,2.621116 -1.428036,4.9953144 -3.735846,6.7142344 -1.153905,0.85946 -1.824287,2.434433 1.398853,6.784273 -6.258422,-3.991066 -8.65379,-4.001712 -10.413335,-4.001712 -7.03818,0 -12.750327,-4.254565 -12.750327,-9.4967954 0,-5.2422321 5.712147,-9.4967971 12.750327,-9.4967971 7.038182,0 12.750328,4.254565 12.750328,9.4967971 z" id="path3175" sodipodi:nodetypes="cccsssc" style="fill:&fill_color;;fill-opacity:1;fill-rule:nonzero;stroke:&stroke_color;;stroke-width:1.96931934;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/Workshop.activity/arrow_back.png b/Workshop.activity/arrow_back.png
new file mode 100644
index 0000000..8263674
--- /dev/null
+++ b/Workshop.activity/arrow_back.png
Binary files differ
diff --git a/Workshop.activity/arrow_next.png b/Workshop.activity/arrow_next.png
new file mode 100644
index 0000000..888e431
--- /dev/null
+++ b/Workshop.activity/arrow_next.png
Binary files differ
diff --git a/Workshop.activity/dialogs.py b/Workshop.activity/dialogs.py
new file mode 100644
index 0000000..3d5f04a
--- /dev/null
+++ b/Workshop.activity/dialogs.py
@@ -0,0 +1,346 @@
+# 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
+import gtk
+import logging
+import threading
+
+class ErrorDialog(gtk.Dialog):
+ def __init__(self,text):
+ gtk.Dialog.__init__(self,"Error",None,gtk.DIALOG_MODAL,(gtk.STOCK_OK,gtk.RESPONSE_ACCEPT))
+ self.label = gtk.Label(text)
+ self.vbox.pack_start(self.label,padding = 10)
+ self.vbox.show_all()
+
+class WaitingDialog(gtk.Dialog):
+ def __init__(self,callback,kwargs):
+ self.callback = callback
+ self.kwargs = kwargs
+ gtk.Dialog.__init__(self,"Please wait...",None,gtk.DIALOG_MODAL)
+ self.label = gtk.Label("Please wait while getting information from the store")
+ self.vbox.pack_start(self.label, padding = 10)
+ self.vbox.show_all()
+
+ def operation_complete(self):
+ self.label.set_text("Operation complete")
+ self.ok_button = gtk.Button("Ok")
+ self.add_button(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
+
+
+
+class LoginDialog(gtk.Dialog):
+ def __init__(self):
+ gtk.Dialog.__init__(self,"Login",None,gtk.DIALOG_MODAL)
+ ok_button = gtk.Button("Login")
+ ok_button.connect("clicked",self.ok_clicked,None)
+ ok_button.show()
+ self.action_area.pack_start(ok_button)
+ self.add_button("Cancel",gtk.RESPONSE_REJECT)
+ self.set_resizable(False)
+ self.prepare_dialog()
+
+ def set_model(self,model):
+ self.model = model
+
+ def get_model(self):
+ return self.model
+
+ def prepare_dialog(self):
+ self.user_line = gtk.HBox(False,10)
+ self.pass_line = gtk.HBox(False,10)
+ self.register_line = gtk.HBox(False)
+ self.remember_line = gtk.HBox(False)
+
+ self.username_lbl = gtk.Label("Email:")
+ self.username_entry = gtk.Entry()
+ self.username_entry.set_width_chars(40)
+
+ self.password_lbl = gtk.Label("Password:")
+ self.password_entry = gtk.Entry()
+ self.password_entry.set_visibility(False)
+ self.password_entry.set_width_chars(40)
+
+ self.register_me = gtk.LinkButton("","Register Now!")
+ self.not_registered = gtk.Label("Not Registered? ")
+
+ self.remember_user = gtk.CheckButton("Remember my username",False)
+
+ self.register_me.connect("clicked",self.click_link,None)
+
+ self.user_line.pack_start(self.username_lbl,False)
+ self.user_line.pack_end(self.username_entry,False)
+
+ self.pass_line.pack_start(self.password_lbl,False)
+ self.pass_line.pack_end(self.password_entry,False)
+
+ self.register_line.pack_end(self.register_me,False)
+ self.register_line.pack_end(self.not_registered,False)
+
+ self.remember_line.pack_start(self.remember_user,False,padding=80)
+
+ self.vbox.pack_start(self.register_line,False)
+ self.vbox.pack_start(self.user_line,False,padding=10)
+ self.vbox.pack_start(self.pass_line,False,padding=10)
+ self.vbox.pack_start(self.remember_line,False)
+
+ self.vbox.show()
+ self.user_line.show()
+ self.pass_line.show()
+ self.register_line.show()
+ self.remember_line.show()
+
+ self.username_lbl.show()
+ self.username_entry.show()
+
+ self.password_lbl.show()
+ self.password_entry.show()
+
+ self.register_me.show()
+ self.not_registered.show()
+
+ self.remember_user.show()
+
+ def ok_clicked(self,widget,data=None):
+ success = self.model.login(self.username_entry.get_text(), self.password_entry.get_text())
+ if success:
+ self.response(gtk.RESPONSE_ACCEPT)
+ else:
+ errorDialog = ErrorDialog("The password or the email address is wrong")
+ errorDialog.run()
+ errorDialog.destroy()
+
+
+ def click_link(self,widget,data=None):
+ self.register_dialog = RegisterDialog(self.model)
+ self.register_dialog.run()
+ self.register_dialog.destroy()
+
+class RegisterDialog(gtk.Dialog):
+ def __init__(self,model):
+ gtk.Dialog.__init__(self,"Register",None,gtk.DIALOG_MODAL)
+ self.model = model
+ ok_button = gtk.Button("Register")
+ ok_button.connect("clicked",self.ok_clicked,None)
+ ok_button.show()
+ self.action_area.pack_start(ok_button)
+ self.add_button("Cancel",gtk.RESPONSE_REJECT)
+ self.set_resizable(False)
+ self.create_content()
+
+ def ok_clicked(self,widget,data=None):
+ if not self.data_validation():
+ return
+ success = self.model.register(self.entries[2].get_text(),self.entries[3].get_text(),self.entries[0].get_text())
+ if success :
+ self.response(gtk.RESPONSE_ACCEPT)
+ else:
+ self.show_error_dialog("An error occured while registering the user")
+
+ def data_validation(self):
+ username = self.entries[2].get_text()
+ password = self.entries[3].get_text()
+ confirmation = self.entries[4].get_text()
+ email = self.entries[0].get_text()
+
+ if username.trim() == "":
+ self.show_error_dialog('You must choose a username')
+ return False
+ if password.trim() == '':
+ self.show_error_dialog('You must provide a password')
+ return False
+ if email.trim() == '':
+ self.show_error_dialog('You must provide a valid email address')
+ return False
+ if password != confirmation:
+ self.show_error_dialog('The password and confirmation must be the same')
+ return False
+
+ return True
+
+ def show_error_dialog(self,error_message):
+ errorDialog = ErrorDialog("An error occured while registering a user")
+ errorDialog.run()
+ errorDialog.destroy()
+
+ def create_content(self):
+ entry_length = 40
+ table = gtk.Table(10,4,False)
+ self.entries = []
+ labels = ["Email address","Name","Username","Password","Confirmation","Location","Web Site"]
+ required=[True,True,True,True,True,False,False]
+ hidden=[False,False,False,True,True,False,False]
+ for x in range(6):
+ lbl = gtk.Label(labels[x]+":")
+ entry = gtk.Entry()
+ self.entries.append(entry)
+ entry.set_width_chars(entry_length)
+ table.attach(lbl,0,1,x,x+1,xpadding=10)
+ table.attach(entry,1,2,x,x+1,ypadding=10)
+ if hidden[x]:
+ entry.set_visibility(False)
+ if required[x]:
+ required_lbl = gtk.Label("*")
+ table.attach(required_lbl,2,3,x,x+1)
+ required_lbl.show()
+ lbl.set_alignment(0.0,0.5)
+ lbl.show()
+ entry.show()
+
+
+ if_required = gtk.Label(" * Required Field")
+ table.attach(if_required,3,4,0,1)
+
+ under_13 = gtk.CheckButton("I am 13 years old or younger",False)
+ legal = gtk.CheckButton("I have read the",False)
+ legal_notices=gtk.LinkButton('',"legal notices")
+ and_lbl = gtk.Label("and")
+ privacy = gtk.LinkButton('',"privacy statement")
+ hbox = gtk.HBox(False,0)
+ hbox.pack_start(legal)
+ hbox.pack_start(legal_notices)
+ hbox.pack_start(and_lbl)
+ hbox.pack_start(privacy)
+
+ table.attach(under_13,1,2,7,8)
+ table.attach(hbox,1,2,8,9)
+ under_13.show()
+ legal.show()
+ legal_notices.show()
+ and_lbl.show()
+ privacy.show()
+ hbox.show()
+ if_required.show()
+ table.show()
+ self.vbox.pack_start(table)
+
+class InfoDialog(gtk.Dialog):
+ def __init__(self,tutorial,controller,categories):
+ gtk.Dialog.__init__(self,"Tutorial Info",None,gtk.DIALOG_MODAL)
+ self.tutorial = tutorial
+ self.controller = controller
+ self.categories = categories
+ ok_button = gtk.Button("Save")
+ ok_button.connect("clicked",self.ok_clicked,None)
+ ok_button.show()
+ self.action_area.pack_start(ok_button)
+ self.add_button("Cancel",gtk.RESPONSE_REJECT)
+ self.set_resizable(False)
+ self.prepare_content()
+
+ def prepare_content(self):
+ table = gtk.Table(4,2,False)
+ labels = ["Tutorial Name","Version","Description","Category"]
+ entry_length = [40,40,40]
+ self.entries = []
+
+ self.name_entry = gtk.Entry()
+ self.name_entry.set_width_chars(40)
+ self.name_entry.set_text(self.tutorial.name)
+ table.attach(self.name_entry,1,2,0,1,ypadding=10)
+
+
+ self.version_entry = gtk.Entry()
+ self.version_entry.set_width_chars(40)
+ self.version_entry.set_text(str(self.tutorial.version))
+ table.attach(self.version_entry,1,2,1,2,ypadding=10)
+
+ self.desc_entry = gtk.Entry()
+ self.desc_entry.set_width_chars(40)
+ self.desc_entry.set_text(self.tutorial.description)
+ table.attach(self.desc_entry,1,2,2,3,ypadding=10)
+
+ self.category_entry = gtk.combo_box_new_text()
+ logging.info(len(self.categories))
+ logging.info(self.categories)
+ for cat in range(0,len(self.categories)):
+ self.category_entry.append_text(self.categories[cat])
+ logging.info(self.tutorial.category)
+ logging.info(self.categories[cat])
+ if self.tutorial.category != '' and self.tutorial.category == str(self.categories[cat]):
+ self.category_entry.set_active(cat)
+ table.attach(self.category_entry,1,2,3,4,ypadding=10)
+
+ for x in range(0,4):
+ label = gtk.Label(labels[x])
+ table.attach(label,0,1,x,x+1,xpadding=10)
+ label.set_alignment(0.0,0.5)
+ table.show_all()
+ self.vbox.pack_start(table)
+
+ def ok_clicked(self,widget,data):
+ self.tutorial.version = int(self.version_entry.get_text())
+ self.tutorial.name = self.name_entry.get_text()
+ self.tutorial.description = self.desc_entry.get_text()
+ self.tutorial.category = self.category_entry.get_active_text()
+
+ self.controller.save_tutorial_info(self.tutorial)
+ self.response(gtk.RESPONSE_ACCEPT)
+
+class StoreInformationDialog(gtk.Dialog):
+ def __init__(self,tutorial):
+ self.text = '<span size="large"> %(text)s: </span>'
+ self.value = '<span size="large"> %(text)s </span>'
+
+ gtk.Dialog.__init__(self,"Tutorial Information",None,gtk.DIALOG_MODAL,(gtk.STOCK_OK,gtk.RESPONSE_ACCEPT))
+
+ self.table = gtk.Table(6,2,False)
+ self.tut_name = gtk.Label("")
+ self.tut_name.set_markup(self.text % {"text":"Name"})
+ self.tut_name_value = gtk.Label()
+ self.tut_name_value.set_markup(self.value % {"text":tutorial.name})
+
+ self.author = gtk.Label()
+ self.author.set_markup(self.text % {"text":"Author"})
+ self.author_value = gtk.Label()
+ self.author_value.set_markup(self.value % {"text":tutorial.author})
+
+
+ self.desc = gtk.Label()
+ self.desc.set_markup(self.text % {"text":"Description"})
+ self.desc_value = gtk.Label()
+ self.desc_value.set_markup(self.value % {"text":tutorial.description})
+
+
+ self.version = gtk.Label()
+ self.version_value = gtk.Label()
+ self.version.set_markup(self.text % {"text":"Version"})
+ self.version_value.set_markup(self.value % {"text":str(tutorial.version)})
+
+ self.rating = gtk.Label()
+ self.rating_value = gtk.Label()
+ self.rating.set_markup(self.text % {"text":"Rating"})
+ self.rating_value.set_markup(self.value % {"text":str(tutorial.rating)})
+
+ self.category = gtk.Label()
+ self.category_value = gtk.Label()
+ self.category.set_markup(self.text % {"text":"Category"})
+ self.category_value.set_markup(self.value % {"text":tutorial.category})
+
+ self.table.attach(self.tut_name,0,1,0,1,ypadding=5)
+ self.table.attach(self.tut_name_value,1,2,0,1,ypadding=5)
+ self.table.attach(self.author,0,1,1,2,ypadding=5)
+ self.table.attach(self.author_value,1,2,1,2,ypadding=5)
+ self.table.attach(self.desc,0,1,2,3,ypadding=5)
+ self.table.attach(self.desc_value,1,2,2,3,ypadding=5)
+ self.table.attach(self.version,0,1,3,4,ypadding=5)
+ self.table.attach(self.version_value,1,2,3,4,ypadding=5)
+ self.table.attach(self.rating,0,1,4,5,ypadding=5)
+ self.table.attach(self.rating_value,1,2,4,5,ypadding=5)
+ self.table.attach(self.category,0,1,5,6,ypadding=5)
+ self.table.attach(self.category_value,1,2,5,6,ypadding=5)
+
+ self.vbox.pack_start(self.table)
+ self.table.show_all()
+
diff --git a/Workshop.activity/full_star.png b/Workshop.activity/full_star.png
new file mode 100644
index 0000000..9f7d095
--- /dev/null
+++ b/Workshop.activity/full_star.png
Binary files differ
diff --git a/Workshop.activity/grayed_star.png b/Workshop.activity/grayed_star.png
new file mode 100644
index 0000000..7f8b1e1
--- /dev/null
+++ b/Workshop.activity/grayed_star.png
Binary files differ
diff --git a/Workshop.activity/half_star.png b/Workshop.activity/half_star.png
new file mode 100644
index 0000000..48ebae3
--- /dev/null
+++ b/Workshop.activity/half_star.png
Binary files differ
diff --git a/Workshop.activity/icon.svg b/Workshop.activity/icon.svg
new file mode 100644
index 0000000..bb28f04
--- /dev/null
+++ b/Workshop.activity/icon.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" ?><!-- Created with Inkscape (http://www.inkscape.org/) --><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#000000">
+ <!ENTITY fill_color "#ffffff">
+]><svg height="55px" id="svg2393" inkscape:output_extension="org.inkscape.output.svg.inkscape" inkscape:version="0.47pre1 r21720" sodipodi:docname="tutortool.svg" sodipodi:version="0.32" version="1.1" width="55px" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg">
+ <defs id="defs2395">
+ <inkscape:perspective id="perspective2401" inkscape:persp3d-origin="16 : 10.666667 : 1" inkscape:vp_x="0 : 16 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_z="32 : 16 : 1" sodipodi:type="inkscape:persp3d"/>
+ </defs>
+ <sodipodi:namedview bordercolor="#666666" borderopacity="1.0" id="base" inkscape:current-layer="layer1" inkscape:cx="3.7661233" inkscape:cy="33.132055" inkscape:document-units="px" inkscape:grid-bbox="true" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:window-height="675" inkscape:window-maximized="0" inkscape:window-width="1057" inkscape:window-x="108" inkscape:window-y="45" inkscape:zoom="3.9590209" pagecolor="#ffffff" showgrid="true"/>
+ <metadata id="metadata2398">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1" inkscape:groupmode="layer" inkscape:label="Layer 1" transform="translate(0,23)">
+ <path d="m 38.01548,1.5478747 c 0,7.1837999 -7.3667,13.0141283 -16.443525,13.0141283 -2.269208,0 -8.124729,3.152936 -13.9608513,4.252763 C 13.382177,14.110994 11.434521,11.926642 9.9463815,10.748864 6.9701032,8.3933076 5.1284282,5.1397735 5.1284282,1.5478747 c 0,-7.1837994 7.3666998,-13.0141297 16.4435268,-13.0141297 9.076825,0 16.443525,5.8303303 16.443525,13.0141297 z" id="path2403" sodipodi:nodetypes="cscsssc" style="fill:&fill_color;;fill-opacity:1;fill-rule:nonzero;stroke:&stroke_color;;stroke-width:1.96931934;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
+ <path d="m 50.150276,6.4721386 c 0,2.621116 -1.428036,4.9953144 -3.735846,6.7142344 -1.153905,0.85946 -1.824287,2.434433 1.398853,6.784273 -6.258422,-3.991066 -8.65379,-4.001712 -10.413335,-4.001712 -7.03818,0 -12.750327,-4.254565 -12.750327,-9.4967954 0,-5.2422321 5.712147,-9.4967971 12.750327,-9.4967971 7.038182,0 12.750328,4.254565 12.750328,9.4967971 z" id="path3175" sodipodi:nodetypes="cccsssc" style="fill:&fill_color;;fill-opacity:1;fill-rule:nonzero;stroke:&stroke_color;;stroke-width:1.96931934;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/Workshop.activity/setup.py b/Workshop.activity/setup.py
new file mode 100755
index 0000000..f95b630
--- /dev/null
+++ b/Workshop.activity/setup.py
@@ -0,0 +1,3 @@
+#! /usr/bin/env python
+from sugar.activity import bundlebuilder
+bundlebuilder.start()
diff --git a/addons/bubblemessage.py b/addons/bubblemessage.py
index 7c3c0fd..53387bf 100644
--- a/addons/bubblemessage.py
+++ b/addons/bubblemessage.py
@@ -94,7 +94,7 @@ class BubbleMessage(Action):
self.overlay.put(self._bubble, x, y)
self._bubble.show()
- self._drag = DragWrapper(self._bubble, self.position, True)
+ self._drag = DragWrapper(self._bubble, self.position, update_action_cb=self.update_property, draggable=True)
def exit_editmode(self, *args):
if self._drag.moved:
diff --git a/addons/bubblemessagewimg.py b/addons/bubblemessagewimg.py
index 1d5d5d3..514a311 100644
--- a/addons/bubblemessagewimg.py
+++ b/addons/bubblemessagewimg.py
@@ -97,7 +97,7 @@ class BubbleMessageWImg(Action):
self.overlay.put(self._bubble, x, y)
self._bubble.show()
- self._drag = DragWrapper(self._bubble, self.position, True)
+ self._drag = DragWrapper(self._bubble, self.position, update_action_cb=self.update_property, draggable=True)
def exit_editmode(self, *args):
x,y = self._drag.position
diff --git a/addons/gtkwidgeteventfilter.py b/addons/gtkwidgeteventfilter.py
index b5ce9ae..ac14399 100644
--- a/addons/gtkwidgeteventfilter.py
+++ b/addons/gtkwidgeteventfilter.py
@@ -65,6 +65,5 @@ __event__ = {
"icon" : "player_play",
"class" : GtkWidgetEventFilter,
"mandatory_props" : ["object_id", "event_name"],
- "test" : True
}
diff --git a/addons/gtkwidgettypefilter.py b/addons/gtkwidgettypefilter.py
index 4ffecb5..8faf172 100644
--- a/addons/gtkwidgettypefilter.py
+++ b/addons/gtkwidgettypefilter.py
@@ -96,5 +96,6 @@ __event__ = {
'display_name' : 'Widget Filter',
'icon' : '',
'class' : GtkWidgetTypeFilter,
- 'mandatory_props' : ['next_state', 'object_id']
+ 'mandatory_props' : ['next_state', 'object_id'],
+ "test" : True,
}
diff --git a/data/ui/creator.glade b/data/ui/creator.glade
index 1c9669d..aeba19c 100644
--- a/data/ui/creator.glade
+++ b/data/ui/creator.glade
@@ -1,16 +1,19 @@
-<?xml version="1.0"?>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.4.5 on Sun Nov 1 16:39:50 2009 -->
<glade-interface>
- <!-- interface-requires gtk+ 2.16 -->
- <!-- interface-naming-policy project-wide -->
<widget class="GtkWindow" id="mainwindow">
<property name="width_request">300</property>
<property name="height_request">500</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="title" translatable="yes">Toolbox</property>
<property name="resizable">False</property>
- <property name="window_position">center-on-parent</property>
+ <property name="decorated">False</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
<property name="default_width">200</property>
<property name="default_height">500</property>
<property name="destroy_with_parent">True</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_UTILITY</property>
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="focus_on_map">False</property>
@@ -19,35 +22,37 @@
<child>
<widget class="GtkVBox" id="vbox1">
<property name="visible">True</property>
- <property name="orientation">vertical</property>
+ <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<property name="spacing">5</property>
+ <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<widget class="GtkHButtonBox" id="hbuttonbox1">
<property name="visible">True</property>
<property name="spacing">5</property>
- <property name="layout_style">start</property>
+ <property name="layout_style">GTK_BUTTONBOX_START</property>
<child>
<widget class="GtkButton" id="button2">
- <property name="label">gtk-save</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
+ <property name="label">gtk-save</property>
<property name="use_stock">True</property>
+ <property name="response_id">0</property>
<signal name="clicked" handler="on_save_clicked"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
- <property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="button4">
- <property name="label">gtk-quit</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
+ <property name="label">gtk-quit</property>
<property name="use_stock">True</property>
+ <property name="response_id">0</property>
<signal name="clicked" handler="on_quit_clicked"/>
</widget>
<packing>
@@ -60,24 +65,24 @@
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
- <property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="hscrollbar_policy">never</property>
- <property name="vscrollbar_policy">automatic</property>
- <property name="shadow_type">in</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
<child>
<widget class="GtkViewport" id="viewport1">
<property name="visible">True</property>
- <property name="resize_mode">queue</property>
+ <property name="resize_mode">GTK_RESIZE_QUEUE</property>
<child>
<widget class="GtkVBox" id="vbox2">
<property name="visible">True</property>
- <property name="orientation">vertical</property>
+ <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
+ <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<widget class="GtkExpander" id="expander1">
<property name="visible">True</property>
@@ -90,7 +95,6 @@
<property name="columns">2</property>
<property name="row_spacing">0</property>
<property name="column_spacing">0</property>
- <property name="item_padding">0</property>
<signal name="item_activated" handler="on_action_activate"/>
</widget>
</child>
@@ -106,7 +110,6 @@
</widget>
<packing>
<property name="expand">False</property>
- <property name="position">0</property>
</packing>
</child>
<child>
@@ -121,7 +124,6 @@
<property name="columns">2</property>
<property name="row_spacing">0</property>
<property name="column_spacing">0</property>
- <property name="item_padding">0</property>
<signal name="item_activated" handler="on_event_activate"/>
</widget>
</child>
@@ -153,8 +155,9 @@
<child>
<widget class="GtkVBox" id="propbox">
<property name="visible">True</property>
- <property name="orientation">vertical</property>
+ <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<property name="spacing">10</property>
+ <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<placeholder/>
</child>
@@ -169,26 +172,27 @@
<widget class="GtkHButtonBox" id="hbuttonbox2">
<property name="visible">True</property>
<property name="spacing">5</property>
- <property name="layout_style">start</property>
+ <property name="layout_style">GTK_BUTTONBOX_START</property>
<child>
<widget class="GtkButton" id="button1">
- <property name="label">gtk-media-record</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
+ <property name="label">gtk-media-record</property>
<property name="use_stock">True</property>
+ <property name="response_id">0</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
- <property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="button3">
- <property name="label">gtk-media-stop</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
+ <property name="label">gtk-media-stop</property>
<property name="use_stock">True</property>
+ <property name="response_id">0</property>
</widget>
<packing>
<property name="expand">False</property>
diff --git a/setup.py b/setup.py
index e33873c..49bae4c 100755
--- a/setup.py
+++ b/setup.py
@@ -74,7 +74,7 @@ class TestCommand(Command):
sources)
coverage.report(sources)
coverage.erase()
-
+
def _listsources(self, arg, dirname, fnames):
fnames = filter(lambda x:x.endswith('.py'), fnames)
for name in fnames:
@@ -83,7 +83,7 @@ class TestCommand(Command):
setup(name='Tutorius',
version='0.0',
description='Interactive tutor and Tutorial creator',
- maintainer='Simon Poirier',
+ maintainer='Simon Poirier',
maintainer_email='simpoir@gmail.com',
author='Tutorius team',
author_email='sugar-narratives@googlegroups.com',
@@ -97,13 +97,15 @@ setup(name='Tutorius',
'sugar.tutorius.apilib.httplib2',
],
package_dir={
- 'sugar.tutorius': 'tutorius',
- 'sugar.tutorius.addons': 'addons',
- },
+ 'sugar.tutorius': 'tutorius',
+ 'sugar.tutorius.addons': 'addons',
+ },
cmdclass = {'test': TestCommand},
data_files=[('share/icons/sugar/scalable/actions', glob.glob('data/icons/*.svg')),
+ ('share/icons/sugar/scalable/device', ['data/icons/tutortool.svg']),
('share/tutorius/ui', glob.glob('data/ui/*.glade')),
+ ('share/sugar/extensions/deviceicon', glob.glob('src/extensions/*')),
]
)
-# vim: set et sw=4 sts=4 ts=4:
+# vim: set et sw=4 sts=4 ts=4:
diff --git a/src/extensions/tutoriusremote.py b/src/extensions/tutoriusremote.py
new file mode 100755
index 0000000..d795141
--- /dev/null
+++ b/src/extensions/tutoriusremote.py
@@ -0,0 +1,134 @@
+# Copyright (C) 2009, Tutorius.org
+# Copyright (C) 2009, Simon Poirier <simpoir@gmail.com>
+#
+#
+# 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 modules regroups the UI elements that drives the tutorial and tutorial
+creator from the Sugar frame.
+"""
+
+import gtk
+
+from gettext import gettext as _
+import gconf
+import dbus
+
+import logging
+
+from sugar.graphics.tray import TrayIcon
+from sugar.graphics.palette import Palette
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics.combobox import ComboBox
+
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+from jarabe.model.shell import get_model
+
+from sugar.tutorius.creator import default_creator
+
+from sugar.tutorius.vault import Vault
+
+_ICON_NAME = 'tutortool'
+
+LOGGER = logging.getLogger('remote')
+
+class TutoriusRemote(TrayIcon):
+ def __init__(self):
+ client = gconf.client_get_default()
+ self._color = XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ super(TutoriusRemote, self).__init__(icon_name=_ICON_NAME,
+ xo_color=self._color)
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+
+ self.palette = TPalette(_('Tutorius'))
+ self.palette.set_group_id('frame')
+
+
+class TPalette(Palette):
+ def __init__(self, primary_text):
+ super(TPalette, self).__init__(primary_text)
+
+ self._creator_item = gtk.MenuItem(_('Create a tutorial'))
+ self._creator_item.connect('activate', self._toggle_creator)
+ self._creator_item.show()
+
+ self._tut_list_item = gtk.MenuItem(_('Show tutorials'))
+ self._tut_list_item.connect('activate', self._list_tutorials)
+ self._tut_list_item.show()
+
+ self.menu.append(self._creator_item)
+ self.menu.append(self._tut_list_item)
+
+ self.set_content(None)
+
+ def _toggle_creator(self, widget):
+ creator = default_creator()
+
+ if creator.is_authoring == False:
+ # Replace the start creator label by stop creator
+ self._creator_item.props.label = _("Stop authoring")
+ creator.start_authoring(tutorial=None)
+
+ else:
+ # Attempt to close the creator - this will popup a confirmation
+ # dialog if the user has unsaved changes
+ creator.cleanup_cb()
+
+ # If the creator was not actually closed - (in case cancel
+ # is implemented one day)
+ if creator.is_authoring == True:
+ return
+ # Switch back to start creator entry
+ self._creator_item.props.label = _("Create a tutorial")
+
+ def _list_tutorials(self, widget):
+ dlg = gtk.Dialog('Run a tutorial',
+ None,
+ gtk.DIALOG_MODAL,
+ (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
+ gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
+ dlg.vbox.pack_start(gtk.Label(_('Which tutorial do you want to run?\n')))
+
+ activity = get_model().get_active_activity()
+
+ act_name = activity.get_activity_name()
+ tutorial_dict = Vault.list_available_tutorials(act_name)
+
+ # Build the combo box
+ combo = ComboBox()
+ for (tuto_name, tuto_guid) in tutorial_dict.items():
+ combo.append_item(tuto_name, tuto_guid)
+ dlg.vbox.pack_end(combo)
+ dlg.show_all()
+
+ result = dlg.run()
+ dlg.destroy()
+
+ if result == gtk.RESPONSE_ACCEPT:
+ row = combo.get_active_item()
+ if row:
+ guid = row[0]
+ name = row[1]
+ LOGGER.debug("TPalette :: Got message to launch tutorial %s with guid %s"%(str(name), str(guid)))
+
+ from sugar.tutorius.service import ServiceProxy
+ service = ServiceProxy()
+
+ service.launch(guid)
+
+def setup(tray):
+ tray.add_device(TutoriusRemote())
diff --git a/tests/probetests.py b/tests/probetests.py
index 92d34a6..357d223 100644
--- a/tests/probetests.py
+++ b/tests/probetests.py
@@ -96,11 +96,13 @@ class MockProbeProxy(object):
def isAlive(self):
return self.MockAlive
- def install(self, action, action_installed_cb, error_cb):
+ def install(self, action, action_installed_cb, error_cb, is_editing=False, editing_cb=None):
self.MockAction = action
self.MockAddressCallback_install = action_installed_cb
self.MockInstallErrorCallback = error_cb
self.MockActionUpdate = None
+ self.MockIsEditing = is_editing
+ self.MockEditCb = editing_cb
return None
def update(self, action_address, newaction, block=False):
@@ -108,9 +110,10 @@ class MockProbeProxy(object):
self.MockActionUpdate = newaction
return None
- def uninstall(self, action_address):
+ def uninstall(self, action_address, is_editing=False):
self.MockAction = None
self.MockActionUpdate = None
+ self.MockIsEditing = None
return None
def subscribe(self, event, notif_cb, subscribe_cb, error_cb):
@@ -224,35 +227,35 @@ class ProbeTest(unittest.TestCase):
assert message_box is None, "Message box should still be empty"
#install 1
- address = self.probe.install(pickle.dumps(action))
+ address = self.probe.install(pickle.dumps(action), False)
assert type(address) == str, "install should return a string"
assert message_box == (5, "woot"), "message box should have (i, s)"
#install 2
action.i, action.s = (10, "ahhah!")
- address2 = self.probe.install(pickle.dumps(action))
+ address2 = self.probe.install(pickle.dumps(action), False)
assert message_box == (10, "ahhah!"), "message box should have changed"
assert address != address2, "action addresses should be different"
#uninstall 2
- self.probe.uninstall(address2)
+ self.probe.uninstall(address2, False)
assert message_box is None, "undo should clear the message box"
#update action 1 with action 2 props
- self.probe.update(address, pickle.dumps(action._props))
+ self.probe.update(address, pickle.dumps(action._props), False)
assert message_box == (10, "ahhah!"), "message box should have changed(i, s)"
#ErrorCase: Update with bad address
#try to update 2, should fail
- self.assertRaises(KeyError, self.probe.update, address2, pickle.dumps(action._props))
+ self.assertRaises(KeyError, self.probe.update, address2, pickle.dumps(action._props), False)
- self.probe.uninstall(address)
+ self.probe.uninstall(address, False)
assert message_box is None, "undo should clear the message box"
message_box = "Test"
#ErrorCase: Uninstall bad address (currently silent fail)
#Uninstall twice should do nothing
- self.probe.uninstall(address)
+ self.probe.uninstall(address, False)
assert message_box == "Test", "undo should not have happened again"
def test_events(self):
@@ -459,10 +462,10 @@ class ProbeProxyTest(unittest.TestCase):
#ErrorCase: Uninstall on not installed action (silent fail)
#Test the uninstall
- self.probeProxy.uninstall(action2_address)
+ self.probeProxy.uninstall(action2_address, False)
assert not "uninstall" in self.mockObj.MockCall, "Uninstall should not be called if action is not installed"
- self.probeProxy.uninstall(address)
+ self.probeProxy.uninstall(address, False)
assert self.mockObj.MockCall["uninstall"]["args"][0] == address, "1 argument, the action address"
def test_events(self):
diff --git a/tests/propertiestests.py b/tests/propertiestests.py
index cb8e884..49d2312 100644
--- a/tests/propertiestests.py
+++ b/tests/propertiestests.py
@@ -29,28 +29,28 @@ def try_wrong_values(obj):
try:
obj.prop = 3
assert False, "Able to insert int value in property of type %s"%typ
- except:
+ except ValueError:
pass
if typ != "float":
try:
obj.prop = 1.1
assert False, "Able to insert float value in property of type %s"%typ
- except:
+ except ValueError:
pass
if typ != "string":
try:
obj.prop = "Fake string"
assert False, "Able to insert string value in property of type %s"%typ
- except:
+ except ValueError:
pass
if typ != "array":
try:
obj.prop = [1, 2000, 3, 400]
assert False, "Able to insert array value in property of type %s"%typ
- except:
+ except ValueError:
pass
if typ != "color":
@@ -58,7 +58,7 @@ def try_wrong_values(obj):
obj.prop = [1,2,3]
if typ != "array":
assert False, "Able to insert color value in property of type %s"%typ
- except:
+ except ValueError:
pass
if typ != "boolean":
@@ -66,7 +66,7 @@ def try_wrong_values(obj):
obj.prop = True
if typ != "boolean":
assert False, "Able to set boolean value in property of type %s"%typ
- except:
+ except ValueError:
pass
class BasePropertyTest(unittest.TestCase):
diff --git a/tutorius/TProbe.py b/tutorius/TProbe.py
index b384e6c..eea0465 100644
--- a/tutorius/TProbe.py
+++ b/tutorius/TProbe.py
@@ -20,14 +20,17 @@ import os
import gobject
-import dbus
import dbus.service
import cPickle as pickle
+from functools import partial
+
+from jarabe.model.shell import get_model
+from sugar.bundle.activitybundle import ActivityBundle
from . import addon
+from . import properties
from .services import ObjectStore
-from .properties import TPropContainer
from .dbustools import save_args, ignore, logError
import copy
@@ -129,11 +132,12 @@ class TProbe(dbus.service.Object):
# ------------------ Action handling --------------------------------------
@dbus.service.method("org.tutorius.ProbeInterface",
- in_signature='s', out_signature='s')
- def install(self, pickled_action):
+ in_signature='sb', out_signature='s')
+ def install(self, pickled_action, is_editing):
"""
Install an action on the Activity
@param pickled_action string pickled action
+ @param is_editing whether this action comes from the editor
@return string address of installed action
"""
loaded_action = pickle.loads(str(pickled_action))
@@ -146,17 +150,22 @@ class TProbe(dbus.service.Object):
if action._props:
action._props.update(loaded_action._props)
- action.do(activity=self._activity)
-
+ if not is_editing:
+ action.do(activity=self._activity)
+ else:
+ action.enter_editmode()
+ action.set_notification_cb(partial(self.update_action, address))
+
return address
@dbus.service.method("org.tutorius.ProbeInterface",
- in_signature='ss', out_signature='')
- def update(self, address, action_props):
+ in_signature='ssb', out_signature='')
+ def update(self, address, action_props, is_editing):
"""
Update an already registered action
@param address string address returned by install()
@param action_props pickled action properties
+ @param is_editing whether this action comes from the editor
@return None
"""
action = self._installedActions[address]
@@ -164,26 +173,47 @@ class TProbe(dbus.service.Object):
if action._props:
props = pickle.loads(str(action_props))
action._props.update(props)
- action.undo()
- action.do()
+ if not is_editing:
+ action.undo()
+ action.do()
+ else:
+ action.exit_editmode()
+ action.enter_editmode()
@dbus.service.method("org.tutorius.ProbeInterface",
- in_signature='s', out_signature='')
- def uninstall(self, address):
+ in_signature='sb', out_signature='')
+ def uninstall(self, address, is_editing):
"""
Uninstall an action
@param address string address returned by install()
+ @param is_editing whether this action comes from the editor
@return None
"""
if self._installedActions.has_key(address):
action = self._installedActions[address]
- action.undo()
+ if not is_editing:
+ action.undo()
+ else:
+ action.exit_editmode()
self._installedActions.pop(address)
# ------------------ Event handling ---------------------------------------
@dbus.service.method("org.tutorius.ProbeInterface",
in_signature='s', out_signature='s')
+ def create_event(self, addon_name):
+ # avoid recursive imports
+ event = addon.create(addon_name)
+ addonname = type(event).__name__
+ meta = addon.get_addon_meta(addonname)
+ for propname in meta['mandatory_props']:
+ prop = getattr(type(event), propname)
+ prop.widget_class.run_dialog(self._activity, event, propname)
+
+ return pickle.dumps(event)
+
+ @dbus.service.method("org.tutorius.ProbeInterface",
+ in_signature='s', out_signature='s')
def subscribe(self, pickled_event):
"""
Subscribe to an Event
@@ -216,7 +246,6 @@ class TProbe(dbus.service.Object):
@param address string adress returned by subscribe()
@return None
"""
-
if self._subscribedEvents.has_key(address):
eventfilter = self._subscribedEvents[address]
eventfilter.remove_handlers()
@@ -237,6 +266,21 @@ class TProbe(dbus.service.Object):
else:
raise RuntimeWarning("Attempted to raise an unregistered event")
+ @dbus.service.signal("org.tutorius.ProbeInterface")
+ def addonUpdated(self, addon_address, pickled_diff_dict):
+ # Don't do any added processing, the signal will be sent
+ # when the method exits
+ pass
+
+ def update_action(self, addon_address, diff_dict):
+ LOGGER.debug("TProbe :: Trying to update action %s with new property dict %s"%(addon_address, str(diff_dict)))
+ # Check that this action is installed
+ if addon_address in self._installedActions.keys():
+ LOGGER.debug("TProbe :: Updating action %s"%(addon_address))
+ self.addonUpdated(addon_address, pickle.dumps(diff_dict))
+ else:
+ raise RuntimeWarning("Attempted to updated an action that wasn't installed")
+
# Return a unique name for this action
def _generate_action_reference(self, action):
# TODO elavoie 2009-07-25 Should return a universal address
@@ -284,6 +328,7 @@ class ProbeProxy:
self._probe = dbus.Interface(self._object, "org.tutorius.ProbeInterface")
self._actions = {}
+ self._edition_callbacks = {}
# We keep those two data structures to be able to have multiple callbacks
# for the same event and be able to remove them independently
# _subscribedEvents holds a list of callback addresses's for each event
@@ -292,6 +337,17 @@ class ProbeProxy:
self._registeredCallbacks = {}
self._object.connect_to_signal("eventOccured", self._handle_signal, dbus_interface="org.tutorius.ProbeInterface")
+ self._object.connect_to_signal("addonUpdated", self._handle_update_signal, dbus_interface="org.tutorius.ProbeInterface")
+
+ def _handle_update_signal(self, addon_address, pickled_diff_dict):
+ address = str(addon_address)
+ diff_dict = pickle.loads(str(pickled_diff_dict))
+ LOGGER.debug("ProbeProxy :: Received update property for action %s"%(address))
+ # Launch the callback to warn the upper layers of a modification of the addon
+ # from a widget inside the activity
+ if self._edition_callbacks.has_key(address):
+ LOGGER.debug("ProbeProxy :: Executing update callback...")
+ self._edition_callbacks[address](address, diff_dict)
def _handle_signal(self, pickled_event):
event = pickle.loads(str(pickled_event))
@@ -312,33 +368,47 @@ class ProbeProxy:
except:
return False
- def __update_action(self, action, callback, address):
+ def __update_action(self, action, callback, editing_cb, address):
LOGGER.debug("ProbeProxy :: Updating action %s with address %s", str(action), str(address))
+ address = str(address)
+ # Store the action
self._actions[address] = action
+ # Store the edition callback
+ if editing_cb:
+ self._edition_callbacks[address] = editing_cb
+ # Propagate the action installed callback upwards in the stack
callback(address)
def __clear_action(self, address):
+ # Remove the action installed at this address
self._actions.pop(address, None)
+ # Remove the edition callback
+ self._edition_callbacks.pop(address, None)
- def install(self, action, action_installed_cb, error_cb):
+ def install(self, action, action_installed_cb, error_cb, is_editing=False, editing_cb=None):
"""
Install an action on the TProbe's activity
@param action Action to install
@param action_installed_cb The callback function to call once the action is installed
@param error_cb The callback function to call when an error happens
+ @param is_editing whether this action comes from the editor
+ @param editing_cb The function to execute when the action is updated
+ (this is only done in edition mode)
@return None
"""
- self._probe.install(pickle.dumps(action),
- reply_handler=save_args(self.__update_action, action, action_installed_cb),
- error_handler=save_args(error_cb, action))
+ self._probe.install(pickle.dumps(action),
+ is_editing,
+ reply_handler=save_args(self.__update_action, action, action_installed_cb, editing_cb),
+ error_handler=save_args(error_cb, action))
- def update(self, action_address, newaction):
+ def update(self, action_address, newaction, is_editing=False):
"""
Update an already installed action's properties and run it again
@param action_address The address of the action to update. This is
provided by the install callback method.
@param newaction Action to update it with
@param block Force a synchroneous dbus call if True
+ @param is_editing whether this action comes from the editor
@return None
"""
#TODO review how to make this work well
@@ -346,19 +416,20 @@ class ProbeProxy:
raise RuntimeWarning("Action not installed")
#TODO Check error handling
return self._probe.update(action_address, pickle.dumps(newaction._props),
+ is_editing,
reply_handler=ignore,
error_handler=logError)
- def uninstall(self, action_address):
+ def uninstall(self, action_address, is_editing):
"""
Uninstall an installed action
@param action_address The address of the action to uninstall. This address was given
on action installation
- @param block Force a synchroneous dbus call if True
+ @param is_editing whether this action comes from the editor
"""
if action_address in self._actions:
self._actions.pop(action_address, None)
- self._probe.uninstall(action_address, reply_handler=ignore, error_handler=logError)
+ self._probe.uninstall(action_address, is_editing, reply_handler=ignore, error_handler=logError)
def __update_event(self, event, callback, event_subscribed_cb, address):
LOGGER.debug("ProbeProxy :: Registered event %s with address %s", str(hash(event)), str(address))
@@ -409,7 +480,18 @@ class ProbeProxy:
else:
LOGGER.debug("ProbeProxy :: unsubsribe address %s inconsistency : not registered", address)
+ def create_event(self, addon_name):
+ """
+ Create an event on the app side and request the user to fill the
+ properties before returning it.
+
+ @param addon_name: the add-on name of the event
+ @returns: an eventfilter instance
+ """
+ return pickle.loads(str(self._probe.create_event(addon_name)))
+
def subscribe(self, event, notification_cb, event_subscribed_cb, error_cb):
+
"""
Register an event listener
@param event Event to listen for
@@ -429,11 +511,10 @@ class ProbeProxy:
reply_handler=save_args(self.__update_event, event, notification_cb, event_subscribed_cb),
error_handler=save_args(error_cb, event))
- def unsubscribe(self, address, block=True):
+ def unsubscribe(self, address):
"""
Unregister an event listener
@param address identifier given by subscribe()
- @param block Force a synchroneous dbus call if True
@return None
"""
LOGGER.debug("ProbeProxy :: Unregister adress %s issued", str(address))
@@ -445,18 +526,19 @@ class ProbeProxy:
else:
LOGGER.debug("ProbeProxy :: unsubscribe address %s failed : not registered", address)
- def detach(self, block=False):
+ def detach(self):
"""
Detach the ProbeProxy from it's TProbe. All installed actions and
subscribed events should be removed.
"""
for action_addr in self._actions.keys():
- self.uninstall(action_addr)
+ # TODO : Make sure there is a way for each action to be properly
+ # uninstalled according to its right edition mode
+ self.uninstall(action_addr, True)
for address in self._subscribedEvents.keys():
self.unsubscribe(address)
-
class ProbeManager(object):
"""
The ProbeManager provides multiplexing across multiple activity ProbeProxies
@@ -482,49 +564,78 @@ class ProbeManager(object):
def setCurrentActivity(self, activity_id):
if not activity_id in self._probes:
- raise RuntimeError("Activity not attached")
+ raise RuntimeError("Activity not attached, id : %s"%activity_id)
self._current_activity = activity_id
def getCurrentActivity(self):
+ # TODO : Insert the correct call to remember the current activity,
+ # taking the views and frame into account
+ current_act = get_model().get_active_activity()
+ current_act_bundle = ActivityBundle(current_act.get_bundle_path())
+ current_act_id = current_act_bundle.get_bundle_id()
+ self._current_activity = current_act_id
return self._current_activity
currentActivity = property(fget=getCurrentActivity, fset=setCurrentActivity)
- def install(self, action, action_installed_cb, error_cb):
+ def install(self, action, action_installed_cb, error_cb, is_editing=False, editing_cb=None):
"""
Install an action on the current activity
@param action Action to install
@param action_installed_cb The callback to call once the action is installed
@param error_cb The callback that will be called if there is an error during installation
@param block Force a synchroneous dbus call if True
+ @param is_editing whether this action comes from the editor
+ @param editing_cb The function to execute when propagating changes on
+ this action (only used when is_editing is true)
@return None
"""
if self.currentActivity:
- return self._first_proxy(self.currentActivity).install(action, action_installed_cb, error_cb)
+ return self._first_proxy(self.currentActivity).install(
+ action=action,
+ is_editing=is_editing,
+ action_installed_cb=action_installed_cb,
+ error_cb=error_cb,
+ editing_cb=editing_cb)
else:
raise RuntimeWarning("No activity attached")
- def update(self, action_address, newaction):
+ def update(self, action_address, newaction, is_editing=False):
"""
Update an already installed action's properties and run it again
@param action_address Action to update
@param newaction Action to update it with
@param block Force a synchroneous dbus call if True
+ @param is_editing whether this action comes from the editor
@return None
"""
if self.currentActivity:
- return self._first_proxy(self.currentActivity).update(action_address, newaction)
+ return self._first_proxy(self.currentActivity).update(action_address, newaction, is_editing)
else:
raise RuntimeWarning("No activity attached")
- def uninstall(self, action_address):
+ def uninstall(self, action_address, is_editing=False):
"""
Uninstall an installed action
- @param action Action to uninstall
+ @param action_address Action to uninstall
@param block Force a synchroneous dbus call if True
+ @param is_editing whether this action comes from the editor
"""
if self.currentActivity:
- return self._first_proxy(self.currentActivity).uninstall(action_address)
+ return self._first_proxy(self.currentActivity).uninstall(action_address, is_editing)
+ else:
+ raise RuntimeWarning("No activity attached")
+
+ def create_event(self, addon_name):
+ """
+ Create an event on the app side and request the user to fill the
+ properties before returning it.
+
+ @param addon_name: the add-on name of the event
+ @returns: an eventfilter instance
+ """
+ if self.currentActivity:
+ return self._first_proxy(self.currentActivity).create_event(addon_name)
else:
raise RuntimeWarning("No activity attached")
@@ -601,8 +712,6 @@ class ProbeManager(object):
return self._probes[process_name]
else:
return []
-
-
def _first_proxy(self, process_name):
"""
diff --git a/tutorius/actions.py b/tutorius/actions.py
index 75c9c9b..8cc5f08 100644
--- a/tutorius/actions.py
+++ b/tutorius/actions.py
@@ -26,14 +26,21 @@ from . import addon
from .services import ObjectStore
from .properties import *
+import pickle
+
+import logging
+
+LOGGER = logging.getLogger("actions")
+
class DragWrapper(object):
"""Wrapper to allow gtk widgets to be dragged around"""
- def __init__(self, widget, position, draggable=False):
+ def __init__(self, widget, position, update_action_cb, draggable=False):
"""
Creates a wrapper to allow gtk widgets to be mouse dragged, if the
parent container supports the move() method, like a gtk.Layout.
@param widget the widget to enhance with drag capability
@param position the widget's position. Will translate the widget if needed
+ @param update_action_cb The callback to trigger
@param draggable wether to enable the drag functionality now
"""
self._widget = widget
@@ -45,6 +52,7 @@ class DragWrapper(object):
self.position = position # position of the widget
self.moved = False
+ self.update_action_cb = update_action_cb
self.draggable = draggable
def _pressed_cb(self, widget, evt):
@@ -79,10 +87,13 @@ class DragWrapper(object):
self._eventbox.grab_remove()
self._dragging = False
+ LOGGER.debug("DragWrapper :: Sending update notification...")
+ self.update_action_cb('position', self.position)
+
def _drag_end(self, *args):
"""Callback for end of drag (stolen focus)."""
self._dragging = False
-
+
def set_draggable(self, value):
"""Setter for the draggable property"""
if bool(value) ^ bool(self._drag_on):
@@ -139,6 +150,9 @@ class Action(TPropContainer):
TPropContainer.__init__(self)
self.position = (0,0)
self._drag = None
+ # The callback that will be triggered when the action is requested
+ # to notify all its changes
+ self._properties_updated_cb = None
def do(self, **kwargs):
"""
@@ -152,6 +166,32 @@ class Action(TPropContainer):
"""
pass #Should raise NotImplemented?
+
+ def set_notification_cb(self, notif_cb):
+ LOGGER.debug("Action :: Setting notification callback for creator...")
+ self._properties_updated_cb = notif_cb
+
+ def update_property(self, name, value):
+ """
+ Callback used in the wrapper to send a new value to an action.
+ """
+ LOGGER.debug("Action :: update_property on %s with value '%s'"%(name, str(value)))
+ # Set the property itself - this will modify the diff dict and we will
+ # be able to notify the owner with the new value
+ self.__setattr__(name, value)
+
+ # Send the notification to the creator
+ self.notify()
+
+ def notify(self):
+ LOGGER.debug("Action :: Notifying creator with new values in dict : %s"%(str(self._diff_dict)))
+ # If a notification callback was registered
+ if self._property_update_cb:
+ # Propagate it
+ self._property_update_cb(self._diff_dict)
+ # Empty the diff dict as we just synchronized with the creator
+ self._diff_dict.clear()
+
def enter_editmode(self, **kwargs):
"""
Enters edit mode. The action should display itself in some way,
@@ -171,7 +211,7 @@ class Action(TPropContainer):
ObjectStore().activity._overlayer.put(self.__edit_img, x, y)
self.__edit_img.show_all()
- self._drag = DragWrapper(self.__edit_img, self.position, True)
+ self._drag = DragWrapper(self.__edit_img, self.position, update_action_cb=self.update_property, draggable=True)
def exit_editmode(self, **kwargs):
x, y = self._drag.position
diff --git a/tutorius/creator.py b/tutorius/creator.py
index 68c5fa6..50017dc 100644
--- a/tutorius/creator.py
+++ b/tutorius/creator.py
@@ -21,83 +21,145 @@ the activity itself.
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-import gtk.gdk
import gtk.glade
import gobject
from gettext import gettext as T
+import pickle
+
import uuid
import os
from sugar.graphics import icon, style
+import jarabe.frame
-from . import overlayer, gtkutils, actions, vault, properties, addon
-from . import filters
+from . import overlayer, gtkutils, vault, addon
from .services import ObjectStore
-from .core import State
from .tutorial import Tutorial
from . import viewer
-from .propwidgets import TextInputDialog, StringPropWidget
+from .propwidgets import TextInputDialog
+from . import TProbe
+
+from functools import partial
+
+from dbus import SessionBus
+from dbus.service import method, Object, BusName
+from .dbustools import ignore
+
+import logging
+
+LOGGER = logging.getLogger("creator")
+
+BUS_PATH = "/org/tutorius/Creator"
+BUS_NAME = "org.tutorius.Creator"
+
+def default_creator():
+ """
+ The Creator class is a singleton. There can never be more than one creator
+ at a time. This method returns a new instance only if none
+ already exists. Else, the existing instance is returned.
+ """
+ return Creator._instance
+
+def get_creator_proxy():
+ """
+ Returns a Creator dbus proxy for inter-process events.
+ """
+ bus = SessionBus()
+ proxy = bus.get_object(BUS_NAME, BUS_PATH)
+ return proxy
-class Creator(object):
+class Creator(Object):
"""
- Class acting as a bridge between the creator, serialization and core
- classes. This contains most of the UI part of the editor.
+ Class acting as a controller for the tutorial edition.
"""
- def __init__(self, activity, tutorial=None):
+
+ _instance = None
+
+ def __init__(self, probe_manager):
+ """
+ Creates the instance of the creator. It is assumed this will be called
+ only once, by the Service.
+
+ @param probe_manager The Probe Manager
"""
- Instanciate a tutorial creator for the activity.
+ bus_name = BusName(BUS_NAME, bus=SessionBus())
+ Object.__init__(self, bus_name, BUS_PATH)
- @param activity to bind the creator to
- @param tutorial an existing tutorial to edit, or None to create one
+ self.tuto = None
+ self.is_authoring = False
+ if Creator._instance:
+ raise RuntimeError("Creator was already instanciated")
+ Creator._instance = self
+ self._probe_mgr = probe_manager
+ self._installed_actions = list()
+
+ def start_authoring(self, tutorial=None):
+ """
+ Start authoring a tutorial.
+
+ @type tutorial: str or None
+ @param tutorial: the unique identifier to an existing tutorial to
+ modify, or None to create a new one.
"""
- self._activity = activity
+ if self.is_authoring:
+ raise Exception("Already authoring")
+
+ self.is_authoring = True
+
if not tutorial:
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)
+ final_event = addon.create(
+ name='MessageButtonNext',
+ message=T('This is the end of this tutorial.')
+ )
+ self._tutorial.add_transition(
+ state_name=self._state,
+ transition=(final_event, self._tutorial.END),
+ )
else:
self._tutorial = tutorial
# TODO load existing tutorial; unused yet
self._action_panel = None
self._current_filter = None
- self._intro_mask = None
- self._intro_handle = None
- allocation = self._activity.get_allocation()
- self._width = allocation.width
- self._height = allocation.height
self._selected_widget = None
self._eventmenu = None
self.tuto = None
self._guid = None
+ self.metadata = None
- self._hlmask = overlayer.Rectangle(None, (1.0, 0.0, 0.0, 0.5))
- self._activity._overlayer.put(self._hlmask, 0, 0)
-
- dlg_width = 300
- dlg_height = 70
- sw = gtk.gdk.screen_width()
- sh = gtk.gdk.screen_height()
+ frame = jarabe.frame.get_view()
- self._propedit = ToolBox(self._activity)
+ self._propedit = ToolBox(None)
self._propedit.tree.signal_autoconnect({
- 'on_quit_clicked': self._cleanup_cb,
+ 'on_quit_clicked': self.cleanup_cb,
'on_save_clicked': self.save,
'on_action_activate': self._add_action_cb,
'on_event_activate': self._add_event_cb,
})
self._propedit.window.move(
- gtk.gdk.screen_width()-self._propedit.window.get_allocation().width,
- 100)
-
+ gtk.gdk.screen_width()-self._propedit.window.get_allocation().width\
+ -style.GRID_CELL_SIZE,
+ style.GRID_CELL_SIZE)
+ self._propedit.window.connect('enter-notify-event',
+ frame._enter_notify_cb)
+ self._propedit.window.connect('leave-notify-event',
+ frame._leave_notify_cb)
self._overview = viewer.Viewer(self._tutorial, self)
- self._overview.win.set_transient_for(self._activity)
+ self._overview.win.set_transient_for(frame._bottom_panel)
+ self._overview.win.connect('enter-notify-event',
+ frame._enter_notify_cb)
+ self._overview.win.connect('leave-notify-event',
+ frame._leave_notify_cb)
- self._overview.win.move(0, gtk.gdk.screen_height()- \
- self._overview.win.get_allocation().height)
+ self._overview.win.move(style.GRID_CELL_SIZE,
+ gtk.gdk.screen_height()-style.GRID_CELL_SIZE \
+ -self._overview.win.get_allocation().height)
self._transitions = dict()
@@ -120,7 +182,8 @@ class Creator(object):
.get(action, None)
if not action_obj:
return False
- action_obj.exit_editmode()
+
+ self._probe_mgr.uninstall(action_obj.address)
self._tutorial.delete_action(action)
self._overview.win.queue_draw()
return True
@@ -133,7 +196,9 @@ class Creator(object):
@returns: True if successful, otherwise False.
"""
- if self._state in (self._tutorial.INIT, self._tutorial.END):
+ if self._state in (self._tutorial.INIT, self._tutorial.END) \
+ or self._tutorial.END in \
+ self._tutorial.get_following_states_dict(self._state):
# last state cannot be removed
return False
@@ -144,103 +209,105 @@ class Creator(object):
return bool(self._tutorial.delete_state(remove_state))
def get_insertion_point(self):
+ """
+ @returns: the current tutorial insertion point.
+ """
return self._state
def set_insertion_point(self, state_name):
- for action in self._tutorial.get_action_dict(self._state).values():
- action.exit_editmode()
+ """
+ Set the tutorial modification point to the specified state.
+ Actions of the state will enter the edit mode.
+ New actions will be inserted to that state and new transitions will
+ shift the current transition to the next state.
+
+ @param state_name: the name of the state to use as insertion point
+ """
+ # first is not modifiable, as the auto transition would make changes
+ # pointless. The end state is also pointless to modify, as the tutorial
+ # gets detached.
+ if state_name == self._tutorial.INIT \
+ or state_name == self._tutorial.END:
+ return
+
+ for action in self._installed_actions:
+ self._probe_mgr.uninstall(action.address,
+ is_editing=True)
+ self._installed_actions = []
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(
- "button-release-event", self._action_refresh_cb, action)
+ return_cb = partial(self._action_installed_cb, action)
+ self._probe_mgr.install(action,
+ action_installed_cb=return_cb,
+ error_cb=self._dbus_exception,
+ is_editing=True,
+ editing_cb=self.update_addon_property)
if state_actions:
+ # I'm really lazy right now and to keep things simple I simply
+ # always select the first action when
+ # we change state. we should really select the clicked block
+ # in the overview instead. FIXME
self._propedit.action = state_actions[0]
else:
self._propedit.action = None
self._overview.win.queue_draw()
-
- def _evfilt_cb(self, menuitem, event):
- """
- This will get called once the user has selected a menu item from the
- event filter popup menu. This should add the correct event filter
- to the FSM and increment states.
- """
- # undo actions so they don't persist through step editing
- for action in self._state.get_action_list():
- action.exit_editmode()
- self._hlmask.covered = None
- self._propedit.action = None
- self._activity.queue_draw()
-
- def _intro_cb(self, widget, evt):
- """
- Callback for capture of widget events, when in introspect mode.
- """
- if evt.type == gtk.gdk.BUTTON_PRESS:
- # widget has focus, let's hilight it
- win = gtk.gdk.display_get_default().get_window_at_pointer()
- click_wdg = win[0].get_user_data()
- if not click_wdg.is_ancestor(self._activity._overlayer):
- # as popups are not (yet) supported, it would break
- # badly if we were to play with a widget not in the
- # hierarchy.
- return
- for hole in self._intro_mask.pass_thru:
- self._intro_mask.mask(hole)
- self._intro_mask.unmask(click_wdg)
- self._selected_widget = gtkutils.raddr_lookup(click_wdg)
-
- if self._eventmenu:
- self._eventmenu.destroy()
- self._eventmenu = gtk.Menu()
- menuitem = gtk.MenuItem(label=type(click_wdg).__name__)
- menuitem.set_sensitive(False)
- self._eventmenu.append(menuitem)
- self._eventmenu.append(gtk.MenuItem())
-
- for item in gobject.signal_list_names(click_wdg):
- menuitem = gtk.MenuItem(label=item)
- menuitem.connect("activate", self._evfilt_cb, item)
- self._eventmenu.append(menuitem)
- self._eventmenu.show_all()
- self._eventmenu.popup(None, None, None, evt.button, evt.time)
- self._activity.queue_draw()
-
def _add_action_cb(self, widget, path):
"""Callback for the action creation toolbar tool"""
action_type = self._propedit.actions_list[path][ToolBox.ICON_NAME]
+ LOGGER.debug("Creator :: Adding an action = %s"%(action_type))
action = addon.create(action_type)
- action.enter_editmode()
+ return_cb = partial(self._action_installed_cb, action)
+ self._probe_mgr.install(action,
+ action_installed_cb=return_cb,
+ error_cb=self._dbus_exception,
+ is_editing=True,
+ editing_cb=self.update_addon_property)
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)
+ self._propedit.action = action
self._overview.win.queue_draw()
def _add_event_cb(self, widget, path):
- """Callback for the event creation toolbar tool"""
+ """
+ Callback for the event creation toolbar tool.
+
+ The behaviour of event addition is to push the transition of the current
+ state to the next (newly created state).
+
+ |
+ v
+ .--------. .-------. .--------.
+ | action |---->| event |---->| action |
+ '--------' '-------' '--------'
+ |
+ .--------. .-----------. v .-------. .--------.
+ | action |--->| new event |-->| event |---->| action |
+ '--------' '-----------' '-------' '--------'
+ The cursor always selects a state (between the action and transition)
+ The result is what the user expects: inserting before an action will
+ effectively shift the next transition to the next state.
+
+ """
event_type = self._propedit.events_list[path][ToolBox.ICON_NAME]
- event = addon.create(event_type)
- addonname = type(event).__name__
- meta = addon.get_addon_meta(addonname)
- for propname in meta['mandatory_props']:
- prop = getattr(type(event), propname)
- prop.widget_class.run_dialog(self._activity, event, propname)
+ event = self._probe_mgr.create_event(event_type)
event_filters = self._tutorial.get_transition_dict(self._state)
# if not at the end of tutorial
if event_filters:
- 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)
+ old_name = event_filters.keys()[0]
+ old_transition = self._tutorial.delete_transition(old_name)
+ new_state = self._tutorial.add_state(
+ transition_list=(old_transition,)
+ )
+ self._tutorial.add_transition(state_name=self._state,
+ transition=(event, new_state),
+ )
else:
# append empty state only if edit inserting at end of linearized
@@ -252,22 +319,32 @@ class Creator(object):
self.set_insertion_point(new_state)
+ def properties_changed(self, action):
+ LOGGER.debug("Creator :: properties_changed for action at address %s to %s"%(action.address))
+ address = action.address
+ self._probe_mgr.update(address,
+ action,
+ is_editing=True)
+
def _action_refresh_cb(self, widget, evt, action):
"""
Callback for refreshing properties values and notifying the
property dialog of the new values.
"""
- action.exit_editmode()
- action.enter_editmode()
- self._activity.queue_draw()
- # TODO: replace following with event catching
- action._drag._eventbox.connect_after(
- "button-release-event", self._action_refresh_cb, action)
+ # TODO : replace with update
+ self._probe_mgr.uninstall(action.address,
+ is_editing=True)
+ return_cb = partial(self._action_installed_cb, action)
+ self._probe_mgr.install(action,
+ action_installed_cb=return_cb,
+ error_cb=self._dbus_exception,
+ is_editing=True,
+ editing_cb=self.update_addon_property)
self._propedit.action = action
self._overview.win.queue_draw()
- def _cleanup_cb(self, *args, **kwargs):
+ def cleanup_cb(self, *args, **kwargs):
"""
Quit editing and cleanup interface artifacts.
@@ -275,61 +352,117 @@ class Creator(object):
"""
# undo actions so they don't persist through step editing
for action in self._tutorial.get_action_dict(self._state).values():
- action.exit_editmode()
+ self._probe_mgr.uninstall(action.address,
+ is_editing=True)
- if kwargs.get('force', False):
+ # TODO : Support quit cancellation - right now,every time we execute this,
+ # we will forcibly end edition afterwards. It would be nice to keep creating
+ if not kwargs.get('force', False):
+ # TODO : Move the dialog in the middle of the screen
dialog = gtk.MessageDialog(
- parent=self._activity,
+ parent=self._overview.win,
flags=gtk.DIALOG_MODAL,
type=gtk.MESSAGE_QUESTION,
buttons=gtk.BUTTONS_YES_NO,
- message_format=T('Do you want to save before stopping edition?'))
+ 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
- self._activity._overlayer.remove(self._hlmask)
- self._hlmask.destroy()
- self._hlmask = None
self._propedit.destroy()
self._overview.destroy()
- self._activity.queue_draw()
- del self._activity._creator
+ self.is_authoring = False
def save(self, widget=None):
+ """
+ Save the currently edited tutorial to bundle, prompting for
+ a name as needed.
+ """
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()
+ tutorial_name = ""
+ while not tutorial_name:
+ tutorial_name = dlg.pop()
dlg.destroy()
self._metadata = {
vault.INI_GUID_PROPERTY: self._guid,
- vault.INI_NAME_PROPERTY: tutorialName,
+ vault.INI_NAME_PROPERTY: tutorial_name,
vault.INI_VERSION_PROPERTY: '1',
- 'activities':{os.environ['SUGAR_BUNDLE_NAME']:
- os.environ['SUGAR_BUNDLE_VERSION']
- },
}
+ # FIXME : The environment does not dispose of the appropriate
+ # variables to inform the creator at this point. We will
+ # need to iterate inside all the actions and remember
+ # their sources.
+
+ # FIXME : I insist. This is a hack.
+ related_activities_dict = {}
+ related_activities_dict['calculate'] = '27'
+
+ self._metadata['activities'] = dict(related_activities_dict)
vault.Vault.saveTutorial(self._tutorial, self._metadata)
+ def launch(self, *args):
+ assert False, "REMOVE THIS CALL!!!"
+ launch = staticmethod(launch)
- def launch(*args, **kwargs):
+ def _action_installed_cb(self, action, address):
"""
- Launch and attach a creator to the currently running activity.
+ This is a callback intented to be use to receive actions addresses
+ after they are installed.
+ @param address: the address of the newly installed action
"""
- activity = ObjectStore().activity
- if not hasattr(activity, "_creator"):
- activity._creator = Creator(activity)
- launch = staticmethod(launch)
+ action.address = address
+ self._installed_actions.append(action)
+
+ def _dbus_exception(self, event, exception):
+ """
+ This is a callback intented to be use to receive exceptions on remote
+ DBUS calls.
+ @param exception: the exception thrown by the remote process
+ """
+ LOGGER.debug("Creator :: Got exception -> %s"%(str(exception)))
+
+ @method(BUS_NAME,
+ in_signature='',
+ out_signature='b')
+ def get_authoring_state(self):
+ """
+ @returns True if the creator is being executed right now, False otherwise.
+ """
+ return self.is_authoring
+
+ def update_addon_property(self, addon_address, diff_dict):
+ """
+ Updates the properties on an addon.
+ @param addon_address The address of the addon that has the property
+ @param diff_dict The updates to apply to the property dict.
+ This is treated as a partial update to the addon's
+ dictionary and contains at least one property value pair
+ @returns True if the property was updated, False otherwise
+ """
+ # Look up the registered addresses inside the installed actions
+ for action in self._installed_actions:
+ # If this is the correct action
+ if action.address == addon_address:
+ # Update its property with the new value
+ action._props.update(diff_dict)
+ # Update the property edition dialog with it
+ self._propedit.action = action
+ return True
+
class ToolBox(object):
+ """
+ Palette window for edition tools, including the actions, states and
+ the editable property list of selected actions.
+ """
ICON_LABEL = 0
ICON_IMAGE = 1
ICON_NAME = 2
@@ -337,21 +470,26 @@ class ToolBox(object):
def __init__(self, parent):
super(ToolBox, self).__init__()
self.__parent = parent
- sugar_prefix = os.getenv("SUGAR_PREFIX",default="/usr")
+ sugar_prefix = os.getenv("SUGAR_PREFIX", default="/usr")
glade_file = os.path.join(sugar_prefix, 'share', 'tutorius',
'ui', 'creator.glade')
self.tree = gtk.glade.XML(glade_file)
self.window = self.tree.get_widget('mainwindow')
+ self.window.modify_bg(gtk.STATE_NORMAL,
+ style.COLOR_TOOLBAR_GREY.get_gdk_color())
self._propbox = self.tree.get_widget('propbox')
self._propedits = []
self.window.set_transient_for(parent)
+ self.window.set_keep_above(True)
self._action = None
self.actions_list = gtk.ListStore(str, gtk.gdk.Pixbuf, str, str)
- self.actions_list.set_sort_column_id(self.ICON_LABEL, gtk.SORT_ASCENDING)
+ self.actions_list.set_sort_column_id(self.ICON_LABEL,
+ gtk.SORT_ASCENDING)
self.events_list = gtk.ListStore(str, gtk.gdk.Pixbuf, str, str)
- self.events_list.set_sort_column_id(self.ICON_LABEL, gtk.SORT_ASCENDING)
+ self.events_list.set_sort_column_id(self.ICON_LABEL,
+ gtk.SORT_ASCENDING)
for toolname in addon.list_addons():
meta = addon.get_addon_meta(toolname)
@@ -361,9 +499,13 @@ class ToolBox(object):
label = format_multiline(meta['display_name'])
if meta['type'] == addon.TYPE_ACTION:
- self.actions_list.append((label, img, toolname, meta['display_name']))
+ self.actions_list.append(
+ (label, img, toolname, meta['display_name'])
+ )
else:
- self.events_list.append((label, img, toolname, meta['display_name']))
+ self.events_list.append(
+ (label, img, toolname, meta['display_name'])
+ )
iconview_action = self.tree.get_widget('iconview1')
iconview_action.set_model(self.actions_list)
@@ -413,7 +555,8 @@ class ToolBox(object):
#Value field
prop = getattr(type(action), propname)
- propedit = prop.widget_class(self.__parent, action, propname, self._refresh_action_cb)
+ propedit = prop.widget_class(self.__parent, action, propname,
+ self._refresh_action_cb)
self._propedits.append(propedit)
row.pack_end(propedit.widget)
@@ -430,8 +573,7 @@ class ToolBox(object):
def _refresh_action_cb(self):
if self._action is not None:
- self.__parent._creator._action_refresh_cb(None, None, self._action)
-
+ default_creator().properties_changed(self._action)
# The purpose of this function is to reformat text, as current IconView
# implentation does not insert carriage returns on long lines.
diff --git a/tutorius/overlayer.py b/tutorius/overlayer.py
index 8c653ac..0b78c53 100644
--- a/tutorius/overlayer.py
+++ b/tutorius/overlayer.py
@@ -150,8 +150,8 @@ class Overlayer(gtk.Layout):
# Since widget is laid out in a Layout box, the Layout will honor the
# requested size. Using size_allocate could make a nasty nested loop in
# some cases.
- self._overlayed.set_size_request(allocation.width, allocation.height)
-
+ if self._overlayed:
+ self._overlayed.set_size_request(allocation.width, allocation.height)
class FrameOverlayer(gtk.Window):
def __init__(self):
diff --git a/tutorius/properties.py b/tutorius/properties.py
index a462782..f273569 100644
--- a/tutorius/properties.py
+++ b/tutorius/properties.py
@@ -19,6 +19,7 @@ TutoriusProperties have the same behaviour as python properties (assuming you
also use the TPropContainer), with the added benefit of having builtin dialog
prompts and constraint validation.
"""
+import uuid
from copy import copy, deepcopy
from .constraints import Constraint, \
@@ -35,6 +36,9 @@ from .propwidgets import PropWidget, \
FloatPropWidget, \
IntArrayPropWidget
+import logging
+LOGGER = logging.getLogger("properties")
+
class TPropContainer(object):
"""
A class containing properties. This does the attribute wrapping between
@@ -60,6 +64,14 @@ class TPropContainer(object):
self._props[attr_name] = propinstance.validate(
copy(propinstance.default))
+ self.__id = hash(uuid.uuid4())
+ # The differences dictionary. This is a structure that holds all the
+ # modifications that were made to the properties since the action
+ # was last installed or the last moment the notification was executed.
+ # Every property change will be logged inside it and it will be sent
+ # to the creator to update its action edition dialog.
+ self._diff_dict = {}
+
def __getattribute__(self, name):
"""
Process the 'fake' read of properties in the appropriate instance
@@ -93,8 +105,11 @@ class TPropContainer(object):
try:
# We attempt to get the property object with __getattribute__
# to work through inheritance and benefit of the MRO.
- return props.__setitem__(name,
+ real_value = props.__setitem__(name,
object.__getattribute__(self, name).validate(value))
+ LOGGER.debug("Action :: caching %s = %s in diff dict"%(name, str(value)))
+ self._diff_dict[name] = value
+ return real_value
except AttributeError:
return object.__setattr__(self, name, value)
@@ -125,24 +140,8 @@ class TPropContainer(object):
"""
return deepcopy(self._props)
- # Providing the hash methods necessary to use TPropContainers
- # in a dictionary, according to their properties
- def __hash__(self):
- #Return a hash of properties (key, value) sorted by key
- #We need to transform the list of property key, value lists into
- # a tuple of key, value tuples
- return hash(tuple(map(tuple,sorted(self._props.items(), cmp=lambda x, y: cmp(x[0], y[0])))))
-
def __eq__(self, e2):
- return isinstance(e2, type(self)) and self._props == e2._props
-
- # Adding methods for pickling and unpickling an object with
- # properties
- def __getstate__(self):
- return self._props.copy()
-
- def __setstate__(self, dict):
- self._props.update(dict)
+ return (isinstance(e2, type(self)) and self._props == e2._props)
class TutoriusProperty(object):
"""
diff --git a/tutorius/service.py b/tutorius/service.py
index 11a94a5..1564339 100644
--- a/tutorius/service.py
+++ b/tutorius/service.py
@@ -3,6 +3,7 @@ import dbus
from .engine import Engine
from .dbustools import remote_call
from .TProbe import ProbeManager
+from .creator import Creator
import logging
LOGGER = logging.getLogger("sugar.tutorius.service")
@@ -24,6 +25,8 @@ class Service(dbus.service.Object):
self._probeMgr = ProbeManager()
+ Creator(self._probeMgr)
+
def start(self):
""" Start the service itself
"""
diff --git a/tutorius/store.py b/tutorius/store.py
index 565295d..69e74af 100644
--- a/tutorius/store.py
+++ b/tutorius/store.py
@@ -220,7 +220,7 @@ class StoreProxy(object):
installnode = xml.getElementsByTagName("install")[0]
installurl = installnode.firstChild.nodeValue
- fp = urllib.urlopen(installurl)
+ fp = urllib2.urlopen(installurl)
return fp
diff --git a/tutorius/translator.py b/tutorius/translator.py
index 4f29078..bd24f8f 100644
--- a/tutorius/translator.py
+++ b/tutorius/translator.py
@@ -177,7 +177,7 @@ class ResourceTranslator(object):
install_error_cb(old_action, exception)
# Decorated functions
- def install(self, action, action_installed_cb, error_cb):
+ def install(self, action, action_installed_cb, error_cb, is_editing=False, editing_cb=None):
# Make a new copy of the action that we want to install,
# because translate() changes the action and we
# don't want to modify the caller's action representation
@@ -187,7 +187,9 @@ class ResourceTranslator(object):
# Send the new action to the probe manager
self._probe_manager.install(new_action, save_args(self.action_installed, action_installed_cb),
- save_args(self.action_install_error, error_cb, new_action))
+ save_args(self.action_install_error, error_cb, new_action),
+ is_editing=is_editing,
+ editing_cb=editing_cb)
def update(self, action_address, newaction):
translated_new_action = copy_module.deepcopy(newaction)
diff --git a/tutorius/viewer.py b/tutorius/viewer.py
index 56428e1..8041162 100644
--- a/tutorius/viewer.py
+++ b/tutorius/viewer.py
@@ -65,7 +65,8 @@ class Viewer(object):
self.drag_pos = None
self.selection = set()
- self.win = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ self.win = gtk.Window(gtk.WINDOW_POPUP)
+ self.win.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_UTILITY)
self.win.set_size_request(400, 200)
self.win.set_gravity(gtk.gdk.GRAVITY_SOUTH_WEST)
self.win.show()