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