2021-09-06 12:40:05 -04:00
|
|
|
from asciimatics.exceptions import NextScene
|
2021-09-06 16:16:45 -04:00
|
|
|
from asciimatics.widgets import Frame, Layout, Divider, Button, Label
|
2021-09-06 12:40:05 -04:00
|
|
|
|
|
|
|
|
|
|
|
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
|
2021-09-06 22:29:53 -04:00
|
|
|
has_dynamic_layouts=False, # whether layouts are created on load
|
2021-09-06 12:40:05 -04:00
|
|
|
):
|
|
|
|
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
|
2021-09-06 22:29:53 -04:00
|
|
|
self._has_dynamic_layouts = has_dynamic_layouts
|
2021-09-07 23:38:12 -04:00
|
|
|
# sanity check
|
|
|
|
if save_data:
|
|
|
|
assert name in model.viewdata
|
2021-09-06 12:40:05 -04:00
|
|
|
|
|
|
|
def _ceoframe_on_load(self):
|
|
|
|
# We usually don't want _on_load() to be called multiple times
|
2021-09-06 12:48:07 -04:00
|
|
|
# e.g. when switching back to a scene, or after calling reset()
|
2021-09-06 12:40:05 -04:00
|
|
|
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()
|
|
|
|
|
2021-09-06 22:29:53 -04:00
|
|
|
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.
|
|
|
|
"""
|
2021-09-06 12:40:05 -04:00
|
|
|
if not self._save_data:
|
|
|
|
return
|
|
|
|
self.save()
|
|
|
|
self._model.viewdata[self._name] = self.data
|
|
|
|
|
2021-09-06 22:29:53 -04:00
|
|
|
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()
|
|
|
|
|
2021-09-06 12:40:05 -04:00
|
|
|
def add_buttons(
|
|
|
|
self, back_btn=False, back_btn_text='Back',
|
2021-09-06 16:16:45 -04:00
|
|
|
next_scene=None, next_btn_text='Next', on_next=None,
|
2021-09-06 12:40:05 -04:00
|
|
|
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():
|
|
|
|
raise NextScene(self._model.scene_stack.pop())
|
|
|
|
|
|
|
|
def _next():
|
|
|
|
if on_next_excl is not None:
|
|
|
|
on_next_excl()
|
|
|
|
return
|
|
|
|
if on_next is not None:
|
|
|
|
on_next()
|
2021-09-06 16:16:45 -04:00
|
|
|
self.go_to_next_scene(next_scene)
|
2021-09-06 12:40:05 -04:00
|
|
|
|
|
|
|
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:
|
2021-09-06 16:16:45 -04:00
|
|
|
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)
|
2021-09-06 23:05:13 -04:00
|
|
|
|
2021-09-06 16:16:45 -04:00
|
|
|
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('')
|