class SimpleTransactionEditor(object): def __init__(self, trans, transid, plugin, gui_parent, change_register_function, book): """Sub classes should not override this __init__ but instead implement simple_init_before_show() to hook in at the right time """ self.trans = trans self.transid = transid self.plugin = plugin self.gui_parent = gui_parent self.change_register_function = change_register_function self.book = book # An offscreen place for the transaction's GUI to hide while not in use. self.hide_parent = Window() self.hide_parent.hide() self.mainvbox = VBox() self.hide_parent.add(self.mainvbox) self.simple_init_before_show() self.mainvbox.show_all() self.mainvbox.reparent(self.gui_parent) def simple_init_before_show(self): """Create the GUI for this type of BoKeep transaction. Widgets can be added to the "self.mainvbox" object.""" raise Exception("simple_init_before_show must be overrided by " "sub classes of SimpleTransactionEditor") def detach(self): """Detach the BoKeep transaction's GUI from the visible window and attach it to a hidden window. Sub classes overriding this are recommended to do their own work first, and then delegate back up to this original detach so it may do the widget reparenting work""" self.mainvbox.reparent(self.hide_parent)
class multipage_glade_editor(object): def __init__(self, trans, transid, plugin, gui_parent, change_register_function, book, display_mode=TRANSACTION_ALL_EDIT_FIRST_TIME, transaction_edit_finished_function=null_function): self.trans = trans self.transid = transid self.plugin = plugin self.gui_parent = gui_parent self.change_register_function = change_register_function self.book = book self.display_mode = display_mode self.transaction_edit_finished_function = ( null_function if display_mode not in HEADLESS_MODES else transaction_edit_finished_function ) self.hide_parent = Window() self.hide_parent.hide() self.mainvbox = VBox() self.hide_parent.add(self.mainvbox) config = self.trans.get_configuration_and_provide_on_load_hook() config_module_name = self.plugin.config_module_name if not config_valid(config): # even in the case of a broken config, we should still # display all of the data we have available... self.mainvbox.pack_start(Label("no configuration")) elif not self.trans.can_safely_proceed_with_config_module(config): # should display all data that's available instead of just # this label # # and should give # user an overide option where they either pick an old config # for one time use or just blow out the memory of having used # a different config... # # should also print the checksum itself so they know # what they need... # # perhaps eventually we even put in place some archival support # for saving old glade and config files and then code into the # the transaction -- hey, code you need to be editable is # over here.. # # now hopefully there is no marking of this transaction dirty # in this mode and the extra safegaurds we put into # MultipageGladeTransaction don't get activated self.mainvbox.pack_start( Label("out of date configuration. data is read only here for " "the safety of your old information, last adler " "CRC was %s" % self.trans.config_crc_cache )) else: # if the safety cache was relied on before we need to tell the # backend that the transaction is actually dirty, # and now that we know that we have a workable config, # there's a chance that we'll actually be able to avoid # relying on the cache this time if self.trans.get_safety_cache_was_used(): self.change_register_function() self.page_label = Label("") (x_align, y_align) = self.page_label.get_alignment() self.page_label.set_alignment(0.0, y_align) self.mainvbox.pack_start(self.page_label, expand=False) # establish maincontainer, which is where the actual glade # pages are put by attach_current_page # # The order of placement here is important, we place this # after page_label has already been added to main # and we also need to do this prior to attach_current_page # being called, as it depends on self.maincontainer being # there # # when we're in headless mode the maincontainer can just be # the mainvbox itself # # but, outside headless mode we save screen real-estate and # place a scrolled window (which becomes the maincontainer) # inside the mainvbox and the glade by glade pages end up # in there instead (again, in attach_current_page) if display_mode in HEADLESS_MODES: self.maincontainer = self.mainvbox else: self.maincontainer = Viewport() sw = ScrolledWindow() sw.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC) sw.add( self.maincontainer) self.mainvbox.pack_start(sw) self.glade_pages = [ self.__setup_page(glade_file, top_glade_element) for glade_file, top_glade_element in config.pages ] self.glade_pages_by_ident_index = dict( ( (key, self.glade_pages[i]) for i, key in enumerate(config.pages) ) # end generator expression ) # end dict self.__setup_auto_widgets() self.current_page = 0 self.attach_current_page() button_hbox = HBox() self.mainvbox.pack_end(button_hbox, expand=False) self.nav_buts = dict( (Button(), i) for i in range(2) ) for but, i in self.nav_buts.iteritems(): but.set_property('use-stock', True) but.set_label( STOCK_GO_BACK if i == GLADE_BACK_NAV else STOCK_GO_FORWARD ) button_hbox.pack_start(but, expand=False) but.connect("clicked", self.nav_but_clicked) config.gui_initialization_hook( self, self.trans, self.plugin, self.book) self.mainvbox.show_all() self.mainvbox.reparent(self.gui_parent) def page_is_current(self, page): # assumption is that config is fine and we're on a page assert( hasattr(self, 'glade_pages_by_ident_index') and True ) return \ self.glade_pages_by_ident_index[page] == self.current_widget_dict def __setup_page(self, glade_file, top_glade_element): widget_dict = {} # this should come from the config, seeing how we're setting up # our stuff manually event_handlers_dict = {} load_glade_file_get_widgets_and_connect_signals( glade_file, top_glade_element, widget_dict, event_handlers_dict ) widget_dict[top_glade_element].hide() return widget_dict def __setup_auto_widgets(self): # go through each page for key, widget_dict in self.glade_pages_by_ident_index.iteritems(): # go through each widget for widget_name, widget in widget_dict.iteritems(): widget_key = (key, widget_name) # determine if the current widget is one that we can take # care of automatically saving, we do this with a linear # search through a table of eligible types, # where the first element of each entry is Widget class # # Once we have a match do one of the following # * Change the widget from the existing stored stage # * Establish new state from some default # The next two entries in the table # (change_widget_from_state, new_state_from_widget) # provide functions to do this # # Lastly, we establish event handlers for this widget using # the last element in the table for (cls, change_widget_from_state, new_state_from_widget, establish_event_handlers) in ( # start table (Entry, lambda w, wk: w.set_text(self.trans.get_widget_state(wk)), lambda w: '', lambda w: w.connect("changed", self.entry_changed), ), # Entry (Calendar, self.__change_calendar_from_saved_version, get_current_date_of_gtkcal, lambda w: w.connect( "day_selected", self.calendar_changed ), ), # Calendar (CheckButton, lambda w, wk: w.set_active(self.trans.get_widget_state(wk)), get_state_of_checkbutton, lambda w: w.connect("toggled", self.checkbutton_changed), ), # CheckButton ): # end of table and for declartion # does the widget match the type in the current table entry? if isinstance(widget, cls): # use the three remaining functions in the table # as appropriate if self.trans.has_widget_state( widget_key ): change_widget_from_state(widget, widget_key) else: self.trans.update_widget_state( widget_key, new_state_from_widget(widget) ) establish_event_handlers(widget) def __change_calendar_from_saved_version(self, widget, widget_key): set_current_date_of_gtkcal(widget, self.trans.get_widget_state( widget_key ) ) def attach_current_page(self): config = self.plugin.get_configuration(allow_reload=False) self.current_widget_dict = self.glade_pages[self.current_page] self.current_window = self.current_widget_dict[ config.pages[self.current_page][TOP_WIDGET] ] self.current_top_vbox = self.current_window.child self.current_top_vbox.reparent( self.maincontainer) self.page_label.set_text( "page %s of %s" %( self.current_page + 1, len(config.pages) ) ) self.update_auto_labels() def detach_current_page(self): # put the spawn back to wence it came self.current_top_vbox.reparent( self.current_window) def detach(self): if hasattr(self, 'current_page'): self.detach_current_page() self.mainvbox.reparent(self.hide_parent) def page_change_acceptable_by_input_valid(self): bad_fields = ', '.join( widget_name for widget_name, widget in self.current_widget_dict.iteritems() if not self.widget_valid(widget_name, widget) ) if bad_fields == '': return True else: # this is kind of primiative, it would be better to # just highlight them by inserting red lights or something gtk_error_message("The following fields are invalid %s" % bad_fields ) return False def __current_page_ident(self): config = self.plugin.get_configuration(allow_reload=False) return config.pages[self.current_page] def __entry_widget_is_check_excempt(self, widget_name): config = self.plugin.get_configuration(allow_reload=False) return (self.__current_page_ident(), widget_name) in \ config.non_decimal_check_labels def widget_valid(self, widget_name, widget): config = self.plugin.get_configuration(allow_reload=False) if isinstance(widget, Entry) and \ not self.__entry_widget_is_check_excempt(widget_name): try: entry_to_decimal_convert( widget.get_text(), widget_name, self.__current_page_ident(), config) except EntryTextToDecimalConversionFail: return False # this covers not only the else case on the first if, but the # the case with the above try, except passes without exception return True def nav_but_clicked(self, but, *args): config = self.plugin.get_configuration(allow_reload=False) old_page = self.current_page delta = -1 if self.nav_buts[but] == GLADE_BACK_NAV else 1 new_page = old_page + delta # reject a change outside the acceptable range.. and hmm, # perhaps this event handler should never even run under those # conditions because we should really just grey the buttons if not (new_page < 0 or new_page == len(self.glade_pages)) and \ self.page_change_acceptable_by_input_valid() and \ config.page_change_acceptable(self.trans, old_page, new_page): # intentionally done before the page is actually attached, # that's what we mean by pre config.page_pre_change_config_hooks(self.trans, old_page, new_page) self.detach_current_page() self.current_page = new_page self.attach_current_page() # intentionally done after the page is actually attached, # that's what we mean by post config.page_post_change_config_hooks(self.trans, old_page, new_page) entry_changed = make_widget_changed_func(lambda w: w.get_text() ) calendar_changed = make_widget_changed_func(get_current_date_of_gtkcal) checkbutton_changed = make_widget_changed_func(get_state_of_checkbutton) def update_auto_labels(self): config = self.plugin.get_configuration(allow_reload=False) # this function should never be called if the config hasn't been # checked out as okay assert( hasattr(config, 'auto_update_labels') ) for page, label_name, label_source_func in config.auto_update_labels: if self.page_is_current(page): try: label_text = str(label_source_func( self.trans.widget_states, config)) except EntryTextToDecimalConversionFail, e: label_text = '' except WidgetFindError, no_find_e: label_text = str(no_find_e) self.current_widget_dict[label_name].set_text(label_text)