161 lines
5.4 KiB
Python
161 lines
5.4 KiB
Python
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,
|
|
on_load=None,
|
|
title=None,
|
|
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._custom_on_load = on_load
|
|
self._model = model
|
|
self._name = name
|
|
# If a view has a custom on_load function, all layouts should
|
|
# be created on load (*not* in the constructor)
|
|
self._has_dynamic_layouts = on_load is not None
|
|
self._quit_keys = [Screen.KEY_ESCAPE]
|
|
if escape_on_q:
|
|
self._quit_keys.append(ord('q'))
|
|
# child classes may override this as a last resort
|
|
self.skip_reload = False
|
|
|
|
def _ceoframe_on_load(self):
|
|
if self.skip_reload:
|
|
self.skip_reload = False
|
|
return
|
|
if self._model.title is not None:
|
|
self.title = self._model.title
|
|
self._model.title = None
|
|
if self._has_dynamic_layouts and self._model.nav_direction == 'forward':
|
|
# We arrive here after a user pressed 'Back' then 'Next',
|
|
# or after we returned to the Welcome screen.
|
|
# The data may have changed, so we need to redraw everything,
|
|
# via self._custom_on_load().
|
|
self.clear_layouts()
|
|
self._custom_on_load()
|
|
|
|
# may be overridden by child classes
|
|
def _ceoframe_on_reset(self):
|
|
"""
|
|
This is called whenever we return to the home screen
|
|
after some kind of operation was completed.
|
|
This is called from Model.reset().
|
|
"""
|
|
pass
|
|
|
|
def clear_layouts(self):
|
|
self._layouts.clear()
|
|
|
|
def force_update(self):
|
|
"""
|
|
This should be called by background threads after they make changes
|
|
to the UI.
|
|
"""
|
|
# Since we're running in a separate thread, we need to force the
|
|
# screen to update. See
|
|
# https://github.com/peterbrittain/asciimatics/issues/56
|
|
self.fix()
|
|
self._model.screen.force_update()
|
|
|
|
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():
|
|
self._model.nav_direction = 'backward'
|
|
last_scene = self._model.scene_stack.pop()
|
|
if last_scene == 'Welcome':
|
|
self._model.reset()
|
|
raise NextScene(last_scene)
|
|
|
|
def _next():
|
|
self._model.nav_direction = 'forward'
|
|
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, force_update: bool = False):
|
|
self.flash_message('', force_update)
|
|
|
|
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")
|