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,266 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JDialogFormInfo">
<Properties>
<Property name="defaultCloseOperation" type="int" value="2"/>
<Property name="title" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="aboutDialog.title" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
<Property name="resizable" type="boolean" value="false"/>
</Properties>
<SyntheticProperties>
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
<SyntheticProperty name="generateCenter" type="boolean" value="false"/>
</SyntheticProperties>
<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,79,0,0,1,58"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="logoPanel">
<Properties>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="ff" green="ff" red="ff" type="rgb"/>
</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="2" ipadX="6" ipadY="6" 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.DesignBorderLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="opentcsLogoLbl">
<Properties>
<Property name="horizontalAlignment" type="int" value="0"/>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/opentcs/kernelcontrolcenter/res/logos/opentcs.gif"/>
</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>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="contactPanel">
<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="1" 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.DesignBoxLayout">
<Property name="axis" type="int" value="1"/>
</Layout>
<SubComponents>
<Container class="javax.swing.JPanel" name="opentcsContactPanel">
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="opentcsLbl">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font bold="true" component="opentcsLbl" property="font" relativeSize="true" size="0"/>
</FontInfo>
</Property>
<Property name="text" type="java.lang.String" value="open Transportation Control System"/>
</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="2" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="6" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="versionLbl">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="aboutDialog.label_baselineVersion.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="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="versionTxtLbl">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="Environment.getBaselineVersion()" type="code"/>
</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="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="6" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="customVersionLbl">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="aboutDialog.label_customVersion.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="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="customVersionTxtLbl">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="Environment.getCustomizationName() + &quot; &quot; + Environment.getCustomizationVersion()" type="code"/>
</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="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="6" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="homepageLbl">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="aboutDialog.label_homepage.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="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="homepageTxtLbl">
<Properties>
<Property name="text" type="java.lang.String" value="https://www.opentcs.org/"/>
</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="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="6" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="emailLbl">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="aboutDialog.label_email.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="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="emailTxtLbl">
<Properties>
<Property name="text" type="java.lang.String" value="business-info@opentcs.org"/>
</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="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="6" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="imlPanel">
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="fraunhoferImlLbl">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font bold="true" component="fraunhoferImlLbl" property="font" relativeSize="true" size="0"/>
</FontInfo>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="aboutDialog.label_fraunhoferIml.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="2" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="6" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="homepageImlLbl">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="aboutDialog.label_homepage.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="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="homepageImlTxtLbl">
<Properties>
<Property name="text" type="java.lang.String" value="http://www.iml.fraunhofer.de/"/>
</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="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="6" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
<Component class="javax.swing.JLabel" name="fillingLbl">
<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="0" gridY="2" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="1.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JButton" name="closeButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="aboutDialog.button_close.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="closeButtonActionPerformed"/>
</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="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="fillingLbl2">
<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="0" gridY="4" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="1.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Form>

View File

@@ -0,0 +1,253 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter;
import java.awt.Frame;
import javax.swing.JDialog;
import org.opentcs.util.Environment;
/**
* An about dialog.
*/
public class AboutDialog
extends
JDialog {
/**
* Creates new AboutDialog.
*
* @param parent The parent frame.
* @param modal Whether the dialog blocks user input to other top-level windows when shown.
*/
@SuppressWarnings("this-escape")
public AboutDialog(Frame parent, boolean modal) {
super(parent, modal);
initComponents();
}
// 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;
logoPanel = new javax.swing.JPanel();
opentcsLogoLbl = new javax.swing.JLabel();
contactPanel = new javax.swing.JPanel();
opentcsContactPanel = new javax.swing.JPanel();
opentcsLbl = new javax.swing.JLabel();
versionLbl = new javax.swing.JLabel();
versionTxtLbl = new javax.swing.JLabel();
customVersionLbl = new javax.swing.JLabel();
customVersionTxtLbl = new javax.swing.JLabel();
homepageLbl = new javax.swing.JLabel();
homepageTxtLbl = new javax.swing.JLabel();
emailLbl = new javax.swing.JLabel();
emailTxtLbl = new javax.swing.JLabel();
imlPanel = new javax.swing.JPanel();
fraunhoferImlLbl = new javax.swing.JLabel();
homepageImlLbl = new javax.swing.JLabel();
homepageImlTxtLbl = new javax.swing.JLabel();
fillingLbl = new javax.swing.JLabel();
closeButton = new javax.swing.JButton();
fillingLbl2 = new javax.swing.JLabel();
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("i18n/org/opentcs/kernelcontrolcenter/Bundle"); // NOI18N
setTitle(bundle.getString("aboutDialog.title")); // NOI18N
setResizable(false);
getContentPane().setLayout(new java.awt.GridBagLayout());
logoPanel.setBackground(new java.awt.Color(255, 255, 255));
logoPanel.setLayout(new java.awt.BorderLayout());
opentcsLogoLbl.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
opentcsLogoLbl.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/opentcs/kernelcontrolcenter/res/logos/opentcs.gif"))); // NOI18N
logoPanel.add(opentcsLogoLbl, java.awt.BorderLayout.CENTER);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.ipadx = 6;
gridBagConstraints.ipady = 6;
getContentPane().add(logoPanel, gridBagConstraints);
contactPanel.setLayout(new javax.swing.BoxLayout(contactPanel, javax.swing.BoxLayout.Y_AXIS));
opentcsContactPanel.setLayout(new java.awt.GridBagLayout());
opentcsLbl.setFont(opentcsLbl.getFont().deriveFont(opentcsLbl.getFont().getStyle() | java.awt.Font.BOLD));
opentcsLbl.setText("open Transportation Control System");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridwidth = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(6, 0, 0, 0);
opentcsContactPanel.add(opentcsLbl, gridBagConstraints);
versionLbl.setText(bundle.getString("aboutDialog.label_baselineVersion.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
opentcsContactPanel.add(versionLbl, gridBagConstraints);
versionTxtLbl.setText(Environment.getBaselineVersion());
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(0, 6, 0, 0);
opentcsContactPanel.add(versionTxtLbl, gridBagConstraints);
customVersionLbl.setText(bundle.getString("aboutDialog.label_customVersion.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
opentcsContactPanel.add(customVersionLbl, gridBagConstraints);
customVersionTxtLbl.setText(Environment.getCustomizationName() + " " + Environment.getCustomizationVersion());
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(0, 6, 0, 0);
opentcsContactPanel.add(customVersionTxtLbl, gridBagConstraints);
homepageLbl.setText(bundle.getString("aboutDialog.label_homepage.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
opentcsContactPanel.add(homepageLbl, gridBagConstraints);
homepageTxtLbl.setText("https://www.opentcs.org/");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(0, 6, 0, 0);
opentcsContactPanel.add(homepageTxtLbl, gridBagConstraints);
emailLbl.setText(bundle.getString("aboutDialog.label_email.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 4;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
opentcsContactPanel.add(emailLbl, gridBagConstraints);
emailTxtLbl.setText("business-info@opentcs.org");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 4;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(0, 6, 0, 0);
opentcsContactPanel.add(emailTxtLbl, gridBagConstraints);
contactPanel.add(opentcsContactPanel);
imlPanel.setLayout(new java.awt.GridBagLayout());
fraunhoferImlLbl.setFont(fraunhoferImlLbl.getFont().deriveFont(fraunhoferImlLbl.getFont().getStyle() | java.awt.Font.BOLD));
fraunhoferImlLbl.setText(bundle.getString("aboutDialog.label_fraunhoferIml.text")); // NOI18N
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(6, 0, 0, 0);
imlPanel.add(fraunhoferImlLbl, gridBagConstraints);
homepageImlLbl.setText(bundle.getString("aboutDialog.label_homepage.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
imlPanel.add(homepageImlLbl, gridBagConstraints);
homepageImlTxtLbl.setText("http://www.iml.fraunhofer.de/");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(0, 6, 0, 0);
imlPanel.add(homepageImlTxtLbl, gridBagConstraints);
contactPanel.add(imlPanel);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
getContentPane().add(contactPanel, gridBagConstraints);
fillingLbl.setText(" ");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.weighty = 1.0;
getContentPane().add(fillingLbl, gridBagConstraints);
closeButton.setText(bundle.getString("aboutDialog.button_close.text")); // NOI18N
closeButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
closeButtonActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 3;
getContentPane().add(closeButton, gridBagConstraints);
fillingLbl2.setText(" ");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 4;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.weighty = 1.0;
getContentPane().add(fillingLbl2, gridBagConstraints);
pack();
}// </editor-fold>//GEN-END:initComponents
// CHECKSTYLE:ON
// FORMATTER:ON
private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed
this.setVisible(false);
}//GEN-LAST:event_closeButtonActionPerformed
// FORMATTER:OFF
// CHECKSTYLE:OFF
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton closeButton;
private javax.swing.JPanel contactPanel;
private javax.swing.JLabel customVersionLbl;
private javax.swing.JLabel customVersionTxtLbl;
private javax.swing.JLabel emailLbl;
private javax.swing.JLabel emailTxtLbl;
private javax.swing.JLabel fillingLbl;
private javax.swing.JLabel fillingLbl2;
private javax.swing.JLabel fraunhoferImlLbl;
private javax.swing.JLabel homepageImlLbl;
private javax.swing.JLabel homepageImlTxtLbl;
private javax.swing.JLabel homepageLbl;
private javax.swing.JLabel homepageTxtLbl;
private javax.swing.JPanel imlPanel;
private javax.swing.JPanel logoPanel;
private javax.swing.JPanel opentcsContactPanel;
private javax.swing.JLabel opentcsLbl;
private javax.swing.JLabel opentcsLogoLbl;
private javax.swing.JLabel versionLbl;
private javax.swing.JLabel versionTxtLbl;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
}

View File

@@ -0,0 +1,160 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter;
import static java.util.Objects.requireNonNull;
import com.google.inject.assistedinject.Assisted;
import jakarta.inject.Inject;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultCaret;
import org.opentcs.access.NotificationPublicationEvent;
import org.opentcs.common.ClientConnectionMode;
import org.opentcs.common.PortalManager;
import org.opentcs.data.notification.UserNotification;
import org.opentcs.kernelcontrolcenter.util.KernelControlCenterConfiguration;
import org.opentcs.util.event.EventHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A logging handler that writes all INFO-logs to KernelControlCenter's logging text area.
*/
public class ControlCenterInfoHandler
implements
EventHandler {
/**
* This class's Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(ControlCenterInfoHandler.class);
/**
* Formats time stamps.
*/
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter
.ofLocalizedDateTime(FormatStyle.SHORT)
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault());
/**
* This class's configuration.
*/
private final KernelControlCenterConfiguration configuration;
/**
* The text area we're writing in.
*/
private final JTextArea textArea;
/**
* A flag whether the text area scrolls.
*/
private boolean autoScroll;
/**
* Creates a new ControlCenterInfoHandler.
*
* @param textArea The textArea we are writing to.
* @param configuration This class' configuration.
*/
@Inject
public ControlCenterInfoHandler(
@Assisted
JTextArea textArea,
KernelControlCenterConfiguration configuration
) {
this.textArea = requireNonNull(textArea, "textArea");
this.configuration = requireNonNull(configuration, "configuration");
autoScroll = true;
}
@Override
public void onEvent(Object event) {
if (event instanceof PortalManager.ConnectionState) {
PortalManager.ConnectionState connectionState = (PortalManager.ConnectionState) event;
SwingUtilities.invokeLater(() -> {
publish(
new UserNotification(
"Kernel connection state: " + connectionState.name(),
UserNotification.Level.INFORMATIONAL
)
);
});
}
else if (event instanceof ClientConnectionMode) {
ClientConnectionMode applicationState = (ClientConnectionMode) event;
SwingUtilities.invokeLater(() -> {
publish(
new UserNotification(
"Application state: " + applicationState.name(),
UserNotification.Level.INFORMATIONAL
)
);
});
}
else if (event instanceof NotificationPublicationEvent) {
SwingUtilities.invokeLater(
() -> publish(((NotificationPublicationEvent) event).getNotification())
);
}
}
/**
* Defines if the textArea autoscrolls.
*
* @param autoScroll true if it should, false otherwise
*/
public void setAutoScroll(boolean autoScroll) {
this.autoScroll = autoScroll;
}
/**
* Displays the notification.
*
* @param notification The notification
*/
private void publish(UserNotification notification) {
DefaultCaret caret = (DefaultCaret) textArea.getCaret();
if (autoScroll) {
caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
textArea.setCaretPosition(textArea.getDocument().getLength());
}
else {
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
}
textArea.append(format(notification));
textArea.append("\n");
checkLength();
}
private String format(UserNotification notification) {
return DATE_FORMAT.format(notification.getTimestamp())
+ " " + notification.getLevel()
+ ": [" + notification.getSource() + "] "
+ notification.getText();
}
/**
* Checks if the length of the document in our textArea is greater than our {@code maxDocLength}
* and cuts it if neccessary.
*/
private synchronized void checkLength() {
SwingUtilities.invokeLater(() -> {
int docLength = textArea.getDocument().getLength();
if (docLength > configuration.loggingAreaCapacity()) {
try {
textArea.getDocument().remove(0, docLength - configuration.loggingAreaCapacity());
}
catch (BadLocationException e) {
LOG.warn("Caught exception", e);
}
}
});
}
}

View File

@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter;
import javax.swing.JTextArea;
/**
* A factory providing {@link ControlCenterInfoHandler} instances.
*/
public interface ControlCenterInfoHandlerFactory {
/**
* Creates a new ControlCenterInfoHandler.
*
* @param textArea The text area.
* @return A new ControlCenterInfoHandler.
*/
ControlCenterInfoHandler createHandler(JTextArea textArea);
}

View File

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

View File

@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.3" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
<NonVisualComponents>
<Menu class="javax.swing.JMenuBar" name="menuBarMain">
<SubComponents>
<Menu class="javax.swing.JMenu" name="menuKernel">
<Properties>
<Property name="text" type="java.lang.String" value="KernelControlCenter"/>
</Properties>
<SubComponents>
<MenuItem class="javax.swing.JMenuItem" name="menuButtonConnect">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="kernelControlCenter.menu_kernel.menuItem_connect.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="menuButtonConnectActionPerformed"/>
</Events>
</MenuItem>
<MenuItem class="javax.swing.JMenuItem" name="menuButtonDisconnect">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="kernelControlCenter.menu_kernel.menuItem_disconnect.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="menuButtonDisconnectActionPerformed"/>
</Events>
</MenuItem>
<MenuItem class="javax.swing.JPopupMenu$Separator" name="jSeparator1">
</MenuItem>
<MenuItem class="javax.swing.JMenuItem" name="menuButtonExit">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="kernelControlCenter.menu_kernel.menuItem_exit.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="menuButtonExitActionPerformed"/>
</Events>
</MenuItem>
</SubComponents>
</Menu>
<Menu class="javax.swing.JMenu" name="menuHelp">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="kernelControlCenter.menu_help.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<SubComponents>
<MenuItem class="javax.swing.JMenuItem" name="menuAbout">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="kernelControlCenter.menu_about.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="menuAboutActionPerformed"/>
</Events>
</MenuItem>
</SubComponents>
</Menu>
</SubComponents>
</Menu>
</NonVisualComponents>
<Properties>
<Property name="defaultCloseOperation" type="int" value="0"/>
<Property name="title" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="kernelControlCenter.title" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[1200, 750]"/>
</Property>
</Properties>
<SyntheticProperties>
<SyntheticProperty name="menuBar" type="java.lang.String" value="menuBarMain"/>
<SyntheticProperty name="formSize" 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,3,14,0,0,4,-72"/>
<SyntheticProperty name="formSizePolicy" type="int" value="0"/>
<SyntheticProperty name="generateSize" type="boolean" value="true"/>
<SyntheticProperty name="generateCenter" type="boolean" value="true"/>
</SyntheticProperties>
<Events>
<EventHandler event="windowClosing" listener="java.awt.event.WindowListener" parameters="java.awt.event.WindowEvent" handler="formWindowClosing"/>
</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"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JTabbedPane" name="tabbedPaneMain">
<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.support.JTabbedPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="loggingPanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
<JTabbedPaneConstraints tabName="Logging">
<Property name="tabTitle" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="kernelControlCenter.tab_logging.title" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</JTabbedPaneConstraints>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="loggingScrollPane">
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</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>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTextArea" name="loggingTextArea">
<Properties>
<Property name="editable" type="boolean" value="false"/>
</Properties>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="loggingPropertyPanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="First"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JCheckBox" name="autoScrollCheckBox">
<Properties>
<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/kernelcontrolcenter/Bundle.properties" key="kernelControlCenter.checkBox_autoScroll.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="autoScrollCheckBoxActionPerformed"/>
</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="17" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@@ -0,0 +1,531 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter;
import static java.util.Objects.requireNonNull;
import static org.opentcs.common.PortalManager.ConnectionState.CONNECTED;
import static org.opentcs.common.PortalManager.ConnectionState.DISCONNECTED;
import static org.opentcs.kernelcontrolcenter.I18nKernelControlCenter.BUNDLE_PATH;
import jakarta.annotation.Nonnull;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.awt.EventQueue;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import org.opentcs.access.Kernel;
import org.opentcs.access.KernelServicePortal;
import org.opentcs.access.KernelStateTransitionEvent;
import org.opentcs.access.ModelTransitionEvent;
import org.opentcs.common.ClientConnectionMode;
import org.opentcs.common.KernelClientApplication;
import org.opentcs.common.PortalManager;
import org.opentcs.components.Lifecycle;
import org.opentcs.components.kernelcontrolcenter.ControlCenterPanel;
import org.opentcs.customizations.ApplicationEventBus;
import org.opentcs.customizations.ServiceCallWrapper;
import org.opentcs.customizations.controlcenter.ActiveInModellingMode;
import org.opentcs.customizations.controlcenter.ActiveInOperatingMode;
import org.opentcs.util.CallWrapper;
import org.opentcs.util.event.EventHandler;
import org.opentcs.util.event.EventSource;
import org.opentcs.util.gui.Icons;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A GUI frontend for basic control over the kernel.
*/
public class KernelControlCenter
extends
JFrame
implements
Lifecycle,
EventHandler {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(KernelControlCenter.class);
/**
* This class's resource bundle.
*/
private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(BUNDLE_PATH);
/**
* The factory providing a ControlCenterInfoHandler.
*/
private final ControlCenterInfoHandlerFactory controlCenterInfoHandlerFactoy;
/**
* Providers for panels shown in modelling mode.
*/
private final Collection<Provider<ControlCenterPanel>> panelProvidersModelling;
/**
* Providers for panels shown in operating mode.
*/
private final Collection<Provider<ControlCenterPanel>> panelProvidersOperating;
/**
* An about dialog.
*/
private final AboutDialog aboutDialog;
/**
* Panels currently active/shown.
*/
private final Set<ControlCenterPanel> activePanels = Collections.synchronizedSet(new HashSet<>());
/**
* The application running this panel.
*/
private final KernelClientApplication application;
/**
* Where this instance registers for application events.
*/
private final EventSource eventSource;
/**
* The service portal to use for kernel interaction.
*/
private final KernelServicePortal servicePortal;
/**
* The portal manager.
*/
private final PortalManager portalManager;
/**
* The call wrapper to use for service calls.
*/
private final CallWrapper callWrapper;
/**
* Indicates whether this component is initialized.
*/
private boolean initialized;
/**
* The ControlCenterInfoHandler.
*/
private ControlCenterInfoHandler infoHandler;
/**
* The current Model Name.
*/
private String currentModel = "";
/**
* Creates new form KernelControlCenter.
*
* @param application The application running this panel.
* @param servicePortal The service portal to use for kernel interaction.
* @param callWrapper The call wrapper to use for service calls.
* @param portalManager The portal manager.
* @param eventSource Where this instance registers for application events.
* @param controlCenterInfoHandlerFactory The factory providing a ControlCenterInfoHandler.
* @param panelProvidersModelling Providers for panels in modelling mode.
* @param panelProvidersOperating Providers for panels in operating mode.
*/
@Inject
@SuppressWarnings("this-escape")
public KernelControlCenter(
@Nonnull
KernelClientApplication application,
@Nonnull
KernelServicePortal servicePortal,
@Nonnull
@ServiceCallWrapper
CallWrapper callWrapper,
@Nonnull
PortalManager portalManager,
@Nonnull
@ApplicationEventBus
EventSource eventSource,
@Nonnull
ControlCenterInfoHandlerFactory controlCenterInfoHandlerFactory,
@Nonnull
@ActiveInModellingMode
Collection<Provider<ControlCenterPanel>> panelProvidersModelling,
@Nonnull
@ActiveInOperatingMode
Collection<Provider<ControlCenterPanel>> panelProvidersOperating
) {
this.application = requireNonNull(application, "application");
this.servicePortal = requireNonNull(servicePortal, "servicePortal");
this.callWrapper = requireNonNull(callWrapper, "callWrapper");
this.portalManager = requireNonNull(portalManager, "portalManager");
this.eventSource = requireNonNull(eventSource, "eventSource");
this.controlCenterInfoHandlerFactoy = requireNonNull(
controlCenterInfoHandlerFactory,
"controlCenterInfoHandlerFactory"
);
this.panelProvidersModelling = requireNonNull(
panelProvidersModelling,
"panelProvidersModelling"
);
this.panelProvidersOperating = requireNonNull(
panelProvidersOperating,
"panelProvidersOperating"
);
initComponents();
setIconImages(Icons.getOpenTCSIcons());
aboutDialog = new AboutDialog(this, false);
aboutDialog.setAlwaysOnTop(true);
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void initialize() {
if (initialized) {
LOG.debug("Already initialized.");
return;
}
registerControlCenterInfoHandler();
eventSource.subscribe(this);
enteringKernelState(Kernel.State.MODELLING);
try {
EventQueue.invokeAndWait(() -> setVisible(true));
}
catch (InterruptedException | InvocationTargetException exc) {
throw new IllegalStateException("Unexpected exception initializing", exc);
}
initialized = true;
}
@Override
public void terminate() {
if (!initialized) {
LOG.debug("Not initialized");
return;
}
removePanels(activePanels);
eventSource.unsubscribe(this);
eventSource.unsubscribe(infoHandler);
// Hide the window.
setVisible(false);
dispose();
initialized = false;
}
private void onKernelConnect() {
try {
Kernel.State kernelState = callWrapper.call(() -> servicePortal.getState());
enteringKernelState(kernelState);
}
catch (Exception ex) {
LOG.warn("Error getting the kernel state", ex);
}
}
private void onKernelDisconnect() {
leavingKernelState(Kernel.State.OPERATING);
enteringKernelState(Kernel.State.MODELLING);
}
@Override
public void onEvent(Object event) {
if (event instanceof ClientConnectionMode) {
ClientConnectionMode applicationState = (ClientConnectionMode) event;
switch (applicationState) {
case ONLINE:
onKernelConnect();
break;
case OFFLINE:
onKernelDisconnect();
break;
default:
LOG.debug("Unhandled connection state: {}", applicationState.name());
}
}
else if (event instanceof PortalManager.ConnectionState) {
PortalManager.ConnectionState connectionState = (PortalManager.ConnectionState) event;
switch (connectionState) {
case CONNECTED:
updateWindowTitle();
break;
case DISCONNECTED:
updateWindowTitle();
break;
default:
}
menuButtonConnect.setEnabled(!portalManager.isConnected());
menuButtonDisconnect.setEnabled(portalManager.isConnected());
}
else if (event instanceof KernelStateTransitionEvent) {
KernelStateTransitionEvent stateEvent = (KernelStateTransitionEvent) event;
if (!stateEvent.isTransitionFinished()) {
leavingKernelState(stateEvent.getLeftState());
}
else {
enteringKernelState(stateEvent.getEnteredState());
}
}
else if (event instanceof ModelTransitionEvent) {
ModelTransitionEvent modelEvent = (ModelTransitionEvent) event;
updateModelName(modelEvent.getNewModelName());
}
}
/**
* Perfoms some tasks when a state is being leaved.
*
* @param oldState The state we're leaving
*/
private void leavingKernelState(Kernel.State oldState) {
requireNonNull(oldState, "oldState");
removePanels(activePanels);
activePanels.clear();
}
/**
* Notifies this control center that the kernel has entered a different state.
*
* @param newState
*/
private void enteringKernelState(Kernel.State newState) {
requireNonNull(newState, "newState");
switch (newState) {
case OPERATING:
addPanels(panelProvidersOperating);
break;
case MODELLING:
addPanels(panelProvidersModelling);
break;
default:
// Do nada.
}
// Updating the window title
updateWindowTitle();
}
private void addPanels(Collection<Provider<ControlCenterPanel>> providers) {
for (Provider<ControlCenterPanel> provider : providers) {
SwingUtilities.invokeLater(() -> addPanel(provider.get()));
}
}
private void addPanel(ControlCenterPanel panel) {
panel.initialize();
activePanels.add(panel);
tabbedPaneMain.add(panel.getTitle(), panel);
}
private void removePanels(Collection<ControlCenterPanel> panels) {
List<ControlCenterPanel> panelsCopy = new ArrayList<>(panels);
SwingUtilities.invokeLater(() -> {
for (ControlCenterPanel panel : panelsCopy) {
tabbedPaneMain.remove(panel);
panel.terminate();
}
});
}
/**
* Updates the model name to the current one.
*
* @param newModelName The new/updated model name.
*/
private void updateModelName(String newModelName) {
this.currentModel = newModelName;
updateWindowTitle();
}
/**
* Adds the ControlCenterInfoHandler to the root logger.
*/
private void registerControlCenterInfoHandler() {
infoHandler = controlCenterInfoHandlerFactoy.createHandler(loggingTextArea);
eventSource.subscribe(infoHandler);
}
private void updateWindowTitle() {
String titleBase = BUNDLE.getString("kernelControlCenter.title");
String loadedModel = currentModel.equals("") ? "" : " - " + "\"" + currentModel + "\"";
String connectedTo = " - " + BUNDLE.getString("kernelControlCenter.title.connectedTo")
+ portalManager.getDescription()
+ " (" + portalManager.getHost()
+ ":"
+ portalManager.getPort() + ")";
setTitle(titleBase + loadedModel + connectedTo);
}
// FORMATTER:OFF
// CHECKSTYLE:OFF
// Generated code starts here.
/**
* 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;
tabbedPaneMain = new javax.swing.JTabbedPane();
loggingPanel = new javax.swing.JPanel();
loggingScrollPane = new javax.swing.JScrollPane();
loggingTextArea = new javax.swing.JTextArea();
loggingPropertyPanel = new javax.swing.JPanel();
autoScrollCheckBox = new javax.swing.JCheckBox();
menuBarMain = new javax.swing.JMenuBar();
menuKernel = new javax.swing.JMenu();
menuButtonConnect = new javax.swing.JMenuItem();
menuButtonDisconnect = new javax.swing.JMenuItem();
jSeparator1 = new javax.swing.JPopupMenu.Separator();
menuButtonExit = new javax.swing.JMenuItem();
menuHelp = new javax.swing.JMenu();
menuAbout = new javax.swing.JMenuItem();
setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("i18n/org/opentcs/kernelcontrolcenter/Bundle"); // NOI18N
setTitle(bundle.getString("kernelControlCenter.title")); // NOI18N
setMinimumSize(new java.awt.Dimension(1200, 750));
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent evt) {
formWindowClosing(evt);
}
});
loggingPanel.setLayout(new java.awt.BorderLayout());
loggingTextArea.setEditable(false);
loggingScrollPane.setViewportView(loggingTextArea);
loggingPanel.add(loggingScrollPane, java.awt.BorderLayout.CENTER);
loggingPropertyPanel.setLayout(new java.awt.GridBagLayout());
autoScrollCheckBox.setSelected(true);
autoScrollCheckBox.setText(bundle.getString("kernelControlCenter.checkBox_autoScroll.text")); // NOI18N
autoScrollCheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
autoScrollCheckBoxActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.weightx = 1.0;
loggingPropertyPanel.add(autoScrollCheckBox, gridBagConstraints);
loggingPanel.add(loggingPropertyPanel, java.awt.BorderLayout.PAGE_START);
tabbedPaneMain.addTab(bundle.getString("kernelControlCenter.tab_logging.title"), loggingPanel); // NOI18N
getContentPane().add(tabbedPaneMain, java.awt.BorderLayout.CENTER);
menuKernel.setText("KernelControlCenter");
menuButtonConnect.setText(bundle.getString("kernelControlCenter.menu_kernel.menuItem_connect.text")); // NOI18N
menuButtonConnect.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
menuButtonConnectActionPerformed(evt);
}
});
menuKernel.add(menuButtonConnect);
menuButtonDisconnect.setText(bundle.getString("kernelControlCenter.menu_kernel.menuItem_disconnect.text")); // NOI18N
menuButtonDisconnect.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
menuButtonDisconnectActionPerformed(evt);
}
});
menuKernel.add(menuButtonDisconnect);
menuKernel.add(jSeparator1);
menuButtonExit.setText(bundle.getString("kernelControlCenter.menu_kernel.menuItem_exit.text")); // NOI18N
menuButtonExit.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
menuButtonExitActionPerformed(evt);
}
});
menuKernel.add(menuButtonExit);
menuBarMain.add(menuKernel);
menuHelp.setText(bundle.getString("kernelControlCenter.menu_help.text")); // NOI18N
menuAbout.setText(bundle.getString("kernelControlCenter.menu_about.text")); // NOI18N
menuAbout.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
menuAboutActionPerformed(evt);
}
});
menuHelp.add(menuAbout);
menuBarMain.add(menuHelp);
setJMenuBar(menuBarMain);
setSize(new java.awt.Dimension(1208, 782));
setLocationRelativeTo(null);
}// </editor-fold>//GEN-END:initComponents
// CHECKSTYLE:ON
// FORMATTER:ON
private void menuButtonExitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_menuButtonExitActionPerformed
application.terminate();
}//GEN-LAST:event_menuButtonExitActionPerformed
private void autoScrollCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_autoScrollCheckBoxActionPerformed
if (autoScrollCheckBox.isSelected()) {
infoHandler.setAutoScroll(true);
}
else {
infoHandler.setAutoScroll(false);
}
}//GEN-LAST:event_autoScrollCheckBoxActionPerformed
private void menuAboutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_menuAboutActionPerformed
aboutDialog.setLocationRelativeTo(null);
aboutDialog.setVisible(true);
}//GEN-LAST:event_menuAboutActionPerformed
private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
application.terminate();
}//GEN-LAST:event_formWindowClosing
private void menuButtonConnectActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_menuButtonConnectActionPerformed
application.online(false);
}//GEN-LAST:event_menuButtonConnectActionPerformed
private void menuButtonDisconnectActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_menuButtonDisconnectActionPerformed
application.offline();
}//GEN-LAST:event_menuButtonDisconnectActionPerformed
// FORMATTER:OFF
// CHECKSTYLE:OFF
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JCheckBox autoScrollCheckBox;
private javax.swing.JPopupMenu.Separator jSeparator1;
private javax.swing.JPanel loggingPanel;
private javax.swing.JPanel loggingPropertyPanel;
private javax.swing.JScrollPane loggingScrollPane;
private javax.swing.JTextArea loggingTextArea;
private javax.swing.JMenuItem menuAbout;
private javax.swing.JMenuBar menuBarMain;
private javax.swing.JMenuItem menuButtonConnect;
private javax.swing.JMenuItem menuButtonDisconnect;
private javax.swing.JMenuItem menuButtonExit;
private javax.swing.JMenu menuHelp;
private javax.swing.JMenu menuKernel;
private javax.swing.JTabbedPane tabbedPaneMain;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
}

View File

@@ -0,0 +1,186 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import org.opentcs.common.ClientConnectionMode;
import org.opentcs.common.KernelClientApplication;
import org.opentcs.common.PortalManager;
import org.opentcs.customizations.ApplicationEventBus;
import org.opentcs.kernelcontrolcenter.exchange.KernelEventFetcher;
import org.opentcs.kernelcontrolcenter.util.KernelControlCenterConfiguration;
import org.opentcs.util.event.EventBus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The kernel control center application's entry point.
*/
public class KernelControlCenterApplication
implements
KernelClientApplication {
/**
* This class' logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(KernelControlCenterApplication.class);
/**
* The instance fetching for kernel events.
*/
private final KernelEventFetcher eventFetcher;
/**
* The actual kernel control center.
*/
private final KernelControlCenter kernelControlCenter;
/**
* The service portal manager.
*/
private final PortalManager portalManager;
/**
* The application's event bus.
*/
private final EventBus eventBus;
/**
* The application's configuration.
*/
private final KernelControlCenterConfiguration configuration;
/**
* Whether this application is online or not.
*/
private ConnectionState connectionState = ConnectionState.OFFLINE;
/**
* Whether this application is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param eventFetcher The instance fetching for kernel events.
* @param kernelControlCenter The actual kernel control center.
* @param portalManager The service portal manager.
* @param eventBus The application's event bus.
* @param configuration The application's configuration.
*/
@Inject
public KernelControlCenterApplication(
KernelEventFetcher eventFetcher,
KernelControlCenter kernelControlCenter,
PortalManager portalManager,
@ApplicationEventBus
EventBus eventBus,
KernelControlCenterConfiguration configuration
) {
this.eventFetcher = requireNonNull(eventFetcher, "eventHub");
this.kernelControlCenter = requireNonNull(kernelControlCenter, "kernelControlCenter");
this.portalManager = requireNonNull(portalManager, "portalManager");
this.eventBus = requireNonNull(eventBus, "eventBus");
this.configuration = requireNonNull(configuration, "configuration");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
kernelControlCenter.initialize();
eventFetcher.initialize();
// Trigger initial connect
online(configuration.connectAutomaticallyOnStartup());
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
// If we want to terminate but are still online, go offline first
offline();
eventFetcher.terminate();
kernelControlCenter.terminate();
initialized = false;
}
@Override
public void online(boolean autoConnect) {
if (isOnline() || isConnecting()) {
return;
}
connectionState = ConnectionState.CONNECTING;
if (portalManager.connect(toConnectionMode(autoConnect))) {
LOG.info("Switching application state to online...");
connectionState = ConnectionState.ONLINE;
eventBus.onEvent(ClientConnectionMode.ONLINE);
}
else {
connectionState = ConnectionState.OFFLINE;
}
}
@Override
public void offline() {
if (!isOnline() && !isConnecting()) {
return;
}
portalManager.disconnect();
LOG.info("Switching application state to offline...");
connectionState = ConnectionState.OFFLINE;
eventBus.onEvent(ClientConnectionMode.OFFLINE);
}
@Override
public boolean isOnline() {
return connectionState == ConnectionState.ONLINE;
}
/**
* Returns <code>true</code> if, and only if the control center is trying to establish a
* connection to the kernel.
*
* @return <code>true</code> if, and only if the control center is trying to establish a
* connection to the kernel
*/
public boolean isConnecting() {
return connectionState == ConnectionState.CONNECTING;
}
private PortalManager.ConnectionMode toConnectionMode(boolean autoConnect) {
return autoConnect ? PortalManager.ConnectionMode.AUTO : PortalManager.ConnectionMode.MANUAL;
}
/**
* An enum to display the different states of the control center application connection to the
* kernel.
*/
private enum ConnectionState {
/**
* The control center is not connected to the kernel and is not trying to.
*/
OFFLINE,
/**
* The control center is currently trying to connect to the kernel.
*/
CONNECTING,
/**
* The control center is connected to the kernel.
*/
ONLINE
}
}

View File

@@ -0,0 +1,138 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.exchange;
import static java.util.Objects.requireNonNull;
import static org.opentcs.kernelcontrolcenter.I18nKernelControlCenter.BUNDLE_PATH;
import jakarta.inject.Inject;
import java.util.ResourceBundle;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import javax.swing.JOptionPane;
import org.opentcs.common.KernelClientApplication;
import org.opentcs.common.PortalManager;
import org.opentcs.components.kernel.services.ServiceUnavailableException;
import org.opentcs.util.CallWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The default {@link CallWrapper} implementation used for calling kernel service methods.
* <p>
* If a service method is called using this implementation and the corresponding service is no
* longer available, this implementation will ask to retry the service method call:
* </p>
* <ul>
* <li>
* If 'Retry - Yes' is selected, this implementation will handle reestablishment of the kernel
* connection and (upon successful reestablishment) try to call the service method again.
* </li>
* <li>
* If 'Retry - No' is selected, this implementation will throw the exception thrown by the service
* mehtod call itself.
* </li>
* <li>
* If 'Cancel' is selected, this implementation will throw the exception thrown by the service
* mehtod call itself and additionally will notify the application it's no longer online, i.e.
* connected to the kernel.
* </li>
* </ul>
*/
public class DefaultServiceCallWrapper
implements
CallWrapper {
/**
* This class' logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(DefaultServiceCallWrapper.class);
/**
* This class' resource bundle.
*/
private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(BUNDLE_PATH);
/**
* The application using this utility.
*/
private final KernelClientApplication application;
/**
* The portal manager taking care of the portal connection.
*/
private final PortalManager portalManager;
/**
* Creates a new instance.
*
* @param application The application.
* @param portalManager The portal manager.
*/
@Inject
public DefaultServiceCallWrapper(
KernelClientApplication application,
PortalManager portalManager
) {
this.application = requireNonNull(application, "application");
this.portalManager = requireNonNull(portalManager, "portalManager");
}
@Override
public <R> R call(Callable<R> callable)
throws Exception {
boolean retry = true;
Exception failureReason = null;
while (retry) {
try {
return callable.call();
}
catch (Exception ex) {
LOG.warn("Failed to call remote service method: {}", callable, ex);
failureReason = ex;
if (ex instanceof ServiceUnavailableException) {
portalManager.disconnect();
retry = showRetryDialog();
}
else {
retry = false;
}
}
}
// At this point the method call failed and we don't want to try it again anymore.
// Therefore throw the exception we caught last.
throw failureReason;
}
@Override
public void call(Runnable runnable)
throws Exception {
Callable<Object> callable = Executors.callable(runnable);
call(callable);
}
private boolean showRetryDialog() {
int dialogSelection
= JOptionPane.showConfirmDialog(
null,
BUNDLE.getString("defaultServiceCallWrapper.optionPane_retryConfirmation.message"),
BUNDLE.getString("defaultServiceCallWrapper.optionPane_retryConfirmation.title"),
JOptionPane.YES_NO_CANCEL_OPTION
);
switch (dialogSelection) {
case JOptionPane.YES_OPTION:
// Only retry if we connected successfully
return portalManager.connect(PortalManager.ConnectionMode.RECONNECT);
case JOptionPane.NO_OPTION:
return false;
case JOptionPane.CANCEL_OPTION:
application.offline();
return false;
case JOptionPane.CLOSED_OPTION:
return false;
default:
return false;
}
}
}

View File

@@ -0,0 +1,214 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.exchange;
import static java.util.Objects.requireNonNull;
import static org.opentcs.util.Assertions.checkInRange;
import jakarta.inject.Inject;
import java.util.List;
import org.opentcs.access.Kernel;
import org.opentcs.access.KernelServicePortal;
import org.opentcs.access.KernelStateTransitionEvent;
import org.opentcs.common.ClientConnectionMode;
import org.opentcs.common.KernelClientApplication;
import org.opentcs.common.PortalManager;
import org.opentcs.components.Lifecycle;
import org.opentcs.customizations.ApplicationEventBus;
import org.opentcs.customizations.ServiceCallWrapper;
import org.opentcs.util.CallWrapper;
import org.opentcs.util.CyclicTask;
import org.opentcs.util.event.EventBus;
import org.opentcs.util.event.EventHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Controls a task that periodically fetches for kernel events.
*/
public class KernelEventFetcher
implements
EventHandler,
Lifecycle {
/**
* This class' logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(KernelEventFetcher.class);
/**
* The time to wait between event fetches with the service portal (in ms).
*/
private final long eventFetchInterval = 100;
/**
* The time to wait for events to arrive when fetching (in ms).
*/
private final long eventFetchTimeout = 1000;
/**
* The application using this event hub.
*/
private final KernelClientApplication application;
/**
* The service portal to fetch events from.
*/
private final KernelServicePortal servicePortal;
/**
* The call wrapper to use.
*/
private final CallWrapper callWrapper;
/**
* The application's event bus.
*/
private final EventBus eventBus;
/**
* The task fetching the service portal for new events.
*/
private EventFetcherTask eventFetcherTask;
/**
* Whether this event hub is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param application The application using this event hub.
* @param servicePortal The service portal to fetch events from.
* @param callWrapper The call wrapper to use for fetching events.
* @param eventBus The application's event bus.
*/
@Inject
public KernelEventFetcher(
KernelClientApplication application,
KernelServicePortal servicePortal,
@ServiceCallWrapper
CallWrapper callWrapper,
@ApplicationEventBus
EventBus eventBus
) {
this.application = requireNonNull(application, "application");
this.servicePortal = requireNonNull(servicePortal, "servicePortal");
this.callWrapper = requireNonNull(callWrapper, "callWrapper");
this.eventBus = requireNonNull(eventBus, "eventBus");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
eventBus.subscribe(this);
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
eventBus.unsubscribe(this);
initialized = false;
}
@Override
public void onEvent(Object event) {
if (event == PortalManager.ConnectionState.DISCONNECTING) {
onKernelDisconnect();
}
else if (event instanceof ClientConnectionMode) {
ClientConnectionMode applicationState = (ClientConnectionMode) event;
switch (applicationState) {
case ONLINE:
onKernelConnect();
break;
case OFFLINE:
onKernelDisconnect();
break;
default:
LOG.debug("Unhandled portal connection state: {}", applicationState.name());
}
}
}
private void onKernelConnect() {
if (eventFetcherTask != null) {
return;
}
eventFetcherTask = new EventFetcherTask(eventFetchInterval, eventFetchTimeout);
Thread eventFetcherThread = new Thread(eventFetcherTask, getClass().getName() + "-fetcherTask");
eventFetcherThread.start();
}
private void onKernelDisconnect() {
if (eventFetcherTask == null) {
return;
}
// Stop polling for events.
eventFetcherTask.terminateAndWait();
eventFetcherTask = null;
}
/**
* A task fetching the service portal for events in regular intervals.
*/
private class EventFetcherTask
extends
CyclicTask {
/**
* The poll timeout.
*/
private final long timeout;
/**
* Creates a new instance.
*
* @param interval The time to wait between polls in ms.
* @param timeout The timeout in ms for which to wait for events to arrive with each polling
* call.
*/
private EventFetcherTask(long interval, long timeout) {
super(interval);
this.timeout = checkInRange(timeout, 1, Long.MAX_VALUE, "timeout");
}
@Override
protected void runActualTask() {
boolean shutDown = false;
try {
LOG.debug("Fetching remote kernel for events");
List<Object> events = callWrapper.call(() -> servicePortal.fetchEvents(timeout));
for (Object event : events) {
LOG.debug("Processing fetched event: {}", event);
// Forward received events to all registered listeners.
eventBus.onEvent(event);
// Check if the kernel notifies us about a state change.
if (event instanceof KernelStateTransitionEvent) {
KernelStateTransitionEvent stateEvent = (KernelStateTransitionEvent) event;
// If the kernel switches to SHUTDOWN, remember to shut down.
shutDown = stateEvent.getEnteredState() == Kernel.State.SHUTDOWN;
}
}
}
catch (Exception exc) {
LOG.error("Exception fetching events", exc);
}
if (shutDown) {
application.offline();
}
}
}
}

View File

@@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.exchange;
import org.opentcs.configuration.ConfigurationEntry;
import org.opentcs.configuration.ConfigurationPrefix;
/**
* Provides methods to configure the ssl connection.
*/
@ConfigurationPrefix(SslConfiguration.PREFIX)
public interface SslConfiguration {
/**
* This configuration's prefix.
*/
String PREFIX = "ssl";
@ConfigurationEntry(
type = "Boolean",
description = "Whether to use SSL to encrypt RMI connections to the kernel.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_connection_0"
)
boolean enable();
@ConfigurationEntry(
type = "String",
description = "The path to the SSL truststore.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_connection_1"
)
String truststoreFile();
@ConfigurationEntry(
type = "String",
description = "The password for the SSL truststore.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_connection_2"
)
String truststorePassword();
}

View File

@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.peripherals;
import java.awt.Component;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import org.opentcs.drivers.peripherals.PeripheralCommAdapterDescription;
/**
* ListCellRenderer for the adapter combo box.
*/
final class AdapterFactoryCellRenderer
implements
ListCellRenderer<PeripheralCommAdapterDescription> {
/**
* A default renderer for creating the label.
*/
private final DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer();
/**
* Creates a new instance.
*/
AdapterFactoryCellRenderer() {
}
@Override
public Component getListCellRendererComponent(
JList<? extends PeripheralCommAdapterDescription> list,
PeripheralCommAdapterDescription value,
int index,
boolean isSelected,
boolean cellHasFocus
) {
JLabel label = (JLabel) defaultRenderer.getListCellRendererComponent(
list,
value,
index,
isSelected,
cellHasFocus
);
if (value != null) {
label.setText(value.getDescription());
}
else {
label.setText(" ");
}
return label;
}
}

View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.peripherals;
import java.awt.Component;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import org.opentcs.drivers.peripherals.PeripheralCommAdapterDescription;
/**
* A {@link TableCellRenderer} for {@link PeripheralCommAdapterDescription} instances.
* This class provides a representation of any PeripheralCommAdapterDescription instance by writing
* its actual description on a JLabel.
*/
class CommAdapterFactoryTableCellRenderer
extends
DefaultTableCellRenderer {
CommAdapterFactoryTableCellRenderer() {
}
@Override
public Component getTableCellRendererComponent(
JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column
)
throws IllegalArgumentException {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (value == null) {
setText("");
}
else if (value instanceof PeripheralCommAdapterDescription) {
setText(((PeripheralCommAdapterDescription) value).getDescription());
}
else {
throw new IllegalArgumentException("value");
}
return this;
}
}

View File

@@ -0,0 +1,137 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.peripherals;
import static java.util.Objects.requireNonNull;
import jakarta.annotation.Nonnull;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import org.opentcs.data.model.Location;
import org.opentcs.data.model.TCSResourceReference;
import org.opentcs.drivers.peripherals.PeripheralCommAdapterDescription;
import org.opentcs.drivers.peripherals.PeripheralProcessModel;
/**
* An entry for a peripheral device registered with the kernel.
*/
public class LocalPeripheralEntry {
/**
* Used for implementing property change events.
*/
@SuppressWarnings("this-escape")
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
/**
* The location representing the peripheral device.
*/
private final TCSResourceReference<Location> location;
/**
* The description of the attached peripheral comm adapter.
*/
private PeripheralCommAdapterDescription attachedCommAdapter;
/**
* The process model that describes the peripheral device's state.
*/
private PeripheralProcessModel processModel;
/**
* Creates a new instance.
*
* @param location The location representing the peripheral device.
* @param attachedCommAdapter The description of the attached peripheral comm adapter.
* @param processModel The process model that describes the peripheral device's state.
*/
public LocalPeripheralEntry(
@Nonnull
TCSResourceReference<Location> location,
@Nonnull
PeripheralCommAdapterDescription attachedCommAdapter,
@Nonnull
PeripheralProcessModel processModel
) {
this.location = requireNonNull(location, "location");
this.attachedCommAdapter = requireNonNull(attachedCommAdapter, "attachedCommAdapter");
this.processModel = requireNonNull(processModel, "processModel");
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
/**
* Returns the location representing the peripheral device.
*
* @return The location representing the peripheral device.
*/
@Nonnull
public TCSResourceReference<Location> getLocation() {
return location;
}
/**
* Returns the description of the attached peripheral comm adapter.
*
* @return The description of the attached peripheral comm adapter.
*/
@Nonnull
public PeripheralCommAdapterDescription getAttachedCommAdapter() {
return attachedCommAdapter;
}
public void setAttachedCommAdapter(
@Nonnull
PeripheralCommAdapterDescription attachedCommAdapter
) {
PeripheralCommAdapterDescription oldAttachedCommAdapter = this.attachedCommAdapter;
this.attachedCommAdapter = requireNonNull(attachedCommAdapter, "attachedCommAdapter");
pcs.firePropertyChange(
Attribute.ATTACHED_COMM_ADAPTER.name(),
oldAttachedCommAdapter,
attachedCommAdapter
);
}
/**
* Returns the process model that describes the peripheral device's state.
*
* @return The process model that describes the peripheral device's state.
*/
@Nonnull
public PeripheralProcessModel getProcessModel() {
return processModel;
}
public void setProcessModel(
@Nonnull
PeripheralProcessModel processModel
) {
PeripheralProcessModel oldProcessModel = this.processModel;
this.processModel = requireNonNull(processModel, "processModel");
pcs.firePropertyChange(
Attribute.PROCESS_MODEL.name(),
oldProcessModel,
processModel
);
}
/**
* Enum elements used as notification arguments to specify which argument changed.
*/
public enum Attribute {
/**
* Indicates a change of the process model.
*/
PROCESS_MODEL,
/**
* Indicates a change of the attached comm adapter.
*/
ATTACHED_COMM_ADAPTER
}
}

View File

@@ -0,0 +1,173 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.peripherals;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.opentcs.access.KernelServicePortal;
import org.opentcs.components.Lifecycle;
import org.opentcs.customizations.ApplicationEventBus;
import org.opentcs.customizations.ServiceCallWrapper;
import org.opentcs.data.model.Location;
import org.opentcs.data.model.TCSResourceReference;
import org.opentcs.drivers.peripherals.PeripheralProcessModel;
import org.opentcs.drivers.peripherals.management.PeripheralAttachmentEvent;
import org.opentcs.drivers.peripherals.management.PeripheralAttachmentInformation;
import org.opentcs.drivers.peripherals.management.PeripheralProcessModelEvent;
import org.opentcs.util.CallWrapper;
import org.opentcs.util.event.EventHandler;
import org.opentcs.util.event.EventSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A pool of {@link LocalPeripheralEntry}'s for every location in the kernel that represents a
* peripheral device.
*/
public class LocalPeripheralEntryPool
implements
EventHandler,
Lifecycle {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(LocalPeripheralEntryPool.class);
/**
* The service portal to use for kernel interactions.
*/
private final KernelServicePortal servicePortal;
/**
* The call wrapper to use for service calls.
*/
private final CallWrapper callWrapper;
/**
* Where this instance registers for application events.
*/
private final EventSource eventSource;
/**
* The entries of this pool.
*/
private final Map<TCSResourceReference<Location>, LocalPeripheralEntry> entries = new HashMap<>();
/**
* Whether the pool is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param servicePortal The service portal to use for kernel interactions.
* @param callWrapper The call wrapper to use for service calls.
* @param eventSource Where this instance registers for application events.
*/
@Inject
public LocalPeripheralEntryPool(
KernelServicePortal servicePortal,
@ServiceCallWrapper
CallWrapper callWrapper,
@ApplicationEventBus
EventSource eventSource
) {
this.servicePortal = requireNonNull(servicePortal, "servicePortal");
this.callWrapper = requireNonNull(callWrapper, "callWrapper");
this.eventSource = requireNonNull(eventSource, "eventSource");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
try {
initializeEntryMap();
LOG.debug("Initialized peripheral entry pool: {}", entries);
}
catch (Exception e) {
LOG.warn("Error initializing peripheral entry pool.", e);
entries.clear();
return;
}
eventSource.subscribe(this);
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
eventSource.unsubscribe(this);
entries.clear();
initialized = false;
}
@Override
public void onEvent(Object event) {
if (event instanceof PeripheralProcessModelEvent) {
onPeripheralProcessModelEvent((PeripheralProcessModelEvent) event);
}
else if (event instanceof PeripheralAttachmentEvent) {
onPeripheralAttachmentEvent((PeripheralAttachmentEvent) event);
}
}
public Map<TCSResourceReference<Location>, LocalPeripheralEntry> getEntries() {
return entries;
}
private void initializeEntryMap()
throws Exception {
Set<Location> locations
= callWrapper.call(() -> servicePortal.getPlantModelService().fetchObjects(Location.class));
for (Location location : locations) {
PeripheralAttachmentInformation ai = callWrapper.call(() -> {
return servicePortal.getPeripheralService()
.fetchAttachmentInformation(location.getReference());
});
PeripheralProcessModel processModel = callWrapper.call(
() -> servicePortal.getPeripheralService().fetchProcessModel(location.getReference())
);
entries.put(
location.getReference(), new LocalPeripheralEntry(
location.getReference(),
ai.getAttachedCommAdapter(),
processModel
)
);
}
}
private void onPeripheralProcessModelEvent(PeripheralProcessModelEvent event) {
if (!entries.containsKey(event.getLocation())) {
LOG.warn("Received an event for an unknown location: {}", event.getLocation().getName());
return;
}
entries.get(event.getLocation()).setProcessModel(event.getProcessModel());
}
private void onPeripheralAttachmentEvent(PeripheralAttachmentEvent event) {
if (!entries.containsKey(event.getLocation())) {
LOG.warn("Received an event for an unknown location: {}", event.getLocation().getName());
return;
}
entries.get(event.getLocation())
.setAttachedCommAdapter(event.getAttachmentInformation().getAttachedCommAdapter());
}
}

View File

@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.peripherals;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Objects;
import javax.swing.JComboBox;
import org.opentcs.drivers.peripherals.PeripheralCommAdapterDescription;
/**
* A wide combobox which sets the selected item when receiving an update event from a
* {@link LocalPeripheralEntry}.
*/
public class PeripheralAdapterComboBox
extends
JComboBox<PeripheralCommAdapterDescription>
implements
PropertyChangeListener {
/**
* Creates a new instance.
*/
public PeripheralAdapterComboBox() {
}
@Override
public PeripheralCommAdapterDescription getSelectedItem() {
return (PeripheralCommAdapterDescription) super.getSelectedItem();
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (!(evt.getSource() instanceof LocalPeripheralEntry)) {
return;
}
LocalPeripheralEntry entry = (LocalPeripheralEntry) evt.getSource();
if (Objects.equals(entry.getAttachedCommAdapter(), getModel().getSelectedItem())) {
return;
}
super.setSelectedItem(entry.getAttachedCommAdapter());
}
}

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.6" maxVersion="1.6" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Container class="javax.swing.JPanel" name="noPeripheralDevicePanel">
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="noPeripheralDeviceLabel">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font component="noPeripheralDeviceLabel" property="font" relativeSize="true" size="3"/>
</FontInfo>
</Property>
<Property name="horizontalAlignment" type="int" value="0"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="peripheralDetailPanel.label_noPeripheralDeviceAttached.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</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>
</Component>
</SubComponents>
</Container>
</NonVisualComponents>
<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="&lt;User Code&gt;">
<Connection PropertyName="titleX" code="DEFAULT_BORDER_TITLE" type="code"/>
</TitledBorder>
</Border>
</Property>
</Properties>
<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,1,-63,0,0,2,-123"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JTabbedPane" name="tabbedPane">
<Properties>
<Property name="tabLayoutPolicy" type="int" value="1"/>
</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.support.JTabbedPaneSupportLayout"/>
</Container>
</SubComponents>
</Form>

View File

@@ -0,0 +1,265 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.peripherals;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.swing.JPanel;
import javax.swing.border.TitledBorder;
import org.opentcs.components.Lifecycle;
import org.opentcs.drivers.peripherals.management.PeripheralCommAdapterPanel;
import org.opentcs.drivers.peripherals.management.PeripheralCommAdapterPanelFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Displays the comm adapter panels for a given peripheral device.
*/
public class PeripheralDetailPanel
extends
JPanel
implements
PropertyChangeListener,
Lifecycle {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(PeripheralDetailPanel.class);
/**
* A panel's default border title.
*/
private static final String DEFAULT_BORDER_TITLE = "";
/**
* The adapter specific list of panels.
*/
private final List<PeripheralCommAdapterPanel> customPanelList = new ArrayList<>();
/**
* The set of factories to create adapter specific panels with.
*/
private final Set<PeripheralCommAdapterPanelFactory> panelFactories;
/**
* The peripheral device that is currently associated with/being displayed in this panel.
*/
private LocalPeripheralEntry peripheralEntry;
/**
* Whether this panel is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param panelFactories The factories to create adapter specific panels with.
*/
@Inject
@SuppressWarnings("this-escape")
public PeripheralDetailPanel(Set<PeripheralCommAdapterPanelFactory> panelFactories) {
this.panelFactories = requireNonNull(panelFactories, "panelFactories");
initComponents();
// Make sure we start with an empty panel.
detachFromEntry();
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
for (PeripheralCommAdapterPanelFactory panelFactory : panelFactories) {
panelFactory.initialize();
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
detachFromEntry();
for (PeripheralCommAdapterPanelFactory panelFactory : panelFactories) {
panelFactory.terminate();
}
initialized = false;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (!(evt.getSource() instanceof LocalPeripheralEntry)) {
return;
}
LocalPeripheralEntry entry = (LocalPeripheralEntry) evt.getSource();
if (!Objects.equals(entry, peripheralEntry)) {
// Since we're registered only to the currently selected/attached entry, this should never
// happen.
return;
}
if (Objects.equals(
evt.getPropertyName(),
LocalPeripheralEntry.Attribute.PROCESS_MODEL.name()
)) {
for (PeripheralCommAdapterPanel panel : customPanelList) {
panel.processModelChanged(entry.getProcessModel());
}
}
}
/**
* Attaches this panel to a peripheral device.
*
* @param newPeripheralEntry The peripheral entry to attach to.
*/
public void attachToEntry(LocalPeripheralEntry newPeripheralEntry) {
requireNonNull(newPeripheralEntry, "newPeripheralEntry");
// Clean up first - but only if we're not reattaching to the same entry.
if (peripheralEntry != newPeripheralEntry) {
detachFromEntry();
}
peripheralEntry = newPeripheralEntry;
setBorderTitle(peripheralEntry.getProcessModel().getLocation().getName());
// Ensure the tabbed pane containing peripheral information is shown.
removeAll();
add(tabbedPane);
updateCustomPanels();
// Update panel contents.
validate();
if (!customPanelList.isEmpty()) {
tabbedPane.setSelectedIndex(0);
}
peripheralEntry.addPropertyChangeListener(this);
}
/**
* Detaches this panel from a peripheral device (if it is currently attached to any).
*/
private void detachFromEntry() {
if (peripheralEntry != null) {
peripheralEntry.removePropertyChangeListener(this);
removeAndClearCustomPanels();
peripheralEntry = null;
}
setBorderTitle(DEFAULT_BORDER_TITLE);
// Remove the contents of this panel.
removeAll();
add(noPeripheralDevicePanel);
// Update panel contents.
validate();
}
/**
* Removes the custom panels from this panel's tabbed pane.
*/
private void removeAndClearCustomPanels() {
for (PeripheralCommAdapterPanel panel : customPanelList) {
LOG.debug("Removing {} from tabbedPane.", panel);
tabbedPane.remove(panel);
}
customPanelList.clear();
}
/**
* Update the list of custom panels in the tabbed pane.
*/
private void updateCustomPanels() {
removeAndClearCustomPanels();
if (peripheralEntry == null) {
return;
}
for (PeripheralCommAdapterPanelFactory panelFactory : panelFactories) {
customPanelList.addAll(
panelFactory.getPanelsFor(
peripheralEntry.getAttachedCommAdapter(),
peripheralEntry.getLocation(),
peripheralEntry.getProcessModel()
)
);
}
for (PeripheralCommAdapterPanel curPanel : customPanelList) {
LOG.debug("Adding {} with title {} to tabbedPane.", curPanel, curPanel.getTitle());
tabbedPane.addTab(curPanel.getTitle(), curPanel);
}
}
/**
* Sets this panel's border title.
*
* @param title This panel's new border title.
*/
private void setBorderTitle(String title) {
requireNonNull(title, "title");
((TitledBorder) getBorder()).setTitle(title);
// Trigger a repaint - the title sometimes looks strange otherwise.
repaint();
}
// 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() {
noPeripheralDevicePanel = new javax.swing.JPanel();
noPeripheralDeviceLabel = new javax.swing.JLabel();
tabbedPane = new javax.swing.JTabbedPane();
noPeripheralDevicePanel.setLayout(new java.awt.BorderLayout());
noPeripheralDeviceLabel.setFont(noPeripheralDeviceLabel.getFont().deriveFont(noPeripheralDeviceLabel.getFont().getSize()+3f));
noPeripheralDeviceLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("i18n/org/opentcs/kernelcontrolcenter/Bundle"); // NOI18N
noPeripheralDeviceLabel.setText(bundle.getString("peripheralDetailPanel.label_noPeripheralDeviceAttached.text")); // NOI18N
noPeripheralDevicePanel.add(noPeripheralDeviceLabel, java.awt.BorderLayout.CENTER);
setBorder(javax.swing.BorderFactory.createTitledBorder(DEFAULT_BORDER_TITLE));
setLayout(new java.awt.BorderLayout());
tabbedPane.setTabLayoutPolicy(javax.swing.JTabbedPane.SCROLL_TAB_LAYOUT);
add(tabbedPane, java.awt.BorderLayout.CENTER);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel noPeripheralDeviceLabel;
private javax.swing.JPanel noPeripheralDevicePanel;
private javax.swing.JTabbedPane tabbedPane;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
}

View File

@@ -0,0 +1,244 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.peripherals;
import static java.util.Objects.requireNonNull;
import static org.opentcs.kernelcontrolcenter.I18nKernelControlCenter.BUNDLE_PATH;
import static org.opentcs.util.Assertions.checkArgument;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.ResourceBundle;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import org.opentcs.access.KernelServicePortal;
import org.opentcs.drivers.peripherals.PeripheralCommAdapterDescription;
import org.opentcs.drivers.peripherals.PeripheralProcessModel;
import org.opentcs.util.CallWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A model for displaying a table of peripherals.
*/
public class PeripheralTableModel
extends
AbstractTableModel
implements
PropertyChangeListener {
/**
* The index of the column showing the peripheral device's name.
*/
public static final int COLUMN_LOCATION = 0;
/**
* The index of the column showing the associated adapter.
*/
public static final int COLUMN_ADAPTER = 1;
/**
* The index of the column showing the adapter's enabled state.
*/
public static final int COLUMN_ENABLED = 2;
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(PeripheralTableModel.class);
/**
* This class's resource bundle.
*/
private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(BUNDLE_PATH);
/**
* The collumn names for the table header.
*/
private static final String[] COLUMN_NAMES
= new String[]{
BUNDLE.getString("peripheralTableModel.column_location.headerText"),
BUNDLE.getString("peripheralTableModel.column_adapter.headerText"),
BUNDLE.getString("peripheralTableModel.column_enabled.headerText")
};
/**
* The column classes.
*/
private static final Class<?>[] COLUMN_CLASSES
= new Class<?>[]{
String.class,
PeripheralCommAdapterDescription.class,
Boolean.class
};
/**
* The service portal to use for kernel interaction.
*/
private final KernelServicePortal servicePortal;
/**
* The call wrapper to use.
*/
private final CallWrapper callWrapper;
/**
* The entries in the table.
*/
private final List<LocalPeripheralEntry> entries = new ArrayList<>();
public PeripheralTableModel(
KernelServicePortal servicePortal,
CallWrapper callWrapper
) {
this.servicePortal = requireNonNull(servicePortal, "servicePortal");
this.callWrapper = requireNonNull(callWrapper, "callWrapper");
}
/**
* Returns the identifier for the adapter column.
*
* @return the idenfitier for the adapter column.
*/
public static String adapterColumnIdentifier() {
return COLUMN_NAMES[COLUMN_ADAPTER];
}
public void addData(LocalPeripheralEntry entry) {
entries.add(entry);
fireTableRowsInserted(entries.size() - 1, entries.size() - 1);
}
public LocalPeripheralEntry getDataAt(int index) {
checkArgument(
index >= 0 && index < entries.size(),
"index must be in 0..%d: %d",
entries.size(),
index
);
return entries.get(index);
}
@Override
public int getRowCount() {
return entries.size();
}
@Override
public int getColumnCount() {
return COLUMN_NAMES.length;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (rowIndex >= entries.size()) {
return null;
}
LocalPeripheralEntry entry = entries.get(rowIndex);
switch (columnIndex) {
case COLUMN_LOCATION:
return entry.getProcessModel().getLocation().getName();
case COLUMN_ADAPTER:
return entry.getAttachedCommAdapter();
case COLUMN_ENABLED:
return entry.getProcessModel().isCommAdapterEnabled();
default:
throw new IllegalArgumentException("Invalid column index: " + columnIndex);
}
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
requireNonNull(aValue, "aValue");
LocalPeripheralEntry entry = entries.get(rowIndex);
switch (columnIndex) {
case COLUMN_LOCATION:
break;
case COLUMN_ADAPTER:
break;
case COLUMN_ENABLED:
try {
if ((boolean) aValue) {
callWrapper.call(
() -> servicePortal.getPeripheralService().enableCommAdapter(entry.getLocation())
);
}
else {
callWrapper.call(
() -> servicePortal.getPeripheralService().disableCommAdapter(entry.getLocation())
);
}
}
catch (Exception ex) {
LOG.warn(
"Error enabling/disabling peripheral comm adapter for {}",
entry.getLocation().getName(),
ex
);
}
break;
default:
throw new IllegalArgumentException("Invalid column index: " + columnIndex);
}
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
switch (columnIndex) {
case COLUMN_LOCATION:
return false;
case COLUMN_ADAPTER:
return true;
case COLUMN_ENABLED:
return true;
default:
return false;
}
}
@Override
public String getColumnName(int columnIndex) {
return COLUMN_NAMES[columnIndex];
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return COLUMN_CLASSES[columnIndex];
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (!isRelevantUpdate(evt)) {
return;
}
LocalPeripheralEntry entry = (LocalPeripheralEntry) evt.getSource();
int index = entries.indexOf(entry);
SwingUtilities.invokeLater(() -> fireTableRowsUpdated(index, index));
}
private boolean isRelevantUpdate(PropertyChangeEvent evt) {
if (!(evt.getSource() instanceof LocalPeripheralEntry)) {
return false;
}
if (Objects.equals(
evt.getPropertyName(),
LocalPeripheralEntry.Attribute.ATTACHED_COMM_ADAPTER.name()
)) {
PeripheralCommAdapterDescription oldInfo
= (PeripheralCommAdapterDescription) evt.getOldValue();
PeripheralCommAdapterDescription newInfo
= (PeripheralCommAdapterDescription) evt.getNewValue();
return !oldInfo.equals(newInfo);
}
if (Objects.equals(
evt.getPropertyName(),
LocalPeripheralEntry.Attribute.PROCESS_MODEL.name()
)) {
PeripheralProcessModel oldTo = (PeripheralProcessModel) evt.getOldValue();
PeripheralProcessModel newTo = (PeripheralProcessModel) evt.getNewValue();
return oldTo.isCommAdapterEnabled() != newTo.isCommAdapterEnabled();
}
return false;
}
}

View File

@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.8" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AccessibilityProperties>
<Property name="AccessibleContext.accessibleName" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="peripheralsPanel.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_formBundle" type="java.lang.String" value="i18n/de/fraunhofer/iml/opentcs/parkplus/controlcenterpanels/peripherals/TransferCabinsPanel"/>
<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,-87,0,0,3,116"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="listPanel">
<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="Peripheral devices in model"/>
</Border>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[464, 2147483647]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[464, 425]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[464, 424]"/>
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="filterPanel">
<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.DesignFlowLayout">
<Property name="alignment" type="int" value="0"/>
</Layout>
<SubComponents>
<Component class="javax.swing.JCheckBox" name="hideDetachedLocationsCheckBox">
<Properties>
<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/kernelcontrolcenter/Bundle.properties" key="peripheralsPanel.checkBox_hideDetachedLocations.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="hideDetachedLocationsCheckBoxActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="jScrollPane1">
<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.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="peripheralTable">
<Events>
<EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="peripheralTableMouseClicked"/>
</Events>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="peripheralDetailsPanel">
<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="Peripheral details"/>
</Border>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[800, 23]"/>
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
</Container>
</SubComponents>
</Form>

View File

@@ -0,0 +1,389 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.peripherals;
import static java.util.Objects.requireNonNull;
import static org.opentcs.kernelcontrolcenter.I18nKernelControlCenter.BUNDLE_PATH;
import static org.opentcs.util.Assertions.checkState;
import jakarta.annotation.Nonnull;
import jakarta.inject.Inject;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.ResourceBundle;
import javax.swing.DefaultCellEditor;
import javax.swing.JOptionPane;
import javax.swing.RowFilter;
import javax.swing.table.TableRowSorter;
import org.opentcs.access.Kernel;
import org.opentcs.access.KernelServicePortal;
import org.opentcs.common.peripherals.NullPeripheralCommAdapterDescription;
import org.opentcs.components.kernelcontrolcenter.ControlCenterPanel;
import org.opentcs.customizations.ServiceCallWrapper;
import org.opentcs.drivers.peripherals.PeripheralCommAdapterDescription;
import org.opentcs.drivers.peripherals.management.PeripheralAttachmentInformation;
import org.opentcs.kernelcontrolcenter.util.SingleCellEditor;
import org.opentcs.util.CallWrapper;
import org.opentcs.util.gui.BoundsPopupMenuListener;
import org.opentcs.util.gui.StringTableCellRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A panel containing all locations representing peripheral devices.
*/
public class PeripheralsPanel
extends
ControlCenterPanel {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(PeripheralsPanel.class);
/**
* The service portal to use for kernel interaction.
*/
private final KernelServicePortal servicePortal;
/**
* The call wrapper to use.
*/
private final CallWrapper callWrapper;
/**
* The pool of peripheral devices.
*/
private final LocalPeripheralEntryPool peripheralEntryPool;
/**
* The details panel.
*/
private final PeripheralDetailPanel detailPanel;
/**
* The table row sorter to use.
*/
private TableRowSorter<PeripheralTableModel> sorter;
/**
* This instance's resource bundle.
*/
private final ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_PATH);
/**
* Whether this component is initialized.
*/
private boolean initialized;
/**
* Creates new instance.
*
* @param servicePortal The service portal to use for kernel interaction.
* @param callWrapper The call wrapper to use for publishing events to the kernel.
* @param peripheralEntryPool The pool of peripheral devices.
* @param detailPanel The details panel.
*/
@Inject
@SuppressWarnings("this-escape")
public PeripheralsPanel(
@Nonnull
KernelServicePortal servicePortal,
@Nonnull
@ServiceCallWrapper
CallWrapper callWrapper,
@Nonnull
LocalPeripheralEntryPool peripheralEntryPool,
@Nonnull
PeripheralDetailPanel detailPanel
) {
this.servicePortal = requireNonNull(servicePortal, "servicePortal");
this.callWrapper = requireNonNull(callWrapper, "callWrapper");
this.peripheralEntryPool = requireNonNull(peripheralEntryPool, "peripheralEntryPool");
this.detailPanel = requireNonNull(detailPanel, "detailPanel");
initComponents();
peripheralTable.setDefaultRenderer(
PeripheralCommAdapterDescription.class,
new CommAdapterFactoryTableCellRenderer()
);
peripheralDetailsPanel.add(detailPanel);
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
// Verify that the kernel is in a state in which controlling peripherals is possible.
Kernel.State kernelState;
try {
kernelState = callWrapper.call(() -> servicePortal.getState());
}
catch (Exception ex) {
LOG.warn("Error getting the kernel state", ex);
return;
}
checkState(
Kernel.State.OPERATING.equals(kernelState),
"Cannot work in kernel state %s",
kernelState
);
peripheralEntryPool.initialize();
detailPanel.initialize();
EventQueue.invokeLater(() -> {
initPeripheralTable();
});
initialized = true;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
detailPanel.terminate();
peripheralEntryPool.terminate();
initialized = false;
}
private void initPeripheralTable() {
PeripheralTableModel model = new PeripheralTableModel(servicePortal, callWrapper);
peripheralTable.setModel(model);
peripheralTable.getColumnModel().getColumn(PeripheralTableModel.COLUMN_ADAPTER).setCellRenderer(
new StringTableCellRenderer<PeripheralCommAdapterDescription>(
description -> description.getDescription()
)
);
sorter = new TableRowSorter<>(model);
peripheralTable.setRowSorter(sorter);
peripheralEntryPool.getEntries().forEach((location, entry) -> {
model.addData(entry);
entry.addPropertyChangeListener(model);
});
initComboBoxes();
updateRowFilter();
}
private void initComboBoxes() {
SingleCellEditor adpaterCellEditor = new SingleCellEditor(peripheralTable);
int index = 0;
for (LocalPeripheralEntry entry : peripheralEntryPool.getEntries().values()) {
initCommAdaptersComboBox(entry, index, adpaterCellEditor);
index++;
}
peripheralTable.getColumn(PeripheralTableModel.adapterColumnIdentifier())
.setCellEditor(adpaterCellEditor);
}
private void initCommAdaptersComboBox(
LocalPeripheralEntry peripheralEntry,
int rowIndex,
SingleCellEditor adapterCellEditor
) {
final PeripheralAdapterComboBox comboBox = new PeripheralAdapterComboBox();
PeripheralAttachmentInformation ai;
try {
ai = callWrapper.call(
() -> servicePortal.getPeripheralService().fetchAttachmentInformation(
peripheralEntry.getLocation()
)
);
}
catch (Exception ex) {
LOG.warn(
"Error fetching attachment information for {}",
peripheralEntry.getLocation().getName(),
ex
);
return;
}
for (PeripheralCommAdapterDescription factory : ai.getAvailableCommAdapters()) {
comboBox.addItem(factory);
}
if (ai.getAvailableCommAdapters().isEmpty()) {
comboBox.addItem(new NullPeripheralCommAdapterDescription());
}
// Set the selection to the attached comm adapter. (The peripheral is already attached to a comm
// adapter due to auto attachment on startup.)
comboBox.setSelectedItem(ai.getAttachedCommAdapter());
comboBox.setRenderer(new AdapterFactoryCellRenderer());
comboBox.addPopupMenuListener(new BoundsPopupMenuListener());
comboBox.addItemListener((ItemEvent evt) -> {
if (evt.getStateChange() == ItemEvent.DESELECTED) {
return;
}
// If we selected a comm adapter that's already attached, do nothing.
if (Objects.equals(evt.getItem(), peripheralEntry.getAttachedCommAdapter())) {
LOG.debug(
"{} is already attached to: {}",
peripheralEntry.getLocation().getName(),
evt.getItem()
);
return;
}
int reply = JOptionPane.showConfirmDialog(
null,
bundle.getString("peripheralsPanel.optionPane_driverChangeConfirmation.message"),
bundle.getString("peripheralsPanel.optionPane_driverChangeConfirmation.title"),
JOptionPane.YES_NO_OPTION
);
if (reply == JOptionPane.NO_OPTION) {
return;
}
PeripheralCommAdapterDescription factory = comboBox.getSelectedItem();
try {
callWrapper.call(
() -> servicePortal.getPeripheralService().attachCommAdapter(
peripheralEntry.getLocation(),
factory
)
);
}
catch (Exception ex) {
LOG.warn(
"Error attaching adapter {} to vehicle {}",
factory,
peripheralEntry.getLocation().getName(),
ex
);
return;
}
LOG.info("Attaching comm adapter {} to {}", factory, peripheralEntry.getLocation().getName());
});
adapterCellEditor.setEditorAt(rowIndex, new DefaultCellEditor(comboBox));
peripheralEntry.addPropertyChangeListener(comboBox);
}
private void updateRowFilter() {
sorter.setRowFilter(RowFilter.andFilter(toRegexFilters()));
}
private List<RowFilter<PeripheralTableModel, Integer>> toRegexFilters() {
List<RowFilter<PeripheralTableModel, Integer>> result = new ArrayList<>();
result.add(RowFilter.regexFilter(".*", PeripheralTableModel.COLUMN_ADAPTER));
if (hideDetachedLocationsCheckBox.isSelected()) {
result.add(
RowFilter.notFilter(
RowFilter.regexFilter(
NullPeripheralCommAdapterDescription.class.getSimpleName(),
PeripheralTableModel.COLUMN_ADAPTER
)
)
);
}
return result;
}
// 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.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
listPanel = new javax.swing.JPanel();
filterPanel = new javax.swing.JPanel();
hideDetachedLocationsCheckBox = new javax.swing.JCheckBox();
jScrollPane1 = new javax.swing.JScrollPane();
peripheralTable = new javax.swing.JTable();
peripheralDetailsPanel = new javax.swing.JPanel();
setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.LINE_AXIS));
listPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Peripheral devices in model"));
listPanel.setMaximumSize(new java.awt.Dimension(464, 2147483647));
listPanel.setMinimumSize(new java.awt.Dimension(464, 425));
listPanel.setPreferredSize(new java.awt.Dimension(464, 424));
listPanel.setLayout(new java.awt.BorderLayout());
filterPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT));
hideDetachedLocationsCheckBox.setSelected(true);
java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("i18n/org/opentcs/kernelcontrolcenter/Bundle"); // NOI18N
hideDetachedLocationsCheckBox.setText(bundle.getString("peripheralsPanel.checkBox_hideDetachedLocations.text")); // NOI18N
hideDetachedLocationsCheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
hideDetachedLocationsCheckBoxActionPerformed(evt);
}
});
filterPanel.add(hideDetachedLocationsCheckBox);
listPanel.add(filterPanel, java.awt.BorderLayout.NORTH);
peripheralTable.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
peripheralTableMouseClicked(evt);
}
});
jScrollPane1.setViewportView(peripheralTable);
listPanel.add(jScrollPane1, java.awt.BorderLayout.CENTER);
add(listPanel);
peripheralDetailsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Peripheral details"));
peripheralDetailsPanel.setPreferredSize(new java.awt.Dimension(800, 23));
peripheralDetailsPanel.setLayout(new java.awt.BorderLayout());
add(peripheralDetailsPanel);
getAccessibleContext().setAccessibleName(bundle.getString("peripheralsPanel.accessibleName")); // NOI18N
}// </editor-fold>//GEN-END:initComponents
// CHECKSTYLE:ON
// FORMATTER:ON
private void peripheralTableMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_peripheralTableMouseClicked
if (evt.getClickCount() == 2) {
int index = peripheralTable.getSelectedRow();
if (index >= 0) {
PeripheralTableModel model = (PeripheralTableModel) peripheralTable.getModel();
LocalPeripheralEntry selectedEntry
= model.getDataAt(peripheralTable.convertRowIndexToModel(index));
detailPanel.attachToEntry(selectedEntry);
}
}
}//GEN-LAST:event_peripheralTableMouseClicked
private void hideDetachedLocationsCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hideDetachedLocationsCheckBoxActionPerformed
updateRowFilter();
}//GEN-LAST:event_hideDetachedLocationsCheckBoxActionPerformed
// FORMATTER:OFF
// CHECKSTYLE:OFF
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel filterPanel;
private javax.swing.JCheckBox hideDetachedLocationsCheckBox;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JPanel listPanel;
private javax.swing.JPanel peripheralDetailsPanel;
private javax.swing.JTable peripheralTable;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
}

View File

@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.util;
import java.util.List;
import org.opentcs.configuration.ConfigurationEntry;
import org.opentcs.configuration.ConfigurationPrefix;
import org.opentcs.util.gui.dialog.ConnectionParamSet;
/**
* Provides methods to configure the KernelControlCenter application.
*/
@ConfigurationPrefix(KernelControlCenterConfiguration.PREFIX)
public interface KernelControlCenterConfiguration {
/**
* This configuration's prefix.
*/
String PREFIX = "kernelcontrolcenter";
@ConfigurationEntry(
type = "String",
description = {"The kernel control center application's locale, as a BCP 47 language tag.",
"Examples: 'en', 'de', 'zh'"},
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_init_0"
)
String locale();
@ConfigurationEntry(
type = "Comma-separated list of <description>\\|<hostname>\\|<port>",
description = "Kernel connection bookmarks to be used.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "1_connection_0"
)
List<ConnectionParamSet> connectionBookmarks();
@ConfigurationEntry(
type = "Boolean",
description = {"Whether to automatically connect to the kernel on startup.",
"If 'true', the first connection bookmark will be used for the initial "
+ "connection attempt.",
"If 'false', a dialog will be shown to enter connection parameters."},
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "1_connection_1"
)
boolean connectAutomaticallyOnStartup();
@ConfigurationEntry(
type = "Integer",
description = "The maximum number of characters in the logging text area.",
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
orderKey = "9_misc_0"
)
int loggingAreaCapacity();
@ConfigurationEntry(
type = "Boolean",
description = "Whether to enable and show the panel for peripheral drivers.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "9_misc_1"
)
boolean enablePeripheralsPanel();
}

View File

@@ -0,0 +1,138 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.util;
import java.awt.Component;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Map;
import javax.swing.DefaultCellEditor;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.event.CellEditorListener;
import javax.swing.table.TableCellEditor;
/**
* A cell editor for maintaining different editors in one column.
*/
public final class SingleCellEditor
implements
TableCellEditor {
/**
* The TableCellEditors for every cell.
*/
private final Map<Integer, TableCellEditor> editors;
/**
* The current editor.
*/
private TableCellEditor editor;
/**
* The default editor.
*/
private final TableCellEditor defaultEditor;
/**
* The table associated with the editors.
*/
private final JTable table;
/**
* Constructs a SingleCellEditor.
*
* @param table The JTable associated
*/
public SingleCellEditor(JTable table) {
this.table = table;
editors = new HashMap<>();
defaultEditor = new DefaultCellEditor(new JTextField());
}
/**
* Assigns an editor to a row.
*
* @param row table row
* @param rowEditor table cell editor
*/
public void setEditorAt(int row, TableCellEditor rowEditor) {
editors.put(row, rowEditor);
}
@Override
public Component getTableCellEditorComponent(
JTable whichTable,
Object value,
boolean isSelected,
int row,
int column
) {
return editor.getTableCellEditorComponent(
whichTable,
value,
isSelected,
row,
column
);
}
@Override
public Object getCellEditorValue() {
return editor.getCellEditorValue();
}
@Override
public boolean stopCellEditing() {
return editor.stopCellEditing();
}
@Override
public void cancelCellEditing() {
editor.cancelCellEditing();
}
@Override
public boolean isCellEditable(EventObject anEvent) {
if (anEvent instanceof KeyEvent) {
return false;
}
selectEditor((MouseEvent) anEvent);
return editor.isCellEditable(anEvent);
}
@Override
public void addCellEditorListener(CellEditorListener l) {
editor.addCellEditorListener(l);
}
@Override
public void removeCellEditorListener(CellEditorListener l) {
editor.removeCellEditorListener(l);
}
@Override
public boolean shouldSelectCell(EventObject anEvent) {
selectEditor((MouseEvent) anEvent);
return editor.shouldSelectCell(anEvent);
}
/**
* Sets the current editor.
*
* @param e A MouseEvent
*/
public void selectEditor(MouseEvent e) {
int row;
if (e == null) {
row = table.getSelectionModel().getAnchorSelectionIndex();
}
else {
row = table.convertRowIndexToModel(table.rowAtPoint(e.getPoint()));
}
editor = editors.get(row);
if (editor == null) {
editor = defaultEditor;
}
table.changeSelection(row, table.getColumn("Adapter").getModelIndex(), false, false);
}
}

View File

@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.vehicles;
import java.awt.Component;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import org.opentcs.drivers.vehicle.VehicleCommAdapterDescription;
/**
* ListCellRenderer for the adapter combo box.
*/
final class AdapterFactoryCellRenderer
implements
ListCellRenderer<VehicleCommAdapterDescription> {
/**
* A default renderer for creating the label.
*/
private final DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer();
/**
* Creates a new instance.
*/
AdapterFactoryCellRenderer() {
}
@Override
public Component getListCellRendererComponent(
JList<? extends VehicleCommAdapterDescription> list,
VehicleCommAdapterDescription value,
int index,
boolean isSelected,
boolean cellHasFocus
) {
JLabel label = (JLabel) defaultRenderer.getListCellRendererComponent(
list,
value,
index,
isSelected,
cellHasFocus
);
if (value != null) {
label.setText(value.getDescription());
}
else {
label.setText(" ");
}
return label;
}
}

View File

@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.vehicles;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Objects;
import javax.swing.JComboBox;
import org.opentcs.drivers.vehicle.VehicleCommAdapterDescription;
/**
* A wide combobox which sets the selected item when receiving an update event from a
* {@link LocalVehicleEntry}.
*/
public class CommAdapterComboBox
extends
JComboBox<VehicleCommAdapterDescription>
implements
PropertyChangeListener {
/**
* Creates a new instance.
*/
public CommAdapterComboBox() {
}
@Override
public VehicleCommAdapterDescription getSelectedItem() {
return (VehicleCommAdapterDescription) super.getSelectedItem();
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (!(evt.getSource() instanceof LocalVehicleEntry)) {
return;
}
LocalVehicleEntry entry = (LocalVehicleEntry) evt.getSource();
if (Objects.equals(entry.getAttachedCommAdapterDescription(), getModel().getSelectedItem())) {
return;
}
super.setSelectedItem(entry.getAttachedCommAdapterDescription());
}
}

View File

@@ -0,0 +1,392 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.6" maxVersion="1.6" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Container class="javax.swing.JPopupMenu" name="logPopupMenu">
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout">
<Property name="useNullLayout" type="boolean" value="true"/>
</Layout>
<SubComponents>
<MenuItem class="javax.swing.JMenuItem" name="clearMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="detailPanel.popupMenu_messageDetails.menuItem_clear.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="clearMenuItemActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
</MenuItem>
</SubComponents>
</Container>
<Container class="javax.swing.JPopupMenu" name="loggingTablePopupMenu">
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout">
<Property name="useNullLayout" type="boolean" value="true"/>
</Layout>
<SubComponents>
<Menu class="javax.swing.JMenu" name="filterMenu">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="detailPanel.popupMenu_messagesTable.subMenu_filter.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
<Property name="actionCommand" type="java.lang.String" value=" message filtering"/>
</Properties>
<SubComponents>
<MenuItem class="javax.swing.JCheckBoxMenuItem" name="everythingCheckBoxMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="detailPanel.popupMenu_messagesTable.subMenu_filter.menuItem_showAll.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="everythingCheckBoxMenuItemActionPerformed"/>
</Events>
</MenuItem>
<MenuItem class="javax.swing.JCheckBoxMenuItem" name="warningsCheckBoxMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="detailPanel.popupMenu_messagesTable.subMenu_filter.menuItem_showErrorsAndWarnings.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="warningsCheckBoxMenuItemActionPerformed"/>
</Events>
</MenuItem>
<MenuItem class="javax.swing.JCheckBoxMenuItem" name="errorsCheckBoxMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="detailPanel.popupMenu_messagesTable.subMenu_filter.menuItem_showErrors.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="errorsCheckBoxMenuItemActionPerformed"/>
</Events>
</MenuItem>
</SubComponents>
</Menu>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="noVehiclePanel">
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="noVehicleLabel">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font component="noVehicleLabel" property="font" relativeSize="true" size="3"/>
</FontInfo>
</Property>
<Property name="horizontalAlignment" type="int" value="0"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="detailPanel.label_noVehicleAttached.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</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>
</Component>
</SubComponents>
</Container>
</NonVisualComponents>
<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="&lt;User Code&gt;">
<Connection PropertyName="titleX" code="DEFAULT_BORDER_TITLE" type="code"/>
</TitledBorder>
</Border>
</Property>
</Properties>
<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,1,-63,0,0,2,-123"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JTabbedPane" name="tabbedPane">
<Properties>
<Property name="tabLayoutPolicy" type="int" value="1"/>
</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.support.JTabbedPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="overviewTabPanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
<JTabbedPaneConstraints tabName="General status">
<Property name="tabTitle" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="detailPanel.tab_generalStatus.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</JTabbedPaneConstraints>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="headPanel">
<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="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.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="statusPanel">
<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"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="adapterStatusPanel">
<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/kernelcontrolcenter/Bundle.properties" key="detailPanel.panel_adapterStatus.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.DesignBorderLayout"/>
<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/kernelcontrolcenter/Bundle.properties" key="detailPanel.checkBox_enableAdapter.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</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.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Center"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="statusFiguresPanel">
<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 status">
<ResourceString PropertyName="titleX" bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="detailPanel.panel_vehicleStatus.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="curPosLbl">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="detailPanel.label_currentPosition.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="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="3" insetsBottom="0" insetsRight="0" anchor="13" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="curPosTxt">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="columns" type="int" value="9"/>
<Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" value="Point-0001"/>
</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="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="curStateLbl">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="detailPanel.label_currentState.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="3" insetsLeft="3" insetsBottom="0" insetsRight="0" anchor="13" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JTextField" name="curStateTxt">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="columns" type="int" value="9"/>
<Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="Vehicle.State.UNKNOWN.name()" type="code"/>
</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="3" insetsLeft="3" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="fillingLbl">
<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>
</SubComponents>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="logoPanel">
<Properties>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="ff" green="ff" red="ff" type="rgb"/>
</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>
<Component class="javax.swing.JLabel" name="logoLbl">
<Properties>
<Property name="horizontalAlignment" type="int" value="0"/>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/opentcs/kernelcontrolcenter/res/logos/opentcs_logo.gif"/>
</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>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="logPanel">
<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="Messages">
<ResourceString PropertyName="titleX" bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="detailPanel.panel_messages.border.title" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[468, 200]"/>
</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="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="logTableScrollPane">
<Properties>
<Property name="componentPopupMenu" type="javax.swing.JPopupMenu" editor="org.netbeans.modules.form.ComponentChooserEditor">
<ComponentRef name="loggingTablePopupMenu"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</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>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="loggingTable">
<Properties>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.editors2.TableModelEditor">
<Table columnCount="2" rowCount="0">
<Column editable="false" title="Time stamp" type="java.lang.Object"/>
<Column editable="false" title="Message" type="java.lang.Object"/>
</Table>
</Property>
<Property name="componentPopupMenu" type="javax.swing.JPopupMenu" editor="org.netbeans.modules.form.ComponentChooserEditor">
<ComponentRef name="loggingTablePopupMenu"/>
</Property>
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
<JTableSelectionModel selectionMode="0"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="logTextScrollPane">
<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.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTextArea" name="loggingTextArea">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="columns" type="int" value="20"/>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Courier New" size="12" style="0"/>
</Property>
<Property name="lineWrap" type="boolean" value="true"/>
<Property name="rows" type="int" value="3"/>
<Property name="componentPopupMenu" type="javax.swing.JPopupMenu" editor="org.netbeans.modules.form.ComponentChooserEditor">
<ComponentRef name="logPopupMenu"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@@ -0,0 +1,736 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.vehicles;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.opentcs.access.KernelServicePortal;
import org.opentcs.components.Lifecycle;
import org.opentcs.customizations.ApplicationEventBus;
import org.opentcs.customizations.ServiceCallWrapper;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.notification.UserNotification;
import org.opentcs.drivers.vehicle.VehicleProcessModel;
import org.opentcs.drivers.vehicle.management.ProcessModelEvent;
import org.opentcs.drivers.vehicle.management.VehicleAttachmentEvent;
import org.opentcs.drivers.vehicle.management.VehicleAttachmentInformation;
import org.opentcs.drivers.vehicle.management.VehicleCommAdapterPanel;
import org.opentcs.drivers.vehicle.management.VehicleCommAdapterPanelFactory;
import org.opentcs.util.CallWrapper;
import org.opentcs.util.event.EventHandler;
import org.opentcs.util.event.EventSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Displays information about a vehicle (VehicleModel) graphically.
*/
public class DetailPanel
extends
JPanel
implements
EventHandler,
Lifecycle {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(DetailPanel.class);
/**
* A panel's default border title.
*/
private static final String DEFAULT_BORDER_TITLE = "";
/**
* A <code>DateFormat</code> instance for formatting message's time stamps.
*/
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter
.ofLocalizedDateTime(FormatStyle.SHORT)
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault());
/**
* The adapter specific list of JPanels.
*/
private final List<VehicleCommAdapterPanel> customPanelList = new ArrayList<>();
/**
* The set of factories to create adapter specific panels.
*/
private final Set<VehicleCommAdapterPanelFactory> panelFactories;
/**
* The logging table model to use.
*/
private final LogTableModel loggingTableModel = new LogTableModel();
/**
* Where this instance registers for application events.
*/
private final EventSource eventSource;
/**
* The service portal to use for kernel interaction.
*/
private final KernelServicePortal servicePortal;
/**
* The call wrapper to use for service calls.
*/
private final CallWrapper callWrapper;
/**
* The vehicle model of the vehicle current associated with this window.
*/
private LocalVehicleEntry vehicleEntry;
/**
* The comm adapter currently attached to the vehicle (model).
*/
private VehicleAttachmentInformation attachmentInfo;
/**
* Whether this panel is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param servicePortal The service portal to use for kernel interaction.
* @param callWrapper The call wrapper to use for service calls.
* @param eventSource Where this instance registers for application events.
* @param panelFactories The factories to create adapter specific panels.
*/
@Inject
@SuppressWarnings("this-escape")
public DetailPanel(
KernelServicePortal servicePortal,
@ServiceCallWrapper
CallWrapper callWrapper,
@ApplicationEventBus
EventSource eventSource,
Set<VehicleCommAdapterPanelFactory> panelFactories
) {
this.servicePortal = requireNonNull(servicePortal, "servicePortal");
this.callWrapper = requireNonNull(callWrapper, "callWrapper");
this.eventSource = requireNonNull(eventSource, "eventSource");
this.panelFactories = requireNonNull(panelFactories, "panelFactories");
initComponents();
loggingTable.setModel(loggingTableModel);
loggingTable.getColumnModel().getColumn(0).setPreferredWidth(40);
loggingTable.getColumnModel().getColumn(1).setPreferredWidth(110);
loggingTable.getSelectionModel().addListSelectionListener(new RowListener());
// Make sure we start with an empty panel.
detachFromVehicle();
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
for (VehicleCommAdapterPanelFactory fatory : panelFactories) {
fatory.initialize();
}
eventSource.subscribe(this);
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
detachFromVehicle();
eventSource.unsubscribe(this);
for (VehicleCommAdapterPanelFactory fatory : panelFactories) {
fatory.terminate();
}
initialized = false;
}
@Override
public void onEvent(Object e) {
// Ignore events if no vehicle entry is associated with this panel.
if (vehicleEntry == null) {
return;
}
if (e instanceof VehicleAttachmentEvent) {
VehicleAttachmentEvent event = (VehicleAttachmentEvent) e;
if (Objects.equals(
vehicleEntry.getVehicleName(),
event.getVehicleName()
)) {
updateFromVehicleEntry(event);
}
}
if (e instanceof ProcessModelEvent) {
ProcessModelEvent event = (ProcessModelEvent) e;
if (Objects.equals(
vehicleEntry.getVehicleName(),
event.getUpdatedProcessModel().getName()
)) {
updateFromVehicleProcessModel(event);
// Forward event to the comm adapter panels
customPanelList.forEach(
panel -> panel.processModelChange(
event.getAttributeChanged(),
event.getUpdatedProcessModel()
)
);
}
}
}
/**
* Attaches this panel to a vehicle.
*
* @param newVehicleEntry The vehicle entry to attach to.
*/
void attachToVehicle(LocalVehicleEntry newVehicleEntry) {
requireNonNull(newVehicleEntry, "newVehicleEntry");
// Clean up first - but only if we're not reattaching the vehicle model which is already
// attached to this panel.
if (vehicleEntry != newVehicleEntry) {
detachFromVehicle();
}
vehicleEntry = newVehicleEntry;
setBorderTitle(vehicleEntry.getVehicleName());
// Ensure the tabbed pane containing vehicle information is shown.
removeAll();
add(tabbedPane);
// Init vehicle status.
loggingTableModel.clear();
for (UserNotification notification : vehicleEntry.getProcessModel().getNotifications()) {
loggingTableModel.addRow(notification);
}
initVehicleEntryAttributes();
// Update panel contents.
validate();
tabbedPane.setSelectedIndex(0);
}
/**
* Detaches this panel from a vehicle (if it is currently attached to any).
*/
private void detachFromVehicle() {
if (vehicleEntry != null) {
// Remove all custom panels and let the comm adapter know we don't need them any more.
removeCustomPanels();
if (attachmentInfo != null) {
attachmentInfo = null;
}
customPanelList.clear();
vehicleEntry = null;
}
// Clear the log message table.
loggingTableModel.clear();
setBorderTitle(DEFAULT_BORDER_TITLE);
// Remove the contents of this panel.
removeAll();
add(noVehiclePanel);
// Update panel contents.
validate();
}
private void initVehicleEntryAttributes() {
updateCommAdapter(vehicleEntry.getAttachmentInformation());
updateCommAdapterEnabled(vehicleEntry.getProcessModel().isCommAdapterEnabled());
updateVehiclePosition(vehicleEntry.getProcessModel().getPosition());
updateVehicleState(vehicleEntry.getProcessModel().getState());
}
private void updateFromVehicleEntry(VehicleAttachmentEvent evt) {
updateCommAdapter(evt.getAttachmentInformation());
}
private void updateFromVehicleProcessModel(ProcessModelEvent evt) {
if (Objects.equals(
evt.getAttributeChanged(),
VehicleProcessModel.Attribute.COMM_ADAPTER_ENABLED.name()
)) {
updateCommAdapterEnabled(evt.getUpdatedProcessModel().isCommAdapterEnabled());
}
else if (Objects.equals(
evt.getAttributeChanged(),
VehicleProcessModel.Attribute.POSITION.name()
)) {
updateVehiclePosition(evt.getUpdatedProcessModel().getPosition());
}
else if (Objects.equals(
evt.getAttributeChanged(),
VehicleProcessModel.Attribute.STATE.name()
)) {
updateVehicleState(evt.getUpdatedProcessModel().getState());
}
else if (Objects.equals(
evt.getAttributeChanged(),
VehicleProcessModel.Attribute.USER_NOTIFICATION.name()
)) {
updateUserNotification(evt.getUpdatedProcessModel().getNotifications());
}
}
private void updateCommAdapter(VehicleAttachmentInformation newAttachmentInfo) {
SwingUtilities.invokeLater(() -> {
// If there was a comm adapter and it changed, we need to clean up a few things first.
if (attachmentInfo != null) {
// Detach all custom panels of the old comm adapter.
removeCustomPanels();
customPanelList.clear();
}
// Update the comm adapter reference.
attachmentInfo = newAttachmentInfo;
// If we have a new comm adapter, set up a few things.
if (attachmentInfo != null) {
// Update the custom panels displayed.
updateCustomPanels();
}
chkBoxEnable.setEnabled(attachmentInfo != null);
chkBoxEnable.setSelected(
attachmentInfo != null
&& vehicleEntry.getProcessModel().isCommAdapterEnabled()
);
});
}
private void updateCommAdapterEnabled(boolean enabled) {
SwingUtilities.invokeLater(() -> chkBoxEnable.setSelected(enabled));
}
private void updateVehiclePosition(String position) {
SwingUtilities.invokeLater(() -> curPosTxt.setText(position));
}
private void updateVehicleState(Vehicle.State state) {
SwingUtilities.invokeLater(() -> curStateTxt.setText(state.toString()));
}
private void updateUserNotification(Queue<UserNotification> notifications) {
SwingUtilities.invokeLater(() -> {
loggingTableModel.clear();
notifications.forEach(notification -> loggingTableModel.addRow(notification));
});
}
/**
* Update the list of custom panels in the tabbed pane.
*/
private void updateCustomPanels() {
for (VehicleCommAdapterPanel curPanel : customPanelList) {
LOG.debug("Removing {} from tabbedPane.", curPanel);
tabbedPane.remove(curPanel);
}
customPanelList.clear();
if (attachmentInfo != null) {
for (VehicleCommAdapterPanelFactory panelFactory : panelFactories) {
customPanelList.addAll(
panelFactory.getPanelsFor(
vehicleEntry.getAttachedCommAdapterDescription(),
vehicleEntry.getAttachmentInformation().getVehicleReference(),
vehicleEntry.getProcessModel()
)
);
for (VehicleCommAdapterPanel curPanel : customPanelList) {
LOG.debug("Adding {} with title {} to tabbedPane.", curPanel, curPanel.getTitle());
tabbedPane.addTab(curPanel.getTitle(), curPanel);
}
}
}
}
/**
* Removes the custom panels from this panel's tabbed pane.
*/
private void removeCustomPanels() {
LOG.debug("Setting selected component of tabbedPane to overviewTabPanel.");
tabbedPane.setSelectedComponent(overviewTabPanel);
for (VehicleCommAdapterPanel panel : customPanelList) {
LOG.debug("Removing {} from tabbedPane.", panel);
tabbedPane.remove(panel);
}
}
/**
* Sets this panel's border title.
*
* @param newTitle This panel's new border title.
*/
private void setBorderTitle(String newTitle) {
requireNonNull(newTitle, "newTitle");
((TitledBorder) getBorder()).setTitle(newTitle);
// Trigger a repaint - the title sometimes looks strange otherwise.
repaint();
}
/**
* This method appends the selected notification to the text area in the log tab.
*
* @param row The selected row in the table.
*/
private void outputLogNotification(int row) {
UserNotification message = loggingTableModel.getRow(row);
String timestamp = DATE_FORMAT.format(message.getTimestamp());
String output = timestamp + " (" + message.getLevel() + "):\n" + message.getText();
loggingTextArea.setText(output);
}
// 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;
logPopupMenu = new javax.swing.JPopupMenu();
clearMenuItem = new javax.swing.JMenuItem();
loggingTablePopupMenu = new javax.swing.JPopupMenu();
filterMenu = new javax.swing.JMenu();
everythingCheckBoxMenuItem = new javax.swing.JCheckBoxMenuItem();
warningsCheckBoxMenuItem = new javax.swing.JCheckBoxMenuItem();
errorsCheckBoxMenuItem = new javax.swing.JCheckBoxMenuItem();
noVehiclePanel = new javax.swing.JPanel();
noVehicleLabel = new javax.swing.JLabel();
tabbedPane = new javax.swing.JTabbedPane();
overviewTabPanel = new javax.swing.JPanel();
headPanel = new javax.swing.JPanel();
statusPanel = new javax.swing.JPanel();
adapterStatusPanel = new javax.swing.JPanel();
chkBoxEnable = new javax.swing.JCheckBox();
statusFiguresPanel = new javax.swing.JPanel();
curPosLbl = new javax.swing.JLabel();
curPosTxt = new javax.swing.JTextField();
curStateLbl = new javax.swing.JLabel();
curStateTxt = new javax.swing.JTextField();
fillingLbl = new javax.swing.JLabel();
logoPanel = new javax.swing.JPanel();
logoLbl = new javax.swing.JLabel();
logPanel = new javax.swing.JPanel();
logTableScrollPane = new javax.swing.JScrollPane();
loggingTable = new javax.swing.JTable();
logTextScrollPane = new javax.swing.JScrollPane();
loggingTextArea = new javax.swing.JTextArea();
java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("i18n/org/opentcs/kernelcontrolcenter/Bundle"); // NOI18N
clearMenuItem.setText(bundle.getString("detailPanel.popupMenu_messageDetails.menuItem_clear.text")); // NOI18N
clearMenuItem.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
clearMenuItemActionPerformed(evt);
}
});
logPopupMenu.add(clearMenuItem);
filterMenu.setText(bundle.getString("detailPanel.popupMenu_messagesTable.subMenu_filter.text")); // NOI18N
filterMenu.setActionCommand(" message filtering");
everythingCheckBoxMenuItem.setText(bundle.getString("detailPanel.popupMenu_messagesTable.subMenu_filter.menuItem_showAll.text")); // NOI18N
everythingCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
everythingCheckBoxMenuItemActionPerformed(evt);
}
});
filterMenu.add(everythingCheckBoxMenuItem);
warningsCheckBoxMenuItem.setText(bundle.getString("detailPanel.popupMenu_messagesTable.subMenu_filter.menuItem_showErrorsAndWarnings.text")); // NOI18N
warningsCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
warningsCheckBoxMenuItemActionPerformed(evt);
}
});
filterMenu.add(warningsCheckBoxMenuItem);
errorsCheckBoxMenuItem.setText(bundle.getString("detailPanel.popupMenu_messagesTable.subMenu_filter.menuItem_showErrors.text")); // NOI18N
errorsCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
errorsCheckBoxMenuItemActionPerformed(evt);
}
});
filterMenu.add(errorsCheckBoxMenuItem);
loggingTablePopupMenu.add(filterMenu);
noVehiclePanel.setLayout(new java.awt.BorderLayout());
noVehicleLabel.setFont(noVehicleLabel.getFont().deriveFont(noVehicleLabel.getFont().getSize()+3f));
noVehicleLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
noVehicleLabel.setText(bundle.getString("detailPanel.label_noVehicleAttached.text")); // NOI18N
noVehiclePanel.add(noVehicleLabel, java.awt.BorderLayout.CENTER);
setBorder(javax.swing.BorderFactory.createTitledBorder(DEFAULT_BORDER_TITLE));
setLayout(new java.awt.BorderLayout());
tabbedPane.setTabLayoutPolicy(javax.swing.JTabbedPane.SCROLL_TAB_LAYOUT);
overviewTabPanel.setLayout(new java.awt.GridBagLayout());
headPanel.setLayout(new java.awt.BorderLayout());
statusPanel.setLayout(new javax.swing.BoxLayout(statusPanel, javax.swing.BoxLayout.LINE_AXIS));
adapterStatusPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(bundle.getString("detailPanel.panel_adapterStatus.border.title"))); // NOI18N
adapterStatusPanel.setLayout(new java.awt.BorderLayout());
chkBoxEnable.setText(bundle.getString("detailPanel.checkBox_enableAdapter.text")); // NOI18N
chkBoxEnable.setEnabled(false);
chkBoxEnable.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
chkBoxEnableActionPerformed(evt);
}
});
adapterStatusPanel.add(chkBoxEnable, java.awt.BorderLayout.CENTER);
statusPanel.add(adapterStatusPanel);
statusFiguresPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(bundle.getString("detailPanel.panel_vehicleStatus.border.title"))); // NOI18N
statusFiguresPanel.setLayout(new java.awt.GridBagLayout());
curPosLbl.setText(bundle.getString("detailPanel.label_currentPosition.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 0);
statusFiguresPanel.add(curPosLbl, gridBagConstraints);
curPosTxt.setEditable(false);
curPosTxt.setColumns(9);
curPosTxt.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
curPosTxt.setText("Point-0001");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 0);
statusFiguresPanel.add(curPosTxt, gridBagConstraints);
curStateLbl.setText(bundle.getString("detailPanel.label_currentState.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 0, 0);
statusFiguresPanel.add(curStateLbl, gridBagConstraints);
curStateTxt.setEditable(false);
curStateTxt.setColumns(9);
curStateTxt.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
curStateTxt.setText(Vehicle.State.UNKNOWN.name());
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 1;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 0, 0);
statusFiguresPanel.add(curStateTxt, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.weightx = 1.0;
statusFiguresPanel.add(fillingLbl, gridBagConstraints);
statusPanel.add(statusFiguresPanel);
headPanel.add(statusPanel, java.awt.BorderLayout.WEST);
logoPanel.setBackground(new java.awt.Color(255, 255, 255));
logoPanel.setLayout(new java.awt.BorderLayout());
logoLbl.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
logoLbl.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/opentcs/kernelcontrolcenter/res/logos/opentcs_logo.gif"))); // NOI18N
logoPanel.add(logoLbl, java.awt.BorderLayout.CENTER);
headPanel.add(logoPanel, java.awt.BorderLayout.CENTER);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
overviewTabPanel.add(headPanel, gridBagConstraints);
logPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(bundle.getString("detailPanel.panel_messages.border.title"))); // NOI18N
logPanel.setPreferredSize(new java.awt.Dimension(468, 200));
logPanel.setLayout(new java.awt.BorderLayout());
logTableScrollPane.setComponentPopupMenu(loggingTablePopupMenu);
loggingTable.setModel(new javax.swing.table.DefaultTableModel(
new Object [][] {
},
new String [] {
"Time stamp", "Message"
}
) {
boolean[] canEdit = new boolean [] {
false, false
};
public boolean isCellEditable(int rowIndex, int columnIndex) {
return canEdit [columnIndex];
}
});
loggingTable.setComponentPopupMenu(loggingTablePopupMenu);
loggingTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
logTableScrollPane.setViewportView(loggingTable);
logPanel.add(logTableScrollPane, java.awt.BorderLayout.CENTER);
loggingTextArea.setEditable(false);
loggingTextArea.setColumns(20);
loggingTextArea.setFont(new java.awt.Font("Courier New", 0, 12)); // NOI18N
loggingTextArea.setLineWrap(true);
loggingTextArea.setRows(3);
loggingTextArea.setComponentPopupMenu(logPopupMenu);
logTextScrollPane.setViewportView(loggingTextArea);
logPanel.add(logTextScrollPane, java.awt.BorderLayout.SOUTH);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
overviewTabPanel.add(logPanel, gridBagConstraints);
tabbedPane.addTab(bundle.getString("detailPanel.tab_generalStatus.text"), overviewTabPanel); // NOI18N
add(tabbedPane, java.awt.BorderLayout.CENTER);
}// </editor-fold>//GEN-END:initComponents
// CHECKSTYLE:ON
// FORMATTER:ON
private void warningsCheckBoxMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_warningsCheckBoxMenuItemActionPerformed
loggingTableModel.filterMessages(
(notification) -> notification.getLevel().equals(UserNotification.Level.IMPORTANT)
|| notification.getLevel().equals(UserNotification.Level.NOTEWORTHY)
);
warningsCheckBoxMenuItem.setSelected(true);
errorsCheckBoxMenuItem.setSelected(false);
everythingCheckBoxMenuItem.setSelected(false);
}//GEN-LAST:event_warningsCheckBoxMenuItemActionPerformed
private void everythingCheckBoxMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_everythingCheckBoxMenuItemActionPerformed
loggingTableModel.filterMessages((notification) -> true);
everythingCheckBoxMenuItem.setSelected(true);
errorsCheckBoxMenuItem.setSelected(false);
warningsCheckBoxMenuItem.setSelected(false);
}//GEN-LAST:event_everythingCheckBoxMenuItemActionPerformed
private void errorsCheckBoxMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_errorsCheckBoxMenuItemActionPerformed
loggingTableModel.filterMessages(
(notification) -> notification.getLevel().equals(UserNotification.Level.IMPORTANT)
);
errorsCheckBoxMenuItem.setSelected(true);
everythingCheckBoxMenuItem.setSelected(false);
warningsCheckBoxMenuItem.setSelected(false);
}//GEN-LAST:event_errorsCheckBoxMenuItemActionPerformed
private void clearMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clearMenuItemActionPerformed
loggingTextArea.setText("");
}//GEN-LAST:event_clearMenuItemActionPerformed
private void chkBoxEnableActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkBoxEnableActionPerformed
try {
if (chkBoxEnable.isSelected()) {
callWrapper.call(
() -> servicePortal.getVehicleService()
.enableCommAdapter(vehicleEntry.getAttachmentInformation().getVehicleReference())
);
}
else {
callWrapper.call(
() -> servicePortal.getVehicleService()
.disableCommAdapter(vehicleEntry.getAttachmentInformation().getVehicleReference())
);
}
}
catch (Exception ex) {
LOG.warn("Error enabling/disabling comm adapter for {}", vehicleEntry.getVehicleName(), ex);
}
}//GEN-LAST:event_chkBoxEnableActionPerformed
// FORMATTER:OFF
// CHECKSTYLE:OFF
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel adapterStatusPanel;
private javax.swing.JCheckBox chkBoxEnable;
private javax.swing.JMenuItem clearMenuItem;
private javax.swing.JLabel curPosLbl;
private javax.swing.JTextField curPosTxt;
private javax.swing.JLabel curStateLbl;
private javax.swing.JTextField curStateTxt;
private javax.swing.JCheckBoxMenuItem errorsCheckBoxMenuItem;
private javax.swing.JCheckBoxMenuItem everythingCheckBoxMenuItem;
private javax.swing.JLabel fillingLbl;
private javax.swing.JMenu filterMenu;
private javax.swing.JPanel headPanel;
private javax.swing.JPanel logPanel;
private javax.swing.JPopupMenu logPopupMenu;
private javax.swing.JScrollPane logTableScrollPane;
private javax.swing.JScrollPane logTextScrollPane;
private javax.swing.JTable loggingTable;
private javax.swing.JPopupMenu loggingTablePopupMenu;
private javax.swing.JTextArea loggingTextArea;
private javax.swing.JLabel logoLbl;
private javax.swing.JPanel logoPanel;
private javax.swing.JLabel noVehicleLabel;
private javax.swing.JPanel noVehiclePanel;
private javax.swing.JPanel overviewTabPanel;
private javax.swing.JPanel statusFiguresPanel;
private javax.swing.JPanel statusPanel;
private javax.swing.JTabbedPane tabbedPane;
private javax.swing.JCheckBoxMenuItem warningsCheckBoxMenuItem;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
/**
* A <code>ListSelectionListener</code> for handling the logging table selection events.
*/
private final class RowListener
implements
ListSelectionListener {
/**
* Creates a new instance.
*/
private RowListener() {
}
@Override
public void valueChanged(ListSelectionEvent event) {
if (event.getValueIsAdjusting()) {
return;
}
if (loggingTable.getSelectedRow() >= 0) {
outputLogNotification(loggingTable.getSelectedRow());
}
else {
loggingTextArea.setText("");
}
}
}
}

View File

@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.3" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Container class="javax.swing.JPopupMenu" name="vehicleListPopupMenu">
<Events>
<EventHandler event="popupMenuWillBecomeVisible" listener="javax.swing.event.PopupMenuListener" parameters="javax.swing.event.PopupMenuEvent" handler="vehicleListPopupMenuPopupMenuWillBecomeVisible"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout">
<Property name="useNullLayout" type="boolean" value="true"/>
</Layout>
<SubComponents>
<Menu class="javax.swing.JMenu" name="driverMenu">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="driverGui.popupMenu_vehicles.subMenu_driver.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="menuSelected" listener="javax.swing.event.MenuListener" parameters="javax.swing.event.MenuEvent" handler="driverMenuMenuSelected"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<SubComponents>
<MenuItem class="javax.swing.JMenuItem" name="noDriversMenuItem">
<Properties>
<Property name="text" type="java.lang.String" value="No drivers available."/>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
</MenuItem>
</SubComponents>
</Menu>
<Component class="javax.swing.JSeparator" name="jSeparator1">
</Component>
<MenuItem class="javax.swing.JMenuItem" name="enableAllMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="driverGui.popupMenu_vehicles.menuItem_enableAll.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="enableAllMenuItemActionPerformed"/>
</Events>
</MenuItem>
<MenuItem class="javax.swing.JMenuItem" name="enableAllSelectedMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="driverGui.popupMenu_vehicles.menuItem_enableSelected.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="enableAllSelectedMenuItemActionPerformed"/>
</Events>
</MenuItem>
<Component class="javax.swing.JSeparator" name="jSeparator4">
</Component>
<MenuItem class="javax.swing.JMenuItem" name="disableAllMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="driverGui.popupMenu_vehicles.menuItem_disableAll.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="disableAllMenuItemActionPerformed"/>
</Events>
</MenuItem>
<MenuItem class="javax.swing.JMenuItem" name="disableAllSelectedMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="driverGui.popupMenu_vehicles.menuItem_disableSelected.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="disableAllSelectedMenuItemActionPerformed"/>
</Events>
</MenuItem>
</SubComponents>
</Container>
</NonVisualComponents>
<AccessibilityProperties>
<Property name="AccessibleContext.accessibleName" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="driverGui.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="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,2,-104,0,0,4,99"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout">
<Property name="axis" type="int" value="0"/>
</Layout>
<SubComponents>
<Container class="javax.swing.JPanel" name="listDisplayPanel">
<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="Vehicles in model">
<ResourceString PropertyName="titleX" bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="driverGui.panel_vehicles.border.title" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[464, 2147483647]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[464, 425]"/>
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<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.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Center"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="vehicleTable">
<Properties>
<Property name="componentPopupMenu" type="javax.swing.JPopupMenu" editor="org.netbeans.modules.form.ComponentChooserEditor">
<ComponentRef name="vehicleListPopupMenu"/>
</Property>
</Properties>
<Events>
<EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="vehicleTableMouseClicked"/>
</Events>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="vehicleDetailPanel">
<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 details">
<ResourceString PropertyName="titleX" bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="driverGui.panel_vehicleDetails.border.title" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[800, 23]"/>
</Property>
</Properties>
<AccessibilityProperties>
<Property name="AccessibleContext.accessibleName" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/kernelcontrolcenter/Bundle.properties" key="driverGui.panel_vehicleDetails.accessibleName" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
</AccessibilityProperties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
</Container>
</SubComponents>
</Form>

View File

@@ -0,0 +1,682 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.vehicles;
import static java.util.Objects.requireNonNull;
import static org.opentcs.kernelcontrolcenter.I18nKernelControlCenter.BUNDLE_PATH;
import static org.opentcs.util.Assertions.checkState;
import jakarta.annotation.Nonnull;
import jakarta.inject.Inject;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import org.opentcs.access.Kernel;
import org.opentcs.access.KernelServicePortal;
import org.opentcs.components.kernelcontrolcenter.ControlCenterPanel;
import org.opentcs.customizations.ServiceCallWrapper;
import org.opentcs.data.model.Point;
import org.opentcs.drivers.vehicle.VehicleCommAdapterDescription;
import org.opentcs.drivers.vehicle.commands.InitPositionCommand;
import org.opentcs.drivers.vehicle.management.VehicleAttachmentInformation;
import org.opentcs.drivers.vehicle.management.VehicleProcessModelTO;
import org.opentcs.kernelcontrolcenter.util.SingleCellEditor;
import org.opentcs.util.CallWrapper;
import org.opentcs.util.Comparators;
import org.opentcs.util.gui.BoundsPopupMenuListener;
import org.opentcs.util.gui.StringListCellRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A panel containing all vehicles and detailed information.
*/
public class DriverGUI
extends
ControlCenterPanel {
/**
* This class's Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(DriverGUI.class);
/**
* This instance's resource bundle.
*/
private final ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_PATH);
/**
* The service portal to use for kernel interaction.
*/
private final KernelServicePortal servicePortal;
/**
* The call wrapper to use for service calls.
*/
private final CallWrapper callWrapper;
/**
* The pool of vehicle entries.
*/
private final LocalVehicleEntryPool vehicleEntryPool;
/**
* The detail panel to dispay when selecting a vehicle.
*/
private final DetailPanel detailPanel;
/**
* Whether this panel is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param servicePortal The service portal to use for kernel interaction.
* @param callWrapper The call wrapper to use for service calls.
* @param vehicleEntryPool The pool of vehicle entries.
* @param detailPanel The detail panel to display.
*/
@Inject
@SuppressWarnings("this-escape")
public DriverGUI(
@Nonnull
KernelServicePortal servicePortal,
@Nonnull
@ServiceCallWrapper
CallWrapper callWrapper,
@Nonnull
LocalVehicleEntryPool vehicleEntryPool,
@Nonnull
DetailPanel detailPanel
) {
this.servicePortal = requireNonNull(servicePortal, "servicePortal");
this.callWrapper = requireNonNull(callWrapper, "callWrapper");
this.vehicleEntryPool = requireNonNull(vehicleEntryPool, "vehicleEntryPool");
this.detailPanel = requireNonNull(detailPanel, "detailPanel");
initComponents();
vehicleTable.setDefaultRenderer(
VehicleCommAdapterDescription.class,
new VehicleCommAdapterFactoryTableCellRenderer()
);
vehicleDetailPanel.add(detailPanel);
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void initialize() {
if (initialized) {
LOG.debug("Already initialized.");
return;
}
// Verify that the kernel is in a state in which controlling vehicles is possible.
Kernel.State kernelState;
try {
kernelState = callWrapper.call(() -> servicePortal.getState());
}
catch (Exception ex) {
LOG.warn("Error getting the kernel state", ex);
return;
}
checkState(
Kernel.State.OPERATING.equals(kernelState),
"Cannot work in kernel state %s",
kernelState
);
vehicleEntryPool.initialize();
detailPanel.initialize();
EventQueue.invokeLater(() -> {
initVehicleList();
});
initialized = true;
}
@Override
public void terminate() {
if (!initialized) {
LOG.debug("Not initialized.");
return;
}
detailPanel.terminate();
vehicleEntryPool.terminate();
initialized = false;
}
private void initVehicleList() {
vehicleTable.setModel(new VehicleTableModel(servicePortal.getVehicleService(), callWrapper));
VehicleTableModel model = (VehicleTableModel) vehicleTable.getModel();
vehicleEntryPool.getEntries().forEach((vehicleName, entry) -> {
model.addData(entry);
entry.addPropertyChangeListener(model);
});
vehicleTable.getComponentPopupMenu().setEnabled(!model.getVehicleEntries().isEmpty());
initAdapterComboBoxes();
}
/**
* Initializes the combo boxes with available adapters for every vehicle.
*/
private void initAdapterComboBoxes() {
SingleCellEditor adapterCellEditor = new SingleCellEditor(vehicleTable);
SingleCellEditor pointsCellEditor = new SingleCellEditor(vehicleTable);
int index = 0;
for (LocalVehicleEntry entry : vehicleEntryPool.getEntries().values()) {
initCommAdaptersComboBox(entry, index, adapterCellEditor);
initPointsComboBox(index, pointsCellEditor);
index++;
}
vehicleTable.getColumn(VehicleTableModel.adapterColumnIdentifier())
.setCellEditor(adapterCellEditor);
vehicleTable.getColumn(VehicleTableModel.positionColumnIdentifier())
.setCellEditor(pointsCellEditor);
}
private void initCommAdaptersComboBox(
LocalVehicleEntry vehicleEntry,
int rowIndex,
SingleCellEditor adapterCellEditor
) {
final CommAdapterComboBox comboBox = new CommAdapterComboBox();
VehicleAttachmentInformation ai;
try {
ai = callWrapper.call(
() -> servicePortal.getVehicleService().fetchAttachmentInformation(
vehicleEntry.getAttachmentInformation().getVehicleReference()
)
);
}
catch (Exception ex) {
LOG.warn("Error fetching attachment information for {}", vehicleEntry.getVehicleName(), ex);
return;
}
ai.getAvailableCommAdapters().forEach(factory -> comboBox.addItem(factory));
// Set the selection to the attached comm adapter, (The vehicle is already attached to a comm
// adapter due to auto attachment on startup.)
comboBox.setSelectedItem(vehicleEntry.getAttachmentInformation().getAttachedCommAdapter());
comboBox.setRenderer(new AdapterFactoryCellRenderer());
comboBox.addPopupMenuListener(new BoundsPopupMenuListener());
comboBox.addItemListener((ItemEvent evt) -> {
if (evt.getStateChange() == ItemEvent.DESELECTED) {
return;
}
// If we selected a comm adapter that's already attached, do nothing.
if (Objects.equals(evt.getItem(), vehicleEntry.getAttachedCommAdapterDescription())) {
LOG.debug("{} is already attached to: {}", vehicleEntry.getVehicleName(), evt.getItem());
return;
}
int reply = JOptionPane.showConfirmDialog(
null,
bundle.getString("driverGui.optionPane_driverChangeConfirmation.message"),
bundle.getString("driverGui.optionPane_driverChangeConfirmation.title"),
JOptionPane.YES_NO_OPTION
);
if (reply == JOptionPane.NO_OPTION) {
return;
}
VehicleCommAdapterDescription factory = comboBox.getSelectedItem();
try {
callWrapper.call(
() -> servicePortal.getVehicleService().attachCommAdapter(
vehicleEntry.getAttachmentInformation().getVehicleReference(), factory
)
);
}
catch (Exception ex) {
LOG.warn(
"Error attaching adapter {} to vehicle {}",
factory,
vehicleEntry.getVehicleName(),
ex
);
return;
}
LOG.info("Attaching comm adapter {} to {}", factory, vehicleEntry.getVehicleName());
});
adapterCellEditor.setEditorAt(rowIndex, new DefaultCellEditor(comboBox));
vehicleEntry.addPropertyChangeListener(comboBox);
}
/**
* If a loopback adapter was chosen, this method initializes the combo boxes with positions the
* user can set the vehicle to.
*
* @param rowIndex An index indicating which row this combo box belongs to
* @param pointsCellEditor The <code>SingleCellEditor</code> containing the combo boxes.
*/
private void initPointsComboBox(int rowIndex, SingleCellEditor pointsCellEditor) {
final JComboBox<Point> pointComboBox = new JComboBox<>();
Set<Point> points;
try {
points = callWrapper.call(() -> servicePortal.getVehicleService().fetchObjects(Point.class));
}
catch (Exception ex) {
LOG.warn("Error fetching points", ex);
return;
}
points.stream().sorted(Comparators.objectsByName())
.forEach(point -> pointComboBox.addItem(point));
pointComboBox.setSelectedIndex(-1);
pointComboBox.setRenderer(new StringListCellRenderer<>(x -> x == null ? "" : x.getName()));
pointComboBox.addItemListener((ItemEvent e) -> {
try {
if (e.getStateChange() == ItemEvent.SELECTED) {
Point newPoint = (Point) e.getItem();
LocalVehicleEntry vehicleEntry = vehicleEntryPool.getEntryFor(getSelectedVehicleName());
if (vehicleEntry.getAttachedCommAdapterDescription().isSimVehicleCommAdapter()) {
callWrapper.call(
() -> servicePortal.getVehicleService().sendCommAdapterCommand(
vehicleEntry.getAttachmentInformation().getVehicleReference(),
new InitPositionCommand(newPoint.getName())
)
);
}
else {
LOG.debug(
"Vehicle {}: Not a simulation adapter -> not setting initial position.",
vehicleEntry.getVehicleName()
);
}
}
}
catch (Exception ex) {
LOG.warn("Error sending init position command", ex);
}
});
pointsCellEditor.setEditorAt(rowIndex, new DefaultCellEditor(pointComboBox));
}
private void enableAllCommAdapters() {
enableCommAdapters(vehicleEntryPool.getEntries().values());
}
private void enableSelectedCommAdapters() {
enableCommAdapters(getSelectedVehicleEntries());
}
private void enableCommAdapters(Collection<LocalVehicleEntry> selectedEntries) {
Collection<LocalVehicleEntry> entries = selectedEntries.stream()
.filter(entry -> !entry.getProcessModel().isCommAdapterEnabled())
.collect(Collectors.toList());
try {
for (LocalVehicleEntry entry : entries) {
callWrapper.call(
() -> servicePortal.getVehicleService().enableCommAdapter(
entry.getAttachmentInformation().getVehicleReference()
)
);
}
}
catch (Exception ex) {
LOG.warn("Error enabling comm adapter, canceling", ex);
}
}
private void disableAllCommAdapters() {
disableCommAdapters(vehicleEntryPool.getEntries().values());
}
private void disableSelectedCommAdapters() {
disableCommAdapters(getSelectedVehicleEntries());
}
private void disableCommAdapters(Collection<LocalVehicleEntry> selectedEntries) {
Collection<LocalVehicleEntry> entries = selectedEntries.stream()
.filter(entry -> entry.getProcessModel().isCommAdapterEnabled())
.collect(Collectors.toList());
try {
for (LocalVehicleEntry entry : entries) {
callWrapper.call(
() -> servicePortal.getVehicleService().disableCommAdapter(
entry.getAttachmentInformation().getVehicleReference()
)
);
}
}
catch (Exception ex) {
LOG.warn("Error disabling comm adapter, canceling", ex);
}
}
private String getSelectedVehicleName() {
VehicleTableModel model = (VehicleTableModel) vehicleTable.getModel();
return model.getDataAt(vehicleTable.getSelectedRow()).getVehicleName();
}
private List<String> getSelectedVehicleNames() {
List<String> selectedVehicleNames = new ArrayList<>();
VehicleTableModel model = (VehicleTableModel) vehicleTable.getModel();
for (int selectedRow : vehicleTable.getSelectedRows()) {
String selectedVehicleName = model.getDataAt(selectedRow).getVehicleName();
selectedVehicleNames.add(selectedVehicleName);
}
return selectedVehicleNames;
}
private List<LocalVehicleEntry> getSelectedVehicleEntries() {
List<LocalVehicleEntry> selectedEntries = new ArrayList<>();
for (String selectedVehicleName : getSelectedVehicleNames()) {
selectedEntries.add(vehicleEntryPool.getEntryFor(selectedVehicleName));
}
return selectedEntries;
}
private void createDriverMenu() {
driverMenu.removeAll();
// Collect all available comm adapters/factories
Set<VehicleCommAdapterDescription> availableDescriptions = new HashSet<>();
vehicleEntryPool.getEntries().forEach((vehicleName, entry) -> {
availableDescriptions.addAll(entry.getAttachmentInformation().getAvailableCommAdapters());
});
for (VehicleCommAdapterDescription description : availableDescriptions) {
// If there's one vehicle not supported by this factory the selection can't be attached to it
boolean factorySupportsSelectedVehicles = getSelectedVehicleEntries().stream()
.map(entry -> entry.getAttachmentInformation().getAvailableCommAdapters())
.allMatch(descriptions -> !Collections.disjoint(descriptions, availableDescriptions));
List<String> vehiclesToAttach = new ArrayList<>();
if (factorySupportsSelectedVehicles) {
vehiclesToAttach = getSelectedVehicleNames();
}
Action action = new AttachCommAdapterAction(vehiclesToAttach, description);
JMenuItem menuItem = driverMenu.add(action);
menuItem.setEnabled(!vehiclesToAttach.isEmpty());
}
}
private void createPopupMenu() {
// Find out how many vehicles (don't) have a driver attached.
StatesCounts stateCounts = getCommAdapterStateCountsFor(vehicleEntryPool.getEntries().values());
enableAllMenuItem.setEnabled(stateCounts.disabledCount > 0);
disableAllMenuItem.setEnabled(stateCounts.enabledCount > 0);
// Now do the same for those that are selected.
stateCounts = getCommAdapterStateCountsFor(getSelectedVehicleEntries());
enableAllSelectedMenuItem.setEnabled(stateCounts.disabledCount > 0);
disableAllSelectedMenuItem.setEnabled(stateCounts.enabledCount > 0);
}
private StatesCounts getCommAdapterStateCountsFor(Collection<LocalVehicleEntry> entries) {
StatesCounts stateCounts = new StatesCounts();
for (LocalVehicleEntry entry : entries) {
VehicleProcessModelTO processModel = entry.getProcessModel();
if (processModel.isCommAdapterEnabled()) {
stateCounts.enabledCount++;
}
else {
stateCounts.disabledCount++;
}
}
return stateCounts;
}
// 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() {
vehicleListPopupMenu = new javax.swing.JPopupMenu();
driverMenu = new javax.swing.JMenu();
noDriversMenuItem = new javax.swing.JMenuItem();
jSeparator1 = new javax.swing.JSeparator();
enableAllMenuItem = new javax.swing.JMenuItem();
enableAllSelectedMenuItem = new javax.swing.JMenuItem();
jSeparator4 = new javax.swing.JSeparator();
disableAllMenuItem = new javax.swing.JMenuItem();
disableAllSelectedMenuItem = new javax.swing.JMenuItem();
listDisplayPanel = new javax.swing.JPanel();
jScrollPane1 = new javax.swing.JScrollPane();
vehicleTable = new javax.swing.JTable();
vehicleDetailPanel = new javax.swing.JPanel();
vehicleListPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() {
public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) {
}
public void popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent evt) {
}
public void popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) {
vehicleListPopupMenuPopupMenuWillBecomeVisible(evt);
}
});
java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("i18n/org/opentcs/kernelcontrolcenter/Bundle"); // NOI18N
driverMenu.setText(bundle.getString("driverGui.popupMenu_vehicles.subMenu_driver.text")); // NOI18N
driverMenu.addMenuListener(new javax.swing.event.MenuListener() {
public void menuCanceled(javax.swing.event.MenuEvent evt) {
}
public void menuDeselected(javax.swing.event.MenuEvent evt) {
}
public void menuSelected(javax.swing.event.MenuEvent evt) {
driverMenuMenuSelected(evt);
}
});
noDriversMenuItem.setText("No drivers available.");
noDriversMenuItem.setEnabled(false);
driverMenu.add(noDriversMenuItem);
vehicleListPopupMenu.add(driverMenu);
vehicleListPopupMenu.add(jSeparator1);
enableAllMenuItem.setText(bundle.getString("driverGui.popupMenu_vehicles.menuItem_enableAll.text")); // NOI18N
enableAllMenuItem.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
enableAllMenuItemActionPerformed(evt);
}
});
vehicleListPopupMenu.add(enableAllMenuItem);
enableAllSelectedMenuItem.setText(bundle.getString("driverGui.popupMenu_vehicles.menuItem_enableSelected.text")); // NOI18N
enableAllSelectedMenuItem.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
enableAllSelectedMenuItemActionPerformed(evt);
}
});
vehicleListPopupMenu.add(enableAllSelectedMenuItem);
vehicleListPopupMenu.add(jSeparator4);
disableAllMenuItem.setText(bundle.getString("driverGui.popupMenu_vehicles.menuItem_disableAll.text")); // NOI18N
disableAllMenuItem.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
disableAllMenuItemActionPerformed(evt);
}
});
vehicleListPopupMenu.add(disableAllMenuItem);
disableAllSelectedMenuItem.setText(bundle.getString("driverGui.popupMenu_vehicles.menuItem_disableSelected.text")); // NOI18N
disableAllSelectedMenuItem.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
disableAllSelectedMenuItemActionPerformed(evt);
}
});
vehicleListPopupMenu.add(disableAllSelectedMenuItem);
setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.X_AXIS));
listDisplayPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(bundle.getString("driverGui.panel_vehicles.border.title"))); // NOI18N
listDisplayPanel.setMaximumSize(new java.awt.Dimension(464, 2147483647));
listDisplayPanel.setMinimumSize(new java.awt.Dimension(464, 425));
listDisplayPanel.setLayout(new java.awt.BorderLayout());
vehicleTable.setComponentPopupMenu(vehicleListPopupMenu);
vehicleTable.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
vehicleTableMouseClicked(evt);
}
});
jScrollPane1.setViewportView(vehicleTable);
listDisplayPanel.add(jScrollPane1, java.awt.BorderLayout.CENTER);
add(listDisplayPanel);
vehicleDetailPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(bundle.getString("driverGui.panel_vehicleDetails.border.title"))); // NOI18N
vehicleDetailPanel.setPreferredSize(new java.awt.Dimension(800, 23));
vehicleDetailPanel.setLayout(new java.awt.BorderLayout());
add(vehicleDetailPanel);
vehicleDetailPanel.getAccessibleContext().setAccessibleName(bundle.getString("driverGui.panel_vehicleDetails.accessibleName")); // NOI18N
getAccessibleContext().setAccessibleName(bundle.getString("driverGui.accessibleName")); // NOI18N
}// </editor-fold>//GEN-END:initComponents
// CHECKSTYLE:ON
// FORMATTER:ON
private void driverMenuMenuSelected(javax.swing.event.MenuEvent evt) {//GEN-FIRST:event_driverMenuMenuSelected
createDriverMenu();
}//GEN-LAST:event_driverMenuMenuSelected
private void enableAllMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_enableAllMenuItemActionPerformed
enableAllCommAdapters();
}//GEN-LAST:event_enableAllMenuItemActionPerformed
private void enableAllSelectedMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_enableAllSelectedMenuItemActionPerformed
enableSelectedCommAdapters();
}//GEN-LAST:event_enableAllSelectedMenuItemActionPerformed
private void disableAllMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_disableAllMenuItemActionPerformed
disableAllCommAdapters();
}//GEN-LAST:event_disableAllMenuItemActionPerformed
private void disableAllSelectedMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_disableAllSelectedMenuItemActionPerformed
disableSelectedCommAdapters();
}//GEN-LAST:event_disableAllSelectedMenuItemActionPerformed
private void vehicleListPopupMenuPopupMenuWillBecomeVisible(
javax.swing.event.PopupMenuEvent evt
) {//GEN-FIRST:event_vehicleListPopupMenuPopupMenuWillBecomeVisible
createPopupMenu();
}//GEN-LAST:event_vehicleListPopupMenuPopupMenuWillBecomeVisible
private void vehicleTableMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_vehicleTableMouseClicked
if (evt.getClickCount() == 2) {
int index = vehicleTable.getSelectedRow();
if (index >= 0) {
VehicleTableModel model = (VehicleTableModel) vehicleTable.getModel();
LocalVehicleEntry clickedEntry = model.getDataAt(index);
DetailPanel detailPanel = (DetailPanel) vehicleDetailPanel.getComponent(0);
detailPanel.attachToVehicle(clickedEntry);
}
}
}//GEN-LAST:event_vehicleTableMouseClicked
// FORMATTER:OFF
// CHECKSTYLE:OFF
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JMenuItem disableAllMenuItem;
private javax.swing.JMenuItem disableAllSelectedMenuItem;
private javax.swing.JMenu driverMenu;
private javax.swing.JMenuItem enableAllMenuItem;
private javax.swing.JMenuItem enableAllSelectedMenuItem;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JSeparator jSeparator1;
private javax.swing.JSeparator jSeparator4;
private javax.swing.JPanel listDisplayPanel;
private javax.swing.JMenuItem noDriversMenuItem;
private javax.swing.JPanel vehicleDetailPanel;
private javax.swing.JPopupMenu vehicleListPopupMenu;
private javax.swing.JTable vehicleTable;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
/**
* Attaches adapters produced by a given factory to a set of vehicles when performed.
*/
private class AttachCommAdapterAction
extends
AbstractAction {
/**
* The affected vehicles' entries.
*/
private final List<String> vehicleNames;
/**
* The factory providing the communication adapter.
*/
private final VehicleCommAdapterDescription commAdapterDescription;
/**
* Creates a new AttachCommAdapterAction.
*
* @param vehicleNames The affected vehicles' entries.
* @param commAdapterDescription The factory providing the communication adapter.
*/
private AttachCommAdapterAction(
List<String> vehicleNames,
VehicleCommAdapterDescription commAdapterDescription
) {
super(commAdapterDescription.getDescription());
this.vehicleNames = requireNonNull(vehicleNames, "vehicleNames");
this.commAdapterDescription = requireNonNull(commAdapterDescription, "factory");
}
@Override
public void actionPerformed(ActionEvent evt) {
for (String vehicleName : vehicleNames) {
servicePortal.getVehicleService().attachCommAdapter(
vehicleEntryPool.getEntryFor(vehicleName)
.getAttachmentInformation()
.getVehicleReference(),
commAdapterDescription
);
}
}
}
private class StatesCounts {
private int enabledCount;
private int disabledCount;
/**
* Creates a new instance.
*/
StatesCounts() {
}
}
}

View File

@@ -0,0 +1,117 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.vehicles;
import static java.util.Objects.requireNonNull;
import jakarta.annotation.Nonnull;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import org.opentcs.drivers.vehicle.VehicleCommAdapterDescription;
import org.opentcs.drivers.vehicle.management.VehicleAttachmentInformation;
import org.opentcs.drivers.vehicle.management.VehicleProcessModelTO;
/**
* An entry for a vehicle present in kernel with detailed information about its attachment state
* and latest process model.
*/
public class LocalVehicleEntry {
/**
* Used for implementing property change events.
*/
@SuppressWarnings("this-escape")
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
/**
* Detailed information about the attachment state.
*/
private VehicleAttachmentInformation attachmentInformation;
/**
* The current process model to this entry.
*/
private VehicleProcessModelTO processModel;
/**
* Creates a new instance.
*
* @param attachmentInformation Detailed information about the attachment state.
* @param processModel The current process model to this entry.
*/
public LocalVehicleEntry(
VehicleAttachmentInformation attachmentInformation,
VehicleProcessModelTO processModel
) {
this.attachmentInformation = requireNonNull(attachmentInformation, "attachmentInformation");
this.processModel = requireNonNull(processModel, "processModel");
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
@Nonnull
public VehicleAttachmentInformation getAttachmentInformation() {
return attachmentInformation;
}
@Nonnull
public VehicleProcessModelTO getProcessModel() {
return processModel;
}
@Nonnull
public String getVehicleName() {
return attachmentInformation.getVehicleReference().getName();
}
@Nonnull
public VehicleCommAdapterDescription getAttachedCommAdapterDescription() {
return attachmentInformation.getAttachedCommAdapter();
}
public void setAttachmentInformation(
@Nonnull
VehicleAttachmentInformation attachmentInformation
) {
VehicleAttachmentInformation oldAttachmentInformation = this.attachmentInformation;
this.attachmentInformation = requireNonNull(attachmentInformation, "attachmentInformation");
pcs.firePropertyChange(
Attribute.ATTACHMENT_INFORMATION.name(),
oldAttachmentInformation,
attachmentInformation
);
}
public void setProcessModel(
@Nonnull
VehicleProcessModelTO processModel
) {
VehicleProcessModelTO oldProcessModel = this.processModel;
this.processModel = requireNonNull(processModel, "processModel");
pcs.firePropertyChange(
Attribute.PROCESS_MODEL.name(),
oldProcessModel,
processModel
);
}
/**
* Enum elements used as notification arguments to specify which argument changed.
*/
public enum Attribute {
/**
* Indicates a change of the process model reference.
*/
PROCESS_MODEL,
/**
* Indicates a change of the attachment information reference.
*/
ATTACHMENT_INFORMATION
}
}

View File

@@ -0,0 +1,164 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.vehicles;
import static java.util.Objects.requireNonNull;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.inject.Inject;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.opentcs.access.KernelServicePortal;
import org.opentcs.components.Lifecycle;
import org.opentcs.customizations.ApplicationEventBus;
import org.opentcs.customizations.ServiceCallWrapper;
import org.opentcs.data.model.Vehicle;
import org.opentcs.drivers.vehicle.management.ProcessModelEvent;
import org.opentcs.drivers.vehicle.management.VehicleAttachmentEvent;
import org.opentcs.drivers.vehicle.management.VehicleAttachmentInformation;
import org.opentcs.drivers.vehicle.management.VehicleProcessModelTO;
import org.opentcs.util.CallWrapper;
import org.opentcs.util.event.EventHandler;
import org.opentcs.util.event.EventSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides a pool of {@link LocalVehicleEntry}s with an entry for every {@link Vehicle} object in
* the kernel.
*/
public class LocalVehicleEntryPool
implements
EventHandler,
Lifecycle {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(LocalVehicleEntryPool.class);
/**
* The service portal to use for kernel interactions.
*/
private final KernelServicePortal servicePortal;
/**
* The call wrapper to use for service calls.
*/
private final CallWrapper callWrapper;
/**
* Where this instance registers for application events.
*/
private final EventSource eventSource;
/**
* The entries of this pool.
*/
private final Map<String, LocalVehicleEntry> entries = new TreeMap<>();
/**
* Whether the pool is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param servicePortal The service portal to use for kernel interactions.
* @param callWrapper The call wrapper to use for service calls.
* @param eventSource Where this instance registers for application events.
*/
@Inject
public LocalVehicleEntryPool(
KernelServicePortal servicePortal,
@ServiceCallWrapper
CallWrapper callWrapper,
@ApplicationEventBus
EventSource eventSource
) {
this.servicePortal = requireNonNull(servicePortal, "servicePortal");
this.callWrapper = requireNonNull(callWrapper, "callWrapper");
this.eventSource = requireNonNull(eventSource, "eventSource");
}
@Override
public void initialize() {
if (isInitialized()) {
LOG.debug("Already initialized.");
return;
}
eventSource.subscribe(this);
try {
Set<Vehicle> vehicles
= callWrapper.call(() -> servicePortal.getVehicleService().fetchObjects(Vehicle.class));
for (Vehicle vehicle : vehicles) {
VehicleAttachmentInformation ai = callWrapper.call(() -> {
return servicePortal.getVehicleService()
.fetchAttachmentInformation(vehicle.getReference());
});
VehicleProcessModelTO processModel = callWrapper.call(() -> {
return servicePortal.getVehicleService().fetchProcessModel(vehicle.getReference());
});
LocalVehicleEntry entry = new LocalVehicleEntry(ai, processModel);
entries.put(vehicle.getName(), entry);
}
}
catch (Exception ex) {
LOG.warn("Error initializing local vehicle entry pool", ex);
entries.clear();
return;
}
LOG.debug("Initialized vehicle entry pool: {}", entries);
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
LOG.debug("Not initialized.");
return;
}
eventSource.unsubscribe(this);
entries.clear();
initialized = false;
}
@Override
public void onEvent(Object event) {
if (event instanceof ProcessModelEvent) {
ProcessModelEvent e = (ProcessModelEvent) event;
LocalVehicleEntry entry = getEntryFor(e.getUpdatedProcessModel().getName());
if (entry == null) {
return;
}
entry.setProcessModel(e.getUpdatedProcessModel());
}
else if (event instanceof VehicleAttachmentEvent) {
VehicleAttachmentEvent e = (VehicleAttachmentEvent) event;
LocalVehicleEntry entry = getEntryFor(e.getVehicleName());
if (entry == null) {
return;
}
entry.setAttachmentInformation(e.getAttachmentInformation());
}
}
@Nonnull
public Map<String, LocalVehicleEntry> getEntries() {
return entries;
}
@Nullable
public LocalVehicleEntry getEntryFor(String vehicleName) {
return vehicleName == null ? null : entries.get(vehicleName);
}
}

View File

@@ -0,0 +1,177 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.vehicles;
import static java.util.Objects.requireNonNull;
import static org.opentcs.kernelcontrolcenter.I18nKernelControlCenter.BUNDLE_PATH;
import java.text.DateFormat;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.swing.table.AbstractTableModel;
import org.opentcs.data.notification.UserNotification;
/**
* A table model for holding {@link UserNotification} instances in rows.
*/
final class LogTableModel
extends
AbstractTableModel {
/**
* The column names.
*/
private static final String[] COLUMN_NAMES
= new String[]{
ResourceBundle.getBundle(BUNDLE_PATH)
.getString("logTableModel.column_timeStamp.headerText"),
ResourceBundle.getBundle(BUNDLE_PATH)
.getString("logTableModel.column_message.headerText")
};
/**
* The column classes.
*/
private static final Class<?>[] COLUMN_CLASSES
= new Class<?>[]{
String.class,
String.class
};
/**
* A {@link DateFormat} instance for formatting notifications' time stamps.
*/
private final DateTimeFormatter dateFormat = DateTimeFormatter
.ofLocalizedDateTime(FormatStyle.SHORT)
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault());
/**
* The buffer for holding the model data.
*/
private final Set<UserNotification> values
= new TreeSet<>((n1, n2) -> n1.getTimestamp().compareTo(n2.getTimestamp()));
/**
* The actual data displayed in the table.
*/
private List<UserNotification> filteredValues = new ArrayList<>();
/**
* The predicate used for filtering table rows.
*/
private Predicate<UserNotification> filterPredicate = (notification) -> true;
/**
* Creates a new instance.
*/
LogTableModel() {
}
@Override
public Object getValueAt(int row, int column) {
if (row < 0 || row >= filteredValues.size()) {
return null;
}
switch (column) {
case 0:
return dateFormat.format(filteredValues.get(row).getTimestamp());
case 1:
return filteredValues.get(row).getText();
default:
return new IllegalArgumentException("Column out of bounds.");
}
}
/**
* Adds a row to the model, containing the given notification.
*
* @param notification The notification to be added in a new row.
*/
public void addRow(UserNotification notification) {
requireNonNull(notification, "notification");
values.add(notification);
updateFilteredValues();
fireTableDataChanged();
}
/**
* Removes the given notification from the internal data set and from the current data model.
*
* @param notification The notification to be removed.
*/
public void removeRow(UserNotification notification) {
if (values.contains(notification)) {
values.remove(notification);
updateFilteredValues();
fireTableDataChanged();
}
}
/**
* Removes all notifications from the model.
*/
public void clear() {
if (!values.isEmpty()) {
values.clear();
updateFilteredValues();
fireTableDataChanged();
}
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return COLUMN_CLASSES[columnIndex];
}
/**
* Returns the notification object representing the indexed row.
*
* @param row The row for which to fetch the notification object.
* @return The notification object representing the indexed row.
*/
public UserNotification getRow(int row) {
return filteredValues.get(row);
}
/**
* Filters the notifications and shows only errors and warnings.
*
* @param predicate The predicate used for filtering table rows.
*/
public void filterMessages(Predicate<UserNotification> predicate) {
this.filterPredicate = requireNonNull(predicate, "predicate");
updateFilteredValues();
fireTableDataChanged();
}
@Override
public int getRowCount() {
return filteredValues.size();
}
@Override
public int getColumnCount() {
return COLUMN_NAMES.length;
}
@Override
public String getColumnName(int columnIndex) {
try {
return COLUMN_NAMES[columnIndex];
}
catch (ArrayIndexOutOfBoundsException exc) {
return "ERROR";
}
}
private void updateFilteredValues() {
filteredValues = values.stream().filter(filterPredicate).collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.vehicles;
import java.awt.Component;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import org.opentcs.drivers.vehicle.VehicleCommAdapterDescription;
/**
* A {@link TableCellRenderer} for {@link VehicleCommAdapterDescription} instances.
* This class provides a representation of any VehicleCommAdapterDescription instance by writing
* its actual description on a JLabel.
*/
class VehicleCommAdapterFactoryTableCellRenderer
extends
DefaultTableCellRenderer {
VehicleCommAdapterFactoryTableCellRenderer() {
}
@Override
public Component getTableCellRendererComponent(
JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column
)
throws IllegalArgumentException {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (value == null) {
setText("");
}
else if (value instanceof VehicleCommAdapterDescription) {
setText(((VehicleCommAdapterDescription) value).getDescription());
}
else {
throw new IllegalArgumentException("value");
}
return this;
}
}

View File

@@ -0,0 +1,319 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernelcontrolcenter.vehicles;
import static java.util.Objects.requireNonNull;
import static org.opentcs.kernelcontrolcenter.I18nKernelControlCenter.BUNDLE_PATH;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.ResourceBundle;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import org.opentcs.components.kernel.services.VehicleService;
import org.opentcs.drivers.vehicle.VehicleCommAdapterDescription;
import org.opentcs.drivers.vehicle.management.VehicleAttachmentInformation;
import org.opentcs.drivers.vehicle.management.VehicleProcessModelTO;
import org.opentcs.util.CallWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A model for displaying a list/table of vehicles in the kernel GUI.
*/
public class VehicleTableModel
extends
AbstractTableModel
implements
PropertyChangeListener {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(VehicleTableModel.class);
/**
* This class's resource bundle.
*/
private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(BUNDLE_PATH);
/**
* The column names.
*/
private static final String[] COLUMN_NAMES
= new String[]{
BUNDLE.getString("vehicleTableModel.column_vehicle.headerText"),
BUNDLE.getString("vehicleTableModel.column_state.headerText"),
BUNDLE.getString("vehicleTableModel.column_adapter.headerText"),
BUNDLE.getString("vehicleTableModel.column_enabled.headerText"),
BUNDLE.getString("vehicleTableModel.column_position.headerText")
};
/**
* The column classes.
*/
private static final Class<?>[] COLUMN_CLASSES
= new Class<?>[]{
String.class,
String.class,
VehicleCommAdapterDescription.class,
Boolean.class,
String.class
};
/**
* The index of the column showing the vehicle name.
*/
private static final int VEHICLE_COLUMN = 0;
/**
* The index of the column showing the vehicle state.
*/
private static final int STATE_COLUMNN = 1;
/**
* The index of the column showing the associated adapter.
*/
private static final int ADAPTER_COLUMN = 2;
/**
* The index of the column showing the adapter's enabled state.
*/
private static final int ENABLED_COLUMN = 3;
/**
* The index of the column showing the vehicle's current position.
*/
private static final int POSITION_COLUMN = 4;
/**
* The identifier for the adapter column.
*/
private static final String ADAPTER_COLUMN_IDENTIFIER = COLUMN_NAMES[ADAPTER_COLUMN];
/**
* The identifier for the position column.
*/
private static final String POSITION_COLUMN_IDENTIFIER = COLUMN_NAMES[POSITION_COLUMN];
/**
* The vehicles we're controlling.
*/
private final List<LocalVehicleEntry> entries = new ArrayList<>();
/**
* The vehicle service used for interactions.
*/
private final VehicleService vehicleService;
/**
* The call wrapper to use for service calls.
*/
private final CallWrapper callWrapper;
/**
* Creates a new instance.
*
* @param vehicleService The vehicle service used for interactions.
* @param callWrapper The call wrapper to use for service calls.
*/
public VehicleTableModel(
VehicleService vehicleService,
CallWrapper callWrapper
) {
this.vehicleService = requireNonNull(vehicleService, "vehicleService");
this.callWrapper = requireNonNull(callWrapper, "callWrapper");
}
/**
* Returns the identifier for the adapter column.
*
* @return The identifier for the adapter column.
*/
public static String adapterColumnIdentifier() {
return ADAPTER_COLUMN_IDENTIFIER;
}
/**
* Returns the identifier for the position column.
*
* @return The identifier for the position column.
*/
public static String positionColumnIdentifier() {
return POSITION_COLUMN_IDENTIFIER;
}
/**
* Adds a new entry to this model.
*
* @param newEntry The new entry.
*/
public void addData(LocalVehicleEntry newEntry) {
entries.add(newEntry);
fireTableRowsInserted(entries.size(), entries.size());
}
/**
* Returns the vehicle entry at the given row.
*
* @param row The row.
* @return The entry at the given row.
*/
public LocalVehicleEntry getDataAt(int row) {
if (row >= 0) {
return entries.get(row);
}
else {
return null;
}
}
@Override
public int getRowCount() {
return entries.size();
}
@Override
public int getColumnCount() {
return COLUMN_NAMES.length;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
LocalVehicleEntry entry = entries.get(rowIndex);
switch (columnIndex) {
case ADAPTER_COLUMN:
break;
case ENABLED_COLUMN:
setEnabledState((boolean) aValue, entry);
break;
case POSITION_COLUMN:
break;
default:
LOG.warn("Unhandled column index: {}", columnIndex);
}
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (rowIndex >= entries.size()) {
return null;
}
LocalVehicleEntry entry = entries.get(rowIndex);
switch (columnIndex) {
case VEHICLE_COLUMN:
return entry.getVehicleName();
case STATE_COLUMNN:
return getVehicleState(entry);
case ADAPTER_COLUMN:
return entry.getAttachmentInformation().getAttachedCommAdapter();
case ENABLED_COLUMN:
return entry.getProcessModel().isCommAdapterEnabled();
case POSITION_COLUMN:
return entry.getProcessModel().getPosition();
default:
LOG.warn("Unhandled column index: {}", columnIndex);
return "Invalid column index " + columnIndex;
}
}
@Override
public String getColumnName(int columnIndex) {
try {
return COLUMN_NAMES[columnIndex];
}
catch (ArrayIndexOutOfBoundsException exc) {
LOG.warn("Invalid columnIndex", exc);
return "Invalid column index " + columnIndex;
}
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return COLUMN_CLASSES[columnIndex];
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
switch (columnIndex) {
case ADAPTER_COLUMN:
return true;
case ENABLED_COLUMN:
return true;
case POSITION_COLUMN:
LocalVehicleEntry entry = entries.get(rowIndex);
return entry.getAttachedCommAdapterDescription().isSimVehicleCommAdapter()
&& entry.getProcessModel().isCommAdapterEnabled();
default:
return false;
}
}
/**
* Returns a list containing the vehicle models associated with this model.
*
* @return A list containing the vehicle models associated with this model.
*/
public List<LocalVehicleEntry> getVehicleEntries() {
return entries;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (!(evt.getSource() instanceof LocalVehicleEntry)) {
return;
}
if (!isRelevantUpdate(evt)) {
return;
}
LocalVehicleEntry entry = (LocalVehicleEntry) evt.getSource();
for (int index = 0; index < entries.size(); index++) {
if (entry == entries.get(index)) {
int myIndex = index;
SwingUtilities.invokeLater(() -> fireTableRowsUpdated(myIndex, myIndex));
}
}
}
private void setEnabledState(boolean enabled, LocalVehicleEntry entry) {
try {
if (enabled) {
callWrapper.call(
() -> vehicleService.enableCommAdapter(
entry.getAttachmentInformation().getVehicleReference()
)
);
}
else {
callWrapper.call(
() -> vehicleService.disableCommAdapter(
entry.getAttachmentInformation().getVehicleReference()
)
);
}
}
catch (Exception ex) {
LOG.warn("Error enabling/disabling comm adapter for {}", entry.getVehicleName(), ex);
}
}
private String getVehicleState(LocalVehicleEntry entry) {
return entry.getProcessModel().getState().name();
}
private boolean isRelevantUpdate(PropertyChangeEvent evt) {
if (Objects.equals(
evt.getPropertyName(),
LocalVehicleEntry.Attribute.ATTACHMENT_INFORMATION.name()
)) {
VehicleAttachmentInformation oldInfo = (VehicleAttachmentInformation) evt.getOldValue();
VehicleAttachmentInformation newInfo = (VehicleAttachmentInformation) evt.getNewValue();
return !oldInfo.getAttachedCommAdapter().equals(newInfo.getAttachedCommAdapter());
}
if (Objects.equals(
evt.getPropertyName(),
LocalVehicleEntry.Attribute.PROCESS_MODEL.name()
)) {
VehicleProcessModelTO oldTo = (VehicleProcessModelTO) evt.getOldValue();
VehicleProcessModelTO newTo = (VehicleProcessModelTO) evt.getNewValue();
return oldTo.isCommAdapterEnabled() != newTo.isCommAdapterEnabled()
|| oldTo.getState() != newTo.getState()
|| !Objects.equals(oldTo.getPosition(), newTo.getPosition());
}
return false;
}
}

View File

@@ -0,0 +1,7 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
/**
* Classes of the kernel control center concerning vehicles/vehicle drivers.
* The GUI parts of the driver framework.
*/
package org.opentcs.kernelcontrolcenter.vehicles;

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,60 @@
# SPDX-FileCopyrightText: The openTCS Authors
# SPDX-License-Identifier: CC-BY-4.0
aboutDialog.button_close.text=Close
aboutDialog.label_baselineVersion.text=Baseline version:
aboutDialog.label_customVersion.text=Custom version:
aboutDialog.label_email.text=E-mail:
aboutDialog.label_fraunhoferIml.text=<html><b>Maintainer:<br>Fraunhofer Institute for<br>Material Flow and Logistics (IML)</b></html>
aboutDialog.label_homepage.text=Home page:
aboutDialog.title=About openTCS
defaultServiceCallWrapper.optionPane_retryConfirmation.message=Do you want to retry the last service call?
defaultServiceCallWrapper.optionPane_retryConfirmation.title=Retry service call?
detailPanel.checkBox_enableAdapter.text=Enable adapter
detailPanel.label_currentPosition.text=Current position:
detailPanel.label_currentState.text=Current state:
detailPanel.label_noVehicleAttached.text=<html>No vehicle attached to this panel.<br>Doubleclick on a vehicle to display it here.</html>
detailPanel.panel_adapterStatus.border.title=Adapter status
detailPanel.panel_messages.border.title=Messages
detailPanel.panel_vehicleStatus.border.title=Vehicle status
detailPanel.popupMenu_messageDetails.menuItem_clear.text=Clear log message text
detailPanel.popupMenu_messagesTable.subMenu_filter.menuItem_showAll.text=Show all messages
detailPanel.popupMenu_messagesTable.subMenu_filter.menuItem_showErrors.text=Show errors only
detailPanel.popupMenu_messagesTable.subMenu_filter.menuItem_showErrorsAndWarnings.text=Show errors and warnings only
detailPanel.popupMenu_messagesTable.subMenu_filter.text=Filter messages...
detailPanel.tab_generalStatus.text=General status
driverGui.accessibleName=Vehicle driver
driverGui.optionPane_driverChangeConfirmation.message=<html>Do you really want to change the associated driver?<br>(This will reset the vehicle's resource allocations.)</html>
driverGui.optionPane_driverChangeConfirmation.title=Really change the driver?
driverGui.panel_vehicleDetails.accessibleName=Vehicle details
driverGui.panel_vehicleDetails.border.title=Vehicle details
driverGui.panel_vehicles.border.title=Vehicles in model
driverGui.popupMenu_vehicles.menuItem_disableAll.text=Disable all
driverGui.popupMenu_vehicles.menuItem_disableSelected.text=Disable selected
driverGui.popupMenu_vehicles.menuItem_enableAll.text=Enable all
driverGui.popupMenu_vehicles.menuItem_enableSelected.text=Enable selected
driverGui.popupMenu_vehicles.subMenu_driver.text=Driver
kernelControlCenter.checkBox_autoScroll.text=Enable automatic scrolling (i.e. always show youngest messages)
kernelControlCenter.menu_about.text=About openTCS
kernelControlCenter.menu_help.text=Help
kernelControlCenter.menu_kernel.menuItem_connect.text=Connect to kernel
kernelControlCenter.menu_kernel.menuItem_disconnect.text=Disconnect from kernel
kernelControlCenter.menu_kernel.menuItem_exit.text=Exit
kernelControlCenter.tab_logging.title=Logging
kernelControlCenter.title.connectedTo=Connected to:
kernelControlCenter.title=Kernel Control Center
logTableModel.column_message.headerText=Message
logTableModel.column_timeStamp.headerText=Time stamp
peripheralDetailPanel.label_noPeripheralDeviceAttached.text=<html>No peripheral device attached to this panel.<br>Doubleclick on a peripheral device to display it here.</html>
peripheralTableModel.column_adapter.headerText=Adapter
peripheralTableModel.column_enabled.headerText=Enabled?
peripheralTableModel.column_location.headerText=Location
peripheralsPanel.accessibleName=Peripheral driver
peripheralsPanel.checkBox_hideDetachedLocations.text=Hide detached locations
peripheralsPanel.optionPane_driverChangeConfirmation.message=Do you really want to change the associated driver?
peripheralsPanel.optionPane_driverChangeConfirmation.title=Really change the driver?
vehicleTableModel.column_adapter.headerText=Adapter
vehicleTableModel.column_enabled.headerText=Enabled?
vehicleTableModel.column_position.headerText=Position
vehicleTableModel.column_state.headerText=State
vehicleTableModel.column_vehicle.headerText=Vehicle

View File

@@ -0,0 +1,59 @@
# SPDX-FileCopyrightText: The openTCS Authors
# SPDX-License-Identifier: CC-BY-4.0
aboutDialog.button_close.text=Schlie\u00dfen
aboutDialog.label_baselineVersion.text=Baseline-Version:
aboutDialog.label_customVersion.text=Custom-Version:
aboutDialog.label_email.text=E-Mail:
aboutDialog.label_fraunhoferIml.text=<html><b>Maintainer:<br>Fraunhofer-Institut f\u00fcr<br>Materialfluss und Logistik (IML)</b></html>
aboutDialog.label_homepage.text=Homepage:
aboutDialog.title=\u00dcber openTCS
defaultServiceCallWrapper.optionPane_retryConfirmation.message=Wollen Sie den letzten Serviceaufruf erneut versuchen?
defaultServiceCallWrapper.optionPane_retryConfirmation.title=Erneuter Serviceaufruf?
detailPanel.checkBox_enableAdapter.text=Adapter einschalten
detailPanel.label_currentPosition.text=Aktuelle Position:
detailPanel.label_currentState.text=Aktueller Zustand:
detailPanel.label_noVehicleAttached.text=<html>Kein Fahrzeug in diesem Fenster.<br>Klicken Sie ein Fahrzeug doppelt an, um es hier anzuzeigen.</html>
detailPanel.panel_adapterStatus.border.title=Adapterzustand
detailPanel.panel_messages.border.title=Nachrichten
detailPanel.panel_vehicleStatus.border.title=Fahrzeugzustand
detailPanel.popupMenu_messageDetails.menuItem_clear.text=Nachrichtentext l\u00f6schen
detailPanel.popupMenu_messagesTable.subMenu_filter.menuItem_showAll.text=Alle Nachrichten anzeigen
detailPanel.popupMenu_messagesTable.subMenu_filter.menuItem_showErrors.text=Nur Fehler anzeigen
detailPanel.popupMenu_messagesTable.subMenu_filter.menuItem_showErrorsAndWarnings.text=Nur Fehler und Warnungen anzeigen
detailPanel.popupMenu_messagesTable.subMenu_filter.text=Nachrichten filtern...
detailPanel.tab_generalStatus.text=Zustand allgemein
driverGui.accessibleName=Fahrzeugtreiber
driverGui.optionPane_driverChangeConfirmation.message=<html>Wollen Sie wirklich den zugewiesenen Treiber \u00e4ndern?<br>(Dies wird die Ressourcenzuweisungen des Fahrzeugs zur\u00fccksetzen.)</html>
driverGui.optionPane_driverChangeConfirmation.title=Wirklich den Treiber \u00e4ndern?
driverGui.panel_vehicleDetails.border.title=Detailansicht
driverGui.panel_vehicles.border.title=Modellansicht
driverGui.popupMenu_vehicles.menuItem_disableAll.text=Alle Treiber ausschalten
driverGui.popupMenu_vehicles.menuItem_disableSelected.text=Ausgew\u00e4hlte Treiber ausschalten
driverGui.popupMenu_vehicles.menuItem_enableAll.text=Alle Treiber einschalten
driverGui.popupMenu_vehicles.menuItem_enableSelected.text=Ausgew\u00e4hlte Treiber einschalten
driverGui.popupMenu_vehicles.subMenu_driver.text=Treiber
kernelControlCenter.checkBox_autoScroll.text=Automatischen Bildlauf aktivieren (d.h. immer die j\u00fcngste Nachricht anzeigen)
kernelControlCenter.menu_about.text=\u00dcber openTCS
kernelControlCenter.menu_help.text=Hilfe
kernelControlCenter.menu_kernel.menuItem_connect.text=Mit Kernel verbinden
kernelControlCenter.menu_kernel.menuItem_disconnect.text=Von Kernel trennen
kernelControlCenter.menu_kernel.menuItem_exit.text=Beenden
kernelControlCenter.tab_logging.title=Protokollierung
kernelControlCenter.title.connectedTo=Verbunden mit:
kernelControlCenter.title=Kernel-Kontrollzentrum
logTableModel.column_message.headerText=Nachricht
logTableModel.column_timeStamp.headerText=Zeitstempel
peripheralDetailPanel.label_noPeripheralDeviceAttached.text=<html>Kein Peripherieger\u00e4t in diesem Fenster.<br>Klicken Sie eine Station doppelt an, um sie hier anzuzeigen.</html>
peripheralTableModel.column_adapter.headerText=Adapter
peripheralTableModel.column_enabled.headerText=Aktiviert?
peripheralTableModel.column_location.headerText=Station
peripheralsPanel.accessibleName=Peripherietreiber
peripheralsPanel.checkBox_hideDetachedLocations.text=Nicht zugewiesene Stationen ausblenden
peripheralsPanel.optionPane_driverChangeConfirmation.message=Wollen Sie wirklich den zugewiesenen Treiber \u00e4ndern?
peripheralsPanel.optionPane_driverChangeConfirmation.title=Wirklich den Treiber \u00e4ndern?
vehicleTableModel.column_adapter.headerText=Adapter
vehicleTableModel.column_enabled.headerText=Aktiviert?
vehicleTableModel.column_position.headerText=Position
vehicleTableModel.column_state.headerText=Zustand
vehicleTableModel.column_vehicle.headerText=Fahrzeug

View File

@@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: The openTCS Authors
# SPDX-License-Identifier: CC-BY-4.0
# This file contains default configuration values and should not be modified.
# To adjust the application configuration, override values in a separate file.
kernelcontrolcenter.locale = en
kernelcontrolcenter.connectionBookmarks = Localhost|localhost|1099
kernelcontrolcenter.connectAutomaticallyOnStartup = true
kernelcontrolcenter.loggingAreaCapacity = 3000
kernelcontrolcenter.enablePeripheralsPanel = true
ssl.enable = false
ssl.truststoreFile = ./config/truststore.p12
ssl.truststorePassword = password

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB