blob: 326ad01582304ab540e6d756cc74e9e18ce65f1b [file] [log] [blame]
chapter:FirstExample[First Example]
The purpose of this first example is to make use of the classical EMF Library Model example and
create a view for editing such models using an EMF Parsley enabled plug-in. We will use
one of the saveable views shipped with Parsley: a Tree Form View.
So let's start by creating the model plug-in with
ol[
item[File -> New... -> Example...]
item[from Category "Eclipse Modeling Framework", select "Extended Library Model Example"]
item[press Next and Finish]
]
You will end up with three plug-ins:
ul[
item[org.eclipse.emf.examples.library (the model plug-in)]
item[org.eclipse.emf.examples.library.edit (the edit plug-in)]
item[org.eclipse.emf.examples.library.editor (the editor plug-in)]
]
The editor plug-in project can be removed.
Please consider that here we are starting from this well known EMF model taken out-of-the-box from Eclipse,
but you can start from your EMF model (in that case you may probably omit the ".edit" and ".editor" plugins, depending on your model).
Now you can create your first example with the appropriate wizard.
ol[
item[select "File" -> "New" -> "Project..."]
item[from the "Emf Parsley" category select "Emf Parsley Dsl based Project"
img[images/01-new-project-dsl-wizard.png][][ ][]
]
item[click "Next"]
item[give a name to the project (e.g. "org.eclipse.emf.parsley.examples.firstexample")
and make sure the checkbox about using one of the templates is checked
img[images/01-new-project-dsl-wizard2.png][][ ][]
]
item[click "Next"]
item[Select "Saveable Tree Form View"
img[images/01-new-project-dsl-wizard3.png][][ ][]
]
item[click "Finish"]
]
The generated project has some classes and a e[module.parlsey] file, which opens automatically:
code[EmfParsley][
import org.eclipse.emf.parsley.examples.firstexample.FirstexampleSaveableTreeFormView
/* org.eclipse.emf.parsley.examples.firstexample Emf Parsley Dsl Module file */
module org.eclipse.emf.parsley.examples.firstexample {
parts {
viewpart org.eclipse.emf.parsley.examples.firstexample {
viewname "Firstexample"
viewclass FirstexampleSaveableTreeFormView
}
}
configurator {
resourceURI {
FirstexampleSaveableTreeFormView -> {
// TODO create and return a org.eclipse.emf.common.util.URI
return null;
}
}
}
resourceManager {
initializeResource {
// Optional: initialize an empty Resource
// 'it' is of type Resource
// e.g., it.getContents += myFactory.createMyClass
}
}
}
]
The e[viewpart] corresponds to the standard Eclipse view part extension point; the Parsley
DSL will generate a e[plugin.xml_emfparsley_gen]
into the root folder of your project. Just copy this file into e["plugin.xml"]. Parsley
will never override your e[plugin.xml] file; each time you modify the module file, the
e[plugin.xml_emfparsley_gen] will be generated: it is up to you to keep the generated file
synchronized with your e["plugin.xml"]. The easiest way is to select to the files,
and use the context menu "Compare With" => "Each Other".
The wizard will also generate a view part class into the project
(in this example, e[FirstexampleSaveableTreeFormView]); you can add other controls
into that view, or customize other behaviors. Note that the Parsley DSL will never
touch the files into the e[src] source folder. On the contrary, files generated into
e[emfparsley-gen] source folder must never be manually modified, since its contents will
be regenerated each time you modify the e[module.parsley] file.
For example, let's change the view name into "My Library Tree Form":
code[EmfParsley][
...
module org.eclipse.emf.parsley.examples.firstexample {
parts {
viewpart org.eclipse.emf.parsley.examples.firstexample {
viewname "My Library Tree Form"
...
]
Let's save the file, wait for Eclipse to rebuild, and update the
e[plugin.xml] with the new e[plugin.xml_emfparsley_gen].
(Other e[viewpart] sections can be created; content assist is available for that).
In the generated e[module.parsley], there is a e[configurator] section, with
a e[TODO] comment (The e[Configurator] is detailed in the section ref:Configurator[Configurator]):
code[EmfParsley][
configurator {
resourceURI {
FirstexampleSaveableTreeFormView -> {
// TODO create and return a org.eclipse.emf.common.util.URI
return null;
}
}
}
]
Let's focus on the above e[resourceURI]: our goal is allowing to manage
a library model instance which persists on a EMF codeRef[org.eclipse.emf.ecore.resource.Resource].
So we must specify the codeRef[org.eclipse.emf.common.util.URI] of the resource
that will be edited by our tree form view.
In this example we choose to use the EMF default persistence (XMI), but you can provide any URI
(e.g. using Teneo, CDO or any other EMF Resource Persistence implementation)
In particular here we choose to persist the Resource in a XMI file named e["MyLibrary.library"]
into the user home folder (you might want to change it with any other path).
To achieve this, we just need to create such a URI (recall that content assist is available
when typing Xbase expressions):
code[EmfParsley][
configurator {
resourceURI {
FirstexampleSaveableTreeFormView -> {
return URI.createFileURI( System.getProperty("user.home") + "/MyLibrary.library" );
}
}
}
]
If you simply copy and paste the above return statement, you'll get an error about
unresolvable Java type URI; "Organize Imports" context menu (or its shortcut "Ctrl+Shift+O")
can be used to automatically add the missing import (make sure you select
codeRef[org.eclipse.emf.common.util.URI]).
Note that the specified URI, will be used for loading the resource only for our specific
view (the resource will be automatically created if it does not exist).
In the e[module.parsley] there is another section that has been generated by the wizard:
code[EmfParsley][
resourceManager {
initializeResource {
// Optional: initialize an empty Resource
// 'it' is of type Resource
// e.g., it.getContents += myFactory.createMyClass
}
}
]
We can use this section to initialize our resource when it is empty
(i.e., the first time the resource is created); (The e[resourceManager]
is detailed in section ref:ResourceManager[Resource Manager]).
In this example, we want to initialize an empty resource with a
e[Library] object; so
we first need a e[Dependency] from the model plug-in: so open e[MANIFEST.MF] file, go to e[Dependencies]
tab, press e["Add..."] button in e["Required Plug-ins"] section and insert e["org.eclipse.emf.examples.library"]
among dependencies.
Now we can implement the e[initializeResource] method
(as described in the comment, the codeRef[org.eclipse.emf.ecore.resource.Resource]
to initialized is available through the parameter e[it]); the Library object
is created using the standard EMF API: we need the factory of the library model:
code[EmfParsley][
resourceManager {
initializeResource {
it.getContents += EXTLibraryFactory.eINSTANCE.createLibrary
}
}
]
Again, use the content assist while typing, e.g., for automatically importing the
type e[EXTLibraryFactory], or use the "Organize Imports" functionality.
Now, we are ready to execute this example:
let's get back to the e[MANIFEST.MF] and run the example
img[images/first-example-launch.png][][ ][]
As an Eclipse RCP developer you know, of course, that this will start another Eclipse instance (unless
you add an Application plug-in to the launch or define an Application in the current plug-in).
In this second Eclipse instance you can show the View in this way:
ol[
item[e[Window -> Show View -> Other...]]
item[from Category "Other", select "My Library Tree Form"]
item[press e[OK]]
]
img[images/first-example-run.png][][ ][]
With this simple view you can start editing the model instance. For example you can set the e["name"]
field; as soon as you start typing characters into this field you will notice that:
ol[
item[the View turns to a e["dirty"] state (an asterisk symbol appears on the view tab)]
item[the e["Save"] toolbar button is enabled]
item[the typed characters are reflected into the label correspondent to the Library icon]
]
if you now perform a e["Save"] action the persistence mechanism will trigger and you will see that file
code[<user.home>/MyLibrary.library]
is being created on the file system. From now on, this file will keep the state of the model object whenever
you change and save it.
To create a Writer into the Library just right-click on the Library object and select e[New Child -> Writer]
img[images/createWriter.png][][ ][]
Please note that you might see a slightly different content in the above context-menu in case you deleted
the .edit plugin when creating the model (e.g. e["Writers Writer"] instead of e["Writer"], e["Stock Book"] instead of e["Book"] and
similar (this is because with EMF it is possible to customize labels also via .edit plugin).
Now set for instance the writer e["name"] field and save.
Now just play around creating Books, associating them to Writers and so on.
As you can see you can entirely manage the EMF model instance: creating, modifying and deleting elements.
Whenever the current selection on the upper side of the view changes, then the lower side shows the detail
of this selection.
img[images/first-example-default.png][][ ][]
However, up to this point, you have no control over the field to be shown and its order; for example
you may want just the e["name"] attribute for the Library and e["name", "address" and "books"] attributes
for Writers and maybe e["title", "authors" and "category"] for Books.
Well, it's indeed very easy to obtain this: just edit the e[module.parsley] file,
adding the following import (without ending line with ";")
code[EmfParsley][
import org.eclipse.emf.examples.extlibrary.*
]
and then defining the features to show (the e[featuresProvider] is detailed
in section ref:FeaturesProvider[Features Provider]):
code[EmfParsley][
module ... {
...
featuresProvider {
features {
Library -> name
Writer -> name, address, books
Book -> author, title, category
}
}
}
]
Remeber that code completion is available, just exploit it since it helps a lot.
If you restart now the application you will see that, when selecting an object, only the features
specified in the above section will be shown for each specified classes.
Furthermore, they are shown in the specified order.
NOTE: Did you run the application in Debug mode? Well, then you can change fields and order, save and see the
changes without even restarting the application.
Do you want to change text used for attribute captions in the form for a specific
class? Just add the following
(e[featureCaptionProvider] is detailed in section ref:FeatureCaptionProvider[Feature Caption Provider]):
code[EmfParsley][
...
featureCaptionProvider {
text {
Book : author -> "Written by:"
Writer : name -> "Name:"
}
}
]
Or do you want to change the label shown on the tree nodes on the upper side and as detail title?
Maybe want to format the book label like this?
(e[labelProvider] is detailed in section ref:ViewerLabelProvider[Viewer Label Provider]):
code[EmfParsley][
...
labelProvider {
text {
Book b -> { '"' + b.title + '"' }
Writer w -> { w.name }
}
}
]
The result of all the above customizations is shown in the following screenshot
(compare it with the previous screenshot):
img[images/first-example-customized.png][][ ][]
Now, let's customize the context menus; by default,
Parsley will generate context menus using EMF.Edit:
img[images/first-example-default-menus.png][][ ][]
We will now customize the context menu for books and writers, using
the e[menuBuilder] in the DSL (context menu customization is detailed
in section ref:ContextualMenu[Contextual Menu]).
What we want to achieve is to have a context menu for a e[Writer]
to add a new book in the library, and set its author to the
selected writer (similarly, we want a context menu for a e[Book]
to add a new writer in the library, and set the selected book as
one of the new writer's books):
code[EmfParsley][
...
menuBuilder {
val factory = EXTLibraryFactory.eINSTANCE
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
\]
),
\]
Book b -> #\[
actionChange("New writer", b.eContainer as Library,
\[
library |
val writer = factory.createWriter
library.writers += writer
writer.name = "A new writer"
writer.books += b
\]
)
\]
}
}
]
In this code we use Xbase features like list literals (e[#\[...\]]) and
lambda expressions.
We use e[actionChange] that allows to specify a menu performing some actions
on the model's elements, keeping track of such changes so that they can be
undone -- redo will work as well. The implementation of the menu is specified
in the lambda expression passed as the last argument of actionChange; this lambda
will receive as argument the model's element specified as the second argument.
Only those modifications performed in the lambda concerning such specified model's element
will be recorded for undo/redo.
If you now restart the application, you see that the new
context menu appears on writer elements:
img[images/first-example-custom-menus1.png][][ ][]
And selecting such a menu on a writer will add a new book, with
a title, and whose author is the selected writer:
img[images/first-example-custom-menus2.png][][ ][]
You may want to try the new context menu on a book as well.
We also add another context menu for books, using e[actionAdd]:
specifying the label for the menu,
the containment list in the model, the object to add in such list
and a lambda expression that will be executed ONLY after the menu
has been selected. In this example, this menu available for
a book object will add a new book to the library with the same
name of the selected book:
code[EmfParsley][
...
menuBuilder {
val factory = EXTLibraryFactory.eINSTANCE
emfMenus {
// ... as above
Book b -> #\[
actionChange(
// ... as above
),
actionAdd("New book (same title)",
(b.eContainer as Library).books,
factory.createBook,
\[title = b.title\]
)
\]
}
}
]
Now, let's customize the contents shown in the tree view: by default,
as you can see from the previous screenshots, the tree will show all
the contents of the library. If we want to show only the writers and
the books we can specify this section in the DSL
(the customization of the content provider is detailed in
section ref:ViewerContentProvider[Viewer Content Provider]):
code[EmfParsley][
...
viewerContentProvider {
children {
Library -> {
writers + books
}
}
}
]
and the result can be seen in the following screenshot:
img[images/first-example-custom-contents1.png][][ ][]
By default, double-clicking on a tree viewer of a saveable view will
show a dialog to edit that object (if you customized the
e[featuresProvider], the dialog will use your customized version);
by default, if you edit a field in such dialog, the modifications will
be applied immediately to the resource: this can be seen in the
labels of the tree which are automatically updated and in the dirty state of
the view:
img[images/first-example-default-dialog.png][][ ][]
Such a strategy for editing is delegated to an injected
codeRef[org.eclipse.emf.parsley.edit.IEditingStrategy], which is
implemented by default by codeRef[org.eclipse.emf.parsley.edit.OnTheFlyEditingStrategy].
One may want to avoid this automatic update of the resource, and
have the changes applied only when the "OK" dialog button is pressed
(if "Cancel" is pressed, no changes should be applied at all).
To achieve this behavior, it is enough to bind the alternative implementation
codeRef[org.eclipse.emf.parsley.edit.UndoableEditingStrategy], in the Guice module.
This can be achieved in the DSL using the e[binding] section
(Guice bindings are detailed in section ref:GuiceBindings[Guice Bindings]):
code[EmfParsley][
...
bindings {
type IEditingStrategy -> UndoableEditingStrategy
}
]
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][][ ][]
Besides types, you can also bind (i.e., inject) specific values
that are used in the framework; for example, you can change the
orientation of the tree form sash as follows
code[EmfParsley][
...
bindings {
type IEditingStrategy -> UndoableEditingStrategy
value int TreeFormSashStyle -> SWT.HORIZONTAL
}
]
and see the result:
img[images/first-example-custom-orientation.png][][ ][]
This ends the first tutorial.