Step 4: Add new editors
Edit new Scenario properties
Recall that we have added new fields to the Scenario
EClass, so let’s add those fields to the Scenario editor.
Go to the '…application' bundle and find the '…pages' package. Open the 'ScenarioPage.java' file. In its 'createControlsInternal()' method, add the following block of code:
addNumericSection("Max delivery time, hrs", DatamodelPackage.Literals.SCENARIO__MAX_DELIVERY_TIME_HRS)
.addTextbox(UpdateValueStrategyFactory.doublePositive());
addDistributionSection("Interval between requests, hrs", DatamodelPackage.Literals.SCENARIO__INTERVAL_BETWEEN_REQUESTS_HRS)
.addTextbox(UpdateValueStrategyFactory.distribution())
.addDialogButton("...", DatamodelPackage.Literals.SCENARIO__INTERVAL_BETWEEN_REQUESTS_HRS)
.setEnabled(false);
Start the desktop application. In the application, create a new scenario (File → New). Click the 'Scenario' tree item in the 'Scenario structure':
Note the two new editable fields:
-
The first field editor allows only positive double values to be entered for the max delivery time.
-
If you click the '…' button next to the last field, a distribution setup window appears. You can select the distribution type and tune the distribution parameters (such as the mean value).
Add new scenario tree items
We will now add new tree items: three for assets (parent 'Assets' item plus two child items, for Warehouses and Stores) and one for trucks.
First, open the TreeElementType
enum (located in the …application.utils
package).
It already contains four element types: NETWORK, ARC, NODE, and SCENARIO.
Add another four element types:
ASSET("/icons/object.png", "Assets"),
WAREHOUSE("/icons/object.png", "Warehouses"),
STORE("/icons/object.png", "Stores"),
TRUCK("/icons/object.png", "Trucks"),
All four new scenario tree items will have the same icon, 'object.png'.
Create a new TreeElementAssets
class in the …application.parts.editor.treeelements
package:
package com.company.tutorial3.application.parts.editor.treeelements;
import java.util.List;
import com.company.tutorial3.application.utils.TreeElementType;
import com.company.tutorial3.datamodel.Scenario;
public class TreeElementAssets extends TreeElement {
private Scenario scenario;
public TreeElementAssets(Scenario scenario) {
super(TreeElementType.ASSET, scenario);
this.scenario = scenario;
}
@Override
protected List<TreeElement> createChildElements() {
return List.of(
createLeaf(TreeElementType.WAREHOUSE, () -> scenario.getWarehouses().size()),
createLeaf(TreeElementType.STORE, () -> scenario.getStores().size())
);
}
}
Update the createChildElements()
method in the TreeElementScenario
class:
@Override
protected List<TreeElement> createChildElements() {
return List.of( new TreeElementNetwork(scenario),
new TreeElementAssets(scenario),
createLeaf(TreeElementType.TRUCK, () -> scenario.getTrucks().size()) );
}
Start the application, create a new scenario and note the new items in the updated scenario tree:
Add object tables for new scenario tree items
When the user selects an item in the scenario tree, an list of objects is shown in the bottom left part. This is already implemented for nodes and arcs. Let’s add similar tables for assets and trucks.
Find the ObjectsPart
class in the …application.parts.editor
package.
In its registerPages()
method, add the following lines:
registerPages()
methodnew ObjectsPage<Warehouse>(this, DatamodelPackage.Literals.SCENARIO__WAREHOUSES, TreeElementType.WAREHOUSE,
()-> {
var wh = DatamodelFactory.eINSTANCE.createWarehouse();
wh.setScenario(scenarioObservable.getValue());
return wh;
}, null, null)
.setTableRefreshBinding(DatamodelPackage.Literals.ASSET__ID, DatamodelPackage.Literals.ASSET__NAME, DatamodelPackage.Literals.ASSET__NODE)
.setAfterCreateTableElementAction(tableView -> {
tableView.addColumn("ID", 100, Warehouse::getId);
tableView.addColumn("Name", 100, p -> p == null ? "" : p.getName());
tableView.addColumn("Node", 100, Warehouse::getNode, p -> p == null ? "" : p.getName());
});
new ObjectsPage<Store>(this, DatamodelPackage.Literals.SCENARIO__STORES, TreeElementType.STORE,
() -> {
var store = DatamodelFactory.eINSTANCE.createStore();
store.setScenario(scenarioObservable.getValue());
return store;
},
null, null)
.setTableRefreshBinding(DatamodelPackage.Literals.ASSET__ID, DatamodelPackage.Literals.ASSET__NAME, DatamodelPackage.Literals.ASSET__NODE)
.setAfterCreateTableElementAction(tableView -> {
tableView.addColumn("ID", 100, Store::getId);
tableView.addColumn("Name", 100, p -> p == null ? "" : p.getName());
tableView.addColumn("Node", 100, Store::getNode, p -> p == null ? "" : p.getName());
});
new ObjectsPage<Truck>(this, DatamodelPackage.Literals.SCENARIO__TRUCKS, TreeElementType.TRUCK,
() -> {
var truck = DatamodelFactory.eINSTANCE.createTruck();
truck.setScenario(scenarioObservable.getValue());
return truck;
},
null, null)
.setTableRefreshBinding(DatamodelPackage.Literals.TRUCK__ID, DatamodelPackage.Literals.TRUCK__NAME,
DatamodelPackage.Literals.TRUCK__SPEED, DatamodelPackage.Literals.TRUCK__INITIAL_NODE)
.setAfterCreateTableElementAction(tableView -> {
tableView.addColumn("ID", 100, Truck::getId);
tableView.addColumn("Name", 100, p -> p == null ? "" : p.getName());
tableView.addColumn("Speed", 100, Truck::getSpeed);
tableView.addColumn("Initial node", 100, Truck::getInitialNode, p -> p == null ? "" : p.getName());
});
When we add new nodes and arcs, their number is updated in the scenario tree.
To do the same object count automatic update for assets and trucks,
rewrite the TreePart.initializeDependentScenarioListFields()
method:
initializeDependentScenarioListFields()
methodprivate void initializeDependentScenarioListFields(List<EStructuralFeature> list) {
list.add(DatamodelPackage.Literals.SCENARIO__NODES);
list.add(DatamodelPackage.Literals.SCENARIO__ARCS);
list.add(DatamodelPackage.Literals.SCENARIO__WAREHOUSES);
list.add(DatamodelPackage.Literals.SCENARIO__STORES);
list.add(DatamodelPackage.Literals.SCENARIO__TRUCKS);
}
Add property pages
In general, to edit the scenario elements represented by the new scenario tree items, we need to do the following:
-
handle scenario tree item selection - to show a list of assets or trucks, in the same way as the lists of arcs and nodes are displayed;
-
add property pages for assets and trucks and show them when the respective entity is selected.
Property pages are located in the …application.pages
package.
You have seen and updated one of those pages already - the ScenarioPage
.
Add three more page classes to that package:
package com.company.tutorial3.application.pages;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.emf.databinding.EMFProperties;
import org.eclipse.emf.databinding.FeaturePath;
import com.amalgamasimulation.desktop.binding.UpdateValueStrategyFactory;
import com.company.tutorial3.common.localization.Messages;
import com.company.tutorial3.datamodel.DatamodelPackage;
import com.company.tutorial3.datamodel.Node;
import com.company.tutorial3.datamodel.Warehouse;
public class WarehousePage extends AbstractPage<Warehouse> {
@SuppressWarnings("all")
private IObservableList<Node> nodeListObservable = EMFProperties.list(DatamodelPackage.Literals.SCENARIO__NODES)
.observeDetail(scenarioObservable);
public WarehousePage(Messages messages) {
super(messages, Warehouse::getScenario);
}
@Override
public boolean isVisible(Object selectedObject) {
return selectedObject instanceof Warehouse;
}
@Override
protected String getNameClassObject() {
return "Warehouse";
}
@Override
protected String getObjectDisplayName() {
return observable.getValue().getId() + " - " + observable.getValue().getName();
}
@Override
protected final FeaturePath[] getUpdateListeners() {
return new FeaturePath[] {
FeaturePath.fromList(DatamodelPackage.Literals.ASSET__ID),
FeaturePath.fromList(DatamodelPackage.Literals.ASSET__NAME) };
}
@Override
protected void createControlsInternal() {
addStringSection("ID", DatamodelPackage.Literals.ASSET__ID)
.addTextbox(UpdateValueStrategyFactory.stringIsNotEmpty());
addStringSection("Name", DatamodelPackage.Literals.ASSET__NAME)
.addTextbox(UpdateValueStrategyFactory.stringIsNotEmpty());
addReferenceSection("Node", DatamodelPackage.Literals.ASSET__NODE)
.addAutoCompleteTextbox(DatamodelPackage.Literals.NODE__NAME, nodeListObservable)
.addSelectionDialogButton("a node", nodeListObservable, tableView -> {
tableView.addColumn("Name", 150, Node::getName);
}).addClearButton().setTextFieldCanBeEmpty(false);
}
}
package com.company.tutorial3.application.pages;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.emf.databinding.EMFProperties;
import org.eclipse.emf.databinding.FeaturePath;
import com.amalgamasimulation.desktop.binding.UpdateValueStrategyFactory;
import com.company.tutorial3.common.localization.Messages;
import com.company.tutorial3.datamodel.DatamodelPackage;
import com.company.tutorial3.datamodel.Node;
import com.company.tutorial3.datamodel.Store;
public class StorePage extends AbstractPage<Store> {
@SuppressWarnings("all")
private IObservableList<Node> nodeListObservable = EMFProperties.list(DatamodelPackage.Literals.SCENARIO__NODES)
.observeDetail(scenarioObservable);
public StorePage(Messages messages) {
super(messages, Store::getScenario);
}
@Override
public boolean isVisible(Object selectedObject) {
return selectedObject instanceof Store;
}
@Override
protected String getNameClassObject() {
return "Store";
}
@Override
protected String getObjectDisplayName() {
return observable.getValue().getId() + " - " + observable.getValue().getName();
}
@Override
protected final FeaturePath[] getUpdateListeners() {
return new FeaturePath[] {
FeaturePath.fromList(DatamodelPackage.Literals.ASSET__ID),
FeaturePath.fromList(DatamodelPackage.Literals.ASSET__NAME) };
}
@Override
protected void createControlsInternal() {
addStringSection("ID", DatamodelPackage.Literals.ASSET__ID)
.addTextbox(UpdateValueStrategyFactory.stringIsNotEmpty());
addStringSection("Name", DatamodelPackage.Literals.ASSET__NAME)
.addTextbox(UpdateValueStrategyFactory.stringIsNotEmpty());
addReferenceSection("Node", DatamodelPackage.Literals.ASSET__NODE)
.addAutoCompleteTextbox(DatamodelPackage.Literals.NODE__NAME, nodeListObservable)
.addSelectionDialogButton("a node", nodeListObservable, tableView -> {
tableView.addColumn("Name", 150, Node::getName);
}).addClearButton().setTextFieldCanBeEmpty(false);
}
}
package com.company.tutorial3.application.pages;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.emf.databinding.EMFProperties;
import org.eclipse.emf.databinding.FeaturePath;
import com.amalgamasimulation.desktop.binding.UpdateValueStrategyFactory;
import com.company.tutorial3.common.localization.Messages;
import com.company.tutorial3.datamodel.DatamodelPackage;
import com.company.tutorial3.datamodel.Node;
import com.company.tutorial3.datamodel.Truck;
public class TruckPage extends AbstractPage<Truck> {
@SuppressWarnings("all")
private IObservableList<Node> nodeListObservable = EMFProperties.list(DatamodelPackage.Literals.SCENARIO__NODES)
.observeDetail(scenarioObservable);
public TruckPage(Messages messages) {
super(messages, Truck::getScenario);
}
@Override
public boolean isVisible(Object selectedObject) {
return selectedObject instanceof Truck;
}
@Override
protected String getNameClassObject() {
return "Truck";
}
@Override
protected String getObjectDisplayName() {
return observable.getValue().getId() + " - " + observable.getValue().getName();
}
@Override
protected final FeaturePath[] getUpdateListeners() {
return new FeaturePath[] {
FeaturePath.fromList(DatamodelPackage.Literals.TRUCK__ID),
FeaturePath.fromList(DatamodelPackage.Literals.TRUCK__NAME) };
}
@Override
protected void createControlsInternal() {
addStringSection("ID", DatamodelPackage.Literals.TRUCK__ID)
.addTextbox(UpdateValueStrategyFactory.stringIsNotEmpty());
addStringSection("Name", DatamodelPackage.Literals.TRUCK__NAME)
.addTextbox(UpdateValueStrategyFactory.stringIsNotEmpty());
addNumericSection("Speed", DatamodelPackage.Literals.TRUCK__SPEED)
.addTextbox(UpdateValueStrategyFactory.doublePositive());
addReferenceSection("Initial node", DatamodelPackage.Literals.TRUCK__INITIAL_NODE)
.addAutoCompleteTextbox(DatamodelPackage.Literals.NODE__NAME, nodeListObservable)
.addSelectionDialogButton("a node", nodeListObservable, tableView -> {
tableView.addColumn("Name", 150, Node::getName);
}).addClearButton().setTextFieldCanBeEmpty(false);
}
}
Locate the PropertiesPart
class in the …application.parts.editor
package.
Update its registerPages()
method (and add appropriate import
statements):
@Override
protected void registerPages() {
registerPage(new ScenarioPage (messages), messages.tab_general);
registerPage(new NodePage(messages), messages.tab_general);
registerPage(new ArcPage(messages), messages.tab_general);
registerPage(new PointPage(messages), messages.tab_bendpoint);
registerPage(new WarehousePage(messages), messages.tab_general);
registerPage(new StorePage(messages), messages.tab_general);
registerPage(new TruckPage(messages), messages.tab_general);
}
This registers new property pages that will shown the detailed (and editable) object information in the bottom right part of the application window when an object (a truck, etc.) is selected in the bottom left part.
Check the result
Start the application and create a new scenario. Use the editor mode to populate the new scenario with some data:
-
Add several Nodes.
-
Add several Warehouses and several Stores. Give them unique IDs and make their names equal to their IDs.
-
Add several Trucks, place them to different nodes.
Each time you add a node, an asset or a truck, its quantity displayed in the scenario tree should be updated.