11"""This module implements a multi line editing text box"""
22from __future__ import annotations
33from copy import copy
4+ from functools import partial
45from logging import getLogger
56from typing import TYPE_CHECKING , Callable , List , Optional , Tuple , Union
7+ from wrapt import ObjectProxy
68from asciimatics .event import KeyboardEvent , MouseEvent , Event
79from asciimatics .screen import Screen
810from asciimatics .strings import ColouredText
1517logger = getLogger (__name__ )
1618
1719
20+ class _ListWrapper (ObjectProxy ): # pylint: disable=abstract-method
21+ """
22+ A proxy for list objects.
23+
24+ This class can be returned by the `value` property, allowing the caller to modify it via standard
25+ list modifications. For example, `textbox.value.append("foo")` will now update the value of the textbox.
26+ """
27+
28+ def __init__ (self , lst : List , parent : TextBox ):
29+ super ().__init__ (lst )
30+ self ._self_parent = parent
31+
32+ def append (self , stuff ):
33+ self ._self_parent .proxy_update (partial (self .__wrapped__ .append , stuff ))
34+
35+ def extend (self , stuff ):
36+ self ._self_parent .proxy_update (partial (self .__wrapped__ .extend , stuff ))
37+
38+ def insert (self , key , value ):
39+ self ._self_parent .proxy_update (partial (self .__wrapped__ .insert , key , value ))
40+
41+ def __setitem__ (self , key , value ):
42+ self ._self_parent .proxy_update (partial (self .__wrapped__ .__setitem__ , key , value ))
43+
44+ def __delitem__ (self , key ):
45+ self ._self_parent .proxy_update (partial (self .__wrapped__ .__delitem__ , key ))
46+
47+ def clear (self ):
48+ self ._self_parent .proxy_update (self .__wrapped__ .clear )
49+
50+
1851class TextBox (Widget ):
1952 """
2053 A TextBox is a widget for multi-line text editing.
@@ -146,8 +179,10 @@ def reset(self):
146179 self ._start_column = 0
147180 if self ._auto_scroll or self ._line > len (self ._value ) - 1 :
148181 self ._line = len (self ._value ) - 1
149-
150- self ._column = 0 if self ._is_disabled else len (self ._value [self ._line ])
182+ try :
183+ self ._column = 0 if self ._is_disabled else len (self ._value [self ._line ])
184+ except IndexError :
185+ self ._column = 0
151186 self ._reflowed_text_cache = None
152187
153188 def _change_line (self , delta : int ):
@@ -347,10 +382,13 @@ def auto_scroll(self, new_value):
347382 def value (self ):
348383 """
349384 The current value for this TextBox.
385+
386+ NOTE: this now uses a proxy to allow insertion and other modifications.
350387 """
351388 if self ._value is None :
352389 self ._value = ["" ]
353- return "\n " .join ([str (x ) for x in self ._value ]) if self ._as_string else self ._value
390+ return "\n " .join ([str (x )
391+ for x in self ._value ]) if self ._as_string else _ListWrapper (self ._value , self )
354392
355393 @value .setter
356394 def value (self , new_value ):
@@ -360,8 +398,23 @@ def value(self, new_value):
360398 new_value = ["" ]
361399 elif self ._as_string :
362400 new_value = new_value .split ("\n " )
401+ elif isinstance (new_value , _ListWrapper ):
402+ new_value = new_value .__wrapped__
363403 self ._value = new_value
404+ self ._handle_changes (old_value )
405+
406+ def proxy_update (self , make_update ):
407+ """
408+ Handle an update from the proxy value property object.
409+
410+ This is an internal method that processes potential updates to the value of the TextBox. It
411+ should never be called directly by an application.
412+ """
413+ old_value = list (self ._value )
414+ make_update ()
415+ self ._handle_changes (old_value )
364416
417+ def _handle_changes (self , old_value ):
365418 # TODO: Sort out speed of this code
366419 if self ._parser :
367420 new_value = []
0 commit comments