blob: 38e6f77721a45ff76a64494d5b0642a245a4561b [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:AdvancedFeatures[Eclipse 4.x & RAP]).
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 polymorphic implementations]
section:GuiceBindings[Dependency Injection With Google Guice]
All Parsley components are assembled by means of e[Dependency Injection (DI)].
This means that whenever some code is in need for functionality (or state)
from another component, one just declares the dependency rather then stating
how to resolve it, i.e. obtaining that component.
For example, when some code wants to use a label provider,
it just declares a field (or method or constructor) and adds the
codeRef[com.google.inject.Inject] annotation:
code[Java][
class MyView extends ViewPart {
@Inject
private ILabelProvider labelProvider;
}
]
It is not the duty of the client code to care about where the
actual codeRef[org.eclipse.jface.viewers.ILabelProvider] comes from or
how it is created.
When the above class is instantiated, Guice sees that it requires an instance
of ILabelProvider and assigns it to the specified field or method parameter.
This of course only works, if the object itself is created by Guice.
In Parsley almost every instance is created that way and therefore the whole
dependency net is controlled and configured by the means of Google Guice.
Guice of course needs to know how to instantiate real objects for declared dependencies.
This is done in so called Modules. A codeRef[com.google.inject.Module]
defines a set of mappings from types to either existing instances,
instance providers or concrete classes.
Modules are implemented in Java. Here's an example:
code[Java][
public class MyGuiceModule extends AbstractGenericModule {
@Override
public void configure(Binder binder) {
super.configure(binder);
binder.bind(ILabelProvider.class).to(MyLabelProvider.class);
binder.bind(...
}
]
With plain Guice modules one implements a method called configure and gets a
codeRef[com.google.inject.Binder] passed in.
That binder provides a fluent API to define the mentioned mappings.
This was just a very brief and simplified description.
We highly recommend to have a look at the link[https://github.com/google/guice][Google Guice]
website to learn more.
section2:ModuleAPI[Module API]
Parsley comes with a slightly enhanced module API
(this was inspired by Xtext, so, if you are already familiar with the
enhnaced Guice module API of Xtext, you can use Parsley API right away).
The enhancement we added to Guice's Module API is that we provide an abstract base class,
which reflectively looks for certain methods in order to find declared bindings.
The standard base class is codeRef[org.eclipse.emf.parsley.EmfParsleyGuiceModule],
which can be used in a standard Eclipse OSGI environment. If you are using
CDO, it is better to use as base class e[CDOEmfParsleyModule], which has defaults
that better fit a CDO environment. If you do not need OSGI, you can use
codeRef[org.eclipse.emf.parsley.EmfParsleyJavaGuiceModule] (e.g., to run tests
with plain Junit, see also ref:Testing[Testing Framework]).
The most common kind of method is
code[Java][
public Class<? extends ILabelProvider> bindILabelProvider() {
return MyLabelProvider.class;
}
]
which would do the same as the code snippet above.
It simply declares a binding from ILabelProvider to MyLabelProvider.
That binding will make Guice instantiate and inject a new instance of
MyLabelProviderProvider whenever a dependency to ILabelProvider is declared.
There are two additional kinds of binding-methods supported.
The first one allows to configure a provider.
A codeRef[com.google.inject.Provider] is an interface with just one method:
code[Java][
public interface Provider<T> extends javax.inject.Provider<T> {
/**
* Provides an instance of {@code T}. Must never return {@code null}.
*/
T get();
}
]
This one can be used if you need a hook whenever an instance of a certain type
is created. For instance if you want to provide lazy access to a singleton
or you need to do some computation each time an instance is created (i.e. factory).
If you want to point to a provider rather than to a concrete class you can
use the following binding method:
code[Java][
public Class<? extends Provider<ILabelProvider>> provideILabelProvider() {
return MyLabelProviderFactory.class;
}
]
The last kind of binding allows to inject values in Parsley components;
here are some examples of such bindings implemented in the base class of
Parsley Guice module:
code[Java][
/**
* The String constant for Content Assist Shortcut
*/
public String valueContentAssistShortcut() {
return "Ctrl+Space";
}
/**
* The String constant used as a ellipses for Iterable string representation
* when it is too long
*/
public String valueIterableStringEllipses() {
return "...";
}
/**
* The list of Integer weights for a table's columns
*/
public List<Integer> valueTableColumnWeights() {
return Collections.<Integer>emptyList();
}
/**
* The int constant defining the Sash style in a TreeFormComposite
*/
public int valueTreeFormSashStyle() {
return SWT.VERTICAL;
}
]
section2:BindingsInTheDSL[Specify Guice Bindings in the DSL]
Guice bindings can be specified directly in the DSL, in the
e[bindings] section.
In this section you can specify bindings of all the three above kinds with
e[type], e[provide] and e[value] respectively, e.g.,
code[EmfParsley][
bindings {
type ILabelProvider -> MyLabelProvider
type ... -> ...
provide ProposalCreator -> MyProposalCreatorProvider
...
value int TreeFormSashStyle -> SWT.HORIZONTAL
}
]
We strongly suggest you use the content assist to discover default
bindings, since they also show Javadoc for each default binding:
img[images/first-example-custom-binding1.png][][ ][]
section:Providers[Providers]
section2:ViewerLabelProvider[Viewer Label Provider]
The Jface Label Provider 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. We allow customization for text, image, font, and foreground and background color.
code[EmfParsley][
labelProvider{
text {
Book -> "Book:"+title
Borrower -> "Borrower: "+firstName
}
image {
Book -> "book.png"
}
font {
Book -> // must return a org.eclipse.swt.graphics.Font
}
foreground {
Book -> // must return a org.eclipse.swt.graphics.Color
}
background {
Book -> // must return a org.eclipse.swt.graphics.Color
}
}
]
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 by overriding e[bindILabelProvider].
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.
Our label provider also implements codeRef[org.eclipse.jface.viewers.IFontProvider] and
codeRef[org.eclipse.jface.viewers.IColorProvider], so that you can customize also the font, the foreground
and the background color.
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 Font font(Book book) {
return // must return a org.eclipse.swt.graphics.Font
}
public Color foreground(Book book) {
return // must return a org.eclipse.swt.graphics.Color
}
public Color background(Book book) {
return // must return a org.eclipse.swt.graphics.Color
}
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
(as detailed in ref:TableViewerContentProvider[Table Viewer Content Provider], for tables
we use a different content provider).
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:TableViewerContentProvider[Table Viewer Content Provider]
For table viewers we use a customized content provider, which inherits from
the one described in ref:ViewerContentProvider[Viewer Content Provider];
for tables we only need to specify how the root e[elements] are computed
(no children are needed for tables).
This content provider, codeRef[org.eclipse.emf.parsley.edit.ui.provider.TableViewerContentProvider],
must be configured with the codeRef[org.eclipse.emf.ecore.EClass] of the objects that
will be shown in the table, so the e[setEClass] must be called before this content provider is
used. This setup is already automatically performed in views that are shipped with Parsley; in
case you need to setup a table viewer yourself with this content provider, we strongly suggest
you inject a codeRef[org.eclipse.emf.parsley.edit.ui.provider.TableViewerContentProviderFactory]
and use its method e[createTableViewerContentProvider(EClass type)].
With the information about the codeRef[org.eclipse.emf.ecore.EClass] this content provider
is able to automatically retrieve all the contents of that type from a codeRef[org.eclipse.emf.ecore.resource.Resource]
or any codeRef[org.eclipse.emf.ecore.EObject], by retrieving inspecting all the containment
references of that type, recursively in the model.
In case you want to optimize the retrieval of contents, or in case you want to
show elements of the specified type which are not contained in an codeRef[org.eclipse.emf.ecore.EObject]
(because they are references with containment set to false), you can inject your own
custom codeRef[org.eclipse.emf.parsley.edit.ui.provider.TableViewerContentProvider]
and define e[elements] methods (again, this uses the polymorphic dispatch idiom).
In the DSL this can be done using the specific section.
code[EmfParsley][
tableViewerContentProvider{
elements{
Library lib -> {
// this is just an optimization: since books are contained in the library
// the default content provider will retrieve them automatically
lib.books
}
Writer w {
// writers' books would not be retrieved by the default content provider
// since they are NOT 'contained' in a writer.
w.books
}
}
}]
IMPORTANT: customizations specified in a codeRef[org.eclipse.emf.parsley.edit.ui.provider.ViewerContentProvider]
will NOT be reused by a codeRef[org.eclipse.emf.parsley.edit.ui.provider.TableViewerContentProvider].
section2:TableLabelProvider[Table Label Provider]
The Jface Table Label Provider allows to specify the representation of a given cell in a table. e[EMF Parsley]
provides an implementation that uses the information provided via the DSL, as you can see in the snippet
below. We allow customization for text, image, font, foreground and background color for a given object's feature
(which corresponds to a table cell), and also font, and foreground and background color for the entire row.
Concerning fonts and colors, a customization for a single cell has the precedence over the customization of an entire row.
Here's an example.
code[EmfParsley][
tableLabelProvider {
text {
Library:name -> 'Name' // constant
Library:books -> 'Books' // constant
Writer:lastName -> name.toFirstUpper // the implicit param is an EStructuralFeature
}
image {
Book: author ->
if (author.name.nullOrEmpty)
"noname.gif"
else
new ImageData("writer.jpeg")
}
font {
Library : name -> JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT)
}
foreground {
Library : books -> Display.getCurrent().getSystemColor(SWT.COLOR_BLUE)
}
background {
Library : address -> Display.getCurrent().getSystemColor(SWT.COLOR_GREEN)
}
rowFont {
Library -> JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT)
}
rowForeground {
Library -> Display.getCurrent().getSystemColor(SWT.COLOR_BLUE)
}
rowBackground {
Library -> Display.getCurrent().getSystemColor(SWT.COLOR_GREEN)
}
}
]
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 -> "Written 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 ref:GuiceBindings[]: this
way you can customize the caption label for controls in a form, dialog, 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 "Written by:";
}
public String text_Writer_name(final EStructuralFeature feature) {
return "Name:";
}
]
If no customization is provided, the text will be computed using the feature's name.
This will always be the default for table column headers (since no object is available
when building the table); while for form and dialog captions we use a slightly different
default strategy, as shown in ref:FormFeatureCaptionProvider[Form and Dialog Feature Caption Provider].
section3:FormFeatureCaptionProvider[Form and Dialog Feature Caption Provider]
The codeRef[org.eclipse.emf.parsley.ui.provider.FormFeatureCaptionProvider]
(codeRef[org.eclipse.emf.parsley.ui.provider.DialogFeatureCaptionProvider], respectively)
can be used if you want to define the description only for forms (for dialogs, respectively).
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
codeRef[org.eclipse.swt.widgets.Label], 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;
}
]
In the DSL you have the corresponding two sections available:
code[EmfParsley][
formFeatureCaptionProvider{
text{
Book:author -> "Written by:"
}
label{
Writer:name -> createLabel(parent, "Name")
}
}
dialogFeatureCaptionProvider{
text{
Book:author -> "Author:"
}
label{
Writer:name -> createLabel(parent, "Writer's name")
}
}
]
If there is no customization in the codeRef[org.eclipse.emf.parsley.ui.provider.FormFeatureCaptionProvider]
(codeRef[org.eclipse.emf.parsley.ui.provider.DialogFeatureCaptionProvider], respectively),
the following steps are executed to create the text for the label:
ul[
item[we take possible customizations from codeRef[org.eclipse.emf.parsley.ui.provider.FeatureCaptionProvider]
if available, otherwise:]
item[we take the text from codeRef[org.eclipse.emf.edit.provider.IItemPropertyDescriptor] if
the EObject provides it, otherwise:]
item[we take the feature name]
]
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.composite.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 codeRef[org.eclipse.jface.viewers.StructuredViewer] by using an
injected codeRef[org.eclipse.emf.parsley.menus.ViewerContextMenuHelper]. This provides some
methods for adding the context menu
code[Java][
@Inject ViewerContextMenuHelper contextMenuHelper;
(...)
// simplest form
contextMenuHelper.addViewerContextMenu(viewer);
// if you have an AdapterFactoryEditingDomain already
contextMenuHelper.addViewerContextMenu(viewer, editingDomain);
// if you're inside an IWorkbenchPart
contextMenuHelper.addViewerContextMenu(viewer, editingDomain, part);
]
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] uses the standard EMF.Edit features to build the contextual menus of
viewers (thus you will get by default the standard "New Child" and "New Sibling"
sections in the context menu).
You can customize context menus on a per class basis
by extending the codeRef[org.eclipse.emf.parsley.edit.action.EditingMenuBuilder]
(and injecting it in the Guice module). However, we suggest to use the
DSL for this task, as detailed in the following.
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 lib -> #\[
actionAdd("Add a new book", lib.books,
EXTLibraryFactory.eINSTANCE.createBook)
\]
}
}
]
For each EClass of your meta-model you can specify a list of menu items
(the #\[\] is the Xbase syntax for a list literal)
Content assist is available to select the editing actions, the separator and
also methods for EMF menu part.
In the e[emfMenus] section, you can use some methods of
the codeRef[org.eclipse.emf.parsley.edit.action.EditingMenuBuilder] class,
as detailed in the following.
The method e[actionAdd], specifying the label for the menu,
the containment list in the model, and the object to add in such list
when the menu is selected (Note that it is up to you to specify a
containment list); the DSL will issue an error if the object cannot be
added to the list (because it is not of the right type).
The object should be created using the standard EMF API (i.e., using
the EMF factory for your model).
If you want to specify further initialization instructions for the
created object you can pass a lambda expression as another argument
to e[actionAdd]: that lambda will be executed ONLY after the menu
has been selected, i.e., ONLY after the created
object is part of the resource:
code[EmfParsley][
emfMenus{
Writer w -> #\[
actionAdd("Add a new book for the writer",
(w.eContainer as Library).books,
EXTLibraryFactory.eINSTANCE.createBook,
\[ book | book.title = "A new book" \]
)
\]
}
]
IMPORTANT: do not set any reference feature of the created EObject in the lambda,
i.e., do not do something like the following
code[EmfParsley][
emfMenus{
Writer w -> #\[
actionAdd("Add a new book for the writer",
(w.eContainer as Library).books,
EXTLibraryFactory.eINSTANCE.createBook,
// WRONG: don't do that
\[ book | book.author = w \]
)
\]
}
]
This will not work if you undo the command: the writer that has been added
to the library will be removed, and the book.author will be a dangling reference!
as a consequence the resource cannot be saved.
If you want to implement more complex menu commands that do not
only add elements to a container, you can use the method
e[actionChange], specifying the label for the menu, the model's element
that will be affected by the changes specified as a lambda expression
(the third argument). The lambda expression will also get the specified
model's element as argument. The model's element can also be the whole
resource itself (formally, it can be any EMF codeRef[org.eclipse.emf.common.notify.Notifier]).
It is crucial to specify the correct model's element to make undo/redo work
correctly: all the modifications performed in the lambda expression that concern the
specified element will be recorded, in order to implement undo/redo.
For example, this command, that will add a new book to the library, and sets
its author to the selected writer will work as expected, and selecting
undo will effectively remove the book from the writer's list and from the library:
code[EmfParsley][
emfMenus{
Writer w -> #\[
actionChange("New book", w.eContainer as Library,
\[
library |
val book = factory.createBook
library.books += book
book.title = "A new book"
book.author = w
\]
)
\]
}
]
This works since we specify the containing library as the model's element, thus,
all modifications that concern the library will be recorded.
On the contrary, this variant, will perform exacty the same actions on the model, but selecting
undo will only remove the book from the writer's list, and the book will still
be present in the library:
code[EmfParsley][
emfMenus{
Writer w -> #\[
// in this variant undo will only unset the book's author,
// but it will not remove the added code from the library
// since we record changes concerning the writer only
actionChange("New book (variant)", w,
\[
writer |
val library = writer.eContainer as Library
val book = factory.createBook
library.books += book
book.title = "A new book"
book.author = w
\]
)
\]
}
]
This happens since we specified the writer as the model's element, thus only
the changes that concern the writer will be undone.
section:DND[Drag and Drop]
Drag and drop can be added to any codeRef[org.eclipse.jface.viewers.StructuredViewer] by using an
injected codeRef[org.eclipse.emf.parsley.edit.ui.dnd.ViewerDragAndDropHelper],
using its methods e[addDragAndDrop].
Currently, drag and drop is completely delegated to EMF.Edit.
section:Factories[Factories]
section2:WidgetFactory[Widget Factory]
The actual creation of text field, buttons, labels, etc. is delegated to an
implementation of codeRef[org.eclipse.emf.parsley.widgets.IWidgetFactory], which has several methods
like e[createText], e[createLabel], etc. We provide two implementations of such interface
ul[
item[codeRef[org.eclipse.emf.parsley.widgets.DialogWidgetFactory]]
item[codeRef[org.eclipse.emf.parsley.widgets.FormWidgetFactory] which is a specialization of the above,
specific for forms.]
]
Usually, you do not need to customize such factories, which are used internally by the framework,
like in ref:FormControlFactory[] and ref:DialogControFactory[].
You may want to customize such factories in case all your controls must have a specific style;
in such case, just inherit from our base classes
(there is no DSL section for such customizations, since they can be made in plain Java easily)
and bind such custom implementations in the Guice module.
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.composite.FormControlFactory].
Using the same polimorphic mechanism of the labels, the programmer can write a method
with name starting with e['control']
followed by the names of the EClass and of the EStructuralFeature undescore-character-separated.
The method must accept as parameters the e[Source Observable] and the e[feature];
the superclass' method e[bindValue] can be used for databinding.
The codeRef[org.eclipse.emf.parsley.util.DatabindingUtil] utility class can be used
for creating the target observable.
Here's an example
code[Java][
public Control control_Writer_name(IObservableValue source, EStructuralFeature f) {
// Creating the control
Text text = getToolkit().createText(getParent(), "");
text.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TREE_BORDER);
text.setBackground(getToolkit().getColors().getColor(IFormColors.TITLE));
// Perform databinding; params: feature, target, source
bindValue(f, DatabindingUtil.observeText(text), source);
return text;
}
]
Another form of the method's parameters is also taken into consideration
during the polymorphic dispatch. This is the old form and it will be likely
deprecated in the future:
the method must accept as parameters the e[DataBinding Context] and the e[Feature Observable]
that can be used for databinding. Here's an example:
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:EditingDomain[Editing Domain]
The concept of codeRef[org.eclipse.emf.edit.domain.EditingDomain] is crucial for editing
EMF models; we refer to the e[EMF.Edit] link[http://help.eclipse.org/mars/index.jsp?topic=%2Forg.eclipse.emf.doc%2Freferences%2Foverview%2FEMF.Edit.html][documentation] for further details.
In particular, the editing domain keeps track of commands executed on an EMF model,
thus enabling undo/redo mechanisms and "dirty state" management for saveable parts.
EMF Parsley aims at hiding the management of the editing domain, so that
everything should work smoothly and as expected, in an automatic way. In particular,
it is rare that you need to perform customizations on this mechanisms.
However, there might be cases when you need to be aware of this concept, especially
if you need to use one of our customizations. Moreover, you need to be aware of some
of the assumptions that EMF Parsley automatic mechanisms rely on (we inherit these assumptions
from EMF.Edit itself).
First of all, all the EMF codeRef[org.eclipse.emf.ecore.resource.Resource]s that you want to edit with EMF Parsley must be
contained in a codeRef[org.eclipse.emf.ecore.resource.ResourceSet], which, in turn,
must be contained in an codeRef[org.eclipse.emf.edit.domain.EditingDomain].
This is achieved automatically when using our e[ResourceLoader], ref:ResourceLoader[Resource Loader].
Two resources loaded with different resource loaders will be contained in two
different editing domains. Two resources loaded with the same resource loader will
be in the same resource set and use the same editing domain.
Our default implementation of editing domain uses the EMF codeRef[org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain],
so that all the EMF.Edit default mechanisms will work correctly. In particular,
our customization uses Google Guice mechanisms (see ref:GuiceBindings[Dependency Injection With Google
Guice]), thus if you need an editing domain in your own views all you have to do is
to inject it, e.g.,
code[Java][
@Inject
private EditingDomain editingDomain;
]
section2:EditingDomainProvider[Editing Domain Provider]
If you need to provide a custom implementation of the editing domain
(for example, because you want to use a transactional editing domain), you need
to implement a custom Google Guice codeRef[com.google.inject.Provider] and
in your Guice module override this method:
code[Java][
public Class<? extends Provider<EditingDomain>> provideEditingDomain() {
return DefaultEditingDomainProvider.class;
}
]
Such custom provider will then have to create an editing domain and return it.
We have some custom editing domain providers that might be useful in some
situations:
ul[
item[e[GlobalAdapterFactoryEditingDomainProvider]: all the injected
editing domains will be the same in the same JVM, thus all your
components will share exactly the same editing domain instances, and
all the resources will be contained in the same resource set of the same
editing domain.]
item[e[SingletonAdapterFactoryEditingDomainModule]: similar to the previous
one, but according to the semantics of Google Guice codeRef[com.google.inject.Singleton],
i.e., only those components injected with the same injector will share
the same editing domain. This basically means that all the components
created with the same Parsley Guice module will share the same editing domain.]
]
section2:EditingDomainFinder[Editing Domain Finder]
All the EMF Parsley saveable views and editors will have their own
editing domain (modulo what we explained in ref:EditingDomainProvider[Editing Domain Provider]).
The EMF Parsley views that react on selection do NOT have a preset
editing domain, since they will represent (and possibly edit) EMF objects
selected in some other views, i.e., such objects can be contained in resources
of different resource sets (and different editing domains).
Thus, the editing domain of the currently shown object is dynamically
retrieved through an injected e[EditingDomainFinder]. This default
implementation basically delegates to the standard EMF.Edit mechanisms
for retrieving the editing domain. In cases where the editing domain cannot
be found (e.g., because the object is not contained in a resource, or its resource
is not contained in a resource set, or its resource set is not contained in an
editing domain), then editing will not be possible (i.e., context menus ref:ContextualMenu[] and drag
and drop ref:DND[Drag and Drop] will not work).
You can provide and bind a custom implementation of the e[EditingDomainFinder]
which is particularly useful if you manage a transactional editing domain.
This is required only in some specific and advanced scenarios.
In standard situations you will not have to worry about that, and
editing mechanisms will work out of the box, including dragging an
element from one view into another view, provided they are in the
same resource set and such drag and drop makes sense.
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],
see also section ref:Validation[Validation]).
section:Configurator[Configurator]
In Parsley, instead of using abstract classes, we often provide concrete
classes that implement superclass' abstract methods (or interface methods)
by delegating to an injected codeRef[org.eclipse.emf.parsley.config.Configurator].
Such configurator calls methods in its hierarchy using polymorphic dispatch;
in particular, the first argument passed to these methods is the object
requesting that specific service to the configurator; typically it will be
a UI object, e.g., a view part.
These are the methods that can be customized declaratively:
code[Java][
/**
* Returns the {@link URI} of the resource for the requestor for any use the requestor may need it
* @param requestor
* @return
*/
public URI resourceURI(Object requestor) {
return null;
}
/**
* Returns the {@link EClass} for the requestor
* @param requestor
* @return
*/
public EClass eClass(Object requestor) {
return null;
}
]
The idea is that clients that use such an injected instance should call
the e[get] methods, e.g., e[getEClass], while the customization should be defined
using polymorphic dispatch, e.g.,
code[Java][
class MyConfigurator extends Configurator {
public EClass eClass(MyView1 view1) {
return ...;
}
public EClass eClass(MyOtherView view) {
return ...;
}
}
]
In the DSL, you can specify a e[configurator] section, e.g.,
(the requestor object can be accessed using the implicit variable
e[it]):
code[EmfParsley][
module my.project {
configurator {
resourceURI {
MyTreeFormView -> {
return ...;
}
MyTableView -> {
return ...;
}
}
eClass {
MyTableView -> {
return ...;
}
MyTableFormView -> {
return ...;
}
}
}
}
]
The project wizard will generate in the e[module.parsley] the
required e[configurator] sections, depending on the specific template chosen,
with some e[// TODO] comments to help implementing them, e.g.,
code[EmfParsley][
module my.project {
configurator {
eClass {
MyView -> {
// TODO return the EClass of objects to be shown
}
}
resourceURI {
MyView -> {
// TODO create and return a org.eclipse.emf.common.util.URI
return null;
}
}
}
}
]
section:Validation[Validation]
EMF Parsley supports standard EMF validation automatically, e.g., via the context menu
"Validate"; thus, if you already have constraints implemented for your meta-model, the
validation action will check them.
EMF validation can also be triggered manually using an injected codeRef[org.eclipse.emf.parsley.validation.ValidationRunner],
which provides methods for validating a single codeRef[org.eclipse.emf.ecore.EObject] or an entire
codeRef[org.eclipse.emf.ecore.resource.Resource]. These e[validate] methods return an EMF
codeRef[org.eclipse.emf.common.util.Diagnostic] that can be used to find out possible errors, warnings
and infos collected during the validation.
There are overloaded versions of e[validate] methods that also take an
codeRef[org.eclipse.emf.parsley.validation.IssueReporter]:
code[Java][
/**
* Validates, reports diagnostics through the passed {@link IssueReporter}
* and returns the list of reported diagnostics.
*
* @param eObject
* @param reporter
* @return
*/
public List<Diagnostic> validate(EObject eObject, IssueReporter reporter) {
return reporter.report(validate(eObject));
}
/**
* Validates, reports diagnostics through the passed {@link IssueReporter}
* and returns the list of reported diagnostics.
*
* @param resource
* @param reporter
* @return
*/
public List<Diagnostic> validate(Resource resource, IssueReporter reporter) {
return reporter.report(validate(resource));
}
]
The reporter is asked to report the collected diagnostic and it is expected to return
the list of issues effectively reported. For example, an issue reporter can
report only errors (e.g., diagnostic whose severity is e[Diagnostic.ERROR]), while
ignoring warnings and other diagnostic information.
We provide a utility class that can be injected, codeRef[org.eclipse.emf.parsley.validation.DiagnosticUtil],
with utility methods, like flattening diagnostic into a list (EMF diagnostic are typically nested in
a tree form), to quickly select only the errors, and to have a string representation.
The default implementation of codeRef[org.eclipse.emf.parsley.validation.IssueReporter]
is codeRef[org.eclipse.emf.parsley.validation.DialogErrorReporter], which uses an EMF
dialog to report ONLY errors. Another implementation that can be used for testing purposes
is codeRef[org.eclipse.emf.parsley.validation.LogIssueReporter], which logs diagnostic using
the corresponding log4j methods (i.e., e[error], e[warn], e[info]).
An example of use of the above classes can be found in codeRef[org.eclipse.emf.parsley.resource.ValidateBeforeSaveStrategy]
(see section ref:ResourceSaveStrategy[Resource Save Strategy]):
code[Java][
public class ValidateBeforeSaveStrategy extends ResourceSaveStrategy {
@Inject
private ValidationRunner validationRunner;
@Inject
private IssueReporter issueReporter;
@Override
public boolean save(Resource resource) throws IOException {
if (!precondition(resource)) {
return false;
}
return super.save(resource);
}
protected boolean precondition(Resource resource) {
return validationRunner.validate(resource, issueReporter).size() == 0;
}
}
]
Thus, if you use a codeRef[org.eclipse.emf.parsley.resource.ValidateBeforeSaveStrategy],
with the default Guice bindings, upon saving, if validation finds errors, it will
cancel the saving and it will show a dialog with errors.
Validation is also automatically triggered when editing object's properties in a form
or in a dialog. The editing field will be decorated with an error
and a tooltip with the error message. Here's an example based on the Library model.
img[images/form-validation.png][][ ][]
Please keep in mind that for forms and dialogs the error decorations are based on
specific features of the object being edited and validated. If you have a custom
EMF validator you need to make sure to specify the codeRef[org.eclipse.emf.ecore.EStructuralFeature]
when creating a diagnostic error.