-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Font trait and parser #1042
Font trait and parser #1042
Conversation
VARIANTS = {'small-caps'} | ||
DECORATIONS = {'underline', 'strikethrough', 'overline'} | ||
NOISE = {'pt', 'point', 'px', 'family'} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to add an EBNF (or other) description of the grammar being parsed, either here or in the docs somewhere (or both)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably not worth it for this parser: it's more-or-less a "bag of words" designed to be backwards compatible with the other ad-hoc parsers in TraitsUI and Enable. It is probably a good idea to have the "words" documented somewhere.
Longer-term I do want a proper parser with a well-defined grammar, which is why the parser is designed to be able to be switched out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, then maybe just an informal description in the comments here? Otherwise there's no description of the intended behaviour outside the code itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've expanded the parser docstring accordingly.
pyface/tests/test_ui_traits.py
Outdated
) | ||
) | ||
|
||
def test_init_empty_string(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need a different method name here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM; a couple of comments
pyface/ui_traits.py
Outdated
if parser is not None: | ||
self.parser = parser | ||
if value is not None: | ||
font = self.validate(None, None, value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks as though this means that a validation error would be turned into a TraitError
. Would it make sense to explicitly raise ValueError
or TypeError
(as appropriate) instead? The error message right now isn't as helpful as it could be:
>>> PyfaceFont(b"times new roman")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/mdickinson/Enthought/ETS/pyface/pyface/ui_traits.py", line 212, in __init__
font = self.validate(None, None, value)
File "/Users/mdickinson/Enthought/ETS/pyface/pyface/ui_traits.py", line 231, in validate
self.error(object, name, value)
File "/Users/mdickinson/.venvs/pyface/lib/python3.9/site-packages/traits/base_trait_handler.py", line 74, in error
raise TraitError(
traits.trait_errors.TraitError: None
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There might be something that's worth looking into in Traits about why the error message isn't good, since calling validate
with None
object and None
trait name is done in a few places. It feels like this code might need an extra branch for that situation: https://github.com/enthought/traits/blob/d22ce1f096e2a6f87c78d7f1bb5bf0abab1a18ff/traits/trait_errors.py#L66-L94
I'll convert this to a ValueError
here for now.
elif word in DECORATIONS: | ||
decorations.add(word) | ||
else: | ||
if size is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may not matter much, but from reading the code it looks as though there's an inconsistency between the first-one-wins handling for size
and the last-one-wins handling for weight
, stretch
, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it's a bit weird. This parser is deliberately following the approach of the code here:
https://github.com/enthought/traitsui/blob/b7c38c7a47bf6ae7971f9ddab70c8a358647dd25/traitsui/qt4/font_trait.py#L92-L111
and here:
https://github.com/enthought/traitsui/blob/b7c38c7a47bf6ae7971f9ddab70c8a358647dd25/traitsui/wx/font_trait.py#L74-L91
The idea is that it should be possible to switch to this parser with a minimum of behaviour change; and then later we can have a "proper" well-defined parser which people can migrate to in a more controlled way.
Should |
Not for this PR, but it may be useful to add an >>> from pyface.ui_traits import PyfaceFont
>>> from traits.api import HasStrictTraits
>>> class A(HasStrictTraits):
... foo = PyfaceFont()
...
>>> a = A()
>>> a.observe(print, "foo")
>>> a.foo = "10 pt helvetica"
TraitChangeEvent(object=<__main__.A object at 0x12f19a540>, name='foo', old=Font(decorations=TraitSetObject(), family=['default'], size=12.0, stretch=100.0, style='normal', variants=TraitSetObject(), weight='normal'), new=Font(decorations=TraitSetObject(), family=['helvetica'], size=10.0, stretch=100.0, style='normal', variants=TraitSetObject(), weight='normal'))
>>> a.foo = "10 pt helvetica"
TraitChangeEvent(object=<__main__.A object at 0x12f19a540>, name='foo', old=Font(decorations=TraitSetObject(), family=['helvetica'], size=10.0, stretch=100.0, style='normal', variants=TraitSetObject(), weight='normal'), new=Font(decorations=TraitSetObject(), family=['helvetica'], size=10.0, stretch=100.0, style='normal', variants=TraitSetObject(), weight='normal'))
>>> a.foo = "10 pt helvetica"
TraitChangeEvent(object=<__main__.A object at 0x12f19a540>, name='foo', old=Font(decorations=TraitSetObject(), family=['helvetica'], size=10.0, stretch=100.0, style='normal', variants=TraitSetObject(), weight='normal'), new=Font(decorations=TraitSetObject(), family=['helvetica'], size=10.0, stretch=100.0, style='normal', variants=TraitSetObject(), weight='normal'))
>>> |
Not yet - but we should probably have an issue for this. |
I had written one at one point, but had decided against because I didn't want to break hashing - which I'm not sure how important that actually is, since I can't think of a specific situation where I'd want a font as the keys of a dictionary or have a set of them. |
Couldn't we just do the usual thing and implement |
But the |
Actually, thinking about this, having a trait change on the same string being passed in is probably the correct behaviour, because you are getting a new |
Done: #1071 |
Yep, agreed - if a |
This PR provides a
PyfaceFont
trait which is similar to theFont
traits in TraitsUI and Enable, but based on the PyfaceFont
class.This permits constructs like:
The value held by the trait is always a
Font
object.Compatibility with the TraitsUI and Enable font traits for string font descriptions is provided by a simple parser class which extends the parsers of those older traits to provide close, but not exact compatibility. The primary difference is that the new parser understands the additional attributes and values exposed by the
Font
class (eg. additional weight values, stretch, variants, etc.), but this means that it will interpret font names with those terms differently. The work around is to simply specify the font more precisely with aFont
object if that level of precision is needed, or replace with a fully backwards compatible parser.The parser used by the Trait can be easily switched with a more (or less) capable parser if desired.
To-do:
Fixes #476 (Color traits are already done)
Other relevant issues: #609 (Font class PR), #520 (older attempt), #600 (there is more work to be done on the parsing issue before this can be closed)