Generating XUL with Python

  • Revision slug: My_Chrome_Oven/Generating_XUL_with_Python
  • Revision title: Generating XUL with Python
  • Revision id: 190148
  • Created:
  • Creator: Andreas Wuest
  • Is current revision? No
  • Comment Migrated from http://www.mozilla.org/docs/xul/xulnotes/xulnote_oven.html

Revision Content

Caveat lector: This article does not describe any actual bindings between XUL and the Python language. I'm just fooling around here, technically speaking. There's been some talk recently about generalizing XPConnect so that the Mozilla APIs described in XPIDL/XPCOM -- which is just about all of them -- could be accessible to a scripting language like Python, and vice versa. This would take a lot of work, and Mozilla isn't anywhere near that now.

In the meantime, hacks like the one described in this article can help familiarize you with the environment of XPFE development and may inspire you to do your own investigation. In fact, even if you don't know Python, this article may help suggest some simple ways to get at Gecko without rebuilding or implementing your own application core.

Introduction

This article describes a Python module that creates and displays XUL windows. If you are familiar with some of the GUI packages that are used with Python, such as Tkinter and wxWindows, then the following Python code will look somewhat familiar to you:

>>> from XUL import *
>>> myXWin = XWindow('XUL Pie', 100, 200)
>>> myXT1 = XText('Hello World!', bclass='marquee')
>>> myXB1 = XButton('Quit', oncommand='window.close()')
>>> myXWin.Bake(myXT1, myXB1)
>>> myXWin.Serve()

Using the xul module, the Serve() function from the example above tells Mozilla to display a XUL window like the following:

Mozilla displays only this window and not the browser. As you can see from the code, the XUL file generated by the Python code contains three widgets: the parent window, a <text> widget, and a <button> widget, all of which are "baked" together into a single XUL file and rendered by Gecko, the rendering engine inside Mozilla just waiting around for extra activities like this. The Python module doesn't provide a lot of widgetry or detail and so the resulting XUL file is sort of simplistic, but the interaction of these two technologies opens up some conspiratorial possibilities for XPFE development: both Python and XUL are truly cross-platform; you can easily create new packages in Mozilla and then use those packages as standalones; Python may one day be XPConnect-able -- which would be an incredible, Prometheus-stealing-fire-from-the-gods-like boon for people like me who aren't very good at systems programming languages; and so on. The mind reels.

In the next few sections, we describe how to set up the environment for this sort of window building (i.e., the "oven" from the title of this article), and we examine the Python module and the generated XUL.

The Oven: Creating Your Own Package Testbed

In order to use this Python module, you have to make a couple of changes to your environment. I think that these changes are good generally -- especially if you are going to be fooling around with Mozilla a lot, as opposed to simply using it to browse the web.

Adding the Mozilla\bin Directory To Your Path

However it's done on your current platform, add Mozilla's bin directory to your path so you can bring it up from the command line without having to qualify it with the full, inconvenient path.

On Windows98, I added the following bit to my autoexec.bat file:

C:\Progra~1\Netscape\Mozilla\bin\

Once this is set (and you have either restarted or re-run the autoexec.bat from the command prompt), you can bring up Mozilla by just entering Mozilla at the prompt.

Displaying Chromes Other Than Mozilla

Once you have Mozilla this close to your keyboard, the next step is to incorporate the special -chrome flag into your Mozilla invocations. When you start Mozilla with the command Mozilla, it's exactly as if you are starting it by double-clicking an icon. When you use the -chrome flag, however, you are telling Mozilla you want to display a chrome other than the default. The following line, for example, brings up the search component as a separate, single window:

mozilla -chrome "chrome://search/content"

The search component is available for this sort of option because it is a package within the package hierarchy of the Mozilla application. Though the chrome:// type url is pointing to a single XUL file (a XUL file named after the package in which it resides is presumed in the chrome:// url, so the line above is equivalent to chrome://search/content/search.xul), it's really pointing out the whole chrome to which that file belongs. When the search.xul file is loaded, it loads all sorts of other data from the package, including the skin, overlays, localization strings, and other content. These are all part of the chrome.

Creating a New Package

The chrome is not magic. Not all magic, anyway. Mozilla provides this special url type and package hierarchy as a convenience mechanism that you can easily appropriate for your own purposes. In order to make the debugging and testing of the Python module easier for myself, I simply created a new package into which I could render the XUL and point the rendering engine. This package testbed, described in this section, is a very good way to open up Mozilla and see some of its flexibility and power in action.

To create a new package, cd to the chrome subdirectory in Mozilla. On my system, that directory is located here:

C:\Program Files\Netscape\Mozilla\bin\chrome\

Create a new directory there and name it "oven". Under oven, create three subdirectories: "content", "skin", and "locale". Under each of these subdirectories add more directories until you have the following basic structure:

oven/
  content/
    default/
  skin/
    default/
  locale/
    US-en/

This structure is what Mozilla expects when it looks into a package, so make sure everything is named and situated properly. The files you generate from the Python module are each called "oven.xul" and are placed, destructively, in the content\default\ subdirectory of the oven as they are created.

Creating a Mini Skin for the Oven

Before you use the XUL Mini-Builder module, you must creat a skin for oven chrome that correlates with the classes of widget that the module is creating. You can extend the skin if you want, but I have only defined a single style in the oven skin. The style is a blue, heading-like text style for text widgets of the class "marquee", like the one in the example at the beginning of this article. But like all good skins, the oven skin loads the global skin, so the basic look and behavior of the XUL widgets realized in the Python module are handled there.

The skin for the oven, oven.css, reads as follows:

@import url(chrome://global/skin/);

text.marquee {
    font-size: 18pt;
    color: darkblue;
    font-weight: bold;
    font-style: italic;
    text-alignment: center;
}

Once the package structure and the skin file are in place, the module can skin the generated XUL --- just to skip ahead a little -- by writing the following line at the top:

<?xml-stylesheet href="chrome://oven/skin" type="text/css"?>

The XUL Mini-Builder Python Module, Such As It Is

The XUL.py module that does the heavy lifting in the creation of the XUL file defines the following three XUL widgets and attributes. You can set the attributes in bold via the module, the others you cannot.

<window>
id="xulpie"
xmlns
xmlns:html
title
height
width
orient="vertical"
align="center"
autostretch="never"
<text>
value
class
<button>
value
class
oncommand

Each of the widgets is a python class. For example, a XUL window is instantiated with the title, height, and width as parameters (though these last two have defaults and can be omitted):

xWin = XWindow('window title', 200, 200)

The XWindow can then instantiate its own children by creating and embedding the other two objects with similar constructors:

xTex = XText('Hello World!')
xBut = XButton('Quit', oncommand='window.close()')

In this case, the XText constructor is getting the value attribute as an input parameter and relying on the default class, "marquee", for styling. And the XButton constructor is getting the title and an event handler -- for now written in JavaScript and not in Python. The XWindow class's Bake() method then creates the actual XUL from the classes and writes it into a file object open for oven.xul in the oven package. Finally, the Serve() method uses the os.system function to invoke Mozilla as configured in the previous sections:

mozilla -chrome "chrome://oven/content"

which brings up the window as created. Note that the generated oven.xul file and the attending oven.css file in the skin directory are all that is needed, beside the package structure itself, to bring up standalone windows in this way. Using the module as described here you can interatively recreate the oven.xul file and bring it up on the desktop.

Unfortunately, the source code for this module is hard-coded in some unfortunate ways: currently, the parent window does not check the type of widgets it bakes in; rather, it expects a single text widget and then a single button widget. But the baking behavior can easily be broadened with the addition of an additional attribute or two (e.g. "type" and maybe the boolean "is_parent") and perhaps the ability to pass in an arbitrarily long list of child widgets (and a list of lists of child widgets!) to be laid out accordingly.

In the meantime, the source code is available {{mediawiki.external('XUL.py.txt here')}}. To use the module, copy the source code into a file called XUL.py and put it in your PYTHONPATH, start the interpreter, and import the module's classes.

Button Widgets and the Button Skin

This sample uses some of the newer widgets in the XUL toolkit. In particular, the XUL.py module takes advantage of the fact that the new <button> widgets uses the global skin for almost all of its style and positioning. Where before the <titledbutton> widget used attributes such as align to specify its location within the parent container, the <button> takes advantages of basic new button classes in the global skin such as "left", "right", "top" to get its behavior.

New classes for the button widget include:

  • button.smallbutton.left
  • button.rightbutton.top
  • button.bottom

These simple definitions represent a further shift away from the styling of elements in the XUL itself. The independence of structure and presentation is one of the best features of XUL; putting presentational aspects of the elements in the CSS files allows us to do things like we have done here, where the Python wrapper can create the basic structure and rely on the global skin to present the objects consistently with the rest of the interface.

Note also that the global skin has been modularized into several different files. The global skin directory (part of the global package defined in Mozilla\bin\chrome\global) now contains the following global skin modules:

  • button.csscheckbox.css
  • commonDialog.cssdialogOverlay.css
  • global.cssmenubutton.css
  • menulist.cssradio.css
  • scrollbars.csstasksOverlay.css
  • wizardOverlay.css

...which provide the skin and point to additional XUL content in XBL, about which I will have more to say in a later article (read: when I learn more about how XBL actually works).

As XUL continues to mature, it begins to seem more like a development framework than a user interface language, offering better support for projects like the one I've described here. It's unlikely that XUL will see any more direct relationship to Python, and yet the ease with which these two technologies can be used together is too inviting. If, like me, you use concepts and technologies with which you are familiar to learn about new technologies, I invite you to take advantage of Mozilla's standards-based technologies in similar ways.

Author: Ian Oeschger
Other Documents: {{mediawiki.external('XUL.py.txt Module source code')}}

Original Document Information

Revision Source

<p>
</p><p><i>Caveat lector: This article does not describe any actual bindings between <a href="en/XUL">XUL</a> and the Python language. I'm just fooling around here, technically speaking.</i> There's been some talk recently about generalizing <a href="en/XPConnect">XPConnect</a> so that the Mozilla APIs described in <a href="en/XPIDL">XPIDL</a>/<a href="en/XPCOM">XPCOM</a> -- which is just about all of them -- could be accessible to a scripting language like Python, and vice versa. This would take <i>a lot</i> of work, and Mozilla isn't anywhere near that now.
</p><p>In the meantime, hacks like the one described in this article can help
familiarize you with the environment of <a href="en/XPFE">XPFE</a> development and may inspire
you to do your own investigation. In fact, even if you don't know Python,
this article may help suggest some simple ways to <i>get at</i> <a href="en/Gecko">Gecko</a>
without rebuilding or implementing your own application core.
</p>
<h2 name="Introduction">Introduction</h2>
<p>This article describes a Python module that creates and displays XUL windows. If you are familiar with some of the GUI packages that are used with Python, such as Tkinter and wxWindows, then the following Python code will look somewhat familiar to you:
</p>
<pre class="eval">&gt;&gt;&gt; from XUL import *
&gt;&gt;&gt; myXWin = XWindow('XUL Pie', 100, 200)
&gt;&gt;&gt; myXT1 = XText('Hello World!', bclass='marquee')
&gt;&gt;&gt; myXB1 = XButton('Quit', oncommand='window.close()')
&gt;&gt;&gt; myXWin.Bake(myXT1, myXB1)
&gt;&gt;&gt; myXWin.Serve()
</pre>
<p>Using the xul module, the <code>Serve()</code> function from the example above tells Mozilla to display a XUL window like the following:
</p><p><img align="none" src="File:en/Media_Gallery/Xulpie.gif">
</p><p>Mozilla displays only this window and not the browser. As you can see from the code, the XUL file generated by the Python code contains three widgets: the parent window, a &lt;text&gt; widget, and a &lt;button&gt; widget, all of which are "baked" together into a single XUL file and rendered by Gecko, the rendering engine inside Mozilla just waiting around for extra activities like this. The Python module doesn't provide a lot of widgetry or detail and so the resulting XUL file is sort of simplistic, but the interaction of these two technologies opens up some conspiratorial possibilities for XPFE development: both Python and XUL are truly cross-platform; you can easily create new packages in Mozilla and then use those packages as standalones; Python <i>may</i> one day be XPConnect-able -- which would be an incredible, Prometheus-stealing-fire-from-the-gods-like boon for people like me who aren't very good at systems programming languages; and so on. The mind reels.
</p><p>In the next few sections, we describe how to set up the environment for this sort of window building (i.e., the "oven" from the title of this article), and we examine the Python module and the generated XUL.
</p>
<h2 name="The_Oven:_Creating_Your_Own_Package_Testbed">The Oven: Creating Your Own Package Testbed</h2>
<p>In order to use this Python module, you have to make a couple of changes to your environment. I think that these changes are good generally -- especially if you are going to be fooling around with Mozilla a lot, as opposed to simply using it to browse the web.
</p>
<h3 name="Adding_the_Mozilla.5Cbin_Directory_To_Your_Path">Adding the Mozilla\bin Directory To Your Path</h3>
<p>However it's done on your current platform, add Mozilla's bin directory to your path so you can bring it up from the command line without having to qualify it with the full, inconvenient path.
</p><p>On Windows98, I added the following bit to my autoexec.bat file:
</p>
<pre class="eval">C:\Progra~1\Netscape\Mozilla\bin\
</pre>
<p>Once this is set (and you have either restarted or re-run the autoexec.bat from the command prompt), you can bring up Mozilla by just entering <code>Mozilla</code> at the prompt.
</p>
<h3 name="Displaying_Chromes_Other_Than_Mozilla">Displaying Chromes Other Than Mozilla</h3>
<p>Once you have Mozilla this close to your keyboard, the next step is to incorporate the special <code>-chrome</code> flag into your Mozilla invocations. When you start Mozilla with the command <code>Mozilla</code>, it's exactly as if you are starting it by double-clicking an icon. When you use the <code>-chrome</code> flag, however, you are telling Mozilla you want to display a chrome other than the default. The following line, for example, brings up the search component as a separate, single window:
</p>
<pre class="eval">mozilla -chrome "chrome://search/content"
</pre>
<p>The search component is available for this sort of option because it is a package within the package hierarchy of the Mozilla application. Though the chrome:// type url is pointing to a single XUL file (a XUL file named after the package in which it resides is presumed in the chrome:// url, so the line above is equivalent to <code>chrome://search/content/search.xul</code>), it's really pointing out the whole chrome to which that file belongs. When the search.xul file is loaded, it loads all sorts of other data from the package, including the skin, overlays, localization strings, and other content. These are all part of the chrome.
</p>
<h3 name="Creating_a_New_Package">Creating a New Package</h3>
<p>The chrome is not magic. Not all magic, anyway. Mozilla provides this special url type and package hierarchy as a convenience mechanism that you can easily appropriate for your own purposes. In order to make the debugging and testing of the Python module easier for myself, I simply created a new package into which I could render the XUL and point the rendering engine. This package testbed, described in this section, is a very good way to open up Mozilla and see some of its flexibility and power in action.
</p><p>To create a new package, <code>cd</code> to the chrome subdirectory in Mozilla. On my system, that directory is located here:
</p>
<pre class="eval">C:\Program Files\Netscape\Mozilla\bin\chrome\
</pre>
<p>Create a new directory there and name it "oven". Under oven, create three subdirectories: "content", "skin", and "locale". Under each of these subdirectories add more directories until you have the following basic structure:
</p>
<pre class="eval">oven/
  content/
    default/
  skin/
    default/
  locale/
    US-en/
</pre>
<p>This structure is what Mozilla expects when it looks into a package, so make sure everything is named and situated properly. The files you generate from the Python module are each called "oven.xul" and are placed, destructively, in the content\default\ subdirectory of the oven as they are created.
</p>
<h3 name="Creating_a_Mini_Skin_for_the_Oven">Creating a Mini Skin for the Oven</h3>
<p>Before you use the XUL Mini-Builder module, you must creat a skin for oven chrome that correlates with the classes of widget that the module is creating. You can extend the skin if you want, but I have only defined a single style in the oven skin. The style is a blue, heading-like text style for text widgets of the class "marquee", like the one in the example at the beginning of this article.
But like all good skins, the oven skin loads the global skin, so the basic look and behavior of the XUL widgets realized in the Python module are handled there.
</p><p>The skin for the oven, oven.css, reads as follows:
</p>
<pre class="eval">@import url(chrome://global/skin/);

text.marquee {
    font-size: 18pt;
    color: darkblue;
    font-weight: bold;
    font-style: italic;
    text-alignment: center;
}
</pre>
<p>Once the package structure and the skin file are in place, the module can skin the generated XUL --- just to skip ahead a little -- by writing the following line at the top:
</p>
<pre class="eval">&lt;?xml-stylesheet href="chrome://oven/skin" type="text/css"?&gt;
</pre>
<h2 name="The_XUL_Mini-Builder_Python_Module.2C_Such_As_It_Is">The XUL Mini-Builder Python Module, Such As It Is</h2>
<p>The XUL.py module that does the heavy lifting in the creation of the XUL file defines the following  three XUL widgets and attributes. You can set the attributes in bold via the module, the others you cannot.
</p>
<table cellpadding="20">
  <tbody><tr>
    <td valign="top">
      <table border="0" cellpadding="1">
        <tbody><tr><th bgcolor="lightgrey">&lt;window&gt;</th>
        </tr><tr>
          <td>id="xulpie"</td>
        </tr>
        <tr>
          <td>xmlns</td>
        </tr>
        <tr>
          <td>xmlns:html</td>
        </tr>
        <tr>
          <td><b>title</b></td>
        </tr>
        <tr>
          <td><b>height</b></td>
        </tr>
        <tr>
          <td><b>width</b></td>
        </tr>
        <tr>
          <td>orient="vertical"</td>
        </tr>
        <tr>
          <td>align="center"</td>
        </tr>
        <tr>
          <td>autostretch="never"</td>
        </tr>
      </tbody></table>
    </td>
    <td valign="top">
      <table border="0" cellpadding="1">
        <tbody><tr><th bgcolor="lightgrey">&lt;text&gt;</th>
        </tr><tr>
          <td><b>value</b></td>
        </tr>
        <tr>
          <td><b>class</b></td>
        </tr>
      </tbody></table>
    </td>
    <td valign="top">
      <table border="0" cellpadding="1">
        <tbody><tr><th bgcolor="lightgrey">&lt;button&gt;</th>
        </tr><tr>
          <td><b>value</b></td>
        </tr>
        <tr>
          <td><b>class</b></td>
        </tr>
        <tr>
          <td><b>oncommand</b></td>
        </tr>
      </tbody></table>
    </td>
  </tr>
</tbody></table>
<p>Each of the widgets is a python class. For example, a XUL window is instantiated with the title, height, and width as parameters (though these last two have defaults and can be omitted):
</p>
<pre class="eval">xWin = XWindow('window title', 200, 200)
</pre>
<p>The XWindow can then instantiate its own children by creating and embedding the other two objects with similar constructors:
</p>
<pre class="eval">xTex = XText('Hello World!')
xBut = XButton('Quit', oncommand='window.close()')
</pre>
<p>In this case, the XText constructor is getting the value attribute as an input parameter and relying on the default class, "marquee", for styling. And the XButton constructor is getting the title and an event handler -- for now written in <a href="en/JavaScript">JavaScript</a> and not in Python. The XWindow class's Bake() method then creates the actual XUL from the classes and writes it into a file object open for <i>oven.xul</i> in the oven package. Finally, the Serve() method uses the os.system function to invoke Mozilla as configured in the previous sections:
</p>
<pre class="eval">mozilla -chrome "chrome://oven/content"
</pre>
<p>which brings up the window as created. Note that the generated <i>oven.xul</i> file and the attending <i>oven.css</i> file in the skin directory are all that is needed, beside the package structure itself, to bring up standalone windows in this way. Using the module as described here you can interatively recreate the oven.xul file and bring it up on the desktop.
</p><p>Unfortunately, the source code for this module is hard-coded in some unfortunate ways: currently, the parent window does not check the type of widgets it bakes in; rather, it expects a single text widget and then a single button widget. But the baking behavior can easily be broadened with the addition of an additional attribute or two (e.g. "type" and maybe the boolean "is_parent") and perhaps the ability to pass in an arbitrarily long list of child widgets (and a list of lists of child widgets!) to be laid out accordingly.
</p><p>In the meantime, the source code is available {{mediawiki.external('XUL.py.txt here')}}. To use the module, copy the source code into a file called XUL.py and put it in your PYTHONPATH, start the interpreter, and import the module's classes.
</p>
<h2 name="Button_Widgets_and_the_Button_Skin">Button Widgets and the Button Skin</h2>
<p>This sample uses some of the newer widgets in the XUL toolkit. In particular, the XUL.py module takes advantage of the fact that the new &lt;button&gt; widgets uses the global skin for almost all of its style and positioning. Where before the &lt;titledbutton&gt; widget used attributes such as align to specify its location within the parent container, the &lt;button&gt; takes advantages of basic new button classes in the global skin such as "left", "right", "top" to get its behavior.
</p><p>New classes for the button widget include:
</p>
<ul><li> button.smallbutton.left
</li><li> button.rightbutton.top
</li><li> button.bottom
</li></ul>
<p>These simple definitions represent a further shift away from the styling of elements in the XUL itself. The independence of structure and presentation is one of the best features of XUL; putting presentational aspects of the elements in the <a href="en/CSS">CSS</a> files allows us to do things like we have done here, where the Python wrapper can create the basic structure and rely on the global skin to present the objects consistently with the rest of the interface.
</p><p>Note also that the global skin has been modularized into several different files. The global skin directory (part of the  global package defined in Mozilla\bin\chrome\global) now contains the following global skin modules:
</p>
<ul><li> button.csscheckbox.css
</li><li> commonDialog.cssdialogOverlay.css
</li><li> global.cssmenubutton.css
</li><li> menulist.cssradio.css
</li><li> scrollbars.csstasksOverlay.css
</li><li> wizardOverlay.css
</li></ul>
<p>...which provide the skin and point to additional XUL content in <a href="en/XBL">XBL</a>, about which I will have more to say in a later article (read: when I learn more about how XBL actually works).
</p><p>As XUL continues to mature, it begins to seem more like a development
framework than a user interface language, offering better support for
projects like the one I've described here. It's unlikely that XUL will see
any more direct relationship to Python, and yet the ease with which these
two technologies can be used together is too inviting. If, like me, you
use concepts and technologies with which you are familiar to learn about
new technologies, I invite you to take advantage of Mozilla's
standards-based technologies in similar ways.
</p><p>Author: <a class="external" href="mailto:oeschger@netscape.com">Ian Oeschger</a><br>
Other Documents: {{mediawiki.external('XUL.py.txt Module source code')}}
</p>
<div class="originaldocinfo">
<h2 name="Original_Document_Information">Original Document Information</h2>
<ul><li> Author(s): <a class="external" href="mailto:oeschger@netscape.com">Ian Oeschger</a>
</li><li> Last Updated Date: March 22, 2000
</li><li> Copyright Information: Copyright (C) <a class="external" href="mailto:oeschger@netscape.com">Ian Oeschger</a>
</li></ul>
</div>
Revert to this revision