Exemple #1
0
    def cancel_analyses(self, *_) -> None:
        if hasattr(self, 'cancel_dialog'): return

        self.cancel_dialog = YesNoDialog(
            parent=self.host,
            message_format='Confirm cancel analysis?',
        )

        def hdl_response(dialog: Gtk.Widget,
                         response: Gtk.ResponseType) -> None:
            del self.cancel_dialog
            dialog.destroy()

            self.progress_helper.disconnect(status_handler_id)

            if response == Gtk.ResponseType.YES:
                self.session.cancel_analyses()

        def hdl_progress_status(*_) -> None:
            if (self.progress_helper.status is
                    ConanAnalysisProgressHelper.Status.FINISHED
                    or self.progress_helper.status is
                    ConanAnalysisProgressHelper.Status.CANCELLED):
                self.cancel_dialog.close()

        # Close dialog if analysis finishes or cancels before user responds.
        status_handler_id = self.progress_helper.connect(
            'notify::status', hdl_progress_status)

        self.cancel_dialog.connect('response', hdl_response)

        self.cancel_dialog.show()
Exemple #2
0
    def show_confirm_discard_dialog(self) -> None:
        if self._confirm_discard_dialog is not None:
            return

        self._confirm_discard_dialog = YesNoDialog(
            message_format='Discard unsaved results?',
            parent=self._window,
        )

        self._confirm_discard_dialog.connect('delete-event', lambda *_: True)
        self._confirm_discard_dialog.connect(
            'response', self._hdl_confirm_discard_dialog_response)

        self._confirm_discard_dialog.show()
Exemple #3
0
    def show_confirm_overwrite_dialog(self, path: Path) -> None:
        if self._confirm_overwrite_dialog is not None:
            return

        self._confirm_overwrite_dialog = YesNoDialog(message_format=(
            "This save location '{!s}' already exists, do you want to clear its contents?"
            .format(path)),
                                                     parent=self._window)

        self._confirm_overwrite_dialog.connect(
            'response', self._hdl_confirm_overwrite_dialog_response)
        self._confirm_overwrite_dialog.connect('delete-event', lambda *_: True)

        self._confirm_overwrite_dialog.show()
Exemple #4
0
    def show_confirm_cancel_dialog(self) -> None:
        if self._confirm_cancel_dialog is not None:
            return

        self._confirm_cancel_dialog = YesNoDialog(
            parent=self._get_parent_window(),
            message_format='Confirm cancel analysis?',
        )

        self._confirm_cancel_dialog.connect('delete-event', lambda *_: True)
        self._confirm_cancel_dialog.connect(
            'response', self._hdl_confirm_cancel_dialog_response)

        self._confirm_cancel_dialog.show()
Exemple #5
0
    def close(self, discard_unsaved: bool = False) -> None:
        if hasattr(self, 'confirm_discard_dialog'): return

        if discard_unsaved or self.session.safe_to_discard():
            self.session.quit()
            self.host.destroy()
        else:
            self.confirm_discard_dialog = YesNoDialog(
                message_format='Discard unsaved results?',
                parent=self.host,
            )

            def hdl_response(dialog: Gtk.Dialog,
                             response: Gtk.ResponseType) -> None:
                del self.confirm_discard_dialog
                dialog.destroy()

                if response == Gtk.ResponseType.YES:
                    self.close(True)

            self.confirm_discard_dialog.connect('response', hdl_response)
            self.confirm_discard_dialog.show()
Exemple #6
0
class IFTRootView(View['IFTRootPresenter', Gtk.Widget]):
    def _do_init(self) -> Gtk.Widget:
        self._window = Gtk.Window(
            title='Interfacial Tension',
            window_position=Gtk.WindowPosition.CENTER,
            width_request=800,
            height_request=600,
        )

        _, wizard_area = self.new_component(
            wizard_cs.factory(
                controller=self.presenter.wizard_controller,
                titles=OrderedDict([
                    (
                        PageID.IMAGE_ACQUISITION,
                        'Image acquisition',
                    ),
                    (
                        PageID.PHYS_PARAMS,
                        'Physical parameters',
                    ),
                    (
                        PageID.IMAGE_PROCESSING,
                        'Image processing',
                    ),
                    (
                        PageID.RESULTS,
                        'Results',
                    ),
                ]),
                pages={
                    PageID.IMAGE_ACQUISITION:
                    image_acquisition_cs.factory(
                        model=self.presenter.image_acquisition_model, ),
                    PageID.PHYS_PARAMS:
                    physical_parameters_cs.factory(
                        model=self.presenter.physical_parameters_model),
                    PageID.IMAGE_PROCESSING:
                    ift_image_processing_cs.factory(
                        model=self.presenter.image_processing_model, ),
                    PageID.RESULTS:
                    ift_results_cs.factory(
                        model=self.presenter.results_model, ),
                }))
        wizard_area.show()
        self._window.add(wizard_area)

        self._window.connect('delete-event', self._hdl_window_delete_event)

        self._confirm_discard_dialog = None

        return self._window

    def _hdl_window_delete_event(self, window: Gtk.Window,
                                 data: Gdk.Event) -> bool:
        self.presenter.exit()
        return True

    def show_confirm_discard_dialog(self) -> None:
        if self._confirm_discard_dialog is not None:
            return

        self._confirm_discard_dialog = YesNoDialog(
            message_format='Discard unsaved results?',
            parent=self._window,
        )

        self._confirm_discard_dialog.connect('delete-event', lambda *_: True)
        self._confirm_discard_dialog.connect(
            'response', self._hdl_confirm_discard_dialog_response)

        self._confirm_discard_dialog.show()

    def _hdl_confirm_discard_dialog_response(
            self, widget: Gtk.Dialog, response: Gtk.ResponseType) -> None:
        discard = (response == Gtk.ResponseType.YES)
        self.presenter.hdl_confirm_discard_dialog_response(discard)

    def hide_confirm_discard_dialog(self) -> None:
        if self._confirm_discard_dialog is None:
            return

        self._confirm_discard_dialog.destroy()
        self._confirm_discard_dialog = None

    def _do_destroy(self) -> None:
        self._window.destroy()
Exemple #7
0
class ConanExperimentPresenter(Presenter[Gtk.Assistant]):
    action_area: TemplateChild[Gtk.Stack] = TemplateChild('action_area')
    analysis_footer = TemplateChild('analysis_footer')
    report_page = TemplateChild('report_page')

    @inject
    def __init__(
        self,
        session: ConanSession,
        progress_helper: ConanAnalysisProgressHelper,
        save_params_factory: ConanSaveParamsFactory,
    ) -> None:
        self.session = session
        self.progress_helper = progress_helper
        self.save_params_factory = save_params_factory
        session.bind_property('analyses', self.progress_helper, 'analyses',
                              SYNC_CREATE)

    def after_view_init(self) -> None:
        self.session.bind_property('analyses', self.report_page, 'analyses',
                                   SYNC_CREATE)

        self.progress_helper.bind_property(
            'status', self.analysis_footer, 'status', SYNC_CREATE,
            lambda _, x: {
                ConanAnalysisProgressHelper.Status.ANALYSING: \
                    AnalysisFooterStatus.IN_PROGRESS,
                ConanAnalysisProgressHelper.Status.FINISHED: \
                    AnalysisFooterStatus.FINISHED,
                ConanAnalysisProgressHelper.Status.CANCELLED: \
                    AnalysisFooterStatus.CANCELLED,
            }[x],
            None,
        )
        self.progress_helper.bind_property('fraction', self.analysis_footer,
                                           'progress', SYNC_CREATE)
        self.progress_helper.bind_property('time-start', self.analysis_footer,
                                           'time-start', SYNC_CREATE)

    def prepare(self, *_) -> None:
        cur_page = self.host.get_current_page()
        self.action_area.set_visible_child_name(str(cur_page))

    def next_page(self, *_) -> None:
        cur_page = self.host.get_current_page()
        if cur_page == 0:
            self.host.next_page()
        elif cur_page == 1:
            self.start_analyses()
            self.host.next_page()
        else:
            # Ignore, on last page.
            return

    def previous_page(self, *_) -> None:
        cur_page = self.host.get_current_page()
        if cur_page == 0:
            # Ignore, on first page.
            return
        elif cur_page == 1:
            self.host.previous_page()
        elif cur_page == 2:
            self.clear_analyses()
            self.host.previous_page()

    def start_analyses(self) -> None:
        self.session.start_analyses()

    def clear_analyses(self) -> None:
        self.session.clear_analyses()

    def cancel_analyses(self, *_) -> None:
        if hasattr(self, 'cancel_dialog'): return

        self.cancel_dialog = YesNoDialog(
            parent=self.host,
            message_format='Confirm cancel analysis?',
        )

        def hdl_response(dialog: Gtk.Widget,
                         response: Gtk.ResponseType) -> None:
            del self.cancel_dialog
            dialog.destroy()

            self.progress_helper.disconnect(status_handler_id)

            if response == Gtk.ResponseType.YES:
                self.session.cancel_analyses()

        def hdl_progress_status(*_) -> None:
            if (self.progress_helper.status is
                    ConanAnalysisProgressHelper.Status.FINISHED
                    or self.progress_helper.status is
                    ConanAnalysisProgressHelper.Status.CANCELLED):
                self.cancel_dialog.close()

        # Close dialog if analysis finishes or cancels before user responds.
        status_handler_id = self.progress_helper.connect(
            'notify::status', hdl_progress_status)

        self.cancel_dialog.connect('response', hdl_response)

        self.cancel_dialog.show()

    def save_analyses(self, *_) -> None:
        if hasattr(self, 'save_dialog_component'): return

        save_options = self.save_params_factory

        def hdl_ok() -> None:
            self.save_dialog_component.destroy()
            del self.save_dialog_component
            self.session.save_analyses()

        def hdl_cancel() -> None:
            self.save_dialog_component.destroy()
            del self.save_dialog_component

        self.save_dialog_component = conan_save_dialog_cs.factory(
            parent_window=self.host,
            model=save_options,
            do_ok=hdl_ok,
            do_cancel=hdl_cancel,
        ).create()
        self.save_dialog_component.view_rep.show()

    def close(self, discard_unsaved: bool = False) -> None:
        if hasattr(self, 'confirm_discard_dialog'): return

        if discard_unsaved or self.session.safe_to_discard():
            self.session.quit()
            self.host.destroy()
        else:
            self.confirm_discard_dialog = YesNoDialog(
                message_format='Discard unsaved results?',
                parent=self.host,
            )

            def hdl_response(dialog: Gtk.Dialog,
                             response: Gtk.ResponseType) -> None:
                del self.confirm_discard_dialog
                dialog.destroy()

                if response == Gtk.ResponseType.YES:
                    self.close(True)

            self.confirm_discard_dialog.connect('response', hdl_response)
            self.confirm_discard_dialog.show()

    def delete_event(self, *_) -> bool:
        self.close()
        return True
Exemple #8
0
class ConanSaveDialogView(View['ConanSaveDialogPresenter', Gtk.Window]):
    STYLE = '''
    .small-pad {
         min-height: 0px;
         min-width: 0px;
         padding: 6px 4px 6px 4px;
    }

    .small-combobox .combo {
        min-height: 0px;
        min-width: 0px;
    }

    .conan-analysis-saver-view-footer-button {
        min-height: 0px;
        min-width: 60px;
        padding: 10px 4px 10px 4px;
    }

    .error {
        color: red;
        border: 1px solid red;
    }

    .error-text {
        color: red;
    }
    '''

    _STYLE_PROV = Gtk.CssProvider()
    _STYLE_PROV.load_from_data(bytes(STYLE, 'utf-8'))
    Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(),
                                             _STYLE_PROV,
                                             Gtk.STYLE_PROVIDER_PRIORITY_USER)

    def _do_init(self,
                 parent_window: Optional[Gtk.Window] = None) -> Gtk.Window:
        self._window = Gtk.Window(
            title='Save analysis',
            resizable=False,
            modal=True,
            transient_for=parent_window,
            window_position=Gtk.WindowPosition.CENTER,
        )

        body = Gtk.Grid(margin=10, row_spacing=10)
        self._window.add(body)

        content = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        body.attach(content, 0, 0, 1, 1)

        save_location_frame = Gtk.Frame(label='Save location')
        content.add(save_location_frame)
        save_location_content = Gtk.Grid(margin=10,
                                         column_spacing=10,
                                         row_spacing=5)
        save_location_frame.add(save_location_content)

        save_dir_lbl = Gtk.Label('Parent:', xalign=0)
        save_location_content.attach(save_dir_lbl, 0, 0, 1, 1)

        self._save_dir_parent_inp = Gtk.FileChooserButton(
            action=Gtk.FileChooserAction.SELECT_FOLDER, hexpand=True)
        self._save_dir_parent_inp.get_style_context().add_class(
            'small-combobox')
        save_location_content.attach_next_to(self._save_dir_parent_inp,
                                             save_dir_lbl,
                                             Gtk.PositionType.RIGHT, 1, 1)

        save_dir_parent_err_lbl = Gtk.Label(xalign=0, width_request=190)
        save_dir_parent_err_lbl.get_style_context().add_class('error-text')
        save_location_content.attach_next_to(save_dir_parent_err_lbl,
                                             self._save_dir_parent_inp,
                                             Gtk.PositionType.RIGHT, 1, 1)

        save_name_lbl = Gtk.Label('Name:', xalign=0)
        save_location_content.attach(save_name_lbl, 0, 1, 1, 1)

        save_dir_name_inp = Gtk.Entry()
        save_dir_name_inp.get_style_context().add_class('small-pad')
        save_location_content.attach_next_to(save_dir_name_inp, save_name_lbl,
                                             Gtk.PositionType.RIGHT, 1, 1)

        save_dir_name_err_lbl = Gtk.Label(xalign=0, width_request=190)
        save_dir_name_err_lbl.get_style_context().add_class('error-text')
        save_location_content.attach_next_to(save_dir_name_err_lbl,
                                             save_dir_name_inp,
                                             Gtk.PositionType.RIGHT, 1, 1)

        figures_frame = Gtk.Frame(label='Figures')
        content.add(figures_frame)
        figures_content = Gtk.Grid(margin=10, column_spacing=10, row_spacing=5)
        figures_frame.add(figures_content)

        _, angles_figure_options_area = self.new_component(
            figure_options_cs.factory(
                model=self.presenter.angle_figure_options,
                figure_name='contact angles plot',
            ))
        angles_figure_options_area.show()
        figures_content.attach(angles_figure_options_area, 0, 0, 1, 1)

        footer = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        body.attach_next_to(footer, content, Gtk.PositionType.BOTTOM, 1, 1)

        ok_btn = Gtk.Button('OK')
        ok_btn.get_style_context().add_class(
            'conan-analysis-saver-view-footer-button')
        footer.pack_end(ok_btn, expand=False, fill=False, padding=0)

        cancel_btn = Gtk.Button('Cancel')
        cancel_btn.get_style_context().add_class(
            'conan-analysis-saver-view-footer-button')
        footer.pack_end(cancel_btn, expand=False, fill=False, padding=0)

        self._window.show_all()

        # Wiring things up

        ok_btn.connect('clicked', lambda *_: self.presenter.ok())
        cancel_btn.connect('clicked', lambda *_: self.presenter.cancel())

        self._window.connect('delete-event', self._hdl_window_delete_event)

        self.bn_save_dir_parent = AccessorBindable(self._get_save_dir_parent,
                                                   self._set_save_dir_parent)
        self.bn_save_dir_name = GObjectPropertyBindable(
            save_dir_name_inp, 'text')

        self._confirm_overwrite_dialog = None
        self._file_exists_info_dialog = None

        self.presenter.view_ready()

        return self._window

    def _hdl_window_delete_event(self, widget: Gtk.Dialog,
                                 event: Gdk.Event) -> bool:
        self.presenter.cancel()

        # return True to prevent the dialog from closing.
        return True

    def show_confirm_overwrite_dialog(self, path: Path) -> None:
        if self._confirm_overwrite_dialog is not None:
            return

        self._confirm_overwrite_dialog = YesNoDialog(message_format=(
            "This save location '{!s}' already exists, do you want to clear its contents?"
            .format(path)),
                                                     parent=self._window)

        self._confirm_overwrite_dialog.connect(
            'response', self._hdl_confirm_overwrite_dialog_response)
        self._confirm_overwrite_dialog.connect('delete-event', lambda *_: True)

        self._confirm_overwrite_dialog.show()

    def _hdl_confirm_overwrite_dialog_response(
            self, widget: Gtk.Dialog, response: Gtk.ResponseType) -> None:
        accept = (response == Gtk.ResponseType.YES)
        self.presenter.hdl_confirm_overwrite_dialog_response(accept)

    def hide_confirm_overwrite_dialog(self) -> None:
        if self._confirm_overwrite_dialog is None:
            return

        self._confirm_overwrite_dialog.destroy()
        self._confirm_overwrite_dialog = None

    def tell_user_file_exists_and_is_not_a_directory(self, path: Path) -> None:
        if self._file_exists_info_dialog is not None:
            return

        self._file_exists_info_dialog = ErrorDialog(message_format=(
            "Cannot save to '{!s}', the path already exists and is a non-directory file."
            .format(path)),
                                                    parent=self._window)
        self._file_exists_info_dialog.show()

        def hdl_delete_event(*_) -> None:
            self._file_exists_info_dialog = None

        def hdl_response(dialog: Gtk.Window, *_) -> None:
            self._file_exists_info_dialog = None
            dialog.destroy()

        self._file_exists_info_dialog.connect('delete-event', hdl_delete_event)
        self._file_exists_info_dialog.connect('response', hdl_response)

    def _get_save_dir_parent(self) -> Path:
        path_str = self._save_dir_parent_inp.get_filename()
        path = Path(path_str) if path_str is not None else None
        return path

    def _set_save_dir_parent(self, path: Optional[Path]) -> None:
        if path is None:
            self._save_dir_parent_inp.unselect_all()
            return

        path = str(path)
        self._save_dir_parent_inp.set_filename(path)

    def flush_save_dir_parent(self) -> None:
        self.bn_save_dir_parent.poke()

    def _do_destroy(self) -> None:
        self._window.destroy()
        self.hide_confirm_overwrite_dialog()
Exemple #9
0
class ConanResultsView(View['ConanResultsPresenter', Gtk.Widget]):
    def _do_init(self, footer_area: Gtk.Grid) -> Gtk.Widget:
        self._widget = Gtk.Grid()

        frame = Gtk.Frame(margin=10, label_xalign=0.5)
        frame.show()
        self._widget.attach(frame, 0, 1, 1, 1)

        self._stack = Gtk.Stack()
        self._stack.show()
        frame.add(self._stack)

        _, self._individual_area = self.new_component(
            individual_cs.factory(model=self.presenter.individual_model))
        self._individual_area.show()
        self._stack.add_titled(self._individual_area,
                               name='Individual Fit',
                               title='Individual Fit')

        _, self._graphs_area = self.new_component(
            graphs_cs.factory(model=self.presenter.graphs_model))
        self._graphs_area.show()
        self._stack.add_titled(self._graphs_area,
                               name='Graphs',
                               title='Graphs')

        self._stack_switcher = Gtk.StackSwitcher(stack=self._stack)
        self._stack_switcher.show()
        frame.props.label_widget = self._stack_switcher

        self._stack.props.visible_child = self._individual_area

        _, footer_inner = self.new_component(
            results_footer_cs.factory(
                in_status=self.presenter.bn_footer_status,
                in_progress=self.presenter.bn_completion_progress,
                in_time_elapsed=self.presenter.bn_time_elapsed,
                in_time_remaining=self.presenter.bn_time_remaining,
                do_back=self.presenter.back,
                do_cancel=self.presenter.cancel,
                do_save=self.presenter.save,
            ))
        footer_inner.show()
        footer_area.add(footer_inner)

        self._confirm_cancel_dialog = None
        self._confirm_discard_dialog = None
        self._save_dialog_cid = None

        self.presenter.view_ready()

        return self._widget

    def show_graphs(self) -> None:
        self._stack_switcher.show()

    def hide_graphs(self) -> None:
        self._stack_switcher.hide()
        self._stack.set_visible_child(self._individual_area)

    def show_confirm_cancel_dialog(self) -> None:
        if self._confirm_cancel_dialog is not None:
            return

        self._confirm_cancel_dialog = YesNoDialog(
            parent=self._get_parent_window(),
            message_format='Confirm cancel analysis?',
        )

        self._confirm_cancel_dialog.connect('delete-event', lambda *_: True)
        self._confirm_cancel_dialog.connect(
            'response', self._hdl_confirm_cancel_dialog_response)

        self._confirm_cancel_dialog.show()

    def _hdl_confirm_cancel_dialog_response(
            self, widget: Gtk.Dialog, response: Gtk.ResponseType) -> None:
        accept = (response == Gtk.ResponseType.YES)
        self.presenter.hdl_confirm_cancel_response(accept)

    def hide_confirm_cancel_dialog(self) -> None:
        if self._confirm_cancel_dialog is None:
            return

        self._confirm_cancel_dialog.destroy()
        self._confirm_cancel_dialog = None

    def show_confirm_discard_dialog(self) -> None:
        if self._confirm_discard_dialog is not None:
            return

        self._confirm_discard_dialog = YesNoDialog(
            parent=self._get_parent_window(),
            message_format='Confirm discard results?',
        )

        self._confirm_discard_dialog.connect('delete-event', lambda *_: True)
        self._confirm_discard_dialog.connect(
            'response', self._hdl_confirm_discard_dialog_response)

        self._confirm_discard_dialog.show()

    def _hdl_confirm_discard_dialog_response(
            self, widget: Gtk.Dialog, response: Gtk.ResponseType) -> None:
        accept = (response == Gtk.ResponseType.YES)
        self.presenter.hdl_confirm_discard_response(accept)

    def hide_confirm_discard_dialog(self) -> None:
        if self._confirm_discard_dialog is None:
            return

        self._confirm_discard_dialog.destroy()
        self._confirm_discard_dialog = None

    def show_save_dialog(self, options: ConanAnalysisSaverOptions) -> None:
        if self._save_dialog_cid is not None:
            return

        self._save_dialog_cid, save_dialog = self.new_component(
            conan_save_dialog_cs.factory(
                model=options,
                do_ok=(lambda: self.presenter.hdl_save_dialog_response(
                    should_save=True)),
                do_cancel=(lambda: self.presenter.hdl_save_dialog_response(
                    should_save=False)),
                parent_window=self._get_parent_window(),
            ))
        save_dialog.show()

    def hide_save_dialog(self) -> None:
        if self._save_dialog_cid is None:
            return

        self.remove_component(self._save_dialog_cid)
        self._save_dialog_cid = None

    def _get_parent_window(self) -> Optional[Gtk.Window]:
        toplevel = self._widget.get_toplevel()
        if isinstance(toplevel, Gtk.Window):
            return toplevel
        else:
            return None

    def _do_destroy(self) -> None:
        self._widget.destroy()
        self.hide_confirm_cancel_dialog()
        self.hide_confirm_discard_dialog()