#! -*- coding: utf-8 -*- #(c) Stefan Heher 2009 #1234567890123456789012345678901234567890123456789012345678901234567890123456789 import gtk import pygtk import pango import time import gobject from timer import Timer from sugar import profile from sugar.graphics import style from sugar.activity import activity class Display: """ Definition and manipulation of all GUI-elemts. Exception (redesign?): def define_buttons in classes derived from Exercise. """ print("in display, class definition") # DnD for gtk_tree in TARGETS = [ ('pixbuf', gtk.TARGET_SAME_WIDGET, 0), ('text', gtk.TARGET_SAME_WIDGET, 1) ] def __init__(self, window): self._permanent_gui_elements(window) self.errors = 0 self._ex = None # needed for feedback_table etc # TODO: imprive information hiding self._sett = None # setting updated by callbacks during input self.running = False # switch settings_/feedback_table TODO rename self._coll_key = None # semaphore for self.finish_collect_callback # and Collection.select_exerc_callback self._testvar = 'FOUND' def register(self, sess, co, learner, exstore): """register _after_ Session and Coach have been instantiated""" self._sess = sess self._co = co self._learner = learner self._exstore = exstore def _permanent_gui_elements(self, window): # The display is partitioned as follows # # table: # 0 1 2 #0+-------------------------+------------------------+ # | | | #1| | OVERLAYS OF | # | scrolled_window | exstore_table | #2| | OR | # | | settings_table | #3+-------------------------+ OR | # | | feedback_table | #4| empty for calcs etc | | # | | | #5+-------------------------+------------------------+ self.main_window = window # Save the sugar main window # whole window with 5 lines and 2 columns self.table = gtk.Table(5, 2, True) self.main_window.set_canvas(self.table) self.log_buffer = gtk.TextBuffer() self.tag_table = self.log_buffer.get_tag_table() self.error_tag = gtk.TextTag("error") # tag for errors self.error_tag.set_property("foreground", "blue") self.tag_table.add(self.error_tag) self.correct_tag = gtk.TextTag("correct")# tag for correct calcs self.correct_tag.set_property("foreground", "green") self.tag_table.add(self.correct_tag) # Create a log_view with the previously created log_buffer self.log_view = gtk.TextView(self.log_buffer) # Set the font size for the log_view self.log_view.modify_font(pango.FontDescription('sans bold 14')) self.log_view.set_editable(False) self.log_view.set_cursor_visible(False) self.log_view.set_justification(gtk.JUSTIFY_CENTER) self.scrolled_window = gtk.ScrolledWindow() # for the log_view self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) # Connect log_view and scrolled window self.scrolled_window.add(self.log_view) # Insert the log_view into the upper half self.table.attach(self.scrolled_window, 0, 1, 0, 3) self.log_view.show() self.scrolled_window.show() # 3 tables as overlays with same 15 lines and 6 columns self.settings_table = gtk.Table (14, 9, True) self.exstore_table = gtk.Table(14, 9, True) self.gtk_tree() #-->self.treeview #@# self.exstore_table.attach(self.treeview, 0, 9, 0, 13) #@# self.treeview.show() self.feedback_table = gtk.Table (14, 9, True) # Insert the 3 tables into the right half of the screen self.table.attach(self.settings_table, 1, 2, 0, 5) self.table.attach(self.exstore_table, 1, 2, 0, 5) self.table.attach(self.feedback_table, 1, 2, 0, 5) # show all tables, scrolled_window already shown above self.settings_table_show() # treat uniformly <>! self.exstore_table.show() # treat uniformly ! self.feedback_table.show() # treat uniformly ! self.table.show() # since all other tables have been inserted def update_time(self): minutes, seconds = divmod(self.stopwatch.elapsed, 60) if(minutes < 10 and seconds < 10): time = "0" + str(int(minutes)) + ":" + "0" + str(int(seconds)) if(minutes > 9 and seconds > 9): time = str(int(minutes)) + ":" + str(int(seconds)) if(minutes > 9 and seconds < 10): time = str(int(minutes)) + ":" + "0" + str(int(seconds)) if(minutes < 10 and seconds > 10): time = "0" + str(int(minutes)) + ":" + str(int(seconds)) self.stopwatch_label.set_label(time) self.stopwatch_label.queue_draw() return True def settings_table_show(self): # rename to .._permanent """ Initialize all permanent gui-elements in settings_table. """ # button releasing settings_table and coach.notify('setting-done',..) self.see_butt = gtk.Button(None, gtk.STOCK_GO_FORWARD) self.see_butt.connect("clicked", self.release_sett_callback) #self.settings_table.attach(self.see_butt, 0, 5, 14, 15) self.settings_table.attach(self.see_butt, 0, 8, 13, 14) self.see_alignment = self.see_butt.get_children()[0] self.see_hbox = self.see_alignment.get_children()[0] self.see_image, self.see_label = self.see_hbox.get_children() self.see_label.set_label("settings_table") self.see_butt.show() # button switching from settings_table to collect_table self.sec_butt = gtk.Button("") self.sec_butt.connect("clicked", self.switch_setts_to_coll_callback) #self.settings_table.attach(self.sec_butt, 5, 6, 14, 15) self.settings_table.attach(self.sec_butt, 8, 9, 13, 14) self.sec_alignment = self.sec_butt.get_children()[0] self.sec_butt.show() def switch_setts_to_coll_callback(self, widget): """ Switch from settings_table to exstore_table. """ self.settings_table.hide() self.exstore_table.show() def feedback_table_show(self): """RENAME to draw_feedback_screen""" # Section for stopwatch self.stopwatch = Timer() self.stopwatch_label = gtk.Label("00:00") self.stopwatch_label.modify_font(pango.FontDescription("sans 16")) #self.feedback_table.attach(self.stopwatch_label, 3, 5, 12, 13) self.feedback_table.attach(self.stopwatch_label, 4, 6, 12, 13) # Section for nickname self.name = profile.get_nick_name() self.name_label = gtk.Label(self.name) self.name_label.modify_font(pango.FontDescription("sans 16")) #self.feedback_table.attach(self.name_label, 0, 6, 13, 14) self.feedback_table.attach(self.name_label, 0, 9, 13, 14) # Section for progress bar self.progressbar = gtk.ProgressBar(adjustment=None) # Color for progress bar style = self.progressbar.get_style() style.bg[gtk.STATE_PRELIGHT] = gtk.gdk.color_parse("green") self.progressbar.set_style (style) self.progressbar.set_fraction(0) self.feedback_table.attach(self.progressbar, 0, 9, 8, 9) # Labels for progress bar self.progress0 = gtk.Label("0") self.progress0.modify_font(pango.FontDescription("sans 16")) self.feedback_table.attach(self.progress0, 0, 1, 9, 10 ) # Labels for status update self.correct_count = 0 self.correct_counter = gtk.Label(str(self.correct_count)) self.correct_counter.modify_font(pango.FontDescription("sans 16")) # Ugly code for label color attr = pango.AttrList() fg_color = pango.AttrForeground(0, 65535, 0, 0, 6) attr.insert(fg_color) self.correct_counter.set_attributes(attr) #self.feedback_table.attach(self.correct_counter, 2, 4, 9, 10 ) self.feedback_table.attach(self.correct_counter, 3, 5, 9, 10 ) self.false_count = 0 self.false_counter = gtk.Label(str(self.false_count)) self.false_counter.modify_font(pango.FontDescription("sans 16")) # Ugly code for label color attr = pango.AttrList() fg_color = pango.AttrForeground(0, 0, 65535, 0, 6) attr.insert(fg_color) self.false_counter.set_attributes(attr) #self.feedback_table.attach(self.false_counter, 2, 4, 10, 11 ) self.feedback_table.attach(self.false_counter, 3, 5, 10, 11 ) self.stopwatch_label.show() gobject.timeout_add(1000, self.update_time) self.name_label.show() self.progressbar.show() self.progress0.show() self.correct_counter.show() self.false_counter.show() self.total_calcs = self._ex.count() self.progress_total = gtk.Label(str(self.total_calcs)) self.progress_total.modify_font(pango.FontDescription("sans 16")) #self.feedback_table.attach(self.progress_total, 5, 6, 9, 10 ) self.feedback_table.attach(self.progress_total, 8, 9, 9, 10 ) self.progress_total.show() self.sfb_butt = gtk.Button(None, gtk.STOCK_STOP) self.sfb_butt.connect("clicked", self.release_feedb_callback) self.feedback_table.attach(self.sfb_butt, 0, 9, 13, 14) self.sfb_alignment = self.sfb_butt.get_children()[0] self.sfb_hbox = self.sfb_alignment.get_children()[0] self.sfb_image, self.sfb_label = self.sfb_hbox.get_children() self.sfb_label.set_label("feedback_table") self.sfb_butt.show() def feedback_table_hide(self): self.progressbar.set_fraction(0) self.stopwatch_label.hide() self.name_label.hide() self.progressbar.hide() self.progress0.hide() self.correct_counter.hide() self.false_counter.hide() self.progress_total.hide() self.sfb_butt.hide() def offer_setting(self,ex): """ Prepare display for update of settings. """ self._ex = ex #WN.LV either drop this or self.current_exercise self._sett = self._ex.get_setting() #WN.LV make _sett local self.current_exercise = ex #WN.LV either drop this or self._ex print('in display.offer_setting, topic=', self._sett['topic']) self.current_exercise.define_buttons() self.current_exercise.set_buttons(self._sett) self.settings_table.show() def release_feedb_callback(self, widget): """ """ self.protocol('----------------------------------------', 0, 'OK') print('in display.release_feedb_callback') self.calc_table.destroy() self.feedback_table_hide() self.exstore_table.show() # from here the next user-interactions def release_sett_callback(self, widget): """ Start calcs with these settings (probably updated). """ print('in display.release_sett_callback') self._co.settings_done(self._ex._sett) self.settings_table.hide() #self.exstore_table.hide() self.feedback_table_show() def init_calc(self): """ prepares for calculations from 1 setting. for instance, a calculation might be on 1 ore more lines. """ print("in display.init_calc") # make empty lines such that all calcs are entered at bottom for i in range(1,21): end_iterator = self.log_buffer.get_end_iter() self.log_buffer.insert(end_iterator, "\n") self.log_view.scroll_mark_onscreen(self.log_buffer.get_insert()) def destroy_box(self): self.calculation_box.destroy() def protocol(self, full_line, errors, feedback): end_iterator = self.log_buffer.get_end_iter() if( feedback == 'OK' ): self.log_buffer.insert_with_tags_by_name(end_iterator, "\n" + full_line, "correct" ) elif ( feedback == 'XXX' ): self.log_buffer.insert_with_tags_by_name(end_iterator, "\n" + full_line, "error" ) end_iterator = self.log_buffer.get_end_iter() mark = self.log_buffer.create_mark(None, end_iterator, True) self.log_view.scroll_mark_onscreen(mark) def protocol_generated_calcs(self, calculations, change): print("DEBUG: in display.protocol_generated_calcs") end_iterator = self.log_buffer.get_end_iter() self.log_buffer.insert_with_tags_by_name(end_iterator, "\n---------- " + change + " ----------" , "fixxxme!" ) for c in calculations: end_iterator = self.log_buffer.get_end_iter() #['1', '-', '1', '=', '0'] to_format = c[0] printable_calc = to_format[0] + " " + \ to_format[1] + " " + \ to_format[2] + " " + \ to_format[3] + " " + \ to_format[4] self.log_buffer.insert_with_tags_by_name(end_iterator, "\n" + printable_calc, "fixxxme!" ) mark = self.log_buffer.create_mark(None, end_iterator, True) self.log_view.scroll_mark_onscreen(mark) def input_digit(self, widget, dig, proterr, protok): """callback: input a digit and give feedback. The _only_ other active widget is the -button on the right""" entry_text = widget.get_text() ### bitte anzeigen !??????? if(entry_text == dig): self.errors = 0 #WN090518 ??? self.protocol(protok, self.errors, 'OK') self.destroy_box() self._learner.digit_done() elif(entry_text == ""): pass #otherwise feedback in protocol written twice: see entry.set_text("") below else: self.false_count = self.false_count + 1 self.false_counter.set_text(str(self.false_count)) self.errors = self.errors + 1 widget.set_text("") self.protocol(proterr, self.errors, 'XXX') #WN090518 def display_calc(self, lines): self.calc_table = gtk.Table(5, 1, True) self.table.attach(self.calc_table, 0, 1, 0, 5) self.calc_table.show() """display the lines of a calc with _ at all input positions""" lino = 0 for li in lines: #print('in Display.display_calc, line=', li) self.create_entryline((lino, -1, 'dummy-dig', 'dummy-proterr', 'dummy-protok', li)) lino = lino + 1 def create_entryline(self, (lineno, linepos, dig, proterr, protok, line)): """create gtk.Entry in line at linepos and set callback_input_digit""" #print('in Display.create_entryline: lineno=', lineno, ', linepos=', # linepos, ', line=', line) calculation, cursor = line, linepos self.calculation_box = gtk.HBox(True, 0) calc_pos = 0 self.calc_table.attach(self.calculation_box, 0, 1, 3 + lineno, 4 + lineno) for i in calculation: if (calc_pos != cursor): self.label = gtk.Label(i) self.label.modify_font(pango.FontDescription("sans 24")) self.calculation_box.pack_start(self.label) self.label.show() else: # prepare for input self.text_entry = gtk.Entry(max = 1) self.text_entry.modify_font(pango.FontDescription("sans 24")) self.text_entry.connect("changed", self.input_digit, dig, proterr, protok) self.calculation_box.pack_start(self.text_entry) self.text_entry.show() calc_pos = calc_pos + 1 #?kann m.die calculation_box ZUERST aufbauen und DANN self.table.attach self.calculation_box.show() if(cursor != -1): self.text_entry.grab_focus() #just add lineno to table.attach #TODO rename calculation, cursor below ... #TODO remove hack on callback: #self.input_digit('widget', dig, protline) def finish_calc(self): self.stopwatch.stop() def show_progress(self): self.progressbar.set_fraction(self.progressbar.get_fraction()+(float(1)/float(self.total_calcs))) self.correct_count = self.correct_count + 1 self.correct_counter.set_text(str(self.correct_count)) self.calc_table.destroy() def offer_coll_to_learner(self, collect): """Show the exstore to the learner""" collect.define_coll_gui() #@# collect.set_coll_gui(collect) #@# sets callbacks self.set_coll_to_learner() # set finish_learner_coll_butt + callb def switch_exercise(self): """ Another exercise has been selected. """ #WN091215 DAS IST AB JETZT FALSCH: ex<->sett + ex<->coll moeglich self.settings_table.destroy() self.settings_table = gtk.Table(14, 9, True) self.table.attach(self.settings_table, 1, 2, 0, 5) self.settings_table.show() self.feedback_table.destroy() self.feedback_table = gtk.Table(14, 9, True) self.table.attach(self.feedback_table, 1, 2, 0, 5) self.feedback_table.show() def set_select_exerc_semaphore(self, coll_key): """ Sets a semaphore for finish_collect_callback. """ print('in display.set_select_exerc_semaphore, coll_key', self._coll_key) self._coll_key = coll_key def set_coll_to_learner(self): """ Set the permanent gui-elements for exstore_table. """ print('in display.set_coll_to_learner') # button switching from collect_table to calcs self.flc_butt = gtk.Button(None, gtk.STOCK_GO_FORWARD) self.flc_butt.connect("clicked", self.finish_learner_coll_callback) self.exstore_table.attach(self.flc_butt, 0, 8, 13, 14) self.flc_alignment = self.flc_butt.get_children()[0] self.flc_hbox = self.flc_alignment.get_children()[0] self.flc_image, self.flc_label = self.flc_hbox.get_children() self.flc_label.set_label("to calcs") self.flc_butt.show() # button switching from collect_table to settings_table self.sts_butt = gtk.Button("") self.sts_butt.connect("clicked", self.switch_learner_to_setts) self.exstore_table.attach(self.sts_butt, 8, 9, 13, 14) self.sts_alignment = self.sts_butt.get_children()[0] self.sts_butt.show() def finish_learner_coll_callback(self, widget): """ Callback on flc_butt; uses self._coll_key as semaphore from Collection.select_exerc_callback. """ print('in display.finish_learner_coll_callback, _coll_key=', self._coll_key) if not(self._coll_key is None): self._co.exercise_selected(self._coll_key) self.exstore_table.hide() self.feedback_table_show() def switch_learner_to_setts(self, widget): """ Callback on sts_butt. asks the Coach if allowed. """ self._co.switch_to_settings(self._coll_key) self.exstore_table.hide() def set_curr_exerc(self, exerc): """ self._ex is needed for feedback_table etc. """ self._ex = exerc def gtk_tree(self): """ Define TreeStore and TreeView """ self.treestore = gtk.TreeStore(gtk.gdk.Pixbuf, str) self.treestore.clear() #self._exstore.fill_store(self.treestore, None, None) self.treeview = gtk.TreeView(self.treestore) # create the TreeViewColumn to display the data self.tvcolumn = gtk.TreeViewColumn('Uebungssammlung') self.cellpb = gtk.CellRendererPixbuf() self.tvcolumn.pack_start(self.cellpb, True) self.tvcolumn.add_attribute(self.cellpb, 'pixbuf', 0) # create a CellRendererText to render the data self.cell = gtk.CellRendererText() # add the cell to the tvcolumn and allow it to expand self.tvcolumn.pack_start(self.cell, True) # set the cell "text" attribute to column 0 - retrieve text # from that column in treestore self.tvcolumn.add_attribute(self.cell, 'text', 1) # add tvcolumn to treeview self.treeview.append_column(self.tvcolumn) # make it searchable self.treeview.set_search_column(0) # Allow sorting on the column self.tvcolumn.set_sort_column_id(-1) # ab hier teil von robert self.treeview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, self.TARGETS, gtk.gdk.ACTION_MOVE) self.treeview.enable_model_drag_dest(self.TARGETS, gtk.gdk.ACTION_MOVE) self.treeview.connect("drag_data_get", self.drag_data_get_data) self.treeview.connect("drag_data_received", self.drag_data_received_data) self.treeview.expand_all() def drag_data_get_data(self, treeview, context, selection, target_id, etime): # selection erfassen und pixbuf in string umwandeln treeselection = treeview.get_selection() model, iter = treeselection.get_selected() # Tupel der die Drag Position angibt path = self.treestore.get_path(iter) print "Tree-Element: ", model.get_value(iter, 1) print "Drag-Position: ", path self.__from = path iconstr = (model.get_value(iter, 0)).get_pixels() title = model.get_value(iter, 1) sep = "*" data = iconstr + sep + title selection.set(selection.target, 8, data) return def drag_data_received_data(self, treeview, context, x, y, selection, info, etime): treeselection = treeview.get_selection() model, from_parent = treeselection.get_selected() model = treeview.get_model() sep = "*" data = selection.data # aus pixel-string wieder pixbuf objekt erstellen iconpixtemp, sep, title = data.partition(sep) iconpix = gtk.gdk.pixbuf_new_from_data( iconpixtemp, gtk.gdk.COLORSPACE_RGB, True, 8, 10, 10, 40) data = [iconpix, title] c = Collection(None,None,None,None) obj = c.get_object(self.__from) #obj = self.get_object(self.__from) drop_info = treeview.get_dest_row_at_pos(x, y) if drop_info: self.__to = drop_info[0] c.insert_object(obj, self.__to) #self.insert_object(obj, self.__to) self.treestore.clear() self.fill_store(self.treestore, None, None) self.treeview.expand_all() if context.action == gtk.gdk.ACTION_MOVE: context.finish(True, True, etime) return