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()
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()
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
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()