Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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