[
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