# coding: utf-8
from typing import List, Optional, Iterable, Union
import tkinter as tk
import logging
from .meta_block import Block
class DashboardWindow(tk.Tk):
"""The GUI for displaying the label values.
.. versionadded:: 1.5.7
.. versionchanged:: 2.0.0
renamed from *Dashboard_window* to *DashboardWindow*
"""
def __init__(self, labels: List[str]) -> None:
"""Initializes the GUI and sets the layout."""
super().__init__()
self.title('Dashboard')
self.resizable(False, False)
self._labels = labels
# Attributes storing the tkinter objects
self._tk_labels = {}
self._tk_values = {}
self.tk_var = {}
# Setting the GUI
self._set_variables()
self._set_layout()
def _set_variables(self) -> None:
"""Attributes one StringVar per label."""
for label in self._labels:
self.tk_var[label] = tk.StringVar(value='')
def _set_layout(self) -> None:
"""Creates the Labels and places them on the GUI."""
for row, label in enumerate(self._labels):
# The name of the labels on the left
self._tk_labels[label] = tk.Label(self, text=f'{label}:', borderwidth=15,
font=("Courier bold", 48))
self._tk_labels[label].grid(row=row, column=0)
# Their values on the right
self._tk_values[label] = tk.Label(self, borderwidth=15,
textvariable=self.tk_var[label],
font=("Courier bold", 48))
self._tk_values[label].grid(row=row, column=1)
[docs]
class Dashboard(Block):
"""This Block generates an interface displaying data as text in a dedicated
window.
It relies on a :obj:`~tkinter.Tk` window for the graphical interface.
In the window, the left column contains the names of the labels to display
and the right column contains the latest received values for these labels.
For each label, only the last value is therefore displayed.
This Block provides a nicer display than the raw
:class:`~crappy.blocks.LinkReader` Block. For displaying the evolution of a
label over time, the :class:`~crappy.blocks.Grapher` Block should be used
instead.
.. versionadded:: 1.4.0
"""
[docs]
def __init__(self,
labels: Union[str, Iterable[str]],
nb_digits: int = 2,
display_freq: bool = False,
freq: Optional[float] = 30,
debug: Optional[bool] = False) -> None:
"""Sets the arguments and initializes the parent class.
Args:
labels: Only the data from these labels will be displayed on the window.
nb_digits: Number of decimals to show.
display_freq: If :obj:`True`, displays the looping frequency of the
Block.
.. versionadded:: 1.5.7
.. versionchanged:: 2.0.0 renamed from *verbose* to *display_freq*
freq: The target looping frequency for the Block. If :obj:`None`, loops
as fast as possible.
.. versionadded:: 1.5.7
debug: If :obj:`True`, displays all the log messages including the
:obj:`~logging.DEBUG` ones. If :obj:`False`, only displays the log
messages with :obj:`~logging.INFO` level or higher. If :obj:`None`,
disables logging for this Block.
.. versionadded:: 2.0.0
"""
self._dashboard: Optional[DashboardWindow] = None
super().__init__()
self.display_freq = display_freq
self.freq = freq
self.debug = debug
self._labels = [labels] if isinstance(labels, str) else list(labels)
self._nb_digits = nb_digits
[docs]
def prepare(self) -> None:
"""Checks that there's at least one incoming :class:`~crappy.links.Link`,
and starts the GUI.
.. versionadded:: 1.5.7
"""
if not self.inputs:
raise IOError("No Link pointing towards the Dashboard Block !")
self.log(logging.INFO, "Creating the dashboard window")
self._dashboard = DashboardWindow(self._labels)
self._dashboard.update()
[docs]
def loop(self) -> None:
"""Receives the data from the incoming :class:`~crappy.links.Link` and
displays it.
.. versionadded:: 1.5.7
"""
data = self.recv_last_data(fill_missing=False)
for label, value in data.items():
# Only displays the required labels
if label in self._labels:
# Possibility to display str values carried by the links
if isinstance(value, str):
self.log(logging.DEBUG, f"Displaying {value} for the label {label} "
f"on the dashboard")
self._dashboard.tk_var[label].set(value)
elif isinstance(value, int) or isinstance(value, float):
self.log(logging.DEBUG, f"Displaying {value:.{self._nb_digits}f} for"
f" the label {label} on the dashboard")
self._dashboard.tk_var[label].set(f'{value:.{self._nb_digits}f}')
# In case the GUI has been destroyed, don't raise an error
try:
self._dashboard.update()
except tk.TclError:
pass
[docs]
def finish(self) -> None:
"""Closes the display.
.. versionadded:: 1.5.7
"""
# In case the GUI has been destroyed, don't raise an error
try:
if self._dashboard is not None:
self.log(logging.INFO, "Closing the dashboard window")
self._dashboard.destroy()
except tk.TclError:
pass