Source code for crappy.blocks.button
# coding: utf-8
from time import time
import logging
from typing import Optional
from .meta_block import Block
from .._global import OptionalModule
try:
import tkinter as tk
except (ModuleNotFoundError, ImportError):
tk = OptionalModule("tkinter")
[docs]
class Button(Block):
"""This Block allows the user to send a signal to downstream Blocks upon
clicking on a button in a Graphical User Interface.
It sends an integer value, that starts from `0` and is incremented every time
the user clicks on the button. This Block relies on a :obj:`~tkinter.Tk`
window for the graphical interface.
This Block is mostly useful for incorporating user feedback in a script, i.e.
triggering actions based on an experimenter's decision. It can be handy for
taking pictures at precise moments, or when an action should only begin after
the experimenter has completed a task, for example.
.. versionadded:: 1.4.0
.. versionchanged:: 2.0.0 renamed from *GUI* to *Button*
"""
[docs]
def __init__(self,
send_0: bool = False,
label: str = 'step',
time_label: str = 't(s)',
freq: Optional[float] = 50,
spam: bool = False,
display_freq: bool = False,
debug: Optional[bool] = False) -> None:
"""Sets the arguments and initializes the parent class.
Args:
send_0: If :obj:`True`, the value `0` will be sent automatically when
starting the Block. Otherwise, `1` will be sent at the first click.
Only relevant when ``spam`` is :obj:`False`.
.. versionadded:: 1.5.10
label: The label carrying the information on the number of clicks,
default is ``'step'``.
time_label: The label carrying the time information, default is
``'t(s)'``.
.. versionadded:: 1.5.10
freq: The target looping frequency for the Block. If :obj:`None`, loops
as fast as possible.
spam: If :obj:`True`, sends the current step value at each loop,
otherwise only sends it at each click.
display_freq: If :obj:`True`, displays the looping frequency of the
Block.
.. versionadded:: 2.0.0
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._root: Optional[tk.Tk] = None
super().__init__()
self.freq = freq
self.labels = [time_label, label]
self.display_freq = display_freq
self.debug = debug
self._spam = spam
self._send_0 = send_0
self._step = None
self._text = None
self._label = None
self._button = None
[docs]
def prepare(self) -> None:
"""Creates the graphical interface and sets its layout and callbacks."""
self.log(logging.INFO, "Creating the GUI")
self._root = tk.Tk()
self._root.title("Button block")
self._root.resizable(False, False)
self._step = tk.IntVar()
self._step.trace_add('write', self._update_text)
self._text = tk.StringVar(value=f'step: {self._step.get()}')
self._label = tk.Label(self._root, textvariable=self._text)
self._label.pack(padx=7, pady=7)
self._button = tk.Button(self._root,
text='Next step',
command=self._next_step)
self._button.pack(padx=25, pady=7)
self._root.update()
[docs]
def begin(self) -> None:
"""Sends the value of the first step (`0`) if required."""
if self._send_0:
self.send([time() - self.t0, self._step.get()])
[docs]
def loop(self) -> None:
"""Updates the interface, and sends the current step value if ``spam`` is
:obj:`True`. """
try:
self._root.update()
self.log(logging.DEBUG, "GUI updated")
except tk.TclError:
return
if self._spam:
self.send([time() - self.t0, self._step.get()])
[docs]
def finish(self) -> None:
"""Closes the interface window."""
self.log(logging.INFO, "closing the GUI")
try:
if self._root is not None:
self._root.destroy()
except tk.TclError:
pass
def _update_text(self, _, __, ___) -> None:
"""Simply updates the displayed text."""
self._text.set(f'step: {self._step.get()}')
def _next_step(self) -> None:
"""Increments the step counter and sends the corresponding signal."""
self.log(logging.DEBUG, "Next step on the GUI")
self._step.set(self._step.get() + 1)
self.send([time() - self.t0, self._step.get()])