GUI Syn - GUI Synthetiser
What is GUISyn ?
GUISyn is an attempt at decoupling the application logic from the user
interface. Ideally, it should be possible to build a front-end and a back-end
application, where the front-end remains exactly the same from project to
project and only the back-end changes.
The back-end builds a representation of the GUI with abstract widgets; it
attaches variables and scripts to them. This representation is then transformed
into a serialised data stream, which is sent to the front-end.
The front-end deserialises the stream and generates the GUI with real
widgets, handling most of the layout by itself. All the interaction is handled
by the front-end. When the user modifies a value (clicks on a button, edits a
field, etc.), the corresponding variable gets updated.
If a variable with an associated script is modified, the front-end generates
a special script event which is serialised and sent to the back-end; the
event includes the state of all the variables. The back-end can then process the
data and do whatever is needed...
Why GUISyn ?
I had several goals in mind when designing GUISyn :
- Make writing really widget tool-kit independent applications possible
(what if I use OPaC now and want to use wxWindows tomorrow, for
instance ?)
- Make sure that the GUI runs in a completely isolated memory space from the
core application. This avoids severe pitfalls, such as memory corruption by
the GUI, which often lead to unexplained crashes in the core.
- Make debugging easier : if a sequence of changes in the GUI leads to
a crash of the core, then re-playing exactly the same sequence should
produce exactly the same results (there is no messing between the GUI and
the core, where the GUI often behaves in very different ways from one run to
another, because of time-related subtelties).
- Make internationalisation easy by providing a possibility of describing
the GUI in a plain text file (in an XML-like format).
Widget definition
A widget is defined by following elements :
- A class, which can be one of the
following :
- window, a full-fledged window.
- box, a box with a frame and a title,
used to group elements together.
- div, a logical grouping element, which
does not paint anything special.
- line, a line of text, which can be
edited.
- switch, a switch, which can be turned
on or off.
- radio, a radio button, where exactly
one can be turned on at a given time.
- icon, an icon.
- button, a push button.
- text, a static piece of text, which
may contain a %1 placeholder in order to
display the associated variable contents.
- grafix, a graphical output zone, used
to display a document view, for instance.
- A symbolic name, which can be empty.
- A label, which can be empty. The label is defined as a Unicode
string.
- An x- and y-size, which can be zero. If a size is specified, GUISyn
will use it as the best size for the widget.
- An x- and y-stretchability, which can be zero. The value 0
disables stretching, a value of 1 specifies
a standard stretchability. The higher the value, the more space the widget
will take up.
- A layout, which can be one of the following :
- col, column;
- row;
- eq_dx, eq_dx_col, eq_dx_column, eq_dx_row;
- eq_dy, eq_dy_col, eq_dy_column, eq_dy_row;
- eq_size, eq_col, eq_column, eq_row;
- A variable name, which is a symbolic name used to tie the widget
with a variable. If there is no symbolic name for the widget, use the
variable name instead.
- A variable value, which defines the value taken by the variable if
the widget gets active. For a switch, the value will be added/removed from a
list of values stored in the variable (this allows grouping several switches
into a single variable).
- A set of margins.
- A macro, which causes the creation of specific widgets in the
current widget. Usually, macros are applied only to div
widgets.
The GUI can be described using a simple text source. Here is an example of
such a GUISyn dialog description :
WINDOW 'Réglages (démo)', dynamic_window, col, var={window-mode,}
BOX 'Réglages de la fenêtre', window_settings, stretch={1,1}, layout=row, margin={5,5,5,5}
DIV stretch={1,1}, layout=col, margin={5,5,5,5}
SWITCH 'Ascenseur horizontal', var={mode,scroll-h}
SWITCH 'Ascenseur vertical', var={mode,scroll-v}
DIV stretch={1,1}, layout=col, margin={5,5,5,5}
DIV stretch={1,0}, layout=row
LINE stretch={5,0}, var={line_1}
LINE stretch={2,0}, var={line_2}
DIV layout=row, stretch={1,0}, margin={0,0,5,5}
BUTTON ' OK ', size={0,24}, layout=eq_size, var={!action,OK}, margin={5,5}
BUTTON ' Annule ', size={0,24}, layout=eq_size, var={!action,CANCEL}, margin={5,5}
DIV stretch={1,1}
BUTTON ' Applique ', size={0,24}, layout=eq_size, var={!action,APPLY}, margin={5,5}
And here is the result :
The parser produces even useful error messages, such as :
19: BUTTON ( Apply ), size={0,24}, var={!action,APPLY, layout=eq_size
^ expected '}' mark
The parser fits into 800 lines of C++ code, relying on templates and other nice
stuff; it took about 4 hours to write, test and debug.