mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-06 21:44:16 +02:00
326 lines
11 KiB
Python
326 lines
11 KiB
Python
from abc import ABC
|
|
from enum import Enum
|
|
from typing import List, Set, Optional, Dict, TypeVar, Type, Union, Tuple
|
|
|
|
|
|
class MessageType(Enum):
|
|
INFO = 0
|
|
WARNING = 1
|
|
ERROR = 2
|
|
|
|
|
|
class ViewObserver:
|
|
|
|
def on_change(self, change):
|
|
pass
|
|
|
|
|
|
class ViewComponent(ABC):
|
|
"""
|
|
Represents a GUI component
|
|
"""
|
|
def __init__(self, id_: Optional[str], observers: Optional[List[ViewObserver]] = None):
|
|
self.id = id_
|
|
self.observers = observers if observers else []
|
|
|
|
def add_observer(self, obs):
|
|
self.observers.append(obs)
|
|
|
|
|
|
V = TypeVar('V', bound=ViewComponent)
|
|
|
|
|
|
class ViewContainer(ViewComponent, ABC):
|
|
"""
|
|
Represents a GUI component composed by other components
|
|
"""
|
|
|
|
def __init__(self, components: Union[List[V], Tuple[V]],
|
|
id_: Optional[str] = None,
|
|
observers: Optional[List[ViewObserver]] = None):
|
|
super(ViewContainer, self).__init__(id_=id_, observers=observers)
|
|
self.components = components
|
|
self.component_map = {c.id: c for c in components if c.id is not None} if components else None
|
|
|
|
def get_component(self, id_: str, type_: Type[V] = ViewComponent) -> Optional[V]:
|
|
if self.component_map:
|
|
instance = self.component_map.get(id_)
|
|
|
|
if instance:
|
|
if isinstance(instance, type_):
|
|
return instance
|
|
|
|
raise Exception(f"The {ViewComponent.__class__.__name__} (id={id_}) is not an "
|
|
f"instance of '{type_.__name__}'")
|
|
|
|
def get_component_by_idx(self, idx: int, type_: Type[V]) -> Optional[V]:
|
|
if self.components and idx < len(self.components):
|
|
c = self.components[idx]
|
|
if isinstance(c, type_):
|
|
return c
|
|
|
|
raise Exception(f"The {ViewComponent.__class__.__name__} at index {idx} is not an "
|
|
f"instance of '{type_.__name__}'")
|
|
|
|
|
|
class SpacerComponent(ViewComponent):
|
|
|
|
def __init__(self):
|
|
super(SpacerComponent, self).__init__(id_=None)
|
|
|
|
|
|
class InputViewComponent(ViewComponent):
|
|
"""
|
|
Represents a component which needs a user interaction to provide its value
|
|
"""
|
|
|
|
|
|
class InputOption:
|
|
"""
|
|
Represents a select component option.
|
|
"""
|
|
|
|
def __init__(self, label: str, value: object, tooltip: Optional[str] = None,
|
|
icon_path: Optional[str] = None, read_only: bool = False, id_: Optional[str] = None,
|
|
invalid: bool = False, extra_properties: Optional[Dict[str, str]] = None):
|
|
"""
|
|
:param label: the string that will be shown to the user
|
|
:param value: the option value (not shown)
|
|
:param tooltip: an optional tooltip
|
|
:param icon_path: an optional icon path
|
|
:param invalid: if this option is considered invalid
|
|
:param extra_properties: extra properties
|
|
"""
|
|
if not label:
|
|
raise Exception("'label' must be a not blank string")
|
|
|
|
self.id = id_
|
|
self.label = label
|
|
self.value = value
|
|
self.tooltip = tooltip
|
|
self.icon_path = icon_path
|
|
self.read_only = read_only
|
|
self.invalid = invalid
|
|
self.extra_properties = extra_properties
|
|
|
|
def __hash__(self):
|
|
return hash(self.label) + hash(self.value)
|
|
|
|
|
|
class SelectViewType(Enum):
|
|
RADIO = 0
|
|
COMBO = 1
|
|
|
|
|
|
class SingleSelectComponent(InputViewComponent):
|
|
|
|
def __init__(self, type_: SelectViewType, label: str, options: List[InputOption], default_option: InputOption = None,
|
|
max_per_line: int = 1, tooltip: str = None, max_width: int = -1, id_: str = None,
|
|
capitalize_label: bool = True):
|
|
super(SingleSelectComponent, self).__init__(id_=id_)
|
|
self.type = type_
|
|
self.label = label
|
|
self.options = options
|
|
self.value = default_option
|
|
self.init_value = default_option
|
|
self.max_per_line = max_per_line
|
|
self.tooltip = tooltip
|
|
self.max_width = max_width
|
|
self.capitalize_label = capitalize_label
|
|
|
|
def get_selected(self):
|
|
if self.value:
|
|
return self.value.value
|
|
|
|
def changed(self) -> bool:
|
|
return self.init_value != self.value
|
|
|
|
|
|
class MultipleSelectComponent(InputViewComponent):
|
|
|
|
def __init__(self, label: Optional[str], options: List[InputOption], default_options: Set[InputOption] = None,
|
|
max_per_line: int = 1, tooltip: str = None, spaces: bool = True, max_width: int = -1,
|
|
max_height: int = -1, id_: str = None, min_width: Optional[int] = None):
|
|
super(MultipleSelectComponent, self).__init__(id_=id_)
|
|
|
|
if not options:
|
|
raise Exception("'options' cannot be None or empty")
|
|
|
|
self.options = options
|
|
self.spaces = spaces
|
|
self.label = label
|
|
self.tooltip = tooltip
|
|
self.values = default_options if default_options else set()
|
|
self.max_per_line = max_per_line
|
|
self.min_width = min_width
|
|
self.max_width = max_width
|
|
self.max_height = max_height
|
|
|
|
def get_selected_values(self) -> list:
|
|
selected = []
|
|
if self.values:
|
|
selected.extend([op.value for op in self.values])
|
|
|
|
return selected
|
|
|
|
|
|
class TextComponent(ViewComponent):
|
|
|
|
def __init__(self, html: str, min_width: Optional[int] = None, max_width: int = -1,
|
|
tooltip: str = None, id_: str = None, size: int = None):
|
|
super(TextComponent, self).__init__(id_=id_)
|
|
self.value = html
|
|
self.min_width = min_width
|
|
self.max_width = max_width
|
|
self.tooltip = tooltip
|
|
self.size = size
|
|
|
|
|
|
class TwoStateButtonComponent(ViewComponent):
|
|
|
|
def __init__(self, label: str, tooltip: str = None, state: bool = False, id_: str = None):
|
|
super(TwoStateButtonComponent, self).__init__(id_=id_)
|
|
self.label = label
|
|
self.tooltip = tooltip
|
|
self.state = state
|
|
|
|
|
|
class TextInputType(Enum):
|
|
SINGLE_LINE = 0
|
|
MULTIPLE_LINES = 1
|
|
|
|
|
|
class TextInputComponent(ViewComponent):
|
|
|
|
def __init__(self, label: str, value: str = '', placeholder: Optional[str] = None, tooltip: Optional[str] = None,
|
|
read_only: bool = False, id_: Optional[str] = None, only_int: bool = False, max_width: int = -1,
|
|
type_: TextInputType = TextInputType.SINGLE_LINE, capitalize_label: bool = True, min_width: int = -1,
|
|
min_height: int = -1):
|
|
super(TextInputComponent, self).__init__(id_=id_)
|
|
self.label = label
|
|
self.value = value
|
|
self.tooltip = tooltip
|
|
self.placeholder = placeholder
|
|
self.read_only = read_only
|
|
self.only_int = only_int
|
|
self.max_width = max_width
|
|
self.type = type_
|
|
self.capitalize_label = capitalize_label
|
|
self.min_width = min_width
|
|
self.min_height = min_height
|
|
|
|
def get_value(self) -> str:
|
|
if self.value is not None:
|
|
return self.value
|
|
else:
|
|
return ''
|
|
|
|
def set_value(self, val: Optional[str], caller: object = None):
|
|
if val != self.value:
|
|
self.value = val
|
|
|
|
if self.observers:
|
|
for o in self.observers:
|
|
if caller != o:
|
|
o.on_change(val)
|
|
|
|
def get_int_value(self) -> Optional[int]:
|
|
if self.value is not None:
|
|
val = self.value.strip() if isinstance(self.value, str) else self.value
|
|
|
|
if val is not None:
|
|
return int(self.value)
|
|
|
|
def get_label(self) -> str:
|
|
if not self.label:
|
|
return ''
|
|
else:
|
|
return self.label.capitalize() if self.capitalize_label else self.label
|
|
|
|
|
|
class FormComponent(ViewContainer):
|
|
|
|
def __init__(self, components: List[ViewComponent], label: str = None, spaces: bool = True, id_: str = None,
|
|
min_width: Optional[int] = None):
|
|
super(FormComponent, self).__init__(id_=id_, components=components)
|
|
self.label = label
|
|
self.spaces = spaces
|
|
self.min_width = min_width
|
|
|
|
|
|
class FileChooserComponent(ViewComponent):
|
|
|
|
def __init__(self, allowed_extensions: Optional[Set[str]] = None, label: Optional[str] = None, tooltip: Optional[str] = None,
|
|
file_path: Optional[str] = None, max_width: int = -1, id_: Optional[str] = None,
|
|
search_path: Optional[str] = None, capitalize_label: bool = True, directory: bool = False):
|
|
super(FileChooserComponent, self).__init__(id_=id_)
|
|
self.label = label
|
|
self.allowed_extensions = allowed_extensions
|
|
self.file_path = file_path
|
|
self.tooltip = tooltip
|
|
self.max_width = max_width
|
|
self.search_path = search_path
|
|
self.capitalize_label = capitalize_label
|
|
self.directory = directory
|
|
|
|
def set_file_path(self, fpath: Optional[str]):
|
|
self.file_path = fpath
|
|
|
|
if self.observers:
|
|
for o in self.observers:
|
|
o.on_change(self.file_path)
|
|
|
|
def get_label(self) -> str:
|
|
if not self.label:
|
|
return ''
|
|
else:
|
|
return self.label.capitalize() if self.capitalize_label else self.label
|
|
|
|
|
|
class TabComponent(ViewComponent):
|
|
|
|
def __init__(self, label: str, content: ViewComponent, icon_path: str = None, id_: str = None):
|
|
super(TabComponent, self).__init__(id_=id_)
|
|
self.label = label
|
|
self._content = content
|
|
self.icon_path = icon_path
|
|
|
|
def get_content(self, type_: Type[V] = ViewComponent) -> V:
|
|
if isinstance(self._content, type_):
|
|
return self._content
|
|
|
|
raise Exception(f"'content' is not an instance of '{type_.__name__}'")
|
|
|
|
|
|
class TabGroupComponent(ViewContainer):
|
|
|
|
def __init__(self, tabs: List[TabComponent], id_: str = None):
|
|
super(TabGroupComponent, self).__init__(id_=id_, components=tabs)
|
|
|
|
def get_tab(self, id_: str) -> Optional[TabComponent]:
|
|
return self.get_component(id_, TabComponent)
|
|
|
|
@property
|
|
def tabs(self) -> List[TabComponent]:
|
|
return self.components
|
|
|
|
|
|
class RangeInputComponent(InputViewComponent):
|
|
|
|
def __init__(self, id_: str, label: str, tooltip: str, min_value: int, max_value: int,
|
|
step_value: int, value: Optional[int] = None, max_width: int = None):
|
|
super(RangeInputComponent, self).__init__(id_=id_)
|
|
self.label = label
|
|
self.tooltip = tooltip
|
|
self.min = min_value
|
|
self.max = max_value
|
|
self.step = step_value
|
|
self.value = value
|
|
self.max_width = max_width
|
|
|
|
|
|
class PanelComponent(ViewContainer):
|
|
|
|
def __init__(self, components: List[ViewComponent], id_: str = None):
|
|
super(PanelComponent, self).__init__(id_=id_, components=components)
|