Initial commit
Some checks failed
Gradle Build / build (push) Has been cancelled

This commit is contained in:
CaiXiang
2024-11-30 18:36:13 +08:00
commit aa56926258
2134 changed files with 232943 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import org.opentcs.customizations.kernel.KernelInjectionModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Configures/binds the loopback communication adapters of the openTCS kernel.
*/
public class LoopbackCommAdapterModule
extends
KernelInjectionModule {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(LoopbackCommAdapterModule.class);
/**
* Creates a new instance.
*/
public LoopbackCommAdapterModule() {
}
@Override
protected void configure() {
VirtualVehicleConfiguration configuration
= getConfigBindingProvider().get(
VirtualVehicleConfiguration.PREFIX,
VirtualVehicleConfiguration.class
);
if (!configuration.enable()) {
LOG.info("Loopback driver disabled by configuration.");
return;
}
bind(VirtualVehicleConfiguration.class)
.toInstance(configuration);
install(new FactoryModuleBuilder().build(LoopbackAdapterComponentsFactory.class));
// tag::documentation_createCommAdapterModule[]
vehicleCommAdaptersBinder().addBinding().to(LoopbackCommunicationAdapterFactory.class);
// end::documentation_createCommAdapterModule[]
}
}

View File

@@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: The openTCS Authors
# SPDX-License-Identifier: MIT
org.opentcs.virtualvehicle.LoopbackCommAdapterModule

View File

@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle;
import org.opentcs.components.kernel.services.VehicleService;
/**
* A factory for creating various comm adapter panel specific instances.
*/
public interface AdapterPanelComponentsFactory {
/**
* Creates a {@link LoopbackCommAdapterPanel} representing the given process model's content.
*
* @param processModel The process model to represent.
* @param vehicleService The vehicle service used for interaction with the comm adapter.
* @return The comm adapter panel.
*/
LoopbackCommAdapterPanel createPanel(
LoopbackVehicleModelTO processModel,
VehicleService vehicleService
);
}

View File

@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle;
/**
* Defines constants regarding internationalization.
*/
public interface I18nLoopbackCommAdapter {
/**
* The path to the project's resource bundle.
*/
String BUNDLE_PATH = "i18n/org/opentcs/commadapter/loopback/Bundle";
}

View File

@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle;
import org.opentcs.data.model.Vehicle;
/**
* A factory for various loopback specific instances.
*/
public interface LoopbackAdapterComponentsFactory {
/**
* Creates a new LoopbackCommunicationAdapter for the given vehicle.
*
* @param vehicle The vehicle.
* @return A new LoopbackCommunicationAdapter for the given vehicle.
*/
LoopbackCommunicationAdapter createLoopbackCommAdapter(Vehicle vehicle);
}

View File

@@ -0,0 +1,961 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.6" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Component class="javax.swing.ButtonGroup" name="modeButtonGroup">
</Component>
<Component class="javax.swing.ButtonGroup" name="propertyEditorGroup">
</Component>
</NonVisualComponents>
<Properties>
<Property name="name" type="java.lang.String" value="LoopbackCommunicationAdapterPanel" noResource="true"/>
</Properties>
<AccessibilityProperties>
<Property name="AccessibleContext.accessibleName" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.accessibleName" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</AccessibilityProperties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="2"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,2,118,0,0,3,12"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="vehicleBahaviourPanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Center"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="PropsPowerOuterContainerPanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="North"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="PropsPowerInnerContainerPanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="West"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout">
<Property name="axis" type="int" value="0"/>
</Layout>
<SubComponents>
<Container class="javax.swing.JPanel" name="vehiclePropsPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="Vehicle properties">
<ResourceString PropertyName="titleX" bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.panel_vehicleProperties.border.title" replaceFormat="BUNDLE.getString(&quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="maxFwdVeloLbl">
<Properties>
<Property name="horizontalAlignment" type="int" value="11"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.label_maximumForwardVelocity.text" replaceFormat="BUNDLE.getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="13" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="maxFwdVeloTxt">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="columns" type="int" value="5"/>
<Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" value="0"/>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="maxFwdVeloUnitLbl">
<Properties>
<Property name="text" type="java.lang.String" value="mm/s"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="17" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="maxRevVeloLbl">
<Properties>
<Property name="horizontalAlignment" type="int" value="11"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.label_maximumReverseVelocity.text" replaceFormat="BUNDLE.getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="13" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="maxRevVeloTxt">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="columns" type="int" value="5"/>
<Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" value="0"/>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="maxRevVeloUnitLbl">
<Properties>
<Property name="text" type="java.lang.String" value="mm/s"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="2" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="17" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="maxAccelLbl">
<Properties>
<Property name="horizontalAlignment" type="int" value="11"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.label_maximumAcceleration.text" replaceFormat="BUNDLE.getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="13" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="maxAccelTxt">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="columns" type="int" value="5"/>
<Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" value="1000"/>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="2" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="maxAccelUnitLbl">
<Properties>
<Property name="text" type="java.lang.String" value="&lt;html&gt;mm/s&lt;sup&gt;2&lt;/sup&gt;"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="2" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="17" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="maxDecelTxt">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="columns" type="int" value="5"/>
<Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" value="1000"/>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="3" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="maxDecelLbl">
<Properties>
<Property name="horizontalAlignment" type="int" value="11"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.label_maximumDeceleration.text" replaceFormat="BUNDLE.getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="13" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="maxDecelUnitLbl">
<Properties>
<Property name="text" type="java.lang.String" value="&lt;html&gt;mm/s&lt;sup&gt;2&lt;/sup&gt;"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="2" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="17" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="defaultOpTimeLbl">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.label_operatingTime.text" replaceFormat="BUNDLE.getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="4" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="13" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="defaultOpTimeUntiLbl">
<Properties>
<Property name="text" type="java.lang.String" value="ms"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="2" gridY="4" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="17" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="opTimeTxt">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="columns" type="int" value="5"/>
<Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" value="1000"/>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="4" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="profilesContainerPanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="South"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Component class="javax.swing.Box$Filler" name="filler1">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 0]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.HorizontalGlue"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Center"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="vehicleStatePanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="West"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="stateContainerPanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="North"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout">
<Property name="axis" type="int" value="1"/>
</Layout>
<SubComponents>
<Container class="javax.swing.JPanel" name="connectionPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="Adapter status">
<ResourceString PropertyName="titleX" bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.panel_adapterStatus.border.title" replaceFormat="BUNDLE.getString(&quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
<Property name="name" type="java.lang.String" value="connectionPanel" noResource="true"/>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JCheckBox" name="chkBoxEnable">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.checkBox_enableAdapter.text" replaceFormat="BUNDLE.getString(&quot;{key}&quot;)"/>
</Property>
<Property name="name" type="java.lang.String" value="chkBoxEnable" noResource="true"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="chkBoxEnableActionPerformed"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="curPosPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="Current position/state">
<ResourceString PropertyName="titleX" bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.panel_vehicleStatus.border.title" replaceFormat="BUNDLE.getString(&quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
<Property name="name" type="java.lang.String" value="curPosPanel" noResource="true"/>
</Properties>
<AccessibilityProperties>
<Property name="AccessibleContext.accessibleName" type="java.lang.String" value="Change"/>
</AccessibilityProperties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JTextField" name="energyLevelTxt">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="ff" green="ff" red="ff" type="rgb"/>
</Property>
<Property name="text" type="java.lang.String" value="100"/>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
<Property name="name" type="java.lang.String" value="energyLevelTxt" noResource="true"/>
</Properties>
<Events>
<EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="energyLevelTxtMouseClicked"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="2" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="energyLevelLbl">
<Properties>
<Property name="text" type="java.lang.String" value="%"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="2" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="3" insetsBottom="0" insetsRight="0" anchor="17" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JCheckBox" name="pauseVehicleCheckBox">
<Properties>
<Property name="enabled" type="boolean" value="false"/>
<Property name="horizontalAlignment" type="int" value="2"/>
<Property name="horizontalTextPosition" type="int" value="10"/>
<Property name="name" type="java.lang.String" value="pauseVehicleCheckBox" noResource="true"/>
</Properties>
<Events>
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="pauseVehicleCheckBoxItemStateChanged"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="5" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="orientationAngleLbl">
<Properties>
<Property name="text" type="java.lang.String" value="&lt;html&gt;&amp;#186;"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="2" gridY="4" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="3" insetsBottom="0" insetsRight="0" anchor="17" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="precisePosUnitLabel">
<Properties>
<Property name="text" type="java.lang.String" value="mm"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="2" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="3" insetsBottom="0" insetsRight="0" anchor="17" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="orientationAngleTxt">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="ff" green="ff" red="ff" type="rgb"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.textField_orientationAngle.angleNotSetPlaceholder" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
<Property name="name" type="java.lang.String" value="orientationAngleTxt" noResource="true"/>
</Properties>
<Events>
<EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="orientationAngleTxtMouseClicked"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="4" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="energyLevelLabel">
<Properties>
<Property name="horizontalAlignment" type="int" value="11"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.label_energyLevel.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="0" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="orientationLabel">
<Properties>
<Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.label_orientationAngle.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="4" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="0" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="positionTxt">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="ff" green="ff" red="ff" type="rgb"/>
</Property>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
<Property name="name" type="java.lang.String" value="positionTxt" noResource="true"/>
</Properties>
<Events>
<EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="positionTxtMouseClicked"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="positionLabel">
<Properties>
<Property name="horizontalAlignment" type="int" value="11"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.label_position.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="0" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="pauseVehicleLabel">
<Properties>
<Property name="horizontalAlignment" type="int" value="11"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.label_pauseVehicle.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="5" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="0" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="jLabel2">
<Properties>
<Property name="horizontalAlignment" type="int" value="11"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.label_state.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="0" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="stateTxt">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="ff" green="ff" red="ff" type="rgb"/>
</Property>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
<Property name="name" type="java.lang.String" value="stateTxt" noResource="true"/>
</Properties>
<Events>
<EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="stateTxtMouseClicked"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="jLabel3">
<Properties>
<Property name="horizontalAlignment" type="int" value="11"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.label_precisePosition.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="0" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextArea" name="precisePosTextArea">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection component="positionTxt" name="getFont" type="method"/>
</Property>
<Property name="rows" type="int" value="3"/>
<Property name="text" type="java.lang.String" value="X:&#xa;Y:&#xa;Z:"/>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
<Property name="name" type="java.lang.String" value="precisePosTextArea" noResource="true"/>
</Properties>
<Events>
<EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="precisePosTextAreaMouseClicked"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="3" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="propertySetterPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="Vehicle property">
<ResourceString PropertyName="titleX" bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.panel_vehicleProperty.border.title" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="keyLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.label_propertyKey.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="3" anchor="13" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="valueTextField">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[4, 18]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[4, 18]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 20]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JButton" name="propSetButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.button_setProperty.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="propSetButtonActionPerformed"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="4" gridWidth="2" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JRadioButton" name="removePropRadioBtn">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="propertyEditorGroup"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.radioButton_removeProperty.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="removePropRadioBtnActionPerformed"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="13" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JRadioButton" name="setPropValueRadioBtn">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="propertyEditorGroup"/>
</Property>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.radioButton_setProperty.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="setPropValueRadioBtnActionPerformed"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="13" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Container class="javax.swing.JPanel" name="jPanel3">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JTextField" name="keyTextField">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 20]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="eventPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="Event dispatching">
<ResourceString PropertyName="titleX" bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.panel_eventDispatching.title" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JCheckBox" name="includeAppendixCheckBox">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.checkBox_includeAppendix.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="includeAppendixCheckBoxItemStateChanged"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="appendixTxt">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="columns" type="int" value="10"/>
<Property name="text" type="java.lang.String" value="XYZ"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="3" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JButton" name="dispatchEventButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.button_dispatchEvent.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="dispatchEventButtonActionPerformed"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="2" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JButton" name="dispatchCommandFailedButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.button_failCurrentCommand.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="dispatchCommandFailedButtonActionPerformed"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="2" gridWidth="2" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="controlTabPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="Command processing">
<ResourceString PropertyName="titleX" bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.panel_commandProcessing.border.title" replaceFormat="BUNDLE.getString(&quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JRadioButton" name="singleModeRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="modeButtonGroup"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.checkBox_commandProcessingManual.text" replaceFormat="BUNDLE.getString(&quot;{key}&quot;)"/>
</Property>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
<EmptyBorder bottom="0" left="0" right="0" top="0"/>
</Border>
</Property>
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
<Insets value="[0, 0, 0, 0]"/>
</Property>
<Property name="name" type="java.lang.String" value="singleModeRadioButton" noResource="true"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="singleModeRadioButtonActionPerformed"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="3" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JRadioButton" name="flowModeRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="modeButtonGroup"/>
</Property>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.checkBox_commandProcessingAutomatic.text" replaceFormat="BUNDLE.getString(&quot;{key}&quot;)"/>
</Property>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
<EmptyBorder bottom="0" left="0" right="0" top="0"/>
</Border>
</Property>
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
<Insets value="[0, 0, 0, 0]"/>
</Property>
<Property name="name" type="java.lang.String" value="flowModeRadioButton" noResource="true"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="flowModeRadioButtonActionPerformed"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="3" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JButton" name="triggerButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.button_nextStep.text" replaceFormat="BUNDLE.getString(&quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
<Property name="name" type="java.lang.String" value="triggerButton" noResource="true"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="triggerButtonActionPerformed"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="3" insetsBottom="0" insetsRight="3" anchor="13" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="loadDevicePanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="Load handling device">
<ResourceString PropertyName="titleX" bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="loopbackCommAdapterPanel.panel_loadHandlingDevice.border.title" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Center"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="jPanel1">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="South"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
</Container>
<Container class="javax.swing.JPanel" name="jPanel2">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="West"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
<SubComponents>
<Component class="javax.swing.JCheckBox" name="lHDCheckbox">
<Properties>
<Property name="text" type="java.lang.String" value="Device loaded"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="lHDCheckboxClicked"/>
</Events>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@@ -0,0 +1,126 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle;
import static java.util.Objects.requireNonNull;
import jakarta.annotation.Nonnull;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import org.opentcs.access.KernelServicePortal;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Vehicle;
import org.opentcs.drivers.vehicle.VehicleCommAdapterDescription;
import org.opentcs.drivers.vehicle.management.VehicleCommAdapterPanel;
import org.opentcs.drivers.vehicle.management.VehicleCommAdapterPanelFactory;
import org.opentcs.drivers.vehicle.management.VehicleProcessModelTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A factory for creating {@link LoopbackCommAdapterPanel} instances.
*/
public class LoopbackCommAdapterPanelFactory
implements
VehicleCommAdapterPanelFactory {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(LoopbackCommAdapterPanelFactory.class);
/**
* The service portal.
*/
private final KernelServicePortal servicePortal;
/**
* The components factory.
*/
private final AdapterPanelComponentsFactory componentsFactory;
/**
* Whether this factory is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param servicePortal The service portal.
* @param componentsFactory The components factory.
*/
@Inject
public LoopbackCommAdapterPanelFactory(
KernelServicePortal servicePortal,
AdapterPanelComponentsFactory componentsFactory
) {
this.servicePortal = requireNonNull(servicePortal, "servicePortal");
this.componentsFactory = requireNonNull(componentsFactory, "componentsFactory");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
initialized = false;
}
@Override
public List<VehicleCommAdapterPanel> getPanelsFor(
@Nonnull
VehicleCommAdapterDescription description,
@Nonnull
TCSObjectReference<Vehicle> vehicle,
@Nonnull
VehicleProcessModelTO processModel
) {
requireNonNull(description, "description");
requireNonNull(vehicle, "vehicle");
requireNonNull(processModel, "processModel");
if (!providesPanelsFor(description, processModel)) {
return new ArrayList<>();
}
List<VehicleCommAdapterPanel> panels = new ArrayList<>();
panels.add(
componentsFactory.createPanel(
((LoopbackVehicleModelTO) processModel),
servicePortal.getVehicleService()
)
);
return panels;
}
/**
* Checks whether this factory can provide comm adapter panels for the given description and the
* given type of process model.
*
* @param description The description to check for.
* @param processModel The process model.
* @return {@code true} if, and only if, this factory can provide comm adapter panels for the
* given description and the given type of process model.
*/
private boolean providesPanelsFor(
VehicleCommAdapterDescription description,
VehicleProcessModelTO processModel
) {
return (description instanceof LoopbackCommunicationAdapterDescription)
&& (processModel instanceof LoopbackVehicleModelTO);
}
}

View File

@@ -0,0 +1,559 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle;
import static java.util.Objects.requireNonNull;
import com.google.inject.assistedinject.Assisted;
import jakarta.inject.Inject;
import java.beans.PropertyChangeEvent;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.opentcs.common.LoopbackAdapterConstants;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.Route.Step;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.drivers.vehicle.BasicVehicleCommAdapter;
import org.opentcs.drivers.vehicle.LoadHandlingDevice;
import org.opentcs.drivers.vehicle.MovementCommand;
import org.opentcs.drivers.vehicle.SimVehicleCommAdapter;
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
import org.opentcs.drivers.vehicle.VehicleProcessModel;
import org.opentcs.drivers.vehicle.management.VehicleProcessModelTO;
import org.opentcs.util.ExplainedBoolean;
import org.opentcs.virtualvehicle.VelocityController.WayEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link VehicleCommAdapter} that does not really communicate with a physical vehicle but roughly
* simulates one.
*/
public class LoopbackCommunicationAdapter
extends
BasicVehicleCommAdapter
implements
SimVehicleCommAdapter {
/**
* The name of the load handling device set by this adapter.
*/
public static final String LHD_NAME = "default";
/**
* This class's Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(LoopbackCommunicationAdapter.class);
/**
* An error code indicating that there's a conflict between a load operation and the vehicle's
* current load state.
*/
private static final String LOAD_OPERATION_CONFLICT = "cannotLoadWhenLoaded";
/**
* An error code indicating that there's a conflict between an unload operation and the vehicle's
* current load state.
*/
private static final String UNLOAD_OPERATION_CONFLICT = "cannotUnloadWhenNotLoaded";
/**
* The time (in ms) of a single simulation step.
*/
private static final int SIMULATION_PERIOD = 100;
/**
* This instance's configuration.
*/
private final VirtualVehicleConfiguration configuration;
/**
* Indicates whether the vehicle simulation is running or not.
*/
private volatile boolean isSimulationRunning;
/**
* The vehicle to this comm adapter instance.
*/
private final Vehicle vehicle;
/**
* The vehicle's load state.
*/
private LoadState loadState = LoadState.EMPTY;
/**
* Whether the loopback adapter is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param configuration This class's configuration.
* @param vehicle The vehicle this adapter is associated with.
* @param kernelExecutor The kernel's executor.
*/
@Inject
public LoopbackCommunicationAdapter(
VirtualVehicleConfiguration configuration,
@Assisted
Vehicle vehicle,
@KernelExecutor
ScheduledExecutorService kernelExecutor
) {
super(
new LoopbackVehicleModel(vehicle),
configuration.commandQueueCapacity(),
configuration.rechargeOperation(),
kernelExecutor
);
this.vehicle = requireNonNull(vehicle, "vehicle");
this.configuration = requireNonNull(configuration, "configuration");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
super.initialize();
String initialPos
= vehicle.getProperties().get(LoopbackAdapterConstants.PROPKEY_INITIAL_POSITION);
if (initialPos != null) {
initVehiclePosition(initialPos);
}
getProcessModel().setState(Vehicle.State.IDLE);
getProcessModel().setLoadHandlingDevices(
Arrays.asList(new LoadHandlingDevice(LHD_NAME, false))
);
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
super.terminate();
initialized = false;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
super.propertyChange(evt);
if (!((evt.getSource()) instanceof LoopbackVehicleModel)) {
return;
}
if (Objects.equals(
evt.getPropertyName(),
VehicleProcessModel.Attribute.LOAD_HANDLING_DEVICES.name()
)) {
if (!getProcessModel().getLoadHandlingDevices().isEmpty()
&& getProcessModel().getLoadHandlingDevices().get(0).isFull()) {
loadState = LoadState.FULL;
getProcessModel().setBoundingBox(
getProcessModel().getBoundingBox().withLength(configuration.vehicleLengthLoaded())
);
}
else {
loadState = LoadState.EMPTY;
getProcessModel().setBoundingBox(
getProcessModel().getBoundingBox().withLength(configuration.vehicleLengthUnloaded())
);
}
}
if (Objects.equals(
evt.getPropertyName(),
LoopbackVehicleModel.Attribute.SINGLE_STEP_MODE.name()
)) {
// When switching from single step mode to automatic mode and there are commands to be
// processed, ensure that we start/continue processing them.
if (!getProcessModel().isSingleStepModeEnabled()
&& !getSentCommands().isEmpty()
&& !isSimulationRunning) {
isSimulationRunning = true;
((ExecutorService) getExecutor()).submit(
() -> startVehicleSimulation(getSentCommands().peek())
);
}
}
}
@Override
public synchronized void enable() {
if (isEnabled()) {
return;
}
super.enable();
}
@Override
public synchronized void disable() {
if (!isEnabled()) {
return;
}
super.disable();
}
@Override
public LoopbackVehicleModel getProcessModel() {
return (LoopbackVehicleModel) super.getProcessModel();
}
@Override
public synchronized void sendCommand(MovementCommand cmd) {
requireNonNull(cmd, "cmd");
// Start the simulation task if we're not in single step mode and not simulating already.
if (!getProcessModel().isSingleStepModeEnabled()
&& !isSimulationRunning) {
isSimulationRunning = true;
// The command is added to the sent queue after this method returns. Therefore
// we have to explicitly start the simulation like this.
if (getSentCommands().isEmpty()) {
((ExecutorService) getExecutor()).submit(() -> startVehicleSimulation(cmd));
}
else {
((ExecutorService) getExecutor()).submit(
() -> startVehicleSimulation(getSentCommands().peek())
);
}
}
}
@Override
public void onVehiclePaused(boolean paused) {
getProcessModel().setVehiclePaused(paused);
}
@Override
public void processMessage(Object message) {
}
@Override
public synchronized void initVehiclePosition(String newPos) {
((ExecutorService) getExecutor()).submit(() -> getProcessModel().setPosition(newPos));
}
@Override
public synchronized ExplainedBoolean canProcess(TransportOrder order) {
requireNonNull(order, "order");
return canProcess(
order.getFutureDriveOrders().stream()
.map(driveOrder -> driveOrder.getDestination().getOperation())
.collect(Collectors.toList())
);
}
private ExplainedBoolean canProcess(List<String> operations) {
requireNonNull(operations, "operations");
LOG.debug("{}: Checking processability of {}...", getName(), operations);
boolean canProcess = true;
String reason = "";
// Do NOT require the vehicle to be IDLE or CHARGING here!
// That would mean a vehicle moving to a parking position or recharging location would always
// have to finish that order first, which would render a transport order's dispensable flag
// useless.
boolean loaded = loadState == LoadState.FULL;
Iterator<String> opIter = operations.iterator();
while (canProcess && opIter.hasNext()) {
final String nextOp = opIter.next();
// If we're loaded, we cannot load another piece, but could unload.
if (loaded) {
if (nextOp.startsWith(getProcessModel().getLoadOperation())) {
canProcess = false;
reason = LOAD_OPERATION_CONFLICT;
}
else if (nextOp.startsWith(getProcessModel().getUnloadOperation())) {
loaded = false;
}
} // If we're not loaded, we could load, but not unload.
else if (nextOp.startsWith(getProcessModel().getLoadOperation())) {
loaded = true;
}
else if (nextOp.startsWith(getProcessModel().getUnloadOperation())) {
canProcess = false;
reason = UNLOAD_OPERATION_CONFLICT;
}
}
if (!canProcess) {
LOG.debug("{}: Cannot process {}, reason: '{}'", getName(), operations, reason);
}
return new ExplainedBoolean(canProcess, reason);
}
@Override
protected synchronized void connectVehicle() {
}
@Override
protected synchronized void disconnectVehicle() {
}
@Override
protected synchronized boolean isVehicleConnected() {
return true;
}
@Override
protected VehicleProcessModelTO createCustomTransferableProcessModel() {
return new LoopbackVehicleModelTO()
.setLoadOperation(getProcessModel().getLoadOperation())
.setMaxAcceleration(getProcessModel().getMaxAcceleration())
.setMaxDeceleration(getProcessModel().getMaxDecceleration())
.setMaxFwdVelocity(getProcessModel().getMaxFwdVelocity())
.setMaxRevVelocity(getProcessModel().getMaxRevVelocity())
.setOperatingTime(getProcessModel().getOperatingTime())
.setSingleStepModeEnabled(getProcessModel().isSingleStepModeEnabled())
.setUnloadOperation(getProcessModel().getUnloadOperation())
.setVehiclePaused(getProcessModel().isVehiclePaused());
}
/**
* Triggers a step in single step mode.
*/
public synchronized void trigger() {
if (getProcessModel().isSingleStepModeEnabled()
&& !getSentCommands().isEmpty()
&& !isSimulationRunning) {
isSimulationRunning = true;
((ExecutorService) getExecutor()).submit(
() -> startVehicleSimulation(getSentCommands().peek())
);
}
}
private void startVehicleSimulation(MovementCommand command) {
LOG.debug("Starting vehicle simulation for command: {}", command);
Step step = command.getStep();
getProcessModel().setState(Vehicle.State.EXECUTING);
if (step.getPath() == null) {
LOG.debug("Starting operation simulation...");
getExecutor().schedule(
() -> operationSimulation(command, 0),
SIMULATION_PERIOD,
TimeUnit.MILLISECONDS
);
}
else {
getProcessModel().getVelocityController().addWayEntry(
new WayEntry(
step.getPath().getLength(),
maxVelocity(step),
step.getDestinationPoint().getName(),
step.getVehicleOrientation()
)
);
LOG.debug("Starting movement simulation...");
getExecutor().schedule(
() -> movementSimulation(command),
SIMULATION_PERIOD,
TimeUnit.MILLISECONDS
);
}
}
private int maxVelocity(Step step) {
return (step.getVehicleOrientation() == Vehicle.Orientation.BACKWARD)
? step.getPath().getMaxReverseVelocity()
: step.getPath().getMaxVelocity();
}
/**
* Simulate the movement part of a MovementCommand.
*
* @param command The command to simulate.
*/
private void movementSimulation(MovementCommand command) {
if (!getProcessModel().getVelocityController().hasWayEntries()) {
return;
}
WayEntry prevWayEntry = getProcessModel().getVelocityController().getCurrentWayEntry();
getProcessModel().getVelocityController().advanceTime(getSimulationTimeStep());
WayEntry currentWayEntry = getProcessModel().getVelocityController().getCurrentWayEntry();
//if we are still on the same way entry then reschedule to do it again
if (prevWayEntry == currentWayEntry) {
getExecutor().schedule(
() -> movementSimulation(command),
SIMULATION_PERIOD,
TimeUnit.MILLISECONDS
);
}
else {
//if the way enties are different then we have finished this step
//and we can move on.
getProcessModel().setPosition(prevWayEntry.getDestPointName());
LOG.debug("Movement simulation finished.");
if (!command.hasEmptyOperation()) {
LOG.debug("Starting operation simulation...");
getExecutor().schedule(
() -> operationSimulation(command, 0),
SIMULATION_PERIOD,
TimeUnit.MILLISECONDS
);
}
else {
finishMovementCommand(command);
simulateNextCommand();
}
}
}
/**
* Simulate the operation part of a movement command.
*
* @param command The command to simulate.
* @param timePassed The amount of time passed since starting the simulation.
*/
private void operationSimulation(
MovementCommand command,
int timePassed
) {
if (timePassed < getProcessModel().getOperatingTime()) {
getProcessModel().getVelocityController().advanceTime(getSimulationTimeStep());
getExecutor().schedule(
() -> operationSimulation(command, timePassed + getSimulationTimeStep()),
SIMULATION_PERIOD,
TimeUnit.MILLISECONDS
);
}
else {
LOG.debug("Operation simulation finished.");
finishMovementCommand(command);
String operation = command.getOperation();
if (operation.equals(getProcessModel().getLoadOperation())) {
// Update load handling devices as defined by this operation
getProcessModel().setLoadHandlingDevices(
Arrays.asList(new LoadHandlingDevice(LHD_NAME, true))
);
simulateNextCommand();
}
else if (operation.equals(getProcessModel().getUnloadOperation())) {
getProcessModel().setLoadHandlingDevices(
Arrays.asList(new LoadHandlingDevice(LHD_NAME, false))
);
simulateNextCommand();
}
else if (operation.equals(this.getRechargeOperation())) {
LOG.debug("Starting recharge simulation...");
finishMovementCommand(command);
getProcessModel().setState(Vehicle.State.CHARGING);
getExecutor().schedule(
() -> chargingSimulation(
getProcessModel().getPosition(),
getProcessModel().getEnergyLevel()
),
SIMULATION_PERIOD,
TimeUnit.MILLISECONDS
);
}
else {
simulateNextCommand();
}
}
}
/**
* Simulate recharging the vehicle.
*
* @param rechargePosition The vehicle position where the recharge simulation was started.
* @param rechargePercentage The recharge percentage of the vehicle while it is charging.
*/
private void chargingSimulation(
String rechargePosition,
float rechargePercentage
) {
if (!getSentCommands().isEmpty()) {
LOG.debug("Aborting recharge operation, vehicle has an order...");
simulateNextCommand();
return;
}
if (getProcessModel().getState() != Vehicle.State.CHARGING) {
LOG.debug("Aborting recharge operation, vehicle no longer charging state...");
simulateNextCommand();
return;
}
if (!Objects.equals(getProcessModel().getPosition(), rechargePosition)) {
LOG.debug("Aborting recharge operation, vehicle position changed...");
simulateNextCommand();
return;
}
if (nextChargePercentage(rechargePercentage) < 100.0) {
getProcessModel().setEnergyLevel((int) rechargePercentage);
getExecutor().schedule(
() -> chargingSimulation(rechargePosition, nextChargePercentage(rechargePercentage)),
SIMULATION_PERIOD,
TimeUnit.MILLISECONDS
);
}
else {
LOG.debug("Finishing recharge operation, vehicle at 100%...");
getProcessModel().setEnergyLevel(100);
simulateNextCommand();
}
}
private float nextChargePercentage(float basePercentage) {
return basePercentage
+ (float) (configuration.rechargePercentagePerSecond() / 1000.0) * SIMULATION_PERIOD;
}
private void finishMovementCommand(MovementCommand command) {
//Set the vehicle state to idle
if (getSentCommands().size() <= 1 && getUnsentCommands().isEmpty()) {
getProcessModel().setState(Vehicle.State.IDLE);
}
if (Objects.equals(getSentCommands().peek(), command)) {
// Let the comm adapter know we have finished this command.
getProcessModel().commandExecuted(getSentCommands().poll());
}
else {
LOG.warn(
"{}: Simulated command not oldest in sent queue: {} != {}",
getName(),
command,
getSentCommands().peek()
);
}
}
void simulateNextCommand() {
if (getSentCommands().isEmpty() || getProcessModel().isSingleStepModeEnabled()) {
LOG.debug("Vehicle simulation is done.");
getProcessModel().setState(Vehicle.State.IDLE);
isSimulationRunning = false;
}
else {
LOG.debug("Triggering simulation for next command: {}", getSentCommands().peek());
((ExecutorService) getExecutor()).submit(
() -> startVehicleSimulation(getSentCommands().peek())
);
}
}
private int getSimulationTimeStep() {
return (int) (SIMULATION_PERIOD * configuration.simulationTimeFactor());
}
/**
* The vehicle's possible load states.
*/
private enum LoadState {
EMPTY,
FULL;
}
}

View File

@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle;
import static org.opentcs.virtualvehicle.I18nLoopbackCommAdapter.BUNDLE_PATH;
import java.util.ResourceBundle;
import org.opentcs.drivers.vehicle.VehicleCommAdapterDescription;
/**
* The loopback adapter's {@link VehicleCommAdapterDescription}.
*/
public class LoopbackCommunicationAdapterDescription
extends
VehicleCommAdapterDescription {
/**
* Creates a new instance.
*/
public LoopbackCommunicationAdapterDescription() {
}
@Override
public String getDescription() {
return ResourceBundle.getBundle(BUNDLE_PATH)
.getString("loopbackCommunicationAdapterDescription.description");
}
@Override
public boolean isSimVehicleCommAdapter() {
return true;
}
}

View File

@@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import org.opentcs.data.model.Vehicle;
import org.opentcs.drivers.vehicle.VehicleCommAdapterDescription;
import org.opentcs.drivers.vehicle.VehicleCommAdapterFactory;
/**
* A factory for loopback communication adapters (virtual vehicles).
*/
public class LoopbackCommunicationAdapterFactory
implements
VehicleCommAdapterFactory {
/**
* The adapter components factory.
*/
private final LoopbackAdapterComponentsFactory adapterFactory;
/**
* Indicates whether this component is initialized or not.
*/
private boolean initialized;
/**
* Creates a new factory.
*
* @param componentsFactory The adapter components factory.
*/
@Inject
public LoopbackCommunicationAdapterFactory(LoopbackAdapterComponentsFactory componentsFactory) {
this.adapterFactory = requireNonNull(componentsFactory, "componentsFactory");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
initialized = false;
}
@Override
public VehicleCommAdapterDescription getDescription() {
return new LoopbackCommunicationAdapterDescription();
}
@Override
public boolean providesAdapterFor(Vehicle vehicle) {
requireNonNull(vehicle, "vehicle");
return true;
}
@Override
public LoopbackCommunicationAdapter getAdapterFor(Vehicle vehicle) {
requireNonNull(vehicle, "vehicle");
return adapterFactory.createLoopbackCommAdapter(vehicle);
}
}

View File

@@ -0,0 +1,326 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle;
import jakarta.annotation.Nonnull;
import org.opentcs.common.LoopbackAdapterConstants;
import org.opentcs.data.model.Vehicle;
import org.opentcs.drivers.vehicle.VehicleProcessModel;
/**
* An observable model of a virtual vehicle's and its comm adapter's attributes.
*/
public class LoopbackVehicleModel
extends
VehicleProcessModel {
/**
* Indicates whether this communication adapter is in single step mode or not (i.e. in automatic
* mode).
*/
private boolean singleStepModeEnabled;
/**
* Indicates which operation is a loading operation.
*/
private final String loadOperation;
/**
* Indicates which operation is an unloading operation.
*/
private final String unloadOperation;
/**
* The time needed for executing operations.
*/
private int operatingTime;
/**
* The velocity controller for calculating the simulated vehicle's velocity and current position.
*/
private final VelocityController velocityController;
public LoopbackVehicleModel(Vehicle attachedVehicle) {
super(attachedVehicle);
this.velocityController = new VelocityController(
parseDeceleration(attachedVehicle),
parseAcceleration(attachedVehicle),
attachedVehicle.getMaxReverseVelocity(),
attachedVehicle.getMaxVelocity()
);
this.operatingTime = parseOperatingTime(attachedVehicle);
this.loadOperation = extractLoadOperation(attachedVehicle);
this.unloadOperation = extractUnloadOperation(attachedVehicle);
}
public String getLoadOperation() {
return this.loadOperation;
}
public String getUnloadOperation() {
return this.unloadOperation;
}
/**
* Sets this communication adapter's <em>single step mode</em> flag.
*
* @param mode If <code>true</code>, sets this adapter to single step mode,
* otherwise sets this adapter to flow mode.
*/
public synchronized void setSingleStepModeEnabled(final boolean mode) {
boolean oldValue = singleStepModeEnabled;
singleStepModeEnabled = mode;
getPropertyChangeSupport().firePropertyChange(
Attribute.SINGLE_STEP_MODE.name(),
oldValue,
mode
);
}
/**
* Returns this communication adapter's <em>single step mode</em> flag.
*
* @return <code>true</code> if, and only if, this adapter is currently in
* single step mode.
*/
public synchronized boolean isSingleStepModeEnabled() {
return singleStepModeEnabled;
}
/**
* Returns the default operating time.
*
* @return The default operating time
*/
public synchronized int getOperatingTime() {
return operatingTime;
}
/**
* Sets the default operating time.
*
* @param defaultOperatingTime The new default operating time
*/
public synchronized void setOperatingTime(int defaultOperatingTime) {
int oldValue = this.operatingTime;
this.operatingTime = defaultOperatingTime;
getPropertyChangeSupport().firePropertyChange(
Attribute.OPERATING_TIME.name(),
oldValue,
defaultOperatingTime
);
}
/**
* Returns the maximum deceleration.
*
* @return The maximum deceleration
*/
public synchronized int getMaxDecceleration() {
return velocityController.getMaxDeceleration();
}
/**
* Sets the maximum deceleration.
*
* @param maxDeceleration The new maximum deceleration
*/
public synchronized void setMaxDeceleration(int maxDeceleration) {
int oldValue = velocityController.getMaxDeceleration();
velocityController.setMaxDeceleration(maxDeceleration);
getPropertyChangeSupport().firePropertyChange(
Attribute.DECELERATION.name(),
oldValue,
maxDeceleration
);
}
/**
* Returns the maximum acceleration.
*
* @return The maximum acceleration
*/
public synchronized int getMaxAcceleration() {
return velocityController.getMaxAcceleration();
}
/**
* Sets the maximum acceleration.
*
* @param maxAcceleration The new maximum acceleration
*/
public synchronized void setMaxAcceleration(int maxAcceleration) {
int oldValue = velocityController.getMaxAcceleration();
velocityController.setMaxAcceleration(maxAcceleration);
getPropertyChangeSupport().firePropertyChange(
Attribute.ACCELERATION.name(),
oldValue,
maxAcceleration
);
}
/**
* Returns the maximum reverse velocity.
*
* @return The maximum reverse velocity.
*/
public synchronized int getMaxRevVelocity() {
return velocityController.getMaxRevVelocity();
}
/**
* Sets the maximum reverse velocity.
*
* @param maxRevVelocity The new maximum reverse velocity
*/
public synchronized void setMaxRevVelocity(int maxRevVelocity) {
int oldValue = velocityController.getMaxRevVelocity();
velocityController.setMaxRevVelocity(maxRevVelocity);
getPropertyChangeSupport().firePropertyChange(
Attribute.MAX_REVERSE_VELOCITY.name(),
oldValue,
maxRevVelocity
);
}
/**
* Returns the maximum forward velocity.
*
* @return The maximum forward velocity.
*/
public synchronized int getMaxFwdVelocity() {
return velocityController.getMaxFwdVelocity();
}
/**
* Sets the maximum forward velocity.
*
* @param maxFwdVelocity The new maximum forward velocity.
*/
public synchronized void setMaxFwdVelocity(int maxFwdVelocity) {
int oldValue = velocityController.getMaxFwdVelocity();
velocityController.setMaxFwdVelocity(maxFwdVelocity);
getPropertyChangeSupport().firePropertyChange(
Attribute.MAX_FORWARD_VELOCITY.name(),
oldValue,
maxFwdVelocity
);
}
/**
* Returns whether the vehicle is paused.
*
* @return paused
*/
public synchronized boolean isVehiclePaused() {
return velocityController.isVehiclePaused();
}
/**
* Pause the vehicle (i.e. set it's velocity to zero).
*
* @param pause True, if vehicle shall be paused. False, otherwise.
*/
public synchronized void setVehiclePaused(boolean pause) {
boolean oldValue = velocityController.isVehiclePaused();
velocityController.setVehiclePaused(pause);
getPropertyChangeSupport().firePropertyChange(
Attribute.VEHICLE_PAUSED.name(),
oldValue,
pause
);
}
/**
* Returns the virtual vehicle's velocity controller.
*
* @return The virtual vehicle's velocity controller.
*/
@Nonnull
public VelocityController getVelocityController() {
return velocityController;
}
private int parseOperatingTime(Vehicle vehicle) {
String opTime = vehicle.getProperty(LoopbackAdapterConstants.PROPKEY_OPERATING_TIME);
// Ensure it's a positive value.
return Math.max(Parsers.tryParseString(opTime, 5000), 1);
}
/**
* Gets the maximum acceleration. If the user did not specify any, 1000(m/s²) is returned.
*
* @param vehicle the vehicle
* @return the maximum acceleration.
*/
private int parseAcceleration(Vehicle vehicle) {
String acceleration = vehicle.getProperty(LoopbackAdapterConstants.PROPKEY_ACCELERATION);
// Ensure it's a positive value.
return Math.max(Parsers.tryParseString(acceleration, 500), 1);
}
/**
* Gets the maximum decceleration. If the user did not specify any, 1000(m/s²) is returned.
*
* @param vehicle the vehicle
* @return the maximum decceleration.
*/
private int parseDeceleration(Vehicle vehicle) {
String deceleration = vehicle.getProperty(LoopbackAdapterConstants.PROPKEY_DECELERATION);
// Ensure it's a negative value.
return Math.min(Parsers.tryParseString(deceleration, -500), -1);
}
private static String extractLoadOperation(Vehicle attachedVehicle) {
String result = attachedVehicle.getProperty(LoopbackAdapterConstants.PROPKEY_LOAD_OPERATION);
if (result == null) {
result = LoopbackAdapterConstants.PROPVAL_LOAD_OPERATION_DEFAULT;
}
return result;
}
private static String extractUnloadOperation(Vehicle attachedVehicle) {
String result = attachedVehicle.getProperty(LoopbackAdapterConstants.PROPKEY_UNLOAD_OPERATION);
if (result == null) {
result = LoopbackAdapterConstants.PROPVAL_UNLOAD_OPERATION_DEFAULT;
}
return result;
}
/**
* Notification arguments to indicate some change.
*/
public enum Attribute {
/**
* Indicates a change of the virtual vehicle's single step mode setting.
*/
SINGLE_STEP_MODE,
/**
* Indicates a change of the virtual vehicle's default operating time.
*/
OPERATING_TIME,
/**
* Indicates a change of the virtual vehicle's maximum acceleration.
*/
ACCELERATION,
/**
* Indicates a change of the virtual vehicle's maximum deceleration.
*/
DECELERATION,
/**
* Indicates a change of the virtual vehicle's maximum forward velocity.
*/
MAX_FORWARD_VELOCITY,
/**
* Indicates a change of the virtual vehicle's maximum reverse velocity.
*/
MAX_REVERSE_VELOCITY,
/**
* Indicates a change of the virtual vehicle's paused setting.
*/
VEHICLE_PAUSED,
}
}

View File

@@ -0,0 +1,137 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle;
import org.opentcs.drivers.vehicle.management.VehicleProcessModelTO;
/**
* A serializable representation of a {@link LoopbackVehicleModel}.
*/
public class LoopbackVehicleModelTO
extends
VehicleProcessModelTO {
/**
* Whether this communication adapter is in single step mode or not (i.e. in automatic mode).
*/
private boolean singleStepModeEnabled;
/**
* Indicates which operation is a loading operation.
*/
private String loadOperation;
/**
* Indicates which operation is an unloading operation.
*/
private String unloadOperation;
/**
* The time needed for executing operations.
*/
private int operatingTime;
/**
* The maximum acceleration.
*/
private int maxAcceleration;
/**
* The maximum deceleration.
*/
private int maxDeceleration;
/**
* The maximum forward velocity.
*/
private int maxFwdVelocity;
/**
* The maximum reverse velocity.
*/
private int maxRevVelocity;
/**
* Whether the vehicle is paused or not.
*/
private boolean vehiclePaused;
/**
* Creates a new instance.
*/
public LoopbackVehicleModelTO() {
}
public boolean isSingleStepModeEnabled() {
return singleStepModeEnabled;
}
public LoopbackVehicleModelTO setSingleStepModeEnabled(boolean singleStepModeEnabled) {
this.singleStepModeEnabled = singleStepModeEnabled;
return this;
}
public String getLoadOperation() {
return loadOperation;
}
public LoopbackVehicleModelTO setLoadOperation(String loadOperation) {
this.loadOperation = loadOperation;
return this;
}
public String getUnloadOperation() {
return unloadOperation;
}
public LoopbackVehicleModelTO setUnloadOperation(String unloadOperation) {
this.unloadOperation = unloadOperation;
return this;
}
public int getOperatingTime() {
return operatingTime;
}
public LoopbackVehicleModelTO setOperatingTime(int operatingTime) {
this.operatingTime = operatingTime;
return this;
}
public int getMaxAcceleration() {
return maxAcceleration;
}
public LoopbackVehicleModelTO setMaxAcceleration(int maxAcceleration) {
this.maxAcceleration = maxAcceleration;
return this;
}
public int getMaxDeceleration() {
return maxDeceleration;
}
public LoopbackVehicleModelTO setMaxDeceleration(int maxDeceleration) {
this.maxDeceleration = maxDeceleration;
return this;
}
public int getMaxFwdVelocity() {
return maxFwdVelocity;
}
public LoopbackVehicleModelTO setMaxFwdVelocity(int maxFwdVelocity) {
this.maxFwdVelocity = maxFwdVelocity;
return this;
}
public int getMaxRevVelocity() {
return maxRevVelocity;
}
public LoopbackVehicleModelTO setMaxRevVelocity(int maxRevVelocity) {
this.maxRevVelocity = maxRevVelocity;
return this;
}
public boolean isVehiclePaused() {
return vehiclePaused;
}
public LoopbackVehicleModelTO setVehiclePaused(boolean vehiclePaused) {
this.vehiclePaused = vehiclePaused;
return this;
}
}

View File

@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle;
import com.google.common.primitives.Ints;
import jakarta.annotation.Nullable;
/**
* This class provides methods for parsing.
*/
public class Parsers {
/**
* Prevents instantiation of this utility class.
*/
private Parsers() {
}
/**
* Parses a String to an int. If <code>toParse</code> could not be parsed successfully then the
* default value <code>retOnFail</code> is returned.
*
* @param toParse the <code>String</code> to be parsed.
* @param retOnFail default value that is returned if the <code>String</code> could not be parsed.
* @return if the <code>String</code> could be parsed then the int value of the
* <code>String</code> is returned, else retOnFail
*/
public static int tryParseString(
@Nullable
String toParse,
int retOnFail
) {
if (toParse == null) {
return retOnFail;
}
Integer parseTry = Ints.tryParse(toParse);
if (parseTry == null) {
return retOnFail;
}
return parseTry;
}
}

View File

@@ -0,0 +1,418 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
import java.io.Serializable;
import java.util.ArrayDeque;
import java.util.Queue;
import org.opentcs.data.model.Vehicle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simulates the velocity of a vehicle depending on the length of the way and
* the time it has moved already.
*/
public class VelocityController {
/**
* This class's Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(VelocityController.class);
/**
* The maximum deceleration of the vehicle (in mm/s<sup>2</sup>).
*/
private int maxDeceleration;
/**
* The maximum acceleration of the vehicle (in mm/s<sup>2</sup>).
*/
private int maxAcceleration;
/**
* The maximum reverse velocity of the vehicle (in mm/s).
*/
private int maxRevVelocity;
/**
* The maximum forward velocity of the vehicle (in mm/s).
*/
private int maxFwdVelocity;
/**
* The current acceleration (in mm/s<sup>2</sup>).
*/
private int currentAcceleration;
/**
* The current velocity (in mm/s).
*/
private int currentVelocity;
/**
* The current position (in mm from the beginning of the current way entry).
*/
private long currentPosition;
/**
* The current time, relative to the point of time at which this velocity
* controller was created.
*/
private long currentTime;
/**
* This controller's processing queue.
*/
private final Queue<WayEntry> wayEntries = new ArrayDeque<>();
/**
* True, if the vehicle has been paused, e.g. via the kernel gui
* or a by a client message.
*/
private boolean paused;
/**
* Creates a new VelocityController.
*
* @param maxDecel The maximum deceleration of the vehicle (in
* mm/s<sup>2</sup>).
* @param maxAccel The maximum acceleration of the vehicle (in
* mm/s<sup>2</sup>).
* @param maxRevVelo The maximum reverse velocity of the vehicle (in mm/s).
* @param maxFwdVelo The maximum forward velocity of the vehicle (in mm/s).
*/
public VelocityController(
int maxDecel,
int maxAccel,
int maxRevVelo,
int maxFwdVelo
) {
maxDeceleration = maxDecel;
maxAcceleration = maxAccel;
maxRevVelocity = maxRevVelo;
maxFwdVelocity = maxFwdVelo;
paused = false;
}
/**
* Returns the maximum deceleration.
*
* @return The maximum deceleration
*/
public int getMaxDeceleration() {
return maxDeceleration;
}
/**
* Sets the maximum deceleration.
*
* @param maxDeceleration The new maximum deceleration
*/
public void setMaxDeceleration(int maxDeceleration) {
this.maxDeceleration = maxDeceleration;
}
/**
* Returns the maximum acceleration.
*
* @return The maximum acceleration
*/
public int getMaxAcceleration() {
return maxAcceleration;
}
/**
* Sets the maximum acceleration.
*
* @param maxAcceleration The new maximum acceleration
*/
public void setMaxAcceleration(int maxAcceleration) {
this.maxAcceleration = maxAcceleration;
}
/**
* Returns the maximum reverse velocity.
*
* @return The maximum reverse velocity
*/
public int getMaxRevVelocity() {
return maxRevVelocity;
}
/**
* Sets the maximum reverse velocity.
*
* @param maxRevVelocity The new maximum reverse velocity
*/
public void setMaxRevVelocity(int maxRevVelocity) {
this.maxRevVelocity = maxRevVelocity;
}
/**
* Returns the maximum forward velocity.
*
* @return The maximum forward velocity
*/
public int getMaxFwdVelocity() {
return maxFwdVelocity;
}
/**
* Sets the maximum forward velocity.
*
* @param maxFwdVelocity The new maximum forward velocity
*/
public void setMaxFwdVelocity(int maxFwdVelocity) {
this.maxFwdVelocity = maxFwdVelocity;
}
/**
* Returns whether the vehicle is paused.
*
* @return paused
*/
public boolean isVehiclePaused() {
return paused;
}
/**
* Pause the vehicle (i.e. set it's velocity to zero).
*
* @param pause True, if vehicle shall be paused. False, otherwise.
*/
public void setVehiclePaused(boolean pause) {
paused = pause;
}
/**
* Returns this controller's current velocity.
*
* @return This controller's current velocity.
*/
public int getCurrentVelocity() {
return currentVelocity;
}
/**
* Returns the vehicle's current position (in mm from the beginning of the
* current way entry.
*
* @return The vehicle's current position (in mm from the beginning of the
* current way entry.
*/
public long getCurrentPosition() {
return currentPosition;
}
/**
* Returns the current time, relative to to the point of time at which this
* controller was started.
*
* @return The current time, relative to to the point of time at which this
* controller was started.
*/
public long getCurrentTime() {
return currentTime;
}
/**
* Adds a way entry to this vehicle controller's processing queue.
*
* @param newEntry The way entry to add.
*/
public void addWayEntry(WayEntry newEntry) {
requireNonNull(newEntry, "newEntry");
wayEntries.add(newEntry);
}
/**
* Returns the way entry this velocity controller is currently processing.
*
* @return The way entry this velocity controller is currently processing. If
* the processing queue is currently empty, <code>null</code> is returned.
*/
public WayEntry getCurrentWayEntry() {
return wayEntries.peek();
}
/**
* Returns <code>true</code> if, and only if, there are way entries to be
* processed in this velocity controller's queue.
*
* @return <code>true</code> if, and only if, there are way entries to be
* processed in this velocity controller's queue.
*/
public boolean hasWayEntries() {
return !wayEntries.isEmpty();
}
/**
* Increase this controller's current time by the given value and simulate
* the events that would happen in this time frame.
*
* @param dt The time by which to advance this controller (in milliseconds).
* Must be at least 1.
*/
public void advanceTime(int dt) {
checkArgument(dt >= 1, "dt is less than 1: %d", dt);
final long oldPosition = currentPosition;
final int oldVelocity = currentVelocity;
final WayEntry curWayEntry = wayEntries.peek();
if (curWayEntry == null || paused) {
currentAcceleration = 0;
currentVelocity = 0;
}
else {
final int maxVelocity;
final Vehicle.Orientation orientation = curWayEntry.vehicleOrientation;
switch (orientation) {
case FORWARD:
maxVelocity = maxFwdVelocity;
break;
case BACKWARD:
maxVelocity = maxRevVelocity;
break;
default:
LOG.warn("Unhandled orientation: {}, assuming forward.", orientation);
maxVelocity = maxFwdVelocity;
}
final int targetVelocity = Math.min(curWayEntry.targetVelocity, maxVelocity);
// Accelerate as quickly as possible.
final long accelerationDistance = 10;
// Recompute the acceleration to reach/keep the desired velocity.
currentAcceleration
= (currentVelocity == targetVelocity) ? 0
: suitableAcceleration(targetVelocity, accelerationDistance);
// Recompute current velocity.
currentVelocity = oldVelocity + currentAcceleration * dt / 1000;
// Recompute current position.
currentPosition = oldPosition + oldVelocity * dt / 1000
+ currentAcceleration * dt * dt / 1000000 / 2;
// Check if we have left the way entry and entered the next.
if (currentPosition >= curWayEntry.length) {
currentPosition -= curWayEntry.length;
wayEntries.poll();
}
}
// The given time has now passed.
currentTime += dt;
}
/**
* Returns the acceleration (in mm/s<sup>2</sup>) needed for reaching a given
* velocity exactly after travelling a given distance (respecting the current
* velocity).
*
* @param targetVelocity The desired velocity (in mm/s).
* @param travelDistance The distance after which the desired velocity is
* supposed to be reached (in mm). Must be a positive value.
* @return The acceleration needed for reaching the given velocity after
* travelling the given distance.
*/
int suitableAcceleration(final int targetVelocity, final long travelDistance) {
if (travelDistance < 1) {
throw new IllegalArgumentException("travelDistance is less than 1");
}
final double v_current = currentVelocity;
final double v_target = targetVelocity;
final double s = travelDistance;
// Compute travelling time.
// XXX Divide by zero if (v_current == -v_target), especially if both are 0!
final double t = s / (v_current + (v_target - v_current) / 2);
LOG.debug(
"t = " + t
+ "; s = " + s
+ "; v_current = " + v_current
+ "; v_target = " + v_target
);
// Compute acceleration.
int result = (int) ((v_target - v_current) / t);
LOG.debug("result = " + result);
if (result > maxAcceleration) {
result = maxAcceleration;
}
else if (result < maxDeceleration) {
result = maxDeceleration;
}
return result;
}
/**
* An entry in a vehicle controller's processing queue.
*/
public static class WayEntry
implements
Serializable {
/**
* The length of the way to drive (in mm).
*/
private final long length;
/**
* The target velocity on this way (in mm/s).
*/
private final int targetVelocity;
/**
* The name of the destination point.
*/
private final String destPointName;
/**
* The vehicle's orientation on this way.
*/
private final Vehicle.Orientation vehicleOrientation;
/**
* Creates a new WayEntry.
*
* @param length The length of the way to drive (in mm).
* @param maxVelocity The maximum velocity on this way (in mm/s).
* @param destPointName The name of the destination point.
* @param orientation The vehicle's orientation on this way.
*/
public WayEntry(
long length,
int maxVelocity,
String destPointName,
Vehicle.Orientation orientation
) {
checkArgument(length > 0, "length is not > 0 but %s", length);
this.length = length;
if (maxVelocity < 1) {
LOG.warn("maxVelocity is zero or negative, setting to 100");
this.targetVelocity = 100;
}
else {
this.targetVelocity = maxVelocity;
}
this.destPointName = requireNonNull(destPointName, "destPointName");
this.vehicleOrientation = requireNonNull(orientation, "vehicleOrientation");
}
/**
* Returns the name of the destination point.
*
* @return The name of the destination point.
*/
public String getDestPointName() {
return destPointName;
}
@Override
public boolean equals(Object o) {
if (o instanceof WayEntry) {
WayEntry other = (WayEntry) o;
return other.length == length
&& other.targetVelocity == targetVelocity
&& destPointName.equals(other.destPointName)
&& vehicleOrientation.equals(other.vehicleOrientation);
}
else {
return false;
}
}
@Override
public int hashCode() {
return (int) (length ^ (length >>> 32))
^ targetVelocity
^ destPointName.hashCode()
^ vehicleOrientation.hashCode();
}
}
}

View File

@@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle;
import org.opentcs.configuration.ConfigurationEntry;
import org.opentcs.configuration.ConfigurationPrefix;
/**
* Provides methods to configure to {@link LoopbackCommunicationAdapter}.
*/
@ConfigurationPrefix(VirtualVehicleConfiguration.PREFIX)
public interface VirtualVehicleConfiguration {
/**
* This configuration's prefix.
*/
String PREFIX = "virtualvehicle";
@ConfigurationEntry(
type = "Boolean",
description = "Whether to enable to register/enable the loopback driver.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_enable"
)
boolean enable();
@ConfigurationEntry(
type = "Integer",
description = "The adapter's command queue capacity.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_NEW_PLANT_MODEL,
orderKey = "1_attributes_1"
)
int commandQueueCapacity();
@ConfigurationEntry(
type = "String",
description = "The string to be treated as a recharge operation.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_NEW_PLANT_MODEL,
orderKey = "1_attributes_2"
)
String rechargeOperation();
@ConfigurationEntry(
type = "Double",
description = "The rate at which the vehicle recharges in percent per second.",
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
orderKey = "1_attributes_3"
)
double rechargePercentagePerSecond();
@ConfigurationEntry(
type = "Double",
description = {
"The simulation time factor.",
"1.0 is real time, greater values speed up simulation."
},
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
orderKey = "2_behaviour_1"
)
double simulationTimeFactor();
@ConfigurationEntry(
type = "Integer",
description = {"The virtual vehicle's length in mm when it's loaded."},
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
orderKey = "2_behaviour_2"
)
int vehicleLengthLoaded();
@ConfigurationEntry(
type = "Integer",
description = {"The virtual vehicle's length in mm when it's unloaded."},
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
orderKey = "2_behaviour_3"
)
int vehicleLengthUnloaded();
}

View File

@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.commands;
import org.opentcs.drivers.vehicle.AdapterCommand;
import org.opentcs.drivers.vehicle.MovementCommand;
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
/**
* A command to notify the loopback adapter the last/current movement command failed.
*/
public class CurrentMovementCommandFailedCommand
implements
AdapterCommand {
/**
* Creates a new instance.
*/
public CurrentMovementCommandFailedCommand() {
}
@Override
public void execute(VehicleCommAdapter adapter) {
MovementCommand failedCommand = adapter.getSentCommands().peek();
if (failedCommand != null) {
adapter.getProcessModel().commandFailed(adapter.getSentCommands().peek());
}
}
}

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.commands;
import static java.util.Objects.requireNonNull;
import jakarta.annotation.Nonnull;
import org.opentcs.drivers.vehicle.AdapterCommand;
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
import org.opentcs.drivers.vehicle.VehicleCommAdapterEvent;
/**
* A command to publish {@link VehicleCommAdapterEvent}s.
*/
public class PublishEventCommand
implements
AdapterCommand {
/**
* The event to publish.
*/
private final VehicleCommAdapterEvent event;
/**
* Creates a new instance.
*
* @param event The event to publish.
*/
public PublishEventCommand(
@Nonnull
VehicleCommAdapterEvent event
) {
this.event = requireNonNull(event, "event");
}
@Override
public void execute(VehicleCommAdapter adapter) {
adapter.getProcessModel().publishEvent(event);
}
}

View File

@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.commands;
import org.opentcs.drivers.vehicle.AdapterCommand;
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
/**
* A command to set a vehicle's energy level.
*/
public class SetEnergyLevelCommand
implements
AdapterCommand {
/**
* The energy level to set.
*/
private final int level;
/**
* Creates a new instance.
*
* @param level The energy level to set.
*/
public SetEnergyLevelCommand(int level) {
this.level = level;
}
@Override
public void execute(VehicleCommAdapter adapter) {
adapter.getProcessModel().setEnergyLevel(level);
}
}

View File

@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.commands;
import static java.util.Objects.requireNonNull;
import jakarta.annotation.Nonnull;
import java.util.List;
import org.opentcs.drivers.vehicle.AdapterCommand;
import org.opentcs.drivers.vehicle.LoadHandlingDevice;
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
/**
* A command to set the {@link LoadHandlingDevice}s attached to a vehicle.
*/
public class SetLoadHandlingDevicesCommand
implements
AdapterCommand {
/**
* The list of load handling devices.
*/
private final List<LoadHandlingDevice> devices;
/**
* Creates a new instance.
*
* @param devices The list of load handling devices.
*/
public SetLoadHandlingDevicesCommand(
@Nonnull
List<LoadHandlingDevice> devices
) {
this.devices = requireNonNull(devices, "devices");
}
@Override
public void execute(VehicleCommAdapter adapter) {
adapter.getProcessModel().setLoadHandlingDevices(devices);
}
}

View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.commands;
import org.opentcs.drivers.vehicle.AdapterCommand;
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
/**
* A command to set a vehicle's orientation angle.
*/
public class SetOrientationAngleCommand
implements
AdapterCommand {
/**
* The orientation angle to set.
*/
private final double angle;
/**
* Creates a new instance.
*
* @param angle The orientation angle to set.
*/
public SetOrientationAngleCommand(double angle) {
this.angle = angle;
}
@Override
public void execute(VehicleCommAdapter adapter) {
adapter.getProcessModel().setPose(
adapter.getProcessModel().getPose().withOrientationAngle(angle)
);
}
}

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.commands;
import jakarta.annotation.Nullable;
import org.opentcs.drivers.vehicle.AdapterCommand;
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
/**
* A command to set a vehicle's position.
*/
public class SetPositionCommand
implements
AdapterCommand {
/**
* The position to set.
*/
private final String position;
/**
* Creates a new instance.
*
* @param position The position to set.
*/
public SetPositionCommand(
@Nullable
String position
) {
this.position = position;
}
@Override
public void execute(VehicleCommAdapter adapter) {
adapter.getProcessModel().setPosition(position);
}
}

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.commands;
import jakarta.annotation.Nullable;
import org.opentcs.data.model.Triple;
import org.opentcs.drivers.vehicle.AdapterCommand;
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
/**
* A command to set a vehicle's precise position.
*/
public class SetPrecisePositionCommand
implements
AdapterCommand {
/**
* The percise position to set.
*/
private final Triple position;
/**
* Creates a new instance.
*
* @param position The precise position to set.
*/
public SetPrecisePositionCommand(
@Nullable
Triple position
) {
this.position = position;
}
@Override
public void execute(VehicleCommAdapter adapter) {
adapter.getProcessModel().setPose(
adapter.getProcessModel().getPose().withPosition(position)
);
}
}

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.commands;
import org.opentcs.drivers.vehicle.AdapterCommand;
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
import org.opentcs.virtualvehicle.LoopbackCommunicationAdapter;
/**
* A command to enable/disable the comm adapter's single step mode.
*/
public class SetSingleStepModeEnabledCommand
implements
AdapterCommand {
/**
* Whether to enable/disable single step mode.
*/
private final boolean enabled;
/**
* Creates a new instance.
*
* @param enabled Whether to enable/disable single step mode.
*/
public SetSingleStepModeEnabledCommand(boolean enabled) {
this.enabled = enabled;
}
@Override
public void execute(VehicleCommAdapter adapter) {
if (!(adapter instanceof LoopbackCommunicationAdapter)) {
return;
}
LoopbackCommunicationAdapter loopbackAdapter = (LoopbackCommunicationAdapter) adapter;
loopbackAdapter.getProcessModel().setSingleStepModeEnabled(enabled);
}
}

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.commands;
import static java.util.Objects.requireNonNull;
import jakarta.annotation.Nonnull;
import org.opentcs.data.model.Vehicle;
import org.opentcs.drivers.vehicle.AdapterCommand;
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
/**
* A command to set a vehicle's state.
*/
public class SetStateCommand
implements
AdapterCommand {
/**
* The vehicle state to set.
*/
private final Vehicle.State state;
/**
* Creates a new instance.
*
* @param state The vehicle state to set.
*/
public SetStateCommand(
@Nonnull
Vehicle.State state
) {
this.state = requireNonNull(state, "state");
}
@Override
public void execute(VehicleCommAdapter adapter) {
adapter.getProcessModel().setState(state);
}
}

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.commands;
import org.opentcs.drivers.vehicle.AdapterCommand;
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
import org.opentcs.virtualvehicle.LoopbackCommunicationAdapter;
/**
* A command to pause/unpause the vehicle.
*/
public class SetVehiclePausedCommand
implements
AdapterCommand {
/**
* Whether to pause/unpause the vehicle.
*/
private final boolean paused;
/**
* Creates a new instance.
*
* @param paused Whether to pause/unpause the vehicle.
*/
public SetVehiclePausedCommand(boolean paused) {
this.paused = paused;
}
@Override
public void execute(VehicleCommAdapter adapter) {
if (!(adapter instanceof LoopbackCommunicationAdapter)) {
return;
}
LoopbackCommunicationAdapter loopbackAdapter = (LoopbackCommunicationAdapter) adapter;
loopbackAdapter.getProcessModel().setVehiclePaused(paused);
}
}

View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.commands;
import static java.util.Objects.requireNonNull;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import org.opentcs.drivers.vehicle.AdapterCommand;
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
/**
* A command to set a vehicle's property.
*/
public class SetVehiclePropertyCommand
implements
AdapterCommand {
/**
* The property key to set.
*/
private final String key;
/**
* The property value to set.
*/
private final String value;
/**
* Creates a new instance.
*
* @param key The property key to set.
* @param value The property value to set.
*/
public SetVehiclePropertyCommand(
@Nonnull
String key,
@Nullable
String value
) {
this.key = requireNonNull(key, "key");
this.value = value;
}
@Override
public void execute(VehicleCommAdapter adapter) {
adapter.getProcessModel().setProperty(key, value);
}
}

View File

@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.commands;
import org.opentcs.drivers.vehicle.AdapterCommand;
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
import org.opentcs.virtualvehicle.LoopbackCommunicationAdapter;
/**
* A command to trigger the comm adapter in single step mode.
*/
public class TriggerCommand
implements
AdapterCommand {
/**
* Creates a new instance.
*/
public TriggerCommand() {
}
@Override
public void execute(VehicleCommAdapter adapter) {
if (!(adapter instanceof LoopbackCommunicationAdapter)) {
return;
}
LoopbackCommunicationAdapter loopbackAdapter = (LoopbackCommunicationAdapter) adapter;
loopbackAdapter.trigger();
}
}

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.3" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="messageLabel">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="text" type="java.lang.String" value="Message"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="2" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="17" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="label">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="text" type="java.lang.String" value="Label"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JComboBox" name="comboBox">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="0"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;E&gt;"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="17" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Form>

View File

@@ -0,0 +1,277 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.inputcomponents;
import static java.util.Objects.requireNonNull;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import javax.swing.DefaultComboBoxModel;
import javax.swing.ListCellRenderer;
/**
* An input panel providing a dropdown list and optionally a message and a
* label.
*
* The <code>Object</code> that is returned by {@link InputPanel#getInput} is
* an object from the provided content list.
*
* @param <E> Type of the elements in the dropdown list
*/
public final class DropdownListInputPanel<E>
extends
InputPanel {
/**
* Create a new panel.
*
* @param title Title of the panel.
*/
private DropdownListInputPanel(String title) {
super(title);
initComponents();
}
@Override
protected void captureInput() {
// If the combobox is editable and the input was entered using the jTextField and confirmed
// using the enter button, then the textField's input is not yet saved as the comboBox
// selection.
// That's why it is safer to get the input from the textfield, if the combobox is editable.
input = comboBox.isEditable() ? comboBox.getEditor().getItem() : comboBox.getSelectedItem();
}
// FORMATTER:OFF
// CHECKSTYLE:OFF
/**
* This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
messageLabel = new javax.swing.JLabel();
label = new javax.swing.JLabel();
comboBox = new javax.swing.JComboBox<>();
setLayout(new java.awt.GridBagLayout());
messageLabel.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
messageLabel.setText("Message");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.gridwidth = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(messageLabel, gridBagConstraints);
label.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
label.setText("Label");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(label, gridBagConstraints);
comboBox.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(comboBox, gridBagConstraints);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JComboBox<E> comboBox;
private javax.swing.JLabel label;
private javax.swing.JLabel messageLabel;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
/**
* See {@link InputPanel.Builder}.
*
* @param <E> Type of the elements in the dropdown list
*/
public static class Builder<E>
implements
InputPanel.Builder {
/**
* The panel's title.
*/
private final String title;
/**
* The optional message.
*/
private String message;
/**
* The label for the dropdown list.
*/
private String label;
/**
* Content for the dropdown list.
*/
private final List<E> content;
/**
* Initially selected index of the dropdown list.
* Default is 0.
*/
private int initialIndex;
/**
* Whether the combo box should be editable.
*/
private boolean editable;
/**
* Strategy for presenting the E-Objects in View.
*/
private ListCellRenderer<? super E> renderer;
/**
* Function for representing a selected element in the combo box.
* This is basically a renderer for the combo box editor's content.
*/
private Function<E, String> selectionRepresenter = o -> o == null ? "" : o.toString();
/**
* Create a new <code>Builder</code>.
*
* @param title The title of the panel.
* @param content List of items.
*/
public Builder(String title, List<E> content) {
this.title = requireNonNull(title, "title");
this.content = requireNonNull(content, "content");
}
@Override
public InputPanel build() {
DropdownListInputPanel<E> panel = new DropdownListInputPanel<>(title);
panel.messageLabel.setText(message);
panel.label.setText(label);
panel.comboBox.setEditable(editable);
DefaultComboBoxModel<E> model = new DefaultComboBoxModel<>();
panel.comboBox.setModel(model);
if (editable) {
EditableComboBoxEditor<E> editor = new EditableComboBoxEditor<>(
Collections.unmodifiableList(panel.getValidationListeners()),
panel.comboBox, selectionRepresenter
);
panel.comboBox.setEditor(editor);
model.addListDataListener(editor);
}
//to notify the editor about new input
for (E c : content) {
model.addElement(c);
}
if (this.renderer != null) {
panel.comboBox.setRenderer(this.renderer);
}
if (!content.isEmpty()) {
panel.comboBox.setSelectedIndex(initialIndex);
}
return panel;
}
/**
* Set the message of the panel.
* The user of this method must take care for the line breaks in the message, as it is not
* wrapped automatically!
*
* @param message The message.
* @return The builder instance.
*/
public Builder<E> setMessage(String message) {
this.message = message;
return this;
}
/**
* Sets the editable flag for the combo box.
*
* @param editable The editable flag.
* @return The builder instance.
*/
public Builder<E> setEditable(boolean editable) {
this.editable = editable;
return this;
}
/**
* Set the CellRenderer for the dropdown list.
* if none is set, then the default renderer will be used, which calls toString().
*
* @param renderer The renderer.
* @return The builder instance.
*/
public Builder<E> setRenderer(ListCellRenderer<? super E> renderer) {
this.renderer = renderer;
return this;
}
/**
* Set the label of the panel.
*
* @param label The label.
* @return The builder instance.
*/
public Builder<E> setLabel(String label) {
this.label = label;
return this;
}
/**
* Set the initial selected list entry.
*
* @param index Must be &gt; 0, will have no effect otherwise.
* @return The builder instance.
*/
public Builder<E> setInitialSelection(int index) {
if (index >= 0) {
initialIndex = index;
}
return this;
}
/**
* Set the initial selected list entry.
*
* @param element Element to select. Selection remains unchanged if element is not in drop down
* list or element is <code>null</code> and the content list does not allow null values.
* @return The builder instance.
*/
public Builder<E> setInitialSelection(Object element) {
int index;
try {
index = content.indexOf(element);
}
catch (NullPointerException e) {
index = -1;
}
return setInitialSelection(index);
}
/**
* Sets the representer for selected elements in the combo box.
* This is basically a renderer for the combo box editor's content.
* If none is set, a default representer will be used, which calls toString().
*
* @param selectionRepresenter Function for representing a selected element in the combo box.
* @return The builder instance.
*/
public Builder<E> setSelectionRepresenter(Function<E, String> selectionRepresenter) {
this.selectionRepresenter = selectionRepresenter;
return this;
}
}
}

View File

@@ -0,0 +1,103 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.inputcomponents;
import static java.util.Objects.requireNonNull;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.plaf.basic.BasicComboBoxEditor;
/**
* An editor for editable combo boxes.
*/
public class EditableComboBoxEditor<E>
extends
BasicComboBoxEditor
implements
ListDataListener {
/**
* Represents the model of the comboBox as a set.
*/
private final Set<E> content = new HashSet<>();
/**
* The relevant combobox.
*/
private final JComboBox<E> comboBox;
/**
* Returns the string representation for the combo box's selected item.
*/
private final Function<E, String> representer;
/**
* Creates and instance and configures an {@link EditableComboBoxListener} for the editor.
*
* @param validationListeners validation listeners.
* @param comboBox the comboBox that is edited.
* @param representer Returns the string representation for the combo box's selected item.
*/
public EditableComboBoxEditor(
List<ValidationListener> validationListeners,
JComboBox<E> comboBox,
Function<E, String> representer
) {
this.comboBox = requireNonNull(comboBox, "comboBox");
this.representer = requireNonNull(representer, "representer");
editor.getDocument().addDocumentListener(
new EditableComboBoxListener<>(
content,
validationListeners,
editor,
representer
)
);
}
@Override
public void intervalAdded(ListDataEvent e) {
loadContent();
}
@Override
public void intervalRemoved(ListDataEvent e) {
loadContent();
}
@Override
public void contentsChanged(ListDataEvent e) {
loadContent();
}
private void loadContent() {
//get the current comboBoxModel and add the modelelements to content
ComboBoxModel<E> model = comboBox.getModel();
for (int i = 0; i < model.getSize(); i++) {
content.add(model.getElementAt(i));
}
}
@Override
public Object getItem() {
for (E p : content) {
if (representer.apply(p).equals(editor.getText())) {
return p;
}
}
return null;
}
@Override
@SuppressWarnings("unchecked")
public void setItem(Object anObject) {
editor.setText(representer.apply((E) anObject));
}
}

View File

@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.inputcomponents;
import static java.util.Objects.requireNonNull;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* A validator for the input of the textfield.
*/
public class EditableComboBoxListener<E>
implements
DocumentListener {
private final JTextField textField;
private final List<ValidationListener> validationListeners;
private final Set<E> content;
/**
* Returns the string representation for the combo box's selected item.
*/
private final Function<E, String> representer;
/**
* Creates an instance.
*
* @param content the elements of the comboBox' dropdownlist.
* @param validationListeners the listeners to be notified about the validity of the user input.
* @param textField the textfield with the input to be validated.
* @param representer Returns the string representation for the combo box's selected item.
*/
public EditableComboBoxListener(
Set<E> content,
List<ValidationListener> validationListeners,
JTextField textField,
Function<E, String> representer
) {
this.content = requireNonNull(content, "content");
this.validationListeners = requireNonNull(validationListeners, "validationListeners");
this.textField = requireNonNull(textField, "textField");
this.representer = requireNonNull(representer, "representer");
}
@Override
public void insertUpdate(DocumentEvent e) {
validate();
}
@Override
public void removeUpdate(DocumentEvent e) {
validate();
}
@Override
public void changedUpdate(DocumentEvent e) {
validate();
}
private void notifyValidationListeners(boolean isValid) {
for (ValidationListener valListener : validationListeners) {
valListener.validityChanged(new ValidationEvent(this, isValid));
}
}
private void validate() {
if (textField.getText().equals("")) {
notifyValidationListeners(true);
return;
}
for (E element : content) {
if (representer.apply(element).equals(textField.getText())) {
notifyValidationListeners(true);
return;
}
}
notifyValidationListeners(false);
}
}

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.3" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JDialogFormInfo">
<Properties>
<Property name="modal" type="boolean" value="true"/>
<Property name="resizable" type="boolean" value="false"/>
</Properties>
<SyntheticProperties>
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
<SyntheticProperty name="generateCenter" type="boolean" value="false"/>
</SyntheticProperties>
<Events>
<EventHandler event="windowClosing" listener="java.awt.event.WindowListener" parameters="java.awt.event.WindowEvent" handler="dialogClosing"/>
</Events>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="buttonPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
<EmptyBorder/>
</Border>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Last"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
<SubComponents>
<Component class="javax.swing.JButton" name="okButton">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="inputDialog.button_ok.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
<Property name="name" type="java.lang.String" value="inputDialogOkButton" noResource="true"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="okButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="cancelButton">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="inputDialog.button_cancel.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
<Property name="name" type="java.lang.String" value="inputDialogCancelButton" noResource="true"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cancelButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="resetButton">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/commadapter/loopback/Bundle.properties" key="inputDialog.button_reset.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
<Property name="name" type="java.lang.String" value="inputDialogResetButton" noResource="true"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="resetButtonActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@@ -0,0 +1,214 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.inputcomponents;
import java.awt.BorderLayout;
import javax.swing.border.EmptyBorder;
/**
* A generic dialog for user input. It has two or three buttons
* (<i>Ok</i>, <i>Cancel</i>, <i>reset</i>(optional)) and acts as a frame for an
* {@link InputPanel}, that must be provided when
* creating the dialog. <code>InputDialog</code> listens to
* <code>InputPanel</code>'s {@link ValidationEvent} to dynamically
* enable/disable the <i>ok</i>-button if the panel validates it's input.
*/
public class InputDialog
extends
javax.swing.JDialog
implements
ValidationListener {
/**
* The panel that contains the content of the dialog.
*/
private final InputPanel panel;
/**
* Return status of the dialog. Will be set when the dialog is beeing closed.
*/
private ReturnStatus returnStatus;
/**
* Enum values to indicate if the dialog accepted input from the user or
* if the input was canceled.
* TODO: no enum needed here?
*/
public enum ReturnStatus {
/**
* The Dialog was cancelled. No input available.
*/
CANCELED,
/**
* The input was accepted and is available.
*/
ACCEPTED
}
/**
* Create a new instance of <code>InputDialog</code>.
*
* @param panel the panel to be displayed in the dialog.
*/
@SuppressWarnings("this-escape")
public InputDialog(InputPanel panel) {
super();
initComponents();
setLocationRelativeTo(null);
// Set up embedded panel
this.panel = panel;
setTitle(panel.getTitle());
panel.setBorder(new EmptyBorder(6, 6, 10, 6));
// Setup dialog
getContentPane().add(panel, BorderLayout.CENTER);
getRootPane().setDefaultButton(okButton);
if (!panel.isResetable()) {
resetButton.setVisible(false);
}
pack();
// Init validation value manually
panel.addValidationListener(this);
}
@Override
public void validityChanged(ValidationEvent e) {
okButton.setEnabled(e.valid());
}
/**
* Get the return status of the dialog that indicates if there is input
* available via {@link #getInput()}.
* If the return status is {@link ReturnStatus#ACCEPTED ACCEPTED},
* the panels input was captured
* or reset and is available through {@link #getInput()}.
* If the return status is {@link ReturnStatus#CANCELED CANCELLED},
* {@link #getInput()} should <b>not</b>
* be called as the dialog was canceled and there is no valid input available.
* If the dialog wasn't closed yet, <code>null</code> will be returned.
*
* @return the return status
*/
public ReturnStatus getReturnStatus() {
return returnStatus;
}
/**
* Get the input from the embedded panel. This is the same as calling
* {@link InputPanel#getInput()} directly.
* Prior to calling this method you should check if there even is any input
* (see {@link #getReturnStatus()}).
*
* @return The input from the panel
*/
public Object getInput() {
return panel.getInput();
}
// FORMATTER:OFF
// CHECKSTYLE:OFF
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
buttonPanel = new javax.swing.JPanel();
okButton = new javax.swing.JButton();
cancelButton = new javax.swing.JButton();
resetButton = new javax.swing.JButton();
setModal(true);
setResizable(false);
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent evt) {
dialogClosing(evt);
}
});
buttonPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1));
okButton.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("i18n/org/opentcs/commadapter/loopback/Bundle"); // NOI18N
okButton.setText(bundle.getString("inputDialog.button_ok.text")); // NOI18N
okButton.setName("inputDialogOkButton"); // NOI18N
okButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
okButtonActionPerformed(evt);
}
});
buttonPanel.add(okButton);
cancelButton.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
cancelButton.setText(bundle.getString("inputDialog.button_cancel.text")); // NOI18N
cancelButton.setName("inputDialogCancelButton"); // NOI18N
cancelButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
cancelButtonActionPerformed(evt);
}
});
buttonPanel.add(cancelButton);
resetButton.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
resetButton.setText(bundle.getString("inputDialog.button_reset.text")); // NOI18N
resetButton.setName("inputDialogResetButton"); // NOI18N
resetButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
resetButtonActionPerformed(evt);
}
});
buttonPanel.add(resetButton);
getContentPane().add(buttonPanel, java.awt.BorderLayout.PAGE_END);
pack();
}// </editor-fold>//GEN-END:initComponents
// CHECKSTYLE:ON
// FORMATTER:ON
private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
doClose(ReturnStatus.CANCELED);
}//GEN-LAST:event_cancelButtonActionPerformed
private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
panel.captureInput();
doClose(ReturnStatus.ACCEPTED);
}//GEN-LAST:event_okButtonActionPerformed
private void resetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_resetButtonActionPerformed
panel.doReset();
doClose(ReturnStatus.ACCEPTED);
}//GEN-LAST:event_resetButtonActionPerformed
/**
* Handler for the WindowClosing event.
* Called when the user closes the dialog via the X-Button or F4.
*
* @param evt WindowEvent
*/
private void dialogClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_dialogClosing
doClose(ReturnStatus.CANCELED);
}//GEN-LAST:event_dialogClosing
/**
* Close the dialog properly and set the return status to indicate
* how/why it was closed.
*/
private void doClose(ReturnStatus retStatus) {
returnStatus = retStatus;
setVisible(false);
dispose();
}
// FORMATTER:OFF
// CHECKSTYLE:OFF
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel buttonPanel;
private javax.swing.JButton cancelButton;
private javax.swing.JButton okButton;
private javax.swing.JButton resetButton;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
}

View File

@@ -0,0 +1,197 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.inputcomponents;
import java.util.ArrayList;
import java.util.List;
/**
* Base class for panels that provide input methods for the user and can be
* embedded in an {@link InputDialog}.
*/
public abstract class InputPanel
extends
javax.swing.JPanel {
/**
* Object to store the user's input. Must be explicitly set via captureInput()
* or doReset(). What exactly this input is depends on the actual
* implementation of InputPanel's subclass.
*/
protected Object input;
/**
* If true, the panel's state/input can be reset via doReset() to a default
* value.
* By default <code>InputPanels</code> are not resetable.
*/
protected boolean resetable;
/**
* List of ValidationListeners that will receive ValidationEvents.
*/
private final List<ValidationListener> validationListeners = new ArrayList<>();
/**
* Title of the panel. Might be used by the surrounding dialog.
*/
private final String title;
/**
* Indicates whether the current user input is valid.
* If the panel validates the input this can be changed via setInputValid().
*/
private boolean inputValid = true;
/**
* Create a new instance of InputPanel.
*
* @param title The title of this panel.
*/
public InputPanel(String title) {
this.title = title;
}
/**
* Get the title of this panel.
*
* @return The title
*/
public String getTitle() {
return title;
}
/**
* Add a {@link ValidationListener} that will receive
* {@link ValidationEvent ValidationEvents}
* whenever the validity of the input in this panel changes. The
* {@link ValidationListener} will receive a {@link ValidationEvent} with the
* current validity state immediately after beeing added.
* If the panel does not validate it's input the validity will never change.
*
* @param listener The {@link ValidationListener}
*/
public void addValidationListener(ValidationListener listener) {
validationListeners.add(listener);
// Fire initial validation event for this listener
listener.validityChanged(new ValidationEvent(this, inputValid));
}
/**
* Mark the input of the panel as valid/invalid and send
* {@link ValidationEvent ValidationEvents}
* to the attached {@link ValidationListener ValidationListeners}.
* The Validity should only be changed via this method!
*
* @param valid true, if input is valid. false otherwise.
*/
protected void setInputValid(boolean valid) {
boolean changed = valid != inputValid;
inputValid = valid;
if (changed) {
ValidationEvent e = new ValidationEvent(this, valid);
for (ValidationListener l : validationListeners) {
l.validityChanged(e);
}
}
}
/**
*
* @return The validation Listeners.
*/
public List<ValidationListener> getValidationListeners() {
return validationListeners;
}
/**
* Determine if the current input in the panel is valid.
* If the input isn't validated this will always return <code>true</code>.
*
* @see #addValidationListener
* @return <code>true</code> if input is valid, <code>false</code> otherwise.
*/
public boolean isInputValid() {
return inputValid;
}
/**
* Get the user input from the panel. If the input wasn't captured before
* (see {@link #captureInput()}) null is returned. Otherwise it depends on the
* concrete implementing panel what the input can look like.
*
* @return The input
*/
public Object getInput() {
return input;
}
/**
* Tells the panel to get and store the user input which will be available
* via {@link #getInput()} afterwards.
* Usually this method should be called from the enclosing dialog when the
* ok button is pressed. It is not intended to be used by the user of the
* panel!
*/
protected abstract void captureInput();
/**
* Returns whether the content of this panel can be reset to a default value.
* If the panel is resetable the enclosing dialog might want to show a reset
* button.
*
* @see #doReset()
* @return panel is resetable?
*/
public boolean isResetable() {
return resetable;
}
/**
* Inform the panel, that it should reset it's input values (probably because
* the reset button in the enclosing dialog was pressed).
* It's up to the specific panel itself to decide what is resetted.´
* The default implementation does nothing.
* It should be overwritten in subclasses if reset functionality is needed.
*/
public void doReset() {
// Do nothing here.
}
/**
* <p>
* An interface that can be used to implement the builder-pattern
* (see Joshua Bloch's <i>Effective Java</i>).
* <br>
* As an <code>InputPanel</code> might have many required and/or optional
* parameters it can be more convinient to use a Builder class instead of
* public constructors.
* A builder should implement a public constructor with required parameters
* for the panel and public setters for optional parameters.
* The <code>InputPanel</code> is created by the {@link #build} method.
* <br>
* For an example implementation see {@link SingleTextInputPanel.Builder}.
* </p>
* <p>
* <b>Usage</b>:
* </p>
* <ol>
* <li>Instanciate the builder, passing required parameters to the
* constructor.</li>
* <li>Set optional parameters via the other public methods.</li>
* <li>Actually build the panel according to the previously specified
* parameters using the build() method.</li>
* </ol>
* <p>
* <b>Note</b>:
* The parameter methods should always return the builder itself, so the
* creation of a panel can be done in a single statement (see the
* <i>Builder-Pattern</i> in Joshua Bloch's <i>Effective Java</i>).
* </p>
*/
public interface Builder {
/**
* Finally build the {@link InputPanel} as described by this <code>Builder</code>.
*
* @return The created <code>InputPanel</code>.
*/
InputPanel build();
}
}

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.3" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="unitLabel">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="horizontalAlignment" type="int" value="2"/>
<Property name="text" type="java.lang.String" value="Unit-Label"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="2" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="label">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="text" type="java.lang.String" value="Label"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="inputField">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" value="initial Value"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[70, 20]"/>
</Property>
</Properties>
<Events>
<EventHandler event="focusGained" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="inputFieldFocusGained"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="messageLabel">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="text" type="java.lang.String" value="Message"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="3" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Form>

View File

@@ -0,0 +1,278 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.inputcomponents;
/**
* A general input panel with a text field and optionally:
* <ul>
* <li>a message/text</li>
* <li>a label</li>
* <li>a unit label for the input</li>
* </ul>
* The input of the text field can be validated
* (see {@link Builder#enableValidation enableValidation}).
* <br>
* For instanciation the contained
* {@link SingleTextInputPanel.Builder Builder}-class must be used.
* <br>
* The <code>Object</code> that is returned by {@link InputPanel#getInput} is
* a <code>String</code> (the text in the text field).
*/
public final class SingleTextInputPanel
extends
TextInputPanel {
/**
* If the panel is resetable this is the value the input is set to when
* doReset() is called.
*/
private Object resetValue;
/**
* Creates new instance of SingleTextInputPanel.
* The given title is not used in the panel itselft but can be used by
* the enclosing component.
*
* @param title The title of the panel.
*/
private SingleTextInputPanel(String title) {
super(title);
initComponents();
}
/**
* Enable input validation against the given regular expression.
*
* @see InputPanel#addValidationListener
* @param format A regular expression.
*/
private void enableInputValidation(String format) {
if (format != null) {
inputField.getDocument().addDocumentListener(new TextInputValidator(format));
}
}
@Override
protected void captureInput() {
input = inputField.getText();
}
@Override
public void doReset() {
input = resetValue;
}
// FORMATTER:OFF
// CHECKSTYLE:OFF
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
unitLabel = new javax.swing.JLabel();
label = new javax.swing.JLabel();
inputField = new javax.swing.JTextField();
messageLabel = new javax.swing.JLabel();
setLayout(new java.awt.GridBagLayout());
unitLabel.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
unitLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
unitLabel.setText("Unit-Label");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 2;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(unitLabel, gridBagConstraints);
label.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
label.setText("Label");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(label, gridBagConstraints);
inputField.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
inputField.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
inputField.setText("initial Value");
inputField.setPreferredSize(new java.awt.Dimension(70, 20));
inputField.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusGained(java.awt.event.FocusEvent evt) {
inputFieldFocusGained(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 1;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(inputField, gridBagConstraints);
messageLabel.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
messageLabel.setText("Message");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.gridwidth = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(messageLabel, gridBagConstraints);
}// </editor-fold>//GEN-END:initComponents
// CHECKSTYLE:ON
// FORMATTER:ON
private void inputFieldFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_inputFieldFocusGained
inputField.selectAll();
}//GEN-LAST:event_inputFieldFocusGained
// FORMATTER:OFF
// CHECKSTYLE:OFF
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTextField inputField;
private javax.swing.JLabel label;
private javax.swing.JLabel messageLabel;
private javax.swing.JLabel unitLabel;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
/**
* See {@link InputPanel.Builder}.
*/
public static class Builder
implements
InputPanel.Builder {
/**
* The panel's title.
*/
private final String title;
/**
* Label of for the text field.
*/
private String label;
/**
* Unit label of the text field.
*/
private String unitLabel;
/**
* Initial value for the text field.
*/
private String initialValue;
/**
* Message to be displayed in the panel.
*/
private String message;
/**
* Regex for validation of the text field's content.
*/
private String format;
/**
* Show a reset button in the panel.
* Default is <code>false</code>.
*/
private boolean resetButton;
/**
* Value the input is reset to when the reset button is used.
*/
private Object resetValue;
/**
* Create a new builder.
*
* @param title Title of the panel.
*/
public Builder(String title) {
this.title = title;
}
@Override
public InputPanel build() {
SingleTextInputPanel panel = new SingleTextInputPanel(title);
panel.enableInputValidation(format);
panel.label.setText(label);
panel.unitLabel.setText(unitLabel);
panel.inputField.setText(initialValue);
panel.messageLabel.setText(message);
panel.resetable = resetButton;
if (panel.resetable) {
panel.resetValue = resetValue;
}
return panel;
}
/**
* Set the label of the panel.
*
* @param label The Label
* @return the instance of this <code>Builder</code>
*/
public Builder setLabel(String label) {
this.label = label;
return this;
}
/**
* Set the initial value for the text field of the panel.
*
* @param initialValue the initial value
* @return the instance of this <code>Builder</code>
*/
public Builder setInitialValue(String initialValue) {
this.initialValue = initialValue;
return this;
}
/**
* Set the text for the unit label of the panel.
*
* @param unitLabel the unit
* @return the instance of this <code>Builder</code>
*/
public Builder setUnitLabel(String unitLabel) {
this.unitLabel = unitLabel;
return this;
}
/**
* Set the message of the panel.
* The user of this method must take care for the line breaks in the message,
* as it is not wrapped automatically!
*
* @param message the message
* @return the instance of this <code>Builder</code>
*/
public Builder setMessage(String message) {
this.message = message;
return this;
}
/**
* Make the panel validate it's input.
*
* @param format The regular expression that will be used for validation.
* @return the instance of this <code>Builder</code>
*/
public Builder enableValidation(String format) {
this.format = format;
return this;
}
/**
* Set a value the panel's input can be reset to.
*
* @param resetValue the reset value
* @return the instance of this <code>Builder</code>
*/
public Builder enableResetButton(Object resetValue) {
this.resetButton = true;
this.resetValue = resetValue;
return this;
}
}
}

View File

@@ -0,0 +1,176 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.inputcomponents;
import java.util.Objects;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract base class for <code>InputPanels</code> that use text fields for input.
* The main purpose of this class is to provide an easy to use way to validate
* text inputs using it's nested class {@link TextInputPanel.TextInputValidator
* TextInputValidator}.
*/
public abstract class TextInputPanel
extends
InputPanel {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(TextInputPanel.class);
/**
* Create a new instance of <code>TextInputPanel</code>.
*
* @param title The title of this panel.
*/
public TextInputPanel(String title) {
super(title);
}
/**
* <p>
* Mark the input of the specified <code>Document</code> as valid/invalid
* and send {@link ValidationEvent ValidationEvents} to the attached
* {@link ValidationListener ValidationListeners}.
* The <code>Document</code> should be related to an input component in this
* panel.
* </p>
* <p>
* <b>Note</b>:<br>
* The default implementation just forwards the call to
* {@link InputPanel#setInputValid(boolean)} without respect to the
* <code>Document</code>. Therefore subclasses with multiple Documents
* should overwrite this method to for example check if <b>all</b> input
* fields are valid and then decide if
* {@link InputPanel#setInputValid(boolean)} should be called or not.
* </p>
*
* @param valid true, if the content of the <code>Document</code> is valid
* @param doc the <code>Document</code>
*/
protected void setInputValid(boolean valid, Document doc) {
setInputValid(valid);
}
/**
* <p>
* A {@link javax.swing.event.DocumentListener DocumentListener} that can be
* used by subclasses of {@link TextInputPanel} to validate input in
* {@link javax.swing.JTextField JTextFields} and other Components that
* use {@link javax.swing.text.Document Documents}.
* It listens to the DocumentEvents of a
* <code>Document</code> and validates the Document's
* content against a specified regular expression. Some convenient regular
* expressions are provided as <code>public static</code> variables.
* After validation {@link TextInputPanel#setInputValid(boolean,
* javax.swing.text.Document)} is called.
* </p>
* <p>
* <b>Note:</b><br>
* The, for convenience, provided regular expressions do
* NOT check whether the given number really fits into the range of the
* corresponding data type (e.g. <code>int</code> for <code>REGEX_INT</code>).
* </p>
*
* @see TextInputPanel#setInputValid(boolean, javax.swing.text.Document)
*/
public class TextInputValidator
implements
DocumentListener {
/**
* Regular expression that accepts a floating point number of arbitary length.
* The decimal point and positions after it can be omitted.
* <p>
* Examples:
* </p>
* <ul>
* <li>3.0 is valid</li>
* <li>-3 is valid</li>
* <li>3. is invalid</li>
* <li>.3 is invalid</li>
* </ul>
*/
public static final String REGEX_FLOAT = "[-+]?[0-9]+(\\.[0-9]+)?";
/**
* Regular expression that accepts a positive floating point number of arbitrary length and 0.
*/
public static final String REGEX_FLOAT_POS = "\\+?[0-9]+(\\.[0-9]+)?";
/**
* Regular expression that accepts a negative floating point number of arbitrary length and 0.
*/
public static final String REGEX_FLOAT_NEG = "-[0-9]+(\\.[0-9]+)?|0+(\\.0+)?";
/**
* Regular expression that accepts any integer of arbitrary length.
*/
public static final String REGEX_INT = "[-+]?[0-9]+";
/**
* Regular expression that accepts any positive integer of arbitrary length and 0.
*/
public static final String REGEX_INT_POS = "\\+?[0-9]+";
/**
* Regular expression that accepts any negative integer of arbitrary length and 0.
*/
public static final String REGEX_INT_NEG = "-[0-9]+|0+";
/**
* Regular expression that accepts an integer in the interval [0,100].
*/
public static final String REGEX_INT_RANGE_0_100 = "[0-9]|[1-9][0-9]|100";
/**
* Regular expression that accepts anything except an empty (or whitespace-only) string.
*/
public static final String REGEX_NOT_EMPTY = ".*\\S.*";
/**
* Regular expression to validate the documents text against.
*/
private final String format;
/**
* Create an instance of <code>TextInputValidator</code>.
*
* @param format The regular expression to use for validation.
*/
protected TextInputValidator(String format) {
this.format = Objects.requireNonNull(format);
}
@Override
public void insertUpdate(DocumentEvent e) {
validate(e.getDocument());
}
@Override
public void removeUpdate(DocumentEvent e) {
validate(e.getDocument());
}
@Override
public void changedUpdate(DocumentEvent e) {
}
/**
* Validate the specified <code>Document</code> and set the validation
* state in the {@link InputPanel} accordingly.
*
* @param doc The <code>Document</code> to validate.
*/
private void validate(Document doc) {
String text;
try {
text = doc.getText(0, doc.getLength());
}
catch (BadLocationException e) {
LOG.warn("Exception retrieving document text", e);
return;
}
setInputValid(text.matches(format), doc);
}
}
}

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.3" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="messageLabel">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="text" type="java.lang.String" value="Message"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="inputField">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[70, 20]"/>
</Property>
</Properties>
<Events>
<EventHandler event="focusGained" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="inputFieldFocusGained"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Container class="javax.swing.JScrollPane" name="jScrollPane1">
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JList" name="list">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="selectionMode" type="int" value="0"/>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@@ -0,0 +1,231 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.inputcomponents;
import java.util.List;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
/**
* An input panel with a text field for user input as well as a list of predefined
* inputs to select from.
*/
public final class TextListInputPanel
extends
TextInputPanel {
/**
* Creates a new instance TextListInputPanel.
*
* @param title the title of the panel
*/
private TextListInputPanel(String title) {
super(title);
resetable = false;
initComponents();
list.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
Object selection = list.getSelectedValue();
if (selection != null) {
inputField.setText((String) selection);
}
}
}
});
}
@Override
protected void captureInput() {
input = inputField.getText();
}
/**
* Enable input validation against the given regular expression.
*
* @see InputPanel#addValidationListener
* @param format A regular expression.
*/
private void enableInputValidation(String format) {
if (format != null) {
inputField.getDocument().addDocumentListener(new TextInputValidator(format));
}
}
// FORMATTER:OFF
// CHECKSTYLE:OFF
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
messageLabel = new javax.swing.JLabel();
inputField = new javax.swing.JTextField();
jScrollPane1 = new javax.swing.JScrollPane();
list = new javax.swing.JList<String>();
setLayout(new java.awt.GridBagLayout());
messageLabel.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
messageLabel.setText("Message");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(messageLabel, gridBagConstraints);
inputField.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
inputField.setPreferredSize(new java.awt.Dimension(70, 20));
inputField.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusGained(java.awt.event.FocusEvent evt) {
inputFieldFocusGained(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(inputField, gridBagConstraints);
list.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
list.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
jScrollPane1.setViewportView(list);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
add(jScrollPane1, gridBagConstraints);
}// </editor-fold>//GEN-END:initComponents
// CHECKSTYLE:ON
// FORMATTER:ON
private void inputFieldFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_inputFieldFocusGained
inputField.selectAll();
}//GEN-LAST:event_inputFieldFocusGained
// CHECKSTYLE:OFF
// FORMATTER:OFF
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTextField inputField;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JList<String> list;
private javax.swing.JLabel messageLabel;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
/**
* See {@link InputPanel.Builder}.
*/
public static class Builder
implements
InputPanel.Builder {
/**
* The panel's title.
*/
private final String title;
/**
* The optional message.
*/
private String message;
/**
* Content for the dropdown list.
*/
private final List<String> content;
/**
* Regex for validation of the text field's content.
*/
private String format;
/**
* Initially selected index of the list.
* Default is 0.
*/
private int initialIndex;
/**
* Create a new <code>Builder</code>.
*
* @param title the title of the panel
* @param content Predefined items to display in the panel's list
*/
public Builder(String title, List<String> content) {
this.title = title;
this.content = content;
}
/**
* Set the message of the panel.
* The user of this method must take care for the line breaks in the message,
* as it is not wrapped automatically!
*
* @param message the message
* @return the instance of this <code>Builder</code>
*/
public Builder setMessage(String message) {
this.message = message;
return this;
}
/**
* Make the panel validate it's input.
*
* @param format The regular expression that will be used for validation.
* @return the instance of this <code>Builder</code>
*/
public Builder enableValidation(String format) {
this.format = format;
return this;
}
/**
* Set the initial selected list entry.
*
* @param index must be &gt; 0, will have no effect otherwise
* @return the instance of this <code>Builder</code>
*/
public Builder setInitialSelection(int index) {
if (index >= 0) {
initialIndex = index;
}
return this;
}
/**
* Set the initial selected list entry.
*
* @param element Element to select. Selection remains unchanged if
* element ist not in the list or element is
* <code>null</code>.
* @return the instance fo this <code>Builder</code>
*/
public Builder setInitialSelection(String element) {
int index;
try {
index = content.indexOf(element);
}
catch (NullPointerException e) {
index = -1;
}
return setInitialSelection(index);
}
@Override
public TextListInputPanel build() {
TextListInputPanel panel = new TextListInputPanel(title);
panel.enableInputValidation(format);
panel.messageLabel.setText(message);
panel.list.setListData(content.toArray(new String[content.size()]));
panel.list.setSelectedIndex(initialIndex);
return panel;
}
}
}

View File

@@ -0,0 +1,186 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.3" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="unitLabel3">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="horizontalAlignment" type="int" value="2"/>
<Property name="text" type="java.lang.String" value="Unit-Label"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="2" gridY="3" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="label3">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="text" type="java.lang.String" value="Label"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="inputField3">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" value="initial Value"/>
<Property name="name" type="java.lang.String" value="inputField3" noResource="true"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[70, 20]"/>
</Property>
</Properties>
<AccessibilityProperties>
<Property name="AccessibleContext.accessibleName" type="java.lang.String" value=""/>
</AccessibilityProperties>
<Events>
<EventHandler event="focusGained" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="inputField3FocusGained"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="messageLabel">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="text" type="java.lang.String" value="Message"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="3" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="label1">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="text" type="java.lang.String" value="Label"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="inputField1">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" value="initial Value"/>
<Property name="name" type="java.lang.String" value="inputField1" noResource="true"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[70, 20]"/>
</Property>
</Properties>
<AccessibilityProperties>
<Property name="AccessibleContext.accessibleName" type="java.lang.String" value=""/>
</AccessibilityProperties>
<Events>
<EventHandler event="focusGained" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="inputField1FocusGained"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="unitLabel1">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="horizontalAlignment" type="int" value="2"/>
<Property name="text" type="java.lang.String" value="Unit-Label"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="2" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="label2">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="text" type="java.lang.String" value="Label"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="inputField2">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" value="initial Value"/>
<Property name="name" type="java.lang.String" value="inputField2" noResource="true"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[70, 20]"/>
</Property>
</Properties>
<AccessibilityProperties>
<Property name="AccessibleContext.accessibleName" type="java.lang.String" value=""/>
</AccessibilityProperties>
<Events>
<EventHandler event="focusGained" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="inputField2FocusGained"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="unitLabel2">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="11" style="0"/>
</Property>
<Property name="horizontalAlignment" type="int" value="2"/>
<Property name="text" type="java.lang.String" value="Unit-Label"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="2" gridY="2" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="3" insetsBottom="3" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Form>

View File

@@ -0,0 +1,498 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.inputcomponents;
import javax.swing.text.Document;
/**
* General input panel with three text fields and optionally:
* <ul>
* <li>a message/text</li>
* <li>labels for the text fields</li>
* <li>unit labels for the text fields</li>
* </ul>
* The input of the text fields can be validated
* (see {@link Builder#enableValidation enableValidation}).
* For instanciation the contained
* {@link TripleTextInputPanel.Builder Builder}-class must be used.
* The <code>Object</code> that is returned by {@link InputPanel#getInput} is
* an <code>Array</code> of three <code>Strings</code>.
*/
public final class TripleTextInputPanel
extends
TextInputPanel {
/**
* If the panel is resetable this is the value the input is set to when
* doReset() is called.
*/
private Object resetValue;
/**
* Flag indicating if the content of the first text field is a valid input.
*/
private boolean inputField1Valid = true;
/**
* Flag indicating if the content of the second text field is a valid input.
*/
private boolean inputField2Valid = true;
/**
* Flag indicating if the content of the third text field is a valid input.
*/
private boolean inputField3Valid = true;
/**
* Create a new <code>TripleTextInputPanel</code>.
*
* @param title Title of the panel
*/
private TripleTextInputPanel(String title) {
super(title);
initComponents();
}
/**
* Enable input validation against the given regular expressions.
* If a format string is <code>null</code> the related text field is not
* validated.
*
* @see InputPanel#addValidationListener
* @param format1 A regular expression for the first text field.
* @param format2 A regular expression for the second text field.
* @param format3 A regular expression for the third text field.
*/
private void enableInputValidation(
String format1,
String format2,
String format3
) {
if (format1 != null) {
inputField1.getDocument().addDocumentListener(new TextInputValidator(format1));
}
if (format2 != null) {
inputField2.getDocument().addDocumentListener(new TextInputValidator(format2));
}
if (format3 != null) {
inputField3.getDocument().addDocumentListener(new TextInputValidator(format3));
}
}
@Override
protected void captureInput() {
input = new String[]{
inputField1.getText(),
inputField2.getText(),
inputField3.getText()
};
}
@Override
public void doReset() {
input = resetValue;
}
/**
* Mark the input of the specified <code>Document</code> as valid, if this
* Document belongs to one of the three text fields in this panel.
* If <code>valid</code> is <code>true</code> the validity of the other
* two documents is checked, too. Only if all three text fields contain valid
* input, the whole input of the panel is marked as valid.
*
* @see TextInputPanel#setInputValid(boolean, javax.swing.text.Document)
* @param valid true, if the content of the <code>Document</code> is valid
* @param doc the <code>Document</code>
*/
@Override
protected void setInputValid(boolean valid, Document doc) {
// Find out to which input field the document belongs and check the others
boolean allValid = valid;
if (doc == inputField1.getDocument()) {
inputField1Valid = valid;
if (!(inputField2Valid && inputField3Valid)) {
allValid = false;
}
}
else if (doc == inputField2.getDocument()) {
inputField2Valid = valid;
if (!(inputField1Valid && inputField3Valid)) {
allValid = false;
}
}
else if (doc == inputField3.getDocument()) {
inputField3Valid = valid;
if (!(inputField1Valid && inputField2Valid)) {
allValid = false;
}
}
else {
allValid = false;
}
setInputValid(allValid);
}
// FORMATTER:OFF
// CHECKSTYLE:OFF
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
unitLabel3 = new javax.swing.JLabel();
label3 = new javax.swing.JLabel();
inputField3 = new javax.swing.JTextField();
messageLabel = new javax.swing.JLabel();
label1 = new javax.swing.JLabel();
inputField1 = new javax.swing.JTextField();
unitLabel1 = new javax.swing.JLabel();
label2 = new javax.swing.JLabel();
inputField2 = new javax.swing.JTextField();
unitLabel2 = new javax.swing.JLabel();
setLayout(new java.awt.GridBagLayout());
unitLabel3.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
unitLabel3.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
unitLabel3.setText("Unit-Label");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 2;
gridBagConstraints.gridy = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(unitLabel3, gridBagConstraints);
label3.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
label3.setText("Label");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 3;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(label3, gridBagConstraints);
inputField3.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
inputField3.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
inputField3.setText("initial Value");
inputField3.setName("inputField3"); // NOI18N
inputField3.setPreferredSize(new java.awt.Dimension(70, 20));
inputField3.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusGained(java.awt.event.FocusEvent evt) {
inputField3FocusGained(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 3;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(inputField3, gridBagConstraints);
inputField3.getAccessibleContext().setAccessibleName("");
messageLabel.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
messageLabel.setText("Message");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.gridwidth = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(messageLabel, gridBagConstraints);
label1.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
label1.setText("Label");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(label1, gridBagConstraints);
inputField1.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
inputField1.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
inputField1.setText("initial Value");
inputField1.setName("inputField1"); // NOI18N
inputField1.setPreferredSize(new java.awt.Dimension(70, 20));
inputField1.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusGained(java.awt.event.FocusEvent evt) {
inputField1FocusGained(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 1;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(inputField1, gridBagConstraints);
inputField1.getAccessibleContext().setAccessibleName("");
unitLabel1.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
unitLabel1.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
unitLabel1.setText("Unit-Label");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 2;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(unitLabel1, gridBagConstraints);
label2.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
label2.setText("Label");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(label2, gridBagConstraints);
inputField2.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
inputField2.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
inputField2.setText("initial Value");
inputField2.setName("inputField2"); // NOI18N
inputField2.setPreferredSize(new java.awt.Dimension(70, 20));
inputField2.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusGained(java.awt.event.FocusEvent evt) {
inputField2FocusGained(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 2;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(inputField2, gridBagConstraints);
inputField2.getAccessibleContext().setAccessibleName("");
unitLabel2.setFont(new java.awt.Font("Arial", 0, 11)); // NOI18N
unitLabel2.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
unitLabel2.setText("Unit-Label");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 2;
gridBagConstraints.gridy = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
add(unitLabel2, gridBagConstraints);
}// </editor-fold>//GEN-END:initComponents
// CHECKSTYLE:ON
// FORMATTER:ON
private void inputField1FocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_inputField1FocusGained
inputField1.selectAll();
}//GEN-LAST:event_inputField1FocusGained
private void inputField2FocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_inputField2FocusGained
inputField2.selectAll();
}//GEN-LAST:event_inputField2FocusGained
private void inputField3FocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_inputField3FocusGained
inputField3.selectAll();
}//GEN-LAST:event_inputField3FocusGained
// FORMATTER:OFF
// CHECKSTYLE:OFF
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTextField inputField1;
private javax.swing.JTextField inputField2;
private javax.swing.JTextField inputField3;
private javax.swing.JLabel label1;
private javax.swing.JLabel label2;
private javax.swing.JLabel label3;
private javax.swing.JLabel messageLabel;
private javax.swing.JLabel unitLabel1;
private javax.swing.JLabel unitLabel2;
private javax.swing.JLabel unitLabel3;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
/**
* See {@link InputPanel.Builder}.
*/
public static class Builder
implements
InputPanel.Builder {
/**
* The panel's title.
*/
private final String title;
/**
* Message to be displayed in the panel.
*/
private String message;
/**
* Labels of for the text fields.
*/
private final String[] labels = new String[3];
/**
* Unit labels of the text fields.
*/
private final String[] unitLabels = new String[3];
/**
* Initial values for the text fields.
*/
private final String[] initialValues = new String[3];
/**
* Regular expressions for validation of the text field contents.
*/
private final String[] formats = new String[3];
/**
* Show a reset button in the panel.
* Default is <code>false</code>.
*/
private boolean resetButton;
/**
* Value the input is reset to when the reset button is used.
*/
private Object resetValue;
/**
* Create a new builder.
*
* @param title Title of the panel.
*/
public Builder(String title) {
this.title = title;
}
@Override
public InputPanel build() {
TripleTextInputPanel panel = new TripleTextInputPanel(title);
panel.enableInputValidation(formats[0], formats[1], formats[2]);
panel.label1.setText(labels[0]);
panel.label2.setText(labels[1]);
panel.label3.setText(labels[2]);
panel.unitLabel1.setText(unitLabels[0]);
panel.unitLabel2.setText(unitLabels[1]);
panel.unitLabel3.setText(unitLabels[2]);
panel.inputField1.setText(initialValues[0]);
panel.inputField2.setText(initialValues[1]);
panel.inputField3.setText(initialValues[2]);
panel.messageLabel.setText(message);
panel.resetable = resetButton;
if (panel.resetable) {
panel.resetValue = resetValue;
}
return panel;
}
/**
* Set the labels for the text fields.
* Passing <code>null</code> means there should not be such a label.
*
* @param label1 The label of the first text field
* @param label2 The label of the second text field
* @param label3 The label of the third text field
* @return the instance of this <code>Builder</code>
*/
public Builder setLabels(String label1, String label2, String label3) {
labels[0] = label1;
labels[1] = label2;
labels[2] = label3;
return this;
}
/**
* Set the same initial value for all three text fields.
*
* @param value The initial value.
* @return the isntance of this <code>Builder</code>
*/
public Builder setInitialValues(String value) {
return setInitialValues(value, value, value);
}
/**
* Set the initial values for the text fields of the panel
* Passing <code>null</code> means there should not be an initial value
* for this text field.
*
* @param val1 The initial value of the first text field
* @param val2 The initial value of the second text field
* @param val3 The initial value of the third text field
* @return the instance of this <code>Builder</code>
*/
public Builder setInitialValues(String val1, String val2, String val3) {
initialValues[0] = val1;
initialValues[1] = val2;
initialValues[2] = val3;
return this;
}
/**
* Set the same text for the unit label for all three text fields.
*
* @param unit The unit
* @return the isntance of this <code>Builder</code>
*/
public Builder setUnitLabels(String unit) {
return setUnitLabels(unit, unit, unit);
}
/**
* Set the text for the unit labels of the panel.
* Passing <code>null</code> means there should not be a unit label for
* the corresponding text field.
*
* @param unit1 The unit of the first text field
* @param unit2 The unit of the second text field
* @param unit3 The unit of the third text field
* @return the instance of this <code>Builder</code>
*/
public Builder setUnitLabels(String unit1, String unit2, String unit3) {
unitLabels[0] = unit1;
unitLabels[1] = unit2;
unitLabels[2] = unit3;
return this;
}
/**
* Set the message of the panel.
* The user of this method must take care for the line breaks in the message,
* as it is not wrapped automatically!
*
* @param message the message
* @return the instance of this <code>Builder</code>
*/
public Builder setMessage(String message) {
this.message = message;
return this;
}
public Builder enableValidation(String format) {
return enableValidation(format, format, format);
}
/**
* Make the panel validate it's input by using the specified regular
* expressions for the text fields.
* Passing null as an argument means that the corresponding text field
* should not be validated.
*
* @param format1 The regular expression that will be used for validation in
* the first text field.
* @param format2 The regular expression that will be used for validation in
* the second text field.
* @param format3 The regular expression that will be used for validation in
* the third text field.
* @return the instance of this <code>Builder</code>
*/
public Builder enableValidation(
String format1,
String format2,
String format3
) {
formats[0] = format1;
formats[1] = format2;
formats[2] = format3;
return this;
}
/**
* Set a value the panel's input can be reset to.
*
* @param resetValue the reset value
* @return the instance of this <code>Builder</code>
*/
public Builder enableResetButton(Object resetValue) {
this.resetButton = true;
this.resetValue = resetValue;
return this;
}
}
}

View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.inputcomponents;
import java.util.EventObject;
/**
* An event holding a single boolean variable indicating if something is valid.
*/
public class ValidationEvent
extends
EventObject {
/**
* Validation state.
*/
private final boolean valid;
/**
* Create a new <code>ValidationEvent</code>.
*
* @param source The source of the event.
* @param valid The state of validation that should be reported by this event.
*/
public ValidationEvent(Object source, boolean valid) {
super(source);
this.valid = valid;
}
/**
* Return the state of validation.
*
* @return valid
*/
public boolean valid() {
return valid;
}
}

View File

@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle.inputcomponents;
import java.util.EventListener;
/**
* A listener interface for {@link ValidationEvent ValidationEvents}.
*/
public interface ValidationListener
extends
EventListener {
/**
* Should be called when the state of validation changed.
*
* @param e The ValidationEvent containing validation information.
*/
void validityChanged(ValidationEvent e);
}

View File

@@ -0,0 +1,6 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
/**
* Generic GUI components for input dialogs.
*/
package org.opentcs.virtualvehicle.inputcomponents;

View File

@@ -0,0 +1,6 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
/**
* Classes for emulating the behaviour of a physical vehicle.
*/
package org.opentcs.virtualvehicle;

View File

@@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: The openTCS Authors
# SPDX-License-Identifier: CC0-1.0
version = 1
[[annotations]]
path = ["**/*.gif", "**/*.jpg", "**/*.png", "**/*.svg"]
precedence = "closest"
SPDX-FileCopyrightText = "The openTCS Authors"
SPDX-License-Identifier = "CC-BY-4.0"

View File

@@ -0,0 +1,44 @@
# SPDX-FileCopyrightText: The openTCS Authors
# SPDX-License-Identifier: CC-BY-4.0
inputDialog.button_cancel.text=Cancel
inputDialog.button_ok.text=Ok
inputDialog.button_reset.text=Reset
loopbackCommAdapterPanel.accessibleName=Loopback options
loopbackCommAdapterPanel.button_dispatchEvent.text=Dispatch event
loopbackCommAdapterPanel.button_failCurrentCommand.text=Fail current command
loopbackCommAdapterPanel.button_nextStep.text=Next step
loopbackCommAdapterPanel.button_setProperty.text = Apply
loopbackCommAdapterPanel.checkBox_commandProcessingAutomatic.text=Automatic mode
loopbackCommAdapterPanel.checkBox_commandProcessingManual.text=Single step/manual mode
loopbackCommAdapterPanel.checkBox_enableAdapter.text=Enable loopback adapter
loopbackCommAdapterPanel.checkBox_includeAppendix.text=Include message as appendix:
loopbackCommAdapterPanel.dialog_setEnergyLevel.title=Set energy level
loopbackCommAdapterPanel.dialog_setOrientationAngle.title=Set orientation angle
loopbackCommAdapterPanel.dialog_setPosition.title=Set vehicle position
loopbackCommAdapterPanel.dialog_setPrecisePosition.title=Set precise position
loopbackCommAdapterPanel.dialog_setState.title=Set vehicle state
loopbackCommAdapterPanel.label_energyLevel.text=Energy level:
loopbackCommAdapterPanel.label_maximumAcceleration.text=Acceleration:
loopbackCommAdapterPanel.label_maximumDeceleration.text=Deceleration:
loopbackCommAdapterPanel.label_maximumForwardVelocity.text=Max. forward velocity:
loopbackCommAdapterPanel.label_maximumReverseVelocity.text=Max. reverse velocity:
loopbackCommAdapterPanel.label_operatingTime.text=Operating time:
loopbackCommAdapterPanel.label_orientationAngle.text=Orientation:
loopbackCommAdapterPanel.label_pauseVehicle.text=Pause vehicle:
loopbackCommAdapterPanel.label_position.text=Position:
loopbackCommAdapterPanel.label_precisePosition.text=Precise position:
loopbackCommAdapterPanel.label_propertyKey.text=Key:
loopbackCommAdapterPanel.label_state.text=State:
loopbackCommAdapterPanel.panel_adapterStatus.border.title=Adapter status
loopbackCommAdapterPanel.panel_commandProcessing.border.title=Command processing
loopbackCommAdapterPanel.panel_eventDispatching.title=Event dispatching
loopbackCommAdapterPanel.panel_loadHandlingDevice.border.title=Load handling device
loopbackCommAdapterPanel.panel_vehicleProperties.border.title=Vehicle properties
loopbackCommAdapterPanel.panel_vehicleProperty.border.title = Vehicle property
loopbackCommAdapterPanel.panel_vehicleStatus.border.title=Current position/state
loopbackCommAdapterPanel.radioButton_removeProperty.text=Remove property
loopbackCommAdapterPanel.radioButton_setProperty.text=Set this value:
loopbackCommAdapterPanel.textArea_precisePosition.positionNotSetPlaceholder=-
loopbackCommAdapterPanel.textField_orientationAngle.angleNotSetPlaceholder=-
loopbackCommunicationAdapterDescription.description=Loopback adapter (virtual vehicle)

View File

@@ -0,0 +1,43 @@
# SPDX-FileCopyrightText: The openTCS Authors
# SPDX-License-Identifier: CC-BY-4.0
inputDialog.button_cancel.text=Abbrechen
inputDialog.button_ok.text=Ok
inputDialog.button_reset.text=Zur\u00fccksetzen
loopbackCommAdapterPanel.accessibleName=Loopback-Optionen
loopbackCommAdapterPanel.button_dispatchEvent.text=Event versenden
loopbackCommAdapterPanel.button_nextStep.text=N\u00e4chster Schritt
loopbackCommAdapterPanel.button_setProperty.text = Anwenden
loopbackCommAdapterPanel.checkBox_commandProcessingAutomatic.text=Automatik
loopbackCommAdapterPanel.checkBox_commandProcessingManual.text=Manuell
loopbackCommAdapterPanel.checkBox_enableAdapter.text=Loopback-Treiber einschalten
loopbackCommAdapterPanel.checkBox_includeAppendix.text=Nachricht als Anhang hinzuf\u00fcgen:
loopbackCommAdapterPanel.dialog_setEnergyLevel.title=Energie-Level setzen
loopbackCommAdapterPanel.dialog_setOrientationAngle.title=Orientierung setzen
loopbackCommAdapterPanel.dialog_setPosition.title=Fahrzeug-Position setzen
loopbackCommAdapterPanel.dialog_setPrecisePosition.title=Exakte Position setzen
loopbackCommAdapterPanel.dialog_setState.title=Fahrzeug-Zustand setzen
loopbackCommAdapterPanel.label_energyLevel.text=Energie-Level:
loopbackCommAdapterPanel.label_maximumAcceleration.text=Beschleunigung:
loopbackCommAdapterPanel.label_maximumDeceleration.text=Abbremsung:
loopbackCommAdapterPanel.label_maximumForwardVelocity.text=Max. Vorw\u00e4rts-Geschwindigkeit:
loopbackCommAdapterPanel.label_maximumReverseVelocity.text=Max. R\u00fcckw\u00e4rts-Geschwindigkeit:
loopbackCommAdapterPanel.label_operatingTime.text=Operationsdauer
loopbackCommAdapterPanel.label_orientationAngle.text=Orientierung:
loopbackCommAdapterPanel.label_pauseVehicle.text=Fahrzeug anhalten:
loopbackCommAdapterPanel.label_position.text=Position:
loopbackCommAdapterPanel.label_precisePosition.text=Exakte Position:
loopbackCommAdapterPanel.label_propertyKey.text = Schl\u00fcssel:
loopbackCommAdapterPanel.label_state.text=Zustand:
loopbackCommAdapterPanel.panel_adapterStatus.border.title=Adapterzustand
loopbackCommAdapterPanel.panel_commandProcessing.border.title=Kommandoverarbeitung
loopbackCommAdapterPanel.panel_eventDispatching.title=Event-Versand
loopbackCommAdapterPanel.panel_loadHandlingDevice.border.title=Lastaufnahmemittel
loopbackCommAdapterPanel.panel_vehicleProperties.border.title=Fahrzeugeigenschaften
loopbackCommAdapterPanel.panel_vehicleProperty.border.title = Fahrzeug-Property
loopbackCommAdapterPanel.panel_vehicleStatus.border.title=Aktuelle Position
loopbackCommAdapterPanel.radioButton_removeProperty.text=Property l\u00f6schen
loopbackCommAdapterPanel.radioButton_setProperty.text=Diesen Wert setzen:
loopbackCommAdapterPanel.textArea_precisePosition.positionNotSetPlaceholder=-
loopbackCommAdapterPanel.textField_orientationAngle.angleNotSetPlaceholder=-
loopbackCommunicationAdapterDescription.description=Loopback-Treiber (virtuelles Fahrzeug)

Binary file not shown.

After

Width:  |  Height:  |  Size: 957 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,229 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) --><svg height="48" id="svg2" inkscape:export-filename="/home/jimmac/Desktop/drive-harddisk.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90" inkscape:output_extension="org.inkscape.output.svg.inkscape" inkscape:version="0.45" sodipodi:docbase="/home/dobey/Projects/gnome-icon-theme/scalable/actions" sodipodi:docname="document-save.svg" sodipodi:version="0.32" version="1.0" width="48" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<metadata>
<rdf:RDF xmlns:cc="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<cc:Work rdf:about="">
<dc:title>Jakub Steiner, Lapo Calamandrei</dc:title>
<dc:description></dc:description>
<dc:subject>
<rdf:Bag>
<rdf:li>hdd</rdf:li>
<rdf:li>drive</rdf:li>
<rdf:li>fixed</rdf:li>
<rdf:li>media</rdf:li>
<rdf:li>hard</rdf:li>
</rdf:Bag>
</dc:subject>
<dc:publisher>
<cc:Agent rdf:about="http://www.openclipart.org/">
<dc:title>Open Clip Art Library, Source: GNOME Icon Theme, Source: GNOME Icon Theme, Source: GNOME Icon Theme, Source: GNOME Icon Theme</dc:title>
</cc:Agent>
</dc:publisher>
<dc:creator>
<cc:Agent>
<dc:title>Jakub Steiner, Lapo Calamandrei</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>Jakub Steiner, Lapo Calamandrei</dc:title>
</cc:Agent>
</dc:rights>
<dc:date></dc:date>
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<cc:license rdf:resource="http://creativecommons.org/licenses/GPL/2.0/"/>
<dc:language>en</dc:language>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs4">
<linearGradient id="linearGradient7781" inkscape:collect="always">
<stop id="stop7783" offset="0" style="stop-color:#ffffff;stop-opacity:1;"/>
<stop id="stop7785" offset="1" style="stop-color:#ffffff;stop-opacity:0;"/>
</linearGradient>
<linearGradient id="linearGradient6715" inkscape:collect="always">
<stop id="stop6717" offset="0" style="stop-color:#000000;stop-opacity:1;"/>
<stop id="stop6719" offset="1" style="stop-color:#000000;stop-opacity:0;"/>
</linearGradient>
<linearGradient id="linearGradient6549">
<stop id="stop6551" offset="0" style="stop-color:#8ae234;stop-opacity:1"/>
<stop id="stop6553" offset="1" style="stop-color:#73d216;stop-opacity:1"/>
</linearGradient>
<linearGradient id="linearGradient2916" inkscape:collect="always">
<stop id="stop2918" offset="0" style="stop-color:white;stop-opacity:1;"/>
<stop id="stop2920" offset="1" style="stop-color:white;stop-opacity:0;"/>
</linearGradient>
<linearGradient id="linearGradient2902" inkscape:collect="always">
<stop id="stop2905" offset="0" style="stop-color:black;stop-opacity:1;"/>
<stop id="stop2907" offset="1" style="stop-color:black;stop-opacity:0;"/>
</linearGradient>
<linearGradient id="linearGradient2986" inkscape:collect="always">
<stop id="stop2988" offset="0" style="stop-color:black;stop-opacity:1;"/>
<stop id="stop2990" offset="1" style="stop-color:black;stop-opacity:0;"/>
</linearGradient>
<linearGradient id="linearGradient2967" inkscape:collect="always">
<stop id="stop2969" offset="0" style="stop-color:white;stop-opacity:1;"/>
<stop id="stop2971" offset="1" style="stop-color:white;stop-opacity:0;"/>
</linearGradient>
<linearGradient id="linearGradient2064">
<stop id="stop2066" offset="0" style="stop-color:white;stop-opacity:1;"/>
<stop id="stop2070" offset="0.5" style="stop-color:#555753;stop-opacity:0.60000002;"/>
<stop id="stop2068" offset="1" style="stop-color:#555753;stop-opacity:0;"/>
</linearGradient>
<linearGradient id="linearGradient9641">
<stop id="stop9643" offset="0" style="stop-color:white;stop-opacity:1"/>
<stop id="stop9645" offset="1" style="stop-color:#888a85;stop-opacity:1"/>
</linearGradient>
<linearGradient id="linearGradient9633">
<stop id="stop9635" offset="0" style="stop-color:#eeeeec;stop-opacity:1"/>
<stop id="stop9639" offset="1" style="stop-color:#888a85;stop-opacity:1"/>
</linearGradient>
<linearGradient id="linearGradient9613">
<stop id="stop9615" offset="0" style="stop-color:white;stop-opacity:1"/>
<stop id="stop9619" offset="0.5" style="stop-color:white;stop-opacity:1;"/>
<stop id="stop9617" offset="1" style="stop-color:#cccfca;stop-opacity:1"/>
</linearGradient>
<linearGradient id="linearGradient8710">
<stop id="stop8712" offset="0" style="stop-color:black;stop-opacity:1;"/>
<stop id="stop8714" offset="1" style="stop-color:white;stop-opacity:1;"/>
</linearGradient>
<linearGradient id="linearGradient8631">
<stop id="stop8633" offset="0" style="stop-color:#eeeeec;stop-opacity:1"/>
<stop id="stop8637" offset="0.2" style="stop-color:#eeeeec;stop-opacity:1;"/>
<stop id="stop8635" offset="1" style="stop-color:#babdb6;stop-opacity:1"/>
</linearGradient>
<linearGradient id="linearGradient8625">
<stop id="stop8627" offset="0" style="stop-color:white;stop-opacity:1"/>
<stop id="stop8629" offset="1" style="stop-color:#babdb6;stop-opacity:1"/>
</linearGradient>
<linearGradient id="linearGradient8613">
<stop id="stop8615" offset="0" style="stop-color:#babdb6;stop-opacity:1"/>
<stop id="stop8617" offset="1" style="stop-color:#2e3436;stop-opacity:1"/>
</linearGradient>
<linearGradient id="linearGradient5740">
<stop id="stop5742" offset="0" style="stop-color:#d0d0cb;stop-opacity:1;"/>
<stop id="stop5744" offset="1" style="stop-color:#babdb6;stop-opacity:1"/>
</linearGradient>
<linearGradient id="linearGradient5690">
<stop id="stop5692" offset="0" style="stop-color:white;stop-opacity:1;"/>
<stop id="stop5694" offset="1" style="stop-color:#888a85;stop-opacity:0.59848487"/>
</linearGradient>
<linearGradient id="linearGradient2899">
<stop id="stop2901" offset="0" style="stop-color:#555753;stop-opacity:1"/>
<stop id="stop2903" offset="1" style="stop-color:#2e3436;stop-opacity:1"/>
</linearGradient>
<linearGradient id="linearGradient3468">
<stop id="stop3470" offset="0" style="stop-color:#fdfdfc;stop-opacity:1"/>
<stop id="stop3472" offset="1" style="stop-color:white;stop-opacity:0.37121212"/>
</linearGradient>
<linearGradient id="linearGradient2909">
<stop id="stop2911" offset="0" style="stop-color:white;stop-opacity:0;"/>
<stop id="stop2917" offset="0.5" style="stop-color:white;stop-opacity:1;"/>
<stop id="stop2913" offset="1" style="stop-color:white;stop-opacity:0;"/>
</linearGradient>
<linearGradient id="linearGradient2839">
<stop id="stop2841" offset="0" style="stop-color:white;stop-opacity:0.25773194;"/>
<stop id="stop2847" offset="0.5472973" style="stop-color:white;stop-opacity:1;"/>
<stop id="stop2849" offset="0.66243607" style="stop-color:white;stop-opacity:0.24705882;"/>
<stop id="stop2851" offset="0.875" style="stop-color:white;stop-opacity:0.83505154;"/>
<stop id="stop2843" offset="1" style="stop-color:white;stop-opacity:0;"/>
</linearGradient>
<linearGradient id="linearGradient2900">
<stop id="stop2902" offset="0" style="stop-color:black;stop-opacity:0;"/>
<stop id="stop2908" offset="0.5" style="stop-color:black;stop-opacity:1;"/>
<stop id="stop2904" offset="1" style="stop-color:black;stop-opacity:0;"/>
</linearGradient>
<linearGradient id="linearGradient2886" inkscape:collect="always">
<stop id="stop2888" offset="0" style="stop-color:black;stop-opacity:1;"/>
<stop id="stop2890" offset="1" style="stop-color:black;stop-opacity:0;"/>
</linearGradient>
<linearGradient gradientTransform="matrix(1,0,0,0.995556,0,1.068887)" gradientUnits="userSpaceOnUse" id="linearGradient3474" inkscape:collect="always" x1="24.748737" x2="24.998737" xlink:href="#linearGradient3468" y1="35.354588" y2="14.997767"/>
<radialGradient cx="0" cy="17" fx="0" fy="17" gradientTransform="matrix(4.095822,0,0,3.101282,-9.53921,-94.5433)" gradientUnits="userSpaceOnUse" id="radialGradient4700" inkscape:collect="always" r="2" xlink:href="#linearGradient2886"/>
<radialGradient cx="0" cy="17" fx="0" fy="17" gradientTransform="matrix(4.095822,0,0,3.101282,38.20996,-10.90025)" gradientUnits="userSpaceOnUse" id="radialGradient4702" inkscape:collect="always" r="2" xlink:href="#linearGradient2886"/>
<linearGradient gradientTransform="matrix(2.047911,0,0,2.067521,1.347566,6.673675)" gradientUnits="userSpaceOnUse" id="linearGradient4704" inkscape:collect="always" x1="9.8994951" x2="9.8994951" xlink:href="#linearGradient2900" y1="20" y2="13.979153"/>
<linearGradient gradientTransform="matrix(1,0,0,1.42294,10.5,-14.95703)" gradientUnits="userSpaceOnUse" id="linearGradient4711" inkscape:collect="always" x1="15.335379" x2="20.329321" xlink:href="#linearGradient2909" y1="33.06237" y2="36.37693"/>
<linearGradient gradientTransform="matrix(1,0,0,1.42294,-0.875,-15.04578)" gradientUnits="userSpaceOnUse" id="linearGradient4713" inkscape:collect="always" x1="15.335379" x2="20.329321" xlink:href="#linearGradient2909" y1="33.06237" y2="36.37693"/>
<linearGradient gradientTransform="matrix(0.459833,0,-0.391165,1.370105,40.62503,-13.29892)" gradientUnits="userSpaceOnUse" id="linearGradient4715" inkscape:collect="always" x1="15.335379" x2="20.329321" xlink:href="#linearGradient2909" y1="33.06237" y2="36.37693"/>
<radialGradient cx="25.251999" cy="16.47991" fx="25.251999" fy="16.47991" gradientTransform="matrix(1.032991,-0.596398,0.575121,0.99614,-12.23456,16.55448)" gradientUnits="userSpaceOnUse" id="radialGradient5748" inkscape:collect="always" r="21.980215" xlink:href="#linearGradient5740"/>
<linearGradient gradientTransform="matrix(0.940224,0,0,0.931632,1.331811,6.401537)" gradientUnits="userSpaceOnUse" id="linearGradient5790" inkscape:collect="always" x1="18.048874" x2="22.211937" xlink:href="#linearGradient2064" y1="25.461344" y2="12.143078"/>
<linearGradient gradientUnits="userSpaceOnUse" id="linearGradient5865" inkscape:collect="always" x1="24" x2="25.818018" xlink:href="#linearGradient8631" y1="36.638382" y2="6.8314762"/>
<linearGradient gradientUnits="userSpaceOnUse" id="linearGradient7658" inkscape:collect="always" x1="27.057796" x2="32.042896" xlink:href="#linearGradient2839" y1="12.669416" y2="31.219666"/>
<linearGradient gradientUnits="userSpaceOnUse" id="linearGradient8603" inkscape:collect="always" x1="20.304037" x2="18.498415" xlink:href="#linearGradient5690" y1="24.035707" y2="40.647167"/>
<radialGradient cx="7.5177727" cy="30.573555" fx="7.5177727" fy="30.573555" gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)" gradientUnits="userSpaceOnUse" id="radialGradient8619" inkscape:collect="always" r="0.53125" xlink:href="#linearGradient8613"/>
<radialGradient cx="7.5191436" cy="30.304251" fx="7.5191436" fy="30.304251" gradientTransform="matrix(1.389748,0,0,1.348872,-2.91982,-10.63815)" gradientUnits="userSpaceOnUse" id="radialGradient8623" inkscape:collect="always" r="0.53125" xlink:href="#linearGradient9613"/>
<radialGradient cx="7.5336008" cy="30.307562" fx="7.5336008" fy="30.307562" gradientTransform="matrix(1.569487,0,0,1.523325,-4.288627,-15.92107)" gradientUnits="userSpaceOnUse" id="radialGradient8664" inkscape:collect="always" r="0.53125" xlink:href="#linearGradient9633"/>
<radialGradient cx="7.5177727" cy="30.573555" fx="7.5177727" fy="30.573555" gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)" gradientUnits="userSpaceOnUse" id="radialGradient8666" inkscape:collect="always" r="0.53125" xlink:href="#linearGradient8613"/>
<radialGradient cx="7.4792061" cy="30.36071" fx="7.4792061" fy="30.36071" gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)" gradientUnits="userSpaceOnUse" id="radialGradient8676" inkscape:collect="always" r="0.53125" xlink:href="#linearGradient8625"/>
<radialGradient cx="7.5177727" cy="30.573555" fx="7.5177727" fy="30.573555" gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)" gradientUnits="userSpaceOnUse" id="radialGradient8678" inkscape:collect="always" r="0.53125" xlink:href="#linearGradient8613"/>
<radialGradient cx="7.4893188" cy="30.337601" fx="7.4893188" fy="30.337601" gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)" gradientUnits="userSpaceOnUse" id="radialGradient8680" inkscape:collect="always" r="0.53125" xlink:href="#linearGradient9641"/>
<radialGradient cx="7.5177727" cy="30.573555" fx="7.5177727" fy="30.573555" gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)" gradientUnits="userSpaceOnUse" id="radialGradient8682" inkscape:collect="always" r="0.53125" xlink:href="#linearGradient8613"/>
<linearGradient gradientTransform="translate(0,5)" gradientUnits="userSpaceOnUse" id="linearGradient8716" inkscape:collect="always" x1="40.617188" x2="40.710938" xlink:href="#linearGradient8710" y1="30.554688" y2="30.359375"/>
<linearGradient gradientTransform="matrix(0.602867,-0.797841,0.797841,0.602867,-41.12611,49.62773)" gradientUnits="userSpaceOnUse" id="linearGradient9605" inkscape:collect="always" x1="40.617188" x2="40.710938" xlink:href="#linearGradient8710" y1="30.554688" y2="30.359375"/>
<linearGradient gradientTransform="matrix(0.866025,-0.5,0.5,0.866025,-38.79233,12.403385)" gradientUnits="userSpaceOnUse" id="linearGradient9649" inkscape:collect="always" x1="40.617188" x2="40.710938" xlink:href="#linearGradient8710" y1="30.554688" y2="30.359375"/>
<linearGradient gradientTransform="matrix(0.707107,0.527555,-0.707107,0.527555,29.0058,-19.09196)" gradientUnits="userSpaceOnUse" id="linearGradient9654" inkscape:collect="always" x1="40.617188" x2="40.710938" xlink:href="#linearGradient8710" y1="30.554688" y2="30.359375"/>
<linearGradient gradientUnits="userSpaceOnUse" id="linearGradient2973" inkscape:collect="always" x1="12.5" x2="12.5" xlink:href="#linearGradient2967" y1="43.1875" y2="34.045513"/>
<linearGradient gradientTransform="translate(0,50)" gradientUnits="userSpaceOnUse" id="linearGradient5655" inkscape:collect="always" x1="53.812813" x2="-2.8138931" xlink:href="#linearGradient2899" y1="43.573235" y2="35.500015"/>
<linearGradient gradientTransform="translate(0,5)" gradientUnits="userSpaceOnUse" id="linearGradient2992" inkscape:collect="always" x1="21.9375" x2="21.9375" xlink:href="#linearGradient2986" y1="39" y2="37.995617"/>
<linearGradient gradientTransform="translate(0,7)" gradientUnits="userSpaceOnUse" id="linearGradient2910" inkscape:collect="always" x1="22.101398" x2="22.971142" xlink:href="#linearGradient2902" y1="27.658131" y2="20.903238"/>
<linearGradient gradientTransform="translate(0,7)" gradientUnits="userSpaceOnUse" id="linearGradient2922" inkscape:collect="always" x1="24.847851" x2="24.847851" xlink:href="#linearGradient2916" y1="28.908398" y2="25.757175"/>
<radialGradient cx="16.5" cy="15" fx="16.5" fy="15" gradientTransform="matrix(1,0,0,0.5454545,0,6.8181818)" gradientUnits="userSpaceOnUse" id="radialGradient6752" inkscape:collect="always" r="5.5" xlink:href="#linearGradient6715"/>
<linearGradient gradientTransform="matrix(1.4314406,0,0,1.4345912,0.332755,-5.7794018)" gradientUnits="userSpaceOnUse" id="linearGradient7822" inkscape:collect="always" x1="17.894716" x2="15.704281" xlink:href="#linearGradient6549" y1="14.069843" y2="4.725668"/>
<linearGradient gradientUnits="userSpaceOnUse" id="linearGradient7826" inkscape:collect="always" x1="23.5" x2="16.124319" xlink:href="#linearGradient7781" y1="17.0625" y2="17.0625"/>
</defs>
<sodipodi:namedview bordercolor="#666" borderlayer="true" borderopacity="0.36078431" gridempspacing="2" gridspacingx="0.5px" gridspacingy="0.5px" height="48px" id="base" inkscape:current-layer="layer1" inkscape:cx="25.082453" inkscape:cy="33.916599" inkscape:document-units="px" inkscape:grid-bbox="true" inkscape:grid-points="true" inkscape:guide-bbox="true" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:showpageshadow="false" inkscape:window-height="715" inkscape:window-width="872" inkscape:window-x="209" inkscape:window-y="285" inkscape:zoom="1" pagecolor="#ffffff" showborder="false" showgrid="false" showguides="false" width="48px">
<sodipodi:guide id="guide2023" orientation="horizontal" position="12"/>
<sodipodi:guide id="guide2025" orientation="horizontal" position="27.5"/>
</sodipodi:namedview>
<g id="layer1" inkscape:groupmode="layer" inkscape:label="Livello 1" style="display:inline">
<g id="g3519" inkscape:export-filename="/home/lapo/Desktop/uhm.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90" style="opacity:0.7" transform="matrix(1.030831,0,0,1.151147,-0.73609,-7.57431)">
<rect height="12.405126" id="rect2884" style="opacity:1;fill:url(#radialGradient4700);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" transform="scale(-1,-1)" width="8.1916437" x="-9.5392103" y="-48.024086"/>
<rect height="12.405126" id="rect2894" style="opacity:1;fill:url(#radialGradient4702);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" width="8.1916437" x="38.209965" y="35.618961"/>
<rect height="12.405126" id="rect2898" style="opacity:1;fill:url(#linearGradient4704);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" width="28.670753" x="9.5392103" y="35.618961"/>
</g>
<g id="g5672" transform="translate(0,-43.99747)">
<path d="M 4.5182287,80.500013 L 43.481768,80.500013 C 44.045867,80.500013 44.499997,80.954143 44.499997,81.518242 L 44.499997,84.481785 C 44.499997,85.797369 44.049766,87.500013 42.044268,87.500013 L 40.5,87.5 L 40.5,88.5 L 7.5,88.5 L 7.5,87.5 L 5.6432287,87.500013 C 4.3720234,87.500013 3.5000005,86.615386 3.5000005,85.244348 L 3.5000005,81.518242 C 3.5000005,80.954143 3.9541302,80.500013 4.5182287,80.500013 z " id="rect2010" sodipodi:nodetypes="ccccccccccccc" style="color:#000000;fill:url(#linearGradient5655);fill-opacity:1;fill-rule:nonzero;stroke:#2e3436;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"/>
<path d="M 4.59375,31.59375 L 4.59375,35.323493 C 4.59375,35.923112 4.9694005,36.428347 5.4800776,36.428347 L 42.426407,36.428347 C 42.938876,36.428347 43.40625,35.921112 43.40625,35.411881 L 43.40625,31.59375 L 4.59375,31.59375 z " id="path2076" sodipodi:nodetypes="ccccccc" style="opacity:0.1;color:#000000;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2973);stroke-width:0.99999988;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" transform="translate(0,50)"/>
<g id="g4706" style="opacity:0.5" transform="translate(0,50)">
<path d="M 26.144738,32.088747 C 26.144738,32.088747 24.642136,37.622686 22.918563,38 C 22.918563,38 29.149941,37.874229 29.149941,37.874229 C 30.537013,37.556768 32.508699,32.088747 32.508699,32.088747 L 26.144738,32.088747 z " id="path2907" inkscape:export-filename="/home/lapo/Desktop/uhm.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90" sodipodi:nodetypes="ccccc" style="opacity:0.10952382;fill:url(#linearGradient4711);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
<path d="M 14.769738,32 C 14.769738,32 13.267136,37.533939 11.543563,37.911253 C 11.543563,37.911253 17.774941,37.785482 17.774941,37.785482 C 19.162013,37.468021 21.133699,32 21.133699,32 L 14.769738,32 z " id="path2892" inkscape:export-filename="/home/lapo/Desktop/uhm.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90" sodipodi:nodetypes="ccccc" style="opacity:0.10952382;fill:url(#linearGradient4713);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
<path d="M 34.886139,32 C 34.886139,32 32.673915,37.328458 31.777636,37.691761 C 31.777636,37.691761 34.677605,37.57066 34.677605,37.57066 C 35.402697,37.264987 37.8125,32 37.8125,32 L 34.886139,32 z " id="path2896" inkscape:export-filename="/home/lapo/Desktop/uhm.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90" sodipodi:nodetypes="ccccc" style="opacity:0.10952382;fill:url(#linearGradient4715);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
</g>
</g>
<path d="M 11.693127,15.498788 L 36.265693,15.498788 C 37.949863,15.498788 38.66221,15.616267 39.305712,17.883793 L 44.380203,35.764912 C 44.881227,37.530383 43.024355,38.500013 41.340185,38.500013 L 6.6186312,38.500013 C 4.7502232,38.500013 3.1292479,37.318596 3.578613,35.764912 L 8.8290448,17.611497 C 9.3973456,15.646592 10.008957,15.498788 11.693127,15.498788 z " id="rect1879" inkscape:export-filename="/home/lapo/Desktop/uhm.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90" sodipodi:nodetypes="cczzcczzc" style="fill:url(#radialGradient5748);fill-opacity:1;stroke:#888a85;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"/>
<path d="M 11.6875,11 C 10.861258,11 10.40275,11.065232 10.125,11.25 C 9.8472503,11.434768 9.5847988,11.808525 9.3125,12.75 L 4.0625,30.875 C 3.8916752,31.465628 4.0939747,31.914316 4.5625,32.3125 C 5.0310253,32.710684 5.7975106,33.000001 6.625,33 L 41.34375,33 C 42.088405,33 42.882691,32.7518 43.375,32.375 C 43.867309,31.9982 44.106017,31.578923 43.90625,30.875 L 38.84375,13 C 38.534499,11.910283 38.220548,11.458908 37.90625,11.25 C 37.591952,11.041092 37.112699,11 36.28125,11 L 11.6875,11 z " id="path5806" inkscape:original="M 11.6875 10.5 C 10.00333 10.5 9.4120513 10.660095 8.84375 12.625 L 3.59375 30.75 C 3.1443849 32.303684 4.7565918 33.500002 6.625 33.5 L 41.34375 33.5 C 43.02792 33.5 44.876024 32.515471 44.375 30.75 L 39.3125 12.875 C 38.668998 10.607474 37.965419 10.5 36.28125 10.5 L 11.6875 10.5 z " inkscape:radius="-0.5" sodipodi:type="inkscape:offset" style="opacity:0.46240599;fill:url(#linearGradient7658);fill-opacity:1;stroke:none;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;display:inline" transform="translate(0,5)"/>
<path d="M 8.15625 30.578125 A 0.53125 0.515625 0 1 1 7.09375,30.578125 A 0.53125 0.515625 0 1 1 8.15625 30.578125 z" id="path8621" sodipodi:cx="7.625" sodipodi:cy="30.578125" sodipodi:rx="0.53125" sodipodi:ry="0.515625" sodipodi:type="arc" style="opacity:1;color:#000000;fill:url(#radialGradient8623);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" transform="matrix(-2.628602,0,0,1.777765,27.79309,-18.77739)"/>
<path d="M 16.110953,17.552805 C 15.537372,17.552805 15.082583,17.984626 15.082583,18.542664 L 14.14236,21.773465 C 11.282398,23.049979 9.5000048,24.872538 9.5000048,26.897441 C 9.5000048,30.754398 15.979029,33.88468 23.985327,33.884681 C 31.991623,33.884681 38.500032,30.754397 38.500031,26.897441 C 38.500031,24.858407 36.66444,23.022053 33.76953,21.744352 L 32.829306,18.542664 C 32.829306,17.984626 32.374518,17.552805 31.800936,17.552805 L 16.110953,17.552805 z " id="path2784" sodipodi:nodetypes="cccssscccc" style="fill:none;fill-opacity:1;stroke:url(#linearGradient5790);stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"/>
<path d="M 11.6875,16.500005 C 10.884376,16.500005 10.590332,16.570515 10.46875,16.655561 C 10.347168,16.740606 10.111043,17.057681 9.78125,17.962228 L 4.53125,36.100014 C 4.3975296,36.466779 4.4764233,36.633879 4.84375,36.940014 C 5.2110767,37.24615 5.910443,37.500014 6.625,37.500015 L 41.34375,37.500015 C 41.983543,37.500015 42.737095,37.262061 43.125,36.971125 C 43.512905,36.68019 43.613311,36.588316 43.4375,36.100014 L 38.375,18.242228 C 37.997794,17.194568 37.69292,16.802931 37.53125,16.686672 C 37.36958,16.570413 37.087539,16.500005 36.28125,16.500005 L 11.6875,16.500005 z " id="path3394" sodipodi:nodetypes="csccsccsccscc" style="fill:none;fill-opacity:1;stroke:url(#linearGradient3474);stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;display:inline"/>
<g id="g5657" style="opacity:0.3028571" transform="translate(7,4)">
<rect height="1.9999924" id="rect5641" rx="0.75130093" ry="0.74712253" style="opacity:1;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#eeeeec;stroke-width:1.00000024;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" width="14.000004" x="18.499996" y="35.500008"/>
<rect height="1" id="rect5645" style="opacity:1;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" width="1" x="19" y="36"/>
<rect height="1" id="rect5647" style="opacity:1;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" width="1" x="22" y="36"/>
<rect height="1" id="rect5649" style="opacity:1;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" width="1" x="24" y="36"/>
<rect height="1" id="rect5651" style="opacity:1;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" width="1" x="26" y="36"/>
<rect height="1" id="rect5653" style="opacity:1;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" width="2" x="29" y="36"/>
</g>
<path d="M 16.125,13 C 15.762388,13 15.53125,13.214848 15.53125,13.53125 C 15.537172,13.572706 15.537172,13.614794 15.53125,13.65625 L 14.59375,16.90625 C 14.55648,17.032564 14.464823,17.135678 14.34375,17.1875 C 12.953848,17.807869 11.845241,18.565737 11.09375,19.375 C 10.342259,20.184263 9.9375,21.02448 9.9375,21.90625 C 9.9375,23.586705 11.392464,25.21545 13.9375,26.4375 C 16.482536,27.65955 20.054107,28.4375 24,28.4375 C 27.945892,28.4375 31.517463,27.65955 34.0625,26.4375 C 36.607537,25.21545 38.0625,23.586705 38.0625,21.90625 C 38.0625,21.018499 37.668724,20.159037 36.90625,19.34375 C 36.143776,18.528463 35.000895,17.777309 33.59375,17.15625 C 33.472677,17.104428 33.38102,17.001314 33.34375,16.875 L 32.40625,13.65625 C 32.400328,13.614794 32.400328,13.572706 32.40625,13.53125 C 32.406249,13.214846 32.175114,13 31.8125,13 L 16.125,13 z " id="path5857" inkscape:original="M 16.125 12.5625 C 15.55142 12.5625 15.09375 12.973212 15.09375 13.53125 L 14.15625 16.78125 C 11.296288 18.057765 9.5 19.881347 9.5 21.90625 C 9.5 25.763206 15.993702 28.874999 24 28.875 C 32.006296 28.874999 38.500001 25.763206 38.5 21.90625 C 38.5 19.867215 36.67616 18.027701 33.78125 16.75 L 32.84375 13.53125 C 32.843748 12.973212 32.386082 12.5625 31.8125 12.5625 L 16.125 12.5625 z " inkscape:radius="-0.44194174" sodipodi:type="inkscape:offset" style="fill:url(#linearGradient5865);fill-opacity:1;stroke:none;stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" transform="translate(0,5)"/>
<path d="M 24.748736 25.107418 A 7.7781744 4.2868347 0 1 1 9.1923876,25.107418 A 7.7781744 4.2868347 0 1 1 24.748736 25.107418 z" id="path8595" sodipodi:cx="16.970562" sodipodi:cy="25.107418" sodipodi:rx="7.7781744" sodipodi:ry="4.2868347" sodipodi:type="arc" style="opacity:0.9;color:#000000;fill:#000000;fill-opacity:0.05303028;fill-rule:nonzero;stroke:url(#linearGradient8603);stroke-width:2.52015233;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" transform="matrix(0.449978,0,0,0.349909,16.36363,17.21469)"/>
<path d="M 8.15625 30.578125 A 0.53125 0.515625 0 1 1 7.09375,30.578125 A 0.53125 0.515625 0 1 1 8.15625 30.578125 z" id="path8611" sodipodi:cx="7.625" sodipodi:cy="30.578125" sodipodi:rx="0.53125" sodipodi:ry="0.515625" sodipodi:type="arc" style="opacity:1;color:#000000;fill:url(#radialGradient8619);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" transform="matrix(1.411772,0,0,0.969697,-3.014767,5.848485)"/>
<path d="M 8.15625 30.578125 A 0.53125 0.515625 0 1 1 7.09375,30.578125 A 0.53125 0.515625 0 1 1 8.15625 30.578125 z" id="path8660" sodipodi:cx="7.625" sodipodi:cy="30.578125" sodipodi:rx="0.53125" sodipodi:ry="0.515625" sodipodi:type="arc" style="opacity:1;color:#000000;fill:url(#radialGradient8664);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.46259445;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" transform="matrix(-2.628602,0,0,1.777765,60.79309,-18.77739)"/>
<path d="M 8.15625 30.578125 A 0.53125 0.515625 0 1 1 7.09375,30.578125 A 0.53125 0.515625 0 1 1 8.15625 30.578125 z" id="path8662" sodipodi:cx="7.625" sodipodi:cy="30.578125" sodipodi:rx="0.53125" sodipodi:ry="0.515625" sodipodi:type="arc" style="opacity:1;color:#000000;fill:url(#radialGradient8666);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" transform="matrix(1.411772,0,0,0.969697,29.98523,5.848485)"/>
<path d="M 8.15625 30.578125 A 0.53125 0.515625 0 1 1 7.09375,30.578125 A 0.53125 0.515625 0 1 1 8.15625 30.578125 z" id="path8668" sodipodi:cx="7.625" sodipodi:cy="30.578125" sodipodi:rx="0.53125" sodipodi:ry="0.515625" sodipodi:type="arc" style="opacity:1;color:#000000;fill:url(#radialGradient8676);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" transform="matrix(-2.628602,0,0,1.777765,31.79309,-35.77739)"/>
<path d="M 8.15625 30.578125 A 0.53125 0.515625 0 1 1 7.09375,30.578125 A 0.53125 0.515625 0 1 1 8.15625 30.578125 z" id="path8670" sodipodi:cx="7.625" sodipodi:cy="30.578125" sodipodi:rx="0.53125" sodipodi:ry="0.515625" sodipodi:type="arc" style="opacity:1;color:#000000;fill:url(#radialGradient8678);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" transform="matrix(1.411772,0,0,0.969697,0.985233,-11.15152)"/>
<path d="M 8.15625 30.578125 A 0.53125 0.515625 0 1 1 7.09375,30.578125 A 0.53125 0.515625 0 1 1 8.15625 30.578125 z" id="path8672" sodipodi:cx="7.625" sodipodi:cy="30.578125" sodipodi:rx="0.53125" sodipodi:ry="0.515625" sodipodi:type="arc" style="opacity:1;color:#000000;fill:url(#radialGradient8680);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" transform="matrix(-2.628602,0,0,1.777765,56.3029,-35.77739)"/>
<path d="M 8.15625 30.578125 A 0.53125 0.515625 0 1 1 7.09375,30.578125 A 0.53125 0.515625 0 1 1 8.15625 30.578125 z" id="path8674" sodipodi:cx="7.625" sodipodi:cy="30.578125" sodipodi:rx="0.53125" sodipodi:ry="0.515625" sodipodi:type="arc" style="opacity:1;color:#000000;fill:url(#radialGradient8682);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" transform="matrix(1.411772,0,0,0.969697,25.49504,-11.15152)"/>
<path d="M 40.328109,35.261401 L 41.203108,35.691733" id="path8700" style="opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8716);stroke-width:0.29999995;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
<path d="M 7.330186,35.695906 L 8.201031,35.257228" id="path9603" style="opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient9605);stroke-width:0.29999995;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline"/>
<path d="M 11.263531,18.446473 L 12.236468,18.381651" id="path9647" style="opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient9649);stroke-width:0.29999995;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline"/>
<path d="M 36.124038,18.147874 L 36.438465,18.836508" id="path9652" style="opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient9654);stroke-width:0.29999998;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline"/>
<rect height="1" id="rect2984" style="opacity:0.12000002;color:#000000;fill:url(#linearGradient2992);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.68183619;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" width="32.03125" x="8" y="43"/>
<path d="M 10.460155,20.082355 L 6.8513979,32.675762 C 8.2982685,33.375511 10.625,34.167061 10.429825,36.533131 L 37.299883,36.533131 C 37.869398,34.640915 39.875,33.375 41.34614,33.25 L 37.498106,20.082355 L 32.350135,17.523347 L 14.318912,17.523347 L 10.460155,20.082355 z " id="path1997" sodipodi:nodetypes="ccccccccc" style="opacity:0.12000002;color:#000000;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2910);stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"/>
<path d="M 7.9763979,32.050762 C 9.4232685,32.750511 11.15533,33.484003 11.402097,35.408131 L 36.857941,35.408131 C 37.427456,33.515915 38.875,32.5 40.34614,32.375 L 7.9763979,32.050762 z " id="path2912" sodipodi:nodetypes="ccccc" style="opacity:0.83428572;color:#000000;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2922);stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"/>
<path d="M 22 15 A 5.5 3 0 1 1 11,15 A 5.5 3 0 1 1 22 15 z" id="path6723" sodipodi:cx="16.5" sodipodi:cy="15" sodipodi:rx="5.5" sodipodi:ry="3" sodipodi:type="arc" style="opacity:0.2;fill:url(#radialGradient6752);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" transform="matrix(2.1471606,0,0,1.4314409,-11.580953,1.2105393)"/>
<g id="g7817" transform="translate(0,-1)">
<path d="M 29.5,1.5 L 18.5,1.5 L 18.5,13.5 L 12.5,13.5 L 24,26.5 L 35.5,13.5 L 29.5,13.5 L 29.5,1.5 z " id="path6526" sodipodi:nodetypes="cccccccc" style="fill:url(#linearGradient7822);fill-opacity:1;stroke:#4e9a06;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline"/>
<path d="M 28.5,2.5 L 19.5,2.5 L 19.5,13.5 C 19.500004,14.052286 19.052286,14.500004 18.5,14.5 L 14.71875,14.5 L 24,25 L 33.28125,14.5 L 29.5,14.5 C 28.947714,14.500004 28.499996,14.052286 28.5,13.5 L 28.5,2.5 z " id="path7769" sodipodi:nodetypes="cccccccccc" style="opacity:0.6;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;display:inline"/>
<path d="M 19,2 L 19,13.5 C 18.999972,13.776131 18.776131,13.999972 18.5,14 L 13.625,14 L 24,25.71875 L 24,2 L 19,2 z " id="path7776" sodipodi:nodetypes="ccccccc" style="opacity:0.5;fill:url(#linearGradient7826);fill-opacity:1;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -0,0 +1,158 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.virtualvehicle;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.opentcs.data.model.Vehicle;
/**
* Unit tests for {@link VelocityController}.
*/
class VelocityControllerTest {
private static final int MAX_DECEL = -1000;
private static final int MAX_ACCEL = 1000;
private static final int MAX_REV_VELO = -500;
private static final int MAX_FWD_VELO = 500;
private static final long WAY_LENGTH = 5000;
private static final int MAX_VELO = 500;
private static final String POINT_NAME = "Destination_Point";
private VelocityController controller;
@BeforeEach
void setUp() {
controller = new VelocityController(MAX_DECEL, MAX_ACCEL, MAX_REV_VELO, MAX_FWD_VELO);
}
@Test
void initialControllerHasNoWayEntries() {
assertFalse(controller.hasWayEntries());
assertNull(controller.getCurrentWayEntry());
}
@Test
void throwOnAddingNullWayEntry() {
assertThrows(NullPointerException.class, () -> controller.addWayEntry(null));
}
@Test
void throwOnAdvancingWithNegativeTime() {
assertThrows(IllegalArgumentException.class, () -> controller.advanceTime(-1));
}
@Test
void advanceTimeAdvancesTime() {
long timeBefore = controller.getCurrentTime();
controller.advanceTime(5);
assertThat(controller.getCurrentTime(), is(timeBefore + 5));
}
@Test
void vehicleDoesNotChangePositionWhilePaused() {
VelocityController.WayEntry wayEntry
= new VelocityController.WayEntry(
WAY_LENGTH,
MAX_VELO,
POINT_NAME,
Vehicle.Orientation.FORWARD
);
controller.addWayEntry(wayEntry);
controller.setVehiclePaused(true);
long posBefore = controller.getCurrentPosition();
controller.advanceTime(5);
assertThat(controller.getCurrentPosition(), is(posBefore));
}
@Test
void processWayEntriesInGivenOrder() {
controller = new VelocityController(MAX_DECEL, 1000, MAX_REV_VELO, 500);
VelocityController.WayEntry firstEntry
= new VelocityController.WayEntry(1, MAX_VELO, POINT_NAME, Vehicle.Orientation.FORWARD);
VelocityController.WayEntry secondEntry
= new VelocityController.WayEntry(10000, MAX_VELO, POINT_NAME, Vehicle.Orientation.FORWARD);
controller.addWayEntry(firstEntry);
controller.addWayEntry(secondEntry);
assertThat(controller.getCurrentWayEntry(), is(sameInstance(firstEntry)));
assertSame(firstEntry, controller.getCurrentWayEntry());
controller.advanceTime(100);
assertThat(controller.getCurrentWayEntry(), is(sameInstance(secondEntry)));
assertSame(secondEntry, controller.getCurrentWayEntry());
}
@Test
void accelerateToMaxVelocityLimitedByController() {
final int maxFwdVelocity = 500; // mm/s
final int maxAcceleration = 250; // mm/s^2
controller = new VelocityController(MAX_DECEL, maxAcceleration, MAX_REV_VELO, maxFwdVelocity);
// Way point with enough length to reach the maximum velocity and with a higher velocity limit
// than the vehicle's own maximum velocity.
VelocityController.WayEntry wayEntry
= new VelocityController.WayEntry(
100000,
2 * maxFwdVelocity,
POINT_NAME,
Vehicle.Orientation.FORWARD
);
controller.addWayEntry(wayEntry);
controller.advanceTime(1000);
// Reach 250 mm/s in 1s:
assertThat(controller.getCurrentVelocity(), is(250));
controller.advanceTime(1000);
// Reach max. velocity of 500 mm/s in 2s:
assertThat(controller.getCurrentVelocity(), is(500));
controller.advanceTime(1000);
// Stay at max velocity:
assertThat(controller.getCurrentVelocity(), is(500));
}
@Test
void accelerateToMaxVelocityLimitedByWayEntry() {
final int maxFwdVelocity = 500; // mm/s
final int maxAcceleration = 500; // mm/s^2
controller = new VelocityController(MAX_DECEL, maxAcceleration, MAX_REV_VELO, maxFwdVelocity);
// Way point with a velocity limit less than the vehicle's own maximum velocity.
VelocityController.WayEntry wayEntry
= new VelocityController.WayEntry(
10000,
250,
POINT_NAME,
Vehicle.Orientation.FORWARD
);
controller.addWayEntry(wayEntry);
for (int i = 0; i < 10; i++) {
controller.advanceTime(100);
}
// Velocity could be 500 mm/s after one second, but should be limited to 250 mm/s.
assertThat(controller.getCurrentVelocity(), is(250));
}
}