blob: 6f98e24258cb24cedbddf705f383ccbdac328db9 [file] [log] [blame]
chapter:Customizations[Customizations]
In this chapter we will describe how e[EMF Parsley] lets you customize the standard behaviours.
A DSL is provided to easily customize most common features, but you can also customize all aspects
manually (i.e., in directly Java).
As a matter of fact each customization is explained in a single section, with the details on how to do
that with the DSL (if available) and in Java.
If you want to provide a specific implementation in
Java, you can use the Google Guice injection mechanism, by overriding the specific class with your own
implementation. Note that in some cases an explicit constructor is needed, with the e[@Inject] annotation to make Guice
correctly works; when this is the case, the base class will already have such a constructor and you will only
need to override it, but you will also need to add the e[@Inject] annotation explicitly.
Although one can specify any Guice codeRef[com.google.inject.Module], e[EMF Parsley] ships with
some default base class modules that should be used for specifying custom
Guice bindings. The default base class is
codeRef[org.eclipse.emf.parsley.EmfParsleyGuiceModule] that is suitable to be used
in an OSGI environment, like Eclipse itself or RAP (see also ref:RAP[EMF Parsley RAP support]).
Our project wizards will automatically use such module as the base class.
For CDO we have a specialized base module.
We also have a module to be used in a non OSGI environment, e.g., a pure Java environment:
codeRef[org.eclipse.emf.parsley.EmfParsleyJavaGuiceModule] (this is the base class of
codeRef[org.eclipse.emf.parsley.EmfParsleyGuiceModule]). This is useful also for
testing purposes, for writing plain Junit tests (i.e., not Plug-in Junit tests).
This is also used in our testing framework (see ref:Testing[EMF Parsley Testing Framework]).
todo[TODO: Explain the concept of Guice module and the corresponding binding methdos]
todo[TODO: Explain polymorphic implementations]
section:Providers[Providers]
section2:ViewerLabelProvider[Viewer Label Provider]
The Jface Label Prorvider allows to specify the representation of a given Object. e[EMF Parsley]
provides an implementation that uses the information provided via the DSL, as you can see in the snippet
below.
code[EmfParsley][
labelProvider{
text{
Book -> "Book:"+title
Borrower -> "Borrower: "+firstName
}
image{
Book -> "book.png"
}
}
]
However if you want to customize the label provider in Java, you need to provide an implementation of codeRef[org.eclipse.jface.viewers.ILabelProvider]
and injecting it in the spefic module e[(TODO)].
e[EMF Parsley] provides such a base implementation with the class codeRef[org.eclipse.emf.parsley.ui.provider.ViewerLabelProvider]
that is meant to be subclassed by the programmer to provide specific implementations like in the example below.
This class, like many others in our framework, relies on the e[polymorphic dispatcher] idiom to declaratively
specify text and image representations for objects. It boils down to the fact that the only thing you need to do is
to implement a method that matches a specific signature: e[text] and e[image] for the String representation and
the image, respectively. These methods will need to specify as parameter the type of the object to represent.
For the image, you can either specify an image filename or an Image object. File names for images are
assumed to refer to files in the e[icons] folder of the containing plug-in.
code[Java][
public class CustomLibraryLabelProvider extends ViewerLabelProvider {
@Inject
public CustomLibraryLabelProvider(AdapterFactoryLabelProvider delegate) {
super(delegate);
}
public String text(Book book) {
return "Book: " + book.getTitle();
}
public String image(Book book) {
return "book.png";
}
public String text(Borrower b) {
return "Borrower: " + b.getFirstName();
}
}
]
section2:ViewerContentProvider[Viewer Content Provider]
As in Jface, the Content Provider is used to get the elements to represent in a tree and their children.
e[EMF Parsley] provides an implementation that uses the DSL as in the code below.
code[EmfParsley][
viewerContentProvider{
elements{
Library -> books
}
children{
Library -> books
Book b-> {
new ArrayList()=>\[
add(b.author)
addAll(b.borrowers)
\]
}
}
}]
The developer can also provide a specific implementation of codeRef[org.eclipse.jface.viewers.IContentProvider]
by injecting it in the spefic module e[(TODO)]. EMF Parsley provides a base implementation with the class
codeRef[org.eclipse.emf.parsley.edit.ui.provider.ViewerContentProvider] that can be easily used to
specify the children of all object on the tree, like in the example below (again, this uses the polymorphic dispatch idiom).
code[Java][
public class CustomLibraryViewerContentProvider extends ViewerContentProvider {
@Inject
public CustomLibraryViewerContentProvider(AdapterFactory adapterFactory) {
super(adapterFactory);
}
public Object elements(Library library) {
return library.getBooks();
}
public Object children(Library library) {
return library.getBooks();
}
public Object children(Book book) {
ArrayList<Object> children = new ArrayList<Object>();
Writer author = book.getAuthor();
if (author != null) {
children.add(author);
}
children.addAll(book.getBorrowers());
return children;
}
}
]
section2:FeaturesProvider[Features Provider]
e[EMF Parsley] uses this kind of provider wherever a list of features is requested for a certain EClass.
The default is to return the list of all the features in the EClass, but the programmer can customize it (for instance,
by returning only a superset, or in a different order) on an EClass-based strategy.
Thus you can use the DSL to specify that list, as in the snipped below.
code[EmfParsley][
featuresProvider{
features{
Book -> title, author, category, pages
}
}
]
If you want to customize it in Java, there are more ways to customize this behaviour, but we need to go deep in some
details of the e[Feature Provider] implementation.
When the framework builds components according to the
codeRef[org.eclipse.emf.ecore.EStructuralFeature]s of a given
codeRef[org.eclipse.emf.ecore.EClass] it relies on an injected
codeRef[org.eclipse.emf.parsley.ui.provider.FeaturesProvider].
The default behavior is to simply return all the features of the a given EClass,
in the order they are defined in the EClass, as implemented by the method e[defaultFeatures] in
codeRef[org.eclipse.emf.parsley.ui.provider.FeaturesProvider].
You can set the mappings, i.e., specify the structural
features you want to be used given an EClass, by implementing
the method e[buildMap], which receives the
codeRef[org.eclipse.emf.parsley.ui.provider.FeaturesProvider$EClassToEStructuralFeatureMap]
that can be filled with the method e[mapTo];
for instance, using the EMF extended library
example, this customization will return only the e[name] and e[address] features
for e[Library], the e[firstName], e[lastName] and e[address] for
e[Person], and the e[firstName], e[lastName] and e[books] (but
not e[address]) for e[Writer] (which inherits from e[Person]).
code[Java][
import static org.eclipse.emf.examples.extlibrary.EXTLibraryPackage.Literals.*;
import org.eclipse.emf.parsley.ui.provider.EStructuralFeaturesProvider;
public class LibraryEStructuralFeaturesProvider extends
FeaturesProvider {
@Override
protected void buildMap(EClassToEStructuralFeatureMap map) {
super.buildMap(map);
map.mapTo(LIBRARY,
LIBRARY__NAME, ADDRESSABLE__ADDRESS);
map.mapTo(PERSON, PERSON__FIRST_NAME, PERSON__LAST_NAME, ADDRESSABLE__ADDRESS);
map.mapTo(WRITER, PERSON__FIRST_NAME, PERSON__LAST_NAME, WRITER__BOOKS);
}
}
]
Another possibility is to build a map which relies on Strings
both for the codeRef[org.eclipse.emf.ecore.EClass] and for
the list of codeRef[org.eclipse.emf.ecore.EStructuralFeature];
note that the name of the codeRef[org.eclipse.emf.ecore.EClass] should
be obtained by using e[getInstanceClassName()]; you can also
combine the two approaches.
section3:TableFeaturesProvider[Table Features Provider]
As an extension, you can use the codeRef[org.eclipse.emf.parsley.ui.provider.TableFeaturesProvider]:
the customizations will be applied only to ref:TableComponent[Tables], not to ref:FormComponent[Forms].
If there are no specific customization in the codeRef[org.eclipse.emf.parsley.ui.provider.TableFeaturesProvider],
we fall back to codeRef[org.eclipse.emf.parsley.ui.provider.FeaturesProvider].
section2:FeatureCaptionProvider[Feature Caption Provider]
The codeRef[org.eclipse.emf.parsley.ui.provider.FeatureCaptionProvider] provides captions for
the features in ref:TableComponent[Tables] and ref:FormComponent[Forms].
Here you can see an example of the DSL.
code[EmfParsley][
featureCaptionProvider{
text{
Book:author -> "Wrote by:"
Writer:name -> "Name:"
}
}
]
If you want to customize it in Java, you need to derive from
codeRef[org.eclipse.emf.parsley.ui.provider.FeatureCaptionProvider].
It can be customized, with injection e[TODO (see Injection paragraph)], to customize the caption label on the
left of each control in a form and the headers in a table's column.
The framework uses a polimorphic mechanism to find customizations: it searches for
methods with a specific signature: the name is built by the string e['text'] followed by the EClass and the EStructuralFeature.
All parts of the name are separated by an underscore character and the method must accept a parameter of type EStructuralFeature.
In the following example we specify the caption text for the feature 'Author' of Book and the feature 'Name' for
Writer.
code[Java][
public String text_Book_author(final EStructuralFeature feature) {
return "Wrote by:";
}
public String text_Writer_name(final EStructuralFeature feature) {
return "Name:";
}
]
section3:FormFeatureCaptionProvider[Form Feature Caption Provider]
The codeRef[org.eclipse.emf.parsley.ui.provider.FormFeatureCaptionProvider] can be used if you want
to define the description only for the form. For example using the ref:TreeFormComponent[Tree
Form] your definition will not be used in the tree.
In this case you can also define a method the returns directly the control, like in the example
below. In such methods there is another parameter that is the parent composite (that is automatically
passed by the framework).
code[Java][
public Label label_Writer_name(Composite parent, EStructuralFeature feature) {
Label label = defaultLabel(parent, feature);
label.setBackground(getFormToolkit().getColors().getColor(IFormColors.TITLE));
return label;
}
]
If there is no customization in the codeRef[org.eclipse.emf.parsley.ui.provider.FormFeatureCaptionProvider]
we fall back to codeRef[org.eclipse.emf.parsley.ui.provider.FeatureCaptionProvider].
section2:ProposalProvider[Proposal Provider]
Some controls use a list of proposals to help the end user experince: for example a single value reference feature
will be rendered by default with a combo box, automatically filled with all the possible targets for
that reference; similarly for Enum features. You can customize the proposals, and you can specify proposals also
for simple text fields (a content assist dialog will show up for text fields).
For each feature you can specify a list of proposals via the DSL. In the example below, we first
compute the default proposals for that feature and then we filter the proposals.
code[EmfParsley][
proposals{
Book:author -> {
defaultProposals(feature).
filter(Writer).
filter\[name.startsWith("F")\].toList
}
}
]
This customization can be done also in Java, by extending the class codeRef[org.eclipse.emf.parsley.binding.ProposalCreator]
and implementing the method code[Java][public List<?> proposals_Book_author(Book book) {...}]. This
method follows the same convention on the signature name as explained in ref:FeatureCaptionProvider[Feature
Provider].
section:ContextualMenu[Contextual Menu]
A context menu can be added to any viewer by using the codeRef[org.eclipse.emf.parsley.viewers.ViewerInitializer].
code[Java][
@Inject ViewerInitializer viewerInitializer;
(...)
treeActionBarContributor.initialize(editingDomain);
viewerInitializer.addContextMenu(treeFormComposite.getViewer(),
treeActionBarContributor, editingDomain, this);
treeFormComposite.getViewer().addSelectionChangedListener(treeActionBarContributor);
]
The contents of such menu are built automatically by the framework or customized by the programmer,
as shown in the next section.
section2:MenuBuilder[Menu Builder]
e[EMF Parsley] logically separates the menu into 2 parts. The first section contains all common edit commands
such as e[copy] and e[paste]. The second section contains EMF specific commands, such as for example e[new child].
You can use the DSL to fully customize the menu, as in the example below.
code[EmfParsley][
menuBuilder{
menus{
Library-> #\[
submenu("Edit",#\[
actionCopy,
actionCut,
separator,
actionPaste
\])
\]
}
emfMenus{
Library -> #\[
actionAdd("Add a new book", books,
EXTLibraryFactory.eINSTANCE.createBook => \[
title="new book"
\]
)
\]
}
}
]
You can customize menu also via Java, by extending the codeRef[org.eclipse.emf.parsley.edit.action.EditingMenuBuilder].
section:Factories[Factories]
section2:FormControlFactory[Form Control Factory]
e[EMF Parsley] lets you customize the e[form controls] via the DSL as in the following example.
code[EmfParsley][
formControlFactory {
control {
Library : name -> { }
Writer : books ->
createLabel(
books.map\[title\].join(", "))
Writer : name -> { createLabel(parent, "") }
target { observeText }
Writer : firstName ->
toolkit.createLabel(parent, "")
target observeText(SWT::Modify)
Borrower : firstName -> {
createText(firstName, SWT::MULTI, SWT::BORDER,
SWT::WRAP, SWT::V_SCROLL)
}
}
}
]
For each pair EClass, EStructuralFeature you can either simply return a Control or specify also the target
for the databinding (see some examples above).
If you want to customize the controls in Java, you can extend the class codeRef[org.eclipse.emf.parsley.binding.FormControlFactory].
Using the same polimorphic mechanism of the labels, the programmer can write a method with the keyword e['control']
followed by the EClass and EStructuralFeature undescore-character-separated. The method
must accept as parameters the e[DataBinding Context] and the e[Feature Observable] that can be used for databinding.
code[Java][
public Control control_Writer_name(DataBindingContext dbc,IObservableValue featureObservable) {
//Creating the control
Text text = getToolkit().createText(getParent(), "");
text.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TREE_BORDER);
text.setBackground(getToolkit().getColors().getColor(IFormColors.TITLE));
//Binding the control to the feature observable
dbc.bindValue(SWTObservables.observeText(text, SWT.Modify), featureObservable);
return text;
}
]
section2:DialogControFactory[Dialog Control Factory]
If you want to customize controls in Dialog, you can use the specific DSL section e[dialogControlFactory]:
code[EmfParsley][
dialogControlFactory {
control {
...
}
}
]
This customization is exactly as in the case of the form of the previous section.
section:Resources[Resources]
ul[
item[If you need a machanism to fill some data for the first time you use a model, you can provide
a specific implementation of ref:ResourceManager[Resource Manager].]
item[If you want to interact with Resource Loading, you can provide a specific ref:ResourceLoader[Resource
Loader]]
]
Concerning saving objects, there are some specific parts that can be customized:
ul[
item[ref:ResourceSaveStrategy[Resource Save Strategy], if you want to manage the save.]
]
section2:ResourceLoader[Resource Loader]
The class codeRef[org.eclipse.emf.parsley.resource.ResourceLoader] can be used to handle resource loading.
This class uses internally the ref:ResourceManager[Resource Manager].
section2:ResourceManager[Resource Manager]
Tasks concerning an EMF codeRef[org.eclipse.emf.ecore.resource.Resource] are
delegated to codeRef[org.eclipse.emf.parsley.resource.ResourceManager].
One of such tasks is initializing the resource, e.g., when, after loading, it is
found empty. You can derive from this class (and bind it in the Guice module) and provide a custom implementation
of the method e[initialize].
Saving a resource is also delegated to this class, using the method e[save],
which is expected to return a boolean value representing whether saving
has succeeded (the default implementation simply saves the resource and returns true).
In the DSL, you can specify a e[resourceManager] block, and within that block
you can specify e[initializeResource] and e[saveResource], which correspond to
e[inizialize] and e[save] methods, respectively. In both cases, inside the
block expression, the resource is available with the name e[it]; for example
code[EmfParsley][
import org.eclipse.emf.parsley.examples.library.EXTLibraryFactory
...
resourceManager {
val EXTLibraryFactory libraryFactory = EXTLibraryFactory.eINSTANCE;
initializeResource {
// it is of type org.eclipse.emf.ecore.resource.Resource
it.getContents() += libraryFactory.createLibrary
}
saveResource {
// it is of type org.eclipse.emf.ecore.resource.Resource
it.save(null)
return true
}
}
...
]
section2:ResourceSaveStrategy[Resource Save Strategy]
Resource saving is delegated to codeRef[org.eclipse.emf.parsley.resource.ResourceSaveStrategy]
which, by defaults only saves the passed codeRef[org.eclipse.emf.ecore.resource.Resource],
by delegating to codeRef[org.eclipse.emf.parsley.resource.ResourceManager]
(see ref:ResourceManager[]).
You can inject your own save strategy and customize the saving strategy, for
instance, you may want to validate the resource before saving
(a usable example of this strategy is
codeRef[org.eclipse.emf.parsley.resource.ValidateBeforeSaveStrategy]).