Description
Bug summary
Under the QtAgg
backend, when creating multiple TextBox
widgets with toolmanager
in use, clicking in one TextBox
and then directly in another sometimes results in ValueError('already locked')
from TextBox.begin_typing
.
Code for reproduction
import os
import sys
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.widgets import TextBox
matplotlib.use('QTAgg')
matplotlib.rcParams['toolbar'] = 'toolmanager'
def main(argv: list[str]) -> int:
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.2)
xs = list(range(25))
ys = [x ** 3 for x in xs]
plt.plot(xs, ys)
tb1 = TextBox(plt.axes([0.15, 0.05, 0.3, 0.075]), 'TB1')
tb2 = TextBox(plt.axes([0.55, 0.05, 0.3, 0.075]), 'TB2')
plt.show()
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))
Actual outcome
Traceback (most recent call last):
File "/redacted/.venv/lib/python3.13/site-packages/matplotlib/cbook.py", line 361, in process
func(*args, **kwargs)
~~~~^^^^^^^^^^^^^^^^^
File "/redacted/.venv/lib/python3.13/site-packages/matplotlib/widgets.py", line 1499, in _click
self.begin_typing()
~~~~~~~~~~~~~~~~~^^
File "/redacted/.venv/lib/python3.13/site-packages/matplotlib/widgets.py", line 1464, in begin_typing
toolmanager.keypresslock(self)
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "/redacted/.venv/lib/python3.13/site-packages/matplotlib/widgets.py", line 44, in __call__
raise ValueError('already locked')
ValueError: already locked
Expected outcome
I'd expect to be able to navigate from one text box to the other without this happening.
Additional information
I believe this is related to the changes in #14343. I subclassed TextBox
to get a little more information:
import os
import sys
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.widgets import TextBox
matplotlib.use('QTAgg')
matplotlib.rcParams['toolbar'] = 'toolmanager'
print(matplotlib.get_backend())
class SnitchBox(TextBox):
def __init__(self, name, *rest) -> None:
self.name = name
super().__init__(*rest)
def begin_typing(self) -> None:
print(f'{self.name} BEGIN TYPING')
super().begin_typing()
def stop_typing(self) -> None:
print(f'{self.name} STOP TYPING')
super().stop_typing()
def main(argv: list[str]) -> int:
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.2)
xs = list(range(25))
ys = [x ** 3 for x in xs]
plt.plot(xs, ys)
tb1 = SnitchBox('TB1', plt.axes([0.15, 0.05, 0.3, 0.075]), 'TB1')
tb2 = SnitchBox('TB2', plt.axes([0.55, 0.05, 0.3, 0.075]), 'TB2')
plt.show()
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))
With this instrumentation, I sometimes see the following trace when clicking between textboxes:
TB2 BEGIN TYPING
TB1 BEGIN TYPING
Traceback (most recent call last):
File "/home/dwt/work/vso/VSO_GBT2/.venv/lib/python3.13/site-packages/matplotlib/cbook.py", line 361, in process
func(*args, **kwargs)
~~~~^^^^^^^^^^^^^^^^^
File "/home/dwt/work/vso/VSO_GBT2/.venv/lib/python3.13/site-packages/matplotlib/widgets.py", line 1499, in _click
self.begin_typing()
~~~~~~~~~~~~~~~~~^^
File "/home/dwt/work/vso/VSO_GBT2/demo.py", line 20, in begin_typing
super().begin_typing()
~~~~~~~~~~~~~~~~~~~~^^
File "/home/dwt/work/vso/VSO_GBT2/.venv/lib/python3.13/site-packages/matplotlib/widgets.py", line 1464, in begin_typing
toolmanager.keypresslock(self)
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "/home/dwt/work/vso/VSO_GBT2/.venv/lib/python3.13/site-packages/matplotlib/widgets.py", line 44, in __call__
raise ValueError('already locked')
ValueError: already locked
TB2 STOP TYPING
Which implies that (perhaps this is related to the QtAgg
backend specifically?) the begin_typing
method on the "new" textbox is sometimes fired before the stop_typing
method on the "old" textbox. begin_typing
tries to take the keypresslock
, but the other textbox still holds it (it's released in stop_typing
).
Operating system
Arch Linux (6.12.7-arch1-1)
Matplotlib Version
3.10.0
Matplotlib Backend
QtAgg
Python version
No response
Jupyter version
No response
Installation
pip