From c14688d67a82b7ec7746beda90da915c98600a3d Mon Sep 17 00:00:00 2001 From: erick Date: Sat, 05 Dec 2009 21:03:59 +0000 Subject: Merge branch 'frame_integration' into revamped_dragndrop Conflicts: tutorius/actions.py --- (limited to 'Workshop.activity') 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 = '%(title)s' + self.author_text = 'by %(author)s' + 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 = '%(title)s' + 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 @@ + + +]> + + + + + + + + image/svg+xml + + + + + + + + + \ 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 = ' %(text)s: ' + self.value = ' %(text)s ' + + 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 @@ + + +]> + + + + + + + + image/svg+xml + + + + + + + + + \ 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() -- cgit v0.9.1