class OperationManager(gtk.VBox): def __init__(self, app): gtk.VBox.__init__(self) self.__objects_with_callbacks = [] self.app = app self.loaded_operations = [] self.events = EventCallbacksList() # repository of loaded sources self.events.set_callback( app.sources_repository, "source-removed", self._cb_detach_source_from_all_operations) # full view of selected operation self.full_view = OperationFullView(self.app) self.__objects_with_callbacks.append(self.full_view) self.events.set_callback( self.full_view, "filter-sources", self._cb_filter_sources) self.events.set_callback( self.full_view, "operation-finished", self._cb_operation_finished) self.events.set_callback( self.full_view, "detach-source", self._cb_detach_source) # toolbar toolbar = gtk.HBox(False) toolbar.set_border_width(5) button = gtk.Button("Load source") button.connect("clicked", lambda w: self._cb_load()) toolbar.pack_start(button, False, False) button = gtk.Button("Disable filter") button.connect("clicked", lambda w: self._cb_filter_off()) toolbar.pack_start(button, False, False) self.pack_start(toolbar, False, False) # sources vbox = gtk.VBox(False) vbox.set_size_request(80,-1) self.sources_title = gtk.Label() self.sources_title.set_markup("Sources:") haling = gtk.Alignment(0, 0, 0, 0) haling.set_padding(0, 5, 2, 0) haling.add(self.sources_title) vbox.pack_start(haling, False, False) self.sources_view = SourcesRepositoryView( app.sources_repository, self.app) self.__objects_with_callbacks.append(self.sources_view) self.events.set_callback( self.sources_view, "attach-source", self._cb_attach_source) self.events.set_callback( self.sources_view, "source-data-changed", self._cb_source_data_changed) scw = gtk.ScrolledWindow() scw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scw.add_with_viewport(self.sources_view) vbox.pack_start(scw, True, True) paned1 = gtk.HPaned() paned1.pack1(vbox, resize=True) # list of operation's views vbox = gtk.VBox(False) label = gtk.Label("Operations:") haling = gtk.Alignment(0, 0, 0, 0) haling.set_padding(0, 5, 2, 0) haling.add(label) vbox.pack_start(haling, False, False) operations = self._load_operations() vbox.pack_start(operations) paned2 = gtk.VPaned() paned2.pack1(vbox, resize=True) # full action view (selected action) scw = gtk.ScrolledWindow() scw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scw.set_size_request(-1, 30) scw.add_with_viewport(self.full_view) paned2.pack2(scw) paned1.pack2(paned2, resize=True) self.pack_start(paned1) self.show_all() def close(self): for obj in self.__objects_with_callbacks: obj.deregister_callbacks() self.events.remove_all() def load_source(self, filename): return self.app.sources_repository.load_source(filename, self.app) def _load_operations(self): """Load modules (operations). It returns a column with all loaded operations. """ scw = gtk.ScrolledWindow() scw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) column = gtk.VBox(False) for name, operation_cls in operations.items(): operation = operation_cls() self.loaded_operations.append(operation) short_view = OperationShortView(operation) self.events.set_callback( short_view, "operation-selected", self._cb_operation_selected) column.pack_start(short_view, False, False) self.__objects_with_callbacks.append(operation) self.__objects_with_callbacks.append(short_view) scw.add_with_viewport(column) return scw def _cb_load(self): """It runs a loader for sources. For button "Load" in toolbar.""" dialog = gtk.FileChooserDialog("Source load", self.app.window, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) dialog.set_default_response(gtk.RESPONSE_OK) for filter in datatypes.get_load_file_filters(): dialog.add_filter(filter) try: response = dialog.run() if response == gtk.RESPONSE_OK: self._cb_filter_off() filename = dialog.get_filename() self.load_source(filename) finally: dialog.destroy() def _cb_operation_selected(self, operation): if self.full_view.operation == operation: return self._cb_filter_off() self.full_view.set_operation(operation) def _cb_filter_sources(self, type): visible_sources, all_sources = self.sources_view.set_filter(type) self.sources_title.set_markup( "Sources (<b>visible {0} from {1}</b>):".format( visible_sources, all_sources)) def _cb_filter_off(self): self.sources_title.set_markup("Sources:") self.sources_view.set_filter(None) if self.full_view.operation is not None: self.full_view.operation.select_argument(None, None) def _cb_operation_finished(self, operation, sources): # destroy filter and selected_argument self.full_view.operation.select_argument(None, None) self.sources_view.set_filter(None) def _cb_attach_source(self, source): operation = self.full_view.operation if operation is not None: operation.attach_source(source) param, idx = operation.selected_argument if param is None: return if param.is_list(): # the filter will stay on, # if a argument is list type operation.select_argument(param, param.sources_count() + 1) else: operation.select_argument(None, None) self.sources_view.set_filter(None) else: self.app.show_message_dialog( "No operation is chosen.", gtk.MESSAGE_INFO) def _cb_detach_source(self, source): operation = self.full_view.operation if operation is not None: param, idx = operation.selected_argument if param is not None: operation.select_argument(param, param.sources_count()) def _cb_detach_source_from_all_operations(self, source): """Detach source from all operation's arguments.""" for operation in self.loaded_operations: for param in operation.arguments: if param.is_list(): idx = 0 while idx < param.minimum + param.sources_count(): psource = param.get_source(idx) if psource is not None and psource == source: param.detach_source(idx) else: idx += 1 else: psource = param.get_source() if psource is not None and psource == source: param.detach_source() def _cb_source_data_changed(self, source): for operation in self.loaded_operations: for argument in operation.arguments: for i in xrange(argument.sources_count()): arg_source = argument.get_source(i) if arg_source == source: argument.emit_event("argument-changed")
class Operation(object, EventSource): def __init__(self): EventSource.__init__(self) self.events = EventCallbacksList() self.arguments = [Argument(param) for param in self.parameters] for argument in self.arguments: self.events.set_callback( argument, "argument-changed", self._cb_argument_changed) self.events.set_callback( argument, "select-argument", lambda p, i: self.select_argument(p, i), argument) self.selected_argument = (None, None) self._state = "ready" if self.all_sources_filled() else "incomplete" @property def state(self): return self._state @state.setter def state(self, state): assert (state == "ready" or state == "incomplete" or state == "incorrect") self._state = state self.emit_event("state-changed", state) def select_argument(self, argument, index=0): """Select a specific argument. The index is important if the selected argument is a list. Then the index specify the position in the list. Arguments: argument -- selected argument Keyword arguments: index -- the specific position in a list (default 0) """ if argument is None: self.selected_argument = (None, None) return if argument.is_list(): if index > argument.sources_count(): index = argument.sources_count() else: index = 0 self.selected_argument = (argument, index) def run(self, *args): """This method is called with attached arguments. Method must not have any side effect and it must not modifies its input arguments. """ return None def execute(self, app, store_results=True): args = [ argument.get_data() for argument in self.arguments ] results = self.run(app, *args) if not store_results: return results if results is None: return try: sources = list(results) except TypeError: sources = [results] for source in sources: app.sources_repository.add(source) return results def attach_source(self, source): argument, index = self.selected_argument if argument is not None and argument.type == source.type: argument.attach_source(source, index) return for argument in self.arguments: if (source.type == argument.type and (argument.is_empty() or argument.is_list())): argument.attach_source(source) return # not attached source self.emit_event("no-free-slot", source) def all_sources_filled(self): for argument in self.arguments: count = 0 for idx in xrange(argument.sources_count()): src = argument.get_source(idx) if src is not None and src.data is not None: count += 1 if count < argument.minimum: return False return True def deregister_callbacks(self): self.events.remove_all() def _cb_argument_changed(self): if self.all_sources_filled(): self.state = "ready" else: self.state = "incomplete"
class OperationFullView(gtk.VBox, EventSource): def __init__(self, app): gtk.VBox.__init__(self) EventSource.__init__(self) self.operation = None self.app = app self.events = EventCallbacksList() def deregister_callbacks(self): self.events.remove_all() def set_operation(self, operation): # remove callbacks self.events.remove_all() # remove old components for comp in self.get_children(): self.remove(comp) # create a new view self.operation = operation if self.operation is None: return self.set_border_width(5) hbox = gtk.HBox(False) icon = drawing.StateIcon(self.operation.state, 25, 25) hbox.pack_start(icon, False, False) self.events.set_callback( self.operation, "no-free-slot", self._cb_no_free_slot) # name label = gtk.Label() label.set_alignment(0, 1) label.set_markup("<b>{0}</b>".format(operation.name)) halign = gtk.Alignment(0, 0.5, 0, 0) halign.set_padding(0, 0, 2, 0) halign.add(label) hbox.pack_start(halign, True, True) # button run button = gtk.Button("Run operation") button.set_sensitive(operation.state == "ready") button.connect("clicked", lambda w: self._cb_run()) hbox.pack_start(button, False, False) self.pack_start(hbox, False, False) self.events.set_callback( self.operation, "state-changed", lambda state: self._cb_state_changed(state, icon, button)) # description def cb_allocate(label, allocation ): label.set_size_request(allocation.width -2, -1) align = gtk.Alignment(0, 0, 1, 1) align.set_padding(0, 5, 5, 5) if "" != self.operation.description != None: frame = gtk.Frame() frame.set_label("Description") label = gtk.Label() label.set_alignment(0, 1) label.set_line_wrap(True) label.set_markup( "<i>{0}</i>".format(self.operation.description)) label.connect( "size-allocate", cb_allocate) frame.add(label) align.add(frame) self.pack_start(align, False, False) # arguments for argument in operation.arguments: param_view = ArgumentView(argument) self.events.set_callback( param_view, "filter-sources", lambda f: self.emit_event("filter-sources", f)) self.events.set_callback( param_view, "detach-source", lambda s: self.emit_event("detach-source", s)) self.pack_start(param_view, False, False) self.show_all() def _cb_run(self): data = self.operation.execute(self.app) self.emit_event("operation-finished", self.operation, data) def _cb_state_changed(self, state, icon, btn_run): icon.set_state(state) if state == "ready": btn_run.set_sensitive(True) else: btn_run.set_sensitive(False) def _cb_no_free_slot(self, source): self.app.show_message_dialog( "There is no free slot for source: '{0}'.".format(source.name), gtk.MESSAGE_INFO)
class Argument(object, EventSource): def __init__(self, parameter): EventSource.__init__(self) self._parameter = parameter self._src_name_changed_events = EventCallbacksList() self._real_attached = 0 self._sources = [None] * self._parameter.minimum @property def name(self): return self._parameter.name @property def type(self): return self._parameter.type @property def minimum(self): return self._parameter.minimum def is_list(self): return self._parameter.list def is_empty(self): return self._real_attached == 0 def sources_count(self): """Return a number of real attached sources, without respect to a minimum count. """ return self._real_attached def get_source(self, index=-1): """Return a chosen source. Keyword arguments: index -- index of chosen source (default -1; last added) """ if not self._sources or index >= len(self._sources): return None else: return self._sources[index] def attach_source(self, source, index=None): old_real_attached = self._real_attached if index is None: # attach attached = False for i, s in enumerate(self._sources): if s is None: # fill required sources firstly self._sources[i] = source attached = True break if not attached: self._sources.append(source) self._real_attached += 1 else: assert(index >= 0) if index < len(self._sources): if self._sources[index] is None: # increase only if the source is None, in the other case # it is only exchange of attached object self._real_attached += 1 self._sources[index] = source else: self._sources.append(source) self._real_attached += 1 if old_real_attached < self._real_attached: self.set_src_name_changed_callback(source, self._real_attached - 1) self.emit_event("argument-changed") def detach_source(self, index=0): if 0 <= index < len(self._sources): if len(self._sources) - self.minimum <= 0: # minimal count of arguments remain visible self._sources.append(None) self._sources.pop(index) self._real_attached -= 1 self.emit_event("argument-changed") def get_data(self): if self.is_list(): return [ source.data for source in self._sources[:self._real_attached] ] else: return self._sources[0].data def set_src_name_changed_callback(self, source, index): self._src_name_changed_events.set_callback( source, "source-name-changed", self._cb_source_name_changed, index) def remove_src_name_changed_callbacks(self): self._src_name_changed_events.remove_all() def _cb_source_name_changed(self, idx, old_name, new_name): self.emit_event("source-name-changed", idx, old_name, new_name)
class ArgumentView(gtk.Table, EventSource): def __init__(self, argument): gtk.Table.__init__(self, 1, 4, False) EventSource.__init__(self) self.entries = [] self.set_border_width(2) self.argument = argument self.events = EventCallbacksList() self.events.set_callback( self.argument, "argument-changed", self._cb_argument_changed) self.events.set_callback( self.argument, "source-name-changed", self._cb_source_name_changed) # initialize view self._cb_argument_changed() def deregister_callbacks(self): self.event.remove() def _cb_argument_changed(self): # remove self.argument.remove_src_name_changed_callbacks() for child in self.get_children(): self.remove(child) self.entries = [] # create actualized view rows = self.argument.sources_count() + 1 columns = 4 self.resize(rows, columns) label = gtk.Label() label.set_alignment(0, 0.5) label.set_markup("<b>{0}</b>".format(self.argument.name)) self.attach(label, 0, 1, 0, 1, xoptions=0) label = gtk.Label() label.set_alignment(0, 0.5) label.set_markup( " ({0})".format(self.argument.type.short_name)) self.attach(label, 1, 2, 0, 1) until = 1 if self.argument.is_list(): until = self.argument.sources_count() + 1 if self.argument.minimum > self.argument.sources_count(): until = self.argument.minimum for i in xrange(until): entry = gtk.Entry() entry.set_editable(False) entry.connect("focus-in-event", self._cb_choose_argument, i) attached_source = self.argument.get_source(i) if attached_source is not None: # reset callback, make right index self.argument.set_src_name_changed_callback(attached_source, i) entry.set_text(attached_source.name) entry.set_sensitive(attached_source.data is not None) self.attach(entry, 2, 3, i, i+1, xoptions=gtk.FILL) self.entries.append(entry) button = gtk.Button("Detach") button.set_sensitive(attached_source is not None) button.connect( "clicked", lambda w, index: self._cb_detach_source(index), i) self.attach(button, 3, 4, i, i+1, xoptions=0) self.show_all() def _cb_source_name_changed(self, idx, old_name, new_name): self.entries[idx].set_text(new_name) def _cb_detach_source(self, index): self.argument.detach_source(index) self.emit_event("detach-source", self.argument.get_source(index)) def _cb_choose_argument(self, widget, event, index): self.emit_event("filter-sources", [self.argument.type]) self.argument.emit_event("select-argument", index)
class OperationManager(gtk.VBox): def __init__(self, app): gtk.VBox.__init__(self) self.__objects_with_callbacks = [] self.app = app self.loaded_operations = [] self.events = EventCallbacksList() # repository of loaded sources self.events.set_callback(app.sources_repository, "source-removed", self._cb_detach_source_from_all_operations) # full view of selected operation self.full_view = OperationFullView(self.app) self.__objects_with_callbacks.append(self.full_view) self.events.set_callback(self.full_view, "filter-sources", self._cb_filter_sources) self.events.set_callback(self.full_view, "operation-finished", self._cb_operation_finished) self.events.set_callback(self.full_view, "detach-source", self._cb_detach_source) # toolbar toolbar = gtk.HBox(False) toolbar.set_border_width(5) button = gtk.Button("Load source") button.connect("clicked", lambda w: self._cb_load()) toolbar.pack_start(button, False, False) button = gtk.Button("Disable filter") button.connect("clicked", lambda w: self._cb_filter_off()) toolbar.pack_start(button, False, False) self.pack_start(toolbar, False, False) # sources vbox = gtk.VBox(False) vbox.set_size_request(80, -1) self.sources_title = gtk.Label() self.sources_title.set_markup("Sources:") haling = gtk.Alignment(0, 0, 0, 0) haling.set_padding(0, 5, 2, 0) haling.add(self.sources_title) vbox.pack_start(haling, False, False) self.sources_view = SourcesRepositoryView(app.sources_repository, self.app) self.__objects_with_callbacks.append(self.sources_view) self.events.set_callback(self.sources_view, "attach-source", self._cb_attach_source) self.events.set_callback(self.sources_view, "source-data-changed", self._cb_source_data_changed) scw = gtk.ScrolledWindow() scw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scw.add_with_viewport(self.sources_view) vbox.pack_start(scw, True, True) paned1 = gtk.HPaned() paned1.pack1(vbox, resize=True) # list of operation's views vbox = gtk.VBox(False) label = gtk.Label("Operations:") haling = gtk.Alignment(0, 0, 0, 0) haling.set_padding(0, 5, 2, 0) haling.add(label) vbox.pack_start(haling, False, False) operations = self._load_operations() vbox.pack_start(operations) paned2 = gtk.VPaned() paned2.pack1(vbox, resize=True) # full action view (selected action) scw = gtk.ScrolledWindow() scw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scw.set_size_request(-1, 30) scw.add_with_viewport(self.full_view) paned2.pack2(scw) paned1.pack2(paned2, resize=True) self.pack_start(paned1) self.show_all() def close(self): for obj in self.__objects_with_callbacks: obj.deregister_callbacks() self.events.remove_all() def load_source(self, filename): return self.app.sources_repository.load_source(filename, self.app) def _load_operations(self): """Load modules (operations). It returns a column with all loaded operations. """ scw = gtk.ScrolledWindow() scw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) column = gtk.VBox(False) for name, operation_cls in operations.items(): operation = operation_cls() self.loaded_operations.append(operation) short_view = OperationShortView(operation) self.events.set_callback(short_view, "operation-selected", self._cb_operation_selected) column.pack_start(short_view, False, False) self.__objects_with_callbacks.append(operation) self.__objects_with_callbacks.append(short_view) scw.add_with_viewport(column) return scw def _cb_load(self): """It runs a loader for sources. For button "Load" in toolbar.""" dialog = gtk.FileChooserDialog("Source load", self.app.window, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) dialog.set_default_response(gtk.RESPONSE_OK) for filter in datatypes.get_load_file_filters(): dialog.add_filter(filter) try: response = dialog.run() if response == gtk.RESPONSE_OK: self._cb_filter_off() filename = dialog.get_filename() self.load_source(filename) finally: dialog.destroy() def _cb_operation_selected(self, operation): if self.full_view.operation == operation: return self._cb_filter_off() self.full_view.set_operation(operation) def _cb_filter_sources(self, type): visible_sources, all_sources = self.sources_view.set_filter(type) self.sources_title.set_markup( "Sources (<b>visible {0} from {1}</b>):".format( visible_sources, all_sources)) def _cb_filter_off(self): self.sources_title.set_markup("Sources:") self.sources_view.set_filter(None) if self.full_view.operation is not None: self.full_view.operation.select_argument(None, None) def _cb_operation_finished(self, operation, sources): # destroy filter and selected_argument self.full_view.operation.select_argument(None, None) self.sources_view.set_filter(None) def _cb_attach_source(self, source): operation = self.full_view.operation if operation is not None: operation.attach_source(source) param, idx = operation.selected_argument if param is None: return if param.is_list(): # the filter will stay on, # if a argument is list type operation.select_argument(param, param.sources_count() + 1) else: operation.select_argument(None, None) self.sources_view.set_filter(None) else: self.app.show_message_dialog("No operation is chosen.", gtk.MESSAGE_INFO) def _cb_detach_source(self, source): operation = self.full_view.operation if operation is not None: param, idx = operation.selected_argument if param is not None: operation.select_argument(param, param.sources_count()) def _cb_detach_source_from_all_operations(self, source): """Detach source from all operation's arguments.""" for operation in self.loaded_operations: for param in operation.arguments: if param.is_list(): idx = 0 while idx < param.minimum + param.sources_count(): psource = param.get_source(idx) if psource is not None and psource == source: param.detach_source(idx) else: idx += 1 else: psource = param.get_source() if psource is not None and psource == source: param.detach_source() def _cb_source_data_changed(self, source): for operation in self.loaded_operations: for argument in operation.arguments: for i in xrange(argument.sources_count()): arg_source = argument.get_source(i) if arg_source == source: argument.emit_event("argument-changed")
class SourcesRepositoryView(gtk.VBox, EventSource): def __init__(self, repository, app): gtk.VBox.__init__(self) EventSource.__init__(self) self.repository = repository self.events = EventCallbacksList() self.events.set_callback( self.repository, "source-added", self._cb_source_added) self.events.set_callback( self.repository, "source-removed", self._cb_source_removed) self.app = app self.sources_views = {} # (source, source_view) sources = self.repository.get_sources() for source in sources: self._cb_source_added(source) def set_filter(self, filter): show_sources = self.repository.get_sources(filter) for source, source_view in self.sources_views.items(): if source in show_sources: source_view.show_all() else: source_view.hide_all() return (len(show_sources), len(self.repository)) def deregister_callbacks(self): for source in self.repository.get_sources(None): source_view = self.sources_views[source] source_view.remove_callback( "attach-source", self._cb_attach_source) source_view.remove_callback( "delete-source", self._cb_delete_source) self.events.remove_all() def _cb_source_added(self, source): source_view = SourceView(source, self.app) source_view.set_callback("attach-source", self._cb_attach_source) source_view.set_callback("delete-source", self._cb_delete_source) source_view.set_callback("source-data-changed", self._cb_data_changed) self.pack_start(source_view, False, False) source_view.show_all() self.sources_views[source] = source_view def _cb_source_removed(self, source): source_view = self.sources_views[source] source_view.remove_callback("attach-source", self._cb_attach_source) source_view.remove_callback("delete-source", self._cb_delete_source) source_view.remove_callback( "source-data-changed", self._cb_data_changed) self.remove(source_view) def _cb_attach_source(self, source): self.emit_event("attach-source", source) def _cb_data_changed(self, source): self.emit_event("source-data-changed", source) def _cb_delete_source(self, source): self.repository.remove(source)
class OperationFullView(gtk.VBox, EventSource): def __init__(self, app): gtk.VBox.__init__(self) EventSource.__init__(self) self.operation = None self.app = app self.events = EventCallbacksList() def deregister_callbacks(self): self.events.remove_all() def set_operation(self, operation): # remove callbacks self.events.remove_all() # remove old components for comp in self.get_children(): self.remove(comp) # create a new view self.operation = operation if self.operation is None: return self.set_border_width(5) hbox = gtk.HBox(False) icon = drawing.StateIcon(self.operation.state, 25, 25) hbox.pack_start(icon, False, False) self.events.set_callback(self.operation, "no-free-slot", self._cb_no_free_slot) # name label = gtk.Label() label.set_alignment(0, 1) label.set_markup("<b>{0}</b>".format(operation.name)) halign = gtk.Alignment(0, 0.5, 0, 0) halign.set_padding(0, 0, 2, 0) halign.add(label) hbox.pack_start(halign, True, True) # button run button = gtk.Button("Run operation") button.set_sensitive(operation.state == "ready") button.connect("clicked", lambda w: self._cb_run()) hbox.pack_start(button, False, False) self.pack_start(hbox, False, False) self.events.set_callback( self.operation, "state-changed", lambda state: self._cb_state_changed(state, icon, button)) # description def cb_allocate(label, allocation): label.set_size_request(allocation.width - 2, -1) align = gtk.Alignment(0, 0, 1, 1) align.set_padding(0, 5, 5, 5) if "" != self.operation.description != None: frame = gtk.Frame() frame.set_label("Description") label = gtk.Label() label.set_alignment(0, 1) label.set_line_wrap(True) label.set_markup("<i>{0}</i>".format(self.operation.description)) label.connect("size-allocate", cb_allocate) frame.add(label) align.add(frame) self.pack_start(align, False, False) # arguments for argument in operation.arguments: param_view = ArgumentView(argument) self.events.set_callback( param_view, "filter-sources", lambda f: self.emit_event("filter-sources", f)) self.events.set_callback( param_view, "detach-source", lambda s: self.emit_event("detach-source", s)) self.pack_start(param_view, False, False) self.show_all() def _cb_run(self): data = self.operation.execute(self.app) self.emit_event("operation-finished", self.operation, data) def _cb_state_changed(self, state, icon, btn_run): icon.set_state(state) if state == "ready": btn_run.set_sensitive(True) else: btn_run.set_sensitive(False) def _cb_no_free_slot(self, source): self.app.show_message_dialog( "There is no free slot for source: '{0}'.".format(source.name), gtk.MESSAGE_INFO)
class Operation(object, EventSource): def __init__(self): EventSource.__init__(self) self.events = EventCallbacksList() self.arguments = [Argument(param) for param in self.parameters] for argument in self.arguments: self.events.set_callback(argument, "argument-changed", self._cb_argument_changed) self.events.set_callback(argument, "select-argument", lambda p, i: self.select_argument(p, i), argument) self.selected_argument = (None, None) self._state = "ready" if self.all_sources_filled() else "incomplete" @property def state(self): return self._state @state.setter def state(self, state): assert (state == "ready" or state == "incomplete" or state == "incorrect") self._state = state self.emit_event("state-changed", state) def select_argument(self, argument, index=0): """Select a specific argument. The index is important if the selected argument is a list. Then the index specify the position in the list. Arguments: argument -- selected argument Keyword arguments: index -- the specific position in a list (default 0) """ if argument is None: self.selected_argument = (None, None) return if argument.is_list(): if index > argument.sources_count(): index = argument.sources_count() else: index = 0 self.selected_argument = (argument, index) def run(self, *args): """This method is called with attached arguments. Method must not have any side effect and it must not modifies its input arguments. """ return None def execute(self, app, store_results=True): args = [argument.get_data() for argument in self.arguments] results = self.run(app, *args) if not store_results: return results if results is None: return try: sources = list(results) except TypeError: sources = [results] for source in sources: app.sources_repository.add(source) return results def attach_source(self, source): argument, index = self.selected_argument if argument is not None and argument.type == source.type: argument.attach_source(source, index) return for argument in self.arguments: if (source.type == argument.type and (argument.is_empty() or argument.is_list())): argument.attach_source(source) return # not attached source self.emit_event("no-free-slot", source) def all_sources_filled(self): for argument in self.arguments: count = 0 for idx in xrange(argument.sources_count()): src = argument.get_source(idx) if src is not None and src.data is not None: count += 1 if count < argument.minimum: return False return True def deregister_callbacks(self): self.events.remove_all() def _cb_argument_changed(self): if self.all_sources_filled(): self.state = "ready" else: self.state = "incomplete"
class ArgumentView(gtk.Table, EventSource): def __init__(self, argument): gtk.Table.__init__(self, 1, 4, False) EventSource.__init__(self) self.entries = [] self.set_border_width(2) self.argument = argument self.events = EventCallbacksList() self.events.set_callback(self.argument, "argument-changed", self._cb_argument_changed) self.events.set_callback(self.argument, "source-name-changed", self._cb_source_name_changed) # initialize view self._cb_argument_changed() def deregister_callbacks(self): self.event.remove() def _cb_argument_changed(self): # remove self.argument.remove_src_name_changed_callbacks() for child in self.get_children(): self.remove(child) self.entries = [] # create actualized view rows = self.argument.sources_count() + 1 columns = 4 self.resize(rows, columns) label = gtk.Label() label.set_alignment(0, 0.5) label.set_markup("<b>{0}</b>".format(self.argument.name)) self.attach(label, 0, 1, 0, 1, xoptions=0) label = gtk.Label() label.set_alignment(0, 0.5) label.set_markup(" ({0})".format(self.argument.type.short_name)) self.attach(label, 1, 2, 0, 1) until = 1 if self.argument.is_list(): until = self.argument.sources_count() + 1 if self.argument.minimum > self.argument.sources_count(): until = self.argument.minimum for i in xrange(until): entry = gtk.Entry() entry.set_editable(False) entry.connect("focus-in-event", self._cb_choose_argument, i) attached_source = self.argument.get_source(i) if attached_source is not None: # reset callback, make right index self.argument.set_src_name_changed_callback(attached_source, i) entry.set_text(attached_source.name) entry.set_sensitive(attached_source.data is not None) self.attach(entry, 2, 3, i, i + 1, xoptions=gtk.FILL) self.entries.append(entry) button = gtk.Button("Detach") button.set_sensitive(attached_source is not None) button.connect("clicked", lambda w, index: self._cb_detach_source(index), i) self.attach(button, 3, 4, i, i + 1, xoptions=0) self.show_all() def _cb_source_name_changed(self, idx, old_name, new_name): self.entries[idx].set_text(new_name) def _cb_detach_source(self, index): self.argument.detach_source(index) self.emit_event("detach-source", self.argument.get_source(index)) def _cb_choose_argument(self, widget, event, index): self.emit_event("filter-sources", [self.argument.type]) self.argument.emit_event("select-argument", index)
class Argument(object, EventSource): def __init__(self, parameter): EventSource.__init__(self) self._parameter = parameter self._src_name_changed_events = EventCallbacksList() self._real_attached = 0 self._sources = [None] * self._parameter.minimum @property def name(self): return self._parameter.name @property def type(self): return self._parameter.type @property def minimum(self): return self._parameter.minimum def is_list(self): return self._parameter.list def is_empty(self): return self._real_attached == 0 def sources_count(self): """Return a number of real attached sources, without respect to a minimum count. """ return self._real_attached def get_source(self, index=-1): """Return a chosen source. Keyword arguments: index -- index of chosen source (default -1; last added) """ if not self._sources or index >= len(self._sources): return None else: return self._sources[index] def attach_source(self, source, index=None): old_real_attached = self._real_attached if index is None: # attach attached = False for i, s in enumerate(self._sources): if s is None: # fill required sources firstly self._sources[i] = source attached = True break if not attached: self._sources.append(source) self._real_attached += 1 else: assert (index >= 0) if index < len(self._sources): if self._sources[index] is None: # increase only if the source is None, in the other case # it is only exchange of attached object self._real_attached += 1 self._sources[index] = source else: self._sources.append(source) self._real_attached += 1 if old_real_attached < self._real_attached: self.set_src_name_changed_callback(source, self._real_attached - 1) self.emit_event("argument-changed") def detach_source(self, index=0): if 0 <= index < len(self._sources): if len(self._sources) - self.minimum <= 0: # minimal count of arguments remain visible self._sources.append(None) self._sources.pop(index) self._real_attached -= 1 self.emit_event("argument-changed") def get_data(self): if self.is_list(): return [ source.data for source in self._sources[:self._real_attached] ] else: return self._sources[0].data def set_src_name_changed_callback(self, source, index): self._src_name_changed_events.set_callback( source, "source-name-changed", self._cb_source_name_changed, index) def remove_src_name_changed_callbacks(self): self._src_name_changed_events.remove_all() def _cb_source_name_changed(self, idx, old_name, new_name): self.emit_event("source-name-changed", idx, old_name, new_name)
class SourcesRepositoryView(gtk.VBox, EventSource): def __init__(self, repository, app): gtk.VBox.__init__(self) EventSource.__init__(self) self.repository = repository self.events = EventCallbacksList() self.events.set_callback(self.repository, "source-added", self._cb_source_added) self.events.set_callback(self.repository, "source-removed", self._cb_source_removed) self.app = app self.sources_views = {} # (source, source_view) sources = self.repository.get_sources() for source in sources: self._cb_source_added(source) def set_filter(self, filter): show_sources = self.repository.get_sources(filter) for source, source_view in self.sources_views.items(): if source in show_sources: source_view.show_all() else: source_view.hide_all() return (len(show_sources), len(self.repository)) def deregister_callbacks(self): for source in self.repository.get_sources(None): source_view = self.sources_views[source] source_view.remove_callback("attach-source", self._cb_attach_source) source_view.remove_callback("delete-source", self._cb_delete_source) self.events.remove_all() def _cb_source_added(self, source): source_view = SourceView(source, self.app) source_view.set_callback("attach-source", self._cb_attach_source) source_view.set_callback("delete-source", self._cb_delete_source) source_view.set_callback("source-data-changed", self._cb_data_changed) self.pack_start(source_view, False, False) source_view.show_all() self.sources_views[source] = source_view def _cb_source_removed(self, source): source_view = self.sources_views[source] source_view.remove_callback("attach-source", self._cb_attach_source) source_view.remove_callback("delete-source", self._cb_delete_source) source_view.remove_callback("source-data-changed", self._cb_data_changed) self.remove(source_view) def _cb_attach_source(self, source): self.emit_event("attach-source", source) def _cb_data_changed(self, source): self.emit_event("source-data-changed", source) def _cb_delete_source(self, source): self.repository.remove(source)