06 March
2008
Digg digg it  |  Slashdot slashdot.org  |  Reddit reddit  |  del.icio.us del.icio.us  |  Technorati Technorati

HyperCard, Python, Views and a little sip of Pan Galactic Gargle Blaster (be careful...)

Been a big fan of HyperCard, it's not so strange I ended up implementing my own reinterpretation of it for the Devil Framework. Lets take a look....

1989 The Year We Make Contact

I remember that the first time I had the chance to lay my hands on a real Macintosh I felt in love...but not with the machine itself. Yes its user interface and design were mind blowing, MacWrite and MacPaint were so beautiful and...., well, there wasn't much more. Except HyperCard. That was a piece of software to love. It was so simple and so powerful and so innovative! I wanted it...but I had no Macintosh, just my beloved Amiga 500 (pictured below at the top of the Tower of Elders. One minute of respectful silence, please).

The Tower of Elders and Octane the Second

2002 Escape From Boredom

In a moment of boredom while toying with wxWindows (at that time, the GUI toolkit I was considering for our system) I came up with the idea of reimplementing HyperCard. The experiment went not so far; I got nothing more than a simple "live" GUI editor, but it was a start and a seed for better things to come.

2004 A Widget Odyssey

The need to implement an user interface design tool became a necessity almost at the beginning of the development of the Devil Framework: the system is designed around the idea of process control, and when you need to interface humans with a process, you need some graphical user interface. To get some inspiration and ideas I looked at the various HMI provided by SCADA systems (like WinCC, Wonderware, etc.), GUI builders (especially, and obviously, Qt Designer) and, last but not least, HyperCard and its offspring.

Lets take a look at the result.

A View That Looks At You

The good old HAL 9000 looks and speaks to you.

You can think at Views as HTML pages "on steroids" or HyperCard cards without the data storage. In a view you can:

  • Insert widgets. Widget are the base components of a view. By default you get almost any widgets defined in the Qt library (like buttons, combo boxes, etc.), plus a lot of not so standard ones (like MatPlot charts, OpenInventor 3D visualization, LEDs, video sources, etc.). New ones can be easily installed by plugins and made automatically and transparently available to all users (and indeed plugins like the AlarmsManager or the TagsManager expose a lot of them). A view is itself a special kind of widget that provides some additional functionalities (like timers, global actions and background "pipelines" and that can be embedded in (and not only) another view.

  • Manage layouts. Views provide a simple and powerful way of specifying the layout of child widgets (thanks to the Qt layout management system). Beside absolute placement, you can organize widgets into hierarchical groups that let you automatically organize widgets horizontally, vertically, in a two dimensional grid and within horizontal or vertical splitters. In the HAL 9000 view above, the eye and the text are placed in a vertical layout: changing the size of the window changes the size of the red eye to fill all available space.

  • Group widgets. After having assigned a set of widgets to a group you can consider them as a single macro widget. You can also place widgets into layers. A layer is a "lesser" group; widgets are not binded together but operations performed on a layer are propagated to all widgets in it.
  • Define "methods". If you consider a view to be like Python class, you can think at methods as class methods. You can define global methods (available to all views) and view/widget methods (available to all widgets inside the view or the widget). The only significant difference with standard Python class methods is that when a method is called it is searched not only in the calling widget, but also in all the parent ones up to the globals space. An example: lets say a global method named "test.echo" is defined and in a view there's a button widget with the "clicked" signal handler (see below) defined as "env.view.methods.test.echo ('Hello')". When the signal handler is invoked, the "test.echo" method is searched first in the button widget, than in the containing view (or any other parent widget) and finally in the global methods space.

  • Define "signal handlers". All views and widgets emit signals when the a user performs some action on them (like clicking the mouse button) or when some automatic event is triggered (like timer timeouts). You can bind code to be executed when one of this signals is emitted. A special method is the "init" one (called at view initialization time): every global defined here will be made available in all other signal handlers.

  • Actions: for almost any view and widget you can define popup menus with custom actions. Within a view you can also define menu and toolbar actions. In the HAL 9000 example a custom menu named "HAL" is defined together with a custom toolbar button: they give you the incredible chance to hear HAL speak!

Press CTRL-P to hear HAL.

Views are quite flexible in the way you can use and display them:

  • The most "natural" way is to use them in a Views Browser window.

  • You can also open a view from another as a dialog: you can pass an get data to an from the dialog.

  • Views can be embedded into another view. Child view's actions are blended with the parent ones.

  • You can insert a view inside any other GUI widget using the included ViewsPanel class. View actions will be added to the parent window (if not specified otherwise).

Live Editing!

Here you can see where HyperCard has really influenced me: a view can be edited whenever you like, live (if you have permissions to do so). Just click the "Toggle edit mode" button and the Views Browser transforms itself into the Views Editor.

Warning, preparing a Pan Galactic Gargle Blaster can bring the system to auto-destruction!

If you know the Qt Designer tool you'll feel right at home. You can add widgets by selecting the desired one from the widgets list and "paint" it into the view. Widget properties can be edited interactively from the properties panel. Double clicking on a widget opens up a configuration dialog from where you have access to more advanced functionalities. The common ones are:

When you have made your changes, click the "Toggle edit mode" button and test the "updated" view right away. Simple, eh?

And whatever incredible and disruptive stunt you have performed on a view you can safely save it without warring about creating problems to other users: the modified version is visible to you only. When you'll feel comfortable to put it in production you'll just "commit" it. From then on the new version becomes the one used by everybody (you can always check out an older committed version, edit it, and make it the production one).

Dynamic Views

The Views Manager can also serve views generate on the fly. The following is a simple example of a dynamic view with two buttons in a layout:

# define the view generator code as a string >>> view_generator = '''
# The following code is executed on the server when a client requests the 
# associated view.
# Locals: 'view_name', 'view_uid', 'user_info', 'env' and 'sys'.
# Returns: a view definition

from DLevel.Devil.Common import Cons
from DLevel.Devil.Common.Plugin.ViewCompiler import ViewCompiler

vc = ViewCompiler ()
vc.set_name (view_name)

# add a layout to binded to the view's root
vc.add_widget ('/v_layout_1', 'layout', layout_type='vertical')
vc.set_root_layout ('/v_layout_1', True)

# add a button
vc.add_widget ('/v_layout_1/b1', 'push_button')
# set button text
vc.set_property ('/v_layout_1/b1', 'text', 'Welcome, %s.' % user_info[Cons.RPCServer.INFO_USER_NAME])
# add button to parent layout
vc.add_widget_to_layout ('/v_layout_1/b1')

# define another button and pass properties to be set
vc.add_widget ('/v_layout_1/b2', 'push_button', text='Button 2')
vc.add_widget_to_layout ('/v_layout_1/b2')

return vc.dump_widgets ()
'''

# create a new view
>>> uid = env.api.views.new ('/test/dynamic_view')
# assign the view generator string to the 'view_generator' metadata. That's it!
>>> env.api.views[uid].set_metadata ('view_generator', view_generator)

The dynamic view defined in the code above.

Obviously you can't edit a dynamic view within the Views Editor. More precisely, you can edit it and either save it, but when you'll re-open it, you'll get the dynamic view (except if there's an error in the view generator code; in this case you get the modified view you have saved).

For Your Eyes Only

As almost everything else in the Devil Framework, views are resources. This simple fact lets you finely tune access using the advanced features provided by the authorization sub-system. But this is a complex argument that will need a post on its own, may be from the author itself in its new, shiny, glamorous, super-beautiful and completely empty blog (but take a look at it nevertheless, as the cool logo and picture are made by me, the new Leonardo da Vinci :-D)

Another Minute Of Respectful Silence

The first picture in this post was about some really venerable and cool piece of hardware. I want to finish with a picture of a really venerable and cool piece of software. Bow and show respect ;-)

No comment needed.


Category Devil Framework 
Posted by alex at 00:44 | Comments (0) | Trackbacks (0)
<< What happens when Python remembers me that programming is fun? | Main | Atomic Batteries Included >>
Comments
There is no comment.
Trackbacks
Please send trackback to:http://www.dlevel.com/blogs/alex/26/tbping
There is no trackback.