Difference between revisions of "EiffelBuild Integration"

Line 1: Line 1:
 
[[Category:EiffelBuild]]
 
[[Category:EiffelBuild]]
 +
[[Category:Projects]]
  
 
This page describes informations on EiffelBuild internals and future plans regarding it's decomposition into disparate components so it may be placed into other tools such as EiffelStudio and EiffelEnvision.
 
This page describes informations on EiffelBuild internals and future plans regarding it's decomposition into disparate components so it may be placed into other tools such as EiffelStudio and EiffelEnvision.

Revision as of 00:59, 9 April 2006


This page describes informations on EiffelBuild internals and future plans regarding it's decomposition into disparate components so it may be placed into other tools such as EiffelStudio and EiffelEnvision.

Overview

This Wiki document describes the internals of EiffelBuild and the recent work undergone to permit EiffelBuild to support multiple instances within the same application. This entailed removal of project-specific onces as well as providing a new interface permitting EiffelBuild to be used as a component client, supporting multiple instances where each instance runs as though it were a running in separate process. This work was required to be undertaken for the integration of EiffelBuild into EiffelStudio and another tools, such as EiffelEnvision.


Eiffel build.main window.png

Author Notes

Although, from initial inspection through writing this document, the current state of EiffelBuild's component interface is not as polished as it could be - it does work. It is my recommendation that some refactoring be done prior to it's integration with EiffelStudio. Where appropriate I have suggested refactored solutions that require implementation. Given time restrains in being instructed on the internal workings for the compilation of this document, I have not had time to evaluate if the refactoring suggested are entirely possible. They are merely surface level suggestions of a brief overview of the internal workings of EiffelBuild.

Notes

Through out this document there are a number of repeated references to make to being a "client of EiffelBuild" and "an EiffelBuild instance". A client of EiffelBuild refers to the application integrating the use of EiffelBuild using the EiffelBuild components and component classes, such as example would be EiffelStudio or EiffelEnvision. An EiffelBuild instance refers to a single instance of EiffelBuild within the client application. EiffelBuild has been adapted to support multiple disparate instances.

Conventions

This document uses a number of conventions for clarity:

  • Element Name refers to visual user interface elements of EiffelBuild
  • CLASS refers to an Eiffel class.
  • `feature' refers to an Eiffel feature of a class.
  • 'file_name' refers to a file name or file path.


Initializing EiffelBuild

Initialization refers to the steps required to be taken to make use of EiffelBuild's core components, acting as a client of EiffelBuild. A good example of using the EiffelBuild components, as a client, is the EiffelBuild stand-alone application.

EiffelBuild is located in 'Src/build2' in the code repository. It's root class, containing the code to initialize and use the EiffelBuild components is in `GB.execute' from 'Src/build2/main/gb.e'.

There are two classes you will make use of during initialization. GB_SHARED_INTERNAL_COMPONENTS and GB_INTERNAL_COMPONENTS, unlike their name may suggest GB_SHARED_INTERNAL_COMPONENTS is not directly related to GB_INTERNAL_COMPONENTS. GB_SHARED_INTERNAL_COMPONENTS contains shared implementation that has no instance specific implementation. GB_INTERNAL_COMPONENTS on the other hand has one instance per each instance of EiffelBuild instantiated in a client application.

Example

Here is a example code snippet on how to initialize a single instance of EiffelBuild.

 class MY_BUILD_APPLICATION
 
 inherit
   EV_APPLICATION
   	export
   		{NONE} all
   	end
   
   GB_SHARED_INTERNAL_COMPONENTS
   	export
   		{NONE} all
       undefine
           default_create, copy
       end
 
 feature {NONE} -- Initialization
        
   execute is
       -- Create new EiffelBuild instance for use within the application.
     local
       l_components: GB_INTERNAL_COMPONENTS
     do
       default_create
       
         -- Initialize EiffelBuild at the application level
       initialize_eiffel_build
       
         -- Create a new component instance.
         -- Note: For every instance of EiffelBuild you want to run in your application you
         -- must create a new component for each.
       l_components := new_build_components
       
         -- Finally you need to set to the top-level window for the component instance.
       l_components.tools.set_main_window (create {MY_SHELL_WINDOW})
     end
     
 end -- class MY_BUILD_APPLICATION

`GB_SHARED_INTERNAL_COMPONENTS.initialize_eiffel_build' is used to initialize EiffelBuild on an application level. Calling `initialize_eiffel_build' only needs to happen once per application. `initialize_eiffel_build' contains/calls all of the application specific initialization, such as preference initialization.

`GB_SHARED_INTERNAL_COMPONENTS.new_build_components' should be called to retrieve a new GB_INTERNAL_COMPONENTS instance. An instance is per-instance of EiffelBuild. This means multiple instances can hold different EiffelBuild projects without interfering with each other. The GB_INTERNAL_COMPONENTS instance will provides clients with access to EiffelBuild instance-scoped tools and services, as the Layout Constructor, clipboard, status bar etc.

After each new GB_INTERNAL_COMPONENTS has been created the respective top-level application window must be set to it to ensure that any spawned dialogs shown are shown relative to the top-level window.

Refactoring

`GB_SHARED_INTERNAL_COMPONENTS.new_build_components' should be passed a top-level window instance so when a new GB_INTERNAL_COMPONENTS is created it will have the top level window already set.

GB_SHARED_INTERNAL_COMPONENTS should be renamed to GB_SHARED_APPLICATION or something more meaningful.

Core EiffelBuild Classes

There are a number of fundamental classes in EiffelBuild that require fuller explanation. GB_SHARED_INTERNAL_COMPONENTS and GB_INTERNAL_COMPONENTS have already been discussed in brevity. These classes are essential to instantiation and initialization of EiffelBuild instance(s).

GB_SHARED_INTERNAL_COMPONENTS

GB_SHARED_INTERNAL_COMPONENTS is only important only to clients of EiffelBuild. It is used to initialize EiffelBuild on an application-level and provides a factory method - `new_build_component' - for creating a EiffelBuild instanced-scoped instance of GB_INTERNAL_COMPONENTS.

`initialize_eiffel_build', as already stated, is to initialize EiffelBuild per-application. `initialize_eiffel_build' currently has no protection so it is possible to initialize it more that once, however it should be called once and only once per application.

GB_INTERNAL_COMPONENTS

GB_INTERNAL_COMPONENTS is use as a per-instance-of-EiffelBuild object. The instance should be created through `GB_SHARED_INTERNAL_COMPONENTS.new_build_components'.

GB_INTERNAL_COMPONENTS function is only to provide a single access point for EiffelBuild instance-scoped related objects needed to integrate EiffelBuild into an application. It provides direct access to EiffelBuild instance-scoped objects via attributes. Every main visual component of the original EiffelBuild (or the stand-alone) is available via a qualified call to `tools'. For non-visual elements of EiffelBuild; such as the undo/redo history, commands, clipboard, system status, persistence objects, etc. are available from a respective attribute in GB_INTERNAL_COMPONENTS.

Most of the exported attributes of GB_INTERNAL_COMPONENTS are discussed in this document so no details will be provided at this stage of the document.

Almost all classes in EiffelBuild, that require access to an instance of GB_INTERNAL_COMPONENTS have, a `components</nowiki>'</nowiki> attribute. Use this attribute to access the EiffelBuild instance version of GB_INTERNAL_COMPONENTS.

Refactoring

There is a lot of repeated code in EiffelBuild where multiple classes define `components</nowiki>'</nowiki> themselves. This should be refactored into it's own class where contracts can also be laid down.

GB_OBJECT*

The items seen in both the Widget Selector and the Layout Constructor are all associated with a GB_OBJECT* instance. More confusingly the Layout Constructor can display instances of GB_OBJECT*, which are instances of other GB_OBJECT* instances. This will be explained later. GB_OBJECT* is common base representation of the EiffelVision2 widgets/components that can be manipulated within EiffelBuild to create a EiffelVision 2 user interface, via the exposed id-represented interface.

Id's within EiffelBuild are important. Every user interface element has a unique, non-recycled, identifier that can be used with GB_OBJECT_HANDLER (see next section.)

Every GB_OBJECT* derived instance will have a unique identifier - `id'. This id will be unique throughout the project and will never change. Id's are not recycled whilst a project is loaded into an instance of EiffelBuild. There are good reasons for not recycling ids and these reason will be detailed later, in the next section. However during a load of a project the ids are compacted because it is safe to do so.

Here is a code snippet from GB_OBJECT*.

feature -- Access

	id: INTEGER
		-- A unique id representing `Current'.
		-- This is stored in the XML.

	is_top_level_object: BOOLEAN is
			-- Is `Current' a top level object in the structure?
			-- i.e. is it represented by an individual class?
		do
			Result := widget_selector_item /= Void
		end

	is_instance_of_top_level_object: BOOLEAN is
			-- Does `Current' represent a top level object within the system.
		do
			Result := associated_top_level_object > 0
		end

	associated_top_level_object: INTEGER
		-- The id of the top level object `Current' represents,
		-- or `0' if NONE.

GB_OBJECT* requires some carefully explaining when talking about the next subject; Objects, top-level objects and instances of top-level objects. As GB_OBJECT* is used by both Layout Constructor items an Widget Selector items, GB_OBJECT* has a notion of an instance of itself. `is_top_level_object' is a query to determine if the "object" is a top-level "window". Window is the legacy term used through out the EiffelBuild code to represent a top-level window or top-level widget in the Widget Selector. For top-level windows/widgets a single class is generated during code generation that represents the top=level window/widget. `is_instance_of_top_level_object' is the query to determine if an "object" in the Layout Constructor is actually an instance of a top-level "object" in the Widget Selector. When using EiffelBuild these are shown as locked widgets in the Layout Constructor, when added. Finally, if `is_instance_of_top_level_object' is True, `associated_top_level_object' will be a non-zero referrer id to the top-level object. The `id' of instances of top-level objects are different from the top-level object's instance `id'.

For items in the Layout Constructor, that are not instances of top-level objects (buttons, menus, containers, ...), `is_top_level_object' and `is_instance_of_top_level_object' will always be False, even though for a user interface perspective they appear to be instances of top-level objects (buttons, menus, lists, etc.) However a button, menu, list, etc. that are present in the Type Selector are not represented by a GB_OBJECT* but by a STRING type name. Therefore the Layout Constructor objects that are primitive widgets and containers represent the respective items on the Layout Constructor and not a top-level object.

GB_OBJECT_HANDLER can be used to retrieve GB_OBJECT* instances by id, and is discussed next.

GB_OBJECT_HANDLER

The object handler, GB_OBJECT_HANDLER, can be thought as a GB_OBJECT* heap. An instance-scoped GB_OBJECT_HANDLER knows about all GB_OBJECT* instances currently in an instance of EiffelBuild, both current (or active in a project) and those that have been "deleted" through a delete action.

GB_OBJECT_HANDLER is actually scoped to an instance of EiffelBuild, it is not enough to simply inherit GB_OBJECT_HANDLER. To access the instance-scoped instance of GB_OBJECT_HANDLER, use `GB_INTERNAL_COMPONENTS.object_handler'

It is important, within EiffelBuild, to retain all of the deleted GB_OBJECT*s for the sake of history. There are times when changes are made that affect the internal state of any number of GB_OBJECT* instances, and sometimes those changes need to be reflected in the deleted objects too, just incase the user wants to undo an action. Such a case is modifying a property on a top-level window/widget, which the deleted GB_OBJECT* is an instance of. Property changes are not undoable so it is foreseeable that a user could delete an instance of a top-level widget from the Layout Constructor, switch to editing the top-level widget, make a modification to one of it's child widget's properties. After the modification, switching back to the previous layout structure and then undoing the delete action. If the changes were not made to the deleted GB_OBJECT* the visual representation in the Display Window would be out of sync with the changes made prior in the Layout Constructor.

GB_OBJECT_HANDLER has two hash tables, for active and deleted objects, indexed by a GB_OBJECT*'s id; `objects' and `deleted_objects' respectively. However there exists a better means to retrieve an object by id using `object_from_id', which will retrieve an active object (not deleted) by id. To retrieve an object irrespective if it is an active or deleted object use `deep_object_from_id'. `deep_object_from_id' will probably most commonly used with undo actions.

Because GB_OBJECT_HANDLER knows about *all* GB_OBJECT*s instances in an instance of EiffelBuild, it should be used to performance any operations on groups objects, or on single objects that GB_OBJECT*s does not provide an accessible interface for.

Features

There are three features of GB_OBJECT_HANDLER that require some notation:

`root_window_object': This feature determines which class should be used as the root class when generating a project's code and system configuration files.

`reset_object': This feature is a powerful feature used to reset GB_OBJECT* instance states where an undo operation, reverting a GB_OBJECT* instance to it's previous state prior to some action, is simply not possible. Such a case is setting minimum widths and heights of a widget. Once set it is impossible to unset via a simple undo, which would reset the respective widget properties. In this case the entire widget structure, represented by GB_OBJECT* instances needs to be rebuilt. `reset_object' will serialize an GB_OBJECT* structure in memory, delete the structure and then resurrect the structure from deserializing the in-memory data. Later when explaining the command object model, `reset_object' is a reason why commands should not hold onto GB_OBJECT* instances but by the instance's `id'.

`build_object_from_string': This is a general purpose factory function used to generate GB_OBJECT* instance(s) from a textual representation of the EiffelVision 2 type name, such as EV_BUTTON. This function is primarily used by the Type Selector whose items pertain little information, which consists of a STRING type name the item is representing. The Type Selector is explained later in this document.

GB_EV_ANY*

GB_EV_ANY* is the base of all EiffelVision 2 associative classes that expose properties common to a number of EiffelVision 2 widgets. Such properties are `is_sensitive' from EV_SENSITIVE. All widgets classes in EiffelVision 2 support the notion of being sensitive so that part of a widget's properties can be covered in a EiffelBuild GB_EV_ANY* descended class - GB_EV_SENSITIVE.

This classification or abstraction of EiffelVision 2 properties is fundamental to the dynamics that play a part in the construction of the Object Editor, project persistence and retrieval as well as playing a role in code generation. All of these topics are covered later in this document

There isn't much more to say on GB_EV_ANY* that wont be explained in more detail and with more context further in this document. However there are two important features that are worth notation. These features handle synchronization of the multiple views supported by a single GB_EV_ANY* descendent instance. The views correspond to the Display Window and Builder Window windows. `for_all_objects' is used to synchronize all views of any changes made to any properties that the GB_EV_ANY* descendent instance exposes. `for_all_objects' takes a agent argument used to do the synchronization. `for_all_instance_referers' is similar to `for_all_objects' but instead examines the passed GB_OBJECT* instance's referrers and applies an agent to them.

Refactoring

GB_EV_ANY* and GB_EV_OBJECT have heavy interfaces. If any significant amount of work is to be done on EiffelBuild, refactoring these classes interfaces and deferring the declaration of the interfaces to more specialized descends would be recommended. From surface inspection I do not see why such an interface has to be available at such at high level of abstraction.

Object Editor

The Object Editor is the properties editor, usually to the right hand side of EiffelBuild and other are also found floating when a new one is opened. The fixed (docked) object editor has an additional characteristic over the floating object editors, is automatically synchronized with the currently selected item in the Layout Constructor. Apart from that, the object editors, floating or not, are one and the same. Each object editor is an instance of GB_OBJECT_EDITOR.

Access to all of the opened object editors is available through the instance-scoped GB_OBJECT_EDITORS instance, available from `GB_INTERNAL_COMPONENTS.object_editors'.

To create a new object editor you can use the `GB_INTERNAL_COMPONENTS.new_object_editor' factory function, passing a relevant GB_OBJECT* instance, which will be used to determine how to build the object editor and what state to display.

Supporting Dynamic Building

Before continuing to explain how the Object Editor work and how it builds its user interfaces dynamically, there are a few things to mention with regards to extending EiffelBuild to include new widgets.

`GB_SUPPORTED_WIDGETS.included_all_components' needs to reference any EiffelVision 2 widgets and EiffelBuild object property classes, such as GB_EV_FONTABLE that EiffelBuild supports. This has to be done because the Object Editor will use INTERNAL to create instances of GB_EV_XXX dynamically based on the corresponding widget, so the GB_EV_XXX classes need to be compiled into EiffelBuild or EiffelBuild will fail at runtime.

GB_EV_HANDLER also needs to be updated if any new property classes need to be added based on a new abstract class used by an EiffelVision 2 widget, such as an addition of a property class like EV_TEXTABLE. `GB_EV_HANDLER.supported_types' returns the list of supported classes, which is used in various places throughout EiffelBuild to support it's dynamic nature. If a newly added class is not present in the list then the properties for it will not appear in the Object Editor when it build its user interface. `supported_types' is used from `GB_OBJECT_EDITOR.construct_editor' to build the user interface according to the EiffelVision 2 conformance to the supported types. `construct_editor' iterates through the supported types and checks the EV_ANY from access via `object.object' for conformance. If the retrieved EV_ANY instance conforms to one of the supported types then a new GB_EV_ANY* derived objects is created and initialized using INTERNAL. `GB_EV_ANY*.attribute_editor' (a fragment of the complete object editor user interface for the conformed type - i.e. EV_FONTABLE) is then appended to the object editor's user interface to provide object edition support.

How it Works

The Object Editor is dynamic in that it examines the inherited hierarchy of a given EiffelVision 2 widget classes. This is done through the creation of a GB_EV_ANY* descendent.

If a user is in the Display Window and they change the state of a toggle button, this change in state needs to be reflected in the active Object Editor. This can be achieved by redefining `GB_EV_ANY*.set_up_user_events', as does GB_EV_GAUGE_EDITOR_CONSTRUCTOR, which is descended by GB_EV_GAUGE. Any widget property states from the Builder Window are ignored, by design. This is because only those widget property state changes from the Display Window should be reflected in the active Object Editor. This means if you move the gauge bar on an EV_GAUGE in the Display Window it's property state changes and will be reflected in the Object Editor, if the associated layout item is selected in the Layout Constructor.

The Object Editor is, internally, a series of "panels". The term panel is just transient term used to identify how the Object Editor consists of user interface fragments and is stitched together to form the complete Object Editor user interface.

Object Editor editor panels, which are retrieve from calling `GB_EV_ANY*.attribute_editor' on a derived GB_EV_ANY* instance, are built using a GB_EV_EDITOR_CONSTRUCTOR* as a base. There are a number of concrete versions such as GB_EV_SENSITIVE_EDITOR_CONSTRUCTOR, which is used to create the is_sensitive check box.

Generally the deferred `GB_EV_EDITOR_CONSTRUCTOR*update_attribute_editor' routine is called from `GB_EV_ANY*.attribute_editor' to synchronize the Object Editor panel with a displayed Display Window EiffelVision 2 widget. Any Object Editor panel user interface, that refers to a property of some EiffelVision 2 widget, is responsible for updating the associated widget in the Display Window and possibly the Builder Window. If any other views are added, then it is possible that those views have to be updated also. This can be achieved using one of the synchronization features of GB_EV_ANY*. If synchronization needs to be applied to all views use `for_all_objects'. Look at the EiffelBuild code to see how these functions are used. It is reasonable to assume that all views should be updated, however there are some cases where updating all of the views should not take place. Such as case is with `disable_sensitive' of EV_SENSITIVE. If this action was applied to all views the Builder Window would be useless because the pick and drop functionality would be disabled as a result, hence why only the Display Window (the first view) is updated.

When changing a property in the Object Editor the EiffelVision 2 widgets in one or more views need to be updated, even if they are not visible. `GB_EV_ANY*.update_editors' needs to be called to refresh all opened objects editors, to synchronize any changes in property state.

GB_EV_SENSTIVE is one of the most basic examples and should be looked at to get a quick view of how things work with constructing an Object Editor panel, and how to synchronize changes made to the Object Editor properties with the available views.

The use of constants with Object Editors are a little more tricky. To save reinventing the wheel each time, constant fields use GB_INPUT_FIELD*. This deferred class handles all of the necessary user interface manipulation to switch between manual entry and selecting a constant, in which case a combo box will be displayed in place of a text field. The buttons are also added to select to use a constant and reset constant. The input field handles updates of the constant manifest and modifies held lists according to what's been added, removed or changed. There are a few descendents of GB_INPUT_FIELD*, one for each type of constant such as GB_INTEGER_INPUT_FIELD for INTEGER constants. GB_EV_EDITOR_CONSTRUCTOR* derived classes (used to build the Object Editor panels) are now much simpler to implement because of the brunt of the work is done using GB_INPUT_FIELD*. It is as simple as becoming a client of a GB_INPUT_FIELD* descendent. GB_EV_GAUGE_EDITOR_CONSTRUCTOR shows a good use of being a client to GB_INPUT_FIELD* descendents.

Dirty Notification

When setting any properties through any opened Object Editor `GB_EV_ANY*.enable_project_modified' should be called. Failure to do this will result in project modifications not being accounted for, resulting in prevention of the notification that the project has been modified on exit. It will also prevent the Save button from becoming active.

Optimizations

`GB_EV_ANY*.update_editors' is not optimized. This could be optimize to only update the corresponding display widget attributes for the properties that have changed in the Object Editor, instead it just sets all of the properties for a associated widget.

Tool Windows

EiffelBuild is comprised of a number of tool windows. Every dialog and pane seen in the EiffelBuild stand-alone application is available from the `GB_INTERNAL_COMPONENTS.tools'. `tools' returns an EiffelBuild instance-scoped instance of GB_TOOLS, which provides access to all of the tool windows and dialogs available in EiffelBuild.

The following sections describe the main tool windows and indicates what classes are used to build their visual representation. This is important to note for anyone extending EiffelBuild to add new widgets to the Type Selector or needs to make modifications to the Widget Selector or Layout Constructor.

Type Selector

Eiffel build.type selector.png

The Type Selector tool window is a window build from one of two items depending on the display mode. There is the classic [tree] mode and the icon [figures] mode. When in the classic mode, the Type Selector will appear as a tree with type icons and descriptions. Icon mode will show only the type icons, for a compact view. These classes are GB_TREE_TYPE_SELECTOR_ITEM and GB_FIGURE_TYPE_SELECTOR_ITEM respectively. Both derive the deferred class GB_TYPE_SELECTOR_ITEM*.

When a pick and drop action is applied from the Type Selector to the Layout Constructor the picked item, represented by a GB_TYPE_SELECTOR_ITEM* derived class instance, has its `GB_TYPE_SELECTOR_ITEM.generate_transportable' implementation called. The implementation is to create a GB_OBJECT* instance that conforms to the STRING representation of `GB_TYPE_SELECTOR_ITEM.type'. The created GB_OBJECT* instance is then used to create a new GB_STANDARD_OBJECT_STONE instance before it is returned from `generate_transportable'.

When a pick and drop action is performed from the Layout Constructor to the Type Selector, for swapping items or a conforming type, `GB_TYPE_SELECTOR_ITEM.can_drop_object' is called to give the item a chance to veto the incoming stone. Type Selector items should implement this to prevent swapping objects that do not conform, because of the number and/or type of children.

Layout Constructor

Eiffel build.layout constructor.png

The Layout Constructor, an instance of GB_LAYOUT_CONSTRUCTOR, is the primary window in EiffelBuild. As all other tools windows, the instance respective window can be retrieved from `GB_LAYOUT_CONSTRUCTOR.tools.layout_constructor'.

Each item in the Layout Constructor is an instance of the GB_LAYOUT_CONSTRUCTOR_ITEM class. GB_LAYOUT_CONSTRUCTOR_ITEM instances are created from the representing GB_OBJECT*'s `layout_item' attribute.

The pick and drop pebble functions are implemented on GB_OBJECT*, which probably can be refactored to be placed at a higher level of specialization. Functions such as `can_add_child' are used when picking and dropping to determine if the target of a pick and drop action, from a peripheral window, is supported given the source. In `can_add_child' it would be if the target in the Layout Constructor supported the notion of children.

The Layout Constructor is able to persist state, both between switching top-level windows/widgets and between sessions (unloading and reloading a project.) Every item in the Layout Constructor' has an associated GB_OBJECT* instance. `GB_OBJECT*.is_expanded' reports the expansion status of an item. Because `is_expanded' is implemented on GB_OBJECT* instead of GB_LAYOUT_CONSTRUCTOR_ITEM it will always be held onto as GB_OBJECT*s are never destroyed and new ones created.

Widget Selector

Eiffel build.widget selector.png

In the EiffelBuild code, due to legacy reasons, a widget in the Widget Selector is sometimes referred to as a window or top-level window. This is because EiffelBuild at a point in history only supported the notion of a top-level window, but now additionally supports top-level widgets.

Top-level widgets refers to those widgets that have been extracted from part of a widget hierarchy, in the Layout Constructor, and have been designated to be a reusable widget. A separate class will be generated for each top-level window/widget during code generation. As such the selected hierarchy can not be expanded or manipulated except when editing the top-level widget on it's own, from the Layout Constructor.

To retrieve the EiffelBuild instance-scoped instance of GB_WIDGET_SELECTOR, use `GB_INTERNAL_COMPONENTS.tools.widget_selector'.

The Widget Selector supports a directory hierarchy and new windows/widgets can be places into arbitrary directories. The locations are never set in stone and a user can pick and drop top-level windows/widgets to and from directories. This functionality needs mentioning because of the implementation in place to do this needs to be smarter than just moving a node around a tree. There is the possibility of class name conflicts unless this action is handled correctly. If a system has already been generated and then a top-level widget/window is moved into a different folder, the old generated classes need to be removed. GB_COMMAND_MOVE_WINDOW command class handles this action. The class is also undoable so it should be used when pragmatically moving items from directory to directory. Although it would be a rare case, it required mentioning for potentially interested parties.

All items in the Widget Selector descend GB_WIDGET_SELECTOR_COMMON_ITEM*. A GB_WIDGET_SELECTOR_DIRECTORY_ITEM instance represents a directory in the Widget Selector and a GB_WIDGET_SELECTOR_ITEM instance is for top-level windows/widgets. Just like the Layout Constructor, there is a GB_OBJECT* instance associated with every top-level window/widget in the Widget Selector. The GB_WIDGET_SELECTOR_ITEM instance representing the associated GB_OBJECT* is accessible through `GB_WIDGET_SELECTOR_ITEM.widget_selector_item'.

Component Selector

Eiffel build.component selector.png

The Component Selector is similar to the Widget Selector but instead of making associated references when adding a top-level widget to the Layout Constructor, the Component Selector creates new GB_OBJECT* object hierarchies. What this means is a pick and drop action from the Layout Constructor to the Component Selector will create a new object hierarchy for the Component Selector item. In reverse, the same applies - a new hierarchy is created in the Layout Constructor from the Component Selector.

The Component Selector, GB_COMPONENT_SELECTOR, is another instanced-scoped tool window available from `GB_INTERNAL_COMPONENTS.tools.component_selector'. The items in the Component Selector are represented by GB_COMPONENT_SELECTOR_ITEM instances.

Design Limitation

The current design of the componentized EiffelBuild poses a design limitation. The EiffelBuild tool windows are per-instance windows and are internally tied to an EiffelBuild project/instance. This means that an application that supports multiple EiffelBuild instances, but only displays one set of EiffelBuild tool windows, will need to remove and add the tool windows for the respective project/instances when each EiffelBuild main windows is activated (focuses, shown, etc.)

Other Tool Windows

There are many other tool windows in EiffelBuild, but, at the time of writing, are not present in the main window user interface. To access and instance-scoped EiffelBuild tools use: `GB_INTERNAL_COMPONENTS.tools.<tool_name>''.

Constants

Every constant in EiffelBuild descends the common EB_CONSTANT* ancestor. There are number of specialized constants, one for every type that can be set in EiffelBuild. Such a concrete class is GB_STRING_CONSTANT, representing STRING constants.

EB_CONSTANT* has a number of attributes one of which is `referers', which is a list containing constant contexts (GB_CONSTANT_CONTEXT instances) of referrers to the constant. A constant context contains; information about the constant, the GB_OBJECT* (via `object') using the constant and a STRING attribute representing the name of the class attribute (`attribute') which the constant is attached to (i.e. `EV_TITLED_WINDOW.title'.) `property' is another attribute, which is the class type name which the attribute is a member of.

`referers' is used to propagate any changes to the visual components, such as those found in the display window. When a constant changes all referrers must be update, so this is what it does through the context object. `referers' is also used to check if a constant is attached before removing the constant.

The instance-scoped constants handler GB_CONSTANTS_HANDLER can be accessed using `GB_INTERNAL_COMPONENTS.constants'.

Commands

The command model used inside of EiffelBuild is very similar to the model used within EiffelStudio. There is a command class for all commands available from the menu and toolbar. This means a EiffelBuild clients can create their own user interface, to represent the commands available in the EiffelBuild command model. Commands are also available for any major operations that may require a undo/redo; such as moving, adding or deleting a widget or widget structure.

There are two types of EiffelBuild commands - Undoable commands and non-undoable commands. For all commands that have undo actions, or are at least undo-applicable, command classes inherit GB_COMMAND*. For those command classes, such as the command to open a project - GB_FILE_OPEN_COMMAND - that have no undo action they inherit E_CMD*. E_CMD* is very abstract and is subclassed many times before specialized to a command class such as GB_FILE_OPEN_COMMAND.

Both GB_COMMAND* and E_CMD* are deferred and both define `execute', which should be called when processing the command action. GB_COMMAND* also defines `undo' to provide a means to undo the actions of `execute'. `textual_representation' is the final feature defined in GB_COMMAND*. All undoable commands should return a STRING to represent the command in the history, which is used by the EiffelBuild history tool to display the command in a list.

deferred class E_CMD

feature -- Access

  executable: BOOLEAN is
      -- Is Current command executable?
      -- (True by default)
    do
      Result := True
    end

feature -- Execution

  execute is
      -- Execute Current command.
    require
      executable: executable
    deferred
    end

end -- class E_CMD
deferred class
  GB_COMMAND

feature -- Basic operations

  execute is
      -- Execute `Current'.
    deferred
    end
    
  undo is
      -- Undo `Current'.
      -- Calling `execute' followed by `undo' must restore
      -- the system to its previous state.
    deferred
    end

feature -- Access
    
  textual_representation: STRING is
      -- Text representation of command exectuted.
    deferred
    ensure
      result_not_void: Result /= Void
    end

end -- class GB_COMMAND

Commands that derive GB_COMMAND* should add instances of themselves to the history when executed (the call to `execute'). The following code is taken from `GB_COMMAND_NAME_CHANGE.execute':

execute is
    -- Execute `Current'.
  do
    ...
    if not components.history.command_list.has (Current) then
      components.history.add_command (Current)
    end
    ...
end

It should be asserted that commands associated with GB_OBJECT*s should store GB_OBJECT*s ids and retrieve the GB_OBJECT* using the GB_OBJECT_HANDLER, and not store the GB_OBJECT* directly. This does not just pertain to undo actions but to all actions. This is because some actions from within EiffelBuild will replace the held GB_OBJECT*. This may sound confusing initially but objects are replaced when the are reconstructed through the `GB_OBJECT_HANDLER.reset_object'. When an object is reset the GB_OBJECT* is removed and an new one is created with the same id. This means that the held GB_OBJECT* in the command is not the same as instance held in the object lists of GB_OBJECT_HANDLER.

Accessing Commands

Like all other components in EiffelBuils the commands can also be accessed through `GB_INTERNAL_COMPNENTS.commands'. This provides a EiffelBuild instance-scoped GB_COMMAND_HANDLER</font>, through which the EiffelBuild user interface related commands can be accessed. User interface designers/implements should go through `GB_INTERNAL_COMPONENTS.commands' to retrieve and execute the respective command for the user interface elements.

Other Instance-Scoped Components

This section of the document describes some of the other EiffelBuild instance-scoped objects, accessible through GB_INTERNAL_COMPONENTS.

History

EiffelBuild provides a instanced-scoped history object, GB_GLOBAL_HISTORY accessible through `GB_INTERNAL_COMPONENTS.history' that may be used to add command execution to the history as well as rollback commands through undo.

System Status

For every instance of EiffelBuild there is an instance of GB_SYSTEM_STATUS, which reports on the EiffelBuild instance's current state. Clients can retrieve the instance-scoped version of GB_SYSTEM_STATUS via `GB_INTERNAL_COMPONENTS.system_status'.

GB_SYSTEM_STATUS provides clients access to `current_project_settings', which can be used to retrieve information on a project loaded into the respective EiffelBuild instance.

Clipboard

EiffelBuild supports the standard clipboard functions; cut, copy and paste. All functions are represented by a command in the command object model. The EiffelBuild clipboard, GB_CLIPBOARD, is per-instance of EiffelBuild, accessible through `GB_INTERNAL_COMPONENTS.clipboard'. Cut, copy and paste operations cannot span multiple instances of EiffelBuild as of current because of the dependencies on top-level objects available in one EiffelBuild instance, which may not (most likely not) be available in another EiffelBuild instances.

The information persisted in the clipboard is a serialized representation of a widget structure in XML. `GB_CLIPBOARD.set_object' serializes a GB_OBJECT* and stores it in `GB_CLIPBOARD.contents_cell' for later retrieval. Querying `GB_CLIPBOARD.object' will perform the reverse, deserializing the XML in `GB_CLIPBOARD.contents_cell' and constructing a GB_OBJECT* from it.

One thing to note is that the clipboard does not preserve any names given to any of the objects serialized, which would be the feature names in the generated code. So serialization and deserialization will yield an object structure without any name information.

EiffelBuild supports the ability to view the clipboard contents via a display window tool as well as a pick and drop version of paste, via a pick and drop from the paste toolbar button to the destination area.

XML Serialization and Persistence

Throughout EiffelBuild there is a need to serialize widget structures, represented by hierarchies of GB_OBJECT*s. The two main classes in serialization of an active widget structures is GB_XML_STORE and GB_XML_HANDLER. An instance-scoped instance of GB_XML_HANDLER can be retrieved from `GB_INTERNAL_COMPONENTS.xml_handler'

There is a EiffelBuild command available - GB_FILE_SAVE_COMMAND - Calling `execute' will begin a complete serialization of all of the project's widget structures and persist the result to disk. It is a good place to look to learn how to serialize widget structures.

GB_XML_HANDLER is a wrapper, of sorts, for the GB_XML_STORE and GB_XML_LOAD classes. It also handles special operations such as importing and merging projects files persisted as XML.

GB_XML_STORE is fairly straight forward in it's interface and implementation. It's primary routine `store' is used to store the project widget structures, built in EiffelBuild, to disk as XML. The process of storing creates an XML structure and then write that to disk in a text document. A document STRINGis create using `GB_XML_STORE.generate_document'. GB_XML_STORE is specific to serializing only the widget structures and storing the serialized XML data to disk.

Creating system_interfaces.xml

'system_interfaces.xml' is generated entirely inside `GB_XML_STORE.store', which is called by `GB_XML_HANDLER.save'

The first thing generated in a document is the constants defined in a loaded project. Constants require to be stored at the beginning of a file so the deserialization process can correctly initialize the constants for future use when assigning constants to widget properties when deserializing the widget structures. Generation of the XML is done by iterating every constant in the project and calling GB_CONSTANT.generate_xml'.

The next step stores all of the directories and windows (or widgets in the Widget Selector.) with a call to `store_windows'. For every window the properties, which are both the top level windows/widgets and property objects (EV_SELECTABLE, EV_TEXTABLE, ...), are generated into the window XML node. If any directories are in the project, directories will also be generated in a nested fashion, with directories in directories and windows in directories.

Any selected events/action sequences that have been subscribed to on a widget, through the Object Editor user interface, are generated after any properties are generated.

The entire XML persistence is nested. So every widget in a window is generated as a child widget, and every widget within a widget is t is generated as a child of that widget, and so on. For every widget/window, the same process as used in the Object Editor; the use of `GB_EV_HANDLER.suppported_types' is iterated, a GB_EV_ANY* is created and conformance is checked. Then for every conforming type/property generate_xml' is called on the associated GB_EV_ANY* instance, with the current XML element node passed.

After all of the top-level windows/widgets have been generated the generated XML is stored in `GB_XML_STORE.document'.

XML Deserialization and Retrieval

Retrieving and deserializing XML is the same process are serializing and storing XML, as explained in the previous section, but in reverse.

Instead of using GB_XML_STORE, GB_XML_LOAD is used. GB_XML_HANDLER wraps the deserialization of the 'system_interface.xml' files in the `GB_XML_HANDLER.load'.

Just like storing XML, there is a EiffelBuild command available for retrieving a project - GB_FILE_OPEN_COMMAND - Calling `execute' will start a complete deserialization and create all of the necessary instances required to retrieve a authored user interface description, created using EiffelBuild.

Loading/retrieval starts at `GB_XML_HANDLER.load'. `load' loads and then parses the XML data. Parsing is the direct opposite of the generation, it looks for constants, windows and then events, in the XML. During the parsing process the necessary objects and structures are rebuilt.

When parsing, upon discover of a constant `GB_INTERNAL_COMPONENTS.constants' is retrieve and `build_constants_from_xml' is called to rebuild the constants for the project using an respective XML node. For windows and widgets and their serialized property classes (EV_SELECTABLE, EV_TEXTABLE, ...) a new GB_EV_ANY* is created and initialized and then `modify_from_xml' is called to modify the object associated with prior created GB_OBJECT* instance.

As a helper function, GB_EV_ANY* provides the function `get_unique_full_info', which returns a hash table containing information read from all of the subnodes converter to objects of type ELEMENT_INFORMATION. Using the returned hash table it is easy to retrieve atomic pieces of information about subnodes of a widget being deserializing . It should be noted that when querying the table for information, some information may not be returned. This is supporting backwards compatibility so that older serialized widget structure XML descriptions, that do not have the new elements, will not be flagged as erroneous. For a simple example see `GB_EV_SENSITIVE.modify_from_xml'.

GB_EV_ANY* also provides `retrieve_and_set_XXX_value', where XXX is a type that supports constants (e.g. string, integer, font, ...). For supported constant types examine the all classes that descend GB_CONSTANT*. This function will return back a respective type; `retrieve_and_set_string_value' returns an STRING instance, `retrieve_and_set_color_value' returns an EV_COLOR instance, etc. These function take a STRING value and examine it to see if the STRING is a constant reference. If so then the constant value is used, else the manifest XML string data is parsed for conversion to the corresponding return type. For an example of this see `GB_EV_TEXTABLE.modify_from_xml'. These functions highlight a need to have the constants loaded before building of the widgets, failure to do so results in the constant value not being returned correctly.

There are a number of situations where setting the attributes of a widget needs to be deferred. Such a case is with EV_VERTICAL_BOX, where `disable_item_expand' needs to be called after the children have been added, not before. For situations such as the one mentioned, use the `deferred_build', available from GB_INTERNAL_COMPONENTS and call `defer_building' passing the current instance of a GB_EV_xxx (GB_EV_VERTICAL_BOX in the provided example) along with the source XML element instance. For those GB_EV_xxx classes that require deferred building of their user interface, they should redefine `GB_EV_ANY*.modify_from_xml_after_build', which will do what `modify_from_xml' should do if attribute setting were not deferred. Please note that the widget itself should be created in `modify_from_xml', only the setting of attributes should be deferred.

That's it. The creation of constant, directories and window/widget objects is done while parsing. Once everything has been created the user is free to modify the widget structure and it's properties, from within EiffelBuild.

Code Generation

Code generation begins with GB_CODE_GENERATOR, the root of all Eiffel code generation for a loaded EiffelBuild project. There are two modes of generation; Full generation, supported by the `generate' routine, which will generate the project files and Eiffel classes for the entire loaded project. Alternatively there is the `generate_window' that can be used to generate a single top-level window/widget Eiffel class. This prevents modification or "touching" of already generated Eiffel classes.

Eiffel code generation is not a single, but a three phase process. The first phase is to generated an intermediate, in-memory XML representation of the project, using GB_XML_STORE. The XML is generated in exactly the same way that the XML is persisted to disk when saving a project but is retained in-memory. The next phase is to take the generated XML and then create an internal data structure that is easier to read and navigate when performing actual code generation. The data structure has to be created so that any system interface that reference yet unparsed objects, further down in the XML, can be resolved before generation occurs. It solves dependencies between top-level window/widget objects. The data structure is created in `parse_directories' and then each window/widget data structure is created in `prepass_xml'.

`parse_directories' and `prepass_xml' takes a instance of GB_GENERATED_INFO, which they will in turn modify and populate with the relevant data. `parse_directories' uses the root node, which is available from `GB_GENERATED_INFO.document_info', and `prepass_xml' uses the current generated info instance, attained from the within the structure of `document_info'. A new `GB_GENERATED_INFO.document_info' is created prior to calling `prepass_xml', and then is passed to `prepass_xml'. These routines are used to populate the GB_GENERATED_INFO data structure so that actually Eiffel code can be generated for them.

`GB_GENERATED_INFO.name' and `GB_GENERATED_INFO.actually_name' features require a little explanation. `name' refers to the name given to an object through the Object Editor user interface. `actual_name' is name that will be generated so it can be correctly accessed in the code. Top-level widgets need to be handled specially depending on if they are being inherited, in which case `actual_name' returns an empty STRING to indicate no qualified call is to be generated. If code generation generates top-level windows/widgets as clients `actual_name' will return a name to use to create a qualified access call. Finally, `actual_name_for_feature_call' uses `actual_name' to determine if a qualified call needs to be generated for the a top-level window/widget. `actual_name_for_feature_call' is called in all of the `generated_code' features present in the GB_EV_xxx widget adapter classes.

Back to `GB_CODE_GENERATOR.generate': After creating the structure, in GB_GENERATED_INFO, and everything else that is necessary, all of the selected project files; such as project ace files and APPLICATION class(es), are generated based off of the user specified project settings. Project settings are EiffelBuild instance and project-based, that is per-instance-per-project. The generation is trivial for these peripheral files/classes, it's a simple matter of replacing token names within a template. After that the `build_main_windows_implementation' routine is called to do the actually building of the classes to represent the project's system interface built using EiffelBuild, which is a little more complex.

All EiffelBuild templates files are located in 'Delivery/build/templates'.

Code generation for top-level windows/widgets is based also on templates. An example is the 'build_class_template_imp.e', located in the templates directory. The default code in there is used to handle the runtime switching of constants, supporting runtime internationalization changes. The code is littered with "tags" that look like non-well-formed XML, in that there is no ending tags. Tags are represented by <TAGNAME> and are replace by EiffelBuild. There is no "engine" to speak of that looks for the angle brackets, it's merely a simple search and replace on text. The brackets were used to prevent keyword/other text clashes within the class text and to provide a simple mechanism to replace text tokens.

`build_main_windows_implementation' is factored down into separate code generation routines, such as `generate_declarations', etc. `generate_settings' is the most interesting because it uses the same mechanism used in the Object Editor to discern what panels to create for a selected widget. For every widget a GB_EV_ANY* descendent is created and initialized and the `generate_code' is called on the created instance. This does the job of adding all of the setter calls for a widget. Again check `GB_EV_SELECTABLE.generate_code' for an example, to see the use of `actual_name_for_feature_call' described earlier.

Making Changes

It is not recommended to change the code generation code unless you have a concrete understanding of how it works. If code generation needs to be modified please do the following:

  • Test generation in all client/inheritance modes.
  • Test it again!

Additional Support

GB_CODE_GENERATOR could be extended to include another feature, or simply just to modify `generate' to only generate classes that have changed. As the time stamps inside each generated class always change it is annoying for source code repositories user to have their generated classes changed when there is no need to change them.

Final Word

This is a high level explanation of code generation. To understand fully how the code generation process works and the three phases used in code generation (XML, data structure and then code generation) you will need to follow the code and read all of the comments.

EiffelVision2 Tour

Be careful when modifying EiffelBuild as part of the Object Editor internals is duplicated and used in the EiffelVision2 Tour. Specifically the XML persistence and retrieval mechanism. You should cross examine the GB_EV_BOXs in both EiffelBuild and EiffelVision2 Tour's code to see how they differ. This forked abstraction is to prevent the requirement of having to compile EiffelBuild in its entirety into the EiffelVision2 Tour, as we do not want to distribute the EiffelBuild code with the tour.

Future Additions/Changes

There is a need to be able to load a single windows at a time. The problem with the current implementation is that there is no support for resolving references to other top-level widget/windows. The other issue is that EiffelBuild currently stores duplicate layout/object information about referenced top-level windows/widgets. This is not good because if the dependency is modified in any way, the window/widget that consumes the top-level window/widget needs to retrieve the changes.

-- Place to specify submitted suggestions --