def _launch_refine_thread(self, refiner, gui_timeout_id): @run_when_idle def thread_completed(*args, **kwargs): """ Called when the refinement is completed """ self.thread = None gobject.source_remove(gui_timeout_id) self.view.stop_spinner() # Make some plots: if self.model.make_psp_plots: self.view.update_refinement_status("Processing...") self.results_controller.generate_images() # Set the labels: self.results_controller.update_labels() # Hide our shit: self.view.hide_refinement_info() self.view.hide() # Show results: self.results_view.present() thread = CancellableThread(refiner.refine, thread_completed) thread.start() return thread
def refine(self, threaded=False, on_complete=None, **kwargs): """ This refines the selected properties using the selected algorithm. This can be run asynchronously when threaded is set to True. """ refine_method = partial(self._inner_refine, self.get_refinement_method(), self.context, **kwargs) if not threaded: context = refine_method() if callable(on_complete): on_complete(context) else: def thread_completed(context): #Assuming this is GTK-thread safe (i.e. wrapped in @run_when_idle) on_complete(context) self.thread = None self.thread = CancellableThread(refine_method, thread_completed) self.thread.start()
class ProjectController(ObjectListStoreController): treemodel_property_name = "specimens" treemodel_class_type = Specimen columns = [] delete_msg = "Deleting a specimen is irreversible!\nAre You sure you want to continue?" auto_adapt = True def register_view(self, view): if view is not None and self.model is not None: if self.parent is not None: # is this still needed? tv = self.view["project_specimens"] tv.set_model(self.treemodel) self.view.treeview = tv self.view.set_x_range_sensitive(self.model.axes_xlimit == 1) self.view.set_y_range_sensitive(self.model.axes_ylimit == 1) return def _idle_register_view(self, *args, **kwargs): super(ProjectController, self)._idle_register_view(*args, **kwargs) def adapt(self, *args, **kwargs): super(ProjectController, self).adapt(*args, **kwargs) def setup_treeview(self, widget): super(ProjectController, self).setup_treeview(widget) store = self.treemodel widget.connect('button-press-event', self.specimen_tv_button_press) # First reset & then (re)create the columns of the treeview: for col in widget.get_columns(): widget.remove_column(col) # Name column: col = new_text_column('Name', text_col=store.c_name, min_width=125, xalign=0.0, ellipsize=pango.ELLIPSIZE_END) col.set_data("colnr", store.c_name) widget.append_column(col) # Check boxes: def toggle_renderer(column, cell, model, itr, data=None): active = False if model.iter_is_valid(itr): col = column.get_col_attr("active") active = model.get_value(itr, col) cell.set_property('active', active) return def setup_check_column(title, colnr): col = new_toggle_column(title, toggled_callback=(self.specimen_tv_toggled, (store, colnr)), data_func=toggle_renderer, resizable=False, expand=False, activatable=True, active_col=colnr) col.set_data("colnr", colnr) widget.append_column(col) setup_check_column('Exp', store.c_display_experimental) if self.model.layout_mode == "FULL": setup_check_column('Cal', store.c_display_calculated) setup_check_column('Sep', store.c_display_phases) # Up and down arrows: def setup_image_button(image, colnr): col = new_pb_column("", resizable=False, expand=False, stock_id=image) col.set_data("colnr", colnr) widget.append_column(col) setup_image_button("213-up-arrow", 501) setup_image_button("212-down-arrow", 502) def edit_object(self, obj): pass # clear this method, we're not having an 'edit' view pane... @BaseController.status_message("Importing multiple specimens...", "add_specimen") def import_multiple_specimen(self): def on_accept(dialog): ## TODO MOVE THIS (PARTIALLY?) TO THE MODEL LEVEL ## filenames = dialog.get_filenames() parser = dialog.get_filter().get_data("parser") task = ThreadedTaskBox() window = DialogFactory.get_custom_dialog( task, parent=self.view.get_top_widget()) # Status: status_dict = dict(total_files=len(filenames), current_file=0, specimens=[]) # Task: def load_specimens(stop=None): for filename in filenames: if stop is not None and stop.is_set(): return try: specimens = Specimen.from_experimental_data( filename=filename, parent=self.model, parser=parser) except Exception as msg: message = "An unexpected error has occurred when trying to parse %s:\n\n<i>" % os.path.basename( filename) message += str(msg) + "</i>\n\n" message += "This is most likely caused by an invalid or unsupported file format." logger.exception(message) @run_when_idle def run_dialog(): DialogFactory.get_information_dialog( message=message, parent=self.view.get_top_widget()).run() return False run_dialog() else: status_dict["specimens"] += specimens status_dict["current_file"] += 1 # Cancel & stop events: def on_interrupted(*args, **kwargs): window.hide() # Status label update: def gui_callback(): task.set_status( "Loading file %d/%d ..." % (status_dict["current_file"], status_dict["total_files"])) return True gui_timeout_id = gobject.timeout_add(250, gui_callback) # Complete event: @run_when_idle def on_complete(*args, **kwargs): last_iter = None for specimen in status_dict["specimens"]: last_iter = self.model.specimens.append(specimen) if last_iter is not None: self.view["project_specimens"].set_cursor(last_iter) gobject.source_remove(gui_timeout_id) window.hide() window.destroy() # Run task box: task.connect("cancelrequested", on_interrupted) task.connect("stoprequested", on_interrupted) task.set_status("Loading ...") task.start() window.show_all() # Run thread: self.thread = CancellableThread(load_specimens, on_complete) self.thread.start() DialogFactory.get_load_dialog( title="Select XRD files for import", filters=xrd_parsers.get_import_file_filters(), parent=self.view.get_top_widget(), multiple=True).run(on_accept) @BaseController.status_message("Deleting specimen...", "del_specimen") def delete_selected_specimens(self): """ Asks the user for confirmation and if positive deletes all the selected specimens. Does nothing when no specimens are selected. """ selection = self.get_selected_objects() if selection is not None and len(selection) >= 1: def delete_objects(dialog): for obj in selection: if obj is not None: self.model.specimens.remove(obj) DialogFactory.get_confirmation_dialog( message= 'Deleting a specimen is irreversible!\nAre You sure you want to continue?', parent=self.view.get_top_widget()).run(delete_objects) @BaseController.status_message("Removing backgrounds...", "del_bg_specimen") def remove_backgrounds(self, specimens): """ Opens the 'remove background' dialog for the given specimens, raises a ValueError error if (one of) the specimens is not part of this project. """ def on_automated(dialog): for specimen in specimens: if not specimen in self.model.specimens: raise ValueError, "Specimen `%s` is not part of this Project!" % specimen else: specimen.experimental_pattern.bg_type = 0 # Linear see settings specimen.experimental_pattern.find_bg_position() specimen.experimental_pattern.remove_background() specimen.experimental_pattern.clear_bg_variables() def on_not_automated(dialog): for specimen in specimens: if not specimen in self.model.specimens: raise ValueError, "Specimen `%s` is not part of this Project!" % specimen else: bg_view = BackgroundView(parent=self.parent.view) BackgroundController(model=specimen.experimental_pattern, view=bg_view, parent=self) bg_view.present() # Ask user if he/she wants automation: DialogFactory.get_confirmation_dialog( "Do you want to perform an automated linear background subtraction?", parent=self.parent.view.get_top_widget()).run( on_automated, on_not_automated) def edit_specimen(self): selection = self.get_selected_objects() if selection is not None and len(selection) == 1: # TODO move the specimen view & controller into the project level self.parent.view.specimen.present() @BaseController.status_message("Creating new specimen...", "add_specimen") def add_specimen(self): specimen = Specimen(parent=self.model, name="New Specimen") self.model.specimens.append(specimen) self.view.specimens_treeview.set_cursor( self.treemodel.on_get_path(specimen)) self.edit_specimen() return True @contextmanager def _multi_operation_context(self): with self.model.hold_mixtures_data_changed(): with self.model.data_changed.hold(): yield # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @Controller.observe("name", assign=True) def notif_change_name(self, model, prop_name, info): self.parent.update_title() return @Controller.observe("axes_xlimit", assign=True) def notif_xlimit_toggled(self, model, prop_name, info): self.view.set_x_range_sensitive(int(self.model.axes_xlimit) == 1) @Controller.observe("axes_ylimit", assign=True) def notif_ylimit_toggled(self, model, prop_name, info): self.view.set_y_range_sensitive(int(self.model.axes_ylimit) == 1) @Controller.observe("layout_mode", assign=True) def notif_layout_mode(self, model, prop_name, info): self.parent.set_layout_mode(self.model.layout_mode) if self.view is not None: self.setup_treeview(self.view.treeview) # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def specimen_tv_toggled(self, cell, path, model, colnr): if model is not None: itr = model.get_iter(path) model.set_value(itr, colnr, not cell.get_active()) return True return False def specimen_tv_button_press(self, tv, event): specimen = None current_specimens = self.parent.model.current_specimens or [] ret = tv.get_path_at_pos(int(event.x), int(event.y)) if ret is not None: path, col, x, y = ret specimen = self.treemodel.get_user_data_from_path(path) # FIXME if event.button == 3: if specimen is not None: # clicked a specimen which is not in the current selection, # so clear selection and select it if not specimen in current_specimens: self.select_object(specimen) else: # clicked an empty space, so clear selection self.select_object(None) self.view.show_specimens_context_menu(event) return True elif event.type == gtk.gdk._2BUTTON_PRESS and specimen is not None and col.get_data( "colnr") == self.treemodel.c_name: # @UndefinedVariable self.parent.on_edit_specimen_activate(event) return True elif (event.button == 1 or event.type == gtk.gdk._2BUTTON_PRESS ) and specimen is not None: # @UndefinedVariable column = col.get_data("colnr") if column in (self.treemodel.c_display_experimental, self.treemodel.c_display_calculated, self.treemodel.c_display_phases): if column == self.treemodel.c_display_experimental: specimen.display_experimental = not specimen.display_experimental elif column == self.treemodel.c_display_calculated: specimen.display_calculated = not specimen.display_calculated elif column == self.treemodel.c_display_phases: specimen.display_phases = not specimen.display_phases # TODO FIXME self.treemodel.on_row_changed(ret) return True elif column == 501: self.model.move_specimen_down(specimen) self.parent.model.current_specimens = self.get_selected_objects( ) return True elif column == 502: self.model.move_specimen_up(specimen) self.parent.model.current_specimens = self.get_selected_objects( ) return True def objects_tv_selection_changed(self, selection): ObjectListStoreController.objects_tv_selection_changed(self, selection) self.parent.model.current_specimens = self.get_selected_objects() return True pass # end of class
def on_accept(dialog): ## TODO MOVE THIS (PARTIALLY?) TO THE MODEL LEVEL ## filenames = dialog.get_filenames() parser = dialog.get_filter().get_data("parser") task = ThreadedTaskBox() window = DialogFactory.get_custom_dialog( task, parent=self.view.get_top_widget()) # Status: status_dict = dict(total_files=len(filenames), current_file=0, specimens=[]) # Task: def load_specimens(stop=None): for filename in filenames: if stop is not None and stop.is_set(): return try: specimens = Specimen.from_experimental_data( filename=filename, parent=self.model, parser=parser) except Exception as msg: message = "An unexpected error has occurred when trying to parse %s:\n\n<i>" % os.path.basename( filename) message += str(msg) + "</i>\n\n" message += "This is most likely caused by an invalid or unsupported file format." logger.exception(message) @run_when_idle def run_dialog(): DialogFactory.get_information_dialog( message=message, parent=self.view.get_top_widget()).run() return False run_dialog() else: status_dict["specimens"] += specimens status_dict["current_file"] += 1 # Cancel & stop events: def on_interrupted(*args, **kwargs): window.hide() # Status label update: def gui_callback(): task.set_status( "Loading file %d/%d ..." % (status_dict["current_file"], status_dict["total_files"])) return True gui_timeout_id = gobject.timeout_add(250, gui_callback) # Complete event: @run_when_idle def on_complete(*args, **kwargs): last_iter = None for specimen in status_dict["specimens"]: last_iter = self.model.specimens.append(specimen) if last_iter is not None: self.view["project_specimens"].set_cursor(last_iter) gobject.source_remove(gui_timeout_id) window.hide() window.destroy() # Run task box: task.connect("cancelrequested", on_interrupted) task.connect("stoprequested", on_interrupted) task.set_status("Loading ...") task.start() window.show_all() # Run thread: self.thread = CancellableThread(load_specimens, on_complete) self.thread.start()
class Refiner(ChildModel): """ A simple model that plugs onto the Mixture model. It provides the functionality related to refinement of parameters. """ # MODEL INTEL: class Meta(ChildModel.Meta): properties = [ # TODO add labels PropIntel(name="refinables", label="", has_widget=True, data_type=object, is_column=True, widget_type="object_tree_view", class_type=RefinableWrapper), PropIntel(name="refine_options", label="", data_type=dict, is_column=False), OptionPropIntel(name="refine_method", label="Refinement method", has_widget=True, data_type=int, options={ key: method.name for key, method in get_all_refine_methods().iteritems() }), PropIntel(name="make_psp_plots", label="", data_type=bool, is_colum=False, has_widget=True, storable=False), ] store_id = "Refiner" mixture = property(ChildModel.parent.fget, ChildModel.parent.fset) #: Refinement context context = None #: Refinement thread (or None if not running) thread = None #: Flag, True if after refinement plots should be generated of the parameter space make_psp_plots = False #: TreeNode containing the refinable properties refinables = None #: A dict containing an instance of each refinement method refine_methods = None _refine_method = 0 @property def refine_method(self): """ An integer describing which method to use for the refinement (see refinement.methods.get_all_refine_methods) """ return self._refine_method @refine_method.setter def refine_method(self, value): self._refine_method = int(value) #: A dict containing the current refinement options @property def refine_options(self): method = self.get_refinement_method() return { name: getattr(method, name) for name in method.options } #: A dict containing all refinement options @property def all_refine_options(self): return { method.index : { name: getattr(method, name) for name in method.options } for method in self.refine_methods.values() } def __init__(self, *args, **kwargs): my_kwargs = self.pop_kwargs(kwargs, "refine_method", "refine_options" ) super(Refiner, self).__init__(*args, **kwargs) kwargs = my_kwargs # Setup the refinables treestore self.refinables = TreeNode() # Setup the refine methods try: self.refine_method = int(self.get_kwarg(kwargs, None, "refine_method")) except ValueError: self.refine_method = self.refine_method pass # ignore faulty values, these indices change from time to time. self.refine_methods = self.create_refine_methods(self.get_kwarg(kwargs, None, "refine_options")) self.update_refinement_treestore() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def setup_context(self, store=False): """ Creates a RefineContext object filled with parameters based on the current state of the Mixture object. """ self.context = RefineContext( parent=self.parent, options=self.parent.refine_options, store=store ) def delete_context(self): """ Clears the RefineContext from this model """ self.context = None def _inner_refine(self, refine_method, context, stop=None, **kwargs): # Suppress updates: with self.mixture.needs_update.hold(): with self.mixture.data_changed.hold(): # If something has been selected: continue... if len(context.ref_props) > 0: # Make sure the stop signal is not set from a previous run: if stop is not None: stop.clear() # Log some information: logger.info("-"*80) logger.info("Starting refinement with this setup:") msg_frm = "%22s: %s" logger.info(msg_frm % ("refinement method", refine_method)) logger.info(msg_frm % ("number of parameters", len(context.ref_props))) logger.info(msg_frm % ("GUI mode", settings.GUI_MODE)) # Record start time t1 = time.time() try: # Run until it ends or it raises an exception: refine_method(context, stop=stop) except any as error: logger.exception("Unhandled run-time error when refining: %s" % error) context.status = "error" context.status_message = "Error occurred..." else: # No errors occurred: if stop is not None and stop.is_set(): context.status = "stopped" context.status_message = "Stopped ..." logger.info("Refinement was stopped prematurely") else: context.status = "finished" context.status_message = "Finished" logger.info("Refinement ended successfully") # Record end time t2 = time.time() # Log some more information: logger.info('%s took %0.3f ms' % ("Total refinement", (t2 - t1) * 1000.0)) logger.info('Best solution found was:') for line in context.best_solution_to_string().split('\n'): logger.info(line) logger.info("-"*80) else: # nothing selected for refinement context.status = "error" context.status_message = "No parameters selected!" # Return the context to whatever called this return context def refine(self, threaded=False, on_complete=None, **kwargs): """ This refines the selected properties using the selected algorithm. This can be run asynchronously when threaded is set to True. """ refine_method = partial(self._inner_refine, self.get_refinement_method(), self.context, **kwargs) if not threaded: context = refine_method() if callable(on_complete): on_complete(context) else: def thread_completed(context): #Assuming this is GTK-thread safe (i.e. wrapped in @run_when_idle) on_complete(context) self.thread = None self.thread = CancellableThread(refine_method, thread_completed) self.thread.start() def cancel(self): """ Cancels a threaded refinement, and will call the on_complete callback passed to `refine` """ if self.thread is not None: logger.info("Refinement cancelled") self.thread.cancel() else: logger.info("Cannot cancel, no refinement running") self.thread = None def stop(self): """ Stops a threaded refinement, not returning any result """ if self.thread is not None: logger.info("Refinement stopped") self.thread.stop() else: logger.info("Cannot stop, no refinement running") self.thread = None # ------------------------------------------------------------ # Refinement Methods Management # ------------------------------------------------------------ @staticmethod def get_all_refine_methods(): return get_all_refine_methods() @staticmethod def create_refine_methods(refine_options): """ Returns a dict of refine methods as values and their index as key with the passed refine_options dict applied. """ # 1. Create a list of refinement instances: refine_methods = {} for index, method in get_all_refine_methods().iteritems(): refine_methods[index] = method() # 2. Create dict of default options default_options = {} for method in refine_methods.values(): default_options[method.index] = { name: getattr(type(method), name).default for name in method.options } # 3. Apply the refine options to the methods if not refine_options == None: for index, options in zip(refine_options.keys(), refine_options.values()): index = int(index) if index in refine_methods: method = refine_methods[index] for arg, value in zip(options.keys(), options.values()): if hasattr(method, arg): setattr(method, arg, value) return refine_methods def get_refinement_method(self): """ Returns the actual refinement method by translating the `refine_method` attribute """ return self.refine_methods[self.refine_method] def get_refinement_option(self, option): return getattr(type(self.get_refinement_method()), option) def get_refinement_option_value(self, option): return getattr(self.get_refinement_method(), option) def set_refinement_option_value(self, option, value): return setattr(self.get_refinement_method(), option, value) # ------------------------------------------------------------ # Refinables Management # ------------------------------------------------------------ # TODO set a restrict range attribute on the PropIntels, so we can use custom ranges for each property def auto_restrict(self): """ Convenience function that restricts the selected properties automatically by setting their minimum and maximum values. """ with self.mixture.needs_update.hold(): for node in self.refinables.iter_children(): ref_prop = node.object if ref_prop.refine and ref_prop.refinable: ref_prop.value_min = ref_prop.value * 0.8 ref_prop.value_max = ref_prop.value * 1.2 def randomize(self): """ Convenience function that randomize the selected properties. Respects the current minimum and maximum values. Executes an optimization after the randomization. """ with self.mixture.data_changed.hold_and_emit(): with self.mixture.needs_update.hold_and_emit(): for node in self.refinables.iter_children(): ref_prop = node.object if ref_prop.refine and ref_prop.refinable: ref_prop.value = random.uniform(ref_prop.value_min, ref_prop.value_max) def update_refinement_treestore(self): """ This creates a tree store with all refinable properties and their minimum, maximum and current value. """ if self.parent is not None: # not linked so no valid phases! self.refinables.clear() def add_property(parent_node, obj, prop, is_grouper): rp = RefinableWrapper(obj=obj, prop=prop, parent=self.mixture, is_grouper=is_grouper) return parent_node.append(TreeNode(rp)) def parse_attribute(obj, prop, root_node): """ obj: the object attr: the attribute of obj or None if obj contains attributes root_node: the root TreeNode new iters should be put under """ if prop is not None: if hasattr(obj, "get_uninherited_property_value"): value = obj.get_uninherited_property_value(prop) else: value = getattr(obj, prop.name) else: value = obj if isinstance(value, RefinementValue): # AtomRelation and UnitCellProperty new_node = add_property(root_node, value, prop, False) elif hasattr(value, "__iter__"): # List or similar for new_obj in value: parse_attribute(new_obj, None, root_node) elif isinstance(value, RefinementGroup): # Phase, Component, Probability if len(value.refinables) > 0: new_node = add_property(root_node, value, prop, True) for prop in value.refinables: parse_attribute(value, prop, new_node) else: # regular values new_node = add_property(root_node, obj, prop, False) for phase in self.mixture.project.phases: if phase in self.mixture.phase_matrix: parse_attribute(phase, None, self.refinables) pass # end of class
class ProjectController(ObjectListStoreController): treemodel_property_name = "specimens" treemodel_class_type = Specimen columns = [ ] delete_msg = "Deleting a specimen is irreversible!\nAre You sure you want to continue?" auto_adapt = True file_filters = Specimen.Meta.file_filters def register_view(self, view): if view is not None and self.model is not None: if self.parent is not None: # is this still needed? tv = self.view["project_specimens"] tv.set_model(self.treemodel) self.view.treeview = tv self.view.set_x_range_sensitive(self.model.axes_xlimit == 1) self.view.set_y_range_sensitive(self.model.axes_ylimit == 1) return def _idle_register_view(self, *args, **kwargs): super(ProjectController, self)._idle_register_view(*args, **kwargs) def adapt(self, *args, **kwargs): super(ProjectController, self).adapt(*args, **kwargs) def setup_treeview(self, widget): super(ProjectController, self).setup_treeview(widget) store = self.treemodel widget.connect('button-press-event', self.specimen_tv_button_press) # First reset & then (re)create the columns of the treeview: for col in widget.get_columns(): widget.remove_column(col) # Name column: col = new_text_column('Name', text_col=store.c_name, min_width=125, xalign=0.0, ellipsize=pango.ELLIPSIZE_END) col.set_data("colnr", store.c_name) widget.append_column(col) # Check boxes: def toggle_renderer(column, cell, model, itr, data=None): active = False if model.iter_is_valid(itr): col = column.get_col_attr("active") active = model.get_value(itr, col) cell.set_property('active', active) return def setup_check_column(title, colnr): col = new_toggle_column(title, toggled_callback=(self.specimen_tv_toggled, (store, colnr)), data_func=toggle_renderer, resizable=False, expand=False, activatable=True, active_col=colnr) col.set_data("colnr", colnr) widget.append_column(col) setup_check_column('Exp', store.c_display_experimental) if self.model.layout_mode == "FULL": setup_check_column('Cal', store.c_display_calculated) setup_check_column('Sep', store.c_display_phases) # Up and down arrows: def setup_image_button(image, colnr): col = new_pb_column("", resizable=False, expand=False, stock_id=image) col.set_data("colnr", colnr) widget.append_column(col) setup_image_button("213-up-arrow", 501) setup_image_button("212-down-arrow", 502) def edit_object(self, obj): pass # clear this method, we're not having an 'edit' view pane... @BaseController.status_message("Importing multiple specimens...", "add_specimen") def import_multiple_specimen(self): def on_accept(dialog): ## TODO MOVE THIS (PARTIALLY?) TO THE MODEL LEVEL ## filenames = dialog.get_filenames() parser = dialog.get_filter().get_data("parser") task = ThreadedTaskBox() window = self.get_custom_dialog(task, parent=self.view.get_top_widget()) # Status: status_dict = dict( total_files=len(filenames), current_file=0, specimens=[] ) # Task: def load_specimens(stop=None): for filename in filenames: if stop is not None and stop.is_set(): return try: specimens = Specimen.from_experimental_data(filename=filename, parent=self.model, parser=parser) except Exception as msg: message = "An unexpected error has occurred when trying to parse %s:\n\n<i>" % os.path.basename(filename) message += str(msg) + "</i>\n\n" message += "This is most likely caused by an invalid or unsupported file format." logger.exception(message) @run_when_idle def run_dialog(): self.run_information_dialog( message=message, parent=self.view.get_top_widget() ) return False run_dialog() else: status_dict["specimens"] += specimens status_dict["current_file"] += 1 # Cancel & stop events: def on_interrupted(*args, **kwargs): window.hide() # Status label update: def gui_callback(): task.set_status("Loading file %d/%d ..." % ( status_dict["current_file"], status_dict["total_files"] )) return True gui_timeout_id = gobject.timeout_add(250, gui_callback) # Complete event: @run_when_idle def on_complete(*args, **kwargs): last_iter = None for specimen in status_dict["specimens"]: last_iter = self.model.specimens.append(specimen) if last_iter is not None: self.view["project_specimens"].set_cursor(last_iter) gobject.source_remove(gui_timeout_id) window.hide() window.destroy() # Run task box: task.connect("cancelrequested", on_interrupted) task.connect("stoprequested", on_interrupted) task.set_status("Loading ...") task.start() window.show_all() # Run thread: self.thread = CancellableThread(load_specimens, on_complete) self.thread.start() self.run_load_dialog(title="Select XRD files for import", on_accept_callback=on_accept, parent=self.view.get_top_widget(), multiple=True) @BaseController.status_message("Deleting specimen...", "del_specimen") def delete_selected_specimens(self): """ Asks the user for confirmation and if positive deletes all the selected specimens. Does nothing when no specimens are selected. """ selection = self.get_selected_objects() if selection is not None and len(selection) >= 1: def delete_objects(dialog): for obj in selection: if obj is not None: self.model.specimens.remove(obj) self.run_confirmation_dialog( message='Deleting a specimen is irreversible!\nAre You sure you want to continue?', on_accept_callback=delete_objects, parent=self.view.get_top_widget()) @BaseController.status_message("Removing backgrounds...", "del_bg_specimen") def remove_backgrounds(self, specimens): """ Opens the 'remove background' dialog for the given specimens, raises a ValueError error if (one of) the specimens is not part of this project. """ def on_automated(dialog): for specimen in specimens: if not specimen in self.model.specimens: raise ValueError, "Specimen `%s` is not part of this Project!" % specimen else: specimen.experimental_pattern.bg_type = 0 # Linear see settings specimen.experimental_pattern.find_bg_position() specimen.experimental_pattern.remove_background() specimen.experimental_pattern.clear_bg_variables() def on_not_automated(dialog): for specimen in specimens: if not specimen in self.model.specimens: raise ValueError, "Specimen `%s` is not part of this Project!" % specimen else: bg_view = BackgroundView(parent=self.parent.view) BackgroundController(model=specimen.experimental_pattern, view=bg_view, parent=self) bg_view.present() # Ask user if he/she wants automation: self.run_confirmation_dialog( "Do you want to perform an automated linear background subtraction?", on_automated, on_not_automated, parent=self.parent.view.get_top_widget() ) def edit_specimen(self): selection = self.get_selected_objects() if selection is not None and len(selection) == 1: # TODO move the specimen view & controller into the project level self.parent.view.specimen.present() @BaseController.status_message("Creating new specimen...", "add_specimen") def add_specimen(self): specimen = Specimen(parent=self.model, name="New Specimen") self.model.specimens.append(specimen) self.view.specimens_treeview.set_cursor(self.treemodel.on_get_path(specimen)) self.edit_specimen() return True @contextmanager def _multi_operation_context(self): with self.model.hold_mixtures_data_changed(): with self.model.data_changed.hold(): yield # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @Controller.observe("name", assign=True) def notif_change_name(self, model, prop_name, info): self.parent.update_title() return @Controller.observe("axes_xlimit", assign=True) def notif_xlimit_toggled(self, model, prop_name, info): self.view.set_x_range_sensitive(int(self.model.axes_xlimit) == 1) @Controller.observe("axes_ylimit", assign=True) def notif_ylimit_toggled(self, model, prop_name, info): self.view.set_y_range_sensitive(int(self.model.axes_ylimit) == 1) @Controller.observe("layout_mode", assign=True) def notif_layout_mode(self, model, prop_name, info): self.parent.set_layout_mode(self.model.layout_mode) if self.view is not None: self.setup_treeview(self.view.treeview) # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def specimen_tv_toggled(self, cell, path, model, colnr): if model is not None: itr = model.get_iter(path) model.set_value(itr, colnr, not cell.get_active()) return True return False def specimen_tv_button_press(self, tv, event): specimen = None current_specimens = self.parent.model.current_specimens or [] ret = tv.get_path_at_pos(int(event.x), int(event.y)) if ret is not None: path, col, x, y = ret specimen = self.treemodel.get_user_data_from_path(path) # FIXME if event.button == 3: if specimen is not None: # clicked a specimen which is not in the current selection, # so clear selection and select it if not specimen in current_specimens: self.select_object(specimen) else: # clicked an empty space, so clear selection self.select_object(None) self.view.show_specimens_context_menu(event) return True elif event.type == gtk.gdk._2BUTTON_PRESS and specimen is not None and col.get_data("colnr") == self.treemodel.c_name: self.parent.on_edit_specimen_activate(event) return True elif (event.button == 1 or event.type == gtk.gdk._2BUTTON_PRESS) and specimen is not None: column = col.get_data("colnr") if column in (self.treemodel.c_display_experimental, self.treemodel.c_display_calculated, self.treemodel.c_display_phases): if column == self.treemodel.c_display_experimental: specimen.display_experimental = not specimen.display_experimental elif column == self.treemodel.c_display_calculated: specimen.display_calculated = not specimen.display_calculated elif column == self.treemodel.c_display_phases: specimen.display_phases = not specimen.display_phases # TODO FIXME self.treemodel.on_row_changed(ret) return True elif column == 501: self.model.move_specimen_down(specimen) self.parent.model.current_specimens = self.get_selected_objects() return True elif column == 502: self.model.move_specimen_up(specimen) self.parent.model.current_specimens = self.get_selected_objects() return True def objects_tv_selection_changed(self, selection): ObjectListStoreController.objects_tv_selection_changed(self, selection) self.parent.model.current_specimens = self.get_selected_objects() return True pass # end of class
def on_accept(dialog): ## TODO MOVE THIS (PARTIALLY?) TO THE MODEL LEVEL ## filenames = dialog.get_filenames() parser = dialog.get_filter().get_data("parser") task = ThreadedTaskBox() window = self.get_custom_dialog(task, parent=self.view.get_top_widget()) # Status: status_dict = dict( total_files=len(filenames), current_file=0, specimens=[] ) # Task: def load_specimens(stop=None): for filename in filenames: if stop is not None and stop.is_set(): return try: specimens = Specimen.from_experimental_data(filename=filename, parent=self.model, parser=parser) except Exception as msg: message = "An unexpected error has occurred when trying to parse %s:\n\n<i>" % os.path.basename(filename) message += str(msg) + "</i>\n\n" message += "This is most likely caused by an invalid or unsupported file format." logger.exception(message) @run_when_idle def run_dialog(): self.run_information_dialog( message=message, parent=self.view.get_top_widget() ) return False run_dialog() else: status_dict["specimens"] += specimens status_dict["current_file"] += 1 # Cancel & stop events: def on_interrupted(*args, **kwargs): window.hide() # Status label update: def gui_callback(): task.set_status("Loading file %d/%d ..." % ( status_dict["current_file"], status_dict["total_files"] )) return True gui_timeout_id = gobject.timeout_add(250, gui_callback) # Complete event: @run_when_idle def on_complete(*args, **kwargs): last_iter = None for specimen in status_dict["specimens"]: last_iter = self.model.specimens.append(specimen) if last_iter is not None: self.view["project_specimens"].set_cursor(last_iter) gobject.source_remove(gui_timeout_id) window.hide() window.destroy() # Run task box: task.connect("cancelrequested", on_interrupted) task.connect("stoprequested", on_interrupted) task.set_status("Loading ...") task.start() window.show_all() # Run thread: self.thread = CancellableThread(load_specimens, on_complete) self.thread.start()