In addition to the top level components, this document discusses a few major subcomponents which exist independently of the Application class, such as the Graph Builder, and the Graph Builder Graphical Interface.
Note: Throughout this document, the term "interface" will be used to describe a logical (and physical) wrapper around a particular class. This should not be confused with a class which represents the graphical user interface of a particular logical construct - these classes will be described as (and contain the name) "user interface".
The TFApp itself will be responsible for the main menu of the application, as well as the preferences dialog box and load/save options (although the other components will be responsible for generating the material to save).
class TFMainApp { public: ... // currently selected item in function library virtual void setSelectedFunction(FunctionID fid); // get handle for current function virtual FunctionID getSelectedFunction(); // get pointer to particular component virtual ComponentType* get-ComponentName-(); ... private: ... // pointers to each component ... };
Internally, the CLUIBuilder contains a couple core elements. First, as mentioned above, the CLUIBuilder contains a UI class which has a Graph Builder User Interface embedded within it, providing the basic editting environment for the program as a whole. Additionally, the CLUIBuilder should maintain a list of currently open sub-graphs to track which windows the user has open.
Of particular importance to the CLUIBuilder's functionality is its "Undo" feature. This should be supported by using the "Command" pattern as described in "Design Patterns" (Gamma, et al), pg 233. The Invokers should be the Graph Builder User Interface, and its containing class, and the receiver should be the CLUIBuilder itself. A queue of commands with Execute and Unexecute methods should be maintained within the CLUIBuilder.
class TFCLCLUIBuilder { public: ... // execute and take responsibility for a command virtual void executeCommand(Command* cmd); // undo, store, and return a command virtual Command* undoCommand(); // redo an undone command, and return it virtual Command* redoCommand(); // get graph for code generation virtual GraphBuilderInterface* getGraphBuilder(); ... private: ... CommandList* _commandsPerformed; CommandList* _commandsUndone; CLUIWrapper* _wrapper; // wrapper around graph builder user interface ... };
First, the UI Layout window is used to actually model the user interface and allows creation of all of the various control widgets (buttons, text fields, sliders, checkboxes, etc) which can be obtained from the Control Toolbox (another window contained in the GUI Builder). Show by double clicking a widget created in the UI Layout window is the Control Edit window. This window is presented with a tab view - one tab for each event associated with the control being editted. Each tabbed section contains an embedded Graph Builder User Interface, allowing the programmer to create graphs associated with responses to each event. The UI Layout window should maintain a list of open windows as to avoid bringing up the same control in multiple editing windows.
Finally, the GUIBuilder needs a general purpose window of information. The primary focus of this window is the list of global variables available in the program. Global variables may be created explicticly by the user, and are created implicitly when controls are created (each control represented by a variable). This window may also be used for other information which may need to be displayed in the GUIBuilder.
As with the CLUIBuilder, the GUIBuilder must fully support Undo/Redo and so must make use of the Command design pattern as described above.
class TFGBGUIBuilder { public: ... // command interface as in TFCLCLUIBuilder // edit the graphs for a control widget virtual void openGraphForControl(ControlID cid); // necessary to maintain state: virtual void closeGraphForControl(ControlID cid); // manipulate global variables virtual Variable* getSelectedVariable(); ... private: ... GBGlobalVariableDisplay* _globalVarDisplay; GBLayoutWindow* _layoutWindow; GBWindowList* _windowList; ... };
In its actual implementation, the internal class FunctionLibrary is not directly accessible. Since it will be used by so many other classes, it must be accessed through a FunctionLibraryInterface class. The FunctionLibraryInterface will mirror the functionality of the FunctionLibrary and will export its interface to the world. Only the Interface class will be allowed direct access to the implementation class, thus reducing compile time overhead and confusion.
When constructing a graph, the user chooses functions from the Function Library (via the Function Library User Interface), identified by a unique identifier. This identifier is later used to retrieve information about the function's name and parameters. In the case of a user-defined function, the Function Library also stores the graph or the code associated with that function.
The Function Library has primary control over the editing of user-defined functions. To avoid multiple edit sessions and potentially a great deal of confusion, user-defined functions must be checked out of the Library before they can be modified. This can be enforced programmatically by returning either a read-only or a read-write subclass of a generic edit wrapper class when a program component requests the internals of a particular user-defined function.
class TFFLFunctionLibrary { public: ... // for use by Function Library User Interface virtual CategoryList* getCategories(); virtual FunctionList* getFunctionsFromCategory(CategoryID cid); // for use in graph creation and code generation virtual char* getNameOfFunction(FunctionID fid); virtual char* getDescriptionOfFunction(FunctionID fid); virtual CodeTemplate* getCodeOfFunction(FunctionID fid); virtual Parameters* getParametersOfFunction(FunctionID fid); // Check out/in a function for editing virtual FunctionEditInterface* editUserFunction(FunctionID fid, GraphBuilderInterface* gbi); virtual void checkInFunction(FunctionEditInterface *fei); ... private: ... // database-like data structure ... }
Most of the significance of the Code Builder actually lies in the implementation of the graph traversing algorithm, and less so in the design. The Code Builder will be a relatively simple component from the design perspective, consisting only of any helper classes for implementation. The design of the Code Builder will not affect any other components. The main restriction on the Code Builder is that it maintain modularity to allow for the possible creation of Code Builders for other languages.
Function Library User Interface
The Function Library User Interface is the programmer's main access to
the toolbox of routines available within The FORCE. The Function
Library User Interface itself, however, will not contain any of its own
information. The FLUI's main purposes are 1) to allow the user to
select a function to meet her demands, and 2) to help teach the user
what a particular function does internally so that he will be able to
use it in textual C code.
Internally, the Function Library User Interface should follow the standard methods of representing a semi-complex GUI in an object oriented program. To be implemented as described in the Specifications document, the FLUI should consist of several classes, representing the category selection list, the function selection list, and finally the description text box. It is important for each of these elements to be smart objects in and of themselves because they will be serving as the interface into the Function Library database. They will need to query the database and display results based on the user's actions.
The only other interaction the Function Library User Interface has with the rest of The FORCE is to inform it of what the currently selected function is. This data will be used by the GUIBuilder and the CLUIBuilder (which in turn will be used by the Graph Builder User Interface) to create nodes in Graph Builder graphs.
class TFFGFunctionLibraryUserInterface { public: ... private: ... // window components CategoryListControl* _catList; FunctionListControl* _funcList; DescriptionField* _desc; };
In its implementation, the Help Server should only be accessed through an interface class, TFHSHelpServerInterface to prevent excessive dependencies on volatile code. Since almost all components will make use of the Help Server in one form or another, it will be extremely helpful to isolate the interface of this code from its implementation.
To get a particular help message from the Help Server, a component should give the Help Server its component ID, and the help ID of the message to be displayed. Messages from the Help Server are identified uniquely by a combination of their component ID and their help ID.
class TFHSHelpServer { public: ... // read in help stored externally virtual void parseHelpFile(char* fileName); // return help information virtual Help* getHelpFor(ComponentID cid, HelpID hid); ... private: ... // database-like structure storing help ... };
The Compiler will consist of only a couple classes which will get its job done. Most of the trick of the compiler will be correctly making the executable, and executing it. Additionally, the Compiler class should be able to kill the executable.
Finally, since the Compiler stores compile information (needed compiler flags, etc), it must provide a simple interface to allow the Code Builder to retrieve that information when it is time to generate a Makefile.
A Graph Builder is not only the means of creating and editing a graph, but is also the graph itself. A Graph Builder instance is the complete set of tools and data used to store a user written routine both in memory (in its data structure) and on disk (in a particular file format). Since the Graph Builder does contain so much, and it will be used in multiple places, it will be accessed through a Graph Builder Interface class, shielding the internal implementation from the rest of the project.
Internally, the Graph Builder conists of three main classes, the encapsulation of the Parameters, encapsulation of the Return Values, and finally the encapsulation of the Flow Graph. The Flow Graph conists of a collection of Nodes, and a list of Variables.
To edit a Graph Builder, the GUIBuilder, the CLUIBuilder, or possibly the Function Library needs to contact the Function Library and request permission to edit a graph using one of the get functions. The Function Library allows access through its FunctionEditInterface mechanism described above, which in turn allows access to a Graph Builder Interface, which finally talks to code in the Graph Builder.
Of importance is the GBUI's counterpart which will allow the user to enter code as opposed to a graph, but that is not discussed indendently since it is no different in design or behavior from the GBUI.
Whenever a Graph Builder User Interface is created, it should be given a Graph Builder Interface (or a FunctionEditInterface class provided by the Function Library) which it will pass all user interaction to. The Graph Builder User Interface contains all graphical user interaction and manipulation, but it doesn't do anything unless 'authorized' to do so by the Graph Builder.