As said in the introduction, SmartFactor heavily relies on two object concepts:
SmartFactor is split in several parts. Each part may be represented by one
Singleton. The singletons are held by SF_GLOBALS, from which inherit each
class of SmartFactor.
Ah yes, one more thing: the main class of SmartFactor is SMARTFACTOR.
So, to the point: the main parts of SmartFactor are:
SF_INTERFACE_FACTORY) and
the root interface (SF_ROOT). The former is used for
low-level "widgets", whereas the latter is used as an abstract gui;SMARTFACTOR.get_started;SF_LOGGER;
|
|
SmartFactor uses a "context" to hold singletons that are set at run-time
(usually at the beginning, by option setting). This context, SF_CONTEXT, contains two "settable
singletons":
SF_CLASS_LOADER), set after
arguments parsing because of the ACE file,SF_INTERFACE_FACTORY),
set after having chosen which GUI to use (either by --interface
or by the compiled root procedure).The directories organization quite follows the objects organization.
SmartFactor/source |
The main sources directory. Here are almost all the main abstractions of SmartFactor, along with the main class:
|
||||
interfaces |
The interfaces submodule, also holding the abstractions of the GUI. |
||||
abstract |
This directory holds all the upper-level "widgets" used by the menus. These widgets use the lower-level abstractions (in interface), which in turn are reified by three implementations (console, dialog, and graphic). It is the upper part of the Bridge. Of those widgets, Those widgets use an event-oriented model. It means that the GUI
widgets "react" to external events (a button may do something when it is
clicked upon, and so on). That "do something" is simply an agent given
to the widget. One specially interesting action is
|
||||
console |
It is the "console" low-level GUI. |
||||
dialog |
It is the "dialog" low-level GUI. It is built using the Newt library developped by RedHat. Newt is great! |
||||
newt |
The Newt widgets eiffelized. A lot of |
||||
graphic |
(This GUI is not yet developped; it will be a graphical interface based on Vision.) |
||||
interface |
It is the low-level GUI abstractions: all the low-level widgets behaviour is defined here. It is the lower part of the Bridge. |
||||
menu |
This directory holds all the implementations of the On the other hand, |
||||
misc |
Some miscellaneous classes I did not want in the main directory. |
||||
project |
Some project-oriented classes. They are an Adapter to the main structures of
SmartEiffel. They define that a project is a set of clusters ( |
||||
refactory |
One of the most important directories, since it holds the heart of
SmartFactor: the refactoring tools (in SmartFactor lingo, the
refactories). All those refactories must inherit from Apart from the menus, the refactories should be the only other objects in SmartFactor to make use of the GUI, though in a more diversified manner (generally they ask some questions, what's the new name of the class, are you sure you want to do that, and so on). |
||||
sorter |
Some modified SmartEiffel library classes, enabling the use of an external comparator. It is mainly used by the menus to sort items like clusters or classes (which are SmartEiffel classes, thus could not be changed to add an internal comparator). |
||||
There are currently three GUIs, depending on which root procedure is used to compile SmartFactor. There is
also a root procedure that has an extra command-line option (viz. --interface) which allows to
choose between the three available interfaces. Note though, that this last procedure slightly slows
down the GUI since there are extra dynamic bindings that SmartEiffel obviously could not compute out.
The SmartFactor GUI design was made with the following idea: it must be simple to create a brand new GUI. Hence, a GUI implementation is simply a plugin component.
The GUI is split in two parts:
The upper layer of widgets is strictly event-driven; it means that the widgets will do something when an event is sent to them (e.g. a button may be clicked, an item may be chosen in a list, and so on). How this event is sent to them is of no concern (the low-level layers will take care of that).
One very important widget is SF_FORM, which is the blessed
child of the classic ideas of a GUI widget container and a GUI widget window. Why is there only one widget for
those two roles? Because SmartFactor has a very simple GUI architecture: no fancy here. Only one window
displayed at a time, and no nested containers which are a difficult concept when you don't know how the
widgets will be displayed.
While we are at that, some other classic GUI notions were left out: there is no notion of position, no notion of layout and those gory details. The abstraction is minimal: a widget is just a thing that can react. How it is displayed is of no concern of the rest of the world, only the low-level parts will deal with that.
Note that it also means that the low-level GUI will make assumptions on how the widgets are used by SmartFactor. For example, a form will generally contain a list and some buttons (and that makes a menu). Hence, this GUI component is an interesting idea, but maybe not general enough to be used out of SmartFactor.
(Back to the forms.) There may be more than one form: but only one is displayed at a time. Imagine they are
stacked modal windows: only the top of the stack is useable. A form is normally held by an SF_ROOT, which knows when to display it.
Well then, there is this SF_ROOT thing. It is, in fact, the
GUI engine of SmartFactor. It holds the stack of forms. It is a singleton object, accessible via
SF_GLOBALS.ui. A classical way of using it is:
|
There are a few lines to look at:
Line (1): tell the GUI that a new form is about to be displayed. The GUI creates a new SF_FORM, accessible by ui.root. You may then start adding
widgets to that form.
Line (2): the action of the button is agent ui.stop which breaks the
event loop.
Line (3): tell the GUI to start the event loop. It means that the program is frozen until some event occurs, which will then trigger the associated agent (in the example, click on the button to close the window).
Line (4): after the event loop was broken, you may either start it again (it will show the same
form), or destroy the form using the finalize feature (this allows to free the underlying
structures held by the operating system or whetevr it is that displays the GUI).
See also SF_MENU.show.
The lower layer consists in one or more GUI plugin components. Those components must reify the interface
of the deferred set, along with providing a concrete class of SF_INTERFACE_FACTORY and SF_LOGGER.
SF_INTERFACE_FACTORY is used by the upper
layer to connect to the lower-level widgets. It's the classical Factory Design Pattern.
SF_LOGGER is the logger (used to display messages to the
user). The logger instance is accessed via SF_INTERFACE_FACTORY.logger. More on that topic: see the logging section.
This set is a 80x25 text-only GUI. I'm quite proud of it because, not only it works well, but it also looks great. Long menus are paged, it is event-driven (yes, it is), and so on.
The difficult part of this GUI was to transform a classic menu (the good old
your choice?) in events. To do that, I used the Chain Of Responsibility Design
Pattern. When the user enters text, the form triggers the chain until some widget handles the request.
This set presents dialogs to the user. You need the Newt library for this GUI set to work (I use 0.51, let me know if older versions work too...)
The transformation was quite straightforward. All the GUI had to do was some layout management. Knowing the SmartFactor restrictions, it was quite simple.
About the layout of the components: the model was quite outrageously simplified. It is centralized in
SF_DIALOG_FORM. Nesting forms has the effect of changing
the layout direction: either they are layed out horizontally or vertically. The upper "level" (the window)
being layed out verticadlly. To understand why it is so: look at the menus aspect: a list and a bunch of
buttons below, but the buttons are one beside another. I chose not to introduce "layout" objects for the sake
of simplicity. I may still refactor in the future ;-)
Newt is basically not event-driven (though I'm aware you could add that sort of behaviour, I'm not sure how well it reacts with Eiffel): therefore, I developped the same kind of Chain of Responsibility as for the console GUI set.
Though not yet developped, this set will use Vision, for two reasons: first, it is architecture-independant (works as well on X11 and Win32), and second, its model is quite the same as mine (event-driven, notion of stacked event loops).
The design may look sloppy because there is an inheritance diamond. Such a diamond is not allowed by SmartEiffel 2.0. Still, the way the Design Patterns are used (Composite plus Bridge plus Chain of Responsibility plus Factory) makes this diamond quite compulsory since the classes are tightly coupled. The diamond would look like, (taking the button widget as example):
|
The problem is the following:
SF_INTERFACE_BUTTON must conform to SF_INTERFACE_WIDGET because the Bridge needs this link to
work (there is a covariant redifinition of SF_ABSTRACT_WIDGET.impl)SF_CONSOLE_WIDGET must conform to SF_INTERFACE_WIDGET
because there is parent-child relationships between widgets (a SF_INTERFACE_FORM contains SF_INTERFACE_WIDGETs), and
because there is a conformant redifinition of the feature SF_INTERFACE_FORM.add_childSF_CONSOLE_BUTTON must
conform to SF_INTERFACE_BUTTON because the
Factory covariantly redefines its factory features (here SF_CONSOLE_FACTORY.new_button)SF_CONSOLE_BUTTON must conform to SF_CONSOLE_WIDGET because the latter
provides functions specific to the console GUI, that are applied on any console widget by the
SF_CONSOLE_FORM (e.g. the SF_CONSOLE_WIDGET.display feature)But some breaks must be introduced. To solve the problem, we introduce a new design pattern: the split object that allows an object to behave as it it had two conformant parents. The Split Object is an object pair, each referring to the other. The resulting diagram is the following:
|
(Note that "engine" is ill-chosen for such a technical role. I kept it for history reasons, simply because I don't like to tinker too much with renaming files in CVS...)
Why does it solve the problem: look at the broken branch: SF_CONSOLE_WIDGET does not conform anymore to SF_INTERFACE_WIDGET. It means that the parent-child relation
looks broken. To restore it:
add_child is "less" covariant (only the generic part has been covariantly redefined); but
its code (below) makes the asumption that an object of the "good" type is provided (hence no
Void test);add_child code is a two-step function that, one, retrieves the "engine" part of the
split object, uses the ?= operator to downcast it, and at last calls back its "widget"
part again.
|
More on this pattern: It should be seldom used. Real "hard" diamond are few, but one can find them when trying to use a tight combination of Design Patterns (like here). In most cases, simply breaking the diamond should be enough.
It is the classic Logger pattern: one Singleton, some log levels, and so on.
To ease the memory pressure though (i.e. avoid that horrible infix "+" feature of
STRING), not only is there a single log feature (which exists for simple cases), but
also a whole protocol that lets you build the message in parts before displaying it.
In fact, the logger is an OUTPUT_STREAM (of the standard SmartEiffel library), hence all the
features such as put_integer and so on are available. The only restrictions for those features
are:
connect feature with the log
level),flush is called; but the
message can be cancelled too. The known log levels are defined in SF_LOGGER:
log_info for some informative messages;log_notice for some information the user should be aware of
(that's the default level);log_warning for some messages that warn the user against
something;log_error for some messages informing the user that an
error occurred;log_fatal_error for some messages telling the user that
some serious error occurred, and that SmartFactor cannot continue its
execution (note that flush kills SmartFactor with status
1);log_internal_error for some messages telling the user
that SmartFactor did not behave well (that should not happen, but if it
does, note that flush crashes SmartFactor). Here also, the design was simplified. At the most generic level, a menu is just an SF_LIST and two SF_BUTTONs (quit and refactor). A menu also holds an
SF_REFACTORY_MENU.
The list contains the items of the menu, depending on the menu itself.
The quit button is always displayed, and allows to exit the menu.
The refactor button is displayed if the refactory menu is not Void; if so, the button
allows to display that menu.
|
Refactory menus are more specialized: their appearance is totally defined by the refactories they hold.
The menu architecture is the following:
SF_MAIN_MENU
SF_CLUSTER_MENU
SF_CLUSTER_REFACTORY_MENUSF_CLASS_MENU
SF_CLASS_REFACTORY_MENUSF_FEATURES_MENU (two of that kind: one for the attributes and one for the methods)
SF_FEATURE_REFACTORY_MENU
(deferred, reified in SF_METHOD_REFACTORY_MENU
and SF_ATTRIBUTE_REFACTORY_MENU)Refactories are executed via the refactory menus. Their design vary from one refactory to another, depending on the work they have to do.
Some hints on how to add new refactories:
IN_OUT_VISITOR which is
an enhanced visitor (provided by SmartEiffel). SF_RENAME_CLASS_REFACTORY allows the user
to rename the selected class. It asks for the name and after some checks (name not already used, and so on) it
renames the class, and all the entities throughout the project that reference that class, all the inheritance
paths, and so on.
SF_RENAME_FEATURE_REFACTORY allows to
rename a feature. (Not yet implemented).