Skip to content

Disable labels with the controls they are associated with#11812

Merged
feerrenrut merged 6 commits into
nvaccess:masterfrom
CyrilleB79:disableLabels
Nov 25, 2020
Merged

Disable labels with the controls they are associated with#11812
feerrenrut merged 6 commits into
nvaccess:masterfrom
CyrilleB79:disableLabels

Conversation

@CyrilleB79

Copy link
Copy Markdown
Contributor

Link to issue number:

Fixes #11809

Summary of the issue:

When some UI controls such as combo-box or edit field are disabled (greyed out), the associated label remains enabled. For low vision people this makes it harder to detect that the option has been disabled. Keeping the label enabled also lets the accelerator key active; thus pressing this acccelerator will make the focus jump to the next focusable control that is not disabled. This is an unexpected behaviour.

Description of how this pull request fixes the issue:

guiHelper.LabeledControlHelper.control now returns an object whose class inherit both from BaseLabeledControl and the wxCtrlClass class that was passed to the constructor.
This object behaves the same way as the wxCtrlClass except for Enable and Disable methods.
The new BaseLabeledControl class contains the code to manage enabling and disabling the (main) control as well as the associated label. See the example in the BaseLabeledControl doc string for usage.
Thus any labeled controls that was created by guiHelper.LabeledControlHelper in the UI disables or enables its associated label along with itself.

Testing performed:

In the Braille settings panel:

  • Played with the "Show cursor" and "Blink" options and checked visually in the 3 options below that the labels were disabled and enabled along with the control they are associated with.
  • For these 3 options, tried their access key and checked that the focus only moves if the option is enabled.

Notes

I am not an expert in advanced concepts ofOOP Python object programming. Thus, any comment to refine my code is welcome. Especially:

  • The check in the init method of BaseLabeledControl may be an indication that I did not use the appropriate OOP tools. Maybe abstract classes? I am not used to them.
  • Passing wxCtrlClass to the init method of BaseLabeledControl seems a bit strange to me. Indeed, BaseLabeledControl should always be subclassed to a class that should also inherit from wxCtrlClass. Thus the information seems rendundant. Any other way to do?

Moreover the group name in the advanced setting panel are not greyed out with the rest of the panel. I do not know how to grey it out and, reading the Wx Python documentation, it seems this is an intended behaviour. So I did not dig further.

Known issues with pull request:

None

Change log entry:

Not needed.

Cc @feerenrut for review.

@feerrenrut feerrenrut self-assigned this Nov 17, 2020
@feerrenrut feerrenrut self-requested a review November 17, 2020 07:59

@feerrenrut feerrenrut left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered using events for this? I think it might be simpler. I had a quick look, but didn't find an event for enable/disable, if there is not you could use a custom event.

See https://docs.wxpython.org/events_overview.html#events-and-event-handling

Comment thread source/gui/guiHelper.py
Comment thread source/gui/guiHelper.py Outdated
The code has totally been reworked according to @feerenrut's suggestion.
@CyrilleB79

Copy link
Copy Markdown
Contributor Author

@feerrenrut thanks for this review and the much better code suggestion. My initial code was clearly not correct regarding the missed new method.

On a functional point of view, I have just added a try/catch in the _onDestroy handler called when the label is destroyed:
When performing tests without this handler, I have encountered a RunTimeError. I have imagined that its cause was that the control was destroyed before the label, and thus _ctrl.Unbind was not working anymore.

  • Does this analysis seem correct to you?
  • In the except clause, should I put something else than just a pass statement? (log.debug, log.debugwarning)

@feerrenrut

Copy link
Copy Markdown
Contributor

Does this analysis seem correct to you?

Yes, it seems likely. The destroy event being used like this seems to be making things more complicated. It's a shame there is no 'isDestroyed' method on a wx.Window, let's add one to the label and then the handler for enableChanged can check if the label has already been destroyed.

		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)

@CyrilleB79

Copy link
Copy Markdown
Contributor Author

Not sure to understand.
Is there still an issue in my last code proposal (commit f3558d5)? The try/catch was aimed to avoid _ctrl.Unbind failing.

Are you fixing something else in your new proposal (in #11812 (comment))?

Or is it just to organize the code better, defining a subclass of wx.Label rather than binding sparse functions with events?

No problem to modify my code following your last suggestion. However, I just want to be sure to fully understand what I am committing!

@feerrenrut

Copy link
Copy Markdown
Contributor

Yes, this is just an alternative approach. I think it's more tidy, it means that the order of destruction doesn't matter anymore.

The core of the problem is understanding that the lifetime of the wx (c++ object) does not necessarily match the lifetime of the python object. I figure this is a neater solution because it doesn't tie the two python objects together in any additional way, like my first suggestion with the introduction of the label listening to the destroy event of the control. This new approach ensure that the label is only watching the control, but will not attempt to modify itself if the C++ object has been destroyed already.

@CyrilleB79

Copy link
Copy Markdown
Contributor Author

Thanks for this clear and detailed explanation.
The code has been updated according to this last suggestion.

@feerrenrut feerrenrut left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @CyrilleB79

@feerrenrut feerrenrut merged commit 001c99b into nvaccess:master Nov 25, 2020
@nvaccessAuto nvaccessAuto added this to the 2020.4 milestone Nov 25, 2020
@CyrilleB79 CyrilleB79 deleted the disableLabels branch November 25, 2020 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Some graphical elements are not disabled in setting panels when they should

3 participants