from asciimatics.event import KeyboardEvent from asciimatics.exceptions import NextScene, StopApplication from asciimatics.screen import Screen from asciimatics.widgets import Frame, Layout, Divider, Button, Label, \ PopUpDialog class CeoFrame(Frame): def __init__( self, screen, height, width, model, name, # key in model.viewdata on_load=None, title=None, save_data=False, # whether to save widget state for resizing has_dynamic_layouts=False, # whether layouts are created on load escape_on_q=False, # whether to quit when 'q' is pressed ): super().__init__( screen, height, width, name=name, can_scroll=False, title=title, on_load=self._ceoframe_on_load, ) self._save_data = save_data self._extra_on_load = on_load self._model = model self._name = name self._loaded = False self._has_dynamic_layouts = has_dynamic_layouts self._quit_keys = [Screen.KEY_ESCAPE] if escape_on_q: self._quit_keys.append(ord('q')) # sanity check if save_data: assert name in model.viewdata def _ceoframe_on_load(self): # We usually don't want _on_load() to be called multiple times # e.g. when switching back to a scene, or after calling reset() if self._loaded: return self._loaded = True if self._model.title is not None: self.title = self._model.title self._model.title = None if self._save_data: # restore the saved input fields' values self.data = self._model.viewdata[self._name] if self._extra_on_load is not None: self._extra_on_load() def _ceoframe_on_unload(self): """ This should be called just after the screen gets resized, but before the new scenes are constructed. The idea is to save the user's data in the input fields so that we can restore them in the new scenes. """ if not self._save_data: return self.save() self._model.viewdata[self._name] = self.data def _ceoframe_on_reset(self): """ This needs to be called whenever we return to the home screen after some kind of operation was completed. Currently this is called from Model.reset(). """ # We want a fresh slate once we return to the home screen, so we # want on_load() to be called for the scenes. self._loaded = False if self._has_dynamic_layouts: # We don't want layouts to accumulate. self.clear_layouts() def clear_layouts(self): # OK so this a *really* bad thing to do, since we're reaching # into the private variables of a third-party library. # Unfortunately asciimatics doesn't allow us to clear the layouts # of an existing frame, and we need this to be able to re-use # frames which create layouts dynamically. self._layouts.clear() def add_buttons( self, back_btn=False, back_btn_text='Back', next_scene=None, next_btn_text='Next', on_next=None, on_next_excl=None, ): """ Add a new layout at the bottom of the frame with buttons. If back_btn is True, a Back button is added. If next_scene is set to the name of the next scene, or on_next_excl is set, a Next button will be added. If on_next is set to a function, it will be called when the Next button is pressed, and the screen will switch to the next scene. If on_next_excl is set to a function, it will be called when the Next button is pressed, and the scene will not be switched. If both on_next and on_next_excl are set, on_next will be ignored. """ layout = Layout([100]) self.add_layout(layout) layout.add_widget(Divider()) def _back(): last_scene = self._model.scene_stack.pop() if last_scene == 'Welcome': self._model.reset() raise NextScene(last_scene) def _next(): if on_next_excl is not None: on_next_excl() return if on_next is not None: on_next() self.go_to_next_scene(next_scene) layout = Layout([1, 1]) self.add_layout(layout) if back_btn: layout.add_widget(Button(back_btn_text, _back), 0) if next_scene is not None or on_next_excl is not None: layout.add_widget(Button(next_btn_text, _next), 1) def go_to_next_scene(self, next_scene: str): self._model.scene_stack.append(self._name) raise NextScene(next_scene) def add_flash_message_layout(self): layout = Layout([100]) self.add_layout(layout) self._status_label = Label('') layout.add_widget(self._status_label) def flash_message(self, msg: str, force_update: bool = False): self._status_label.text = msg if force_update: self._model.screen.force_update() self._model.screen.draw_next_frame() def clear_flash_message(self): self.flash_message('') def process_event(self, event): if not isinstance(event, KeyboardEvent): return super().process_event(event) c = event.key_code # Stop on 'q' or 'Esc' if c in self._quit_keys: self._scene.add_effect(PopUpDialog( self.screen, 'Are you sure you want to quit?', ['Yes', 'No'], has_shadow=True, on_close=self._quit_on_yes, )) return super().process_event(event) @staticmethod def _quit_on_yes(selected): # Yes is the first button if selected == 0: raise StopApplication("User terminated app")