custom widgets in enaml

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

custom widgets in enaml

Matthew Scott
As one leg of my evaluation and experimentation with enaml, I'm using it to create a custom browser for a friend of mine to facilitate the screen-scraping and light data processing he does in his line of work.  The project is in very early stages but is available at https://github.com/11craft/kilometer-browser/

I'm posting my notes here in hopes of helping other enaml users, and to see if the core developers have any feedback or corrections.

A custom browser necessitated creating a custom enaml component to wrap QtWebKit.QWebView.  Here's my progress so far:

The browser itself and the startup script:

Some notes about the experience so far:

- The docs for adding new widgets are helpful and generally complete

- Regarding step 4 of the aforementioned docs on adding new widgets, be aware that this registration must occur before importing any enaml file that makes use of your components. In particular, it is not sufficient to import a module to kick off this process at the top of such an enaml file.  I solved this by creating a short "if main" section in a py file, which first ensures custom component registration, then includes the enaml file and shows its Main component.

- When debugging, IDs do not appear to be stored in the resulting tree created by an enamldef.  If you want to inspect a component in a shell, assign its name attribute, then at runtime use the find_by_name() method on a parent component to find it:

# twolabels.enaml
enamldef Main(MainWindow):
    Container:
        Label:
            name = 'label1'
            text = 'Label 1'
        Label:
            name = 'label2'
            text = 'Label 2'

# client code (python, not enaml)
import enaml
with enaml.imports():
    from two labels import Main
main = Main()
label_1 = main.find_by_name('label1')
label_2 = main.find_by_name('label2')


-- 
Matthew Scott
ElevenCraft Inc.


_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: custom widgets in enaml

Chris Colbert
My comments are inline

On Sun, Jun 24, 2012 at 2:12 PM, Matthew Scott <[hidden email]> wrote:
As one leg of my evaluation and experimentation with enaml, I'm using it to create a custom browser for a friend of mine to facilitate the screen-scraping and light data processing he does in his line of work.  The project is in very early stages but is available at https://github.com/11craft/kilometer-browser/

I'm posting my notes here in hopes of helping other enaml users, and to see if the core developers have any feedback or corrections.

A custom browser necessitated creating a custom enaml component to wrap QtWebKit.QWebView.  Here's my progress so far:

This looks pretty good.

One comment, don't override the size_hint() method. It's pretty integral to Enaml functioning correctly. Instead,
manipulate the attribute 'hug' and 'resist_clip' on the Enaml component to affect how the constraints respond to the size hint provided from Qt.

If you want the webview to freely expand:

class WebView(Control):
    hug_width = 'ignore'
    hug_height = 'ignore'

You can do the same thing with resist_clip if you want the web view to freely shrink.

By defining the policies in this way, you allow the user to override them in their Enaml code and customize how the control resizes.

 

The browser itself and the startup script:


If you do the above wrt to the hug policies, you won't need the constraints on width and height in your browser example.
 
Some notes about the experience so far:

- The docs for adding new widgets are helpful and generally complete

- Regarding step 4 of the aforementioned docs on adding new widgets, be aware that this registration must occur before importing any enaml file that makes use of your components. In particular, it is not sufficient to import a module to kick off this process at the top of such an enaml file.  I solved this by creating a short "if main" section in a py file, which first ensures custom component registration, then includes the enaml file and shows its Main component.

An easier way is to just create your own toolkit and add to that, then enter that toolkit context when creating your view:

if __name__ = '__main__':
    import enaml
    
    with enaml.imports():
        from browser import Browser
       
    from my_webview import webview_ctor

    tk = enaml.qt_toolkit()
    tk['WebView'] = webview_ctor

    with tk:
        brwsr = Browser()

    brwsr.show()
 

- When debugging, IDs do not appear to be stored in the resulting tree created by an enamldef.  If you want to inspect a component in a shell, assign its name attribute, then at runtime use the find_by_name() method on a parent component to find it:


Correct, think of id's as a local variable declaration in the current enamldef block. Since you can refer to the same component instance in different enamldef blocks, with potentially different id's, it wouldn't make sense to store the
id on the component itself (just as it doesn't make sense to store the variable name on a Python object).

The use case you mention is exactly why the 'name' attribute exists on a component. However, it's still not as easy as i'd like, and I'm not convinced its any better than this:

enamldef Main(MainWindow):
    attr label1 = label1
    Container:
        Label:
            id: label1

import enaml
with enaml.imports():
    from labels import Main
main = Main()
label1 = main.label1
 
 
I'm open to suggestions on this front.


# twolabels.enaml
enamldef Main(MainWindow):
    Container:
        Label:
            name = 'label1'
            text = 'Label 1'
        Label:
            name = 'label2'
            text = 'Label 2'

# client code (python, not enaml)
import enaml
with enaml.imports():
    from two labels import Main
main = Main()
label_1 = main.find_by_name('label1')
label_2 = main.find_by_name('label2')


-- 
Matthew Scott
ElevenCraft Inc.


_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev



Chris

_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: custom widgets in enaml

Chris Colbert


On Sun, Jun 24, 2012 at 3:33 PM, Chris Colbert <[hidden email]> wrote:
My comments are inline

On Sun, Jun 24, 2012 at 2:12 PM, Matthew Scott <[hidden email]> wrote:
As one leg of my evaluation and experimentation with enaml, I'm using it to create a custom browser for a friend of mine to facilitate the screen-scraping and light data processing he does in his line of work.  The project is in very early stages but is available at https://github.com/11craft/kilometer-browser/

I'm posting my notes here in hopes of helping other enaml users, and to see if the core developers have any feedback or corrections.

A custom browser necessitated creating a custom enaml component to wrap QtWebKit.QWebView.  Here's my progress so far:

This looks pretty good.

One comment, don't override the size_hint() method. It's pretty integral to Enaml functioning correctly. Instead,
manipulate the attribute 'hug' and 'resist_clip' on the Enaml component to affect how the constraints respond to the size hint provided from Qt.

If you want the webview to freely expand:

class WebView(Control):
    hug_width = 'ignore'
    hug_height = 'ignore'

You can do the same thing with resist_clip if you want the web view to freely shrink.

By defining the policies in this way, you allow the user to override them in their Enaml code and customize how the control resizes.
If you do the above wrt to the hug policies, you won't need the constraints on width and height in your browser example.
 
Some notes about the experience so far:

- The docs for adding new widgets are helpful and generally complete

- Regarding step 4 of the aforementioned docs on adding new widgets, be aware that this registration must occur before importing any enaml file that makes use of your components. In particular, it is not sufficient to import a module to kick off this process at the top of such an enaml file.  I solved this by creating a short "if main" section in a py file, which first ensures custom component registration, then includes the enaml file and shows its Main component.

An easier way is to just create your own toolkit and add to that, then enter that toolkit context when creating your view:

if __name__ = '__main__':
    import enaml
    
    with enaml.imports():
        from browser import Browser
       
    from my_webview import webview_ctor

    tk = enaml.qt_toolkit()
    tk['WebView'] = webview_ctor

    with tk:
        brwsr = Browser()

    brwsr.show()
 

- When debugging, IDs do not appear to be stored in the resulting tree created by an enamldef.  If you want to inspect a component in a shell, assign its name attribute, then at runtime use the find_by_name() method on a parent component to find it:


Correct, think of id's as a local variable declaration in the current enamldef block. Since you can refer to the same component instance in different enamldef blocks, with potentially different id's, it wouldn't make sense to store the
id on the component itself (just as it doesn't make sense to store the variable name on a Python object).

The use case you mention is exactly why the 'name' attribute exists on a component. However, it's still not as easy as i'd like, and I'm not convinced its any better than this:

enamldef Main(MainWindow):
    attr label1 = label1
    Container:
        Label:
            id: label1

import enaml
with enaml.imports():
    from labels import Main
main = Main()
label1 = main.label1
 
 
I'm open to suggestions on this front.


# twolabels.enaml
enamldef Main(MainWindow):
    Container:
        Label:
            name = 'label1'
            text = 'Label 1'
        Label:
            name = 'label2'
            text = 'Label 2'

# client code (python, not enaml)
import enaml
with enaml.imports():
    from two labels import Main
main = Main()
label_1 = main.find_by_name('label1')
label_2 = main.find_by_name('label2')


-- 
Matthew Scott
ElevenCraft Inc.


_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev



Chris


Also, once you think the WebView is good to go, please consider contributing it back to enaml via a PR. We've been wanting a WebView constrol, but haven't gotten around to it.

_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: custom widgets in enaml

Matthew Scott
On Sunday, June 24, 2012 at 14:35, Chris Colbert wrote:

One comment, don't override the size_hint() method. It's pretty integral to Enaml functioning correctly. Instead,
manipulate the attribute 'hug' and 'resist_clip' on the Enaml component to affect how the constraints respond to the size hint provided from Qt.
Thanks for the tips about hug_* and resist_clip_* attributes.  I still need to do some deeper reading and understanding about how those work, but now I'm closer.  :)



An easier way is to just create your own toolkit and add to that, then enter that toolkit context when creating your view:

if __name__ = '__main__':
    import enaml
    
    with enaml.imports():
        from browser import Browser
       
    from my_webview import webview_ctor

    tk = enaml.qt_toolkit()
    tk['WebView'] = webview_ctor

    with tk:
        brwsr = Browser()

    brwsr.show()
The use case I was having trouble with, actually, was that I couldn't put something like this into "browser.enaml" and then run "enaml-run kmb/components/browser.enaml":

import kmb.components  # registers 'WebView' component
enamldef Browser(...):
   # ...
   WebView:
       # ...
enamldef Main(MainWindow):
    Container:
        Browser:
            pass

I just get a traceback like this:

Traceback (most recent call last):
  File "/Users/gldnspud/env/miles/bin/enaml-run", line 9, in <module>
    load_entry_point('enaml==0.2.1', 'console_scripts', 'enaml-run')()
  File "/Users/gldnspud/env/miles/src/enaml/enaml/runner.py", line 120, in main
    window = component()
  File "/Users/gldnspud/env/miles/src/enaml/enaml/core/factory.py", line 41, in __call__
    component = self.__enaml_call__()
  File "/Users/gldnspud/env/miles/src/enaml/enaml/core/factory.py", line 69, in __enaml_call__
    component = self.build(identifiers, toolkit)
  File "/Users/gldnspud/env/miles/src/enaml/enaml/core/factory.py", line 129, in build
    return self.__func__(identifiers, toolkit)
  File "kmb/components/webview_test.enaml", line 3, in Main
    enamldef Main(MainWindow):
  File "kmb/components/webview_test.enaml", line 1, in <module>
    import kmb.components
NameError: name 'WebView' is not defined

I really like being able to include a Main component in an .enaml file, so I can use enaml-run as a way to test individual components.

However if those .enaml files make use of custom components such as this WebView example, doing so is not possible from my experience so far.

Is there (or can there be) a way to use enaml-run in this manner?


 
The use case you mention is exactly why the 'name' attribute exists on a component. However, it's still not as easy as i'd like, and I'm not convinced its any better than this:

enamldef Main(MainWindow):
    attr label1 = label1
    Container:
        Label:
            id: label1

import enaml
with enaml.imports():
    from labels import Main
main = Main()
label1 = main.label1

Now that you show this example, I like it better at first glance for what I was using it for.

Seems like having an optional .name for every widget could be useful for other situations though, e.g. a component inspector tool: if any widget could be assigned a name, it could help build a more useful tree in such an inspector.

 

Thanks,
- Matthew


_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: custom widgets in enaml

Chris Colbert


On Thu, Jun 28, 2012 at 8:59 PM, Matthew Scott <[hidden email]> wrote:
On Sunday, June 24, 2012 at 14:35, Chris Colbert wrote:

One comment, don't override the size_hint() method. It's pretty integral to Enaml functioning correctly. Instead,
manipulate the attribute 'hug' and 'resist_clip' on the Enaml component to affect how the constraints respond to the size hint provided from Qt.
Thanks for the tips about hug_* and resist_clip_* attributes.  I still need to do some deeper reading and understanding about how those work, but now I'm closer.  :)


Yep, this looks better.
 


An easier way is to just create your own toolkit and add to that, then enter that toolkit context when creating your view:

if __name__ = '__main__':
    import enaml
    
    with enaml.imports():
        from browser import Browser
       
    from my_webview import webview_ctor

    tk = enaml.qt_toolkit()
    tk['WebView'] = webview_ctor

    with tk:
        brwsr = Browser()

    brwsr.show()
The use case I was having trouble with, actually, was that I couldn't put something like this into "browser.enaml" and then run "enaml-run kmb/components/browser.enaml":

import kmb.components  # registers 'WebView' component
enamldef Browser(...):
   # ...
   WebView:
       # ...
enamldef Main(MainWindow):
    Container:
        Browser:
            pass

I just get a traceback like this:

Traceback (most recent call last):
  File "/Users/gldnspud/env/miles/bin/enaml-run", line 9, in <module>
    load_entry_point('enaml==0.2.1', 'console_scripts', 'enaml-run')()
  File "/Users/gldnspud/env/miles/src/enaml/enaml/runner.py", line 120, in main
    window = component()
  File "/Users/gldnspud/env/miles/src/enaml/enaml/core/factory.py", line 41, in __call__
    component = self.__enaml_call__()
  File "/Users/gldnspud/env/miles/src/enaml/enaml/core/factory.py", line 69, in __enaml_call__
    component = self.build(identifiers, toolkit)
  File "/Users/gldnspud/env/miles/src/enaml/enaml/core/factory.py", line 129, in build
    return self.__func__(identifiers, toolkit)
  File "kmb/components/webview_test.enaml", line 3, in Main
    enamldef Main(MainWindow):
  File "kmb/components/webview_test.enaml", line 1, in <module>
    import kmb.components
NameError: name 'WebView' is not defined

I really like being able to include a Main component in an .enaml file, so I can use enaml-run as a way to test individual components.

However if those .enaml files make use of custom components such as this WebView example, doing so is not possible from my experience so far.

Is there (or can there be) a way to use enaml-run in this manner?



enaml-run looks for one of two things when running an .enaml file:

  1. An enamldef specified by the --component flag. This defaults to "Main". Said component is instantiated with 0 arguments followed by a call to .show()
  2. If no component is found in step #1, the module is searched for a symbol named "main". If found, it is expected to be a callable which takes 0 arguments and does whatever you want. 

Given #2, you can do things like this:

enamldef Whatever(Window):
    ...

def main():
   <assemble my_toolkit>
   with my_toolkit:
       view = Whatever()
   view.show()

This method will allow you to assemble whatever toolkit you want as a custom thing. We generally consider it bad-form to modify the default toolkits. The whole purpose behind making toolkits simply dict subclasses with context methods, was that its easy to create and customize them. In your case, enaml-run doesn't know anything about custom toolkits, so you would have to use the approach of #2 to get the behavior you desire.


_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev