Repository: nvaccess/nvda

Issue:
Some graphical elements are not disabled in setting panels when they should

### Steps to reproduce:

1. Open braille setting panel
2. Uncheck "Show cursor"
3. Press Alt+F

### Actual behavior:

At step 2, the edit field "Cursor blink rate (ms)" and the combo boxes "Cursor shape for focus:" and "Cursor shape for review:" are greyed out but not their associated label. For people with low vision, it is not so clear that these items are disabled.
At step 3, the focus movesto "Message timeout (sec)" field, whereas the acelerator key is alt+T, not alt+F.

### Expected behavior:

At step 2., the labels should also be greyed out.
At step 3, the focus should not move since "Cursor shape for focus:" combo-box (having alt+f accelerator) is disabled.

### System configuration
#### NVDA installed/portable/running from source:
Any
#### NVDA version:
2020.3
#### Windows version:
10
#### Name and version of other software in use when reproducing the issue:

#### Other information about your system:

### Other questions
#### Does the issue still occur after restarting your computer?
Yes
#### Have you tried any other versions of NVDA? If so, please report their behaviors.
2020.2 with same issue
#### If addons are disabled, is your problem still occuring?
Yes
#### Did you try to run the COM registry fixing tool in NVDA menu / tools?
No

### Note

I have already a prototype branch to fix this issue. Coming soon.




Discussion:

Resolving patch:
diff --git a/source/gui/guiHelper.py b/source/gui/guiHelper.py
--- a/source/gui/guiHelper.py
+++ b/source/gui/guiHelper.py
@@ -45,7 +45,7 @@ def __init__(self,parent):
 from contextlib import contextmanager
 
 import wx
-from wx.lib import scrolledpanel
+from wx.lib import scrolledpanel, newevent
 from abc import ABCMeta
 
 #: border space to be used around all controls in dialogs
@@ -159,17 +159,49 @@ class LabeledControlHelper(object):
 	the two controls together.
 	Relies on guiHelper.associateElements(), any limitations in guiHelper.associateElements() also apply here.
 	"""
-	def __init__(self, parent, labelText, wxCtrlClass, **kwargs):
+	
+	# When the control is enabled / disabled this event is raised.
+	# A handler is automatically added to the control to ensure the label is also enabled/disabled.
+	EnableChanged, EVT_ENABLE_CHANGED = newevent.NewEvent()
+
+	def __init__(self, parent: wx.Window, labelText: str, wxCtrlClass: wx.Control, **kwargs):
 		""" @param parent: An instance of the parent wx window. EG wx.Dialog
 			@param labelText: The text to associate with a wx control.
-			@type labelText: string
 			@param wxCtrlClass: The class to associate with the label, eg: wx.TextCtrl
-			@type wxCtrlClass: class
 			@param kwargs: The keyword arguments used to instantiate the wxCtrlClass
 		"""
 		object.__init__(self)
-		self._label = wx.StaticText(parent, label=labelText)
-		self._ctrl = wxCtrlClass(parent, **kwargs)
+
+		class LabelEnableChangedListener(wx.StaticText):
+			isDestroyed = False
+			isListening = False
+
+			def _onDestroy(self, evt: wx.WindowDestroyEvent):
+				self.isDestroyed = True
+
+			def listenForEnableChanged(self, _ctrl: wx.Window):
+				self.Bind(wx.EVT_WINDOW_DESTROY, self._onDestroy)
+				_ctrl.Bind(LabeledControlHelper.EVT_ENABLE_CHANGED, self._onEnableChanged)
+				self.isListening = True
+
+			def _onEnableChanged(self, evt: wx.Event):
+				if self.isListening and not self.isDestroyed:
+					self.Enable(evt.isEnabled)
+
+		class WxCtrlWithEnableEvnt(wxCtrlClass):
+			def Enable(self, enable=True):
+				evt = LabeledControlHelper.EnableChanged(isEnabled=enable)
+				wx.PostEvent(self, evt)
+				super().Enable(enable)
+
+			def Disable(self):
+				evt = LabeledControlHelper.EnableChanged(isEnabled=False)
+				wx.PostEvent(self, evt)
+				super().Disable()
+
+		self._label = LabelEnableChangedListener(parent, label=labelText)
+		self._ctrl = WxCtrlWithEnableEvnt(parent, **kwargs)
+		self._label.listenForEnableChanged(self._ctrl)
 		self._sizer = associateElements(self._label, self._ctrl)
 
 	@property


Test patch: