QALE - A lex/yacc (flex/bison) driven, cross platform (unix, win32) framework for Qt dialog design/layout
-------------------------------------------------------------------------------
Introduction
-------------------------------------------------------------------------------
QALE is a library designed to work with Qt to make dialog layout easier
and more flexible. The fundamental ideas behind QALE are:
- All geometry information for your dialog can be stored in a flat
file so that the look of your dialog can be altered without
recompiling your code.
- The flat file format must be sufficiently flexible to handle even
the most demanding layout tasks. It should also provide a mechanism
to allow your dialog to easily handle being resized.
- It is also possible to embed the contents of the flat file into
your application to make it easier to distribute.
QALE is designed to be non-intrusive. Your dialog can be made
to work with QALE by simply including one header file and by adding as
little as three lines of code to your top-level widget.
-------------------------------------------------------------------------------
Release Notes
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Building the library and example program
-------------------------------------------------------------------------------
General Notes
-------------
(o) You will need GNU Make to build QALE, even if you are working
on win32
(o) The most recent version of Qt that I own for both windows and
linux is 3.1.2 so that's what will be used for the primary
distribution
(o) Also available is a version updated to work with Qt 4.2.3
but that version has only been tested on linux
You can get GNU Make for win32 (as well as a lot of other handy unix
utilities for win32) from:
http://www.cygwin.com/
You can get the free version of Qt via Troll Tech's website,
http://www.trolltech.com/
Windows Build Instructions
--------------------------
I build QALE on Windows XP using MicroSoft's Visual C++ compiler
version 6.0.
(1) You will need to edit the file "config.inc" in this
directory. The lines that are important are the ones that
begin with "CONFIG_WIN32_QTROOT" and "CONFIG_WIN32_VC_INCLUDE"
(2) CONFIG_WIN32_QTROOT - Should be set to point to the location
where you installed Qt. As a simple test, the directory
you supply for CONFIG_WIN32_QTROOT should contain the files
'INSTALL' and 'README'.
(3) CONFIG_WIN32_VC_INCLUDE - Should be set to point to the location
where your vc++ include files are located. As a simple test,
the directory you supply for CONFIG_WIN32_VC_INCLUDE should contain
the file 'stdio.h'.
(4) To build the library and examples, type either of ...
make debug
make release
from the examples directory.
Note:
-----
For win32, my Makefiles assume you will be linking Qt statically. If you
are not linking Qt statically you will need to modify the Makefile in
'examples' to use the dynamic library version of Qt instead of the static
version.
Unix Build Instructions
-----------------------
I build QALE on linux using g++ version 3.2.2.
(1) You will need to edit the file "config.inc" in this
directory. The line that is important is the one that
begins with: "CONFIG_UNIX_QTROOT"
(2) CONFIG_UNIX_QTROOT - Should be set to point to the location where
you installed Qt. As a simple test, the directory you supply
for CONFIG_UNIX_QTROOT should contain the files 'INSTALL' and 'README'
(3) To build the library and examples, type either of ...
make debug
make release
from the examples directory.
Note:
-----
My Makefiles assume you will be linking Qt dynamically. If you are not
linking Qt dynamically you will need to modify the Makefile in 'examples'
to do static linking.
-------------------------------------------------------------------------------
The QALE file format - the basics
-------------------------------------------------------------------------------
The QALE file format is an ascii file whose structure is built around
QALE's idea of a widget definition. A widget definition looks like this:
widgetName(widgetWidth, widgetHeight, widgetX, widgetY, alignment)
A widget can also have children ...
widgetName(widgetWidth, widgetHeight, widgetX, widgetY, alignment)
{
child(childWidth, childHeight, childX, childY, alignment)
}
Many GUI layout tools require the height, width, x and y location of your
widgets to be hard-coded numbers. QALE's approach is based upon the idea
that often you want to specify a widget's size or location in terms of
another widget's size or location. As an example:
"I want the left hand side of this button to be 10 pixels from the right
hand side of that button"
... or ...
"I want the center of this button to be 1/3 of the way across the
dialog and I want the center of that button to be 2/3 of the
way across the dialog"
Every widget specified in QALE has a number of attributes. Assuming
your widget is called "button1", here is a list of its attributes and an
explanation of their meaning. Note that all values are specified in pixels.
(position-related attributes)
button1.l - The left-hand side of the widget
button1.r - The right-hand side of the widget
button1.t - The top of the widget
button1.b - The bottom of the widget
button1.x - The center (in x) of the widget
button1.y - The center (in y) of the widget
(dimension-related attributes)
button1.w - The height of the widget
button1.h - The width of the widget
button1.dw - The default width of the widget
button1.dh - The default height of the widget
How would we express the relationships given as examples above using
QALE attributes?
"I want the left hand side of this button to be 10 pixels from the right
hand side of that button"
thatButton(dw, dh, 0, 0, LT)
thisButton(dw, dh, thatButton.r + 10, 0, LT)
We have given "thisButton" and "thatButton their default height and width.
We have placed "thatButton" at (0, 0) and aligned it left-top (LT) with
that point.
We have placed "thisButton" at (thatButton.r + 10, 0) and aligned it
left-top with that point.
Note, we align 'left top' because in Qt's coordinate system the origin
is at the upper left with y values increasing as you head downward.
"I want the center of this button to be 1/3 of the way across the
dialog and I want the center of that button to be 2/3 of the
way across the dialog"
mainDialog(640, 480, 0, 0, LT)
{
thisButton(dw, dh, w / 3, h / 2, CC)
thatButton(dw, dh, 2 * w / 3, h / 2, CC)
}
We have located "thisButton" at (w / 3, h / 2) and aligned it center-center
around that point.
We have located "thatButton" at (2 * w / 3, h / 2) and aligned it
center-center around that point.
At this point it is probably worthwhile to go into a discussion about
the alignment values. We've seen "LT" and "CC" so far, but we now present
a more complete explanation. For each of the alignment values, the "@"
represents a point and the box is shown aligned to the point according to
the associated alignment value.
LT (left-top) CT (center-top) RT (right-top)
@------+ +---@---+ +-------@
| | | | | |
| | | | | |
| | | | | |
+------+ +-------+ +-------+
LC (left-center) CC (center-center) RC (right-center)
+------+ +-------+ +-------+
| | | | | |
@ | | @ | | @
| | | | | |
+------+ +-------+ +-------+
LB (left-bottom) CB (center-bottom) RB (right-bottom)
+------+ +-------+ +-------+
| | | | | |
| | | | | |
| | | | | |
@------+ +---@---+ +-------@
Widget attributes: abbreviated and fully-qualified names. In the
following example:
button1(dw, dh, w / 3, h / 2, CC)
We see 4 widget attributes being used: dw, dh, w, h. We can interpret
this widget specification as meaning:
- Make button1's width be its default width
- Make button1's height be its default height
- Make button1's x location be 1/3 of the way across its parent
- Make button1's y location be 1/2 of the way down its parent
Note that while dw, dh refer to the default width and height of button1,
w and h refer to the width and height of button1's parent. All widget
attributes can be qualified (prefixed by the explicit name of the widget)
but when they appear unqualified as they do here the way things work is:
dw, dh -> refer to the widget that is being defined using them (button1 in
this case)
w, h, x, y, l, r, t, b -> refer to the parent of the widget that is being
defined using them (button1's parent in this case)
Qualified attributes are always explicit, the widget to whom they refer
is always indicated explicitly. Here is an example:
mainWidget(640, 480, 0, 0, LT)
{
cancelButton(dw, dh, w / 3, h / 2, CC)
okButton(cancelButton.w, dh, 2 * w / 3, h / 2, CC)
}
The best way to think about using qualified attributes is to consider
them being defined from the perspective of the container. "cancelButton" and
"okButton" are inside the container "mainWidget", so effectively we are
defining everything from the point of view of: "mainWidget". "mainWidget"
has two children "cancelButton" and "okButton" so it is sufficient to
say "cancelButton.w" - this unambiguously determines what we're referring to.
There are also some "pseudo qualifiers", they are:
[this] - Refers to the container widget itself
[parent] - Refers to the container widget's container
Here is an example:
mainWidget(640, 480, 0, 0, LT)
{
groupBox(600, 400, 20, 40, LT)
{
button1([parent].w / 4, dh, w / 2, h / 2, CC)
}
}
In this case button1's width would be 160 pixels.
-------------------------------------------------------------------------------
The QALE file format - additional features
-------------------------------------------------------------------------------
A widget is also allowed to have a "define" section. This section lets
you specify macros that you can use to minimize duplication in your QALE
file. Here is an example:
mainWidget(640, 480, 0, 0, LT)
{
define {
leftMargin = 20
}
button1(dw, dh, $leftMargin, h / 3, LC)
button2(dw, dh, $leftMargin, 2 * h / 3, LC)
}
If you decided that 20 pixels didn't look right, you could change it
within the define section without having to change anything in the widget
definitions that use $leftMargin. It is worth nothing that define values
are evaluated where they are used, not where they are defined. Here is
an example:
mainWidget(640, 480, 0, 0, LT)
{
define {
value = w
}
groupBox($value / 2, 400, 0, 0, LT)
{
button1($value / 2, dh, w / 2, h / 2, CC)
}
}
The width of groupBox will be 320 while the width of button1 will be 160.
The reason for this is because the above is exactly equivalent to:
mainWidget(640, 480, 0, 0, LT)
{
groupBox(w / 2, 400, 0, 0, LT)
{
button1(w / 2, dh, w / 2, h / 2, CC)
}
}
We get 320 for groupBox's width and 160 for button1's width because "w"
unqualified always refers to the width of the container. An easy way to
remember how the defines behave is to think of them as being like #defines
in C/C++.
When a define is used (a variable preceded by a "$") the container is
first examined to see if it has the definition. If it does not then that
container's container is examined and so on until a definition is found.
Defines can also be qualified, just like attributes. We could have written
our example as:
mainWidget(640, 480, 0, 0, LT)
{
define {
value = w
}
groupBox($value / 2, 400, 0, 0, LT)
{
button1([parent].$value, dh, w / 2, h / 2, CC)
}
}
Another QALE feature is the ability to call arbitrary functions
by listing their names in the QALE file. Suppose you had a dialog like
this:
mainWidget(640, 480, 0, 0, LT)
{
cancelButton(dw, dh, w / 3, h / 2, CC)
okButton(cancelButton.w, dh, 2 * w / 3, h / 2, CC)
}
Note that we've set up the okButton to have the same width as the
cancel button. In a sense this is cheating because we just happen to know
that the cancel button will be the longer of the two. Suppose we had to
internationalize the software and for some languages the word for "ok"
was actually longer than the word for "cancel", how could we handle this?
It would be nice if we could do something like this:
mainWidget(640, 480, 0, 0, LT)
{
cancelButton(getMaxWidth(), dh, w / 3, h / 2, CC)
okButton(getMaxWidth(), dh, 2 * w / 3, h / 2, CC)
}
QALE supports this feature by using Qt's "properties". QALE can find the
specified function (and call it) as long as it is public and has an associated
Q_PROPERTY.
mainWidget(640, 480, 0, 0, LT)
{
cancelButton(getMaxWidth(), dh, w / 3, h / 2, CC)
...
(a) QALE first looks to see if "cancelButton" has a slot called
"getMaxWidth()". If it does, it uses it
(b) if cancelButton does not have the function, it then looks to
"mainWidget" and so on up the chain until it finds the function
or runs out of parents.
The rules you have to follow to allow QALE to find your functions:
(1) The function must be a public function and a Q_PROPERTY
(2) The function must take no arguments
(3) The function must return a double
You might be wondering:
"Ok, but in general QALE tends to view things from the perspective of
the container - why do we start looking for the function with the
widget itself rather than from its parent?"
Good question - the only reason is that if we started with our parent
this would prevent our top-level widget from being able to use functions
at all! So something like ...
mainWidget(getMainWidth(), getMainHeight(), 0, 0, LT)
{
// ...
}
Would be impossible. Also, this searching behavior only applies when
the function isn't qualified. If you write your QALE file like this:
mainWidget(640, 480, 0, 0, LT)
{
button1(dw, dh, w / 2, h / 2, CC)
button2(button1.getValue(), dh, w / 3, h / 3, CC)
}
QALE will only look to button1 for "getValue()".
The final QALE feature is a couple of built-in functions. They are
"min" and "max" and both take 2 arguments. They cannot be qualified
as they are built-ins and they work just as you would expect them to:
mainWidget(640, 480, 0, 0, LT)
{
button1(max(dw, w / 2), min(dh, h / 3), w / 2, max(max(1, 2), 3), CC)
}
-------------------------------------------------------------------------------
QALE from the Qt point of view
-------------------------------------------------------------------------------
Up till now we'd been talking about QALE from the point of view of the
QALE file exclusively. Now we'll talk about what things need to be done
on the Qt side of things to QALE-enable your dialogs. Here are some
things you'll need to be aware of:
(1) QALE relies on the fact that you will give your widgets names.
If you don't name your widgets QALE won't be able to find them.
So do this:
// Qt 3.x
QPushButton *theButton = new QPushButton("Cancel", this, "theButton");
// Qt 4.x
QPushButton *theButton = new QPushButton("Cancel", this);
theButton->setObjectName("theButton");
Don't do this:
QPushButton *theButton = new QPushButton("Cancel", this);
(2) Also, be sure your widget names are unique within each container.
The names do not have to be unique across your entire dialog, what
you should not do is have two siblings with the same name.
QALE won't crash if you choose to give two widgets within the same
container the same name, it will just use whichever one was
added to the parent last.
(3) The heirarchy of widgets in your QALE file must reflect the heirarchy
of widgets in your Qt source code. If you have:
mainWidget(640, 480, 0, 0, LT)
{
button1(dw, dh, w / 2, h / 2, CC)
}
In your QALE file, QALE will expect button1 to be a child of mainWidget
in your Qt code.
(4) Since QALE is a dynamic environment everything it does is
done at runtime. This means that QALE may not like what it finds
(your QALE file might have a syntax error, you might refer to a
non-existant widget, etc.). Whenever QALE finds something it doesn't
like it will write a message to a MessageReceiver object that you
can pass to QALE when you construct the object. By default you can
omit the MessageReceiver object and QALE will use a MessageReceiver
that just prints its messages to stdout. If you are working in
an environment where messages written to stdout get "swallowed"
(like windows) you'll have to create a subclass of MessageReceiver
that allows you to see the messages and pass that object to QALE when
you construct it.
(5) Assume for the purposes of illustration that your top-level widget
has a header file "TopLevel.h" and a source file "TopLevel.cpp".
Here is what you'll see in your Qt code when using QALE:
----------------------------------------------------------------------
in TopLevel.h
----------------------------------------------------------------------
// You'll need a forward declaration for QALE
class QALE;
class TopLevel : public QWidget // or whatever
{
// TopLevel methods here ...
private:
QALE *mQALE;
};
----------------------------------------------------------------------
in TopLevel.cpp
----------------------------------------------------------------------
#include "QALE.h"
// TopLevel's constructor
TopLevel::TopLevel( // whatever arguments ...
{
// Put this at the very end of your top-level object's
// constructor, note that "in.qale" is just an example -
// you specify here the name of your QALE file.
mQALE = QALE::constructFromFile(this, "in.qale");
}
// Be sure and clean up mQALE when we're done
TopLevel::~TopLevel()
{
delete mQALE;
}
// You'll also need to do this so that QALE can do the right thing
// when your widget is resized
void TopLevel::resizeEvent(QResizeEvent *)
{
mQALE->process();
}
Source Code
Examples (Windows Executables)
Return to Main |