Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[eclipse-incubator-e4-dev] Initial discussion on the 'modelled' workbench UI



Hello, since I'm the person respnsible for the current modelled UI demo presented at EclipseCon I thought I'd kick off the discussions about the architecture we're going to end up with by presenting the thought process that lead to the current demo's structure...

[ Coffee Alert: this email is somewhat long but I wanted to capture everything in one place. You may want to get a fresh cup before proceeding...;-). Once it's been through the mill here for a bit I'll capture the result, polish it up with screen caps etc and put it on the WIKI. ]

The goal of this email is to stimulate discussion about the design and its implementation; it's an open invitation for *YOU* to become involved !

The general idea is to provide an implementation that is much simpler both in its internal implementation and in its external API's (while maintaining the ability to host 'API-clean' extensions written against 3.x). As alluded to in McQ's talk at EclipseCon much of the codebase has, over time, become 'baroque' (read 'overly complex'). UI code is particularly susceptible to this type of erosion since any GUI is under unrelenting pressure from its community for 'tweaks' (this can be treated as a constant that the new architecture should at least attempt to mitigate).

I see re-working this code as simple self-preservation, it's just as hard for me to work with as it is for external developers; I've just spent the requisite 2-3 years working with it and have grown calluses to mask the pain...;-).

The bottom line is that it's become soooo complex internally that it has become extremely difficult for us to get any significant level of community input (our *real* goal here!). The learning curve to understand enough to create a valid patch for any but the simplest of fixes is simply too high, requiring too much of an investment from the would-be contributor. It also means that when patches are submitted (even by truly solid devs) the committers have to spend a -lot- of time working with the contributor to refine it to cover a slew of 'side effects' that, while not necessarily a part of the functionality of the new feature/fix, are required in order to have it observe all of the niceties of 'proper' integration into the Workbench (i.e. Does it handle all the view options like Movable, Closable? Themes? Correct use of Workbench constants / prefs?...ad nauseam, especially for the poor sod who contributed the original patch...;-). While some of this is or can be aided by simply documenting the requirements, hopefully when we're done we'll then be in a state where external contributors can provide features/fixes without adverse side-effects on other parts of the implementation. The same issues serve to limit the effectiveness of the current committers, slowing the overall pace of bug fixing and enhancement.

This -must- change...OK, how ?

Well, it's pretty well generally accepted that the Model/View/Controller architecture is the way to go in UI's and, indeed, much of the current framework is implemented this way already. Unfortunately we have way too many different models. In many cases this is the result of each UI enhancement being discreetly coded by the individual responsible and, being good GUI developers, they each implemented their own MVC architecture. We'll see some examples later...

The basic premise behind the demo is to migrate the various models under a single modelling architecture and show how this structure can subsequently be used to drive the UI (rather than "divide and conquer" I call this "consolidate and conquer"...;-). The Model Engine and its elements would become 'first class' citizens in the platform which we can then 'tool up' as appropriate (scripting...) and get maximum benefit.

The rules are simple (and many folks will say "Duh") but you'd be amazed at how many folks break them...

1) The modelling engine is responsible for containing the currently defined state and reporting any changes to its listeners. Proper implementations would also support save/restore and transactions with (optional) undo/redo. Clients will be supplied with an implementation (implementations?) and will gain immediate benefit from using it (they can expect standardized JFace viewer support including automatic update and editing support).

2) The GUI components are responsible for presenting the current state of (some subset of) the model and maintaining the correct display of the model's state as it changes. In this case the GUI is everything you see when running eclipse except for externally defined parts (even here we expect to 'port' many of our existing views over to a modelled approach).

3) In a non read-only world the GUI will also map certain user gestures (i.e. clicking a tool, activating a view) onto -proposed- changes in the model. The word 'proposed' is very important because it's IMO the most common cause of breakages in the architecture. the GUI bit proposing the changes should never take any presumptive action (like adding the element to its list, changing the item's name...). It should say "the user wants to do 'x' and then sit back and listen like everybody else to see what actually happens. This allows a GUI whose model contains internal logic (perhaps an attempt to rename an object to "Foo" will result in a name clash so the model automatically sets it to "Foo(2)").

NOTE: we gain an immediate advantage for threading here. Changes to the model can be made from -any- thread but the listeners are always fired on the GUI thread, little or no need for 'sychExec'.

[ Disclaimer #1: the current ModelElement implementation is sufficient to get started but should likely be replaced with a 'proper' modelling engine ASAP. Candidates? However, whatever the actual implementation(s) are the public API should be as simple as the ModelElement's. ]


In this particular case our model will represent the graphical elements of an Eclipse session; WorkbenchWindows along with their main menus/toolbars/trim and detached windows. Also included in the model is the equivalent to the 'Perspective'; that which owns the 'client area' of a given Workbench (or Detached) window. The life-cycle of the actual widgets is through a factory that enforces a particular protocol on -all- gui elements and is extendable so that clients can add new element 'types' and/or override the default implementation for an existing type (this covers custom tabs/menus/toolbars/sashes as well as being the mechanism through which 'external' parts create themselves...).

NOTE: In its current implementation the complete workbench window's UI model doesn't need anything but SWT + the appropriate factories!! This is by design; one of the main paths to simplicity here is to separate the GUI's 'capabilities' (what elements it can render) from the any logic that constrains the model's structure (such as only allowing a single copy of a (singleton) view in a perspective...). This approach is -not- a magic bullet. As much care must be taken in the model definition, its documentation and UI listener implementation as is currently taken when defining our current API. The advantage is that at least all of this information can be defined in a single location and shouldn't be nearly as overwhelming as our current API (the current demo is based on ~50 properties and 6 listeners).

Let's take a quick look at the demo's model to see what we have so far:

If you run the 'presentation model' demo (I'll be updating the WIKI with the setup instructions once I've got this note out). If you open the 'Workbench Model' view you'll see a tree that exposes the current model. At the top level are 5 main sub-trees:

Tab Styles:

This model's contents define set of common attributes that can are applied to a CTabFolder. The choice of which 'bucket' to use is based on the GuiModel#CSS_CUR_STYLE property. Right now it contains three choices; 'active' view stacks, non-active view stacks and editor stacks. its most interesting feature is probably that it maintains a list of the GUI elements that are currently using it, allowing the GUI to respond to changes in the styling model by updating -all- of the relevant stacks.

In its current form this is a hack! We need to move to a proper CSS styling metaphor but, unfortunately, I can barely spell CSS so I'm gonna need help on this...;-). The styling mechanism (whatever it turns out to be) should be part of the 'createGui' protocol provided by the ControlFactory to allow for the styling to be applied to any part in a symmetric manner.

[Disclaimer #2: The current choices of class and property names need polish. For example, the 'ControlFactory' actually can create Widgets. ]


Workbench Preferences:

This entry only contains two children; 'API' and 'Internal'. The properties of these elements come from a 'PreferenceModel' implementation. This is a subclass of ModelElement that brings a ScopedPreferenceStore under the generic model's umbrella. This is a classic example of what we're trying to do; the preference stores are pretty well completely parallel to the generic model, they're a property bag with get/set property and event notification of property changes so with simple wrapper (currently 79 lines) we can move it directly into the generic model without affecting any of the current code.

One of the interesting points to note here is that changes in many of the properties exposed here will -not-  cause the GUI to 'refresh' properly. This is the result of an incorrect implementation; the refresh code is implemented directly in the PreferencePage rather than being captured in a listener that responds to a change in the property's value. An example of one that works is "SHOW_TEXT_ON_PERSPECTIVE_BAR", an API property.

Note that this is not an artifact of the modeling approach; there is existing API that allows clients to modify these values. Using these calls would also result in a mis-match between the preference's value and the current display (i.e. they're already broken, the demo just exposes it).

Platform UI Extensions:

This sub-tree contains the complete set of -all- of the extensions to extension points defined by the platform UI. It is again implemented by a small wrapper that moves an IConfigurationElement into the generic model (i.e. there's no need to make a deep copy of the information because IConfigurationElement already contains analogues for the necessary ModelElement API).

This is my most likely candidate for introducing the approach in the Workbench (I'm personally aiming for 3.5). We can change our internal code that currently reads the EP's over to getting its information from the corresponding ModelElements (this is a mechanical process of replacing IConfigurationElement.getAttribute('x') with ModelElement.getProperty('x') etc). Then we can standardize our response to changes in the model's properties, finally allowing us to get a handle on the 'dynamic package' issues that are a current pain point.More importantly for our clients we currently define programmatic API's that 'mirror' the capabilities of a given extension point.

There are two problems with this:

1) The API spec and the Extension Point definition drift apart, with some capabilities only being available through the EP and others only through API.

2) It is a -major- contributor to our API-bloat. It's interesting to note that even I (as a Platform UI committer) don't know the extent of the API we define to mirror the EPs and can't find out easily...let's just say it's significant (~80 classes/interfaces would be my guess).

Ok then, how about this? Rather than using this API we have our clients -construct- ModelElements with the same 'shape' (i.e. same properties) as their desired extension point and place them into the appropriate slot in the Extension Points model. Voila! No extra API is needed and it would be impossible for the handling of declarative and programmatic extensions to be different (something that's currently not the case). Note that extension points already have fairly robust tooling for supporting the documentation of an extension point so making the EP schema -the- place for definition would be a big win anyways.

Finally, imagine how useful this would be for an RCP client; after the model is populated they can gain access to it through the WorkbenchModelService (this is the other cornerstone of our simplification efforts, replacing 'hard-coded' API with a more general service architecture but I'll let the folks involved in that talk about it...;-). Now, at this point it's only data in the model so they can freely not only add new extensions but they can also -remove- existing extensions at an extremely fine-grained level (for example if they want a single view/command from an existing bundle they can remove the other elements from the model). It would even be possible to 'tweak' existing extensions; changing an existing view/command/perspective's label and icon  for example.

The WorkbenchPage structure:

The sub-tree of this element contain the Perspective's definition, under which is the widget-level definition  of the client area (In the demo this is parsed out of the existing 'workbench.xml' file). This structure is simply passed into the ControlFactory where the widget's themselves are rendered. Note that the structural definition for a 'stack' contains all of the definitions for its content. The current platform code uses completely different classes for visible views than it does for 'placeholders' and also uses a completely different structure (ContainerPlaceholder) for a stack with no visible views than it does for a visible one. The model implementation simply defines that there's a 'part' at a particular location and leaves the layout to do the 'right thing'(tm) for all combinations.The other elements contained in a perspective (Main Menu, Trim Areas...) are currently not rendered using the model but you can right-click on a perspective and pick 'Populate Workbench Window'. This will scrape the current GUI and create model elements for the existing menu and toolbar items. As we proceed more of the existing UI will be rendered through the model until, ultimately, -all- UI artifacts are managed this way.

Workbench State:

This sub-tree contains the modelled equivalent of the 'workbench.xml' file. It's another case of bringing an existing model-like architecture under the generic model's umbrella; in this case it's 'IMemento'. This gives an easy path for folks using the existing saveState/restoreState paradigm for moving over to using ModelElements instead...the big win is that save/restore then become the responsiility of the model engine, not the author of the view/editor(/workbench) whose state has to be maintained. This feeds into the general pattern of moving things under the model engine and then leveraging its capabilities to off-load the developer. Currently each part author rolls their own save/restore code...sometimes right, sometimes wrong (especially in terms of being adequately 'safe' in their exception handling, logging...).

Whew!! Finally, nothing in the above discussion mentions the 'web' enablement since I'd have defined the architecture the same with or without that as a consideration. That being said it's readily apparent that once the model's structure is defined it (or parts of it) can be moved across the wire. This also means that we won't be stopping once we get the Workbench/Page/Perspective/view|editor rendering in place; we also expect to use the same approach in our view implementations. Also we'd very much like to work with folks that are defining other structural hierarchies not directly UI related (i.e. the Resources 'model') to have them use the model API directly (or at least to factor their model in a manner that allows for it to be brought under the generic model using a simple wrapper (as i've already done for preferences, extension points and IMemento).

Enough!! My brain (and fingers) hurt...;-).

Comments and suggestions are welcome. Stepping up an offering to help....priceless !!

Onwards,
Eric


Back to the top