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,147 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application;
import static java.util.Objects.requireNonNull;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.intern.DefaultCommonDockable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.opentcs.guing.common.components.dockable.DockableTitleComparator;
import org.opentcs.guing.common.components.drawing.DrawingViewScrollPane;
import org.opentcs.util.event.EventSource;
/**
* Manages the mapping of dockables to drawing views, transport order views and
* order sequence views.
*/
public abstract class AbstractViewManager
implements
ViewManager {
/**
* Where we register event listeners.
*/
private final EventSource eventSource;
/**
* Map for Dockable -> DrawingView + Rulers.
*/
private final Map<DefaultSingleCDockable, DrawingViewScrollPane> drawingViewMap;
/**
* Creates a new instance.
*
* @param eventSource Where this instance registers event listeners.
*/
public AbstractViewManager(EventSource eventSource) {
this.eventSource = requireNonNull(eventSource, "eventSource");
drawingViewMap = new TreeMap<>(new DockableTitleComparator());
}
@Override
public Map<DefaultSingleCDockable, DrawingViewScrollPane> getDrawingViewMap() {
return drawingViewMap;
}
/**
* Returns the title texts of all drawing views.
*
* @return List of strings containing the names.
*/
@Override
public List<String> getDrawingViewNames() {
return drawingViewMap.keySet().stream()
.map(dock -> dock.getTitleText())
.sorted()
.collect(Collectors.toList());
}
/**
* Forgets the given dockable.
*
* @param dockable The dockable.
*/
@Override
public void removeDockable(DefaultSingleCDockable dockable) {
DrawingViewScrollPane scrollPane = drawingViewMap.remove(dockable);
if (scrollPane != null) {
eventSource.unsubscribe(scrollPane.getDrawingView());
}
}
/**
* Resets all components.
*/
public void reset() {
drawingViewMap.clear();
}
public int getNextDrawingViewIndex() {
return nextAvailableIndex(drawingViewMap.keySet());
}
/**
* Puts a scroll pane with a key dockable into the drawing view map.
* The scroll pane has to contain the drawing view and both rulers.
*
* @param dockable The dockable the scrollPane is wrapped into. Used as the key.
* @param scrollPane The scroll pane containing the drawing view and rulers.
*/
public void addDrawingView(
DefaultSingleCDockable dockable,
DrawingViewScrollPane scrollPane
) {
requireNonNull(dockable, "dockable");
requireNonNull(scrollPane, "scrollPane");
eventSource.subscribe(scrollPane.getDrawingView());
drawingViewMap.put(dockable, scrollPane);
}
/**
* Evaluates which dockable should be the front dockable.
*
* @return The dockable that should be the front dockable. <code>null</code>
* if no dockables exist.
*/
public DefaultCommonDockable evaluateFrontDockable() {
if (!drawingViewMap.isEmpty()) {
return drawingViewMap.keySet().iterator().next().intern();
}
return null;
}
/**
* Returns the next available index of a set of dockables.
* E.g. if "Dock 0" and "Dock 2" are being used, 1 would be returned.
*
* @param setToIterate The set to iterate.
* @return The next available index.
*/
protected int nextAvailableIndex(Set<DefaultSingleCDockable> setToIterate) {
// Name
Pattern p = Pattern.compile("\\d");
Matcher m;
int biggestIndex = 0;
for (DefaultSingleCDockable dock : setToIterate) {
m = p.matcher(dock.getTitleText());
if (m.find()) {
int index = Integer.parseInt(m.group(0));
if (index > biggestIndex) {
biggestIndex = index;
}
}
}
return biggestIndex + 1;
}
}

View File

@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
/**
* Keeps and provides information about the current state of the application as
* a whole.
*/
public class ApplicationState {
/**
* The application's current mode of operation.
*/
private OperationMode operationMode = OperationMode.UNDEFINED;
/**
* Creates a new instance.
*/
@Inject
public ApplicationState() {
}
/**
* Returns the application's current mode of operation.
*
* @return The application's current mode of operation.
*/
public OperationMode getOperationMode() {
return operationMode;
}
/**
* Checks whether the application is currently in the given mode of operation.
*
* @param mode The mode to check for.
* @return <code>true</code> if, and only if, the application is currently in
* the given mode.
*/
public boolean hasOperationMode(OperationMode mode) {
return operationMode == mode;
}
/**
* Sets the application's current mode of operation.
*
* @param operationMode The application's new mode of operation.
*/
public void setOperationMode(OperationMode operationMode) {
this.operationMode = requireNonNull(operationMode, "operationMode");
}
}

View File

@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application;
import java.util.List;
import org.opentcs.guing.common.components.tree.elements.UserObject;
/**
*/
public interface ComponentsManager {
/**
* Adds the given model components to the data model. (e.g. when pasting)
*
* @param userObjects The user objects to restore.
* @return The restored user objects.
*/
List<UserObject> restoreModelComponents(List<UserObject> userObjects);
}

View File

@@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application;
import org.opentcs.components.plantoverview.PlantModelExporter;
import org.opentcs.components.plantoverview.PlantModelImporter;
import org.opentcs.guing.base.model.ModelComponent;
/**
* Provides some central services for various parts of the plant overview application.
*/
public interface GuiManager {
/**
* Called when an object was selected in the tree view.
*
* @param modelComponent The selected object.
*/
void selectModelComponent(ModelComponent modelComponent);
/**
* Called when an additional object was selected in the tree view.
*
* @param modelComponent The selected object.
*/
void addSelectedModelComponent(ModelComponent modelComponent);
/**
* Called when an object was removed from the tree view (by user interaction).
*
* @param fDataObject The object to be removed.
* @return Indicates whether the object was really removed from the model.
*/
boolean treeComponentRemoved(ModelComponent fDataObject);
/**
* Notifies about a figure object being selected.
*
* @param modelComponent The selected object.
*/
void figureSelected(ModelComponent modelComponent);
/**
* Creates a new, empty model and initializes it.
*/
void createEmptyModel();
/**
* Loads a plant model.
*/
void loadModel();
/**
* Imports a plant model using the given importer.
*
* @param importer The importer.
*/
void importModel(PlantModelImporter importer);
/**
* @return
*/
boolean saveModel();
/**
*
* @return
*/
boolean saveModelAs();
/**
* Exports a plant model using the given exporter.
*
* @param exporter The exporter.
*/
void exportModel(PlantModelExporter exporter);
}

View File

@@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application;
import jakarta.annotation.Nonnull;
import org.opentcs.guing.base.model.elements.BlockModel;
import org.opentcs.guing.base.model.elements.LocationTypeModel;
import org.opentcs.guing.base.model.elements.VehicleModel;
/**
* Provides services concerning model editing.
*/
public interface GuiManagerModeling
extends
GuiManager {
/**
* Creates a new vehicle model.
*
* @return The created vehicle model.
*/
VehicleModel createVehicleModel();
/**
* Creates a new location type model.
*
* @return The created location type model.
*/
LocationTypeModel createLocationTypeModel();
/**
* Creates a new block model.
*
* @return The created block model.
*/
BlockModel createBlockModel();
/**
* Removes a block model.
*
* <p>
* This method is primarily provided for use in plugin panels.
* </p>
*
* @param blockModel The block model to be removed.
*/
void removeBlockModel(
@Nonnull
BlockModel blockModel
);
}

View File

@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application;
import org.opentcs.access.Kernel;
/**
* Listener interface implemented by classes interested in changes of a
* connected kernel's state.
*/
public interface KernelStateHandler {
/**
* Informs the handler that the kernel is now in the given state.
*
* @param state The new state.
*/
void enterKernelState(Kernel.State state);
}

View File

@@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application;
import java.util.ResourceBundle;
import org.opentcs.guing.common.util.I18nPlantOverview;
/**
* Progress status for the process of loading a model.
*/
public enum ModelRestorationProgressStatus
implements
ProgressStatus {
/**
* Cleanup phase.
*/
CLEANUP(0, "modelRestorationProgressStatus.description.cleanup"),
/**
* Starting to load model.
*/
START_LOADING_MODEL(10, "modelRestorationProgressStatus.description.startLoadingModel"),
/**
* Loading points.
*/
LOADING_POINTS(20, "modelRestorationProgressStatus.description.startLoadingPoints"),
/**
* Loading paths.
*/
LOADING_PATHS(30, "modelRestorationProgressStatus.description.startLoadingPaths"),
/**
* Loading locations.
*/
LOADING_LOCATIONS(40, "modelRestorationProgressStatus.description.startLoadingLocations"),
/**
* Loading vehicles.
*/
LOADING_VEHICLES(50, "modelRestorationProgressStatus.description.startLoadingVehicles"),
/**
* Loading blocks.
*/
LOADING_BLOCKS(60, "modelRestorationProgressStatus.description.startLoadingBlocks"),
/**
* Setting up model view.
*/
SET_UP_MODEL_VIEW(70, "modelRestorationProgressStatus.description.setUpModelView"),
/**
* Setting up directory tree.
*/
SET_UP_DIRECTORY_TREE(80, "modelRestorationProgressStatus.description.setUpDirectoryTree"),
/**
* Setting up working area.
*/
SET_UP_WORKING_AREA(90, "modelRestorationProgressStatus.description.setUpWorkingArea");
private final int percentage;
private final String description;
ModelRestorationProgressStatus(int percentage, String description) {
this.percentage = percentage;
this.description = ResourceBundle.getBundle(I18nPlantOverview.MISC_PATH).getString(description);
}
@Override
public int getPercentage() {
return percentage;
}
@Override
public String getStatusDescription() {
return description;
}
}

View File

@@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application;
import java.util.Objects;
import org.opentcs.access.Kernel;
/**
* Defines the plant overview's potential modes of operation.
*/
public enum OperationMode {
/**
* For cases in which the mode of operation has not been defined, yet.
*/
UNDEFINED,
/**
* Used when modelling a driving course.
*/
MODELLING,
/**
* Used when operating a plant/system.
*/
OPERATING;
/**
* Returns the equivalent operation mode to the given kernel state.
*
* @param state The kernel state.
* @return The equivalent operation mode to the given kernel state.
*/
public static OperationMode equivalent(Kernel.State state) {
if (Objects.equals(state, Kernel.State.MODELLING)) {
return MODELLING;
}
else if (Objects.equals(state, Kernel.State.OPERATING)) {
return OPERATING;
}
else {
return UNDEFINED;
}
}
public static Kernel.State equivalent(OperationMode mode) {
if (Objects.equals(mode, MODELLING)) {
return Kernel.State.MODELLING;
}
else if (Objects.equals(mode, OPERATING)) {
return Kernel.State.OPERATING;
}
else {
return null;
}
}
}

View File

@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application;
import org.opentcs.components.plantoverview.PluggablePanelFactory;
/**
*/
public interface PluginPanelManager {
/**
* Shows or hides the specific {@code PanelFactory}.
*
* @param factory The factory resp. panel that shall be shown / hidden.
* @param visible True to set it visible, false otherwise.
*/
void showPluginPanel(PluggablePanelFactory factory, boolean visible);
}

View File

@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application;
/**
* Makes progress information available in some way.
*/
public interface ProgressIndicator {
/**
* Initializes the progress indicator, possibly resetting the percentage and
* message.
*/
void initialize();
/**
* Sets/publishes the current progress status.
*
* @param progressStatus The progress status.
*/
void setProgress(ProgressStatus progressStatus);
/**
* Terminates the progress indicator, indicating that no further progress is
* going to be published.
* The progress indicator may be reused after a call to {@code initialize()}.
*/
void terminate();
}

View File

@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application;
/**
* A state of progress, to be used with {@link ProgressIndicator}.
*/
public interface ProgressStatus {
/**
* Returns a percentage value.
*
* @return A percentage value, greater than or equal to 0 and less than or equal to 100.
*/
int getPercentage();
/**
* Returns a (possibly localized) description of the current status to be displayed.
*
* @return A description of the current status to be displayed.
*/
String getStatusDescription();
}

View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
<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/plantoverview/system.properties" key="splashFrame.title.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="ff" green="ff" red="ff" type="rgb"/>
</Property>
<Property name="iconImages" type="java.util.List" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="Icons.getOpenTCSIcons()" type="code"/>
</Property>
<Property name="undecorated" type="boolean" value="true"/>
</Properties>
<SyntheticProperties>
<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,0,-70,0,0,1,60"/>
<SyntheticProperty name="formSizePolicy" type="int" value="0"/>
<SyntheticProperty name="generateSize" type="boolean" value="true"/>
<SyntheticProperty name="generateCenter" type="boolean" value="true"/>
</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_formBundle" type="java.lang.String" value="i18n/org/opentcs/plantoverview/system"/>
<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.DesignGridBagLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="panel">
<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="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.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="labelImage">
<Properties>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/opentcs/guing/res/symbols/openTCS/splash.320x152.gif"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="23" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="labelMessage">
<Properties>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="ff" green="ff" red="ff" type="rgb"/>
</Property>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Arial" size="12" style="1"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="i18n/org/opentcs/plantoverview/system.properties" key="splashFrame.label_message.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="4" insetsBottom="0" insetsRight="4" anchor="15" weightX="0.5" weightY="0.5"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JProgressBar" name="progressBar">
<Properties>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="ff" green="ff" red="ff" type="rgb"/>
</Property>
<Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="ff" green="99" red="99" 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="0" gridY="2" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="0.0" weightY="0.5"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@@ -0,0 +1,178 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application;
import static java.util.Objects.requireNonNull;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import org.opentcs.util.gui.Icons;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A frame for displaying the progress of longer-running processes.
*/
public class SplashFrame
extends
JFrame
implements
ProgressIndicator {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(SplashFrame.class);
/**
* Creates new form SplashFrame
*/
@SuppressWarnings("this-escape")
public SplashFrame() {
initComponents();
}
@Override
public void initialize() {
// Ensure this method is called on the event dispatcher thread.
if (!SwingUtilities.isEventDispatchThread()) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
initialize();
}
});
}
catch (InterruptedException | InvocationTargetException exc) {
LOG.warn("Unexpected exception", exc);
}
return;
}
setVisible(true);
setProgress(0, "");
}
@Override
public void setProgress(ProgressStatus progressStatus) {
requireNonNull(progressStatus, "progressStatus");
setProgress(progressStatus.getPercentage(), progressStatus.getStatusDescription());
}
@Override
public void terminate() {
// Ensure this method is called on the event dispatcher thread.
if (!SwingUtilities.isEventDispatchThread()) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
terminate();
}
});
}
catch (InterruptedException | InvocationTargetException exc) {
LOG.warn("Unexpected exception", exc);
}
return;
}
dispose();
}
private void setProgress(final int percent, final String message) {
// Ensure this method is called on the event dispatcher thread.
if (!SwingUtilities.isEventDispatchThread()) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
setProgress(percent, message);
}
});
}
catch (InterruptedException | InvocationTargetException exc) {
LOG.warn("Unexpected exception", exc);
}
return;
}
labelMessage.setText(message);
progressBar.setValue(percent);
update(getGraphics());
toFront();
}
// 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;
panel = new javax.swing.JPanel();
labelImage = new javax.swing.JLabel();
labelMessage = new javax.swing.JLabel();
progressBar = new javax.swing.JProgressBar();
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("i18n/org/opentcs/plantoverview/system"); // NOI18N
setTitle(bundle.getString("splashFrame.title.text")); // NOI18N
setBackground(new java.awt.Color(255, 255, 255));
setIconImages(Icons.getOpenTCSIcons());
setUndecorated(true);
getContentPane().setLayout(new java.awt.GridBagLayout());
panel.setBackground(new java.awt.Color(255, 255, 255));
panel.setLayout(new java.awt.GridBagLayout());
labelImage.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/opentcs/guing/res/symbols/openTCS/splash.320x152.gif"))); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
panel.add(labelImage, gridBagConstraints);
labelMessage.setBackground(new java.awt.Color(255, 255, 255));
labelMessage.setFont(new java.awt.Font("Arial", 1, 12)); // NOI18N
labelMessage.setText(bundle.getString("splashFrame.label_message.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
gridBagConstraints.weightx = 0.5;
gridBagConstraints.weighty = 0.5;
gridBagConstraints.insets = new java.awt.Insets(0, 4, 0, 4);
panel.add(labelMessage, gridBagConstraints);
progressBar.setBackground(new java.awt.Color(255, 255, 255));
progressBar.setForeground(new java.awt.Color(153, 153, 255));
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
gridBagConstraints.weighty = 0.5;
panel.add(progressBar, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
getContentPane().add(panel, gridBagConstraints);
setSize(new java.awt.Dimension(316, 186));
setLocationRelativeTo(null);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel labelImage;
private javax.swing.JLabel labelMessage;
private javax.swing.JPanel panel;
private javax.swing.JProgressBar progressBar;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
}

View File

@@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application;
import java.util.ResourceBundle;
import org.opentcs.guing.common.util.I18nPlantOverview;
/**
* Progress status for the process of starting the application.
*/
public enum StartupProgressStatus
implements
ProgressStatus {
/**
* Starting the plant overview.
*/
START_PLANT_OVERVIEW(0, "startupProgressStatus.description.startPlantOverview"),
/**
* Showing the plant overview.
*/
SHOW_PLANT_OVERVIEW(5, "startupProgressStatus.description.showPlantOverview"),
/**
* Application initialized.
*/
INITIALIZED(10, "startupProgressStatus.description.initialized"),
/**
* Initializing model.
*/
INITIALIZE_MODEL(15, "startupProgressStatus.description.initializeModel");
private final int percentage;
private final String description;
StartupProgressStatus(int percentage, String description) {
this.percentage = percentage;
this.description = ResourceBundle.getBundle(I18nPlantOverview.MISC_PATH).getString(description);
}
@Override
public int getPercentage() {
return percentage;
}
@Override
public String getStatusDescription() {
return description;
}
}

View File

@@ -0,0 +1,122 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application;
import static java.util.Objects.requireNonNull;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.logging.Level;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A panel at the bottom of the view, showing the mouse position and status.
*/
public class StatusPanel
extends
JPanel {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(StatusPanel.class);
/**
* A text field for status messages.
*/
private final JTextField textFieldStatus = new JTextField();
/**
* A text field for cursor positions/coordinates.
*/
private final JTextField textFieldPosition = new JTextField();
/**
* Creates a new instance.
*/
@SuppressWarnings("this-escape")
public StatusPanel() {
initComponents();
}
private void initComponents() {
textFieldStatus.setText(null);
textFieldPosition.setText(null);
removeAll();
setLayout(new GridBagLayout());
textFieldPosition.setEditable(false);
GridBagConstraints gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = GridBagConstraints.BOTH;
add(textFieldPosition, gridBagConstraints);
textFieldStatus.setEditable(false);
gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
gridBagConstraints.weightx = 0.8;
add(textFieldStatus, gridBagConstraints);
}
/**
* Clears the status textfield, removing any logged messages.
*/
public void clear() {
textFieldStatus.setForeground(Color.black);
textFieldStatus.setText("");
}
/**
* Text display in the status bar (at the bottom).
*
* @param level Log-Level, determines the text color.
* @param text Text to display.
*/
public void setLogMessage(Level level, String text) {
if (level == Level.SEVERE) {
showOptionPane(text);
textFieldStatus.setForeground(Color.magenta);
LOG.error(text);
}
else if (level == Level.WARNING) {
showOptionPane(text);
textFieldStatus.setForeground(Color.red);
LOG.warn(text);
}
else if (level == Level.INFO) {
textFieldStatus.setForeground(Color.blue);
LOG.info(text);
}
else {
textFieldStatus.setForeground(Color.black);
LOG.info(text);
}
textFieldStatus.setText(text);
}
/**
* Sets the given text to the position text field.
*
* @param text The text to set.
*/
public void setPositionText(String text) {
requireNonNull(text, "text");
// Add a space in front of the position text to avoid that a part of the lefthand side gets cut
// off with some graphical environments (observed on Windows 10).
textFieldPosition.setText(" " + text);
revalidate();
}
private void showOptionPane(String text) {
JOptionPane.showMessageDialog(this.getParent(), text, "", JOptionPane.ERROR_MESSAGE);
}
}

View File

@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import java.util.List;
import java.util.Map;
import org.opentcs.guing.common.components.drawing.DrawingViewScrollPane;
/**
* Manages the mapping of dockables to drawing views, transport order views and
* order sequence views.
*/
public interface ViewManager {
Map<DefaultSingleCDockable, DrawingViewScrollPane> getDrawingViewMap();
/**
* Returns the title texts of all drawing views.
*
* @return List of strings containing the names.
*/
List<String> getDrawingViewNames();
/**
* Forgets the given dockable.
*
* @param dockable The dockable.
*/
void removeDockable(DefaultSingleCDockable dockable);
}

View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application.action;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Objects;
import org.jhotdraw.draw.DrawingEditor;
import org.jhotdraw.draw.tool.Tool;
/**
* A listener if a tool was (de)selected.
*/
public final class ToolButtonListener
implements
ItemListener {
private final Tool tool;
private final DrawingEditor editor;
/**
* Creates a new instance.
*
* @param tool The tool
* @param editor The drawing editor
*/
public ToolButtonListener(Tool tool, DrawingEditor editor) {
this.tool = Objects.requireNonNull(tool, "tool is null");
this.editor = Objects.requireNonNull(editor, "editor is null");
}
@Override
public void itemStateChanged(ItemEvent evt) {
if (evt.getStateChange() == ItemEvent.SELECTED) {
editor.setTool(tool);
}
}
}

View File

@@ -0,0 +1,127 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application.action.file;
import static java.util.Objects.requireNonNull;
import static org.opentcs.guing.common.util.I18nPlantOverview.MENU_PATH;
import jakarta.inject.Inject;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.util.Objects;
import javax.swing.AbstractAction;
import javax.swing.JOptionPane;
import org.opentcs.customizations.plantoverview.ApplicationFrame;
import org.opentcs.data.ObjectPropConstants;
import org.opentcs.guing.common.persistence.ModelManager;
import org.opentcs.guing.common.util.I18nPlantOverview;
import org.opentcs.thirdparty.guing.common.jhotdraw.util.ResourceBundleUtil;
/**
* Shows a message window with some of the currently loaded model's properties.
*/
public class ModelPropertiesAction
extends
AbstractAction {
/**
* This action's ID.
*/
public static final String ID = "file.modelProperties";
private static final ResourceBundleUtil BUNDLE = ResourceBundleUtil.getBundle(MENU_PATH);
/**
* The parent component for dialogs shown by this action.
*/
private final Component dialogParent;
/**
* Provides the current system model.
*/
private final ModelManager modelManager;
@Inject
@SuppressWarnings("this-escape")
public ModelPropertiesAction(
@ApplicationFrame
Component dialogParent,
ModelManager modelManager
) {
this.dialogParent = requireNonNull(dialogParent, "dialogParent");
this.modelManager = requireNonNull(modelManager, "modelManager");
putValue(NAME, BUNDLE.getString("modelPropertiesAction.name"));
putValue(SHORT_DESCRIPTION, BUNDLE.getString("modelPropertiesAction.shortDescription"));
}
@Override
public void actionPerformed(ActionEvent e) {
ResourceBundleUtil bundle
= ResourceBundleUtil.getBundle(I18nPlantOverview.MODELPROPERTIES_PATH);
JOptionPane.showMessageDialog(
dialogParent,
"<html><p><b>" + modelManager.getModel().getName() + "</b><br>"
+ bundle.getString("modelPropertiesAction.optionPane_properties.message.numberOfPoints")
+ numberOfPoints()
+ "<br>"
+ bundle.getString("modelPropertiesAction.optionPane_properties.message.numberOfPaths")
+ numberOfPaths()
+ "<br>"
+ bundle.getString(
"modelPropertiesAction.optionPane_properties.message.numberOfLocations"
)
+ numberOfLocations()
+ "<br>"
+ bundle.getString(
"modelPropertiesAction.optionPane_properties.message.numberOfLocationTypes"
)
+ numberOfLocationTypes()
+ "<br>"
+ bundle.getString("modelPropertiesAction.optionPane_properties.message.numberOfBlocks")
+ numberOfBlocks()
+ "<br>"
+ bundle.getString(
"modelPropertiesAction.optionPane_properties.message.numberOfVehicles"
)
+ numberOfVehicles()
+ "<br>"
+ "<br>"
+ bundle.getString("modelPropertiesAction.optionPane_properties.message.lastModified")
+ lastModified()
+ "</p></html>"
);
}
private String lastModified() {
return modelManager.getModel().getPropertyMiscellaneous().getItems().stream()
.filter(kvp -> Objects.equals(kvp.getKey(), ObjectPropConstants.MODEL_FILE_LAST_MODIFIED))
.findAny()
.map(kvp -> kvp.getValue())
.orElse("?");
}
private int numberOfPoints() {
return modelManager.getModel().getPointModels().size();
}
private int numberOfPaths() {
return modelManager.getModel().getPathModels().size();
}
private int numberOfLocations() {
return modelManager.getModel().getLocationModels().size();
}
private int numberOfLocationTypes() {
return modelManager.getModel().getLocationTypeModels().size();
}
private int numberOfBlocks() {
return modelManager.getModel().getBlockModels().size();
}
private int numberOfVehicles() {
return modelManager.getModel().getVehicleModels().size();
}
}

View File

@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application.action.file;
import static org.opentcs.guing.common.util.I18nPlantOverview.MENU_PATH;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.KeyStroke;
import org.opentcs.guing.common.application.GuiManager;
import org.opentcs.guing.common.util.ImageDirectory;
import org.opentcs.thirdparty.guing.common.jhotdraw.util.ResourceBundleUtil;
/**
*/
public class SaveModelAction
extends
AbstractAction {
/**
* This action's ID.
*/
public static final String ID = "file.saveModel";
private static final ResourceBundleUtil BUNDLE = ResourceBundleUtil.getBundle(MENU_PATH);
private final GuiManager view;
/**
* Creates a new instance.
*
* @param view The gui manager
*/
@SuppressWarnings("this-escape")
public SaveModelAction(GuiManager view) {
this.view = view;
putValue(NAME, BUNDLE.getString("saveModelAction.name"));
putValue(SHORT_DESCRIPTION, BUNDLE.getString("saveModelAction.shortDescription"));
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("ctrl S"));
putValue(MNEMONIC_KEY, Integer.valueOf('S'));
ImageIcon icon = ImageDirectory.getImageIcon("/menu/document-save.png");
putValue(SMALL_ICON, icon);
putValue(LARGE_ICON_KEY, icon);
}
@Override
public void actionPerformed(ActionEvent evt) {
view.saveModel();
}
}

View File

@@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application.action.file;
import static org.opentcs.guing.common.util.I18nPlantOverview.MENU_PATH;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.KeyStroke;
import org.opentcs.guing.common.application.GuiManager;
import org.opentcs.guing.common.util.ImageDirectory;
import org.opentcs.thirdparty.guing.common.jhotdraw.util.ResourceBundleUtil;
/**
*/
public class SaveModelAsAction
extends
AbstractAction {
/**
* This action's ID.
*/
public static final String ID = "file.saveModelAs";
private static final ResourceBundleUtil BUNDLE = ResourceBundleUtil.getBundle(MENU_PATH);
/**
* The manager this instance is working with.
*/
private final GuiManager guiManager;
/**
* Creates a new instance.
*
* @param manager The gui manager
*/
@SuppressWarnings("this-escape")
public SaveModelAsAction(final GuiManager manager) {
this.guiManager = manager;
putValue(NAME, BUNDLE.getString("saveModelAsAction.name"));
putValue(SHORT_DESCRIPTION, BUNDLE.getString("saveModelAsAction.shortDescription"));
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("shift ctrl S"));
putValue(MNEMONIC_KEY, Integer.valueOf('A'));
ImageIcon icon = ImageDirectory.getImageIcon("/menu/document-save-as.png");
putValue(SMALL_ICON, icon);
putValue(LARGE_ICON_KEY, icon);
}
@Override
public void actionPerformed(ActionEvent evt) {
guiManager.saveModelAs();
}
}

View File

@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application.action.view;
import static java.util.Objects.requireNonNull;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JCheckBoxMenuItem;
import org.opentcs.components.plantoverview.PluggablePanelFactory;
import org.opentcs.guing.common.application.PluginPanelManager;
/**
* An action to add a plugin panel.
*/
public class AddPluginPanelAction
extends
AbstractAction {
/**
* This action's ID.
*/
public static final String ID = "view.addPluginPanel";
private final PluggablePanelFactory factory;
private final PluginPanelManager pluginPanelManager;
/**
* Creates a new instance.
*
* @param pluginPanelManager The openTCS view
* @param factory The pluggable panel factory
*/
public AddPluginPanelAction(
PluginPanelManager pluginPanelManager,
PluggablePanelFactory factory
) {
this.pluginPanelManager = requireNonNull(pluginPanelManager, "pluginPanelManager");
this.factory = requireNonNull(factory, "factory");
}
@Override
public void actionPerformed(ActionEvent e) {
JCheckBoxMenuItem item = (JCheckBoxMenuItem) e.getSource();
pluginPanelManager.showPluginPanel(factory, item.isSelected());
}
}

View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application.menus.menubar;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Objects;
import javax.swing.JCheckBoxMenuItem;
import org.opentcs.guing.common.components.dockable.AbstractDockingManager;
/**
* Handles changes of a plugin panel's properties.
*/
class PluginPanelPropertyHandler
implements
PropertyChangeListener {
/**
* The menu item corresponding to the plugin panel.
*/
private final JCheckBoxMenuItem utilMenuItem;
/**
* Creates a new instance.
*
* @param utilMenuItem The menu item corresponding to the plugin panel.
*/
PluginPanelPropertyHandler(JCheckBoxMenuItem utilMenuItem) {
this.utilMenuItem = Objects.requireNonNull(utilMenuItem, "utilMenuItem is null");
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(AbstractDockingManager.DOCKABLE_CLOSED)) {
utilMenuItem.setSelected(false);
}
}
}

View File

@@ -0,0 +1,101 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application.menus.menubar;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import org.opentcs.access.Kernel;
import org.opentcs.components.plantoverview.PluggablePanelFactory;
import org.opentcs.guing.common.application.OperationMode;
import org.opentcs.guing.common.application.PluginPanelManager;
import org.opentcs.guing.common.application.action.view.AddPluginPanelAction;
import org.opentcs.guing.common.components.dockable.DockingManager;
import org.opentcs.guing.common.util.I18nPlantOverview;
import org.opentcs.guing.common.util.PanelRegistry;
import org.opentcs.thirdparty.guing.common.jhotdraw.util.ResourceBundleUtil;
/**
*/
public class ViewPluginPanelsMenu
extends
JMenu {
private static final ResourceBundleUtil BUNDLE
= ResourceBundleUtil.getBundle(I18nPlantOverview.MENU_PATH);
/**
* The plugin panel manager.
*/
private final PluginPanelManager pluginPanelManager;
/**
* Provides the registered plugin panel factories.
*/
private final PanelRegistry panelRegistry;
/**
* Manages docking frames.
*/
private final DockingManager dockingManager;
@Inject
public ViewPluginPanelsMenu(
PluginPanelManager pluginPanelManager,
PanelRegistry panelRegistry,
DockingManager dockingManager
) {
super(BUNDLE.getString("viewPluginPanelsMenu.text"));
this.pluginPanelManager = requireNonNull(pluginPanelManager, "pluginPanelManager");
this.panelRegistry = requireNonNull(panelRegistry, "panelRegistry");
this.dockingManager = requireNonNull(dockingManager, "dockingManager");
}
public void setOperationMode(OperationMode mode) {
requireNonNull(mode, "mode");
evaluatePluginPanels(mode);
}
/**
* Removes/adds plugin panels depending on the <code>OperationMode</code>.
*
* @param operationMode The operation mode.
*/
private void evaluatePluginPanels(OperationMode operationMode) {
Kernel.State kernelState = OperationMode.equivalent(operationMode);
if (kernelState == null) {
return;
}
removeAll();
SortedSet<PluggablePanelFactory> factories = new TreeSet<>((factory1, factory2) -> {
return factory1.getPanelDescription().compareTo(factory2.getPanelDescription());
});
factories.addAll(panelRegistry.getFactories());
for (final PluggablePanelFactory factory : factories) {
if (factory.providesPanel(kernelState)) {
String title = factory.getPanelDescription();
final JCheckBoxMenuItem utilMenuItem = new JCheckBoxMenuItem();
utilMenuItem.setAction(new AddPluginPanelAction(pluginPanelManager, factory));
utilMenuItem.setText(title);
dockingManager.addPropertyChangeListener(new PluginPanelPropertyHandler(utilMenuItem));
add(utilMenuItem);
}
}
// If the menu is empty, add a single disabled menu item to it that explains
// to the user that no plugin panels are available.
if (getMenuComponentCount() == 0) {
JMenuItem dummyItem
= new JMenuItem(BUNDLE.getString("viewPluginPanelsMenu.menuItem_none.text"));
dummyItem.setEnabled(false);
add(dummyItem);
}
}
}

View File

@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.application.toolbar;
import java.awt.event.MouseEvent;
import org.jhotdraw.draw.tool.AbstractTool;
/**
* The tool to drag the drawing.
*/
public class DragTool
extends
AbstractTool {
public DragTool() {
super();
}
@Override
public void mouseDragged(MouseEvent e) {
// Do nada.
}
}

View File

@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components;
/**
*/
public interface EditableComponent
extends
org.jhotdraw.gui.EditableComponent {
/**
* Delete the components that are currently selected in the tree and save
* them to allow restoring by a Paste operation.
*/
void cutSelectedItems();
/**
* Save the components that are currently selected in the tree
* to allow creating a clone by a Paste operation.
*/
void copySelectedItems();
/**
*
*/
void pasteBufferedItems();
}

View File

@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dialogs;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.JButton;
import javax.swing.KeyStroke;
/**
* Cancel Button which closes a dialog by pressing ESC.
*/
public class CancelButton
extends
JButton {
/**
* Creates a new instance.
*/
public CancelButton() {
this(null);
}
/**
* Creates a new instance.
*
* @param text Label of this button.
*/
@SuppressWarnings("this-escape")
public CancelButton(String text) {
super(text);
ActionListener al = new ActionListener() {
@Override
public void actionPerformed(ActionEvent event) {
String cmd = event.getActionCommand();
if (cmd.equals("PressedESCAPE")) {
doClick();
}
}
};
registerKeyboardAction(
al, "PressedESCAPE",
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
JButton.WHEN_IN_FOCUSED_WINDOW
);
}
}

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JDialogFormInfo">
<SyntheticProperties>
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
<SyntheticProperty name="generateCenter" type="boolean" value="false"/>
</SyntheticProperties>
<Events>
<EventHandler event="windowClosing" listener="java.awt.event.WindowListener" parameters="java.awt.event.WindowEvent" handler="closeDialog"/>
</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_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="panelButton">
<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.DesignFlowLayout"/>
<SubComponents>
<Component class="javax.swing.JButton" name="buttonClose">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font bold="true" component="buttonClose" 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/plantoverview/system.properties" key="closableDialog.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="buttonCloseActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new CancelButton()"/>
</AuxValues>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@@ -0,0 +1,100 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dialogs;
import java.awt.Component;
import java.awt.Insets;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.border.EmptyBorder;
/**
* A dialog that has a close button.
*/
public class ClosableDialog
extends
JDialog {
/**
* Creates new instance.
*
* @param parent The dialog's parent.
* @param modal Whether the dialog is modal or not.
* @param content The dialog's actual content.
* @param title The dialog's title.
*/
@SuppressWarnings("this-escape")
public ClosableDialog(Component parent, boolean modal, JComponent content, String title) {
super(JOptionPane.getFrameForComponent(parent), title, modal);
initComponents();
getContentPane().add(content, java.awt.BorderLayout.CENTER);
content.setBorder(new EmptyBorder(new Insets(4, 4, 4, 4)));
getRootPane().setDefaultButton(buttonClose);
pack();
}
/**
* Closes the dialog.
*/
private void doClose() {
setVisible(false);
dispose();
}
// 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() {
panelButton = new javax.swing.JPanel();
buttonClose = new CancelButton();
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent evt) {
closeDialog(evt);
}
});
buttonClose.setFont(buttonClose.getFont().deriveFont(buttonClose.getFont().getStyle() | java.awt.Font.BOLD));
java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("i18n/org/opentcs/plantoverview/system"); // NOI18N
buttonClose.setText(bundle.getString("closableDialog.button_close.text")); // NOI18N
buttonClose.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
buttonCloseActionPerformed(evt);
}
});
panelButton.add(buttonClose);
getContentPane().add(panelButton, java.awt.BorderLayout.SOUTH);
pack();
}// </editor-fold>//GEN-END:initComponents
// CHECKSTYLE:ON
// FORMATTER:ON
private void buttonCloseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonCloseActionPerformed
doClose();
}//GEN-LAST:event_buttonCloseActionPerformed
/**
* Closes the dialog
*/
private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog
doClose();
}//GEN-LAST:event_closeDialog
// FORMATTER:OFF
// CHECKSTYLE:OFF
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton buttonClose;
private javax.swing.JPanel panelButton;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
}

View File

@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dialogs;
/**
* Defines a dialog that allows for easy editing of a property.
* The actual editing of the properties is implemented in a {@link DetailsDialogContent}.
*/
public interface DetailsDialog {
/**
* Returns the {@link DetailsDialogContent} that is used to edit the property.
*
* @return
*/
DetailsDialogContent getDialogContent();
/**
* Activates the dialog.
*/
void activate();
}

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dialogs;
import org.opentcs.guing.base.components.properties.type.Property;
/**
* Interface for components to edit properties.
* Classes that implement this interface are generally embedded in a dialog.
* The dialog then calls these methods.
*/
public interface DetailsDialogContent {
/**
* Writes the values of the dialog back to the attribute object.
* This should happen when the user clicked "OK".
*/
void updateValues();
/**
* Returns the title of the dialog.
*
* @return The title.
*/
String getTitle();
/**
* Sets the property.
*
* @param property The property.
*/
void setProperty(Property property);
/**
* Returns the property.
*
* @return The property.
*/
Property getProperty();
}

View File

@@ -0,0 +1,131 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dialogs;
import javax.swing.JComponent;
import javax.swing.JPanel;
/**
* Base implementation for a dialog and tab content.
*/
public abstract class DialogContent
extends
JPanel {
/**
* Title of the component in a dialog.
*/
protected String fDialogTitle;
/**
* Title of the component in a tab.
*/
protected String fTabTitle;
/**
* Indicates that the update of the value has failed.
*/
protected boolean updateFailed;
/**
* Whether or not this dialog is modal.
*/
protected boolean fModal;
/**
* Parent dialog where this content is added to.
*/
protected StandardContentDialog fDialog;
/**
* Creates a new instance of AbstractDialogContent.
*/
public DialogContent() {
setModal(true);
}
/**
* Returns the component.
*
* @return The component of this dialog.
*/
public JComponent getComponent() {
return this;
}
/**
* Sets the dialog to be modal.
*
* @param modal true if the dialog should be modal.
*/
public final void setModal(boolean modal) {
fModal = modal;
}
/**
* Returns whether or not the component is modal.
*
* @return Whether or not the component is modal.
*/
public boolean getModal() {
return fModal;
}
/**
* Returns the title for a tab.
*
* @return The title for a tab.
*/
public String getTabTitle() {
return fTabTitle;
}
/**
* Returns the title for a dialog.
*
* @return The title for a dialog.
*/
public String getDialogTitle() {
return fDialogTitle;
}
/**
* Set the title for a dialog.
*
* @param title The new title for a dialog.
*/
protected void setDialogTitle(String title) {
fDialogTitle = title;
}
/**
* Set the title for a tab.
*
* @param title The new title for a tab.
*/
protected void setTabTitle(String title) {
fTabTitle = title;
}
/**
* Notifies the registered listeners that the dialog would like to close.
*/
protected void notifyRequestClose() {
fDialog.requestClose();
}
/**
* Returns whether or not the update of the UI elements failed.
*
* @return true if the update failed.
*/
public boolean updateFailed() {
return updateFailed;
}
/**
* Initialises the dialog elements.
*/
public abstract void initFields();
/**
* Updates the values from the dialog elements.
*/
public abstract void update();
}

View File

@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dialogs;
/**
* A Listener Interface, instances can handle validation of the user input.
*/
public interface InputValidationListener {
/**
* Notifies about the validity of an input.
*
* @param success true if input is valid, false otherwise.
*/
void inputValidationSuccessful(boolean success);
}

View File

@@ -0,0 +1,144 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dialogs;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;
/**
* A modified version of FlowLayout that allows containers using this
* Layout to behave in a reasonable manner when placed inside a
* JScrollPane.
*/
public class ModifiedFlowLayout
extends
FlowLayout {
/**
* Creates a new instance.
*/
public ModifiedFlowLayout() {
super();
}
/**
* Creates a new instance.
*
* @param align The alignment value.
*/
public ModifiedFlowLayout(int align) {
super(align);
}
/**
* Creates a new instance.
*
* @param align the alignment value
* @param hgap the horizontal gap between components and between the
* components and the borders of the container
* @param vgap the vertical gap between components and between the components
* and the borders of the container
*/
public ModifiedFlowLayout(int align, int hgap, int vgap) {
super(align, hgap, vgap);
}
@Override
public Dimension minimumLayoutSize(Container target) {
// Size of largest component, so we can resize it in
// either direction with something like a split-pane.
return computeMinSize(target);
}
@Override
public Dimension preferredLayoutSize(Container target) {
return computeSize(target);
}
private Dimension computeSize(Container target) {
synchronized (target.getTreeLock()) {
int hgap = getHgap();
int vgap = getVgap();
int w = target.getWidth();
// Let this behave like a regular FlowLayout (single row)
// if the container hasn't been assigned any size yet
if (w == 0) {
w = Integer.MAX_VALUE;
}
Insets insets = target.getInsets();
if (insets == null) {
insets = new Insets(0, 0, 0, 0);
}
int reqdWidth = 0;
int maxwidth = w - (insets.left + insets.right + hgap * 2);
int n = target.getComponentCount();
int x = 0;
int y = insets.top + vgap; // FlowLayout starts by adding vgap, so do that here too.
int rowHeight = 0;
for (int i = 0; i < n; i++) {
Component c = target.getComponent(i);
if (c.isVisible()) {
Dimension d = c.getPreferredSize();
if ((x == 0) || ((x + d.width) <= maxwidth)) {
// fits in current row.
if (x > 0) {
x += hgap;
}
x += d.width;
rowHeight = Math.max(rowHeight, d.height);
}
else {
// Start of new row
x = d.width;
y += vgap + rowHeight;
rowHeight = d.height;
}
reqdWidth = Math.max(reqdWidth, x);
}
}
y += rowHeight;
y += insets.bottom;
return new Dimension(reqdWidth + insets.left + insets.right, y);
}
}
private Dimension computeMinSize(Container target) {
synchronized (target.getTreeLock()) {
int minx = Integer.MAX_VALUE;
int miny = Integer.MIN_VALUE;
boolean foundOne = false;
int n = target.getComponentCount();
for (int i = 0; i < n; i++) {
Component c = target.getComponent(i);
if (c.isVisible()) {
foundOne = true;
Dimension d = c.getPreferredSize();
minx = Math.min(minx, d.width);
miny = Math.min(miny, d.height);
}
}
if (foundOne) {
return new Dimension(minx, miny);
}
return new Dimension(0, 0);
}
}
}

View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JDialogFormInfo">
<SyntheticProperties>
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
<SyntheticProperty name="generateCenter" type="boolean" value="false"/>
</SyntheticProperties>
<Events>
<EventHandler event="windowClosing" listener="java.awt.event.WindowListener" parameters="java.awt.event.WindowEvent" handler="closeDialog"/>
</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_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-118"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="buttonPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
<EmptyBorder bottom="0" left="0" right="5" top="0"/>
</Border>
</Property>
</Properties>
<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.DesignFlowLayout">
<Property name="horizontalGap" type="int" value="10"/>
</Layout>
<SubComponents>
<Component class="javax.swing.JButton" name="okButton">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font bold="true" component="okButton" 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/plantoverview/system.properties" key="standardContentDialog.button_ok.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="okButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="cancelButton">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font component="cancelButton" 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/plantoverview/system.properties" key="standardContentDialog.button_cancel.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="cancelButtonActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new CancelButton()"/>
</AuxValues>
</Component>
<Component class="javax.swing.JButton" name="applyButton">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font component="applyButton" 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/plantoverview/system.properties" key="standardContentDialog.button_apply.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="applyButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="closeButton">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font component="closeButton" 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/plantoverview/system.properties" key="standardContentDialog.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>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@@ -0,0 +1,305 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dialogs;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.border.EmptyBorder;
/**
* A standard dialog with an OK and a cancel button.
*/
public class StandardContentDialog
extends
javax.swing.JDialog
implements
InputValidationListener {
/**
* A return status code - returned if Cancel button has been pressed.
*/
public static final int RET_CANCEL = 0;
/**
* A return status code - returned if OK button has been pressed.
*/
public static final int RET_OK = 1;
/**
* Button configuration for an OK and a cancel button.
*/
public static final int OK_CANCEL = 10;
/**
* Button configuration for an OK, cancel and apply button.
*/
public static final int OK_CANCEL_APPLY = 11;
/**
* Button configuration for a close button.
*/
public static final int CLOSE = 12;
/**
* Button configuration user-defined.
*/
public static final int USER_DEFINED = 13;
/**
* Content for this dialog.
*/
protected DialogContent fContent;
/**
* The return status.
*/
private int returnStatus = RET_CANCEL;
/**
* Creates new instance.
*/
public StandardContentDialog(Component parent, DialogContent content) {
this(parent, content, true, OK_CANCEL);
}
/**
* Creates new form StandardDialog.
*
* @param parent The parent component on which this dialog is centered.
* @param content The content.
* @param modal whether or not this dialog is modal.
* @param options Which user interface options to use.
*/
@SuppressWarnings("this-escape")
public StandardContentDialog(
Component parent,
DialogContent content,
boolean modal,
int options
) {
super(JOptionPane.getFrameForComponent(parent), modal);
initComponents();
initButtons(options);
JComponent component = content.getComponent();
if (component.getBorder() == null) {
component.setBorder(new EmptyBorder(4, 4, 4, 4));
}
getContentPane().add(component, BorderLayout.CENTER);
setTitle(content.getDialogTitle());
content.initFields();
pack();
setLocationRelativeTo(parent);
fContent = content;
getRootPane().setDefaultButton(okButton);
}
@Override
public void inputValidationSuccessful(boolean success) {
this.okButton.setEnabled(success);
}
/**
* Returns the returns status code.
*
* @return the return status of this dialog - one of RET_OK or RET_CANCEL
*/
public int getReturnStatus() {
return returnStatus;
}
/**
* Adds a user-defined button.
*
* @param text The text for the button.
* @param returnStatus The return value when the button is pressed.
*/
public void addUserDefinedButton(String text, final int returnStatus) {
JButton button = new JButton(text);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
doClose(returnStatus);
}
});
buttonPanel.add(button);
}
/**
* Event of the dialog content that the dialog can be closed.
*/
public void requestClose() {
doClose(RET_CANCEL);
}
protected final void initButtons(int options) {
switch (options) {
case OK_CANCEL:
applyButton.setVisible(false);
closeButton.setVisible(false);
break;
case OK_CANCEL_APPLY:
closeButton.setVisible(false);
break;
case CLOSE:
okButton.setVisible(false);
cancelButton.setVisible(false);
applyButton.setVisible(false);
break;
case USER_DEFINED:
okButton.setVisible(false);
cancelButton.setVisible(false);
applyButton.setVisible(false);
closeButton.setVisible(false);
break;
default:
}
}
// FORMATTER:OFF
// CHECKSTYLE:OFF
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
buttonPanel = new javax.swing.JPanel();
okButton = new javax.swing.JButton();
cancelButton = new CancelButton();
applyButton = new javax.swing.JButton();
closeButton = new javax.swing.JButton();
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent evt) {
closeDialog(evt);
}
});
buttonPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 5));
buttonPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 10, 5));
okButton.setFont(okButton.getFont().deriveFont(okButton.getFont().getStyle() | java.awt.Font.BOLD));
java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("i18n/org/opentcs/plantoverview/system"); // NOI18N
okButton.setText(bundle.getString("standardContentDialog.button_ok.text")); // NOI18N
okButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
okButtonActionPerformed(evt);
}
});
buttonPanel.add(okButton);
cancelButton.setFont(cancelButton.getFont());
cancelButton.setText(bundle.getString("standardContentDialog.button_cancel.text")); // NOI18N
cancelButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
cancelButtonActionPerformed(evt);
}
});
buttonPanel.add(cancelButton);
applyButton.setFont(applyButton.getFont());
applyButton.setText(bundle.getString("standardContentDialog.button_apply.text")); // NOI18N
applyButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
applyButtonActionPerformed(evt);
}
});
buttonPanel.add(applyButton);
closeButton.setFont(closeButton.getFont());
closeButton.setText(bundle.getString("standardContentDialog.button_close.text")); // NOI18N
closeButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
closeButtonActionPerformed(evt);
}
});
buttonPanel.add(closeButton);
getContentPane().add(buttonPanel, java.awt.BorderLayout.SOUTH);
pack();
}// </editor-fold>//GEN-END:initComponents
// CHECKSTYLE:ON
// FORMATTER:ON
/**
* Button "close" pressed.
*
* @param evt The action event.
*/
private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed
doClose(RET_CANCEL);
}//GEN-LAST:event_closeButtonActionPerformed
/**
* Button "apply" pressed.
*
* @param evt The action event.
*/
private void applyButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_applyButtonActionPerformed
fContent.update();
}//GEN-LAST:event_applyButtonActionPerformed
/**
* Button "Ok" pressed.
*
* @param evt The action event.
*/
private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
fContent.update();
if (!fContent.updateFailed()) {
doClose(RET_OK);
}
}//GEN-LAST:event_okButtonActionPerformed
/**
* Button "cancel" pressed.
*
* @param evt The action event.
*/
private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
doClose(RET_CANCEL);
}//GEN-LAST:event_cancelButtonActionPerformed
/**
* Closes the dialog
*/
private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog
doClose(RET_CANCEL);
}//GEN-LAST:event_closeDialog
/**
* Closes the dialog.
*
* @param retStatus The return status code.
*/
private void doClose(int retStatus) {
returnStatus = retStatus;
setVisible(false);
dispose();
}
// FORMATTER:OFF
// CHECKSTYLE:OFF
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton applyButton;
private javax.swing.JPanel buttonPanel;
private javax.swing.JButton cancelButton;
private javax.swing.JButton closeButton;
private javax.swing.JButton okButton;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
}

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JDialogFormInfo">
<Properties>
<Property name="defaultCloseOperation" type="int" value="2"/>
<Property name="iconImage" type="java.awt.Image" editor="org.netbeans.modules.form.ComponentChooserEditor">
<ComponentRef name="null"/>
</Property>
</Properties>
<SyntheticProperties>
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
<SyntheticProperty name="generateCenter" type="boolean" value="false"/>
</SyntheticProperties>
<Events>
<EventHandler event="windowClosing" listener="java.awt.event.WindowListener" parameters="java.awt.event.WindowEvent" handler="closeDialog"/>
</Events>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,119,0,0,1,72"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="controlPanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="South"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="buttonPanel">
<Properties>
<Property name="opaque" type="boolean" value="false"/>
</Properties>
<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.DesignFlowLayout"/>
<SubComponents>
<Component class="javax.swing.JButton" name="okButton">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font bold="true" component="okButton" 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/plantoverview/system.properties" key="standardDetailsDialog.button_ok.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="okButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="cancelButton">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font component="cancelButton" 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/plantoverview/system.properties" key="standardDetailsDialog.button_cancel.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
<Property name="opaque" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cancelButtonActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new CancelButton()"/>
</AuxValues>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@@ -0,0 +1,201 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dialogs;
import java.awt.Component;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import org.opentcs.guing.base.components.properties.type.ModelAttribute;
import org.opentcs.guing.base.components.properties.type.Property;
/**
* A dialog in which a {@link DialogContent} can be added.
* The dialog has an OK and a cancel button.
*/
public class StandardDetailsDialog
extends
javax.swing.JDialog
implements
DetailsDialog {
/**
* A return status code - returned if Cancel button has been pressed
*/
public static final int RET_CANCEL = 0;
/**
* A return status code - returned if OK button has been pressed
*/
public static final int RET_OK = 1;
private int returnStatus = RET_CANCEL;
/**
* The details dialog content to change a property.
*/
private final DetailsDialogContent fContent;
private final Component fParentComponent;
/**
* Creates new form JDialog
*
* @param parent The parent component.
* @param content Details dialog content.
* @param modal Whether or not the dialog is modal.
*/
@SuppressWarnings("this-escape")
public StandardDetailsDialog(JPanel parent, boolean modal, DetailsDialogContent content) {
super(JOptionPane.getFrameForComponent(parent), modal);
fContent = content;
fParentComponent = parent;
initialize();
}
/**
* Creates a new dialog.
*
* @param parent The parent dialog.
* @param modal Whether or not the dialog is modal.
* @param content Details dialog content.
*/
@SuppressWarnings("this-escape")
public StandardDetailsDialog(JDialog parent, boolean modal, DetailsDialogContent content) {
super(parent, modal);
fContent = content;
fParentComponent = parent;
initialize();
}
/*
* Initialises the dialog.
*/
protected final void initialize() {
JComponent component = (JComponent) fContent;
component.setBorder(new EmptyBorder(new java.awt.Insets(5, 5, 5, 5)));
getContentPane().add(component, java.awt.BorderLayout.CENTER);
initComponents();
setTitle(fContent.getTitle());
activate();
}
@Override
public void activate() {
getRootPane().setDefaultButton(okButton);
pack();
setLocationRelativeTo(fParentComponent);
}
public Component getParentComponent() {
return fParentComponent;
}
/**
* Returns the return status.
*
* @return the return status of this dialog - one of RET_OK or RET_CANCEL
*/
public int getReturnStatus() {
return returnStatus;
}
// 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() {
controlPanel = new javax.swing.JPanel();
buttonPanel = new javax.swing.JPanel();
okButton = new javax.swing.JButton();
cancelButton = new CancelButton();
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
setIconImage(null);
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent evt) {
closeDialog(evt);
}
});
controlPanel.setLayout(new java.awt.BorderLayout());
buttonPanel.setOpaque(false);
okButton.setFont(okButton.getFont().deriveFont(okButton.getFont().getStyle() | java.awt.Font.BOLD));
java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("i18n/org/opentcs/plantoverview/system"); // NOI18N
okButton.setText(bundle.getString("standardDetailsDialog.button_ok.text")); // NOI18N
okButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
okButtonActionPerformed(evt);
}
});
buttonPanel.add(okButton);
cancelButton.setFont(cancelButton.getFont());
cancelButton.setText(bundle.getString("standardDetailsDialog.button_cancel.text")); // NOI18N
cancelButton.setOpaque(false);
cancelButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
cancelButtonActionPerformed(evt);
}
});
buttonPanel.add(cancelButton);
controlPanel.add(buttonPanel, java.awt.BorderLayout.SOUTH);
getContentPane().add(controlPanel, java.awt.BorderLayout.SOUTH);
pack();
}// </editor-fold>//GEN-END:initComponents
// CHECKSTYLE:ON
// FORMATTER:ON
private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
doClose(RET_CANCEL);
}//GEN-LAST:event_cancelButtonActionPerformed
private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
fContent.updateValues();
Property property = fContent.getProperty();
if (property != null) {
property.setChangeState(ModelAttribute.ChangeState.DETAIL_CHANGED);
}
doClose(RET_OK);
}//GEN-LAST:event_okButtonActionPerformed
/**
* Closes the dialog.
*/
private void doClose(int retStatus) {
returnStatus = retStatus;
setVisible(false);
}
private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog
doClose(RET_CANCEL);
}//GEN-LAST:event_closeDialog
@Override
public DetailsDialogContent getDialogContent() {
return fContent;
}
// FORMATTER:OFF
// CHECKSTYLE:OFF
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel buttonPanel;
private javax.swing.JButton cancelButton;
private javax.swing.JPanel controlPanel;
private javax.swing.JButton okButton;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
}

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JDialogFormInfo">
<SyntheticProperties>
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
<SyntheticProperty name="generateCenter" type="boolean" value="false"/>
</SyntheticProperties>
<Events>
<EventHandler event="windowClosing" listener="java.awt.event.WindowListener" parameters="java.awt.event.WindowEvent" handler="closeDialog"/>
</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_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,0,-106,0,0,0,-43"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="buttonPanel">
<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.DesignFlowLayout"/>
<SubComponents>
<Component class="javax.swing.JButton" name="okButton">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font bold="true" component="okButton" 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/plantoverview/system.properties" key="standardDialog.button_ok.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
<Property name="opaque" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="okButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="cancelButton">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font component="cancelButton" 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/plantoverview/system.properties" key="standardDialog.button_cancel.text" replaceFormat="java.util.ResourceBundle.getBundle(&quot;{bundleNameSlashes}&quot;).getString(&quot;{key}&quot;)"/>
</Property>
<Property name="opaque" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cancelButtonActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new CancelButton()"/>
</AuxValues>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@@ -0,0 +1,161 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dialogs;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Insets;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.border.EmptyBorder;
import org.opentcs.util.gui.Icons;
/**
* A dialog with an ok and a cancel button.
*/
public class StandardDialog
extends
JDialog {
/**
* A return status code - returned if Cancel button has been pressed
*/
public static final int RET_CANCEL = 0;
/**
* A return status code - returned if OK button has been pressed
*/
public static final int RET_OK = 1;
/**
* The content of this dialog.
*/
protected JComponent fContent;
private int returnStatus = RET_CANCEL;
/**
* Creates a new instance.
*
* @param parent The parent component.
* @param modal Whether or not the dialog is modal.
* @param content The content component.
* @param title The title of the dialog.
*/
@SuppressWarnings("this-escape")
public StandardDialog(Component parent, boolean modal, JComponent content, String title) {
super(JOptionPane.getFrameForComponent(parent), title, modal);
initComponents();
initSize(content);
setTitle(title);
setIconImages(Icons.getOpenTCSIcons());
}
/**
* Initialises the size of the dialog based on the content.
*
* @param content the dialog to base the size on.
*/
protected final void initSize(JComponent content) {
fContent = content;
getContentPane().add(content, BorderLayout.CENTER);
content.setBorder(new EmptyBorder(new Insets(3, 3, 3, 3)));
getRootPane().setDefaultButton(okButton);
pack();
}
/**
* Returns the content of this dialog.
*
* @return the content of this dialog.
*/
public JComponent getContent() {
return fContent;
}
/**
* Return the return status of this dialog - one of RET_OK or RET_CANCEL.
*
* @return the return status of this dialog - one of RET_OK or RET_CANCEL.
*/
public int getReturnStatus() {
return returnStatus;
}
// FORMATTER:OFF
// CHECKSTYLE:OFF
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
buttonPanel = new javax.swing.JPanel();
okButton = new javax.swing.JButton();
cancelButton = new CancelButton();
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent evt) {
closeDialog(evt);
}
});
okButton.setFont(okButton.getFont().deriveFont(okButton.getFont().getStyle() | java.awt.Font.BOLD));
java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("i18n/org/opentcs/plantoverview/system"); // NOI18N
okButton.setText(bundle.getString("standardDialog.button_ok.text")); // NOI18N
okButton.setOpaque(false);
okButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
okButtonActionPerformed(evt);
}
});
buttonPanel.add(okButton);
cancelButton.setFont(cancelButton.getFont());
cancelButton.setText(bundle.getString("standardDialog.button_cancel.text")); // NOI18N
cancelButton.setOpaque(false);
cancelButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
cancelButtonActionPerformed(evt);
}
});
buttonPanel.add(cancelButton);
getContentPane().add(buttonPanel, java.awt.BorderLayout.SOUTH);
pack();
}// </editor-fold>//GEN-END:initComponents
// CHECKSTYLE:ON
// FORMATTER:ON
private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
doClose(RET_OK);
}//GEN-LAST:event_okButtonActionPerformed
private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
doClose(RET_CANCEL);
}//GEN-LAST:event_cancelButtonActionPerformed
private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog
doClose(RET_CANCEL);
}//GEN-LAST:event_closeDialog
/**
* Closes the dialog.
*/
private void doClose(int retStatus) {
returnStatus = retStatus;
setVisible(false);
dispose();
}
// FORMATTER:OFF
// CHECKSTYLE:OFF
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel buttonPanel;
private javax.swing.JButton cancelButton;
private javax.swing.JButton okButton;
// End of variables declaration//GEN-END:variables
// CHECKSTYLE:ON
// FORMATTER:ON
}

View File

@@ -0,0 +1,289 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dockable;
import static java.util.Objects.requireNonNull;
import bibliothek.gui.dock.common.CContentArea;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CLocation;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.SingleCDockable;
import bibliothek.gui.dock.common.event.CVetoClosingEvent;
import bibliothek.gui.dock.common.event.CVetoClosingListener;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.swing.JComponent;
/**
* Utility class for working with dockables.
*/
public abstract class AbstractDockingManager
implements
DockingManager {
/**
* PropertyChangeEvent when a floating dockable closes.
*/
public static final String DOCKABLE_CLOSED = "DOCKABLE_CLOSED";
/**
* Map that contains all tab panes. They are stored by their id.
*/
private final Map<String, CStack> tabPanes = new HashMap<>();
/**
* Control for the dockable panels.
*/
private final CControl control;
/**
* The listeners for closing events.
*/
private final List<PropertyChangeListener> listeners = new ArrayList<>();
public AbstractDockingManager(CControl control) {
this.control = requireNonNull(control, "control");
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
/**
* Creates a new dockable.
*
* @param id The unique id for this dockable.
* @param title The title text of this new dockable.
* @param comp The JComponent wrapped by the new dockable.
* @param closeable If the dockable can be closeable or not.
* @return The newly created dockable.
*/
public DefaultSingleCDockable createDockable(
String id,
String title,
JComponent comp,
boolean closeable
) {
Objects.requireNonNull(id, "id is null");
Objects.requireNonNull(title, "title is null");
Objects.requireNonNull(comp, "comp is null");
if (control == null) {
return null;
}
DefaultSingleCDockable dockable = new DefaultSingleCDockable(id, title);
dockable.setCloseable(closeable);
dockable.add(comp);
return dockable;
}
/**
* Creates a new floating dockable.
*
* @param id The unique id for this dockable.
* @param title The title text of this new dockable.
* @param comp The JComponent wrapped by the new dockable.
* @return The newly created dockable.
*/
public DefaultSingleCDockable createFloatingDockable(
String id,
String title,
JComponent comp
) {
if (control == null) {
return null;
}
final DefaultSingleCDockable dockable = new DefaultSingleCDockable(id, title);
dockable.setCloseable(true);
dockable.setFocusComponent(comp);
dockable.add(comp);
dockable.addVetoClosingListener(new CVetoClosingListener() {
@Override
public void closing(CVetoClosingEvent event) {
}
@Override
public void closed(CVetoClosingEvent event) {
fireFloatingDockableClosed(dockable);
}
});
control.addDockable(dockable);
dockable.setExtendedMode(ExtendedMode.EXTERNALIZED);
Rectangle centerRectangle = control.getContentArea().getCenter().getBounds();
dockable.setLocation(
CLocation.external(
(centerRectangle.width - comp.getWidth()) / 2,
(centerRectangle.height - comp.getHeight()) / 2,
comp.getWidth(),
comp.getHeight()
)
);
return dockable;
}
/**
* Adds a dockable as tab to the tab pane identified by the given id.
*
* @param newTab The new dockable that shall be added.
* @param id The ID of the tab pane.
* @param index Index where to insert the dockable in the tab pane.
*/
public void addTabTo(DefaultSingleCDockable newTab, String id, int index) {
Objects.requireNonNull(newTab, "newTab is null.");
Objects.requireNonNull(id, "id is null");
CStack tabPane = tabPanes.get(id);
if (tabPane != null) {
control.addDockable(newTab);
newTab.setWorkingArea(tabPane);
tabPane.getStation().add(newTab.intern(), index);
tabPane.getStation().setFrontDockable(newTab.intern());
}
}
@Override
public void removeDockable(SingleCDockable dockable) {
Objects.requireNonNull(dockable, "dockable is null");
if (control != null) {
control.removeDockable(dockable);
}
}
@Override
public void removeDockable(String id) {
Objects.requireNonNull(id);
SingleCDockable dock = control.getSingleDockable(id);
if (dock != null) {
removeDockable(dock);
}
}
/**
* Returns the CControl.
*
* @return The CControl.
*/
public CControl getCControl() {
return control;
}
/**
* Returns the whole component with all dockables, tab panes etc.
*
* @return The CContentArea of the CControl.
*/
public CContentArea getContentArea() {
if (control != null) {
return control.getContentArea();
}
else {
return null;
}
}
/**
* Returns the tab pane with the given id.
*
* @param id ID of the tab pane.
* @return The tab pane or null if there is no tab pane with this id.
*/
public CStack getTabPane(String id) {
if (control != null) {
return tabPanes.get(id);
}
else {
return null;
}
}
public abstract void reset();
/**
* Wraps all given JComponents into a dockable and deploys them on the CControl.
*/
public abstract void initializeDockables();
protected void addTabPane(String id, CStack tabPane) {
tabPanes.put(id, tabPane);
}
/**
* Hides a dockable (by actually removing it from its station).
*
* @param station The CStackDockStation the dockable belongs to.
* @param dockable The dockable to hide.
*/
public void hideDockable(CStackDockStation station, DefaultSingleCDockable dockable) {
int index = station.indexOf(dockable.intern());
if (index <= -1) {
station.add(dockable.intern(), station.getDockableCount());
index = station.indexOf(dockable.intern());
}
station.remove(index);
}
/**
* Shows a dockable (by actually adding it to its station).
*
* @param station The CStackDockStation the dockable belongs to.
* @param dockable The dockable to show.
* @param index Where to add the dockable.
*/
public void showDockable(
CStackDockStation station,
DefaultSingleCDockable dockable,
int index
) {
if (station.indexOf(dockable.intern()) <= -1) {
station.add(dockable.intern(), index);
}
}
/**
* Sets the visibility status of a dockable with the given id.
*
* @param id The id of the dockable.
* @param visible If it shall be visible or not.
*/
public void setDockableVisibility(String id, boolean visible) {
if (control != null) {
SingleCDockable dockable = control.getSingleDockable(id);
if (dockable != null) {
dockable.setVisible(visible);
}
}
}
/**
* Checks if the given dockable is docked to its CStackDockStation.
*
* @param station The station the dockable should be docked in.
* @param dockable The dockable to check.
* @return True if it is docked, false otherwise.
*/
public boolean isDockableDocked(CStackDockStation station, DefaultSingleCDockable dockable) {
return station.indexOf(dockable.intern()) <= -1;
}
/**
* Fires a <code>PropertyChangeEvent</code> when a floatable dockable is closed
* (eg a plugin panel).
*
* @param dockable The dockable that was closed.
*/
private void fireFloatingDockableClosed(DefaultSingleCDockable dockable) {
for (PropertyChangeListener listener : listeners) {
listener.propertyChange(
new PropertyChangeEvent(this, DOCKABLE_CLOSED, dockable, dockable)
);
}
}
}

View File

@@ -0,0 +1,207 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dockable;
import bibliothek.gui.DockController;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.common.CLocation;
import bibliothek.gui.dock.common.intern.AbstractDockableCStation;
import bibliothek.gui.dock.common.intern.CControlAccess;
import bibliothek.gui.dock.common.mode.CNormalModeArea;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.common.perspective.CStationPerspective;
import bibliothek.gui.dock.facile.mode.Location;
import bibliothek.gui.dock.facile.mode.LocationMode;
import bibliothek.gui.dock.facile.mode.ModeAreaListener;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.support.mode.AffectedSet;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.util.Path;
/**
* A tab dockable copied from the dockingframes examples.
*/
public class CStack
extends
AbstractDockableCStation<CStackDockStation>
implements
CNormalModeArea {
@SuppressWarnings("this-escape")
public CStack(String id) {
CStackDockStation delegate = new CStackDockStation(this);
@SuppressWarnings("deprecation")
CLocation stationLocation = new CLocation() {
@Override
public CLocation getParent() {
return null;
}
@Override
public String findRoot() {
return getUniqueId();
}
@Override
public DockableProperty findProperty(DockableProperty successor) {
return successor;
}
@Override
public ExtendedMode findMode() {
return ExtendedMode.NORMALIZED;
}
@Override
public CLocation aside() {
return this;
}
};
init(delegate, id, stationLocation, delegate);
}
@Override
protected void install(CControlAccess access) {
access.getLocationManager().getNormalMode().add(this);
}
@Override
protected void uninstall(CControlAccess access) {
access.getLocationManager().getNormalMode().remove(getUniqueId());
}
@Override
public CStationPerspective createPerspective() {
throw new IllegalStateException("not implemented");
}
@Override
public boolean isNormalModeChild(Dockable dockable) {
return isChild(dockable);
}
@Override
public DockableProperty getLocation(Dockable child) {
return DockUtilities.getPropertyChain(getStation(), child);
}
@Override
public boolean setLocation(Dockable dockable, DockableProperty location, AffectedSet set) {
set.add(dockable);
if (isChild(dockable)) {
getStation().move(dockable, location);
}
else {
boolean acceptable = DockUtilities.acceptable(getStation(), dockable);
if (!acceptable) {
return false;
}
if (!getStation().drop(dockable, location)) {
getStation().drop(dockable);
}
}
return true;
}
@Override
public void addModeAreaListener(ModeAreaListener listener) {
}
@Override
public boolean autoDefaultArea() {
return true;
}
@Override
public boolean isLocationRoot() {
return true;
}
@Override
public boolean isChild(Dockable dockable) {
return dockable.getDockParent() == getStation();
}
@Override
public void removeModeAreaListener(ModeAreaListener listener) {
}
@Override
public void setController(DockController controller) {
}
@Override
public void setMode(LocationMode mode) {
}
@Override
public CLocation getCLocation(Dockable dockable) {
DockableProperty property = DockUtilities.getPropertyChain(getStation(), dockable);
return getStationLocation().expandProperty(getStation().getController(), property);
}
@Override
public CLocation getCLocation(Dockable dockable, Location location) {
DockableProperty property = location.getLocation();
if (property == null) {
return getStationLocation();
}
return getStationLocation().expandProperty(getStation().getController(), property);
}
@Override
public boolean respectWorkingAreas() {
return true;
}
@Override
public boolean isCloseable() {
return false;
}
@Override
public boolean isExternalizable() {
return false;
}
@Override
public boolean isMinimizable() {
return false;
}
@Override
public boolean isStackable() {
return false;
}
@Override
public boolean isWorkingArea() {
return true;
}
public DockActionSource[] getSources() {
return new DockActionSource[]{getClose()};
}
@Override
public boolean isMaximizable() {
return false;
}
@Override
public Path getTypeId() {
return null;
}
}

View File

@@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dockable;
import bibliothek.gui.dock.StackDockStation;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.common.CStation;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CommonDockable;
import bibliothek.gui.dock.common.intern.station.CommonDockStation;
import bibliothek.gui.dock.common.intern.station.CommonDockStationFactory;
/**
*/
public class CStackDockStation
extends
StackDockStation
implements
CommonDockStation<StackDockStation, CStackDockStation>,
CommonDockable {
private final CStack delegate;
public CStackDockStation(CStack stack) {
this.delegate = stack;
}
@Override
public String getFactoryID() {
return CommonDockStationFactory.FACTORY_ID;
}
@Override
public String getConverterID() {
return super.getFactoryID();
}
@Override
public CDockable getDockable() {
return delegate;
}
@Override
public DockActionSource[] getSources() {
return delegate.getSources();
}
@Override
public CStation<CStackDockStation> getStation() {
return delegate;
}
@Override
public StackDockStation getDockStation() {
return this;
}
@Override
public CStackDockStation asDockStation() {
return this;
}
@Override
public CommonDockable asDockable() {
return this;
}
}

View File

@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dockable;
import static java.util.Objects.requireNonNull;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.event.CVetoClosingEvent;
import bibliothek.gui.dock.common.event.CVetoClosingListener;
import com.google.inject.assistedinject.Assisted;
import jakarta.inject.Inject;
import org.opentcs.guing.common.application.ViewManager;
/**
* Handles closing of a dockable.
*/
public class DockableClosingHandler
implements
CVetoClosingListener {
/**
* The dockable.
*/
private final DefaultSingleCDockable dockable;
/**
* Manages the application's dockables.
*/
private final DockingManager dockingManager;
/**
* Manages the application's views.
*/
private final ViewManager viewManager;
/**
* Creates a new instance.
*
* @param dockable The dockable.
* @param viewManager Manages the application's views.
* @param dockingManager Manages the application's dockables.
*/
@Inject
public DockableClosingHandler(
@Assisted
DefaultSingleCDockable dockable,
ViewManager viewManager,
DockingManager dockingManager
) {
this.dockable = requireNonNull(dockable, "dockable");
this.viewManager = requireNonNull(viewManager, "viewManager");
this.dockingManager = requireNonNull(dockingManager, "dockingManager");
}
@Override
public void closing(CVetoClosingEvent event) {
}
@Override
public void closed(CVetoClosingEvent event) {
if (event.isExpected()) {
dockingManager.removeDockable(dockable);
viewManager.removeDockable(dockable);
}
}
}

View File

@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dockable;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
/**
* A factory for handlers related to dockables.
*/
public interface DockableHandlerFactory {
/**
* Creates a new handler for closing the given dockable.
*
* @param dockable The dockable.
* @return A new handler for closing the given dockable.
*/
DockableClosingHandler createDockableClosingHandler(
DefaultSingleCDockable dockable
);
}

View File

@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dockable;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import java.util.Comparator;
/**
* Compares two <code>DefaultSingleCDockable</code> instances by their titles.
*/
public class DockableTitleComparator
implements
Comparator<DefaultSingleCDockable> {
/**
* Creates a new instance.
*/
public DockableTitleComparator() {
}
@Override
public int compare(DefaultSingleCDockable o1, DefaultSingleCDockable o2) {
return o1.getTitleText().compareTo(o2.getTitleText());
}
}

View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dockable;
import bibliothek.gui.dock.common.SingleCDockable;
import java.beans.PropertyChangeListener;
/**
* Utility class for working with dockables.
*/
public interface DockingManager {
/**
* PropertyChangeEvent when a floating dockable closes.
*/
String DOCKABLE_CLOSED = "DOCKABLE_CLOSED";
/**
* Adds a PropertyChangeListener.
*
* @param listener The new listener.
*/
void addPropertyChangeListener(PropertyChangeListener listener);
/**
* Removes a dockable from the CControl.
*
* @param dockable The dockable that shall be removed.
*/
void removeDockable(SingleCDockable dockable);
/**
* Removes a dockable with the given id.
*
* @param id The id of the dockable to remove.
*/
void removeDockable(String id);
}

View File

@@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.dockable;
import static java.util.Objects.requireNonNull;
import bibliothek.gui.dock.common.event.CFocusListener;
import bibliothek.gui.dock.common.intern.CDockable;
import jakarta.inject.Inject;
import java.awt.event.FocusEvent;
import org.jhotdraw.draw.DrawingEditor;
import org.opentcs.guing.common.application.ViewManager;
import org.opentcs.guing.common.components.drawing.DrawingViewScrollPane;
import org.opentcs.guing.common.components.drawing.OpenTCSDrawingView;
/**
* Handles focussing of dockable drawing views.
*/
public class DrawingViewFocusHandler
implements
CFocusListener {
/**
* Manages the application's views.
*/
private final ViewManager viewManager;
/**
* The drawing editor.
*/
private final DrawingEditor drawingEditor;
/**
* Creates a new instance.
*
* @param viewManager Manages the application's views.
* @param drawingEditor The drawing editor.
*/
@Inject
public DrawingViewFocusHandler(
ViewManager viewManager,
DrawingEditor drawingEditor
) {
this.viewManager = requireNonNull(viewManager, "viewManager");
this.drawingEditor = requireNonNull(drawingEditor, "drawingEditor");
}
@Override
public void focusGained(CDockable dockable) {
DrawingViewScrollPane scrollPane = viewManager.getDrawingViewMap().get(dockable);
if (scrollPane == null) {
return;
}
OpenTCSDrawingView drawView = scrollPane.getDrawingView();
drawingEditor.setActiveView(drawView);
// XXX Looks suspicious: Why are the same values set again here?
drawView.setConstrainerVisible(drawView.isConstrainerVisible());
drawView.setLabelsVisible(drawView.isLabelsVisible());
scrollPane.setRulersVisible(scrollPane.isRulersVisible());
drawView.getComponent().dispatchEvent(new FocusEvent(scrollPane, FocusEvent.FOCUS_GAINED));
}
@Override
public void focusLost(CDockable dockable) {
}
}

View File

@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import org.jhotdraw.draw.BezierFigure;
import org.opentcs.guing.base.model.elements.PathModel;
import org.opentcs.guing.common.components.drawing.figures.PathConnection;
import org.opentcs.guing.common.components.drawing.figures.liner.BezierLinerEdit;
/**
* Updates a bezier-style PathConnection's control points on edits.
*/
public class BezierLinerEditHandler
implements
UndoableEditListener {
/**
* Creates a new instance.
*/
public BezierLinerEditHandler() {
}
@Override
public void undoableEditHappened(UndoableEditEvent evt) {
if (!(evt.getEdit() instanceof BezierLinerEdit)) {
return;
}
BezierFigure owner = ((BezierLinerEdit) evt.getEdit()).getOwner();
if (!(owner instanceof PathConnection)) {
return;
}
PathConnection path = (PathConnection) owner;
path.updateControlPoints();
PathModel pathModel = path.getModel();
pathModel.getPropertyPathControlPoints().markChanged();
pathModel.propertiesChanged(path);
}
}

View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing;
/**
* Allows the configuration of drawing options.
*/
public class DrawingOptions {
/**
* Indicates whether blocks should be drawn or not.
*/
private boolean blocksVisible = true;
public DrawingOptions() {
}
/**
* Returns whether blocks should be drawn or not.
*
* @return {@code true}, if blocks should be drawn, otherwise {@code false}.
*/
public boolean isBlocksVisible() {
return blocksVisible;
}
/**
* Sets whether blocks should be drawn or not.
*
* @param blocksVisible The new value.
*/
public void setBlocksVisible(boolean blocksVisible) {
this.blocksVisible = blocksVisible;
}
}

View File

@@ -0,0 +1,306 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing;
import static java.util.Objects.requireNonNull;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.beans.PropertyChangeEvent;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import org.opentcs.guing.common.util.I18nPlantOverview;
import org.opentcs.guing.common.util.ImageDirectory;
import org.opentcs.thirdparty.guing.common.jhotdraw.util.ResourceBundleUtil;
/**
* A placard panel for drawing views.
*/
public class DrawingViewPlacardPanel
extends
JPanel {
/**
* This instance's resource bundle.
*/
private final ResourceBundleUtil labels
= ResourceBundleUtil.getBundle(I18nPlantOverview.MODELVIEW_PATH);
/**
* A combo box for selecting the zoom level.
*/
private final JComboBox<ZoomItem> zoomComboBox;
/**
* A toggle button for turning rulers on/off.
*/
private final JToggleButton toggleRulersButton;
/**
* The drawing options.
*/
private final DrawingOptions drawingOptions;
/**
* Creates a new instance.
*
* @param drawingView The drawing view.
* @param drawingOptions The drawing options.
*/
@SuppressWarnings("this-escape")
public DrawingViewPlacardPanel(
OpenTCSDrawingView drawingView,
DrawingOptions drawingOptions
) {
requireNonNull(drawingView, "drawingView");
this.drawingOptions = requireNonNull(drawingOptions, "drawingOptions");
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
// Create an extra panel so that the contents can be centered vertically.
JPanel vCenteringPanel = new JPanel();
vCenteringPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
this.add(Box.createVerticalGlue());
this.add(vCenteringPanel);
this.zoomComboBox = zoomComboBox(drawingView);
vCenteringPanel.add(zoomComboBox);
vCenteringPanel.add(zoomViewToWindowButton(drawingView));
// Show/hide grid
JToggleButton toggleConstrainerButton = toggleConstrainerButton(drawingView);
toggleConstrainerButton.setSelected(drawingView.isConstrainerVisible());
vCenteringPanel.add(toggleConstrainerButton);
// Show/hide rulers
toggleRulersButton = toggleRulersButton();
vCenteringPanel.add(toggleRulersButton);
// Show/hide leabels
JToggleButton toggleLabelsButton = toggleLabelsButton(drawingView);
toggleLabelsButton.setSelected(drawingView.isLabelsVisible());
vCenteringPanel.add(toggleLabelsButton);
// Show/hide blocks
JToggleButton toggleBlocksButton = toggleBlocksButton(drawingView);
toggleBlocksButton.setSelected(drawingOptions.isBlocksVisible());
vCenteringPanel.add(toggleBlocksButton);
}
public JComboBox<ZoomItem> getZoomComboBox() {
return zoomComboBox;
}
public JToggleButton getToggleRulersButton() {
return toggleRulersButton;
}
/**
* Creates the combo box with different zoom factors.
*
* @param drawingView The DrawingView this combo box will belong to.
* @return The created combo box.
*/
private JComboBox<ZoomItem> zoomComboBox(final OpenTCSDrawingView drawingView) {
final JComboBox<ZoomItem> comboBox = new JComboBox<>();
comboBox.setEditable(true);
comboBox.setFocusable(true);
final double[] scaleFactors = {
5.00, 4.00, 3.00, 2.00, 1.50, 1.25, 1.00, 0.75, 0.50, 0.25, 0.10
};
for (int i = 0; i < scaleFactors.length; i++) {
comboBox.addItem(new ZoomItem(scaleFactors[i]));
if (scaleFactors[i] == 1.0) {
comboBox.setSelectedIndex(i);
}
}
comboBox.addActionListener((ActionEvent e) -> {
final double scaleFactor;
if (comboBox.getSelectedItem() instanceof ZoomItem) {
// A zoom step of the array scaleFactors[]
ZoomItem item = (ZoomItem) comboBox.getSelectedItem();
scaleFactor = item.getScaleFactor();
}
else {
try {
// Text input in the combo box
String text = (String) comboBox.getSelectedItem();
double factor = Double.parseDouble(text.split(" ")[0]);
scaleFactor = factor * 0.01; // Eingabe in %
comboBox.setSelectedItem((int) (factor + 0.5) + " %");
}
catch (NumberFormatException ex) {
comboBox.setSelectedIndex(0);
return;
}
}
drawingView.setScaleFactor(scaleFactor);
});
drawingView.addPropertyChangeListener((PropertyChangeEvent evt) -> {
// String constants are interned
if ("scaleFactor".equals(evt.getPropertyName())) {
double scaleFactor = (double) evt.getNewValue();
for (int i = 0; i < comboBox.getItemCount(); i++) {
// One of the predefined scale factors was selected
if (scaleFactor == comboBox.getItemAt(i).getScaleFactor()) {
comboBox.setSelectedIndex(i);
break;
}
if (i + 1 < comboBox.getItemCount()
&& scaleFactor < comboBox.getItemAt(i).getScaleFactor()
&& scaleFactor > comboBox.getItemAt(i + 1).getScaleFactor()) {
// Insert the new scale factor between the next smaller / larger entries
ZoomItem newItem = new ZoomItem(scaleFactor);
comboBox.insertItemAt(newItem, i + 1);
comboBox.setSelectedItem(newItem);
}
else if (scaleFactor > comboBox.getItemAt(0).getScaleFactor()) {
// Insert new item for scale factor larger than the largest predefined factor
ZoomItem newItem = new ZoomItem(scaleFactor);
comboBox.insertItemAt(newItem, 0);
comboBox.setSelectedItem(newItem);
}
else if (scaleFactor < comboBox.getItemAt(comboBox.getItemCount() - 1).getScaleFactor()) {
// Insert new item for scale factor larger than the largest predefined factor
ZoomItem newItem = new ZoomItem(scaleFactor);
comboBox.insertItemAt(newItem, comboBox.getItemCount());
comboBox.setSelectedItem(newItem);
}
}
}
});
return comboBox;
}
/**
* Creates a button that zooms the drawing to a scale factor so that
* it fits the window size.
*
* @return The created button.
*/
private JButton zoomViewToWindowButton(final OpenTCSDrawingView drawingView) {
final JButton button = new JButton();
button.setToolTipText(
labels.getString("drawingViewPlacardPanel.button_zoomViewToWindow.tooltipText")
);
button.setIcon(ImageDirectory.getImageIcon("/menu/zoom-fit-best-4.png"));
button.setMargin(new Insets(0, 0, 0, 0));
button.setFocusable(false);
button.addActionListener((ActionEvent e) -> drawingView.zoomViewToWindow());
return button;
}
/**
* Creates a button to toggle the grid in the drawing.
*
* @param view The DrawingView the button will belong to.
* @return The created button.
*/
private JToggleButton toggleConstrainerButton(final OpenTCSDrawingView drawingView) {
final JToggleButton toggleButton = new JToggleButton();
toggleButton.setToolTipText(
labels.getString("drawingViewPlacardPanel.button_toggleGrid.tooltipText")
);
toggleButton.setIcon(ImageDirectory.getImageIcon("/menu/view-split.png"));
toggleButton.setMargin(new Insets(0, 0, 0, 0));
toggleButton.setFocusable(false);
toggleButton.addItemListener(
(ItemEvent event) -> drawingView.setConstrainerVisible(toggleButton.isSelected())
);
return toggleButton;
}
/**
* Creates a button to toggle the rulers in the drawing.
*
* @return The created button.
*/
private JToggleButton toggleRulersButton() {
final JToggleButton toggleButton = new JToggleButton();
toggleButton.setToolTipText(
labels.getString("drawingViewPlacardPanel.button_toggleRulers.tooltipText")
);
toggleButton.setIcon(ImageDirectory.getImageIcon("/toolbar/document-page-setup.16x16.png"));
toggleButton.setMargin(new Insets(0, 0, 0, 0));
toggleButton.setFocusable(false);
return toggleButton;
}
/**
* Creates a button to toglle the labels.
*
* @param view The DrawingView the button will belong to.
* @return The created button.
*/
private JToggleButton toggleLabelsButton(final OpenTCSDrawingView drawingView) {
final JToggleButton toggleButton = new JToggleButton();
toggleButton.setToolTipText(
labels.getString("drawingViewPlacardPanel.button_toggleLabels.tooltipText")
);
toggleButton.setIcon(ImageDirectory.getImageIcon("/menu/comment-add.16.png"));
toggleButton.setMargin(new Insets(0, 0, 0, 0));
toggleButton.setFocusable(false);
toggleButton.addItemListener(
(ItemEvent event) -> drawingView.setLabelsVisible(toggleButton.isSelected())
);
return toggleButton;
}
/**
* Creates a button to toggle the blocks in the drawing.
*
* @param view The DrawingView the button will belong to.
* @return The created button.
*/
private JToggleButton toggleBlocksButton(final OpenTCSDrawingView drawingView) {
final JToggleButton toggleButton = new JToggleButton();
toggleButton.setToolTipText(
labels.getString("drawingViewPlacardPanel.button_toggleBlocks.tooltipText")
);
toggleButton.setIcon(ImageDirectory.getImageIcon("/tree/block.18x18.png"));
toggleButton.setMargin(new Insets(0, 0, 0, 0));
toggleButton.setFocusable(false);
toggleButton.addItemListener(itemEvent -> {
drawingOptions.setBlocksVisible(toggleButton.isSelected());
drawingView.drawingOptionsChanged();
});
return toggleButton;
}
}

View File

@@ -0,0 +1,181 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing;
import static java.util.Objects.requireNonNull;
import jakarta.annotation.Nonnull;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.EventObject;
import javax.swing.BorderFactory;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import javax.swing.JViewport;
import javax.swing.ScrollPaneConstants;
import javax.swing.border.EmptyBorder;
import org.jhotdraw.gui.PlacardScrollPaneLayout;
import org.opentcs.guing.common.components.drawing.course.Origin;
import org.opentcs.guing.common.components.drawing.course.OriginChangeListener;
/**
* A custom scroll pane to wrap an <code>OpenTCSDrawingView</code>.
*/
public class DrawingViewScrollPane
extends
JScrollPane
implements
OriginChangeListener {
/**
* The drawing view.
*/
private final OpenTCSDrawingView drawingView;
/**
* The view's placard panel.
*/
private final DrawingViewPlacardPanel placardPanel;
/**
* Whether the rulers are currently visible or not.
*/
private boolean rulersVisible = true;
private Origin origin = new Origin();
/**
* Creates a new instance.
*
* @param drawingView The drawing view.
* @param placardPanel The view's placard panel.
*/
@SuppressWarnings("this-escape")
public DrawingViewScrollPane(
OpenTCSDrawingView drawingView,
DrawingViewPlacardPanel placardPanel
) {
this.drawingView = requireNonNull(drawingView, "drawingView");
this.placardPanel = requireNonNull(placardPanel, "placardPanel");
setViewport(new JViewport());
getViewport().setView(drawingView.getComponent());
setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
setViewportBorder(BorderFactory.createLineBorder(new Color(0, 0, 0)));
setLayout(new PlacardScrollPaneLayout());
setBorder(new EmptyBorder(0, 0, 0, 0));
// Horizontal and vertical rulers
Ruler.Horizontal newHorizontalRuler = new Ruler.Horizontal(drawingView);
drawingView.addPropertyChangeListener(newHorizontalRuler);
newHorizontalRuler.setPreferredWidth(drawingView.getComponent().getWidth());
Ruler.Vertical newVerticalRuler = new Ruler.Vertical(drawingView);
drawingView.addPropertyChangeListener(newVerticalRuler);
newVerticalRuler.setPreferredHeight(drawingView.getComponent().getHeight());
setColumnHeaderView(newHorizontalRuler);
setRowHeaderView(newVerticalRuler);
this.add(placardPanel, JScrollPane.LOWER_LEFT_CORNER);
// Increase the preferred height of the horizontal scroll bar, which also affects the height of
// the corner in which the DrawingViewPlacardPanel is located. This ensures there is enough
// vertical space for all components in all graphical environments. (Without this, the vertical
// space is not sufficient for some components e.g. on Ubuntu 20.04.)
getHorizontalScrollBar().setPreferredSize(new Dimension(100, 34));
// Register handler for rulers toggle button.
placardPanel.getToggleRulersButton().addItemListener(
new RulersToggleListener(placardPanel.getToggleRulersButton())
);
placardPanel.getToggleRulersButton().setSelected(rulersVisible);
}
public OpenTCSDrawingView getDrawingView() {
return drawingView;
}
public DrawingViewPlacardPanel getPlacardPanel() {
return placardPanel;
}
public Ruler.Horizontal getHorizontalRuler() {
return (Ruler.Horizontal) getColumnHeader().getView();
}
public Ruler.Vertical getVerticalRuler() {
return (Ruler.Vertical) getRowHeader().getView();
}
public boolean isRulersVisible() {
return rulersVisible;
}
public void setRulersVisible(boolean visible) {
this.rulersVisible = visible;
if (visible) {
getHorizontalRuler().setVisible(true);
getHorizontalRuler().setPreferredWidth(getWidth());
getVerticalRuler().setVisible(true);
getVerticalRuler().setPreferredHeight(getHeight());
getPlacardPanel().getToggleRulersButton().setSelected(true);
}
else {
getHorizontalRuler().setVisible(false);
getHorizontalRuler().setPreferredSize(new Dimension(0, 0));
getVerticalRuler().setVisible(false);
getVerticalRuler().setPreferredSize(new Dimension(0, 0));
getPlacardPanel().getToggleRulersButton().setSelected(false);
}
}
public void originChanged(
@Nonnull
Origin origin
) {
requireNonNull(origin, "origin");
if (origin == this.origin) {
return;
}
this.origin.removeListener(getHorizontalRuler());
this.origin.removeListener(getVerticalRuler());
this.origin.removeListener(this);
this.origin = origin;
origin.addListener(getHorizontalRuler());
origin.addListener(getVerticalRuler());
origin.addListener(this);
// Notify the rulers directly. This is necessary to initialize/update the rulers scale when a
// model is created or loaded.
// Calling origin.notifyScaleChanged() would lead to all model elements being notified (loading
// times for bigger models would suffer).
getHorizontalRuler().originScaleChanged(new EventObject(origin));
getVerticalRuler().originScaleChanged(new EventObject(origin));
}
@Override
public void originLocationChanged(EventObject evt) {
}
@Override
public void originScaleChanged(EventObject evt) {
drawingView.getComponent().revalidate();
}
private class RulersToggleListener
implements
ItemListener {
private final JToggleButton rulersButton;
RulersToggleListener(JToggleButton rulersButton) {
this.rulersButton = requireNonNull(rulersButton, "rulersButton");
}
@Override
public void itemStateChanged(ItemEvent e) {
setRulersVisible(rulersButton.isSelected());
}
}
}

View File

@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
/**
* Triggers a (re-)initialization of the view's offset figures when notified
* about resize events.
*/
public class OffsetListener
implements
ComponentListener {
/**
* The drawing view we're working with.
*/
private final OpenTCSDrawingEditor drawingEditor;
/**
* Initiales the offset figures once the view is resized (normally
* done when the window becomes visible). But the listener shouldn't
* listen to further resizing events.
* XXX get rid of this listener?
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param drawingEditor The drawing editor to call for (re-)initialization of its
* offset figures.
*/
public OffsetListener(OpenTCSDrawingEditor drawingEditor) {
this.drawingEditor = drawingEditor;
}
@Override
public void componentResized(ComponentEvent e) {
if (!initialized) {
drawingEditor.initializeViewport();
initialized = true;
}
}
@Override
public void componentMoved(ComponentEvent e) {
}
@Override
public void componentShown(ComponentEvent e) {
}
@Override
public void componentHidden(ComponentEvent e) {
}
}

View File

@@ -0,0 +1,380 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.KeyStroke;
import org.jhotdraw.draw.DefaultDrawingEditor;
import org.jhotdraw.draw.Drawing;
import org.jhotdraw.draw.DrawingView;
import org.jhotdraw.draw.Figure;
import org.jhotdraw.draw.action.IncreaseHandleDetailLevelAction;
import org.jhotdraw.draw.event.CompositeFigureEvent;
import org.jhotdraw.draw.event.CompositeFigureListener;
import org.opentcs.guing.common.components.drawing.figures.LabeledFigure;
import org.opentcs.guing.common.components.drawing.figures.OffsetFigure;
import org.opentcs.guing.common.components.drawing.figures.TCSLabelFigure;
import org.opentcs.guing.common.event.DrawingEditorEvent;
import org.opentcs.guing.common.event.DrawingEditorListener;
import org.opentcs.guing.common.model.SystemModel;
import org.opentcs.guing.common.util.CourseObjectFactory;
import org.opentcs.thirdparty.guing.common.jhotdraw.application.action.draw.MoveAction;
import org.opentcs.thirdparty.guing.common.jhotdraw.application.action.edit.DeleteAction;
import org.opentcs.thirdparty.guing.common.jhotdraw.application.action.edit.SelectAllAction;
import org.opentcs.util.event.EventHandler;
/**
* The <code>DrawingEditor</code> coordinates <code>DrawingViews</code>
* and the <code>Drawing</code>.
* It also offers methods to add specific unique figures to the
* <code>Drawing</code>.
*/
public class OpenTCSDrawingEditor
extends
DefaultDrawingEditor
implements
EventHandler {
/**
* Width on the screen edge.
*/
private static final int MARGIN = 20;
/**
* A factory for course objects.
*/
private final CourseObjectFactory crsObjectFactory;
/**
*
*/
private final CompositeFigureEventHandler cmpFigureEvtHandler = new CompositeFigureEventHandler();
/**
* Listens for figure selection, addition or removal events.
*/
private final List<DrawingEditorListener> fDrawingEditorListeners = new ArrayList<>();
/**
* The drawing that contains all figures.
*/
private Drawing fDrawing;
// These invisible figures are moved automatically to enlarge the drawing.
private OffsetFigure topOffsetFigure;
private OffsetFigure bottomOffsetFigure;
private OffsetFigure rightOffsetFigure;
private OffsetFigure leftOffsetFigure;
/**
* Creates a new instance.
*
* @param crsObjFactory A factory for course objects.
*/
@Inject
public OpenTCSDrawingEditor(CourseObjectFactory crsObjFactory) {
this.crsObjectFactory = requireNonNull(crsObjFactory, "crsObjectFactory");
}
@Override
public void onEvent(Object event) {
}
/**
* Creates the offset figures, sets their position to the current bounds of the
* view and repaints the ruler to fit the current grid.
*/
public void initializeViewport() {
initializeRuler();
removeOffsetFigures();
topOffsetFigure = crsObjectFactory.createOffsetFigure();
bottomOffsetFigure = crsObjectFactory.createOffsetFigure();
leftOffsetFigure = crsObjectFactory.createOffsetFigure();
rightOffsetFigure = crsObjectFactory.createOffsetFigure();
OpenTCSDrawingView activeView = getActiveView();
if (activeView == null) {
return;
}
// Rectangle that contains all figures
Rectangle2D.Double drawingArea = getDrawing().getDrawingArea();
// The visible rectangle
Rectangle visibleRect = activeView.getComponent().getVisibleRect();
// The size of the invisible offset figures
double wFigure = topOffsetFigure.getBounds().width;
double hFigure = topOffsetFigure.getBounds().height;
// When the drawing already contains figures
double xLeft = drawingArea.x;
double xRight = drawingArea.x + drawingArea.width;
double yTop = drawingArea.y;
double yBottom = drawingArea.y + drawingArea.height;
// An empty drawing only contains the origin figure, which shall be on the bottom left.
if (visibleRect.width > drawingArea.width && visibleRect.height > drawingArea.height) {
xLeft = -drawingArea.width / 2 - MARGIN;
xRight = visibleRect.width + xLeft - (MARGIN + wFigure / 2);
yBottom = -(-drawingArea.height / 2 - MARGIN);
yTop = -(visibleRect.height - yBottom - (MARGIN + hFigure / 2));
}
double xCenter = (xLeft + xRight) / 2;
double yCenter = (yBottom + yTop) / 2;
topOffsetFigure.setBounds(new Point2D.Double(xCenter, yTop), null);
bottomOffsetFigure.setBounds(new Point2D.Double(xCenter, yBottom), null);
leftOffsetFigure.setBounds(new Point2D.Double(xLeft, yCenter), null);
rightOffsetFigure.setBounds(new Point2D.Double(xRight, yCenter), null);
getDrawing().add(topOffsetFigure);
getDrawing().add(bottomOffsetFigure);
getDrawing().add(leftOffsetFigure);
getDrawing().add(rightOffsetFigure);
// validateViewTranslation();
}
protected CourseObjectFactory getCourseObjectFactory() {
return crsObjectFactory;
}
private void initializeRuler() {
OpenTCSDrawingView activeView = getActiveView();
DrawingViewScrollPane scrollPane
= (DrawingViewScrollPane) activeView.getComponent().getParent().getParent();
Rectangle2D.Double drawingArea
= activeView.getDrawing().getDrawingArea();
scrollPane.getHorizontalRuler().setPreferredWidth((int) drawingArea.width);
scrollPane.getVerticalRuler().setPreferredHeight((int) drawingArea.height);
}
/**
* Removes the <code>OffsetFigure</code>s off the drawing.
*/
private void removeOffsetFigures() {
if (getDrawing() == null) {
return;
}
getDrawing().remove(topOffsetFigure);
getDrawing().remove(bottomOffsetFigure);
getDrawing().remove(leftOffsetFigure);
getDrawing().remove(rightOffsetFigure);
}
/**
* Adds a listener.
*
* @param listener The listener.
*/
public void addDrawingEditorListener(DrawingEditorListener listener) {
requireNonNull(listener, "listener");
fDrawingEditorListeners.add(listener);
}
/**
* Removes a listener.
*
* @param listener The listener.
*/
public void removeDrawingEditorListener(DrawingEditorListener listener) {
requireNonNull(listener, "listener");
fDrawingEditorListeners.remove(listener);
}
/**
* Sets the system model.
*
* @param systemModel The model of the course.
*/
public void setSystemModel(SystemModel systemModel) {
setDrawing(systemModel.getDrawing());
for (DrawingView drawView : getDrawingViews()) {
((OpenTCSDrawingView) drawView).setBlocks(
systemModel.getMainFolder(SystemModel.FolderKey.BLOCKS)
);
}
}
public Drawing getDrawing() {
return fDrawing;
}
public void setDrawing(Drawing drawing) {
requireNonNull(drawing, "drawing");
if (fDrawing != null) {
fDrawing.removeCompositeFigureListener(cmpFigureEvtHandler);
}
fDrawing = drawing;
fDrawing.addCompositeFigureListener(cmpFigureEvtHandler);
// Also let the drawing views know about the new drawing.
for (DrawingView view : getDrawingViews()) {
view.setDrawing(drawing);
}
}
@Override
public void add(DrawingView view) {
super.add(view);
view.setDrawing(fDrawing);
}
@Override
public OpenTCSDrawingView getActiveView() {
return (OpenTCSDrawingView) super.getActiveView();
}
public Collection<OpenTCSDrawingView> getAllViews() {
Collection<OpenTCSDrawingView> result = new ArrayList<>();
for (DrawingView view : getDrawingViews()) {
result.add((OpenTCSDrawingView) view);
}
return result;
}
/**
* Notification of the <code>DrawingView</code> that a figure was added.
*
* @param figure The added figure.
*/
public void figureAdded(Figure figure) {
// Create the data model to a new point or location figure and show
// the name in the label
if (figure instanceof LabeledFigure) {
LabeledFigure labeledFigure = (LabeledFigure) figure;
if (labeledFigure.getLabel() == null) {
// Create the label and add the figure to the data model
TCSLabelFigure label = new TCSLabelFigure();
Point2D.Double pos = labeledFigure.getStartPoint();
pos.x += label.getOffset().x;
pos.y += label.getOffset().y;
label.setBounds(pos, pos);
labeledFigure.setLabel(label);
}
}
for (DrawingEditorListener listener : fDrawingEditorListeners) {
listener.figureAdded(new DrawingEditorEvent(this, figure));
}
}
/**
* Notification of the <code>DrawingView</code> that a figure was removed.
*
* @param figure The figure that was removed.
*/
public void figureRemoved(Figure figure) {
for (DrawingEditorListener listener : fDrawingEditorListeners) {
listener.figureRemoved(new DrawingEditorEvent(this, figure));
}
}
/**
* Notification of the <code>DrawingView</code> that figures were selected.
*
* @param figures The selected figures.
*/
public void figuresSelected(List<Figure> figures) {
for (DrawingEditorListener listener : fDrawingEditorListeners) {
listener.figureSelected(new DrawingEditorEvent(this, figures));
}
}
/**
* Overrides the method from DefaultDrawingEditor to create a tool-specific
* input map.
* The implementation of this class creates an input map for the following
* action ID's:
* - DeleteAction
* - MoveAction.West, .East, .North, .South
*
* SelectAll, Cut, Copy, Paste are handled by SelectAllAction etc.
*
* @return The input map.
*/
@Override // DefaultDrawingEditor
protected InputMap createInputMap() {
InputMap m = new InputMap();
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), DeleteAction.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), DeleteAction.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), MoveAction.West.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), MoveAction.East.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), MoveAction.North.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), MoveAction.South.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.ALT_DOWN_MASK), MoveAction.West.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.ALT_DOWN_MASK), MoveAction.East.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK), MoveAction.North.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK), MoveAction.South.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.SHIFT_DOWN_MASK), MoveAction.West.ID);
m.put(
KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.SHIFT_DOWN_MASK),
MoveAction.East.ID
);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.SHIFT_DOWN_MASK), MoveAction.North.ID);
m.put(
KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.SHIFT_DOWN_MASK),
MoveAction.South.ID
);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.CTRL_DOWN_MASK), MoveAction.West.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.CTRL_DOWN_MASK), MoveAction.East.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_DOWN_MASK), MoveAction.North.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK), MoveAction.South.ID);
return m;
}
@Override // DefaultDrawingEditor
protected ActionMap createActionMap() {
ActionMap m = new ActionMap();
m.put(DeleteAction.ID, new DeleteAction());
m.put(SelectAllAction.ID, new SelectAllAction());
m.put(IncreaseHandleDetailLevelAction.ID, new IncreaseHandleDetailLevelAction(this));
m.put(MoveAction.East.ID, new MoveAction.East(this));
m.put(MoveAction.West.ID, new MoveAction.West(this));
m.put(MoveAction.North.ID, new MoveAction.North(this));
m.put(MoveAction.South.ID, new MoveAction.South(this));
// m.put(CutAction.ID, new CutAction());
// m.put(CopyAction.ID, new CopyAction());
// m.put(PasteAction.ID, new PasteAction());
return m;
}
private class CompositeFigureEventHandler
implements
CompositeFigureListener {
/**
* Creates a new instance.
*/
CompositeFigureEventHandler() {
}
@Override
public void figureAdded(CompositeFigureEvent e) {
OpenTCSDrawingEditor.this.figureAdded(e.getChildFigure());
}
@Override
public void figureRemoved(CompositeFigureEvent e) {
OpenTCSDrawingEditor.this.figureRemoved(e.getChildFigure());
}
}
}

View File

@@ -0,0 +1,111 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing;
import jakarta.annotation.Nonnull;
import java.awt.Point;
import java.io.File;
import java.util.Set;
import org.jhotdraw.draw.DrawingView;
import org.jhotdraw.draw.Figure;
import org.opentcs.guing.base.model.ModelComponent;
import org.opentcs.guing.base.model.elements.BlockModel;
import org.opentcs.guing.base.model.elements.VehicleModel;
import org.opentcs.guing.common.components.drawing.figures.BitmapFigure;
import org.opentcs.util.event.EventHandler;
/**
*/
public interface OpenTCSDrawingView
extends
DrawingView,
EventHandler {
boolean isLabelsVisible();
void setLabelsVisible(boolean newValue);
/**
* Called when the drawing options have changed.
*/
void drawingOptionsChanged();
/**
* Returns if a given point on the screen is contained in this drawing view.
*
* @param p The reference point on the screen.
* @return Boolean if this point is contained.
*/
boolean containsPointOnScreen(Point p);
/**
* Adds a background image to this drawing view.
*
* @param file The file with the image.
*/
void addBackgroundBitmap(File file);
/**
* Adds a background image to this drawing view.
*
* @param bitmapFigure The figure containing the image.
*/
void addBackgroundBitmap(BitmapFigure bitmapFigure);
/**
* Scales the view to a value so the whole model fits.
*/
void zoomViewToWindow();
/**
* Sets the elements of the blocks.
*
* @param blocks A <code>ModelComponent</code> which childs must be <code>BlockModels</code>.
*/
void setBlocks(ModelComponent blocks);
/**
* Shows or hides the current route of a vehicle.
*
* @param vehicle The vehicle
* @param visible <code>true</code> to set it to visible, <code>false</code> otherwise.
*/
void displayDriveOrders(VehicleModel vehicle, boolean visible);
/**
* Updates the figures of a block.
*
* @param block The block.
*/
void updateBlock(BlockModel block);
/**
* Scrolls to the given figure. Normally called when the user clicks on a model component in the
* TreeView and wants to see the corresponding figure.
*
* @param figure The figure to be scrolled to.
*/
void scrollTo(Figure figure);
/**
* Fixes the view on the vehicle and marks it and its destination with a colored circle.
*
* @param model The vehicle model.
*/
void followVehicle(
@Nonnull
VehicleModel model
);
/**
* Releases the view and stops following the current vehicle.
*/
void stopFollowVehicle();
/**
* Deletes the given model components from the drawing view.
*
* @param components The components to delete.
*/
void delete(Set<ModelComponent> components);
}

View File

@@ -0,0 +1,397 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing;
import static java.util.Objects.requireNonNull;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.EventObject;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import org.jhotdraw.draw.DrawingView;
import org.opentcs.guing.common.components.drawing.course.Origin;
import org.opentcs.guing.common.components.drawing.course.OriginChangeListener;
/**
* A ruler.
*/
public abstract class Ruler
extends
JComponent
implements
PropertyChangeListener,
OriginChangeListener {
/**
* Size of the rulers (height of the horizontal ruler, width of the vertical).
*/
private static final int SIZE = 25;
/**
* The standard translation of the drawing view. Not sure though why
* it is -12.
*/
private static final int STANDARD_TRANSLATION = -12;
/**
* The DrawingView.
*/
protected final DrawingView drawingView;
/**
* The current scale factor.
*/
protected double scaleFactor = 1.0;
/**
* The scale factor for the horizontal ruler.
*/
protected double horizontalRulerScale = Origin.DEFAULT_SCALE;
/**
* The scale factor for the vertical ruler.
*/
protected double verticalRulerScale = Origin.DEFAULT_SCALE;
/**
* Creates a new instance.
*
* @param drawingView The drawing view.
*/
private Ruler(DrawingView drawingView) {
this.drawingView = requireNonNull(drawingView, "drawingView");
}
/**
* A horizontal ruler.
*/
public static class Horizontal
extends
Ruler {
/**
* Creates a new instance.
*
* @param drawingView The drawing view.
*/
public Horizontal(DrawingView drawingView) {
super(drawingView);
}
/**
* Sets a new width of the ruler and repaints it.
*
* @param preferredWidth The new width.
*/
public void setPreferredWidth(int preferredWidth) {
setPreferredSize(new Dimension(preferredWidth, SIZE));
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Rectangle drawHere = g.getClipBounds();
Point translation = new Point(
(int) -drawingView.getDrawingToViewTransform().getTranslateX(),
(int) -drawingView.getDrawingToViewTransform().getTranslateY()
);
// If we scroll right, the translation isn't incremented by default.
// We use the translation of the visible rectangle instead.
int visibleRectX = drawingView.getComponent().getVisibleRect().x + STANDARD_TRANSLATION;
if (STANDARD_TRANSLATION == translation.x) {
translation.x = visibleRectX;
}
Graphics2D g2d = (Graphics2D) g;
g2d.setFont(new Font("Arial", Font.PLAIN, 10));
// i translated
int translated;
// i normalized to decimal
int draw;
int drawOld = 0;
// draw translated
int drawTranslated;
String translatedAsString;
String lastIndex;
// base line
g2d.drawLine(
0, SIZE - 1,
getWidth(), SIZE - 1
);
for (int i = drawHere.x; i < getWidth(); i += 10) {
translated = translateValue(i, translation);
translatedAsString = Integer.toString(translated);
lastIndex = translatedAsString.substring(translatedAsString.length() - 1);
int decimal = Integer.parseInt(lastIndex);
{
// These steps are neccessary to guarantee lines are drawn
// at every pixel. It always rounds i to a decimal, so the modulo
// operators work
draw = i;
if (translated < 0) {
draw += decimal;
}
else {
draw -= decimal;
}
drawTranslated = translateValue(draw, translation);
// draw has to be incremented by 1, otherwise the drawn lines
// are wrong by 1 pixel
draw++;
}
if (drawTranslated % (10 * scaleFactor) == 0) {
g2d.drawLine(draw, SIZE - 1, draw, SIZE - 4);
}
if (drawTranslated % (50 * scaleFactor) == 0) {
g2d.drawLine(draw, SIZE - 1, draw, SIZE - 7);
}
if (drawTranslated % (100 * scaleFactor) == 0) {
g2d.drawLine(draw, SIZE - 1, draw, SIZE - 11);
int value = (int) (drawTranslated / scaleFactor) * (int) horizontalRulerScale;
String textValue = Integer.toString(value);
if (scaleFactor < 0.06) {
if (value % 5000 == 0) {
g2d.drawString(textValue, value == 0 ? draw - 2 : draw - 8, 9);
}
}
else if ((draw - drawOld) < 31) {
if (value % 500 == 0) {
g2d.drawString(textValue, value == 0 ? draw - 2 : draw - 8, 9);
}
}
else {
g2d.drawString(textValue, value == 0 ? draw - 2 : draw - 8, 9);
}
drawOld = draw;
}
}
}
/**
* Returns a translated value, considering current translation of the view.
*
* @param i The value.
* @return The translated value.
*/
private int translateValue(int i, Point translation) {
if (translation.x < 0) {
return i + translation.x;
}
else {
return i;
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("scaleFactor")) {
scaleFactor = (double) evt.getNewValue();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setPreferredWidth(drawingView.getComponent().getWidth());
}
});
}
}
@Override
public void originLocationChanged(EventObject evt) {
}
@Override
public void originScaleChanged(EventObject evt) {
if (evt.getSource() instanceof Origin) {
Origin origin = (Origin) evt.getSource();
SwingUtilities.invokeLater(() -> {
horizontalRulerScale = origin.getScaleX();
repaint();
});
}
}
}
/**
* A vertical ruler.
*/
public static class Vertical
extends
Ruler {
/**
* Creates a new instance.
*
* @param drawingView The drawing view.
*/
public Vertical(DrawingView drawingView) {
super(drawingView);
}
/**
* Sets a new height of the ruler and repaints it.
*
* @param preferredHeight The new height.
*/
public void setPreferredHeight(int preferredHeight) {
setPreferredSize(new Dimension(SIZE, preferredHeight));
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Rectangle drawHere = g.getClipBounds();
Point translation = new Point(
(int) -drawingView.getDrawingToViewTransform().getTranslateX(),
(int) -drawingView.getDrawingToViewTransform().getTranslateY()
);
// If we scroll downwards, the translation isn't incremented by default.
// We use the translation of the visible rectangle instead.
if (translation.y == STANDARD_TRANSLATION) {
translation.y = drawingView.getComponent().getVisibleRect().y + STANDARD_TRANSLATION;
}
Graphics2D g2d = (Graphics2D) g;
g2d.setFont(new Font("Arial", Font.PLAIN, 10));
// i translated
int translated;
// i normalized to decimal
int draw;
int drawOld = 0;
// draw translated
int drawTranslated;
String translatedAsString;
String lastIndex;
// base line
g2d.drawLine(
SIZE - 1, 0,
SIZE - 1, getHeight()
);
// Rotate the font for vertical axis
AffineTransform fontAT = new AffineTransform();
fontAT.rotate(270 * java.lang.Math.PI / 180);
Font font = g2d.getFont().deriveFont(fontAT);
g2d.setFont(font);
for (int i = drawHere.y; i < getHeight(); i += 10) {
translated = translateValue(i, translation);
translatedAsString = Integer.toString(translated);
lastIndex = translatedAsString.substring(translatedAsString.length() - 1);
int decimal = Integer.parseInt(lastIndex);
{
// These steps are neccessary to guarantee lines are drawn
// at every pixel. It always rounds i to a decimal, so the modulo
// operators work
draw = i;
if (translated < 0) {
draw += decimal;
}
else {
draw -= decimal;
}
drawTranslated = translateValue(draw, translation);
draw++;
}
if (drawTranslated % (10 * scaleFactor) == 0) {
g2d.drawLine(SIZE - 1, draw, SIZE - 4, draw);
}
if (drawTranslated % (50 * scaleFactor) == 0) {
g2d.drawLine(SIZE - 1, draw, SIZE - 7, draw);
}
if (drawTranslated % (100 * scaleFactor) == 0) {
g2d.drawLine(SIZE - 1, draw, SIZE - 11, draw);
int value = -(int) (drawTranslated / scaleFactor) * (int) verticalRulerScale;
String textValue = Integer.toString(value);
if (scaleFactor < 0.06) {
if (value % 5000 == 0) {
g2d.drawString(textValue, 9, value == 0 ? draw + 2 : draw + 8);
}
}
else if ((draw - drawOld) < 31) {
if (value % 500 == 0) {
g2d.drawString(textValue, 9, value == 0 ? draw + 2 : draw + 8);
}
}
else {
g2d.drawString(textValue, 9, value == 0 ? draw + 2 : draw + 8);
}
drawOld = draw;
}
}
}
/**
* Returns a translated value, considering current translation of the view.
*
* @param i The value.
* @return The translated value.
*/
private int translateValue(int i, Point translation) {
if (translation.y < 0) {
return i + translation.y;
}
else {
return i;
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("scaleFactor")) {
scaleFactor = (double) evt.getNewValue();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setPreferredHeight(drawingView.getComponent().getHeight());
}
});
}
}
@Override
public void originLocationChanged(EventObject evt) {
}
@Override
public void originScaleChanged(EventObject evt) {
if (evt.getSource() instanceof Origin) {
Origin origin = (Origin) evt.getSource();
SwingUtilities.invokeLater(() -> {
verticalRulerScale = origin.getScaleY();
repaint();
});
}
}
}
}

View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing;
import java.awt.BasicStroke;
import java.awt.Stroke;
/**
* Strokes used in the drawing.
*/
public class Strokes {
/**
* Decoration of paths, points and locations that are part of a block.
*/
public static final Stroke BLOCK_ELEMENT = new BasicStroke(4.0f);
/**
* Decoration of paths that are part of a transport order.
*/
public static final Stroke PATH_ON_ROUTE
= new BasicStroke(
6.0f,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0f,
new float[]{10.0f, 5.0f},
0.0f
);
/**
* Decoration of paths that are part of a withdrawn transport order.
*/
public static final Stroke PATH_ON_WITHDRAWN_ROUTE
= new BasicStroke(
6.0f,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0f,
new float[]{8.0f, 4.0f, 2.0f, 4.0f},
0.0f
);
/**
* Prevents instantiation of this utility class.
*/
private Strokes() {
}
}

View File

@@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing;
/**
* An item to show in a combo box.
*/
public class ZoomItem {
private final double scaleFactor;
public ZoomItem(double scaleFactor) {
this.scaleFactor = scaleFactor;
}
public double getScaleFactor() {
return scaleFactor;
}
@Override
public String toString() {
return String.format("%d %%", (int) (scaleFactor * 100));
}
}

View File

@@ -0,0 +1,145 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.io.Serializable;
/**
* Represents an exact point that won't change when zooming the model.
*/
public class ZoomPoint
implements
Serializable {
/**
* The x position with a scale of 1.
*/
protected double fX;
/**
* The y position with a scale of 1.
*/
protected double fY;
/**
* The current scale.
*/
protected double fScale;
/**
* Creates a new instance of ZoomPoint
*/
public ZoomPoint() {
this(0, 0);
}
/**
* Creates a new instance.
*
* @param x The x position for this point.
* @param y The y position for this point.
*/
public ZoomPoint(double x, double y) {
this(x, y, 1.0);
}
/**
* Creates a new instance.
*
* @param x The x position for this point.
* @param y The y position for this point.
* @param scale The current scale.
*/
public ZoomPoint(double x, double y, double scale) {
fX = x / scale;
fY = y / scale;
fScale = scale;
}
/**
* Returns the current scale.
*/
public double scale() {
return fScale;
}
/**
* Sets the x position.
*
* @param x the x position.
*/
public void setX(double x) {
fX = x;
}
/**
* Sets the y position.
*
* @param y the y position.
*/
public void setY(double y) {
fY = y;
}
/**
* Returns the x position.
*
* @return the x position.
*/
public double getX() {
return fX;
}
/**
* Returns the y position.
*
* @return the y position.
*/
public double getY() {
return fY;
}
/**
* Returns a point with the position.
*
* @return a point with the position.
*/
public Point getPixelLocation() {
int x = (int) (getX() * scale());
int y = (int) (getY() * scale());
return new Point(x, y);
}
/**
* Returns the exact position of the point in pixels with the current zoom level.
*
* @return the exact position.
*/
public Point2D getPixelLocationExactly() {
double x = getX() * scale();
double y = getY() * scale();
return new Point2D.Double(x, y);
}
/**
* Event that the scale has changed.
*
* @param scale The new scale factor.
*/
public void scaleChanged(double scale) {
fScale = scale;
}
/**
* Event that the point has been moved by the user.
*
* @param x The x-coordinate in Pixel.
* @param y The y-coordinate in Pixel.
*/
public void movedByMouse(int x, int y) {
fX = x / scale();
fY = y / scale();
}
}

View File

@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.course;
/**
* A drawing method where the position of a figure and the real position are in relation
* to each other.
*/
public class CoordinateBasedDrawingMethod
implements
DrawingMethod {
/**
* The origin point.
*/
protected Origin fOrigin;
/**
* Creates a new instance.
*/
public CoordinateBasedDrawingMethod() {
fOrigin = new Origin();
}
@Override
public Origin getOrigin() {
return fOrigin;
}
}

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.course;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.io.Serializable;
/**
* A strategy that can translate pixel coordinates to real coordinates.
*/
public interface CoordinateSystem
extends
Serializable {
/**
* Translates the real coordinate into a pixel coordinate.
*
* @param refPointLocation The current position of the reference point.
* @param realValue The real position to translate.
* @param relationX The amount of mm for one pixel in the x axis.
* @param relationY The amount of mm for one pixel in the y axis.
* @return A point with the pixel coordinates.
*/
Point2D toPixel(Point refPointLocation, Point2D realValue, double relationX, double relationY);
/**
* Translates the pixel coordinate into a real coordinate.
*
* @param refPointLocation The current position of the reference point.
* @param pixelValue The pixel coordinate position to translate.
* @param relationX The amount of mm for one pixel in the x axis.
* @param relationY The amount of mm for one pixel in the y axis.
* @return A point with the real position.
*/
Point2D toReal(Point refPointLocation, Point pixelValue, double relationX, double relationY);
}

View File

@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.course;
/**
* An interface for drawing methods. Possible drawing methods are:
* <p>
* <ul> <li> symbolic: No relation between the real position and the position of the figure.
* <li> coordinate based: The position of the figure is the exact real position. </ul>
*/
public interface DrawingMethod {
/**
* Returns the origin point.
*
* @return the origin point.
*/
Origin getOrigin();
}

View File

@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.course;
import java.awt.Point;
import java.awt.geom.Point2D;
/**
* A coordinate system strategy that converts pixel coordinates into real coordinates.
*/
public class NormalCoordinateSystem
implements
CoordinateSystem {
/**
* Creates a new instance.
*/
public NormalCoordinateSystem() {
}
@Override
public Point2D toPixel(Point refPointLocation, Point2D realValue, double scaleX, double scaleY) {
double xPixel = realValue.getX() / scaleX;
double yPixel = realValue.getY() / scaleY;
return new Point2D.Double(refPointLocation.x + xPixel, -(refPointLocation.y + yPixel));
}
@Override
public Point2D toReal(Point refPointLocation, Point pixelValue, double scaleX, double scaleY) {
int xDiff = pixelValue.x - refPointLocation.x;
int yDiff = pixelValue.y - refPointLocation.y;
return new Point2D.Double(scaleX * xDiff, -scaleY * yDiff);
}
}

View File

@@ -0,0 +1,266 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.course;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.EventObject;
import java.util.HashSet;
import java.util.Set;
import org.opentcs.guing.base.components.properties.type.LengthProperty;
import org.opentcs.guing.base.components.properties.type.StringProperty;
import org.opentcs.guing.common.components.drawing.figures.OriginFigure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The origin of the coordinate system. Represents the current scale, coordinate system and
* position of the origin on the screen.
*/
public final class Origin {
/**
* Scale (in mm per pixel) of the layout.
*/
public static final double DEFAULT_SCALE = 50.0;
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(Origin.class);
/**
* Amount of mm to equal one pixel on screen in horizontal direction.
*/
private double fScaleX = DEFAULT_SCALE;
/**
* Amount of mm to equal one pixel on screen in horizontal direction.
*/
private double fScaleY = DEFAULT_SCALE;
/**
* Current position in pixels.
*/
private Point fPosition;
/**
* The coordinate system.
*/
private CoordinateSystem fCoordinateSystem;
/**
* List of {@link OriginChangeListener}.
*/
private final Set<OriginChangeListener> fListeners = new HashSet<>();
/**
* Graphical figure to represent the origin.
*/
private final OriginFigure fFigure = new OriginFigure();
/**
* Creates a new instance.
*/
public Origin() {
setCoordinateSystem(new NormalCoordinateSystem());
fFigure.setModel(this);
}
/**
* Set the scale in millimeter per pixel.
*/
public void setScale(double scaleX, double scaleY) {
if (fScaleX == scaleX && fScaleY == scaleY) {
return;
}
fScaleX = scaleX;
fScaleY = scaleY;
notifyScaleChanged();
}
/**
* Return the millimeter per pixel in horizontal direction.
*
* @return the millimeter per pixel in horizontal direction.
*/
public double getScaleX() {
return fScaleX;
}
/**
* Return the millimeter per pixel in vertical direction.
*
* @return the millimeter per pixel in vertical direction.
*/
public double getScaleY() {
return fScaleY;
}
/**
* Set the coordinate system.
*/
public void setCoordinateSystem(CoordinateSystem coordinateSystem) {
fCoordinateSystem = coordinateSystem;
notifyLocationChanged();
}
/**
* Set the position of the origin.
*
* @param position the position of the origin.
*/
public void setPosition(Point position) {
fPosition = position;
}
/**
* Return the current position of the origin.
*
* @return the current position of the origin.
*/
public Point getPosition() {
return fPosition;
}
/**
* Translates the real coordinate into pixel coordinates.
*
* @param xReal The real x position.
* @param yReal The real y position.
* @return A point with the pixel position.
*/
public Point calculatePixelPosition(LengthProperty xReal, LengthProperty yReal) {
Point2D pixelExact = calculatePixelPositionExactly(xReal, yReal);
return new Point((int) pixelExact.getX(), (int) pixelExact.getY());
}
/**
* Translates the real coordinate into pixel coordinates with double precision.
*
* @param xReal The real x position.
* @param yReal The real y position.
* @return A point with the pixel position with double precision.
*/
public Point2D calculatePixelPositionExactly(LengthProperty xReal, LengthProperty yReal) {
Point2D realPosition = new Point2D.Double(
xReal.getValueByUnit(LengthProperty.Unit.MM),
yReal.getValueByUnit(LengthProperty.Unit.MM)
);
Point2D pixelPosition = fCoordinateSystem.toPixel(fPosition, realPosition, fScaleX, fScaleY);
return pixelPosition;
}
/**
* Translates the real coordinate into pixel coordinates with double precision from
* string properties.
*
* @param xReal The real x position.
* @param yReal The real y position.
* @return A point with the pixel position with double precision.
*/
public Point2D calculatePixelPositionExactly(StringProperty xReal, StringProperty yReal) {
try {
double xPos = Double.parseDouble(xReal.getText());
double yPos = Double.parseDouble(yReal.getText());
Point2D realPosition = new Point2D.Double(xPos, yPos);
Point2D pixelPosition = fCoordinateSystem.toPixel(fPosition, realPosition, fScaleX, fScaleY);
return pixelPosition;
}
catch (NumberFormatException e) {
LOG.info("Couldn't parse layout coordinates", e);
return new Point2D.Double();
}
}
/**
* Translates a pixel position into a real position and write to the length properties.
*
*
* @param pixelPosition The pixel position to convert.
* @param xReal The length property to write the x position to.
* @param yReal The length property to write the y position to.
* @return A point with the pixel position with double precision.
*/
public Point2D calculateRealPosition(
Point pixelPosition, LengthProperty xReal,
LengthProperty yReal
) {
Point2D realPosition = fCoordinateSystem.toReal(fPosition, pixelPosition, fScaleX, fScaleY);
LengthProperty.Unit unitX = xReal.getUnit();
LengthProperty.Unit unitY = yReal.getUnit();
xReal.setValueAndUnit((int) realPosition.getX(), LengthProperty.Unit.MM);
yReal.setValueAndUnit((int) realPosition.getY(), LengthProperty.Unit.MM);
xReal.convertTo(unitX);
yReal.convertTo(unitY);
return realPosition;
}
/**
* Translates a pixel position onto a real position.
*
* @param pixelPosition The pixel position to convert.
* @return A point with the pixel position with double precision.
*/
public Point2D calculateRealPosition(Point pixelPosition) {
Point2D realPosition = fCoordinateSystem.toReal(fPosition, pixelPosition, fScaleX, fScaleY);
return realPosition;
}
/**
* Add an origin change listener.
*
* @param l The origin change listener to add.
*/
public void addListener(OriginChangeListener l) {
fListeners.add(l);
}
/**
* Remove an origin change listener.
*
* @param l The origin change listener to remove.
*/
public void removeListener(OriginChangeListener l) {
fListeners.remove(l);
}
/**
*
* Tests whether a specific origin change listener is registerd.
*
* @param l The origin change listener to test for.
* @return <code> true </code>, if the listener is registerd.
*/
public boolean containsListener(OriginChangeListener l) {
return fListeners.contains(l);
}
/**
* Notifies all registered listeners that the position of the origin has changed.
*/
public void notifyLocationChanged() {
for (OriginChangeListener l : fListeners) {
l.originLocationChanged(new EventObject(this));
}
}
/**
* Notifies all registered listeners that the scale has changed.
*/
public void notifyScaleChanged() {
for (OriginChangeListener l : fListeners) {
l.originScaleChanged(new EventObject(this));
}
}
/**
* Return the graphical representation of the origin.
*
* @return The graphical representation of the origin.
*/
public OriginFigure getFigure() {
return fFigure;
}
}

View File

@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.course;
import java.util.EventObject;
/**
* Interface for classes that want to be notified about origin position changes
* and origin scale changes.
*/
public interface OriginChangeListener {
/**
* Event that the position of the origin has changed.
*
* @param evt event that the position has changed.
*/
void originLocationChanged(EventObject evt);
/**
* Event that the scale of the origin has changed.
*
* @param evt event that the scale of the origin has changed.
*/
void originScaleChanged(EventObject evt);
}

View File

@@ -0,0 +1,183 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import static java.awt.image.ImageObserver.ABORT;
import static java.awt.image.ImageObserver.ALLBITS;
import static java.awt.image.ImageObserver.FRAMEBITS;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.jhotdraw.draw.AbstractAttributedDecoratedFigure;
import org.opentcs.guing.common.components.drawing.ZoomPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A Figure displaying a bitmap.
*/
public class BitmapFigure
extends
AbstractAttributedDecoratedFigure
implements
ImageObserver {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(BitmapFigure.class);
/**
* The image to be displayed.
*/
private BufferedImage image;
/**
* The enclosing rectangle.
*/
private Rectangle fDisplayBox;
/**
* The exact position of the figure's center.
*/
private ZoomPoint fZoomPoint;
/**
* Flag, if this figures has been removed from the drawing due to
* an other view is active or if it visible.
*/
private boolean temporarilyRemoved = false;
/**
* Path of the image.
*/
private String imagePath;
@SuppressWarnings("this-escape")
public BitmapFigure(File file) {
try {
image = ImageIO.read(file);
imagePath = file.getPath();
if (image == null) {
LOG.error("Couldn't open image file at" + file.getPath());
fDisplayBox = new Rectangle(0, 0, 0, 0);
fZoomPoint = new ZoomPoint(0, 0);
requestRemove();
return;
}
fDisplayBox = new Rectangle(image.getWidth(), image.getHeight());
fZoomPoint = new ZoomPoint(0.5 * image.getWidth(), 0.5 * image.getHeight());
}
catch (IOException ex) {
LOG.error("", ex);
requestRemove();
}
}
public String getImagePath() {
return imagePath;
}
public boolean isTemporarilyRemoved() {
return temporarilyRemoved;
}
public void setTemporarilyRemoved(boolean temporarilyRemoved) {
this.temporarilyRemoved = temporarilyRemoved;
}
public Rectangle displayBox() {
return new Rectangle(
fDisplayBox.x, fDisplayBox.y,
fDisplayBox.width, fDisplayBox.height
);
}
public void setDisplayBox(Rectangle displayBox) {
fDisplayBox = displayBox;
}
@Override // AbstractFigure
public void setBounds(Point2D.Double anchor, Point2D.Double lead) {
//resize
if (lead != null) {
//anchor is upper left, lead lower right
fDisplayBox.width = (int) (lead.x - anchor.x);
fDisplayBox.height = (int) (lead.y - anchor.y);
}
else {
fZoomPoint.setX(anchor.x);
fZoomPoint.setY(anchor.y);
fDisplayBox.x = (int) (anchor.x - 0.5 * fDisplayBox.width);
fDisplayBox.y = (int) (anchor.y - 0.5 * fDisplayBox.height);
}
}
@Override // ImageObserver
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
if ((infoflags & (FRAMEBITS | ALLBITS)) != 0) {
invalidate();
}
return (infoflags & (ALLBITS | ABORT)) == 0;
}
@Override
protected boolean figureContains(Point2D.Double p) {
return fDisplayBox.contains(p);
}
@Override
protected void drawFill(Graphics2D g) {
if (image != null) {
Rectangle r = displayBox();
g.drawImage(image, r.x, r.y, r.width, r.height, this);
}
}
@Override
protected void drawStroke(Graphics2D g) {
Rectangle r = displayBox();
g.drawRect(r.x, r.y, r.width - 1, r.height - 1);
}
@Override
public Rectangle2D.Double getBounds() {
Rectangle2D r2 = fDisplayBox.getBounds2D();
Rectangle2D.Double r2d = new Rectangle2D.Double();
r2d.setRect(r2);
return r2d;
}
@Override
public Object getTransformRestoreData() {
return fDisplayBox.clone();
}
@Override
public void restoreTransformTo(Object restoreData) {
Rectangle r = (Rectangle) restoreData;
fDisplayBox.x = r.x;
fDisplayBox.y = r.y;
fDisplayBox.width = r.width;
fDisplayBox.height = r.height;
fZoomPoint.setX(r.x + 0.5 * r.width);
fZoomPoint.setY(r.y + 0.5 * r.height);
}
@Override
public void transform(AffineTransform tx) {
Point2D center = fZoomPoint.getPixelLocationExactly();
setBounds((Point2D.Double) tx.transform(center, center), null);
}
public void setScaleFactor(double oldValue, double newValue) {
fDisplayBox.width = (int) (fDisplayBox.width / oldValue * newValue);
fDisplayBox.height = (int) (fDisplayBox.height / oldValue * newValue);
}
}

View File

@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import org.jhotdraw.draw.AttributeKey;
import org.opentcs.guing.base.model.ModelComponent;
import org.opentcs.guing.common.components.drawing.course.Origin;
/**
* Constants that are relevant to figures.
*/
public interface FigureConstants {
/**
* Key for figures to access their models.
*/
AttributeKey<ModelComponent> MODEL = new AttributeKey<>("Model", ModelComponent.class);
/**
* Key for figures to access the origin.
*/
AttributeKey<Origin> ORIGIN = new AttributeKey<>("Origin", Origin.class);
}

View File

@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import org.opentcs.guing.base.model.elements.LinkModel;
import org.opentcs.guing.base.model.elements.LocationModel;
import org.opentcs.guing.base.model.elements.PathModel;
import org.opentcs.guing.base.model.elements.PointModel;
/**
*/
public interface FigureFactory {
PointFigure createPointFigure(PointModel model);
LabeledPointFigure createLabeledPointFigure(PointFigure figure);
LocationFigure createLocationFigure(LocationModel model);
LabeledLocationFigure createLabeledLocationFigure(LocationFigure figure);
PathConnection createPathConnection(PathModel model);
LinkConnection createLinkConnection(LinkModel model);
OffsetFigure createOffsetFigure();
}

View File

@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
/**
* Defines fixed ordinals for some figures.
*/
public interface FigureOrdinals {
/**
* The layer ordinal to be used for the origin figure.
* Note: Be cautious with this value. The ordinal for the default layer is 0 . So a value
* of -1 for the origin figure should be enough. We can't really use e.g. Integer.MIN_VALUE as
* this leads to unexpected behavior and exceptions when moving layers in the Model Editor
* application (probably caused by FigureLayerComparator).
*/
int ORIGIN_FIGURE_ORDINAL = -1;
/**
* The layer ordinal to be used for vehicle figures.
* Note: Be cautious with this value. We want vehicles to be on the uppermost layer. We can't
* really use e.g. Integer.MAX_VALUE as this leads to unexpected behavior when showing/hiding
* layers in the Operations Desk application (probably caused by FigureLayerComparator).
*/
int VEHICLE_FIGURE_ORDINAL = 1000000;
}

View File

@@ -0,0 +1,159 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventObject;
import org.jhotdraw.draw.AttributeKey;
import org.jhotdraw.draw.DrawingView;
import org.jhotdraw.draw.Figure;
import org.jhotdraw.draw.GraphicalCompositeFigure;
import org.jhotdraw.draw.handle.BoundsOutlineHandle;
import org.jhotdraw.draw.handle.DragHandle;
import org.jhotdraw.draw.handle.Handle;
import org.jhotdraw.draw.handle.MoveHandle;
import org.jhotdraw.draw.handle.ResizeHandleKit;
import org.opentcs.guing.base.components.properties.event.AttributesChangeListener;
import org.opentcs.guing.common.components.drawing.course.OriginChangeListener;
/**
* A figure that is labeled by another figure.
*/
public abstract class LabeledFigure
extends
GraphicalCompositeFigure
implements
AttributesChangeListener,
OriginChangeListener {
/**
* The figure of the label of this labeled figure.
*/
private TCSLabelFigure fLabel;
/**
* Creates a new instance.
*/
public LabeledFigure() {
}
public void setLabel(TCSLabelFigure label) {
add(0, label); // Allow only one label for each figure
addFigureListener(label);
label.setParent(this);
fLabel = label;
}
public TCSLabelFigure getLabel() {
return fLabel;
}
/**
* Sets the visibility of the label.
*
* @param visible Indicates whether the label should be visible or not.
*/
public void setLabelVisible(boolean visible) {
fLabel.setLabelVisible(visible);
}
public abstract Shape getShape();
@Override
public TCSFigure getPresentationFigure() {
return (TCSFigure) super.getPresentationFigure();
}
@Override
public boolean handleMouseClick(Point2D.Double p, MouseEvent evt, DrawingView view) {
boolean ret = getPresentationFigure().handleMouseClick(p, evt, view);
return ret;
}
@Override
public void changed() {
super.changed();
updateModel();
}
@Override
public Collection<Handle> createHandles(int detailLevel) {
Collection<Handle> handles = new ArrayList<>();
switch (detailLevel) {
case -1: // Mouse Moved
handles.add(new BoundsOutlineHandle(getPresentationFigure(), false, true));
break;
case 0: // Mouse clicked
MoveHandle.addMoveHandles(this, handles);
for (Figure child : getChildren()) {
MoveHandle.addMoveHandles(child, handles);
handles.add(new DragHandle(child));
}
break;
case 1: // Double-Click
ResizeHandleKit.addResizeHandles(this, handles);
break;
default:
break;
}
return handles;
}
@Override
public <T> void set(AttributeKey<T> key, T newValue) {
super.set(key, newValue);
if (fLabel != null) {
fLabel.set(key, newValue);
}
}
@Override
public void setBounds(Point2D.Double anchor, Point2D.Double lead) {
basicSetPresentationFigureBounds(anchor, anchor);
if (fLabel != null) {
Point2D.Double p = getStartPoint();
p.x += fLabel.getOffset().x;
p.y += fLabel.getOffset().y;
fLabel.setBounds(p, p);
}
}
@Override
public void originLocationChanged(EventObject event) {
updateModel();
}
@Override
public void originScaleChanged(EventObject event) {
scaleModel(event);
}
public abstract void updateModel();
/**
* Scales the model coodinates accodring to changes to the layout scale.
*
* @param event The event containing the layout scale change.
*/
public abstract void scaleModel(EventObject event);
@Override
public LabeledFigure clone() {
LabeledFigure clone = (LabeledFigure) super.clone();
clone.fLabel = null;
return clone;
}
}

View File

@@ -0,0 +1,230 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import static java.util.Objects.requireNonNull;
import com.google.common.base.Strings;
import com.google.inject.assistedinject.Assisted;
import jakarta.inject.Inject;
import java.awt.Shape;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventObject;
import javax.swing.Action;
import org.jhotdraw.draw.handle.Handle;
import org.opentcs.guing.base.components.properties.event.AttributesChangeEvent;
import org.opentcs.guing.base.components.properties.type.CoordinateProperty;
import org.opentcs.guing.base.components.properties.type.StringProperty;
import org.opentcs.guing.base.model.elements.LocationModel;
import org.opentcs.guing.common.components.drawing.ZoomPoint;
import org.opentcs.guing.common.components.drawing.course.Origin;
/**
* {@link LocationFigure} with a label.
*/
public class LabeledLocationFigure
extends
LabeledFigure {
/**
* The tool tip text generator.
*/
private final ToolTipTextGenerator textGenerator;
/**
* Creates a new instance.
*
* @param figure The presentation figure.
* @param textGenerator The tool tip text generator.
*/
@Inject
@SuppressWarnings("this-escape")
public LabeledLocationFigure(
@Assisted
LocationFigure figure,
ToolTipTextGenerator textGenerator
) {
requireNonNull(figure, "figure");
this.textGenerator = requireNonNull(textGenerator, "textGenerator");
setPresentationFigure(figure);
}
@Override
public LocationFigure getPresentationFigure() {
return (LocationFigure) super.getPresentationFigure();
}
@Override
public Shape getShape() {
return getPresentationFigure().getDrawingArea();
}
@Override
public String getToolTipText(Point2D.Double p) {
return textGenerator.getToolTipText(getPresentationFigure().getModel());
}
@Override
public LabeledLocationFigure clone() {
// Do NOT clone the label here.
LabeledLocationFigure that = (LabeledLocationFigure) super.clone();
if (that.getChildCount() > 0) {
that.removeChild(0);
}
LocationFigure thatPresentationFigure = that.getPresentationFigure();
thatPresentationFigure.addFigureListener(that.eventHandler);
// Force loading of the symbol bitmap
thatPresentationFigure.propertiesChanged(null);
return that;
}
@Override
public Collection<Action> getActions(Point2D.Double p) {
return new ArrayList<>();
}
@Override
public Collection<Handle> createHandles(int detailLevel) {
if (!isVisible()) {
return new ArrayList<>();
}
return super.createHandles(detailLevel);
}
@Override
public int getLayer() {
return getPresentationFigure().getLayer();
}
@Override
public boolean isVisible() {
return getPresentationFigure().isVisible();
}
@Override
public void propertiesChanged(AttributesChangeEvent event) {
if (event.getInitiator().equals(this)) {
return;
}
// Move the figure if the model coordinates have been changed in the
// Properties panel
Origin origin = get(FigureConstants.ORIGIN);
if (origin != null) {
LocationFigure lf = getPresentationFigure();
if (lf.getModel().getPropertyLayoutPositionX().hasChanged()
|| lf.getModel().getPropertyLayoutPositionY().hasChanged()) {
getLabel().willChange();
Point2D exact
= origin.calculatePixelPositionExactly(
lf.getModel().getPropertyLayoutPositionX(),
lf.getModel().getPropertyLayoutPositionY()
);
double scale = lf.getZoomPoint().scale();
double xNew = exact.getX() / scale;
double yNew = exact.getY() / scale;
Point2D.Double anchor = new Point2D.Double(xNew, yNew);
setBounds(anchor, anchor);
getLabel().changed();
}
}
// Update the image of the actual Location type
getPresentationFigure().propertiesChanged(event);
invalidate();
// also update the label.
fireFigureChanged();
}
@Override
public void scaleModel(EventObject event) {
Origin origin = get(FigureConstants.ORIGIN);
if (origin != null) {
LocationFigure lf = getPresentationFigure();
Point2D exact
= origin.calculatePixelPositionExactly(
lf.getModel().getPropertyLayoutPositionX(),
lf.getModel().getPropertyLayoutPositionY()
);
Point2D.Double anchor = new Point2D.Double(exact.getX(), exact.getY());
setBounds(anchor, anchor);
}
invalidate();
// also update the label.
fireFigureChanged();
}
@Override
public void updateModel() {
Origin origin = get(FigureConstants.ORIGIN);
LocationFigure lf = getPresentationFigure();
LocationModel model = lf.getModel();
CoordinateProperty cpx = model.getPropertyModelPositionX();
CoordinateProperty cpy = model.getPropertyModelPositionY();
if ((double) cpx.getValue() == 0.0 && (double) cpy.getValue() == 0.0) {
origin.calculateRealPosition(lf.center(), cpx, cpy);
cpx.markChanged();
cpy.markChanged();
}
ZoomPoint zoomPoint = lf.getZoomPoint();
if (zoomPoint != null && origin != null) {
StringProperty lpx = model.getPropertyLayoutPositionX();
int oldX = 0;
if (!Strings.isNullOrEmpty(lpx.getText())) {
oldX = (int) Double.parseDouble(lpx.getText());
}
int newX = (int) (zoomPoint.getX() * origin.getScaleX());
if (newX != oldX || newX == 0) {
lpx.setText(String.format("%d", newX));
lpx.markChanged();
}
StringProperty lpy = model.getPropertyLayoutPositionY();
int oldY = 0;
if (!Strings.isNullOrEmpty(lpy.getText())) {
oldY = (int) Double.parseDouble(lpy.getText());
}
int newY = (int) (-zoomPoint.getY() * origin.getScaleY()); // Vorzeichen!
if (newY != oldY || newY == 0) {
lpy.setText(String.format("%d", newY));
lpy.markChanged();
}
// Offset of the labels of the location will be updated here.
StringProperty propOffsetX = model.getPropertyLabelOffsetX();
if (Strings.isNullOrEmpty(propOffsetX.getText())) {
propOffsetX.setText(String.format("%d", TCSLabelFigure.DEFAULT_LABEL_OFFSET_X));
}
StringProperty propOffsetY = model.getPropertyLabelOffsetY();
if (Strings.isNullOrEmpty(propOffsetY.getText())) {
propOffsetY.setText(String.format("%d", TCSLabelFigure.DEFAULT_LABEL_OFFSET_Y));
}
}
// update the type.
model.getPropertyType().markChanged();
model.propertiesChanged(this);
// also update the label.
fireFigureChanged();
}
}

View File

@@ -0,0 +1,252 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import static java.util.Objects.requireNonNull;
import com.google.common.base.Strings;
import com.google.inject.assistedinject.Assisted;
import jakarta.inject.Inject;
import java.awt.Shape;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventObject;
import javax.swing.Action;
import org.jhotdraw.draw.ConnectionFigure;
import org.jhotdraw.draw.Figure;
import org.jhotdraw.draw.connector.ChopEllipseConnector;
import org.jhotdraw.draw.connector.Connector;
import org.jhotdraw.draw.handle.DragHandle;
import org.jhotdraw.draw.handle.Handle;
import org.jhotdraw.draw.handle.MoveHandle;
import org.jhotdraw.draw.handle.ResizeHandleKit;
import org.opentcs.guing.base.components.properties.event.AttributesChangeEvent;
import org.opentcs.guing.base.components.properties.type.CoordinateProperty;
import org.opentcs.guing.base.components.properties.type.StringProperty;
import org.opentcs.guing.base.model.elements.PointModel;
import org.opentcs.guing.common.components.drawing.ZoomPoint;
import org.opentcs.guing.common.components.drawing.course.Origin;
import org.opentcs.guing.common.components.drawing.figures.decoration.PointOutlineHandle;
/**
* {@link PointFigure} with a label.
*/
public class LabeledPointFigure
extends
LabeledFigure {
/**
* The tool tip text generator.
*/
private final ToolTipTextGenerator textGenerator;
/**
* Creates a new instance.
*
* @param figure The presentation figure.
* @param textGenerator The tool tip text generator.
*/
@Inject
@SuppressWarnings("this-escape")
public LabeledPointFigure(
@Assisted
PointFigure figure,
ToolTipTextGenerator textGenerator
) {
requireNonNull(figure, "figure");
this.textGenerator = requireNonNull(textGenerator, "textGenerator");
setPresentationFigure(figure);
}
@Override
public PointFigure getPresentationFigure() {
return (PointFigure) super.getPresentationFigure();
}
@Override
public Shape getShape() {
return getPresentationFigure().getShape();
}
@Override
public Connector findConnector(Point2D.Double p, ConnectionFigure prototype) {
return new ChopEllipseConnector(this);
}
@Override
public String getToolTipText(Point2D.Double p) {
return textGenerator.getToolTipText(getPresentationFigure().getModel());
}
@Override
public LabeledPointFigure clone() {
// Do NOT clone the label here.
LabeledPointFigure that = (LabeledPointFigure) super.clone();
if (that.getChildCount() > 0) {
that.basicRemoveAllChildren();
}
return that;
}
@Override
public Collection<Action> getActions(Point2D.Double p) {
return new ArrayList<>();
}
@Override
public int getLayer() {
return getPresentationFigure().getLayer();
}
@Override
public boolean isVisible() {
return getPresentationFigure().isVisible();
}
@Override
public void propertiesChanged(AttributesChangeEvent event) {
if (event.getInitiator().equals(this)) {
return;
}
// Move the figure if the model coordinates have been changed in the
// Properties panel
Origin origin = get(FigureConstants.ORIGIN);
if (origin != null) {
PointFigure pf = getPresentationFigure();
StringProperty xLayout = pf.getModel().getPropertyLayoutPosX();
StringProperty yLayout = pf.getModel().getPropertyLayoutPosY();
if (xLayout.hasChanged() || yLayout.hasChanged()) {
getLabel().willChange();
Point2D exact = origin.calculatePixelPositionExactly(xLayout, yLayout);
double scale = pf.getZoomPoint().scale();
double xNew = exact.getX() / scale;
double yNew = exact.getY() / scale;
Point2D.Double anchor = new Point2D.Double(xNew, yNew);
setBounds(anchor, anchor);
getLabel().changed();
}
}
invalidate();
fireFigureChanged();
}
@Override
public void scaleModel(EventObject event) {
Origin origin = get(FigureConstants.ORIGIN);
if (origin != null) {
PointFigure pf = getPresentationFigure();
Point2D exact = origin.calculatePixelPositionExactly(
pf.getModel().getPropertyLayoutPosX(),
pf.getModel().getPropertyLayoutPosY()
);
Point2D.Double anchor = new Point2D.Double(exact.getX(), exact.getY());
setBounds(anchor, anchor);
}
invalidate();
fireFigureChanged();
}
@Override
public void updateModel() {
Origin origin = get(FigureConstants.ORIGIN);
PointFigure pf = getPresentationFigure();
PointModel model = pf.getModel();
CoordinateProperty cpx = model.getPropertyModelPositionX();
CoordinateProperty cpy = model.getPropertyModelPositionY();
// Write current model position to properties once when creating the layout.
if ((double) cpx.getValue() == 0.0 && (double) cpy.getValue() == 0.0) {
origin.calculateRealPosition(pf.center(), cpx, cpy);
cpx.markChanged();
cpy.markChanged();
}
ZoomPoint zoomPoint = pf.getZoomPoint();
if (zoomPoint != null && origin != null) {
StringProperty lpx = model.getPropertyLayoutPosX();
int oldX = 0;
if (!Strings.isNullOrEmpty(lpx.getText())) {
oldX = (int) Double.parseDouble(lpx.getText());
}
int newX = (int) (zoomPoint.getX() * origin.getScaleX());
if (newX != oldX) {
lpx.setText(String.format("%d", newX));
lpx.markChanged();
}
StringProperty lpy = model.getPropertyLayoutPosY();
int oldY = 0;
if (!Strings.isNullOrEmpty(lpy.getText())) {
oldY = (int) Double.parseDouble(lpy.getText());
}
int newY = (int) (-zoomPoint.getY() * origin.getScaleY()); // Vorzeichen!
if (newY != oldY) {
lpy.setText(String.format("%d", newY));
lpy.markChanged();
}
// Offset of the labels of the location will be updated here.
StringProperty propOffsetX = model.getPropertyPointLabelOffsetX();
if (Strings.isNullOrEmpty(propOffsetX.getText())) {
propOffsetX.setText(String.format("%d", TCSLabelFigure.DEFAULT_LABEL_OFFSET_X));
}
StringProperty propOffsetY = model.getPropertyPointLabelOffsetY();
if (Strings.isNullOrEmpty(propOffsetY.getText())) {
propOffsetY.setText(String.format("%d", TCSLabelFigure.DEFAULT_LABEL_OFFSET_Y));
}
}
model.getPropertyType().markChanged();
model.propertiesChanged(this);
fireFigureChanged();
}
@Override
public Collection<Handle> createHandles(int detailLevel) {
Collection<Handle> handles = new ArrayList<>();
if (!isVisible()) {
return handles;
}
switch (detailLevel) {
case -1: // Mouse Moved
handles.add(new PointOutlineHandle(getPresentationFigure()));
break;
case 0: // Mouse clicked
MoveHandle.addMoveHandles(this, handles);
for (Figure child : getChildren()) {
MoveHandle.addMoveHandles(child, handles);
handles.add(new DragHandle(child));
}
break;
case 1: // Double-Click
ResizeHandleKit.addResizeHandles(this, handles);
break;
default:
break;
}
return handles;
}
}

View File

@@ -0,0 +1,161 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import static java.util.Objects.requireNonNull;
import com.google.inject.assistedinject.Assisted;
import jakarta.inject.Inject;
import java.awt.BasicStroke;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventObject;
import org.jhotdraw.draw.AttributeKeys;
import org.jhotdraw.draw.connector.ChopEllipseConnector;
import org.jhotdraw.draw.connector.Connector;
import org.jhotdraw.draw.handle.Handle;
import org.opentcs.guing.base.model.ModelComponent;
import org.opentcs.guing.base.model.elements.LinkModel;
import org.opentcs.guing.base.model.elements.LocationModel;
import org.opentcs.guing.base.model.elements.PointModel;
/**
* A dashed line that connects a decision point with a location.
*/
public class LinkConnection
extends
SimpleLineConnection {
/**
* The tool tip text generator.
*/
private final ToolTipTextGenerator textGenerator;
/**
* Creates a new instance.
*
* @param model The model corresponding to this graphical object.
* @param textGenerator The tool tip text generator.
*/
@Inject
@SuppressWarnings("this-escape")
public LinkConnection(
@Assisted
LinkModel model,
ToolTipTextGenerator textGenerator
) {
super(model);
this.textGenerator = requireNonNull(textGenerator, "textGenerator");
double[] dash = {5.0, 5.0};
set(AttributeKeys.START_DECORATION, null);
set(AttributeKeys.END_DECORATION, null);
set(AttributeKeys.STROKE_WIDTH, 1.0);
set(AttributeKeys.STROKE_CAP, BasicStroke.CAP_BUTT);
set(AttributeKeys.STROKE_JOIN, BasicStroke.JOIN_MITER);
set(AttributeKeys.STROKE_MITER_LIMIT, 1.0);
set(AttributeKeys.STROKE_DASHES, dash);
set(AttributeKeys.STROKE_DASH_PHASE, 0.0);
}
@Override
public LinkModel getModel() {
return (LinkModel) get(FigureConstants.MODEL);
}
/**
* Connects two figures.
*
* @param point The point figure to connect.
* @param location The location figure to connect.
*/
public void connect(LabeledPointFigure point, LabeledLocationFigure location) {
Connector compConnector = new ChopEllipseConnector();
Connector startConnector = point.findCompatibleConnector(compConnector, true);
Connector endConnector = location.findCompatibleConnector(compConnector, true);
if (!canConnect(startConnector, endConnector)) {
return;
}
setStartConnector(startConnector);
setEndConnector(endConnector);
}
@Override // AbstractFigure
public String getToolTipText(Point2D.Double p) {
return textGenerator.getToolTipText(getModel());
}
@Override
public boolean canConnect(Connector start) {
return start.getOwner().get(FigureConstants.MODEL) instanceof LocationModel;
}
@Override // SimpleLineConnection
public boolean canConnect(Connector start, Connector end) {
ModelComponent modelStart = start.getOwner().get(FigureConstants.MODEL);
ModelComponent modelEnd = end.getOwner().get(FigureConstants.MODEL);
if (modelStart == null || modelEnd == null) {
return false;
}
// Even though new links can now only be created starting from a location, we need this to
// ensure backward campatibility for older models that may still have links with a point
// as the start component. Otherwise those links would not be connected/drawn properly.
if ((modelStart instanceof PointModel) && modelEnd instanceof LocationModel) {
LocationModel location = (LocationModel) modelEnd;
PointModel point = (PointModel) modelStart;
return !location.hasConnectionTo(point);
}
if (modelStart instanceof LocationModel && (modelEnd instanceof PointModel)) {
LocationModel location = (LocationModel) modelStart;
PointModel point = (PointModel) modelEnd;
return !point.hasConnectionTo(location);
}
return false;
}
@Override
public Collection<Handle> createHandles(int detailLevel) {
if (!isVisible()) {
return new ArrayList<>();
}
return super.createHandles(detailLevel);
}
@Override
public int getLayer() {
return getModel().getPropertyLayerWrapper().getValue().getLayer().getOrdinal();
}
@Override
public boolean isVisible() {
return super.isVisible()
&& getModel().getPropertyLayerWrapper().getValue().getLayer().isVisible()
&& getModel().getPropertyLayerWrapper().getValue().getLayerGroup().isVisible();
}
@Override
public void updateModel() {
}
@Override
public void scaleModel(EventObject event) {
}
@Override
public LinkConnection clone() {
LinkConnection clone = (LinkConnection) super.clone();
clone.initConnectionFigure();
return clone;
}
}

View File

@@ -0,0 +1,326 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import static java.util.Objects.requireNonNull;
import static org.opentcs.guing.base.AllocationState.ALLOCATED;
import static org.opentcs.guing.base.AllocationState.CLAIMED;
import com.google.inject.assistedinject.Assisted;
import jakarta.inject.Inject;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.ImageObserver;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.jhotdraw.draw.AttributeKeys;
import org.jhotdraw.draw.ConnectionFigure;
import org.jhotdraw.draw.connector.ChopEllipseConnector;
import org.jhotdraw.draw.connector.Connector;
import org.jhotdraw.geom.Geom;
import org.opentcs.components.plantoverview.LocationTheme;
import org.opentcs.data.model.TCSResourceReference;
import org.opentcs.data.model.visualization.LocationRepresentation;
import org.opentcs.guing.base.AllocationState;
import org.opentcs.guing.base.components.properties.event.AttributesChangeEvent;
import org.opentcs.guing.base.components.properties.type.SymbolProperty;
import org.opentcs.guing.base.model.elements.BlockModel;
import org.opentcs.guing.base.model.elements.LocationModel;
import org.opentcs.guing.base.model.elements.LocationTypeModel;
import org.opentcs.guing.base.model.elements.VehicleModel;
import org.opentcs.guing.common.components.drawing.DrawingOptions;
import org.opentcs.guing.common.components.drawing.Strokes;
import org.opentcs.guing.common.components.drawing.ZoomPoint;
/**
* A figure for locations.
*/
public class LocationFigure
extends
TCSFigure
implements
ImageObserver {
/**
* The fill color for locked locations.
*/
private static final Color LOCKED_COLOR = new Color(255, 50, 50);
/**
* The image representing the location.
*/
private transient Image fImage;
private int fWidth;
private int fHeight;
private final LocationTheme locationTheme;
private final DrawingOptions drawingOptions;
/**
* Creates a new instance.
*
* @param locationTheme The location theme to be used.
* @param model The model corresponding to this graphical object.
* @param drawingOptions The drawing options.
*/
@Inject
public LocationFigure(
LocationTheme locationTheme,
@Assisted
LocationModel model,
DrawingOptions drawingOptions
) {
super(model);
this.locationTheme = requireNonNull(locationTheme, "locationTheme");
this.drawingOptions = requireNonNull(drawingOptions, "drawingOptions");
fWidth = 30;
fHeight = 30;
fDisplayBox = new Rectangle(fWidth, fHeight);
fZoomPoint = new ZoomPoint(0.5 * fWidth, 0.5 * fHeight);
}
@Override
public LocationModel getModel() {
return (LocationModel) get(FigureConstants.MODEL);
}
public Point center() {
return Geom.center(fDisplayBox);
}
@Override // Figure
public Rectangle2D.Double getBounds() {
Rectangle2D r2 = fDisplayBox.getBounds2D();
Rectangle2D.Double r2d = new Rectangle2D.Double();
r2d.setRect(r2);
return r2d;
}
@Override // Figure
public Object getTransformRestoreData() {
return fDisplayBox.clone();
}
@Override // Figure
public void restoreTransformTo(Object restoreData) {
Rectangle r = (Rectangle) restoreData;
fDisplayBox.x = r.x;
fDisplayBox.y = r.y;
fDisplayBox.width = r.width;
fDisplayBox.height = r.height;
fZoomPoint.setX(r.x + 0.5 * r.width);
fZoomPoint.setY(r.y + 0.5 * r.height);
}
@Override // Figure
public void transform(AffineTransform tx) {
Point2D center = getZoomPoint().getPixelLocationExactly();
setBounds((Point2D.Double) tx.transform(center, center), null);
}
@Override // AbstractFigure
public void setBounds(Point2D.Double anchor, Point2D.Double lead) {
fZoomPoint.setX(anchor.x);
fZoomPoint.setY(anchor.y);
fDisplayBox.x = (int) (anchor.x - 0.5 * fDisplayBox.width);
fDisplayBox.y = (int) (anchor.y - 0.5 * fDisplayBox.height);
}
@Override // AbstractFigure
public Connector findConnector(Point2D.Double p, ConnectionFigure prototype) {
return new ChopEllipseConnector(this);
}
@Override // AbstractFigure
public Connector findCompatibleConnector(Connector c, boolean isStartConnector) {
return new ChopEllipseConnector(this);
}
@Override
public int getLayer() {
return getModel().getPropertyLayerWrapper().getValue().getLayer().getOrdinal();
}
@Override
public boolean isVisible() {
return super.isVisible()
&& getModel().getPropertyLayerWrapper().getValue().getLayer().isVisible()
&& getModel().getPropertyLayerWrapper().getValue().getLayerGroup().isVisible();
}
@Override
protected void drawFigure(Graphics2D g) {
if (drawingOptions.isBlocksVisible()) {
drawBlockDecoration(g);
}
drawRouteDecoration(g);
super.drawFigure(g);
}
private void drawRouteDecoration(Graphics2D g) {
for (Map.Entry<VehicleModel, AllocationState> entry : getModel().getAllocationStates()
.entrySet()) {
VehicleModel vehicleModel = entry.getKey();
switch (entry.getValue()) {
case CLAIMED:
drawDecoration(
g,
Strokes.PATH_ON_ROUTE,
transparentColor(vehicleModel.getDriveOrderColor(), 70)
);
break;
case ALLOCATED:
drawDecoration(g, Strokes.PATH_ON_ROUTE, vehicleModel.getDriveOrderColor());
break;
case ALLOCATED_WITHDRAWN:
drawDecoration(g, Strokes.PATH_ON_WITHDRAWN_ROUTE, Color.GRAY);
break;
default:
// Don't draw any decoration.
}
}
}
private void drawBlockDecoration(Graphics2D g) {
for (BlockModel blockModel : getModel().getBlockModels()) {
drawDecoration(g, Strokes.BLOCK_ELEMENT, transparentColor(blockModel.getColor(), 192));
}
}
private Color transparentColor(Color color, int alpha) {
return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
}
private void drawDecoration(Graphics2D g, Stroke stroke, Color color) {
g.setStroke(stroke);
g.setColor(color);
g.draw(this.getDrawingArea());
}
@Override // AbstractAttributedFigure
protected void drawFill(Graphics2D g) {
int dx;
int dy;
Rectangle r = displayBox();
g.fillRect(r.x, r.y, r.width, r.height);
if (fImage != null) {
dx = (r.width - fImage.getWidth(this)) / 2;
dy = (r.height - fImage.getHeight(this)) / 2;
g.drawImage(fImage, r.x + dx, r.y + dy, this);
}
}
@Override // AbstractAttributedFigure
protected void drawStroke(Graphics2D g) {
Rectangle r = displayBox();
g.drawRect(r.x, r.y, r.width - 1, r.height - 1);
}
@Override
public LocationFigure clone() {
LocationFigure thatFigure = (LocationFigure) super.clone();
thatFigure.setZoomPoint(new ZoomPoint(fZoomPoint.getX(), fZoomPoint.getY()));
return thatFigure;
}
@Override // ImageObserver
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
if ((infoflags & (FRAMEBITS | ALLBITS)) != 0) {
invalidate();
}
return (infoflags & (ALLBITS | ABORT)) == 0;
}
public void propertiesChanged(AttributesChangeEvent e) {
handleLocationTypeChanged();
handleLocationLockChanged();
}
private void handleLocationTypeChanged() {
LocationTypeModel locationType = getModel().getLocationType();
if (locationType == null) {
return;
}
if (getModel().getLocation() != null && locationType.getLocationType() != null) {
fImage = locationTheme.getImageFor(getModel().getLocation(), locationType.getLocationType());
}
else {
SymbolProperty pSymbol = getModel().getPropertyDefaultRepresentation();
LocationRepresentation locationRepresentation = pSymbol.getLocationRepresentation();
if (locationRepresentation == null
|| locationRepresentation == LocationRepresentation.DEFAULT) {
pSymbol = locationType.getPropertyDefaultRepresentation();
locationRepresentation = pSymbol.getLocationRepresentation();
fImage = locationTheme.getImageFor(locationRepresentation);
}
else {
fImage = locationTheme.getImageFor(locationRepresentation);
}
}
fWidth = Math.max(fImage.getWidth(this) + 10, 30);
fHeight = Math.max(fImage.getHeight(this) + 10, 30);
fDisplayBox.setSize(fWidth, fHeight);
}
private void handleLocationLockChanged() {
set(
AttributeKeys.FILL_COLOR,
(Boolean) getModel().getPropertyLocked().getValue() ? LOCKED_COLOR : Color.WHITE
);
}
private List<Set<TCSResourceReference<?>>> getCurrentDriveOrderClaim(VehicleModel vehicle) {
List<Set<TCSResourceReference<?>>> result = new ArrayList<>();
boolean driveOrderEndFound = false;
for (Set<TCSResourceReference<?>> res : vehicle.getClaimedResources().getItems()) {
result.add(res);
if (containsDriveOrderDestination(res, vehicle)) {
driveOrderEndFound = true;
break;
}
}
if (driveOrderEndFound) {
return result;
}
else {
// With the end of the drive order not found, there is nothing from the current drive order in
// the claimed resources.
return List.of();
}
}
private boolean containsDriveOrderDestination(
Set<TCSResourceReference<?>> resources,
VehicleModel vehicle
) {
if (vehicle.getDriveOrderDestination() == null) {
return false;
}
return resources.stream()
.anyMatch(
resource -> Objects.equals(
resource.getName(),
vehicle.getDriveOrderDestination().getName()
)
);
}
}

View File

@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import org.jhotdraw.draw.Figure;
import org.opentcs.guing.base.model.DrawnModelComponent;
import org.opentcs.guing.base.model.ModelComponent;
/**
* A figure that is based on/is a graphical representation for a {@link ModelComponent}.
*/
public interface ModelBasedFigure
extends
Figure {
/**
* Returns the model component for this figure.
*
* @return The model component for this figure.
*/
DrawnModelComponent getModel();
}

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import org.jhotdraw.draw.AttributeKeys;
import org.opentcs.guing.common.components.drawing.course.Origin;
/**
* An OffsetFigure is an (invisible) figure that moves as the user drags the view
* beyond its current bounds, so the view becomes larger resp is repainted
* larger.
*/
public class OffsetFigure
extends
OriginFigure {
@SuppressWarnings("this-escape")
public OffsetFigure() {
super();
setModel(new Origin()); // The figure needs a model to work
set(AttributeKeys.STROKE_COLOR, Color.darkGray);
setVisible(false); // only visible for test
}
@Override
protected void drawStroke(Graphics2D g) {
// Shape: "Crosshair" with square
Rectangle r = (Rectangle) fDisplayBox.clone();
if (r.width > 0 && r.height > 0) {
g.drawLine(r.x + r.width / 2, r.y, r.x + r.width / 2, r.y + r.height);
g.drawLine(r.x, r.y + r.height / 2, r.x + r.width, r.y + r.height / 2);
r.grow(-4, -4);
g.drawRect(r.x, r.y, r.width, r.height);
}
}
}

View File

@@ -0,0 +1,179 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import org.jhotdraw.draw.AbstractAttributedFigure;
import org.jhotdraw.draw.AttributeKeys;
import org.jhotdraw.draw.handle.Handle;
import org.jhotdraw.geom.Geom;
import org.opentcs.guing.common.components.drawing.ZoomPoint;
import org.opentcs.guing.common.components.drawing.course.Origin;
/**
* A Figure for the coordinate system's origin.
*/
public class OriginFigure
extends
AbstractAttributedFigure {
/**
* The enclosing rectangle.
*/
protected final Rectangle fDisplayBox;
/**
* The width and height.
*/
private final int fSideLength;
/**
* The origin's model.
*/
private Origin fModel;
/**
* The exact position of the figure.
*/
private final ZoomPoint fZoomPoint;
/**
* Creates a new instance.
*/
@SuppressWarnings("this-escape")
public OriginFigure() {
super();
fSideLength = 20;
fZoomPoint = new ZoomPoint(0.0, 0.0);
fDisplayBox = new Rectangle(
-fSideLength / 2, -fSideLength / 2,
fSideLength, fSideLength
);
set(AttributeKeys.STROKE_COLOR, Color.blue);
setSelectable(false);
}
/**
* Set's the origin's model.
*
* @param model The model.
*/
public void setModel(Origin model) {
fModel = model;
getModel().setPosition(Geom.center(fDisplayBox));
}
/**
* Returns the origin's model.
*
* @return The model.
*/
public Origin getModel() {
return fModel;
}
/**
* Returns the exact position of the origin.
*
* @return The exact position of the origin.
*/
public ZoomPoint getZoomPoint() {
return fZoomPoint;
}
@Override
public Rectangle2D.Double getBounds() {
Rectangle2D.Double r2d = new Rectangle2D.Double();
r2d.setRect(fDisplayBox.getBounds2D());
return r2d;
}
@Override
public boolean contains(Point2D.Double p) {
Rectangle r = (Rectangle) fDisplayBox.clone();
double grow = AttributeKeys.getPerpendicularHitGrowth(this);
r.x = (int) (r.x - grow);
r.y = (int) (r.y - grow);
r.width = (int) (r.width + grow * 2);
r.height = (int) (r.height + grow * 2);
return r.contains(p);
}
@Override
public Object getTransformRestoreData() {
return fDisplayBox.clone();
}
@Override
public void restoreTransformTo(Object restoreData) {
Rectangle r = (Rectangle) restoreData;
fDisplayBox.x = r.x;
fDisplayBox.y = r.y;
fDisplayBox.width = r.width;
fDisplayBox.height = r.height;
fZoomPoint.setX(r.x + 0.5 * r.width);
fZoomPoint.setY(r.y + 0.5 * r.height);
}
@Override
public void transform(AffineTransform tx) {
Point2D center = getZoomPoint().getPixelLocationExactly();
Point2D lead = new Point2D.Double(); // not used
setBounds(
(Point2D.Double) tx.transform(center, center),
(Point2D.Double) tx.transform(lead, lead)
);
}
@Override
public void changed() {
super.changed();
getModel().setPosition(Geom.center(fDisplayBox));
getModel().notifyLocationChanged();
}
@Override
public void setBounds(Point2D.Double anchor, Point2D.Double lead) {
// Only change the position here, NOT the size!
// Draw the center of the figure at the mouse cursor's position.
fZoomPoint.setX(anchor.x);
fZoomPoint.setY(anchor.y);
fDisplayBox.x = (int) (anchor.x - 0.5 * fSideLength);
fDisplayBox.y = (int) (anchor.y - 0.5 * fSideLength);
}
@Override
public Collection<Handle> createHandles(int detailLevel) {
// No handles for the origin figure.
return new ArrayList<>();
}
@Override
protected void drawFill(Graphics2D g) {
// No filling for the origin's figure.
}
@Override
protected void drawStroke(Graphics2D g) {
// Outline: "Crosshair" with circle
Rectangle r = (Rectangle) fDisplayBox.clone();
if (r.width > 0 && r.height > 0) {
g.drawLine(r.x + r.width / 2, r.y, r.x + r.width / 2, r.y + r.height);
g.drawLine(r.x, r.y + r.height / 2, r.x + r.width, r.y + r.height / 2);
r.grow(-4, -4);
g.drawOval(r.x, r.y, r.width, r.height);
}
}
@Override
public int getLayer() {
return FigureOrdinals.ORIGIN_FIGURE_ORDINAL;
}
}

View File

@@ -0,0 +1,945 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import static java.util.Objects.requireNonNull;
import com.google.inject.assistedinject.Assisted;
import jakarta.inject.Inject;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Point2D.Double;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventObject;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
import org.jhotdraw.draw.AttributeKey;
import org.jhotdraw.draw.AttributeKeys;
import org.jhotdraw.draw.DrawingView;
import org.jhotdraw.draw.connector.ChopEllipseConnector;
import org.jhotdraw.draw.connector.Connector;
import org.jhotdraw.draw.handle.BezierOutlineHandle;
import org.jhotdraw.draw.handle.Handle;
import org.jhotdraw.draw.liner.ElbowLiner;
import org.jhotdraw.draw.liner.Liner;
import org.jhotdraw.draw.liner.SlantedLiner;
import org.jhotdraw.geom.BezierPath;
import org.opentcs.data.model.TCSResourceReference;
import org.opentcs.guing.base.AllocationState;
import org.opentcs.guing.base.components.properties.event.AttributesChangeEvent;
import org.opentcs.guing.base.components.properties.type.AbstractProperty;
import org.opentcs.guing.base.components.properties.type.LengthProperty;
import org.opentcs.guing.base.components.properties.type.SpeedProperty;
import org.opentcs.guing.base.components.properties.type.StringProperty;
import org.opentcs.guing.base.model.ModelComponent;
import org.opentcs.guing.base.model.elements.BlockModel;
import org.opentcs.guing.base.model.elements.PathModel;
import org.opentcs.guing.base.model.elements.PointModel;
import org.opentcs.guing.base.model.elements.VehicleModel;
import org.opentcs.guing.common.components.drawing.DrawingOptions;
import org.opentcs.guing.common.components.drawing.Strokes;
import org.opentcs.guing.common.components.drawing.course.Origin;
import org.opentcs.guing.common.components.drawing.figures.liner.BezierLinerControlPointHandle;
import org.opentcs.guing.common.components.drawing.figures.liner.PolyPathLiner;
import org.opentcs.guing.common.components.drawing.figures.liner.TripleBezierLiner;
import org.opentcs.guing.common.components.drawing.figures.liner.TupelBezierLiner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A connection between two points.
*/
public class PathConnection
extends
SimpleLineConnection {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(PathConnection.class);
/**
* The dash pattern for locked paths.
*/
private static final double[] LOCKED_DASH = {6.0, 4.0};
/**
* The dash pattern for unlocked paths.
*/
private static final double[] UNLOCKED_DASH = {10.0, 0.0};
/**
* The tool tip text generator.
*/
private final ToolTipTextGenerator textGenerator;
/**
* The drawing options.
*/
private final DrawingOptions drawingOptions;
/**
* Control point 1.
*/
private Point2D.Double cp1;
/**
* Control point 2.
*/
private Point2D.Double cp2;
/**
* Control point 3.
*/
private Point2D.Double cp3;
/**
* Control point 4.
*/
private Point2D.Double cp4;
/**
* Control point 5.
*/
private Point2D.Double cp5;
private Origin previousOrigin;
/**
* Creates a new instance.
*
* @param model The model corresponding to this graphical object.
* @param textGenerator The tool tip text generator.
* @param drawingOptions The drawing options.
*/
@Inject
@SuppressWarnings("this-escape")
public PathConnection(
@Assisted
PathModel model,
ToolTipTextGenerator textGenerator,
DrawingOptions drawingOptions
) {
super(model);
this.textGenerator = requireNonNull(textGenerator, "textGenerator");
this.drawingOptions = requireNonNull(drawingOptions, "drawingOptions");
resetPath();
}
@Override
public PathModel getModel() {
return (PathModel) get(FigureConstants.MODEL);
}
@Override
public void updateConnection() {
super.updateConnection();
initializePreviousOrigin();
updateControlPoints();
}
/**
* Resets control points and connects start and end point with a straight line.
*/
private void resetPath() {
Point2D.Double sp = path.get(0, BezierPath.C0_MASK);
Point2D.Double ep = path.get(path.size() - 1, BezierPath.C0_MASK);
path.clear();
path.add(new BezierPath.Node(sp));
path.add(new BezierPath.Node(ep));
cp1 = null;
cp2 = null;
cp3 = null;
cp4 = null;
cp5 = null;
getModel().getPropertyPathControlPoints().markChanged();
}
/**
* Initialise the control points when converting into BEZIER curve.
*
* @param type the type of the curve
*/
private void initControlPoints(PathModel.Type type) {
Point2D.Double sp = path.get(0, BezierPath.C0_MASK);
Point2D.Double ep = path.get(path.size() - 1, BezierPath.C0_MASK);
if (sp.x != ep.x || sp.y != ep.y) {
path.clear();
if (type == PathModel.Type.BEZIER_3) { //BEZIER curve with 3 control points);
//Add the scaled vector between start and endpoint to the startpoint
cp1 = new Point2D.Double(sp.x + (ep.x - sp.x) * 1 / 6, sp.y + (ep.y - sp.y) * 1 / 6);
cp2 = new Point2D.Double(sp.x + (ep.x - sp.x) * 2 / 6, sp.y + (ep.y - sp.y) * 2 / 6);
cp3 = new Point2D.Double(sp.x + (ep.x - sp.x) * 3 / 6, sp.y + (ep.y - sp.y) * 3 / 6);
cp4 = new Point2D.Double(sp.x + (ep.x - sp.x) * 4 / 6, sp.y + (ep.y - sp.y) * 4 / 6);
cp5 = new Point2D.Double(sp.x + (ep.x - sp.x) * 5 / 6, sp.y + (ep.y - sp.y) * 5 / 6);
path.add(
new BezierPath.Node(
BezierPath.C2_MASK,
sp.x, sp.y, //Current point
sp.x, sp.y, //Previous point - not in use because of C2_MASK
cp1.x, cp1.y
)
); //Next point
//Use cp1 and cp2 to draw between sp and cp3
path.add(
new BezierPath.Node(
BezierPath.C1C2_MASK,
cp3.x, cp3.y, //Current point
cp2.x, cp2.y, //Previous point
cp4.x, cp4.y
)
); //Next point
//Use cp4 and cp5 to draw between cp3 and ep
path.add(
new BezierPath.Node(
BezierPath.C1_MASK,
ep.x, ep.y, //Current point
cp5.x, cp5.y, //Previous point
ep.x, ep.y
)
); //Next point - not in use because of C1_MASK
}
else {
cp1 = new Point2D.Double(sp.x + (ep.x - sp.x) / 3, sp.y + (ep.y - sp.y) / 3); //point at 1/3
cp2 = new Point2D.Double(ep.x - (ep.x - sp.x) / 3, ep.y - (ep.y - sp.y) / 3); //point at 2/3
cp3 = null;
cp4 = null;
cp5 = null;
path.add(
new BezierPath.Node(
BezierPath.C2_MASK,
sp.x, sp.y, //Current point
sp.x, sp.y, //Previous point - not in use because of C2_MASK
cp1.x, cp1.y
)
); //Next point
path.add(
new BezierPath.Node(
BezierPath.C1_MASK,
ep.x, ep.y, //Current point
cp2.x, cp2.y, //Previous point
ep.x, ep.y
)
); //Next point - not in use because of C1_MASK
}
getModel().getPropertyPathControlPoints().markChanged();
path.invalidatePath();
}
}
/**
* Add control points.
*
* @param cp1 First control point.
* @param cp2 Identical with cp1 for quadratic curves.
*/
public void addControlPoints(Point2D.Double cp1, Point2D.Double cp2) {
this.cp1 = cp1;
this.cp2 = cp2;
this.cp3 = null;
Point2D.Double sp = path.get(0, BezierPath.C0_MASK);
Point2D.Double ep = path.get(1, BezierPath.C0_MASK);
path.clear();
path.add(
new BezierPath.Node(
BezierPath.C2_MASK,
sp.x, sp.y, //Current point
sp.x, sp.y, //Previous point
cp1.x, cp1.y
)
); //Next point
path.add(
new BezierPath.Node(
BezierPath.C1_MASK,
ep.x, ep.y, //Current point
cp2.x, cp2.y, //Previous point
ep.x, ep.y
)
); //Next point
}
/**
* A bezier curve with three control points.
*
* @param cp1 Control point 1
* @param cp2 Control point 2
* @param cp3 Control point 3
* @param cp4 Control point 4
* @param cp5 Control point 5
*/
public void addControlPoints(
Point2D.Double cp1,
Point2D.Double cp2,
Point2D.Double cp3,
Point2D.Double cp4,
Point2D.Double cp5
) {
this.cp1 = cp1;
this.cp2 = cp2;
this.cp3 = cp3;
this.cp4 = cp4;
this.cp5 = cp5;
Point2D.Double sp = path.get(0, BezierPath.C0_MASK);
Point2D.Double ep = path.get(path.size() - 1, BezierPath.C0_MASK);
path.clear();
path.add(
new BezierPath.Node(
BezierPath.C2_MASK,
sp.x, sp.y, //Current point
sp.x, sp.y, //Previous point
cp1.x, cp1.y
)
); //Next point
//Use cp1 and cp2 to draw between sp and cp3
path.add(
new BezierPath.Node(
BezierPath.C1C2_MASK,
cp3.x, cp3.y, //Current point
cp2.x, cp2.y, //Previous point
cp4.x, cp4.y
)
); //Next point
//Use cp4 and cp5 to draw between cp3 and ep
path.add(
new BezierPath.Node(
BezierPath.C1_MASK,
ep.x, ep.y, //Current point
cp5.x, cp5.y, //Previous point
cp4.x, cp4.y
)
); //Next point
StringProperty sProp = getModel().getPropertyPathControlPoints();
sProp.setText(
String.format(
"%d,%d;%d,%d;%d,%d;%d,%d;%d,%d;",
(int) cp1.x, (int) cp1.y,
(int) cp2.x, (int) cp2.y,
(int) cp3.x, (int) cp3.y,
(int) cp4.x, (int) cp4.y,
(int) cp5.x, (int) cp5.y
)
);
sProp.markChanged();
getModel().propertiesChanged(this);
}
public Point2D.Double getCp1() {
return cp1;
}
public Point2D.Double getCp2() {
return cp2;
}
public Point2D.Double getCp3() {
return cp3;
}
public Point2D.Double getCp4() {
return cp4;
}
public Point2D.Double getCp5() {
return cp5;
}
@Override
public Point2D.Double getCenter() {
// Computes the center of the curve.
// Approximation: Center of the control points.
Point2D.Double p1;
Point2D.Double p2;
Point2D.Double pc;
p1 = (cp1 == null ? path.get(0, BezierPath.C0_MASK) : cp1);
p2 = (cp2 == null ? path.get(1, BezierPath.C0_MASK) : cp2);
if (cp3 == null) {
pc = new Point2D.Double((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}
else {
//Use cp3 for 3-bezier as center because the curve goes through it at 50%
pc = (cp3 == null ? path.get(3, BezierPath.C0_MASK) : cp3);
}
return pc;
}
@Override
public int getLayer() {
return getModel().getPropertyLayerWrapper().getValue().getLayer().getOrdinal();
}
@Override
public boolean isVisible() {
return super.isVisible()
&& getModel().getPropertyLayerWrapper().getValue().getLayer().isVisible()
&& getModel().getPropertyLayerWrapper().getValue().getLayerGroup().isVisible();
}
/**
* Initializes the previous origin which is used to scale the control points of this path.
*/
private void initializePreviousOrigin() {
if (previousOrigin == null) {
Origin origin = get(FigureConstants.ORIGIN);
previousOrigin = new Origin();
previousOrigin.setScale(origin.getScaleX(), origin.getScaleY());
}
}
/**
* Update bezier and polypath control points.
*/
public void updateControlPoints() {
String sControlPoints = "";
if (getLinerType() == PathModel.Type.POLYPATH) {
sControlPoints = updatePolyPathControlPoints();
}
else if (getLinerType() == PathModel.Type.BEZIER || getLinerType() == PathModel.Type.BEZIER_3) {
sControlPoints = updateBezierControlPoints();
}
StringProperty sProp = getModel().getPropertyPathControlPoints();
sProp.setText(sControlPoints);
invalidate();
sProp.markChanged();
getModel().propertiesChanged(this);
}
private String updatePolyPathControlPoints() {
StringJoiner rtn = new StringJoiner(";");
for (int i = 1; i < path.size() - 1; i++) {
Point2D.Double p = path.get(i, BezierPath.C0_MASK);
rtn.add(String.format("%d,%d", (int) p.x, (int) p.y));
}
return rtn.toString();
}
private String updateBezierControlPoints() {
if (cp1 != null && cp2 != null) {
if (cp3 != null) {
cp1 = path.get(0, BezierPath.C2_MASK);
cp2 = path.get(1, BezierPath.C1_MASK);
cp3 = path.get(1, BezierPath.C0_MASK);
cp4 = path.get(1, BezierPath.C2_MASK);
cp5 = path.get(2, BezierPath.C1_MASK);
}
else {
cp1 = path.get(0, BezierPath.C2_MASK);
cp2 = path.get(1, BezierPath.C1_MASK);
}
}
String sControlPoints = "";
if (cp1 != null) {
if (cp2 != null) {
if (cp3 != null) {
// Format: x1,y1;x2,y2;x3,y3;x4,y4;x5,y5
sControlPoints = String.format(
"%d,%d;%d,%d;%d,%d;%d,%d;%d,%d",
(int) (cp1.x),
(int) (cp1.y),
(int) (cp2.x),
(int) (cp2.y),
(int) (cp3.x),
(int) (cp3.y),
(int) (cp4.x),
(int) (cp4.y),
(int) (cp5.x),
(int) (cp5.y)
);
}
else {
// Format: x1,y1;x2,y2
sControlPoints = String.format(
"%d,%d;%d,%d", (int) (cp1.x),
(int) (cp1.y), (int) (cp2.x),
(int) (cp2.y)
);
}
}
else {
// Format: x1,y1
sControlPoints = String.format("%d,%d", (int) (cp1.x), (int) (cp1.y));
}
}
return sControlPoints;
}
/**
* Connects two figures with this connection.
*
* @param start The first figure.
* @param end The second figure.
*/
public void connect(LabeledPointFigure start, LabeledPointFigure end) {
Connector compConnector = new ChopEllipseConnector();
Connector startConnector = start.findCompatibleConnector(compConnector, true);
Connector endConnector = end.findCompatibleConnector(compConnector, true);
if (!canConnect(startConnector, endConnector)) {
return;
}
setStartConnector(startConnector);
setEndConnector(endConnector);
getModel().setConnectedComponents(
start.get(FigureConstants.MODEL),
end.get(FigureConstants.MODEL)
);
}
/**
* Returns the type of this path.
*
* @return The type of this path
*/
public PathModel.Type getLinerType() {
return (PathModel.Type) getModel().getPropertyPathConnType().getValue();
}
public void setLinerByType(PathModel.Type type) {
switch (type) {
case DIRECT:
resetPath();
updateLiner(null);
break;
case ELBOW:
if (!(getLiner() instanceof ElbowLiner)) {
resetPath();
updateLiner(new ElbowLiner());
}
break;
case SLANTED:
if (!(getLiner() instanceof SlantedLiner)) {
resetPath();
updateLiner(new SlantedLiner());
}
break;
case BEZIER:
if (!(getLiner() instanceof TupelBezierLiner)) {
initControlPoints(type);
updateLiner(new TupelBezierLiner());
}
break;
case BEZIER_3:
if (!(getLiner() instanceof TripleBezierLiner)) {
initControlPoints(type);
updateLiner(new TripleBezierLiner());
}
break;
case POLYPATH:
if (!(getLiner() instanceof PolyPathLiner)) {
initPolyPath();
updateLiner(new PolyPathLiner());
}
break;
default:
setLiner(null);
}
}
private void updateLiner(Liner newLiner) {
setLiner(newLiner);
fireFigureHandlesChanged();
fireAreaInvalidated();
updateControlPoints();
invalidate();
getModel().propertiesChanged(this);
}
private LengthProperty calculateLength() {
try {
LengthProperty property = getModel().getPropertyLength();
if (property != null) {
double length = (double) property.getValue();
if (length <= 0.0) {
PointFigure start = ((LabeledPointFigure) getStartFigure()).getPresentationFigure();
PointFigure end = ((LabeledPointFigure) getEndFigure()).getPresentationFigure();
double startPosX
= start.getModel().getPropertyModelPositionX().getValueByUnit(LengthProperty.Unit.MM);
double startPosY
= start.getModel().getPropertyModelPositionY().getValueByUnit(LengthProperty.Unit.MM);
double endPosX
= end.getModel().getPropertyModelPositionX().getValueByUnit(LengthProperty.Unit.MM);
double endPosY
= end.getModel().getPropertyModelPositionY().getValueByUnit(LengthProperty.Unit.MM);
length = distance(startPosX, startPosY, endPosX, endPosY);
property.setValueAndUnit(length, LengthProperty.Unit.MM);
property.markChanged();
}
}
return property;
}
catch (IllegalArgumentException ex) {
LOG.error("calculateLength()", ex);
return null;
}
}
@Override
public String getToolTipText(Point2D.Double p) {
return textGenerator.getToolTipText(getModel());
}
/**
* Checks whether two points can be connected with each other.
*
* @param start The start connector.
* @param end The end connector.
* @return {@code true}, if the two points can be connected, otherwise {@code false}.
*/
@Override
public boolean canConnect(Connector start, Connector end) {
ModelComponent modelStart = start.getOwner().get(FigureConstants.MODEL);
ModelComponent modelEnd = end.getOwner().get(FigureConstants.MODEL);
return modelStart instanceof PointModel
&& modelEnd instanceof PointModel
&& modelStart != modelEnd;
}
@Override
public Collection<Handle> createHandles(int detailLevel) {
Collection<Handle> handles = new ArrayList<>();
if (!isVisible()) {
return handles;
}
// see BezierFigure
switch (detailLevel % 2) {
case -1: // Mouse hover handles
handles.add(new BezierOutlineHandle(this, true));
break;
case 0: // Mouse clicked
if (getLinerType() == PathModel.Type.POLYPATH) {
for (int i = 1; i < path.size() - 1; i++) {
handles.add(new BezierLinerControlPointHandle(this, i, BezierPath.C0_MASK));
}
}
else {
if (cp1 != null) {
// Start point: handle to CP2
handles.add(new BezierLinerControlPointHandle(this, 0, BezierPath.C2_MASK));
if (cp2 != null) {
// End point: handle to CP3
handles.add(new BezierLinerControlPointHandle(this, 1, BezierPath.C1_MASK));
if (cp3 != null) {
// End point: handle to EP
handles.add(new BezierLinerControlPointHandle(this, 2, BezierPath.C1_MASK));
}
}
}
}
break;
case 1: // double click
handles.add(new BezierOutlineHandle(this));
break;
default:
}
return handles;
}
@Override
public void lineout() {
if (getLiner() == null) {
path.invalidatePath();
}
else {
getLiner().lineout(this);
}
}
@Override
public void propertiesChanged(AttributesChangeEvent e) {
if (!e.getInitiator().equals(this)) {
setLinerByType((PathModel.Type) getModel().getPropertyPathConnType().getValue());
calculateLength();
lineout();
}
super.propertiesChanged(e);
}
@Override
public void draw(Graphics2D g) {
if (drawingOptions.isBlocksVisible()) {
drawBlockDecoration(g);
}
drawRouteDecoration(g);
super.draw(g);
}
private void drawRouteDecoration(Graphics2D g) {
for (Map.Entry<VehicleModel, AllocationState> entry : getModel().getAllocationStates()
.entrySet()) {
VehicleModel vehicleModel = entry.getKey();
switch (entry.getValue()) {
case CLAIMED:
drawDecoration(
g,
Strokes.PATH_ON_ROUTE,
transparentColor(vehicleModel.getDriveOrderColor(), 70)
);
break;
case ALLOCATED:
drawDecoration(g, Strokes.PATH_ON_ROUTE, vehicleModel.getDriveOrderColor());
break;
case ALLOCATED_WITHDRAWN:
drawDecoration(g, Strokes.PATH_ON_WITHDRAWN_ROUTE, Color.GRAY);
break;
default:
// Don't draw any decoration.
}
}
}
private void drawBlockDecoration(Graphics2D g) {
for (BlockModel blockModel : getModel().getBlockModels()) {
drawDecoration(g, Strokes.BLOCK_ELEMENT, transparentColor(blockModel.getColor(), 192));
}
}
private Color transparentColor(Color color, int alpha) {
return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
}
private void drawDecoration(Graphics2D g, Stroke stroke, Color color) {
g.setStroke(stroke);
g.setColor(color);
g.draw(this.getShape());
}
@Override
public void updateDecorations() {
if (getModel() == null) {
return;
}
set(AttributeKeys.START_DECORATION, navigableBackward() ? ARROW_BACKWARD : null);
set(AttributeKeys.END_DECORATION, navigableForward() ? ARROW_FORWARD : null);
// Mark locked path.
if (Boolean.TRUE.equals(getModel().getPropertyLocked().getValue())) {
set(AttributeKeys.STROKE_COLOR, Color.red);
set(AttributeKeys.STROKE_DASHES, LOCKED_DASH);
}
else {
set(AttributeKeys.STROKE_COLOR, Color.black);
set(AttributeKeys.STROKE_DASHES, UNLOCKED_DASH);
}
}
@Override
public <T> void set(AttributeKey<T> key, T newValue) {
super.set(key, newValue);
// if the ModelComponent is set we update the decorations, because
// properties like maxReverseVelocity could have changed
if (key.equals(FigureConstants.MODEL)) {
updateDecorations();
}
}
@Override
public void updateModel() {
if (calculateLength() == null) {
return;
}
getModel().getPropertyMaxVelocity().markChanged();
getModel().getPropertyMaxReverseVelocity().markChanged();
getModel().propertiesChanged(this);
}
@Override
public void scaleModel(EventObject event) {
if (!(event.getSource() instanceof Origin)) {
return;
}
Origin origin = (Origin) event.getSource();
if (previousOrigin.getScaleX() == origin.getScaleX()
&& previousOrigin.getScaleY() == origin.getScaleY()) {
return;
}
if (isTupelBezier()) { // BEZIER
Point2D.Double scaledControlPoint = scaleControlPoint(cp1, origin);
path.set(0, BezierPath.C2_MASK, scaledControlPoint);
scaledControlPoint = scaleControlPoint(cp2, origin);
path.set(1, BezierPath.C1_MASK, scaledControlPoint);
}
else if (isTripleBezier()) { // BEZIER_3
Point2D.Double scaledControlPoint = scaleControlPoint(cp1, origin);
path.set(0, BezierPath.C2_MASK, scaledControlPoint);
scaledControlPoint = scaleControlPoint(cp2, origin);
path.set(1, BezierPath.C1_MASK, scaledControlPoint);
scaledControlPoint = scaleControlPoint(cp3, origin);
path.set(1, BezierPath.C0_MASK, scaledControlPoint);
scaledControlPoint = scaleControlPoint(cp4, origin);
path.set(1, BezierPath.C2_MASK, scaledControlPoint);
path.set(2, BezierPath.C2_MASK, scaledControlPoint);
scaledControlPoint = scaleControlPoint(cp5, origin);
path.set(2, BezierPath.C1_MASK, scaledControlPoint);
}
// Remember the new scale
previousOrigin.setScale(origin.getScaleX(), origin.getScaleY());
updateControlPoints();
}
private boolean navigableForward() {
return getModel().getPropertyMaxVelocity().getValueByUnit(SpeedProperty.Unit.MM_S) > 0.0;
}
private boolean navigableBackward() {
return getModel().getPropertyMaxReverseVelocity().getValueByUnit(SpeedProperty.Unit.MM_S) > 0.0;
}
private Point2D.Double scaleControlPoint(Point2D.Double p, Origin newScale) {
return new Double(
(p.x * previousOrigin.getScaleX()) / newScale.getScaleX(),
(p.y * previousOrigin.getScaleY()) / newScale.getScaleY()
);
}
private boolean isTupelBezier() {
return cp1 != null && cp2 != null && cp3 == null && cp4 == null && cp5 == null;
}
private boolean isTripleBezier() {
return cp1 != null && cp2 != null && cp3 != null && cp4 != null && cp5 != null;
}
@Override // LineConnectionFigure
public PathConnection clone() {
PathConnection clone = (PathConnection) super.clone();
AbstractProperty pConnType = (AbstractProperty) clone.getModel().getPropertyPathConnType();
if (getLiner() instanceof TupelBezierLiner) {
pConnType.setValue(PathModel.Type.BEZIER);
}
else if (getLiner() instanceof TripleBezierLiner) {
pConnType.setValue(PathModel.Type.BEZIER_3);
}
else if (getLiner() instanceof ElbowLiner) {
pConnType.setValue(PathModel.Type.ELBOW);
}
else if (getLiner() instanceof SlantedLiner) {
pConnType.setValue(PathModel.Type.SLANTED);
}
return clone;
}
private void initPolyPath() {
Point2D.Double sp = path.get(0, BezierPath.C0_MASK);
Point2D.Double ep = path.get(path.size() - 1, BezierPath.C0_MASK);
if (sp.x == ep.x && sp.y == ep.y) {
path.clear();
path.add(sp);
String[] coords = getModel().getPropertyPathControlPoints().getText().split("[;]");
for (String coordinates : coords) {
String[] c = coordinates.split("[,]");
int x = (int) java.lang.Double.parseDouble(c[0]);
int y = (int) java.lang.Double.parseDouble(c[1]);
path.add(x, y);
}
path.add(ep);
}
else {
path.clear();
path.add(new BezierPath.Node(sp));
path.add(new BezierPath.Node((sp.x + ep.x) / 2.0, (sp.y + ep.y) / 2.0));
path.add(new BezierPath.Node(ep));
}
}
@Override // SimpleLineConnection
public boolean handleMouseClick(Point2D.Double p, MouseEvent evt, DrawingView drawingView) {
if (getLinerType() == PathModel.Type.POLYPATH) {
int addPointMask = MouseEvent.CTRL_DOWN_MASK;
int deletePointMask = MouseEvent.ALT_DOWN_MASK | MouseEvent.CTRL_DOWN_MASK;
if ((evt.getModifiersEx() & (addPointMask | deletePointMask)) == addPointMask) {
int index = path.findSegment(p, 10);
if (index != -1) {
path.add(index + 1, new BezierPath.Node(p));
updateConnection();
}
}
else if ((evt.getModifiersEx() & (deletePointMask | addPointMask)) == deletePointMask) {
int index = path.findSegment(p, 10);
if (index != -1) {
Point2D.Double[] points = path.toPolygonArray();
path.clear();
for (int i = 0; i < points.length; i++) {
if (i != index) {
path.add(new BezierPath.Node(points[i]));
}
}
updateConnection();
}
}
}
return false;
}
private List<Set<TCSResourceReference<?>>> getCurrentDriveOrderClaim(VehicleModel vehicle) {
List<Set<TCSResourceReference<?>>> result = new ArrayList<>();
boolean driveOrderEndFound = false;
for (Set<TCSResourceReference<?>> res : vehicle.getClaimedResources().getItems()) {
result.add(res);
if (containsDriveOrderDestination(res, vehicle)) {
driveOrderEndFound = true;
break;
}
}
if (driveOrderEndFound) {
return result;
}
else {
// With the end of the drive order not found, there is nothing from the current drive order in
// the claimed resources.
return List.of();
}
}
private boolean containsDriveOrderDestination(
Set<TCSResourceReference<?>> resources,
VehicleModel vehicle
) {
if (vehicle.getDriveOrderDestination() == null) {
return false;
}
return resources.stream()
.anyMatch(
resource -> Objects.equals(
resource.getName(),
vehicle.getDriveOrderDestination().getName()
)
);
}
}

View File

@@ -0,0 +1,282 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import static java.util.Objects.requireNonNull;
import com.google.inject.assistedinject.Assisted;
import jakarta.inject.Inject;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.jhotdraw.geom.Geom;
import org.opentcs.data.model.TCSResourceReference;
import org.opentcs.guing.base.AllocationState;
import org.opentcs.guing.base.model.elements.BlockModel;
import org.opentcs.guing.base.model.elements.PointModel;
import org.opentcs.guing.base.model.elements.VehicleModel;
import org.opentcs.guing.common.components.drawing.DrawingOptions;
import org.opentcs.guing.common.components.drawing.Strokes;
import org.opentcs.guing.common.components.drawing.ZoomPoint;
/**
* A figure that represents a decision point.
*/
public class PointFigure
extends
TCSFigure {
/**
* A color for parking positions.
*/
private static final Color C_PARK = Color.BLUE;
/**
* A color for halt positions.
*/
private static final Color C_HALT = Color.LIGHT_GRAY;
/**
* The figure's diameter in drawing units (pixels at 100% zoom).
*/
private final int fDiameter;
/**
* The drawing options.
*/
private final DrawingOptions drawingOptions;
/**
* Creates a new instance.
*
* @param model The model corresponding to this graphical object.
* @param drawingOptions The drawing options.
*/
@Inject
public PointFigure(
@Assisted
PointModel model,
DrawingOptions drawingOptions
) {
super(model);
this.drawingOptions = requireNonNull(drawingOptions, "drawingOptions");
fDiameter = 10;
fDisplayBox = new Rectangle(fDiameter, fDiameter);
fZoomPoint = new ZoomPoint(0.5 * fDiameter, 0.5 * fDiameter);
}
@Override
public PointModel getModel() {
return (PointModel) get(FigureConstants.MODEL);
}
public Point center() {
return Geom.center(fDisplayBox);
}
public Ellipse2D.Double getShape() {
Rectangle2D r2 = fDisplayBox.getBounds2D();
Ellipse2D.Double shape
= new Ellipse2D.Double(r2.getX(), r2.getY(), fDiameter - 1, fDiameter - 1);
return shape;
}
@Override // Figure
public Rectangle2D.Double getBounds() {
Rectangle2D r2 = fDisplayBox.getBounds2D();
Rectangle2D.Double r2d = new Rectangle2D.Double();
r2d.setRect(r2);
return r2d;
}
@Override // Figure
public Object getTransformRestoreData() {
// Never used?
return fDisplayBox.clone();
}
@Override // Figure
public void restoreTransformTo(Object restoreData) {
// Never used?
Rectangle r = (Rectangle) restoreData;
fDisplayBox.x = r.x;
fDisplayBox.y = r.y;
fDisplayBox.width = r.width;
fDisplayBox.height = r.height;
fZoomPoint.setX(r.x + 0.5 * r.width);
fZoomPoint.setY(r.y + 0.5 * r.height);
}
@Override // Figure
public void transform(AffineTransform tx) {
Point2D center = getZoomPoint().getPixelLocationExactly();
Point2D lead = new Point2D.Double(); // not used
setBounds(
(Point2D.Double) tx.transform(center, center),
(Point2D.Double) tx.transform(lead, lead)
);
}
@Override // AbstractFigure
public void setBounds(Point2D.Double anchor, Point2D.Double lead) {
fZoomPoint.setX(anchor.x);
fZoomPoint.setY(anchor.y);
fDisplayBox.x = (int) (anchor.x - 0.5 * fDiameter);
fDisplayBox.y = (int) (anchor.y - 0.5 * fDiameter);
}
@Override
protected void drawFigure(Graphics2D g) {
if (drawingOptions.isBlocksVisible()) {
drawBlockDecoration(g);
}
drawRouteDecoration(g);
super.drawFigure(g);
}
private void drawRouteDecoration(Graphics2D g) {
for (Map.Entry<VehicleModel, AllocationState> entry : getModel().getAllocationStates()
.entrySet()) {
VehicleModel vehicleModel = entry.getKey();
switch (entry.getValue()) {
case CLAIMED:
drawDecoration(
g,
Strokes.PATH_ON_ROUTE,
transparentColor(vehicleModel.getDriveOrderColor(), 70)
);
break;
case ALLOCATED:
drawDecoration(g, Strokes.PATH_ON_ROUTE, vehicleModel.getDriveOrderColor());
break;
case ALLOCATED_WITHDRAWN:
drawDecoration(g, Strokes.PATH_ON_WITHDRAWN_ROUTE, Color.GRAY);
break;
default:
// Don't draw any decoration.
}
}
}
private void drawBlockDecoration(Graphics2D g) {
for (BlockModel blockModel : getModel().getBlockModels()) {
drawDecoration(g, Strokes.BLOCK_ELEMENT, transparentColor(blockModel.getColor(), 192));
}
}
private Color transparentColor(Color color, int alpha) {
return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
}
private void drawDecoration(Graphics2D g, Stroke stroke, Color color) {
g.setStroke(stroke);
g.setColor(color);
g.draw(this.getShape());
}
@Override
protected void drawFill(Graphics2D g) {
Rectangle rect = fDisplayBox;
if (getModel().getPropertyType().getValue() == PointModel.Type.PARK) {
g.setColor(C_PARK);
}
else {
g.setColor(C_HALT);
}
if (rect.width > 0 && rect.height > 0) {
g.fillOval(rect.x, rect.y, rect.width, rect.height);
}
if (getModel().getPropertyType().getValue() == PointModel.Type.PARK) {
g.setColor(Color.white);
Font oldFont = g.getFont();
Font newFont = new Font(Font.DIALOG, Font.BOLD, 7);
g.setFont(newFont);
g.drawString("P", rect.x + 3, rect.y + rect.height - 3);
g.setFont(oldFont);
}
}
@Override // AbstractAttributedFigure
protected void drawStroke(Graphics2D g) {
Rectangle r = fDisplayBox;
if (r.width > 0 && r.height > 0) {
g.drawOval(r.x, r.y, r.width - 1, r.height - 1);
}
}
@Override // AbstractAttributedDecoratedFigure
public PointFigure clone() {
PointFigure thatFigure = (PointFigure) super.clone();
thatFigure.setZoomPoint(new ZoomPoint(fZoomPoint.getX(), fZoomPoint.getY()));
return thatFigure;
}
@Override // AbstractFigure
public int getLayer() {
return getModel().getPropertyLayerWrapper().getValue().getLayer().getOrdinal();
}
@Override
public boolean isVisible() {
return super.isVisible()
&& getModel().getPropertyLayerWrapper().getValue().getLayer().isVisible()
&& getModel().getPropertyLayerWrapper().getValue().getLayerGroup().isVisible();
}
private List<Set<TCSResourceReference<?>>> getCurrentDriveOrderClaim(VehicleModel vehicle) {
List<Set<TCSResourceReference<?>>> result = new ArrayList<>();
boolean driveOrderEndFound = false;
for (Set<TCSResourceReference<?>> res : vehicle.getClaimedResources().getItems()) {
result.add(res);
if (containsDriveOrderDestination(res, vehicle)) {
driveOrderEndFound = true;
break;
}
}
if (driveOrderEndFound) {
return result;
}
else {
// With the end of the drive order not found, there is nothing from the current drive order in
// the claimed resources.
return List.of();
}
}
private boolean containsDriveOrderDestination(
Set<TCSResourceReference<?>> resources,
VehicleModel vehicle
) {
if (vehicle.getDriveOrderDestination() == null) {
return false;
}
return resources.stream()
.anyMatch(
resource -> Objects.equals(
resource.getName(),
vehicle.getDriveOrderDestination().getName()
)
);
}
}

View File

@@ -0,0 +1,172 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import java.awt.Color;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.EventObject;
import org.jhotdraw.draw.AttributeKey;
import org.jhotdraw.draw.DrawingView;
import org.jhotdraw.draw.LineConnectionFigure;
import org.jhotdraw.draw.connector.Connector;
import org.jhotdraw.draw.decoration.ArrowTip;
import org.jhotdraw.geom.BezierPath;
import org.opentcs.guing.base.components.properties.event.AttributesChangeEvent;
import org.opentcs.guing.base.components.properties.event.AttributesChangeListener;
import org.opentcs.guing.base.model.ModelComponent;
import org.opentcs.guing.base.model.elements.AbstractConnection;
import org.opentcs.guing.common.components.drawing.course.OriginChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*/
public abstract class SimpleLineConnection
extends
LineConnectionFigure
implements
ModelBasedFigure,
AttributesChangeListener,
OriginChangeListener {
protected static final AttributeKey<Color> FILL_COLOR
= new AttributeKey<>("FillColor", Color.class);
protected static final AttributeKey<Color> STROKE_COLOR
= new AttributeKey<>("StrokeColor", Color.class);
protected static final ArrowTip ARROW_FORWARD
= new ArrowTip(0.35, 12.0, 11.3, true, true, true);
protected static final ArrowTip ARROW_BACKWARD
= new ArrowTip(0.35, 12.0, 11.3, true, true, false);
private static final Logger LOG = LoggerFactory.getLogger(SimpleLineConnection.class);
/**
* Creates a new instance.
*
* @param model The model corresponding to this graphical object.
*/
@SuppressWarnings("this-escape")
public SimpleLineConnection(AbstractConnection model) {
set(FigureConstants.MODEL, model);
initConnectionFigure();
}
/**
* Initialise this figure.
*/
protected final void initConnectionFigure() {
updateDecorations();
}
@Override
public AbstractConnection getModel() {
return (AbstractConnection) get(FigureConstants.MODEL);
}
/**
* Return the shape.
*
* @return the shape.
*/
public Shape getShape() {
return path;
}
@Override // BezierFigure
protected BezierPath getCappedPath() {
// Workaround for NullPointerException in BezierFigure.getCappedPath()
try {
return super.getCappedPath();
}
catch (NullPointerException ex) {
LOG.warn("", ex);
return path.clone();
}
}
/**
* Update the properties of the model.
*/
public abstract void updateModel();
/**
* Scales the model coodinates accodring to changes to the layout scale.
*
* @param event The event containing the layout scale change.
*/
public abstract void scaleModel(EventObject event);
/**
* Calculates the euclid distance between the start position and the end position.
*
* @param startPosX The x coordiante of the start position.
* @param startPosY The y coordiante of the start position.
* @param endPosX The x coordinate of the end position.
* @param endPosY The y coordinate of the end position.
* @return the euclid distance between start and end point rounded to the next integer.
*/
protected double distance(double startPosX, double startPosY, double endPosX, double endPosY) {
double dX = startPosX - endPosX;
double dY = startPosY - endPosY;
double dist = Math.sqrt(dX * dX + dY * dY);
dist = Math.floor(dist + 0.5); // round to an integer value.
return dist;
}
public void updateDecorations() {
}
@Override // LineConnectionFigure
protected void handleConnect(Connector start, Connector end) {
if (start != null && end != null) {
ModelComponent startModel = start.getOwner().get(FigureConstants.MODEL);
ModelComponent endModel = end.getOwner().get(FigureConstants.MODEL);
getModel().setConnectedComponents(startModel, endModel);
updateModel();
}
}
@Override // LineConnectionFigure
protected void handleDisconnect(Connector start, Connector end) {
super.handleDisconnect(start, end);
getModel().removingConnection();
}
@Override // LineConnectionFigure
public boolean handleMouseClick(Point2D.Double p, MouseEvent evt, DrawingView drawingView) {
return false;
}
@Override // AttributesChangeListener
public void propertiesChanged(AttributesChangeEvent e) {
if (!e.getInitiator().equals(this)) {
updateDecorations();
fireFigureChanged(getDrawingArea());
}
}
@Override // OriginChangeListener
public void originLocationChanged(EventObject event) {
}
@Override // OriginChangeListener
public void originScaleChanged(EventObject event) {
scaleModel(event);
}
@Override
public SimpleLineConnection clone() {
try {
SimpleLineConnection clone = (SimpleLineConnection) super.clone();
clone.set(FigureConstants.MODEL, getModel().clone());
return clone;
}
catch (CloneNotSupportedException exc) {
throw new IllegalStateException("Unexpected exception encountered", exc);
}
}
}

View File

@@ -0,0 +1,119 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import org.jhotdraw.draw.AbstractAttributedDecoratedFigure;
import org.jhotdraw.geom.Geom;
import org.opentcs.guing.base.model.DrawnModelComponent;
import org.opentcs.guing.base.model.ModelComponent;
import org.opentcs.guing.common.components.drawing.ZoomPoint;
/**
* Base implementation for figures.
*/
public abstract class TCSFigure
extends
AbstractAttributedDecoratedFigure
implements
ModelBasedFigure {
/**
* The enclosing rectangle.
*/
protected Rectangle fDisplayBox;
/**
* The exact position for the middle of the figure.
*/
protected ZoomPoint fZoomPoint;
/**
* Creates a new instance.
*
* @param modelComponent The model corresponding to this graphical object.
*/
@SuppressWarnings("this-escape")
public TCSFigure(ModelComponent modelComponent) {
super();
set(FigureConstants.MODEL, modelComponent);
}
/**
* Returns the exact point at the middle of the figure.
*
* @return the exact point at the middle of the figure.
*/
public ZoomPoint getZoomPoint() {
return fZoomPoint;
}
/**
* Sets the zoom point.
*
* @param zoomPoint The point at the middle of the figure.
*/
public void setZoomPoint(ZoomPoint zoomPoint) {
fZoomPoint = zoomPoint;
}
/**
* Clones this figure, also clones the associated model component.
*
* @return
*/
@Override // AbstractAttributedDecoratedFigure
public TCSFigure clone() {
try {
TCSFigure that = (TCSFigure) super.clone();
that.fDisplayBox = new Rectangle(fDisplayBox);
that.setModel(getModel().clone());
return that;
}
catch (CloneNotSupportedException ex) {
throw new Error("Cannot clone() unexpectedly", ex);
}
}
@Override
protected Rectangle2D.Double getFigureDrawingArea() {
// Add some margin to the drawing area of the figure, so the
// drawing area scrolls a little earlier
Rectangle2D.Double drawingArea = super.getFigureDrawingArea();
// if we add these two lines the Drawing becomes grey, if we start
// the application in operating mode..
// drawingArea.height += 50;
// drawingArea.width += 100;
return drawingArea;
}
@Override
public DrawnModelComponent getModel() {
return (DrawnModelComponent) get(FigureConstants.MODEL);
}
public void setModel(ModelComponent model) {
set(FigureConstants.MODEL, model);
}
/**
* Returns the enclosing rectangle.
*
* @return The enclosing rectangle.
*/
public Rectangle displayBox() {
return new Rectangle(fDisplayBox);
}
@Override
public boolean figureContains(Point2D.Double p) {
Rectangle2D.Double r2d = getBounds();
// Grow for connectors
Geom.grow(r2d, 10d, 10d);
return (r2d.contains(p));
}
}

View File

@@ -0,0 +1,144 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import java.awt.geom.Point2D;
import java.awt.geom.Point2D.Double;
import org.jhotdraw.draw.LabelFigure;
import org.jhotdraw.draw.event.FigureEvent;
import org.opentcs.data.model.visualization.ElementPropKeys;
import org.opentcs.guing.base.components.properties.type.StringProperty;
import org.opentcs.guing.base.model.ModelComponent;
/**
* A label belonging to another {@code Figure} that shows the name of the affiliated object in the
* kernel model.
*/
public class TCSLabelFigure
extends
LabelFigure {
/**
* The default x label offset for label figures.
*/
public static final int DEFAULT_LABEL_OFFSET_X = -10;
/**
* The default y label offset for label figures.
*/
public static final int DEFAULT_LABEL_OFFSET_Y = -20;
private Point2D.Double fOffset;
private LabeledFigure fParent;
private boolean isLabelVisible = true;
/**
* Creates a new instance.
*/
public TCSLabelFigure() {
this("?");
}
/**
* Creates a new instance.
*
* @param text The text of the label
*/
public TCSLabelFigure(String text) {
super(text);
fOffset = new Point2D.Double(DEFAULT_LABEL_OFFSET_X, DEFAULT_LABEL_OFFSET_Y);
}
/**
* Sets the visibility flag of the label.
*
* @param visible The visibility flag.
*/
public void setLabelVisible(boolean visible) {
isLabelVisible = visible;
if (visible) {
setText(fParent.getPresentationFigure().getModel().getName());
}
else {
setText("");
}
invalidate();
validate();
}
/**
* Sets the position relative to the {@code Figure}.
*
* @param posX The X-Offset of the label.
* @param posY The Y-Offset of the label.
*/
public void setOffset(int posX, int posY) {
fOffset = new Point2D.Double(posX, posY);
}
public Double getOffset() {
return fOffset;
}
void setParent(LabeledFigure parent) {
fParent = parent;
}
@Override // AbstractFigure
public void changed() {
// Called when the figure has changed - movement with MouseDragger.
super.changed();
if (fParent != null) {
TCSFigure figure = fParent.getPresentationFigure();
ModelComponent model = figure.getModel();
Point2D.Double newOffset = new Point2D.Double(
getBounds().getX() - figure.getBounds().x,
getBounds().getY() - figure.getBounds().y
);
if (newOffset.x != fOffset.x || newOffset.y != fOffset.y) {
fOffset = newOffset;
StringProperty sp
= (StringProperty) model.getProperty(ElementPropKeys.POINT_LABEL_OFFSET_X);
if (sp != null) {
sp.setText(String.format("%d", (long) newOffset.x));
sp.markChanged();
}
sp = (StringProperty) model.getProperty(ElementPropKeys.POINT_LABEL_OFFSET_Y);
if (sp != null) {
sp.setText(String.format("%d", (long) newOffset.y));
sp.markChanged();
}
model.propertiesChanged(fParent);
}
}
}
@Override // AbstractFigure
public int getLayer() {
return 1; // stay above other figures ?
}
@Override // LabelFigure
public void figureChanged(FigureEvent event) {
if (event.getFigure() instanceof LabeledFigure) {
LabeledFigure lf = (LabeledFigure) event.getFigure();
TCSFigure figure = lf.getPresentationFigure();
ModelComponent model = figure.getModel();
String name = model.getName();
setText(name);
if (isLabelVisible) {
invalidate();
validate();
}
}
}
}

View File

@@ -0,0 +1,216 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures;
import static java.util.Objects.requireNonNull;
import static org.opentcs.guing.base.model.ModelComponent.MISCELLANEOUS;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import org.opentcs.guing.base.components.properties.type.KeyValueProperty;
import org.opentcs.guing.base.components.properties.type.KeyValueSetProperty;
import org.opentcs.guing.base.model.FigureDecorationDetails;
import org.opentcs.guing.base.model.ModelComponent;
import org.opentcs.guing.base.model.elements.BlockModel;
import org.opentcs.guing.base.model.elements.LinkModel;
import org.opentcs.guing.base.model.elements.LocationModel;
import org.opentcs.guing.base.model.elements.PathModel;
import org.opentcs.guing.base.model.elements.PointModel;
import org.opentcs.guing.base.model.elements.VehicleModel;
import org.opentcs.guing.common.persistence.ModelManager;
/**
* Generates tooltip texts for various model elements.
*/
public class ToolTipTextGenerator {
/**
* The model manager.
*/
private final ModelManager modelManager;
/**
* Create a new instance.
*
* @param modelManager The model manager to use.
*/
@Inject
public ToolTipTextGenerator(ModelManager modelManager) {
this.modelManager = requireNonNull(modelManager, "modelManager");
}
/**
* Generate a tooltip text for a vehicle model.
*
* @param model The vehicle model.
* @return A tooltip text for the model element.
*/
public String getToolTipText(VehicleModel model) {
return "";
}
/**
* Generate a tooltip text for a point model.
*
* @param model The point model.
* @return A tooltip text for the model element.
*/
public String getToolTipText(PointModel model) {
String pointDesc = model.getDescription();
StringBuilder sb = new StringBuilder("<html>");
sb.append(pointDesc).append(" ").append("<b>").append(model.getName()).append("</b>");
appendBlockInfo(sb, model);
appendMiscProps(sb, model);
appendAllocatingVehicle(sb, model);
sb.append("</html>");
return sb.toString();
}
/**
* Generate a tooltip text for a location model.
*
* @param model The location model.
* @return A tooltip text for the model element.
*/
public String getToolTipText(LocationModel model) {
String locationDesc = model.getDescription();
StringBuilder sb = new StringBuilder("<html>");
sb.append(locationDesc).append(" ").append("<b>").append(model.getName()).append("</b>");
appendBlockInfo(sb, model);
appendPeripheralInformation(sb, model);
appendMiscProps(sb, model);
appendAllocatingVehicle(sb, model);
sb.append("</html>");
return sb.toString();
}
/**
* Generate a tooltip text for a path model.
*
* @param model The path model.
* @return A tooltip text for the model element.
*/
public String getToolTipText(PathModel model) {
String pathDesc = model.getDescription();
StringBuilder sb = new StringBuilder("<html>");
sb.append(pathDesc).append(" ").append("<b>").append(model.getName()).append("</b>");
appendBlockInfo(sb, model);
appendMiscProps(sb, model);
appendAllocatingVehicle(sb, model);
sb.append("</html>");
return sb.toString();
}
/**
* Generate a tooltip text for a link model.
*
* @param model The link model.
* @return A tooltip text for the model element.
*/
public String getToolTipText(LinkModel model) {
return new StringBuilder("<html>")
.append(model.getDescription()).append(" ")
.append("<b>").append(model.getName()).append("</b>")
.append("</html>").toString();
}
private void appendBlockInfo(StringBuilder sb, ModelComponent component) {
sb.append(blocksToToolTipContent(getBlocksWith(component)));
}
private void appendBlockInfo(StringBuilder sb, LocationModel location) {
List<LinkModel> links = modelManager.getModel().getLinkModels();
links = links.stream()
.filter(link -> link.getLocation().getName().equals(location.getName()))
.collect(Collectors.toList());
List<BlockModel> partOfBlocks = new ArrayList<>();
for (LinkModel link : links) {
partOfBlocks.addAll(getBlocksWith(link.getPoint()));
}
sb.append(blocksToToolTipContent(partOfBlocks));
}
protected void appendMiscProps(StringBuilder sb, ModelComponent component) {
KeyValueSetProperty miscProps = (KeyValueSetProperty) component.getProperty(MISCELLANEOUS);
if (miscProps.getItems().isEmpty()) {
return;
}
sb.append("<hr>\n");
sb.append(miscProps.getDescription()).append(": \n");
sb.append("<ul>\n");
miscProps.getItems().stream()
.sorted(Comparator.comparing(KeyValueProperty::getKey))
.forEach(kvp -> {
sb.append("<li>")
.append(kvp.getKey()).append(": ").append(kvp.getValue())
.append("</li>\n");
});
sb.append("</ul>\n");
}
protected void appendAllocatingVehicle(StringBuilder sb, FigureDecorationDetails figure) {
// Displaying information about allocating vehicles is only relevant in the Operations Desk
// application, which is why this method is empty here.
}
private void appendPeripheralInformation(StringBuilder sb, LocationModel model) {
sb.append("<hr>");
sb.append("<br>").append(model.getPropertyPeripheralReservationToken().getDescription())
.append(": ")
.append(model.getPropertyPeripheralReservationToken().getText());
sb.append("<br>").append(model.getPropertyPeripheralState().getDescription())
.append(": ")
.append(model.getPropertyPeripheralState().getText());
sb.append("<br>").append(model.getPropertyPeripheralProcState().getDescription())
.append(": ")
.append(model.getPropertyPeripheralProcState().getText());
sb.append("<br>").append(model.getPropertyPeripheralJob().getDescription())
.append(": ")
.append(model.getPropertyPeripheralJob().getText());
}
private List<BlockModel> getBlocksWith(ModelComponent component) {
List<BlockModel> result = new ArrayList<>();
List<BlockModel> blocks = modelManager.getModel().getBlockModels();
for (BlockModel block : blocks) {
if (block.contains(component)) {
result.add(block);
}
}
return result;
}
private String blocksToToolTipContent(List<BlockModel> blocks) {
if (blocks.isEmpty()) {
return "";
}
blocks.sort((b1, b2) -> b1.getName().compareTo(b2.getName()));
String desc = blocks.get(0).getDescription();
StringBuilder sb = new StringBuilder("<hr>")
.append(desc).append(": ");
for (BlockModel block : blocks) {
sb.append(block.getName()).append(", ");
}
sb.delete(sb.lastIndexOf(", "), sb.length());
return sb.toString();
}
}

View File

@@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures.decoration;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RadialGradientPaint;
import java.awt.Shape;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import org.jhotdraw.draw.AttributeKeys;
import org.jhotdraw.draw.Figure;
import org.jhotdraw.draw.handle.BoundsOutlineHandle;
import org.opentcs.guing.common.components.drawing.figures.PointFigure;
/**
*/
public class PointOutlineHandle
extends
BoundsOutlineHandle {
public PointOutlineHandle(Figure owner) {
super(owner);
}
@Override
public void draw(Graphics2D g) {
PointFigure pf = (PointFigure) getOwner();
Shape bounds = pf.getShape();
if (getOwner().get(AttributeKeys.TRANSFORM) != null) {
bounds = getOwner().get(AttributeKeys.TRANSFORM).createTransformedShape(bounds);
}
if (view != null) {
bounds = view.getDrawingToViewTransform().createTransformedShape(bounds);
Rectangle2D bounds2D = bounds.getBounds2D();
float centerX = (float) bounds2D.getCenterX();
float centerY = (float) bounds2D.getCenterY();
Point2D center = new Point2D.Float(centerX, centerY);
float radius = 10.0f;
float[] dist = {0.1f, 0.9f};
Color[] colors = {Color.CYAN, Color.BLUE};
RadialGradientPaint radialGradientPaint
= new RadialGradientPaint(center, radius, dist, colors);
Paint oldPaint = g.getPaint();
g.setPaint(radialGradientPaint);
g.fill(bounds);
g.setPaint(oldPaint);
}
}
}

View File

@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures.liner;
import java.awt.Point;
import java.util.Objects;
import org.jhotdraw.draw.BezierFigure;
import org.jhotdraw.draw.Drawing;
/**
* A Handle which allows to interactively change a control point
*/
public class BezierLinerControlPointHandle
extends
org.jhotdraw.draw.handle.BezierControlPointHandle {
public BezierLinerControlPointHandle(BezierFigure owner, int index, int coord) {
super(owner, index, coord);
}
@Override // BezierControlPointHandle
public void trackEnd(Point anchor, Point lead, int modifiersEx) {
super.trackEnd(anchor, lead, modifiersEx);
// Fire edit event to update the control points of the Path figure
Drawing drawing = Objects.requireNonNull(view.getDrawing());
drawing.fireUndoableEditHappened(new BezierLinerEdit(getBezierFigure()));
}
}

View File

@@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures.liner;
import javax.swing.SwingUtilities;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import org.jhotdraw.draw.BezierFigure;
import org.jhotdraw.draw.event.BezierNodeEdit;
import org.jhotdraw.geom.BezierPath;
import org.opentcs.guing.common.components.drawing.figures.PathConnection;
import org.opentcs.guing.common.util.I18nPlantOverview;
import org.opentcs.thirdparty.guing.common.jhotdraw.util.ResourceBundleUtil;
/**
*/
public class BezierLinerEdit
extends
javax.swing.undo.AbstractUndoableEdit {
private final BezierFigure fOwner;
private final BezierNodeEdit fNodeEdit;
/**
* @param owner A path
*/
public BezierLinerEdit(BezierFigure owner) {
fOwner = owner;
BezierPath.Node node = owner.getNode(0);
fNodeEdit = new BezierNodeEdit(owner, 0, node, node);
}
/**
*
* @return The associated PathConnection
*/
public BezierFigure getOwner() {
return fOwner;
}
@Override // AbstractUndoableEdit
public boolean isSignificant() {
return false;
}
@Override // AbstractUndoableEdit
public String getPresentationName() {
return ResourceBundleUtil.getBundle(I18nPlantOverview.MISC_PATH)
.getString("bezierLinerEdit.presentationName");
}
@Override // AbstractUndoableEdit
public void redo()
throws CannotRedoException {
fNodeEdit.redo();
updateProperties();
}
@Override // AbstractUndoableEdit
public void undo()
throws CannotUndoException {
fNodeEdit.undo();
updateProperties();
}
private void updateProperties() {
SwingUtilities.invokeLater(() -> {
PathConnection path = (PathConnection) fOwner;
path.updateControlPoints();
path.getModel().getPropertyPathControlPoints().markChanged();
path.getModel().propertiesChanged(path);
});
}
}

View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures.liner;
import java.util.Collection;
import java.util.Collections;
import org.jhotdraw.draw.ConnectionFigure;
import org.jhotdraw.draw.LineConnectionFigure;
import org.jhotdraw.draw.handle.Handle;
import org.jhotdraw.draw.liner.Liner;
import org.jhotdraw.geom.BezierPath;
/**
*/
public class PolyPathLiner
implements
org.jhotdraw.draw.liner.Liner {
public PolyPathLiner() {
}
@Override
public void lineout(ConnectionFigure figure) {
BezierPath path = ((LineConnectionFigure) figure).getBezierPath();
if (path != null) {
path.invalidatePath();
}
}
@Override
public Collection<Handle> createHandles(BezierPath path) {
return Collections.emptyList();
}
@Override // Object
public Liner clone() {
try {
return (Liner) super.clone();
}
catch (CloneNotSupportedException ex) {
InternalError error = new InternalError(ex.getMessage());
error.initCause(ex);
throw error;
}
}
}

View File

@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures.liner;
import java.util.Collection;
import java.util.Collections;
import org.jhotdraw.draw.ConnectionFigure;
import org.jhotdraw.draw.LineConnectionFigure;
import org.jhotdraw.draw.handle.Handle;
import org.jhotdraw.draw.liner.Liner;
import org.jhotdraw.geom.BezierPath;
/**
* A {@link Liner} that constrains a connection to a fourth-order curved
* line.
*/
public class TripleBezierLiner
implements
org.jhotdraw.draw.liner.Liner {
/**
* Creates a new instance.
*/
public TripleBezierLiner() {
}
@Override // Liner
public Collection<Handle> createHandles(BezierPath path) {
return Collections.emptyList();
}
@Override // Liner
public void lineout(ConnectionFigure figure) {
BezierPath path = ((LineConnectionFigure) figure).getBezierPath();
if (path != null) {
path.invalidatePath();
}
}
@Override // Object
public Liner clone() {
try {
return (Liner) super.clone();
}
catch (CloneNotSupportedException ex) {
InternalError error = new InternalError(ex.getMessage());
error.initCause(ex);
throw error;
}
}
}

View File

@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.drawing.figures.liner;
import java.util.Collection;
import java.util.Collections;
import org.jhotdraw.draw.ConnectionFigure;
import org.jhotdraw.draw.LineConnectionFigure;
import org.jhotdraw.draw.handle.Handle;
import org.jhotdraw.draw.liner.Liner;
import org.jhotdraw.geom.BezierPath;
/**
* A {@link Liner} that constrains a connection to a quadratic or cubic curved
* line.
*/
public class TupelBezierLiner
implements
org.jhotdraw.draw.liner.Liner {
/**
* Creates a new instance.
*/
public TupelBezierLiner() {
}
@Override // Liner
public Collection<Handle> createHandles(BezierPath path) {
return Collections.emptyList();
}
@Override // Liner
public void lineout(ConnectionFigure figure) {
BezierPath path = ((LineConnectionFigure) figure).getBezierPath();
if (path != null) {
path.invalidatePath();
}
}
@Override // Object
public Liner clone() {
try {
return (Liner) super.clone();
}
catch (CloneNotSupportedException ex) {
InternalError error = new InternalError(ex.getMessage());
error.initCause(ex);
throw error;
}
}
}

View File

@@ -0,0 +1,177 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.layer;
import static java.util.Objects.requireNonNull;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import javax.swing.table.AbstractTableModel;
import org.opentcs.data.model.visualization.LayerGroup;
import org.opentcs.guing.common.persistence.ModelManager;
import org.opentcs.guing.common.util.I18nPlantOverview;
/**
* A table model for layer groups.
*/
public abstract class AbstractLayerGroupsTableModel
extends
AbstractTableModel
implements
LayerGroupChangeListener {
/**
* The number of the "ID" column.
*/
public static final int COLUMN_ID = 0;
/**
* The number of the "Name" column.
*/
public static final int COLUMN_NAME = 1;
/**
* The number of the "Visible" column.
*/
public static final int COLUMN_VISIBLE = 2;
/**
* The resource bundle to use.
*/
private static final ResourceBundle BUNDLE
= ResourceBundle.getBundle(I18nPlantOverview.LAYERS_PATH);
/**
* The column names.
*/
private static final String[] COLUMN_NAMES
= new String[]{
BUNDLE.getString("abstractLayerGroupsTableModel.column_id.headerText"),
BUNDLE.getString("abstractLayerGroupsTableModel.column_name.headerText"),
BUNDLE.getString("abstractLayerGroupsTableModel.column_visible.headerText")
};
/**
* The column classes.
*/
private static final Class<?>[] COLUMN_CLASSES
= new Class<?>[]{
Integer.class,
String.class,
Boolean.class
};
/**
* The model manager.
*/
private final ModelManager modelManager;
/**
* The layer group editor.
*/
private final LayerGroupEditor layerGroupEditor;
/**
* Creates a new instance.
*
* @param modelManager The model manager.
* @param layerGroupEditor The layer group editor.
*/
public AbstractLayerGroupsTableModel(
ModelManager modelManager,
LayerGroupEditor layerGroupEditor
) {
this.modelManager = requireNonNull(modelManager, "modelManager");
this.layerGroupEditor = requireNonNull(layerGroupEditor, "layerGroupEditor");
}
@Override
public int getRowCount() {
return layerGroups().size();
}
@Override
public int getColumnCount() {
return COLUMN_NAMES.length;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (rowIndex < 0 || rowIndex >= getRowCount()) {
return null;
}
LayerGroup entry = layerGroups().get(rowIndex);
switch (columnIndex) {
case COLUMN_ID:
return entry.getId();
case COLUMN_NAME:
return entry.getName();
case COLUMN_VISIBLE:
return entry.isVisible();
default:
throw new IllegalArgumentException("Invalid column index: " + columnIndex);
}
}
@Override
public String getColumnName(int columnIndex) {
return COLUMN_NAMES[columnIndex];
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return COLUMN_CLASSES[columnIndex];
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
switch (columnIndex) {
case COLUMN_ID:
return false;
case COLUMN_NAME:
return isNameColumnEditable();
case COLUMN_VISIBLE:
return isVisibleColumnEditable();
default:
throw new IllegalArgumentException("Invalid column index: " + columnIndex);
}
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
if (rowIndex < 0 || rowIndex >= getRowCount()) {
return;
}
if (aValue == null) {
return;
}
LayerGroup entry = layerGroups().get(rowIndex);
switch (columnIndex) {
case COLUMN_ID:
// Do nothing.
break;
case COLUMN_NAME:
layerGroupEditor.setGroupName(entry.getId(), aValue.toString());
break;
case COLUMN_VISIBLE:
layerGroupEditor.setGroupVisible(entry.getId(), (boolean) aValue);
break;
default:
throw new IllegalArgumentException("Invalid column index: " + columnIndex);
}
}
public LayerGroup getDataAt(int index) {
return layerGroups().get(index);
}
protected abstract boolean isNameColumnEditable();
protected abstract boolean isVisibleColumnEditable();
private List<LayerGroup> layerGroups() {
return getLayerGroups().values().stream().collect(Collectors.toList());
}
private Map<Integer, LayerGroup> getLayerGroups() {
return modelManager.getModel().getLayoutModel().getPropertyLayerGroups().getValue();
}
}

View File

@@ -0,0 +1,406 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.layer;
import static java.util.Objects.requireNonNull;
import static org.opentcs.util.Assertions.checkArgument;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.SwingUtilities;
import org.jhotdraw.draw.Drawing;
import org.opentcs.customizations.ApplicationEventBus;
import org.opentcs.data.model.visualization.Layer;
import org.opentcs.data.model.visualization.LayerGroup;
import org.opentcs.guing.base.components.layer.LayerWrapper;
import org.opentcs.guing.base.model.DrawnModelComponent;
import org.opentcs.guing.common.application.ViewManager;
import org.opentcs.guing.common.event.SystemModelTransitionEvent;
import org.opentcs.guing.common.model.SystemModel;
import org.opentcs.util.event.EventBus;
/**
* The default implementation of {@link LayerManager}.
*/
public class DefaultLayerManager
implements
LayerManager,
LayerGroupManager {
/**
* The sets of model components mapped to the IDs of the layers the model components are drawn on.
*/
private final Map<Integer, Set<DrawnModelComponent>> components = new HashMap<>();
/**
* The view manager.
*/
private final ViewManager viewManager;
/**
* The application's event bus.
*/
private final EventBus eventBus;
/**
* The listeners for layer group data changes.
*/
private final Set<LayerGroupChangeListener> layerGroupChangeListeners = new HashSet<>();
/**
* The listener for layer data changes.
*/
private LayerChangeListener layerChangeListener;
/**
* The system model we're working with.
*/
private SystemModel systemModel;
/**
* Whether this instance is initialized or not.
*/
private boolean initialized;
@Inject
public DefaultLayerManager(
ViewManager viewManager,
@ApplicationEventBus
EventBus eventBus
) {
this.viewManager = requireNonNull(viewManager, "viewManager");
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 setLayerChangeListener(LayerChangeListener listener) {
layerChangeListener = listener;
}
@Override
public void setLayerVisible(int layerId, boolean visible) {
checkArgument(
getLayerWrapper(layerId) != null,
"A layer with layer ID '%d' doesn't exist.",
layerId
);
LayerWrapper wrapper = getLayerWrapper(layerId);
// We want to manipulate the drawing (by adding or removing figures) only if the visible state
// of the layer has actually changed. This prevents figures from being added multiple times and
// figures from being removed although they are no longer present in the drawing.
if (wrapper.getLayer().isVisible() == visible) {
return;
}
boolean visibleBefore = wrapper.getLayer().isVisible() && wrapper.getLayerGroup().isVisible();
Layer oldLayer = wrapper.getLayer();
Layer newLayer = oldLayer.withVisible(visible);
wrapper.setLayer(newLayer);
boolean visibleAfter = wrapper.getLayer().isVisible() && wrapper.getLayerGroup().isVisible();
if (visibleBefore != visibleAfter) {
layerVisibilityChanged(newLayer, visibleAfter);
}
layerChangeListener.layersChanged();
}
@Override
public void setLayerName(int layerId, String name) {
checkArgument(
getLayerWrapper(layerId) != null,
"A layer with layer ID '%d' doesn't exist.",
layerId
);
LayerWrapper wrapper = getLayerWrapper(layerId);
Layer oldLayer = wrapper.getLayer();
Layer newLayer = oldLayer.withName(name);
wrapper.setLayer(newLayer);
layerChangeListener.layersChanged();
}
@Override
public void onEvent(Object event) {
if (!(event instanceof SystemModelTransitionEvent)) {
return;
}
SystemModelTransitionEvent evt = (SystemModelTransitionEvent) event;
switch (evt.getStage()) {
case UNLOADED:
reset();
break;
case LOADED:
systemModel = evt.getModel();
restoreLayers();
break;
default: // Do nothing.
}
}
@Override
public void addLayerGroupChangeListener(LayerGroupChangeListener listener) {
layerGroupChangeListeners.add(listener);
}
@Override
public boolean containsComponents(int layerId) {
return !components.getOrDefault(layerId, new HashSet<>()).isEmpty();
}
@Override
public void setGroupVisible(int groupId, boolean visible) {
checkArgument(
getLayerGroup(groupId) != null,
"A layer group with layer group ID '%d' doesn't exist.",
groupId
);
LayerGroup oldGroup = getLayerGroup(groupId);
if (oldGroup.isVisible() == visible) {
return;
}
Map<Boolean, List<LayerWrapper>> wrappersByLayerVisibility
= getLayerWrappers().values().stream()
.filter(wrapper -> wrapper.getLayer().getGroupId() == groupId)
.collect(Collectors.partitioningBy(wrapper -> wrapper.getLayer().isVisible()));
// We only need to take care of "visible" layers. Non-visible layers are not affected by
// any changes to a group's visibility.
for (LayerWrapper wrapper : wrappersByLayerVisibility.get(Boolean.TRUE)) {
if (wrapper.getLayerGroup().isVisible() != visible) {
layerVisibilityChanged(wrapper.getLayer(), visible);
}
}
// Update the group for all layer wrappers that are assigned to it.
LayerGroup newGroup = oldGroup.withVisible(visible);
getLayerWrappers().values().stream()
.filter(wrapper -> wrapper.getLayer().getGroupId() == groupId)
.forEach(wrapper -> wrapper.setLayerGroup(newGroup));
// Update the group in the system model's layout.
getLayerGroups().put(groupId, newGroup);
notifyGroupsChanged();
}
@Override
public void setGroupName(int groupId, String name) {
checkArgument(
getLayerGroup(groupId) != null,
"A layer group with layer group ID '%d' doesn't exist.",
groupId
);
LayerGroup oldGroup = getLayerGroup(groupId);
LayerGroup newGroup = oldGroup.withName(name);
// Update the group for all layer wrappers that are assigned to it.
getLayerWrappers().values().stream()
.filter(wrapper -> wrapper.getLayer().getGroupId() == groupId)
.forEach(wrapper -> wrapper.setLayerGroup(newGroup));
// Update the group in the system model's layout.
getLayerGroups().put(groupId, newGroup);
notifyGroupsChanged();
layerChangeListener.layersChanged();
}
protected void reset() {
components.clear();
}
protected void restoreLayers() {
restoreComponentsMap();
notifyGroupsInitialized();
layerChangeListener.layersInitialized();
}
protected LayerChangeListener getLayerChangeListener() {
return layerChangeListener;
}
/**
* Returns the system model the layer manager is working with.
*
* @return The system model.
*/
protected SystemModel getSystemModel() {
return systemModel;
}
/**
* Returns the model components mapped to the IDs of the layers they are drawn on.
*
* @return The model components.
*/
protected Map<Integer, Set<DrawnModelComponent>> getComponents() {
return components;
}
/**
* Returns the view manager the layer manager is working with.
*
* @return The view manager.
*/
protected ViewManager getViewManager() {
return viewManager;
}
/**
* Returns the set of drawings the layer manager is working with.
*
* @return The set of drawings the layer manager is working with.
*/
protected Set<Drawing> getDrawings() {
return viewManager.getDrawingViewMap().values().stream()
.map(scrollPane -> scrollPane.getDrawingView().getDrawing())
.collect(Collectors.toSet());
}
/**
* Maps the given model component to the given layer ID.
*
* @param modelComponent The model component to add.
* @param layerId The ID of the layer to map the model component to.
* @see #getComponents()
*/
protected void addComponent(DrawnModelComponent modelComponent, int layerId) {
components.get(layerId).add(modelComponent);
}
/**
* Removes the mapping for the given model component.
*
* @param modelComponent The model component to remove.
* @see #getComponents()
*/
protected void removeComponent(DrawnModelComponent modelComponent) {
LayerWrapper layerWrapper = modelComponent.getPropertyLayerWrapper().getValue();
components.get(layerWrapper.getLayer().getId()).remove(modelComponent);
}
/**
* Returns the layer wrappers in the system model's layout.
*
* @return The layer wrappers in the system model's layout.
*/
protected Map<Integer, LayerWrapper> getLayerWrappers() {
return systemModel.getLayoutModel().getPropertyLayerWrappers().getValue();
}
/**
* Returns the layer wrapper for the given layer ID.
*
* @param layerId The layer ID.
* @return The layer wrapper.
*/
protected LayerWrapper getLayerWrapper(int layerId) {
return getLayerWrappers().get(layerId);
}
/**
* Returns the layer groups in the system model's layout.
*
* @return The layer groups in the system model's layout.
*/
protected Map<Integer, LayerGroup> getLayerGroups() {
return systemModel.getLayoutModel().getPropertyLayerGroups().getValue();
}
/**
* Returns the layer group for the given layer ID.
*
* @param groupId The layer group ID.
* @return The layer group.
*/
protected LayerGroup getLayerGroup(int groupId) {
return getLayerGroups().get(groupId);
}
protected void notifyGroupsInitialized() {
for (LayerGroupChangeListener listener : layerGroupChangeListeners) {
listener.groupsInitialized();
}
}
protected void notifyGroupsChanged() {
for (LayerGroupChangeListener listener : layerGroupChangeListeners) {
listener.groupsChanged();
}
}
protected void notifyGroupAdded() {
for (LayerGroupChangeListener listener : layerGroupChangeListeners) {
listener.groupAdded();
}
}
protected void notifyGroupRemoved() {
for (LayerGroupChangeListener listener : layerGroupChangeListeners) {
listener.groupRemoved();
}
}
private void restoreComponentsMap() {
// Prepare an entry in the components map for every registered layer.
for (LayerWrapper wrapper : getLayerWrappers().values()) {
components.put(wrapper.getLayer().getId(), new HashSet<>());
}
List<DrawnModelComponent> drawnModelComponents = new ArrayList<>();
drawnModelComponents.addAll(systemModel.getPointModels());
drawnModelComponents.addAll(systemModel.getPathModels());
drawnModelComponents.addAll(systemModel.getLocationModels());
drawnModelComponents.addAll(systemModel.getLinkModels());
// Add all model components to their respective layer.
for (DrawnModelComponent modelComponent : drawnModelComponents) {
addComponent(
modelComponent,
modelComponent.getPropertyLayerWrapper().getValue().getLayer().getId()
);
}
}
protected void layerVisibilityChanged(Layer layer, boolean visible) {
Set<Drawing> drawings = getDrawings();
SwingUtilities.invokeLater(() -> drawings.forEach(drawing -> drawing.willChange()));
SwingUtilities.invokeLater(() -> drawings.forEach(drawing -> drawing.changed()));
}
}

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.layer;
import java.awt.Color;
import java.awt.Component;
import javax.swing.JCheckBox;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.table.TableCellRenderer;
/**
* A table cell renderer for a disabled check box.
*/
public class DisabledCheckBoxCellRenderer
implements
TableCellRenderer {
private final JCheckBox checkBox;
public DisabledCheckBoxCellRenderer() {
checkBox = new JCheckBox();
checkBox.setHorizontalAlignment(SwingConstants.CENTER);
}
@Override
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column
) {
Color bg = isSelected ? table.getSelectionBackground() : table.getBackground();
checkBox.setBackground(bg);
checkBox.setEnabled(false);
checkBox.setSelected(value == Boolean.TRUE);
return checkBox;
}
}

View File

@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.layer;
/**
* Listens for changes to/updates on layer data.
*/
public interface LayerChangeListener {
/**
* Notifies the listener that the layer data has been initialized.
*/
void layersInitialized();
/**
* Notifies the listener that some layer data has changed.
*/
void layersChanged();
/**
* Notifies the listener that a layer has been added.
*/
void layerAdded();
/**
* Notifies the listener that a layer has been removed.
*/
void layerRemoved();
}

View File

@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.layer;
/**
* Provides methods to edit layers.
*/
public interface LayerEditor {
/**
* Sets a layer's visible state.
*
* @param layerId The ID of the layer.
* @param visible The layer's new visible state.
*/
void setLayerVisible(int layerId, boolean visible);
/**
* Sets a layer's name.
*
* @param layerId The ID of the layer.
* @param name The layer's new name.
*/
void setLayerName(int layerId, String name);
}

View File

@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.layer;
import javax.swing.table.DefaultTableCellRenderer;
import org.opentcs.data.model.visualization.LayerGroup;
/**
* A table cell renderer for {@link LayerGroup}s.
*/
public class LayerGroupCellRenderer
extends
DefaultTableCellRenderer {
/**
* Creates a new instance.
*/
public LayerGroupCellRenderer() {
}
@Override
protected void setValue(Object value) {
setText(((LayerGroup) value).getName());
}
}

View File

@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.layer;
/**
* Listens for changes to/updates on layer group data.
*/
public interface LayerGroupChangeListener {
/**
* Notifies the listener that the layer group data has been initialized.
*/
void groupsInitialized();
/**
* Notifies the listener that some layer group data has changed.
*/
void groupsChanged();
/**
* Notifies the listener that a layer group has been added.
*/
void groupAdded();
/**
* Notifies the listener that a layer group has been removed.
*/
void groupRemoved();
}

View File

@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.layer;
/**
* Provides methods to edit layer groups.
*/
public interface LayerGroupEditor {
/**
* Sets a layer group's visible state.
*
* @param groupId The ID of the layer group.
* @param visible The layer group's new visible state.
*/
void setGroupVisible(int groupId, boolean visible);
/**
* Sets a layer group's name.
*
* @param groupId The ID of the layer group.
* @param name The layer group's new name.
*/
void setGroupName(int groupId, String name);
}

View File

@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.layer;
/**
* Organizes the layer groups in a plant model.
*/
public interface LayerGroupManager
extends
LayerGroupEditor {
/**
* Add a listener to the set that's notified each time a change to group data occurs.
*
* @param listener The listener to add.
*/
void addLayerGroupChangeListener(LayerGroupChangeListener listener);
}

View File

@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.layer;
import org.opentcs.components.Lifecycle;
import org.opentcs.util.event.EventHandler;
/**
* Organizes the layers in a plant model.
*/
public interface LayerManager
extends
Lifecycle,
LayerEditor,
EventHandler {
/**
* Sets the listener for layer data changes.
*
* @param listener The listener for layer data changes.
*/
void setLayerChangeListener(LayerChangeListener listener);
/**
* Returns whether the layer with the given layer ID contains any components.
*
* @param layerId The ID of the layer.
* @return Whether the layer contains any components.
*/
boolean containsComponents(int layerId);
}

View File

@@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.properties;
import javax.swing.JComponent;
import org.opentcs.guing.base.model.ModelComponent;
import org.opentcs.thirdparty.guing.common.jhotdraw.application.action.edit.UndoRedoManager;
/**
* Base implementation for visualisation of model component properties.
*/
public abstract class AbstractAttributesContent
implements
AttributesContent {
/**
* The model component to show the properties of.
*/
protected ModelComponent fModel;
/**
* The undo manager.
*/
protected UndoRedoManager fUndoRedoManager;
/**
* The swing component.
*/
protected JComponent fComponent;
/**
* Creates a new instance of AbstractAttributesContent
*/
public AbstractAttributesContent() {
}
@Override // AttributesContent
public void setModel(ModelComponent model) {
fModel = model;
}
@Override // AttributesContent
public abstract void reset();
@Override // AttributesContent
public JComponent getComponent() {
return fComponent;
}
@Override // AttributesContent
public String getDescription() {
return fModel.getDescription();
}
@Override // AttributesContent
public void setup(UndoRedoManager undoManager) {
fUndoRedoManager = undoManager;
fComponent = createComponent();
}
/**
* Creates the component.
*
* @return The created component.
*/
protected abstract JComponent createComponent();
}

View File

@@ -0,0 +1,133 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.guing.common.components.properties;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Provider;
import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import org.opentcs.guing.base.components.properties.type.Property;
import org.opentcs.guing.common.components.properties.event.TableChangeListener;
import org.opentcs.guing.common.components.properties.event.TableSelectionChangeEvent;
import org.opentcs.guing.common.components.properties.table.AttributesTable;
/**
* Base implementation for content that displays model properties in a table.
*/
public abstract class AbstractTableContent
extends
AbstractAttributesContent
implements
TableChangeListener {
/**
* The table that shows the properties.
*/
protected AttributesTable fTable;
/**
* The cell editors.
*/
protected List<TableCellEditor> fCellEditors = new ArrayList<>();
/**
* Indicates that changes to the tabel model should also update the model component.
*/
protected boolean fEvaluateTableChanges = true;
/**
* Provides attribute tables.
*/
private final Provider<AttributesTable> tableProvider;
/**
* Creates a new instance.
*
* @param tableProvider Provides attribute tables.
*/
public AbstractTableContent(Provider<AttributesTable> tableProvider) {
this.tableProvider = requireNonNull(tableProvider, "tableProvider");
}
@Override // AbstractAttributesContent
protected JComponent createComponent() {
JPanel component = new JPanel();
initTable();
JScrollPane scrollPane = new JScrollPane(fTable);
component.setLayout(new BorderLayout());
component.add(scrollPane, BorderLayout.CENTER);
return component;
}
@Override // TableChangeListener
public void tableSelectionChanged(TableSelectionChangeEvent e) {
}
@Override // TableChangeListener
public void tableModelChanged() {
}
/**
* Initialises the table.
*/
protected void initTable() {
fTable = tableProvider.get();
setTableCellRenderers();
setTableCellEditors();
fTable.addTableChangeListener(this);
}
/**
* Set the table cell renderers.
*/
protected void setTableCellRenderers() {
}
/**
* Set the table cell editors.
*/
protected void setTableCellEditors() {
}
/**
* Set new table content.
*
* @param content A map from property name to property.
*/
protected void setTableContent(Map<String, Property> content) {
fEvaluateTableChanges = false;
TableColumnModel columnModel = fTable.getColumnModel();
int[] widths = new int[columnModel.getColumnCount()];
for (int i = 0; i < widths.length; i++) {
widths[i] = columnModel.getColumn(i).getWidth();
}
fTable.setModel(createTableModel(content));
for (int i = 0; i < widths.length; i++) {
columnModel.getColumn(i).setPreferredWidth(widths[i]);
}
fEvaluateTableChanges = true;
}
/**
* Creates a new table model from the content.
*
* @param content Map from property name to property.
* @return A table model that represents the content.
*/
protected abstract TableModel createTableModel(Map<String, Property> content);
}

Some files were not shown because too many files have changed in this diff Show More