| 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. |