Module sbx.ui.controls
Reusable controls, components, classes used in SBX
Expand source code
"""
Reusable controls, components, classes used in SBX
"""
import abc
from abc import ABCMeta
from prompt_toolkit import Application
from prompt_toolkit.clipboard import ClipboardData
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.layout import Layout
from prompt_toolkit.layout.containers import Float, FloatContainer
from prompt_toolkit.layout.processors import (
DisplayMultipleCursors,
TabsProcessor,
)
from prompt_toolkit.lexers import PygmentsLexer
from prompt_toolkit.widgets import Button, Dialog, Label, TextArea
from sbx.ui.mdlexer import CustomMarkdownLexer
class MarkdownArea(TextArea):
"""
Pre-configured markdown component for prompt_toolkit
"""
def __init__(self, readonly=False):
super().__init__(
lexer=PygmentsLexer(CustomMarkdownLexer),
scrollbar=True,
line_numbers=True,
focus_on_click=not readonly,
input_processors=[TabsProcessor(), DisplayMultipleCursors()],
wrap_lines=True,
read_only=readonly,
focusable=not readonly,
)
def indent(self):
"""
Insert a tab
"""
if self.read_only:
return
current_doc = self.document
self.document = current_doc.paste_clipboard_data(
ClipboardData(text="\t")
)
class BaseUi(metaclass=ABCMeta):
"""
Core UI class for Editor & Study interfaces to inherit from
"""
def __init__(self):
self._layout_stack = []
self._focus_stack = []
self._float = None
self.kb = KeyBindings()
def hide_current_dialog(self):
"""
Hide current displayed dialog box
"""
# Nothing to hide, ignore
if not self._float.floats:
return
# WHY: Ensure that we restore previous focused item
prev_focus = self._focus_stack.pop()
if prev_focus:
self.get_current_layout().focus(prev_focus)
# Garbage clean up
float_dialog = self._float.floats.pop()
del float_dialog
self.get_current_app().invalidate()
def exit_clicked(self, _=None):
self.get_current_app().exit()
@abc.abstractmethod
def get_current_app(self) -> Application:
pass
@abc.abstractmethod
def get_current_layout(self) -> Layout:
pass
@abc.abstractmethod
def get_actions(self) -> dict:
pass
@abc.abstractmethod
def get_keybindings(self) -> dict:
pass
def create_key_bindings(self):
actions = self.get_actions()
for action, keys in self.get_keybindings().items():
if callable(keys):
keys(self.kb, actions[action])
continue
for key in keys.split(","):
try:
self.kb.add(key.strip())(actions[action])
except KeyError:
pass
def create_root_layout(self, container, focused_element):
self._float = FloatContainer(container, floats=[])
return Layout(self._float, focused_element=focused_element)
def message_box(self, title: str, text: str):
"""
Show a message box
* `title` - title of the message box
* `text` - text of the message box
"""
self.custom_dialog(
title, Label(text=text, dont_extend_height=True), show_ok=True
)
def confirm_box(self, title: str, text: str, on_yes, on_no):
"""
Show a message box with yes/no confirmation
* `title` - title of the message
* `text` - text asking a question
* `on_yes` - call back function (no args)
* `on_no` - call back function (no args)
"""
body = Label(text=text, dont_extend_height=True)
yes_btn = Button(text="Yes", handler=self._hide_then_call(on_yes))
no_btn = Button(text="No", handler=self._hide_then_call(on_no))
self.custom_dialog(
title, body, focus_element=yes_btn, buttons=[yes_btn, no_btn]
)
def _hide_then_call(self, fnc):
def callback():
self.hide_current_dialog()
fnc()
return callback
def custom_dialog(
self, title: str, body, show_ok=False, focus_element=None, buttons=None
):
"""
Create a custom dialog
* `title` - title of the message
* `body` - prompt_toolkit widget to display as body
* `show_ok` - show OK button (use this if you don't want to
provide your own buttons)
* `focus_element` - prompt_toolkit element to focus on.
* `buttons` = list of prompt_toolkit buttons to show
Note:
* Please note that you need to call `hide_current_dialog` after a
button is clicked and you no longer want to show a dialog.
* All custom dialog are stored in a stack. If `n` dialog are shown
then you need to call `hide_current_dialog` exactly `n` times.
* Refer to code in `confirm_box` & `message_box` for samples.
"""
ok = None
if show_ok:
ok = Button(text="OK", handler=self.hide_current_dialog)
btns = [ok]
elif buttons:
btns = buttons
else:
btns = []
dialog = Dialog(
title=title,
body=body,
buttons=btns,
with_background=False,
)
self._focus_stack.append(self.get_current_layout().current_window)
self._float.floats.append(Float(dialog, allow_cover_cursor=True))
if show_ok and ok:
self.get_current_layout().focus(ok)
if focus_element:
self.get_current_layout().focus(focus_element)
self.get_current_app().invalidate()
Classes
class BaseUi
-
Core UI class for Editor & Study interfaces to inherit from
Expand source code
class BaseUi(metaclass=ABCMeta): """ Core UI class for Editor & Study interfaces to inherit from """ def __init__(self): self._layout_stack = [] self._focus_stack = [] self._float = None self.kb = KeyBindings() def hide_current_dialog(self): """ Hide current displayed dialog box """ # Nothing to hide, ignore if not self._float.floats: return # WHY: Ensure that we restore previous focused item prev_focus = self._focus_stack.pop() if prev_focus: self.get_current_layout().focus(prev_focus) # Garbage clean up float_dialog = self._float.floats.pop() del float_dialog self.get_current_app().invalidate() def exit_clicked(self, _=None): self.get_current_app().exit() @abc.abstractmethod def get_current_app(self) -> Application: pass @abc.abstractmethod def get_current_layout(self) -> Layout: pass @abc.abstractmethod def get_actions(self) -> dict: pass @abc.abstractmethod def get_keybindings(self) -> dict: pass def create_key_bindings(self): actions = self.get_actions() for action, keys in self.get_keybindings().items(): if callable(keys): keys(self.kb, actions[action]) continue for key in keys.split(","): try: self.kb.add(key.strip())(actions[action]) except KeyError: pass def create_root_layout(self, container, focused_element): self._float = FloatContainer(container, floats=[]) return Layout(self._float, focused_element=focused_element) def message_box(self, title: str, text: str): """ Show a message box * `title` - title of the message box * `text` - text of the message box """ self.custom_dialog( title, Label(text=text, dont_extend_height=True), show_ok=True ) def confirm_box(self, title: str, text: str, on_yes, on_no): """ Show a message box with yes/no confirmation * `title` - title of the message * `text` - text asking a question * `on_yes` - call back function (no args) * `on_no` - call back function (no args) """ body = Label(text=text, dont_extend_height=True) yes_btn = Button(text="Yes", handler=self._hide_then_call(on_yes)) no_btn = Button(text="No", handler=self._hide_then_call(on_no)) self.custom_dialog( title, body, focus_element=yes_btn, buttons=[yes_btn, no_btn] ) def _hide_then_call(self, fnc): def callback(): self.hide_current_dialog() fnc() return callback def custom_dialog( self, title: str, body, show_ok=False, focus_element=None, buttons=None ): """ Create a custom dialog * `title` - title of the message * `body` - prompt_toolkit widget to display as body * `show_ok` - show OK button (use this if you don't want to provide your own buttons) * `focus_element` - prompt_toolkit element to focus on. * `buttons` = list of prompt_toolkit buttons to show Note: * Please note that you need to call `hide_current_dialog` after a button is clicked and you no longer want to show a dialog. * All custom dialog are stored in a stack. If `n` dialog are shown then you need to call `hide_current_dialog` exactly `n` times. * Refer to code in `confirm_box` & `message_box` for samples. """ ok = None if show_ok: ok = Button(text="OK", handler=self.hide_current_dialog) btns = [ok] elif buttons: btns = buttons else: btns = [] dialog = Dialog( title=title, body=body, buttons=btns, with_background=False, ) self._focus_stack.append(self.get_current_layout().current_window) self._float.floats.append(Float(dialog, allow_cover_cursor=True)) if show_ok and ok: self.get_current_layout().focus(ok) if focus_element: self.get_current_layout().focus(focus_element) self.get_current_app().invalidate()
Subclasses
Methods
def confirm_box(self, title: str, text: str, on_yes, on_no)
-
Show a message box with yes/no confirmation
title
- title of the messagetext
- text asking a questionon_yes
- call back function (no args)on_no
- call back function (no args)
Expand source code
def confirm_box(self, title: str, text: str, on_yes, on_no): """ Show a message box with yes/no confirmation * `title` - title of the message * `text` - text asking a question * `on_yes` - call back function (no args) * `on_no` - call back function (no args) """ body = Label(text=text, dont_extend_height=True) yes_btn = Button(text="Yes", handler=self._hide_then_call(on_yes)) no_btn = Button(text="No", handler=self._hide_then_call(on_no)) self.custom_dialog( title, body, focus_element=yes_btn, buttons=[yes_btn, no_btn] )
def create_key_bindings(self)
-
Expand source code
def create_key_bindings(self): actions = self.get_actions() for action, keys in self.get_keybindings().items(): if callable(keys): keys(self.kb, actions[action]) continue for key in keys.split(","): try: self.kb.add(key.strip())(actions[action]) except KeyError: pass
def create_root_layout(self, container, focused_element)
-
Expand source code
def create_root_layout(self, container, focused_element): self._float = FloatContainer(container, floats=[]) return Layout(self._float, focused_element=focused_element)
def custom_dialog(self, title: str, body, show_ok=False, focus_element=None, buttons=None)
-
Create a custom dialog
title
- title of the messagebody
- prompt_toolkit widget to display as bodyshow_ok
- show OK button (use this if you don't want to provide your own buttons)focus_element
- prompt_toolkit element to focus on.buttons
= list of prompt_toolkit buttons to show
Note:
- Please note that you need to call
hide_current_dialog
after a button is clicked and you no longer want to show a dialog. - All custom dialog are stored in a stack. If
n
dialog are shown then you need to callhide_current_dialog
exactlyn
times. - Refer to code in
confirm_box
&message_box
for samples.
Expand source code
def custom_dialog( self, title: str, body, show_ok=False, focus_element=None, buttons=None ): """ Create a custom dialog * `title` - title of the message * `body` - prompt_toolkit widget to display as body * `show_ok` - show OK button (use this if you don't want to provide your own buttons) * `focus_element` - prompt_toolkit element to focus on. * `buttons` = list of prompt_toolkit buttons to show Note: * Please note that you need to call `hide_current_dialog` after a button is clicked and you no longer want to show a dialog. * All custom dialog are stored in a stack. If `n` dialog are shown then you need to call `hide_current_dialog` exactly `n` times. * Refer to code in `confirm_box` & `message_box` for samples. """ ok = None if show_ok: ok = Button(text="OK", handler=self.hide_current_dialog) btns = [ok] elif buttons: btns = buttons else: btns = [] dialog = Dialog( title=title, body=body, buttons=btns, with_background=False, ) self._focus_stack.append(self.get_current_layout().current_window) self._float.floats.append(Float(dialog, allow_cover_cursor=True)) if show_ok and ok: self.get_current_layout().focus(ok) if focus_element: self.get_current_layout().focus(focus_element) self.get_current_app().invalidate()
def exit_clicked(self)
-
Expand source code
def exit_clicked(self, _=None): self.get_current_app().exit()
def get_actions(self) ‑> dict
-
Expand source code
@abc.abstractmethod def get_actions(self) -> dict: pass
def get_current_app(self) ‑> prompt_toolkit.application.application.Application
-
Expand source code
@abc.abstractmethod def get_current_app(self) -> Application: pass
def get_current_layout(self) ‑> prompt_toolkit.layout.layout.Layout
-
Expand source code
@abc.abstractmethod def get_current_layout(self) -> Layout: pass
def get_keybindings(self) ‑> dict
-
Expand source code
@abc.abstractmethod def get_keybindings(self) -> dict: pass
def hide_current_dialog(self)
-
Hide current displayed dialog box
Expand source code
def hide_current_dialog(self): """ Hide current displayed dialog box """ # Nothing to hide, ignore if not self._float.floats: return # WHY: Ensure that we restore previous focused item prev_focus = self._focus_stack.pop() if prev_focus: self.get_current_layout().focus(prev_focus) # Garbage clean up float_dialog = self._float.floats.pop() del float_dialog self.get_current_app().invalidate()
def message_box(self, title: str, text: str)
-
Show a message box
title
- title of the message boxtext
- text of the message box
Expand source code
def message_box(self, title: str, text: str): """ Show a message box * `title` - title of the message box * `text` - text of the message box """ self.custom_dialog( title, Label(text=text, dont_extend_height=True), show_ok=True )
class MarkdownArea (readonly=False)
-
Pre-configured markdown component for prompt_toolkit
Expand source code
class MarkdownArea(TextArea): """ Pre-configured markdown component for prompt_toolkit """ def __init__(self, readonly=False): super().__init__( lexer=PygmentsLexer(CustomMarkdownLexer), scrollbar=True, line_numbers=True, focus_on_click=not readonly, input_processors=[TabsProcessor(), DisplayMultipleCursors()], wrap_lines=True, read_only=readonly, focusable=not readonly, ) def indent(self): """ Insert a tab """ if self.read_only: return current_doc = self.document self.document = current_doc.paste_clipboard_data( ClipboardData(text="\t") )
Ancestors
- prompt_toolkit.widgets.base.TextArea
Methods
def indent(self)
-
Insert a tab
Expand source code
def indent(self): """ Insert a tab """ if self.read_only: return current_doc = self.document self.document = current_doc.paste_clipboard_data( ClipboardData(text="\t") )