java/src/jmri/jmrit/display/layoutEditor/LayoutEditor.java
package jmri.jmrit.display.layoutEditor;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.io.File;
import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.List;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import jmri.*;
import jmri.configurexml.StoreXmlUserAction;
import jmri.jmrit.catalog.NamedIcon;
import jmri.jmrit.dispatcher.DispatcherAction;
import jmri.jmrit.dispatcher.DispatcherFrame;
import jmri.jmrit.display.*;
import jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.*;
import jmri.jmrit.display.layoutEditor.LayoutEditorToolBarPanel.LocationFormat;
import jmri.jmrit.display.panelEditor.PanelEditor;
import jmri.jmrit.entryexit.AddEntryExitPairAction;
import jmri.jmrit.logixng.GlobalVariable;
import jmri.swing.NamedBeanComboBox;
import jmri.util.*;
import jmri.util.swing.JComboBoxUtil;
import jmri.util.swing.JmriColorChooser;
import jmri.util.swing.JmriJOptionPane;
import jmri.util.swing.JmriMouseEvent;
/**
* Provides a scrollable Layout Panel and editor toolbars (that can be hidden)
* <p>
* This module serves as a manager for the LayoutTurnout, Layout Block,
* PositionablePoint, Track Segment, LayoutSlip and LevelXing objects which are
* integral subparts of the LayoutEditor class.
* <p>
* All created objects are put on specific levels depending on their type
* (higher levels are in front): Note that higher numbers appear behind lower
* numbers.
* <p>
* The "contents" List keeps track of all text and icon label objects added to
* the target frame for later manipulation. Other Lists keep track of drawn
* items.
* <p>
* Based in part on PanelEditor.java (Bob Jacobsen (c) 2002, 2003). In
* particular, text and icon label items are copied from Panel editor, as well
* as some of the control design.
*
* @author Dave Duchamp Copyright: (c) 2004-2007
* @author George Warner Copyright: (c) 2017-2019
*/
final public class LayoutEditor extends PanelEditor implements MouseWheelListener, LayoutModels {
// Operational instance variables - not saved to disk
private JmriJFrame floatingEditToolBoxFrame = null;
private JScrollPane floatingEditContentScrollPane = null;
private JPanel floatEditHelpPanel = null;
private JPanel editToolBarContainerPanel = null;
private JScrollPane editToolBarScrollPane = null;
private JPanel helpBarPanel = null;
private final JPanel helpBar = new JPanel();
private final boolean editorUseOldLocSize;
private LayoutEditorToolBarPanel leToolBarPanel = null;
@Nonnull
public LayoutEditorToolBarPanel getLayoutEditorToolBarPanel() {
return leToolBarPanel;
}
// end of main panel controls
private boolean delayedPopupTrigger = false;
private Point2D currentPoint = new Point2D.Double(100.0, 100.0);
private Point2D dLoc = new Point2D.Double(0.0, 0.0);
private int toolbarHeight = 100;
private int toolbarWidth = 100;
private TrackSegment newTrack = null;
private boolean panelChanged = false;
// size of point boxes
public static final double SIZE = 3.0;
public static final double SIZE2 = SIZE * 2.; // must be twice SIZE
public Color turnoutCircleColor = Color.black; // matches earlier versions
public Color turnoutCircleThrownColor = Color.black;
private boolean turnoutFillControlCircles = false;
private int turnoutCircleSize = 4; // matches earlier versions
// use turnoutCircleSize when you need an int and these when you need a double
// note: these only change when setTurnoutCircleSize is called
// using these avoids having to call getTurnoutCircleSize() and
// the multiply (x2) and the int -> double conversion overhead
public double circleRadius = SIZE * getTurnoutCircleSize();
public double circleDiameter = 2.0 * circleRadius;
// selection variables
public boolean selectionActive = false;
private double selectionX = 0.0;
private double selectionY = 0.0;
public double selectionWidth = 0.0;
public double selectionHeight = 0.0;
// Option menu items
private JCheckBoxMenuItem editModeCheckBoxMenuItem = null;
private JRadioButtonMenuItem toolBarSideTopButton = null;
private JRadioButtonMenuItem toolBarSideLeftButton = null;
private JRadioButtonMenuItem toolBarSideBottomButton = null;
private JRadioButtonMenuItem toolBarSideRightButton = null;
private JRadioButtonMenuItem toolBarSideFloatButton = null;
private final JCheckBoxMenuItem wideToolBarCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("ToolBarWide"));
private JCheckBoxMenuItem positionableCheckBoxMenuItem = null;
private JCheckBoxMenuItem controlCheckBoxMenuItem = null;
private JCheckBoxMenuItem animationCheckBoxMenuItem = null;
private JCheckBoxMenuItem showHelpCheckBoxMenuItem = null;
private JCheckBoxMenuItem showGridCheckBoxMenuItem = null;
private JCheckBoxMenuItem autoAssignBlocksCheckBoxMenuItem = null;
private JMenu scrollMenu = null;
private JRadioButtonMenuItem scrollBothMenuItem = null;
private JRadioButtonMenuItem scrollNoneMenuItem = null;
private JRadioButtonMenuItem scrollHorizontalMenuItem = null;
private JRadioButtonMenuItem scrollVerticalMenuItem = null;
private JMenu tooltipMenu = null;
private JRadioButtonMenuItem tooltipAlwaysMenuItem = null;
private JRadioButtonMenuItem tooltipNoneMenuItem = null;
private JRadioButtonMenuItem tooltipInEditMenuItem = null;
private JRadioButtonMenuItem tooltipNotInEditMenuItem = null;
private JCheckBoxMenuItem pixelsCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("Pixels"));
private JCheckBoxMenuItem metricCMCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("MetricCM"));
private JCheckBoxMenuItem englishFeetInchesCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("EnglishFeetInches"));
private JCheckBoxMenuItem snapToGridOnAddCheckBoxMenuItem = null;
private JCheckBoxMenuItem snapToGridOnMoveCheckBoxMenuItem = null;
private JCheckBoxMenuItem antialiasingOnCheckBoxMenuItem = null;
private JCheckBoxMenuItem drawLayoutTracksLabelCheckBoxMenuItem = null;
private JCheckBoxMenuItem turnoutCirclesOnCheckBoxMenuItem = null;
private JCheckBoxMenuItem turnoutDrawUnselectedLegCheckBoxMenuItem = null;
private JCheckBoxMenuItem turnoutFillControlCirclesCheckBoxMenuItem = null;
private JCheckBoxMenuItem hideTrackSegmentConstructionLinesCheckBoxMenuItem = null;
private JCheckBoxMenuItem useDirectTurnoutControlCheckBoxMenuItem = null;
private ButtonGroup turnoutCircleSizeButtonGroup = null;
private boolean turnoutDrawUnselectedLeg = true;
private boolean autoAssignBlocks = false;
// Tools menu items
private final JMenu zoomMenu = new JMenu(Bundle.getMessage("MenuZoom"));
private final JRadioButtonMenuItem zoom025Item = new JRadioButtonMenuItem("x 0.25");
private final JRadioButtonMenuItem zoom05Item = new JRadioButtonMenuItem("x 0.5");
private final JRadioButtonMenuItem zoom075Item = new JRadioButtonMenuItem("x 0.75");
private final JRadioButtonMenuItem noZoomItem = new JRadioButtonMenuItem(Bundle.getMessage("NoZoom"));
private final JRadioButtonMenuItem zoom15Item = new JRadioButtonMenuItem("x 1.5");
private final JRadioButtonMenuItem zoom20Item = new JRadioButtonMenuItem("x 2.0");
private final JRadioButtonMenuItem zoom30Item = new JRadioButtonMenuItem("x 3.0");
private final JRadioButtonMenuItem zoom40Item = new JRadioButtonMenuItem("x 4.0");
private final JRadioButtonMenuItem zoom50Item = new JRadioButtonMenuItem("x 5.0");
private final JRadioButtonMenuItem zoom60Item = new JRadioButtonMenuItem("x 6.0");
private final JRadioButtonMenuItem zoom70Item = new JRadioButtonMenuItem("x 7.0");
private final JRadioButtonMenuItem zoom80Item = new JRadioButtonMenuItem("x 8.0");
private final JMenuItem undoTranslateSelectionMenuItem = new JMenuItem(Bundle.getMessage("UndoTranslateSelection"));
private final JMenuItem assignBlockToSelectionMenuItem = new JMenuItem(Bundle.getMessage("AssignBlockToSelectionTitle") + "...");
// Selected point information
private Point2D startDelta = new Point2D.Double(0.0, 0.0); // starting delta coordinates
public Object selectedObject = null; // selected object, null if nothing selected
public Object prevSelectedObject = null; // previous selected object, for undo
private HitPointType selectedHitPointType = HitPointType.NONE; // hit point type within the selected object
public LayoutTrack foundTrack = null; // found object, null if nothing found
public LayoutTrackView foundTrackView = null; // found view object, null if nothing found
private Point2D foundLocation = new Point2D.Double(0.0, 0.0); // location of found object
public HitPointType foundHitPointType = HitPointType.NONE; // connection type within the found object
public LayoutTrack beginTrack = null; // begin track segment connection object, null if none
public Point2D beginLocation = new Point2D.Double(0.0, 0.0); // location of begin object
private HitPointType beginHitPointType = HitPointType.NONE; // connection type within begin connection object
public Point2D currentLocation = new Point2D.Double(0.0, 0.0); // current location
// Lists of items that describe the Layout, and allow it to be drawn
// Each of the items must be saved to disk over sessions
private List<AnalogClock2Display> clocks = new ArrayList<>(); // fast clocks
private List<LocoIcon> markerImage = new ArrayList<>(); // marker images
private List<MultiSensorIcon> multiSensors = new ArrayList<>(); // multi-sensor images
private List<PositionableLabel> backgroundImage = new ArrayList<>(); // background images
private List<PositionableLabel> labelImage = new ArrayList<>(); // positionable label images
private List<SensorIcon> sensorImage = new ArrayList<>(); // sensor images
private List<SignalHeadIcon> signalHeadImage = new ArrayList<>(); // signal head images
// PositionableLabel's
private List<BlockContentsIcon> blockContentsLabelList = new ArrayList<>(); // BlockContentsIcon Label List
private List<MemoryIcon> memoryLabelList = new ArrayList<>(); // Memory Label List
private List<GlobalVariableIcon> globalVariableLabelList = new ArrayList<>(); // LogixNG Global Variable Label List
private List<SensorIcon> sensorList = new ArrayList<>(); // Sensor Icons
private List<SignalHeadIcon> signalList = new ArrayList<>(); // Signal Head Icons
private List<SignalMastIcon> signalMastList = new ArrayList<>(); // Signal Mast Icons
public final LayoutEditorViewContext gContext = new LayoutEditorViewContext(); // public for now, as things work access changes
@Nonnull
public List<SensorIcon> getSensorList() {
return sensorList;
}
@Nonnull
public List<PositionableLabel> getLabelImageList() {
return labelImage;
}
@Nonnull
public List<BlockContentsIcon> getBlockContentsLabelList() {
return blockContentsLabelList;
}
@Nonnull
public List<MemoryIcon> getMemoryLabelList() {
return memoryLabelList;
}
@Nonnull
public List<GlobalVariableIcon> getGlobalVariableLabelList() {
return globalVariableLabelList;
}
@Nonnull
public List<SignalHeadIcon> getSignalList() {
return signalList;
}
@Nonnull
public List<SignalMastIcon> getSignalMastList() {
return signalMastList;
}
private final List<LayoutShape> layoutShapes = new ArrayList<>(); // LayoutShap list
// counts used to determine unique internal names
private int numAnchors = 0;
private int numEndBumpers = 0;
private int numEdgeConnectors = 0;
private int numTrackSegments = 0;
private int numLevelXings = 0;
private int numLayoutSlips = 0;
private int numLayoutTurnouts = 0;
private int numLayoutTurntables = 0;
private LayoutEditorFindItems finder = new LayoutEditorFindItems(this);
@Nonnull
public LayoutEditorFindItems getFinder() {
return finder;
}
private Color mainlineTrackColor = Color.DARK_GRAY;
private Color sidelineTrackColor = Color.DARK_GRAY;
public Color defaultTrackColor = Color.DARK_GRAY;
private Color defaultOccupiedTrackColor = Color.red;
private Color defaultAlternativeTrackColor = Color.white;
private Color defaultTextColor = Color.black;
private String layoutName = "";
private boolean animatingLayout = true;
private boolean showHelpBar = true;
private boolean drawGrid = true;
private boolean snapToGridOnAdd = false;
private boolean snapToGridOnMove = false;
private boolean snapToGridInvert = false;
private boolean antialiasingOn = false;
private boolean drawLayoutTracksLabel = false;
private boolean highlightSelectedBlockFlag = false;
private boolean turnoutCirclesWithoutEditMode = false;
private boolean tooltipsWithoutEditMode = false;
private boolean tooltipsInEditMode = true;
private boolean tooltipsAlwaysOrNever = false; // When true, don't call setAllShowToolTip().
// turnout size parameters - saved with panel
private double turnoutBX = LayoutTurnout.turnoutBXDefault; // RH, LH, WYE
private double turnoutCX = LayoutTurnout.turnoutCXDefault;
private double turnoutWid = LayoutTurnout.turnoutWidDefault;
private double xOverLong = LayoutTurnout.xOverLongDefault; // DOUBLE_XOVER, RH_XOVER, LH_XOVER
private double xOverHWid = LayoutTurnout.xOverHWidDefault;
private double xOverShort = LayoutTurnout.xOverShortDefault;
private boolean useDirectTurnoutControl = false; // Uses Left click for closing points, Right click for throwing.
// saved state of options when panel was loaded or created
private boolean savedEditMode = true;
private boolean savedPositionable = true;
private boolean savedControlLayout = true;
private boolean savedAnimatingLayout = true;
private boolean savedShowHelpBar = true;
// zoom
private double minZoom = 0.25;
private final double maxZoom = 8.0;
// A hash to store string -> KeyEvent constants, used to set keyboard shortcuts per locale
private HashMap<String, Integer> stringsToVTCodes = new HashMap<>();
/*==============*\
|* Toolbar side *|
\*==============*/
private enum ToolBarSide {
eTOP("top"),
eLEFT("left"),
eBOTTOM("bottom"),
eRIGHT("right"),
eFLOAT("float");
private final String name;
private static final Map<String, ToolBarSide> ENUM_MAP;
ToolBarSide(String name) {
this.name = name;
}
// Build an immutable map of String name to enum pairs.
static {
Map<String, ToolBarSide> map = new ConcurrentHashMap<>();
for (ToolBarSide instance : ToolBarSide.values()) {
map.put(instance.getName(), instance);
}
ENUM_MAP = Collections.unmodifiableMap(map);
}
public static ToolBarSide getName(@CheckForNull String name) {
return ENUM_MAP.get(name);
}
public String getName() {
return name;
}
}
private ToolBarSide toolBarSide = ToolBarSide.eTOP;
public LayoutEditor() {
this("My Layout");
}
public LayoutEditor(@Nonnull String name) {
super(name);
setSaveSize(true);
layoutName = name;
editorUseOldLocSize = InstanceManager.getDefault(jmri.util.gui.GuiLafPreferencesManager.class).isEditorUseOldLocSize();
// initialise keycode map
initStringsToVTCodes();
setupToolBar();
setupMenuBar();
super.setDefaultToolTip(new ToolTip(null, 0, 0, new Font("SansSerif", Font.PLAIN, 12),
Color.black, new Color(215, 225, 255), Color.black, null));
// setup help bar
helpBar.setLayout(new BoxLayout(helpBar, BoxLayout.PAGE_AXIS));
JTextArea helpTextArea1 = new JTextArea(Bundle.getMessage("Help1"));
helpBar.add(helpTextArea1);
JTextArea helpTextArea2 = new JTextArea(Bundle.getMessage("Help2"));
helpBar.add(helpTextArea2);
String helpText3 = "";
switch (SystemType.getType()) {
case SystemType.MACOSX: {
helpText3 = Bundle.getMessage("Help3Mac");
break;
}
case SystemType.WINDOWS:
case SystemType.LINUX: {
helpText3 = Bundle.getMessage("Help3Win");
break;
}
default:
helpText3 = Bundle.getMessage("Help3");
}
JTextArea helpTextArea3 = new JTextArea(helpText3);
helpBar.add(helpTextArea3);
// set to full screen
Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
gContext.setWindowWidth(screenDim.width - 20);
gContext.setWindowHeight(screenDim.height - 120);
// Let Editor make target, and use this frame
super.setTargetPanel(null, null);
super.setTargetPanelSize(gContext.getWindowWidth(), gContext.getWindowHeight());
setSize(screenDim.width, screenDim.height);
// register the resulting panel for later configuration
InstanceManager.getOptionalDefault(ConfigureManager.class)
.ifPresent(cm -> cm.registerUser(this));
// confirm that panel hasn't already been loaded
if (!this.equals(InstanceManager.getDefault(EditorManager.class).get(name))) {
log.warn("File contains a panel with the same name ({}) as an existing panel", name);
}
setFocusable(true);
addKeyListener(this);
resetDirty();
// establish link to LayoutEditor Tools
auxTools = getLEAuxTools();
SwingUtilities.invokeLater(() -> {
// initialize preferences
InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> {
String windowFrameRef = getWindowFrameRef();
Object prefsProp = prefsMgr.getProperty(windowFrameRef, "toolBarSide");
// log.debug("{}.toolBarSide is {}", windowFrameRef, prefsProp);
if (prefsProp != null) {
ToolBarSide newToolBarSide = ToolBarSide.getName((String) prefsProp);
setToolBarSide(newToolBarSide);
}
// Note: since prefs default to false and we want wide to be the default
// we invert it and save it as thin
boolean prefsToolBarIsWide = prefsMgr.getSimplePreferenceState(windowFrameRef + ".toolBarThin");
log.debug("{}.toolBarThin is {}", windowFrameRef, prefsProp);
setToolBarWide(prefsToolBarIsWide);
boolean prefsShowHelpBar = prefsMgr.getSimplePreferenceState(windowFrameRef + ".showHelpBar");
// log.debug("{}.showHelpBar is {}", windowFrameRef, prefsShowHelpBar);
setShowHelpBar(prefsShowHelpBar);
boolean prefsAntialiasingOn = prefsMgr.getSimplePreferenceState(windowFrameRef + ".antialiasingOn");
// log.debug("{}.antialiasingOn is {}", windowFrameRef, prefsAntialiasingOn);
setAntialiasingOn(prefsAntialiasingOn);
boolean prefsDrawLayoutTracksLabel = prefsMgr.getSimplePreferenceState(windowFrameRef + ".drawLayoutTracksLabel");
// log.debug("{}.drawLayoutTracksLabel is {}", windowFrameRef, prefsDrawLayoutTracksLabel);
setDrawLayoutTracksLabel(prefsDrawLayoutTracksLabel);
boolean prefsHighlightSelectedBlockFlag
= prefsMgr.getSimplePreferenceState(windowFrameRef + ".highlightSelectedBlock");
// log.debug("{}.highlightSelectedBlock is {}", windowFrameRef, prefsHighlightSelectedBlockFlag);
setHighlightSelectedBlock(prefsHighlightSelectedBlockFlag);
}); // InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr)
// make sure that the layoutEditorComponent is in the _targetPanel components
List<Component> componentList = Arrays.asList(_targetPanel.getComponents());
if (!componentList.contains(layoutEditorComponent)) {
try {
_targetPanel.remove(layoutEditorComponent);
_targetPanel.add(layoutEditorComponent, Integer.valueOf(3));
_targetPanel.moveToFront(layoutEditorComponent);
} catch (Exception e) {
log.warn("paintTargetPanelBefore: ", e);
}
}
});
}
@SuppressWarnings("deprecation") // getMenuShortcutKeyMask()
private void setupMenuBar() {
// initialize menu bar
JMenuBar menuBar = new JMenuBar();
// set up File menu
JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile"));
fileMenu.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("MenuFileMnemonic")));
menuBar.add(fileMenu);
StoreXmlUserAction store = new StoreXmlUserAction(Bundle.getMessage("FileMenuItemStore"));
int primary_modifier = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
store.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
stringsToVTCodes.get(Bundle.getMessage("MenuItemStoreAccelerator")), primary_modifier));
fileMenu.add(store);
fileMenu.addSeparator();
JMenuItem deleteItem = new JMenuItem(Bundle.getMessage("DeletePanel"));
fileMenu.add(deleteItem);
deleteItem.addActionListener((ActionEvent event) -> {
if (deletePanel()) {
dispose();
}
});
setJMenuBar(menuBar);
// setup Options menu
setupOptionMenu(menuBar);
// setup Tools menu
setupToolsMenu(menuBar);
// setup Zoom menu
setupZoomMenu(menuBar);
// setup marker menu
setupMarkerMenu(menuBar);
// Setup Dispatcher window
setupDispatcherMenu(menuBar);
// setup Help menu
addHelpMenu("package.jmri.jmrit.display.LayoutEditor", true);
}
@Override
public void newPanelDefaults() {
getLayoutTrackDrawingOptions().setMainRailWidth(2);
getLayoutTrackDrawingOptions().setSideRailWidth(1);
setBackgroundColor(defaultBackgroundColor);
JmriColorChooser.addRecentColor(defaultTrackColor);
JmriColorChooser.addRecentColor(defaultOccupiedTrackColor);
JmriColorChooser.addRecentColor(defaultAlternativeTrackColor);
JmriColorChooser.addRecentColor(defaultBackgroundColor);
JmriColorChooser.addRecentColor(defaultTextColor);
}
private final LayoutEditorComponent layoutEditorComponent = new LayoutEditorComponent(this);
private void setupToolBar() {
// Initial setup for both horizontal and vertical
Container contentPane = getContentPane();
// remove these (if present) so we can add them back (without duplicates)
if (editToolBarContainerPanel != null) {
editToolBarContainerPanel.setVisible(false);
contentPane.remove(editToolBarContainerPanel);
}
if (helpBarPanel != null) {
contentPane.remove(helpBarPanel);
}
deletefloatingEditToolBoxFrame();
if (toolBarSide.equals(ToolBarSide.eFLOAT)) {
createfloatingEditToolBoxFrame();
createFloatingHelpPanel();
return;
}
Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
boolean toolBarIsVertical = (toolBarSide.equals(ToolBarSide.eRIGHT) || toolBarSide.equals(ToolBarSide.eLEFT));
if (toolBarIsVertical) {
leToolBarPanel = new LayoutEditorVerticalToolBarPanel(this);
editToolBarScrollPane = new JScrollPane(leToolBarPanel);
toolbarWidth = editToolBarScrollPane.getPreferredSize().width;
toolbarHeight = screenDim.height;
} else {
leToolBarPanel = new LayoutEditorHorizontalToolBarPanel(this);
editToolBarScrollPane = new JScrollPane(leToolBarPanel);
toolbarWidth = screenDim.width;
toolbarHeight = editToolBarScrollPane.getPreferredSize().height;
}
editToolBarContainerPanel = new JPanel();
editToolBarContainerPanel.setLayout(new BoxLayout(editToolBarContainerPanel, BoxLayout.PAGE_AXIS));
editToolBarContainerPanel.add(editToolBarScrollPane);
// setup notification for when horizontal scrollbar changes visibility
// editToolBarScroll.getViewport().addChangeListener(e -> {
// log.warn("scrollbars visible: " + editToolBarScroll.getHorizontalScrollBar().isVisible());
//});
editToolBarContainerPanel.setMinimumSize(new Dimension(toolbarWidth, toolbarHeight));
editToolBarContainerPanel.setPreferredSize(new Dimension(toolbarWidth, toolbarHeight));
helpBarPanel = new JPanel();
helpBarPanel.add(helpBar);
for (Component c : helpBar.getComponents()) {
if (c instanceof JTextArea) {
JTextArea j = (JTextArea) c;
j.setSize(new Dimension(toolbarWidth, j.getSize().height));
j.setLineWrap(toolBarIsVertical);
j.setWrapStyleWord(toolBarIsVertical);
}
}
contentPane.setLayout(new BoxLayout(contentPane, toolBarIsVertical ? BoxLayout.LINE_AXIS : BoxLayout.PAGE_AXIS));
switch (toolBarSide) {
case eTOP:
case eLEFT:
contentPane.add(editToolBarContainerPanel, 0);
break;
case eBOTTOM:
case eRIGHT:
contentPane.add(editToolBarContainerPanel);
break;
default:
// fall through
break;
}
if (toolBarIsVertical) {
editToolBarContainerPanel.add(helpBarPanel);
} else {
contentPane.add(helpBarPanel);
}
helpBarPanel.setVisible(isEditable() && getShowHelpBar());
editToolBarContainerPanel.setVisible(isEditable());
}
private void createfloatingEditToolBoxFrame() {
if (isEditable() && floatingEditToolBoxFrame == null) {
// Create a scroll pane to hold the window content.
leToolBarPanel = new LayoutEditorFloatingToolBarPanel(this);
floatingEditContentScrollPane = new JScrollPane(leToolBarPanel);
floatingEditContentScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
floatingEditContentScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
// Create the window and add the toolbox content
floatingEditToolBoxFrame = new JmriJFrame(Bundle.getMessage("ToolBox", getLayoutName()));
floatingEditToolBoxFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
floatingEditToolBoxFrame.setContentPane(floatingEditContentScrollPane);
floatingEditToolBoxFrame.pack();
floatingEditToolBoxFrame.setAlwaysOnTop(true);
floatingEditToolBoxFrame.setVisible(true);
}
}
private void deletefloatingEditToolBoxFrame() {
if (floatingEditContentScrollPane != null) {
floatingEditContentScrollPane.removeAll();
floatingEditContentScrollPane = null;
}
if (floatingEditToolBoxFrame != null) {
floatingEditToolBoxFrame.dispose();
floatingEditToolBoxFrame = null;
}
}
private void createFloatingHelpPanel() {
if (leToolBarPanel instanceof LayoutEditorFloatingToolBarPanel) {
LayoutEditorFloatingToolBarPanel leftbp = (LayoutEditorFloatingToolBarPanel) leToolBarPanel;
floatEditHelpPanel = new JPanel();
leToolBarPanel.add(floatEditHelpPanel);
// Notice: End tree structure indenting
// Force the help panel width to the same as the tabs section
int tabSectionWidth = (int) leftbp.getPreferredSize().getWidth();
// Change the textarea settings
for (Component c : helpBar.getComponents()) {
if (c instanceof JTextArea) {
JTextArea j = (JTextArea) c;
j.setSize(new Dimension(tabSectionWidth, j.getSize().height));
j.setLineWrap(true);
j.setWrapStyleWord(true);
}
}
// Change the width of the help panel section
floatEditHelpPanel.setMaximumSize(new Dimension(tabSectionWidth, Integer.MAX_VALUE));
floatEditHelpPanel.add(helpBar);
floatEditHelpPanel.setVisible(isEditable() && getShowHelpBar());
}
}
@Override
public void init(String name) {
}
@Override
public void initView() {
editModeCheckBoxMenuItem.setSelected(isEditable());
positionableCheckBoxMenuItem.setSelected(allPositionable());
controlCheckBoxMenuItem.setSelected(allControlling());
if (isEditable()) {
if (!tooltipsAlwaysOrNever) {
setAllShowToolTip(tooltipsInEditMode);
setAllShowLayoutTurnoutToolTip(tooltipsInEditMode);
}
} else {
if (!tooltipsAlwaysOrNever) {
setAllShowToolTip(tooltipsWithoutEditMode);
setAllShowLayoutTurnoutToolTip(tooltipsWithoutEditMode);
}
}
scrollNoneMenuItem.setSelected(_scrollState == Editor.SCROLL_NONE);
scrollBothMenuItem.setSelected(_scrollState == Editor.SCROLL_BOTH);
scrollHorizontalMenuItem.setSelected(_scrollState == Editor.SCROLL_HORIZONTAL);
scrollVerticalMenuItem.setSelected(_scrollState == Editor.SCROLL_VERTICAL);
}
@Override
public void setSize(int w, int h) {
super.setSize(w, h);
}
@Override
public void targetWindowClosingEvent(WindowEvent e) {
boolean save = (isDirty() || (savedEditMode != isEditable())
|| (savedPositionable != allPositionable())
|| (savedControlLayout != allControlling())
|| (savedAnimatingLayout != isAnimating())
|| (savedShowHelpBar != getShowHelpBar()));
log.trace("Temp fix to disable CI errors: save = {}", save);
targetWindowClosing();
}
/**
* Set up NamedBeanComboBox
*
* @param inComboBox the NamedBeanComboBox to set up
* @param inValidateMode true to validate typed inputs; false otherwise
* @param inEnable boolean to enable / disable the NamedBeanComboBox
* @param inEditable boolean to make the NamedBeanComboBox editable
*/
public static void setupComboBox(@Nonnull NamedBeanComboBox<?> inComboBox, boolean inValidateMode, boolean inEnable, boolean inEditable) {
log.debug("LE setupComboBox called");
assert inComboBox != null;
inComboBox.setEnabled(inEnable);
inComboBox.setEditable(inEditable);
inComboBox.setValidatingInput(inValidateMode);
inComboBox.setSelectedIndex(-1);
// This has to be set before calling setupComboBoxMaxRows
// (otherwise if inFirstBlank then the number of rows will be wrong)
inComboBox.setAllowNull(!inValidateMode);
// set the max number of rows that will fit onscreen
JComboBoxUtil.setupComboBoxMaxRows(inComboBox);
inComboBox.setSelectedIndex(-1);
}
/**
* Grabs a subset of the possible KeyEvent constants and puts them into a
* hash for fast lookups later. These lookups are used to enable bundles to
* specify keyboard shortcuts on a per-locale basis.
*/
private void initStringsToVTCodes() {
Field[] fields = KeyEvent.class
.getFields();
for (Field field : fields) {
String name = field.getName();
if (name.startsWith("VK")) {
int code = 0;
try {
code = field.getInt(null);
} catch (IllegalAccessException | IllegalArgumentException e) {
// exceptions make me throw up...
}
String key = name.substring(3);
// log.debug("VTCode[{}]:'{}'", key, code);
stringsToVTCodes.put(key, code);
}
}
}
/**
* The Java run times for 11 and 12 running on macOS have a bug that causes double events for
* JCheckBoxMenuItem when invoked by an accelerator key combination.
* <p>
* The java.version property is parsed to determine the run time version. If the event occurs
* on macOS and Java 11 or 12 and a modifier key was active, true is returned. The five affected
* action events will drop the event and process the second occurrence.
* @aparam event The action event.
* @return true if the event is affected, otherwise return false.
*/
private boolean fixMacBugOn11(ActionEvent event) {
boolean result = false;
if (SystemType.isMacOSX()) {
if (event.getModifiers() != 0) {
// MacOSX and modifier key, test Java version
String version = System.getProperty("java.version");
if (version.startsWith("1.")) {
version = version.substring(2, 3);
} else {
int dot = version.indexOf(".");
if (dot != -1) {
version = version.substring(0, dot);
}
}
int vers = Integer.parseInt(version);
result = (vers == 11 || vers == 12);
}
}
return result;
}
/**
* Set up the Option menu.
*
* @param menuBar to add the option menu to
* @return option menu that was added
*/
@SuppressWarnings("deprecation") // getMenuShortcutKeyMask()
private JMenu setupOptionMenu(@Nonnull JMenuBar menuBar) {
assert menuBar != null;
JMenu optionMenu = new JMenu(Bundle.getMessage("MenuOptions"));
optionMenu.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("OptionsMnemonic")));
menuBar.add(optionMenu);
//
// edit mode
//
editModeCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("EditMode"));
optionMenu.add(editModeCheckBoxMenuItem);
editModeCheckBoxMenuItem.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("EditModeMnemonic")));
int primary_modifier = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
editModeCheckBoxMenuItem.setAccelerator(KeyStroke.getKeyStroke(
stringsToVTCodes.get(Bundle.getMessage("EditModeAccelerator")), primary_modifier));
editModeCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
if (fixMacBugOn11(event)) {
editModeCheckBoxMenuItem.setSelected(!editModeCheckBoxMenuItem.isSelected());
return;
}
setAllEditable(editModeCheckBoxMenuItem.isSelected());
// show/hide the help bar
if (toolBarSide.equals(ToolBarSide.eFLOAT)) {
if (floatEditHelpPanel != null) {
floatEditHelpPanel.setVisible(isEditable() && getShowHelpBar());
}
} else {
helpBarPanel.setVisible(isEditable() && getShowHelpBar());
}
if (isEditable()) {
if (!tooltipsAlwaysOrNever) {
setAllShowToolTip(tooltipsInEditMode);
setAllShowLayoutTurnoutToolTip(tooltipsInEditMode);
}
// redo using the "Extra" color to highlight the selected block
if (highlightSelectedBlockFlag) {
if (!highlightBlockInComboBox(leToolBarPanel.blockIDComboBox)) {
highlightBlockInComboBox(leToolBarPanel.blockContentsComboBox);
}
}
} else {
if (!tooltipsAlwaysOrNever) {
setAllShowToolTip(tooltipsWithoutEditMode);
setAllShowLayoutTurnoutToolTip(tooltipsWithoutEditMode);
}
// undo using the "Extra" color to highlight the selected block
if (highlightSelectedBlockFlag) {
highlightBlock(null);
}
}
awaitingIconChange = false;
});
editModeCheckBoxMenuItem.setSelected(isEditable());
//
// toolbar
//
JMenu toolBarMenu = new JMenu(Bundle.getMessage("ToolBar")); // used for ToolBar SubMenu
optionMenu.add(toolBarMenu);
JMenu toolBarSideMenu = new JMenu(Bundle.getMessage("ToolBarSide"));
ButtonGroup toolBarSideGroup = new ButtonGroup();
//
// create toolbar side menu items: (top, left, bottom, right)
//
toolBarSideTopButton = new JRadioButtonMenuItem(Bundle.getMessage("ToolBarSideTop"));
toolBarSideTopButton.addActionListener((ActionEvent event) -> setToolBarSide(ToolBarSide.eTOP));
toolBarSideTopButton.setSelected(toolBarSide.equals(ToolBarSide.eTOP));
toolBarSideMenu.add(toolBarSideTopButton);
toolBarSideGroup.add(toolBarSideTopButton);
toolBarSideLeftButton = new JRadioButtonMenuItem(Bundle.getMessage("ToolBarSideLeft"));
toolBarSideLeftButton.addActionListener((ActionEvent event) -> setToolBarSide(ToolBarSide.eLEFT));
toolBarSideLeftButton.setSelected(toolBarSide.equals(ToolBarSide.eLEFT));
toolBarSideMenu.add(toolBarSideLeftButton);
toolBarSideGroup.add(toolBarSideLeftButton);
toolBarSideBottomButton = new JRadioButtonMenuItem(Bundle.getMessage("ToolBarSideBottom"));
toolBarSideBottomButton.addActionListener((ActionEvent event) -> setToolBarSide(ToolBarSide.eBOTTOM));
toolBarSideBottomButton.setSelected(toolBarSide.equals(ToolBarSide.eBOTTOM));
toolBarSideMenu.add(toolBarSideBottomButton);
toolBarSideGroup.add(toolBarSideBottomButton);
toolBarSideRightButton = new JRadioButtonMenuItem(Bundle.getMessage("ToolBarSideRight"));
toolBarSideRightButton.addActionListener((ActionEvent event) -> setToolBarSide(ToolBarSide.eRIGHT));
toolBarSideRightButton.setSelected(toolBarSide.equals(ToolBarSide.eRIGHT));
toolBarSideMenu.add(toolBarSideRightButton);
toolBarSideGroup.add(toolBarSideRightButton);
toolBarSideFloatButton = new JRadioButtonMenuItem(Bundle.getMessage("ToolBarSideFloat"));
toolBarSideFloatButton.addActionListener((ActionEvent event) -> setToolBarSide(ToolBarSide.eFLOAT));
toolBarSideFloatButton.setSelected(toolBarSide.equals(ToolBarSide.eFLOAT));
toolBarSideMenu.add(toolBarSideFloatButton);
toolBarSideGroup.add(toolBarSideFloatButton);
toolBarMenu.add(toolBarSideMenu);
//
// toolbar wide menu
//
toolBarMenu.add(wideToolBarCheckBoxMenuItem);
wideToolBarCheckBoxMenuItem.addActionListener((ActionEvent event) -> setToolBarWide(wideToolBarCheckBoxMenuItem.isSelected()));
wideToolBarCheckBoxMenuItem.setSelected(leToolBarPanel.toolBarIsWide);
wideToolBarCheckBoxMenuItem.setEnabled(toolBarSide.equals(ToolBarSide.eTOP) || toolBarSide.equals(ToolBarSide.eBOTTOM));
//
// Scroll Bars
//
scrollMenu = new JMenu(Bundle.getMessage("ComboBoxScrollable")); // used for ScrollBarsSubMenu
optionMenu.add(scrollMenu);
ButtonGroup scrollGroup = new ButtonGroup();
scrollBothMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("ScrollBoth"));
scrollGroup.add(scrollBothMenuItem);
scrollMenu.add(scrollBothMenuItem);
scrollBothMenuItem.setSelected(_scrollState == Editor.SCROLL_BOTH);
scrollBothMenuItem.addActionListener((ActionEvent event) -> {
_scrollState = Editor.SCROLL_BOTH;
setScroll(_scrollState);
redrawPanel();
});
scrollNoneMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("ScrollNone"));
scrollGroup.add(scrollNoneMenuItem);
scrollMenu.add(scrollNoneMenuItem);
scrollNoneMenuItem.setSelected(_scrollState == Editor.SCROLL_NONE);
scrollNoneMenuItem.addActionListener((ActionEvent event) -> {
_scrollState = Editor.SCROLL_NONE;
setScroll(_scrollState);
redrawPanel();
});
scrollHorizontalMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("ScrollHorizontal"));
scrollGroup.add(scrollHorizontalMenuItem);
scrollMenu.add(scrollHorizontalMenuItem);
scrollHorizontalMenuItem.setSelected(_scrollState == Editor.SCROLL_HORIZONTAL);
scrollHorizontalMenuItem.addActionListener((ActionEvent event) -> {
_scrollState = Editor.SCROLL_HORIZONTAL;
setScroll(_scrollState);
redrawPanel();
});
scrollVerticalMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("ScrollVertical"));
scrollGroup.add(scrollVerticalMenuItem);
scrollMenu.add(scrollVerticalMenuItem);
scrollVerticalMenuItem.setSelected(_scrollState == Editor.SCROLL_VERTICAL);
scrollVerticalMenuItem.addActionListener((ActionEvent event) -> {
_scrollState = Editor.SCROLL_VERTICAL;
setScroll(_scrollState);
redrawPanel();
});
//
// Tooltips
//
tooltipMenu = new JMenu(Bundle.getMessage("TooltipSubMenu"));
optionMenu.add(tooltipMenu);
ButtonGroup tooltipGroup = new ButtonGroup();
tooltipNoneMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("TooltipNone"));
tooltipGroup.add(tooltipNoneMenuItem);
tooltipMenu.add(tooltipNoneMenuItem);
tooltipNoneMenuItem.setSelected((!tooltipsInEditMode) && (!tooltipsWithoutEditMode));
tooltipNoneMenuItem.addActionListener((ActionEvent event) -> {
tooltipsInEditMode = false;
tooltipsWithoutEditMode = false;
tooltipsAlwaysOrNever = true;
setAllShowToolTip(false);
setAllShowLayoutTurnoutToolTip(false);
});
tooltipAlwaysMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("TooltipAlways"));
tooltipGroup.add(tooltipAlwaysMenuItem);
tooltipMenu.add(tooltipAlwaysMenuItem);
tooltipAlwaysMenuItem.setSelected((tooltipsInEditMode) && (tooltipsWithoutEditMode));
tooltipAlwaysMenuItem.addActionListener((ActionEvent event) -> {
tooltipsInEditMode = true;
tooltipsWithoutEditMode = true;
tooltipsAlwaysOrNever = true;
setAllShowToolTip(true);
setAllShowLayoutTurnoutToolTip(true);
});
tooltipInEditMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("TooltipEdit"));
tooltipGroup.add(tooltipInEditMenuItem);
tooltipMenu.add(tooltipInEditMenuItem);
tooltipInEditMenuItem.setSelected((tooltipsInEditMode) && (!tooltipsWithoutEditMode));
tooltipInEditMenuItem.addActionListener((ActionEvent event) -> {
tooltipsInEditMode = true;
tooltipsWithoutEditMode = false;
tooltipsAlwaysOrNever = false;
setAllShowToolTip(isEditable());
setAllShowLayoutTurnoutToolTip(isEditable());
});
tooltipNotInEditMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("TooltipNotEdit"));
tooltipGroup.add(tooltipNotInEditMenuItem);
tooltipMenu.add(tooltipNotInEditMenuItem);
tooltipNotInEditMenuItem.setSelected((!tooltipsInEditMode) && (tooltipsWithoutEditMode));
tooltipNotInEditMenuItem.addActionListener((ActionEvent event) -> {
tooltipsInEditMode = false;
tooltipsWithoutEditMode = true;
tooltipsAlwaysOrNever = false;
setAllShowToolTip(!isEditable());
setAllShowLayoutTurnoutToolTip(!isEditable());
});
//
// show edit help
//
showHelpCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("ShowEditHelp"));
optionMenu.add(showHelpCheckBoxMenuItem);
showHelpCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
boolean newShowHelpBar = showHelpCheckBoxMenuItem.isSelected();
setShowHelpBar(newShowHelpBar);
});
showHelpCheckBoxMenuItem.setSelected(getShowHelpBar());
//
// Allow Repositioning
//
positionableCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("AllowRepositioning"));
optionMenu.add(positionableCheckBoxMenuItem);
positionableCheckBoxMenuItem.addActionListener((ActionEvent event) -> setAllPositionable(positionableCheckBoxMenuItem.isSelected()));
positionableCheckBoxMenuItem.setSelected(allPositionable());
//
// Allow Layout Control
//
controlCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("AllowLayoutControl"));
optionMenu.add(controlCheckBoxMenuItem);
controlCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
setAllControlling(controlCheckBoxMenuItem.isSelected());
redrawPanel();
});
controlCheckBoxMenuItem.setSelected(allControlling());
//
// use direct turnout control
//
useDirectTurnoutControlCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("UseDirectTurnoutControl")); // NOI18N
optionMenu.add(useDirectTurnoutControlCheckBoxMenuItem);
useDirectTurnoutControlCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
setDirectTurnoutControl(useDirectTurnoutControlCheckBoxMenuItem.isSelected());
});
useDirectTurnoutControlCheckBoxMenuItem.setSelected(useDirectTurnoutControl);
//
// antialiasing
//
antialiasingOnCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("AntialiasingOn"));
optionMenu.add(antialiasingOnCheckBoxMenuItem);
antialiasingOnCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
setAntialiasingOn(antialiasingOnCheckBoxMenuItem.isSelected());
redrawPanel();
});
antialiasingOnCheckBoxMenuItem.setSelected(antialiasingOn);
//
// drawLayoutTracksLabel
//
drawLayoutTracksLabelCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("DrawLayoutTracksLabel"));
optionMenu.add(drawLayoutTracksLabelCheckBoxMenuItem);
drawLayoutTracksLabelCheckBoxMenuItem.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("DrawLayoutTracksMnemonic")));
drawLayoutTracksLabelCheckBoxMenuItem.setAccelerator(KeyStroke.getKeyStroke(
stringsToVTCodes.get(Bundle.getMessage("DrawLayoutTracksAccelerator")), primary_modifier));
drawLayoutTracksLabelCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
if (fixMacBugOn11(event)) {
drawLayoutTracksLabelCheckBoxMenuItem.setSelected(!drawLayoutTracksLabelCheckBoxMenuItem.isSelected());
return;
}
setDrawLayoutTracksLabel(drawLayoutTracksLabelCheckBoxMenuItem.isSelected());
redrawPanel();
});
drawLayoutTracksLabelCheckBoxMenuItem.setSelected(drawLayoutTracksLabel);
//
// edit title
//
optionMenu.addSeparator();
JMenuItem titleItem = new JMenuItem(Bundle.getMessage("EditTitle") + "...");
optionMenu.add(titleItem);
titleItem.addActionListener((ActionEvent event) -> {
// prompt for name
String newName = (String) JmriJOptionPane.showInputDialog(getTargetFrame(),
Bundle.getMessage("MakeLabel", Bundle.getMessage("EnterTitle")),
Bundle.getMessage("EditTitleMessageTitle"),
JmriJOptionPane.PLAIN_MESSAGE, null, null, getLayoutName());
if (newName != null) {
if (!newName.equals(getLayoutName())) {
if (InstanceManager.getDefault(EditorManager.class).contains(newName)) {
JmriJOptionPane.showMessageDialog(null,
Bundle.getMessage("CanNotRename", Bundle.getMessage("Panel")),
Bundle.getMessage("AlreadyExist", Bundle.getMessage("Panel")),
JmriJOptionPane.ERROR_MESSAGE);
} else {
setTitle(newName);
setLayoutName(newName);
getLayoutTrackDrawingOptions().setName(newName);
setDirty();
if (toolBarSide.equals(ToolBarSide.eFLOAT) && isEditable()) {
// Rebuild the toolbox after a name change.
deletefloatingEditToolBoxFrame();
createfloatingEditToolBoxFrame();
createFloatingHelpPanel();
}
}
}
}
});
//
// set background color
//
JMenuItem backgroundColorMenuItem = new JMenuItem(Bundle.getMessage("SetBackgroundColor", "..."));
optionMenu.add(backgroundColorMenuItem);
backgroundColorMenuItem.addActionListener((ActionEvent event) -> {
Color desiredColor = JmriColorChooser.showDialog(this,
Bundle.getMessage("SetBackgroundColor", ""),
defaultBackgroundColor);
if (desiredColor != null && !defaultBackgroundColor.equals(desiredColor)) {
defaultBackgroundColor = desiredColor;
setBackgroundColor(desiredColor);
setDirty();
redrawPanel();
}
});
//
// set default text color
//
JMenuItem textColorMenuItem = new JMenuItem(Bundle.getMessage("DefaultTextColor", "..."));
optionMenu.add(textColorMenuItem);
textColorMenuItem.addActionListener((ActionEvent event) -> {
Color desiredColor = JmriColorChooser.showDialog(this,
Bundle.getMessage("DefaultTextColor", ""),
defaultTextColor);
if (desiredColor != null && !defaultTextColor.equals(desiredColor)) {
setDefaultTextColor(desiredColor);
setDirty();
redrawPanel();
}
});
if (editorUseOldLocSize) {
//
// save location and size
//
JMenuItem locationItem = new JMenuItem(Bundle.getMessage("SetLocation"));
optionMenu.add(locationItem);
locationItem.addActionListener((ActionEvent event) -> {
setCurrentPositionAndSize();
log.debug("Bounds:{}, {}, {}, {}, {}, {}",
gContext.getUpperLeftX(), gContext.getUpperLeftY(),
gContext.getWindowWidth(), gContext.getWindowHeight(),
gContext.getLayoutWidth(), gContext.getLayoutHeight());
});
}
//
// Add Options
//
JMenu optionsAddMenu = new JMenu(Bundle.getMessage("AddMenuTitle"));
optionMenu.add(optionsAddMenu);
// add background image
JMenuItem backgroundItem = new JMenuItem(Bundle.getMessage("AddBackground") + "...");
optionsAddMenu.add(backgroundItem);
backgroundItem.addActionListener((ActionEvent event) -> {
addBackground();
// note: panel resized in addBackground
setDirty();
redrawPanel();
});
// add fast clock
JMenuItem clockItem = new JMenuItem(Bundle.getMessage("AddItem", Bundle.getMessage("FastClock")));
optionsAddMenu.add(clockItem);
clockItem.addActionListener((ActionEvent event) -> {
AnalogClock2Display c = addClock();
unionToPanelBounds(c.getBounds());
setDirty();
redrawPanel();
});
// add turntable
JMenuItem turntableItem = new JMenuItem(Bundle.getMessage("AddTurntable"));
optionsAddMenu.add(turntableItem);
turntableItem.addActionListener((ActionEvent event) -> {
Point2D pt = windowCenter();
if (selectionActive) {
pt = MathUtil.midPoint(getSelectionRect());
}
addTurntable(pt);
// note: panel resized in addTurntable
setDirty();
redrawPanel();
});
// add reporter
JMenuItem reporterItem = new JMenuItem(Bundle.getMessage("AddReporter") + "...");
optionsAddMenu.add(reporterItem);
reporterItem.addActionListener((ActionEvent event) -> {
Point2D pt = windowCenter();
if (selectionActive) {
pt = MathUtil.midPoint(getSelectionRect());
}
EnterReporterDialog d = new EnterReporterDialog(this);
d.enterReporter((int) pt.getX(), (int) pt.getY());
// note: panel resized in enterReporter
setDirty();
redrawPanel();
});
//
// location coordinates format menu
//
JMenu locationMenu = new JMenu(Bundle.getMessage("LocationMenuTitle")); // used for location format SubMenu
optionMenu.add(locationMenu);
InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> {
String windowFrameRef = getWindowFrameRef();
Object prefsProp = prefsMgr.getProperty(windowFrameRef, "LocationFormat");
// log.debug("{}.LocationFormat is {}", windowFrameRef, prefsProp);
if (prefsProp != null) {
getLayoutEditorToolBarPanel().setLocationFormat(LocationFormat.valueOf((String) prefsProp));
}
});
// pixels (jmri classic)
locationMenu.add(pixelsCheckBoxMenuItem);
pixelsCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
getLayoutEditorToolBarPanel().setLocationFormat(LocationFormat.ePIXELS);
selectLocationFormatCheckBoxMenuItem();
redrawPanel();
});
// metric cm's
locationMenu.add(metricCMCheckBoxMenuItem);
metricCMCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
getLayoutEditorToolBarPanel().setLocationFormat(LocationFormat.eMETRIC_CM);
selectLocationFormatCheckBoxMenuItem();
redrawPanel();
});
// english feet/inches/16th's
locationMenu.add(englishFeetInchesCheckBoxMenuItem);
englishFeetInchesCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
getLayoutEditorToolBarPanel().setLocationFormat(LocationFormat.eENGLISH_FEET_INCHES);
selectLocationFormatCheckBoxMenuItem();
redrawPanel();
});
selectLocationFormatCheckBoxMenuItem();
//
// grid menu
//
JMenu gridMenu = new JMenu(Bundle.getMessage("GridMenuTitle")); // used for Grid SubMenu
optionMenu.add(gridMenu);
// show grid
showGridCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("ShowEditGrid"));
showGridCheckBoxMenuItem.setAccelerator(KeyStroke.getKeyStroke(stringsToVTCodes.get(
Bundle.getMessage("ShowEditGridAccelerator")), primary_modifier));
gridMenu.add(showGridCheckBoxMenuItem);
showGridCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
if (fixMacBugOn11(event)) {
showGridCheckBoxMenuItem.setSelected(!showGridCheckBoxMenuItem.isSelected());
return;
}
drawGrid = showGridCheckBoxMenuItem.isSelected();
redrawPanel();
});
showGridCheckBoxMenuItem.setSelected(getDrawGrid());
// snap to grid on add
snapToGridOnAddCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("SnapToGridOnAdd"));
snapToGridOnAddCheckBoxMenuItem.setAccelerator(KeyStroke.getKeyStroke(stringsToVTCodes.get(
Bundle.getMessage("SnapToGridOnAddAccelerator")),
primary_modifier | ActionEvent.SHIFT_MASK));
gridMenu.add(snapToGridOnAddCheckBoxMenuItem);
snapToGridOnAddCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
if (fixMacBugOn11(event)) {
snapToGridOnAddCheckBoxMenuItem.setSelected(!snapToGridOnAddCheckBoxMenuItem.isSelected());
return;
}
snapToGridOnAdd = snapToGridOnAddCheckBoxMenuItem.isSelected();
redrawPanel();
});
snapToGridOnAddCheckBoxMenuItem.setSelected(snapToGridOnAdd);
// snap to grid on move
snapToGridOnMoveCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("SnapToGridOnMove"));
snapToGridOnMoveCheckBoxMenuItem.setAccelerator(KeyStroke.getKeyStroke(stringsToVTCodes.get(
Bundle.getMessage("SnapToGridOnMoveAccelerator")),
primary_modifier | ActionEvent.SHIFT_MASK));
gridMenu.add(snapToGridOnMoveCheckBoxMenuItem);
snapToGridOnMoveCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
if (fixMacBugOn11(event)) {
snapToGridOnMoveCheckBoxMenuItem.setSelected(!snapToGridOnMoveCheckBoxMenuItem.isSelected());
return;
}
snapToGridOnMove = snapToGridOnMoveCheckBoxMenuItem.isSelected();
redrawPanel();
});
snapToGridOnMoveCheckBoxMenuItem.setSelected(snapToGridOnMove);
// specify grid square size
JMenuItem gridSizeItem = new JMenuItem(Bundle.getMessage("SetGridSizes") + "...");
gridMenu.add(gridSizeItem);
gridSizeItem.addActionListener((ActionEvent event) -> {
EnterGridSizesDialog d = new EnterGridSizesDialog(this);
d.enterGridSizes();
});
//
// track menu
//
JMenu trackMenu = new JMenu(Bundle.getMessage("TrackMenuTitle"));
optionMenu.add(trackMenu);
// set track drawing options menu item
JMenuItem jmi = new JMenuItem(Bundle.getMessage("SetTrackDrawingOptions"));
trackMenu.add(jmi);
jmi.setToolTipText(Bundle.getMessage("SetTrackDrawingOptionsToolTip"));
jmi.addActionListener((ActionEvent event) -> {
LayoutTrackDrawingOptionsDialog ltdod
= new LayoutTrackDrawingOptionsDialog(
this, true, getLayoutTrackDrawingOptions());
ltdod.setVisible(true);
});
// track colors item menu item
JMenu trkColourMenu = new JMenu(Bundle.getMessage("TrackColorSubMenu"));
trackMenu.add(trkColourMenu);
JMenuItem trackColorMenuItem = new JMenuItem(Bundle.getMessage("DefaultTrackColor"));
trkColourMenu.add(trackColorMenuItem);
trackColorMenuItem.addActionListener((ActionEvent event) -> {
Color desiredColor = JmriColorChooser.showDialog(this,
Bundle.getMessage("DefaultTrackColor"),
defaultTrackColor);
if (desiredColor != null && !defaultTrackColor.equals(desiredColor)) {
setDefaultTrackColor(desiredColor);
setDirty();
redrawPanel();
}
});
JMenuItem trackOccupiedColorMenuItem = new JMenuItem(Bundle.getMessage("DefaultOccupiedTrackColor"));
trkColourMenu.add(trackOccupiedColorMenuItem);
trackOccupiedColorMenuItem.addActionListener((ActionEvent event) -> {
Color desiredColor = JmriColorChooser.showDialog(this,
Bundle.getMessage("DefaultOccupiedTrackColor"),
defaultOccupiedTrackColor);
if (desiredColor != null && !defaultOccupiedTrackColor.equals(desiredColor)) {
setDefaultOccupiedTrackColor(desiredColor);
setDirty();
redrawPanel();
}
});
JMenuItem trackAlternativeColorMenuItem = new JMenuItem(Bundle.getMessage("DefaultAlternativeTrackColor"));
trkColourMenu.add(trackAlternativeColorMenuItem);
trackAlternativeColorMenuItem.addActionListener((ActionEvent event) -> {
Color desiredColor = JmriColorChooser.showDialog(this,
Bundle.getMessage("DefaultAlternativeTrackColor"),
defaultAlternativeTrackColor);
if (desiredColor != null && !defaultAlternativeTrackColor.equals(desiredColor)) {
setDefaultAlternativeTrackColor(desiredColor);
setDirty();
redrawPanel();
}
});
// Set All Tracks To Default Colors
JMenuItem setAllTracksToDefaultColorsMenuItem = new JMenuItem(Bundle.getMessage("SetAllTracksToDefaultColors"));
trkColourMenu.add(setAllTracksToDefaultColorsMenuItem);
setAllTracksToDefaultColorsMenuItem.addActionListener((ActionEvent event) -> {
if (setAllTracksToDefaultColors() > 0) {
setDirty();
redrawPanel();
}
});
// Automatically Assign Blocks to Track
autoAssignBlocksCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("AutoAssignBlock"));
trackMenu.add(autoAssignBlocksCheckBoxMenuItem);
autoAssignBlocksCheckBoxMenuItem.addActionListener((ActionEvent event) -> autoAssignBlocks = autoAssignBlocksCheckBoxMenuItem.isSelected());
autoAssignBlocksCheckBoxMenuItem.setSelected(autoAssignBlocks);
// add hideTrackSegmentConstructionLines menu item
hideTrackSegmentConstructionLinesCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("HideTrackConLines"));
trackMenu.add(hideTrackSegmentConstructionLinesCheckBoxMenuItem);
hideTrackSegmentConstructionLinesCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
int show = TrackSegmentView.SHOWCON;
if (hideTrackSegmentConstructionLinesCheckBoxMenuItem.isSelected()) {
show = TrackSegmentView.HIDECONALL;
}
for (TrackSegmentView tsv : getTrackSegmentViews()) {
tsv.hideConstructionLines(show);
}
redrawPanel();
});
hideTrackSegmentConstructionLinesCheckBoxMenuItem.setSelected(autoAssignBlocks);
//
// add turnout options submenu
//
JMenu turnoutOptionsMenu = new JMenu(Bundle.getMessage("TurnoutOptions"));
optionMenu.add(turnoutOptionsMenu);
// animation item
animationCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("AllowTurnoutAnimation"));
turnoutOptionsMenu.add(animationCheckBoxMenuItem);
animationCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
boolean mode = animationCheckBoxMenuItem.isSelected();
setTurnoutAnimation(mode);
});
animationCheckBoxMenuItem.setSelected(true);
// circle on Turnouts
turnoutCirclesOnCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("TurnoutCirclesOn"));
turnoutOptionsMenu.add(turnoutCirclesOnCheckBoxMenuItem);
turnoutCirclesOnCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
turnoutCirclesWithoutEditMode = turnoutCirclesOnCheckBoxMenuItem.isSelected();
redrawPanel();
});
turnoutCirclesOnCheckBoxMenuItem.setSelected(turnoutCirclesWithoutEditMode);
// select turnout circle color
JMenuItem turnoutCircleColorMenuItem = new JMenuItem(Bundle.getMessage("TurnoutCircleColor"));
turnoutCircleColorMenuItem.addActionListener((ActionEvent event) -> {
Color desiredColor = JmriColorChooser.showDialog(this,
Bundle.getMessage("TurnoutCircleColor"),
turnoutCircleColor);
if (desiredColor != null && !turnoutCircleColor.equals(desiredColor)) {
setTurnoutCircleColor(desiredColor);
setDirty();
redrawPanel();
}
});
turnoutOptionsMenu.add(turnoutCircleColorMenuItem);
// select turnout circle thrown color
JMenuItem turnoutCircleThrownColorMenuItem = new JMenuItem(Bundle.getMessage("TurnoutCircleThrownColor"));
turnoutCircleThrownColorMenuItem.addActionListener((ActionEvent event) -> {
Color desiredColor = JmriColorChooser.showDialog(this,
Bundle.getMessage("TurnoutCircleThrownColor"),
turnoutCircleThrownColor);
if (desiredColor != null && !turnoutCircleThrownColor.equals(desiredColor)) {
setTurnoutCircleThrownColor(desiredColor);
setDirty();
redrawPanel();
}
});
turnoutOptionsMenu.add(turnoutCircleThrownColorMenuItem);
turnoutFillControlCirclesCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("TurnoutFillControlCircles"));
turnoutOptionsMenu.add(turnoutFillControlCirclesCheckBoxMenuItem);
turnoutFillControlCirclesCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
turnoutFillControlCircles = turnoutFillControlCirclesCheckBoxMenuItem.isSelected();
redrawPanel();
});
turnoutFillControlCirclesCheckBoxMenuItem.setSelected(turnoutFillControlCircles);
// select turnout circle size
JMenu turnoutCircleSizeMenu = new JMenu(Bundle.getMessage("TurnoutCircleSize"));
turnoutCircleSizeButtonGroup = new ButtonGroup();
addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "1", 1);
addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "2", 2);
addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "3", 3);
addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "4", 4);
addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "5", 5);
addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "6", 6);
addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "7", 7);
addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "8", 8);
addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "9", 9);
addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "10", 10);
turnoutOptionsMenu.add(turnoutCircleSizeMenu);
// add "enable drawing of unselected leg " menu item (helps when diverging angle is small)
turnoutDrawUnselectedLegCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("TurnoutDrawUnselectedLeg"));
turnoutOptionsMenu.add(turnoutDrawUnselectedLegCheckBoxMenuItem);
turnoutDrawUnselectedLegCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
turnoutDrawUnselectedLeg = turnoutDrawUnselectedLegCheckBoxMenuItem.isSelected();
redrawPanel();
});
turnoutDrawUnselectedLegCheckBoxMenuItem.setSelected(turnoutDrawUnselectedLeg);
return optionMenu;
}
private void selectLocationFormatCheckBoxMenuItem() {
pixelsCheckBoxMenuItem.setSelected(getLayoutEditorToolBarPanel().getLocationFormat() == LocationFormat.ePIXELS);
metricCMCheckBoxMenuItem.setSelected(getLayoutEditorToolBarPanel().getLocationFormat() == LocationFormat.eMETRIC_CM);
englishFeetInchesCheckBoxMenuItem.setSelected(getLayoutEditorToolBarPanel().getLocationFormat() == LocationFormat.eENGLISH_FEET_INCHES);
}
/*============================================*\
|* LayoutTrackDrawingOptions accessor methods *|
\*============================================*/
private LayoutTrackDrawingOptions layoutTrackDrawingOptions = null;
/**
*
* Getter Layout Track Drawing Options. since 4.15.6 split variable
* defaultTrackColor and mainlineTrackColor/sidelineTrackColor <br>
* blockDefaultColor, blockOccupiedColor and blockAlternativeColor added to
* LayoutTrackDrawingOptions <br>
*
* @return LayoutTrackDrawingOptions object
*/
@Nonnull
public LayoutTrackDrawingOptions getLayoutTrackDrawingOptions() {
if (layoutTrackDrawingOptions == null) {
layoutTrackDrawingOptions = new LayoutTrackDrawingOptions(getLayoutName());
// integrate LayoutEditor drawing options with previous drawing options
layoutTrackDrawingOptions.setMainBlockLineWidth(gContext.getMainlineTrackWidth());
layoutTrackDrawingOptions.setSideBlockLineWidth(gContext.getSidelineTrackWidth());
layoutTrackDrawingOptions.setMainRailWidth(gContext.getMainlineTrackWidth());
layoutTrackDrawingOptions.setSideRailWidth(gContext.getSidelineTrackWidth());
layoutTrackDrawingOptions.setMainRailColor(mainlineTrackColor);
layoutTrackDrawingOptions.setSideRailColor(sidelineTrackColor);
layoutTrackDrawingOptions.setBlockDefaultColor(defaultTrackColor);
layoutTrackDrawingOptions.setBlockOccupiedColor(defaultOccupiedTrackColor);
layoutTrackDrawingOptions.setBlockAlternativeColor(defaultAlternativeTrackColor);
}
return layoutTrackDrawingOptions;
}
/**
* since 4.15.6 split variable defaultTrackColor and
* mainlineTrackColor/sidelineTrackColor
*
* @param ltdo LayoutTrackDrawingOptions object
*/
public void setLayoutTrackDrawingOptions(LayoutTrackDrawingOptions ltdo) {
layoutTrackDrawingOptions = ltdo;
// copy main/side line block widths
gContext.setMainlineBlockWidth(layoutTrackDrawingOptions.getMainBlockLineWidth());
gContext.setSidelineBlockWidth(layoutTrackDrawingOptions.getSideBlockLineWidth());
// copy main/side line track (rail) widths
gContext.setMainlineTrackWidth(layoutTrackDrawingOptions.getMainRailWidth());
gContext.setSidelineTrackWidth(layoutTrackDrawingOptions.getSideRailWidth());
mainlineTrackColor = layoutTrackDrawingOptions.getMainRailColor();
sidelineTrackColor = layoutTrackDrawingOptions.getSideRailColor();
redrawPanel();
}
private JCheckBoxMenuItem skipTurnoutCheckBoxMenuItem = null;
private AddEntryExitPairAction addEntryExitPairAction = null;
/**
* setup the Layout Editor Tools menu
*
* @param menuBar the menu bar to add the Tools menu to
*/
private void setupToolsMenu(@Nonnull JMenuBar menuBar) {
JMenu toolsMenu = new JMenu(Bundle.getMessage("MenuTools"));
toolsMenu.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("MenuToolsMnemonic")));
menuBar.add(toolsMenu);
// setup checks menu
getLEChecks().setupChecksMenu(toolsMenu);
// assign blocks to selection
assignBlockToSelectionMenuItem.setToolTipText(Bundle.getMessage("AssignBlockToSelectionToolTip"));
toolsMenu.add(assignBlockToSelectionMenuItem);
assignBlockToSelectionMenuItem.addActionListener((ActionEvent event) -> {
// bring up scale track diagram dialog
assignBlockToSelection();
});
assignBlockToSelectionMenuItem.setEnabled(!_layoutTrackSelection.isEmpty());
// scale track diagram
JMenuItem jmi = new JMenuItem(Bundle.getMessage("ScaleTrackDiagram") + "...");
jmi.setToolTipText(Bundle.getMessage("ScaleTrackDiagramToolTip"));
toolsMenu.add(jmi);
jmi.addActionListener((ActionEvent event) -> {
// bring up scale track diagram dialog
ScaleTrackDiagramDialog d = new ScaleTrackDiagramDialog(this);
d.scaleTrackDiagram();
});
// translate selection
jmi = new JMenuItem(Bundle.getMessage("TranslateSelection") + "...");
jmi.setToolTipText(Bundle.getMessage("TranslateSelectionToolTip"));
toolsMenu.add(jmi);
jmi.addActionListener((ActionEvent event) -> {
// bring up translate selection dialog
if (!selectionActive || (selectionWidth == 0.0) || (selectionHeight == 0.0)) {
// no selection has been made - nothing to move
JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error12"),
Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
} else {
// bring up move selection dialog
MoveSelectionDialog d = new MoveSelectionDialog(this);
d.moveSelection();
}
});
// undo translate selection
undoTranslateSelectionMenuItem.setToolTipText(Bundle.getMessage("UndoTranslateSelectionToolTip"));
toolsMenu.add(undoTranslateSelectionMenuItem);
undoTranslateSelectionMenuItem.addActionListener((ActionEvent event) -> {
// undo previous move selection
undoMoveSelection();
});
undoTranslateSelectionMenuItem.setEnabled(canUndoMoveSelection);
// rotate selection
jmi = new JMenuItem(Bundle.getMessage("RotateSelection90MenuItemTitle"));
jmi.setToolTipText(Bundle.getMessage("RotateSelection90MenuItemToolTip"));
toolsMenu.add(jmi);
jmi.addActionListener((ActionEvent event) -> rotateSelection90());
// rotate entire layout
jmi = new JMenuItem(Bundle.getMessage("RotateLayout90MenuItemTitle"));
jmi.setToolTipText(Bundle.getMessage("RotateLayout90MenuItemToolTip"));
toolsMenu.add(jmi);
jmi.addActionListener((ActionEvent event) -> rotateLayout90());
// align layout to grid
jmi = new JMenuItem(Bundle.getMessage("AlignLayoutToGridMenuItemTitle") + "...");
jmi.setToolTipText(Bundle.getMessage("AlignLayoutToGridMenuItemToolTip"));
toolsMenu.add(jmi);
jmi.addActionListener((ActionEvent event) -> alignLayoutToGrid());
// align selection to grid
jmi = new JMenuItem(Bundle.getMessage("AlignSelectionToGridMenuItemTitle") + "...");
jmi.setToolTipText(Bundle.getMessage("AlignSelectionToGridMenuItemToolTip"));
toolsMenu.add(jmi);
jmi.addActionListener((ActionEvent event) -> alignSelectionToGrid());
// reset turnout size to program defaults
jmi = new JMenuItem(Bundle.getMessage("ResetTurnoutSize"));
jmi.setToolTipText(Bundle.getMessage("ResetTurnoutSizeToolTip"));
toolsMenu.add(jmi);
jmi.addActionListener((ActionEvent event) -> {
// undo previous move selection
resetTurnoutSize();
});
toolsMenu.addSeparator();
// skip turnout
skipTurnoutCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("SkipInternalTurnout"));
skipTurnoutCheckBoxMenuItem.setToolTipText(Bundle.getMessage("SkipInternalTurnoutToolTip"));
toolsMenu.add(skipTurnoutCheckBoxMenuItem);
skipTurnoutCheckBoxMenuItem.addActionListener((ActionEvent event) -> setIncludedTurnoutSkipped(skipTurnoutCheckBoxMenuItem.isSelected()));
skipTurnoutCheckBoxMenuItem.setSelected(isIncludedTurnoutSkipped());
// set signals at turnout
jmi = new JMenuItem(Bundle.getMessage("SignalsAtTurnout") + "...");
jmi.setToolTipText(Bundle.getMessage("SignalsAtTurnoutToolTip"));
toolsMenu.add(jmi);
jmi.addActionListener((ActionEvent event) -> {
// bring up signals at turnout tool dialog
getLETools().setSignalsAtTurnout(leToolBarPanel.signalIconEditor, leToolBarPanel.signalFrame);
});
// set signals at block boundary
jmi = new JMenuItem(Bundle.getMessage("SignalsAtBoundary") + "...");
jmi.setToolTipText(Bundle.getMessage("SignalsAtBoundaryToolTip"));
toolsMenu.add(jmi);
jmi.addActionListener((ActionEvent event) -> {
// bring up signals at block boundary tool dialog
getLETools().setSignalsAtBlockBoundary(leToolBarPanel.signalIconEditor, leToolBarPanel.signalFrame);
});
// set signals at crossover turnout
jmi = new JMenuItem(Bundle.getMessage("SignalsAtXoverTurnout") + "...");
jmi.setToolTipText(Bundle.getMessage("SignalsAtXoverTurnoutToolTip"));
toolsMenu.add(jmi);
jmi.addActionListener((ActionEvent event) -> {
// bring up signals at crossover tool dialog
getLETools().setSignalsAtXoverTurnout(leToolBarPanel.signalIconEditor, leToolBarPanel.signalFrame);
});
// set signals at level crossing
jmi = new JMenuItem(Bundle.getMessage("SignalsAtLevelXing") + "...");
jmi.setToolTipText(Bundle.getMessage("SignalsAtLevelXingToolTip"));
toolsMenu.add(jmi);
jmi.addActionListener((ActionEvent event) -> {
// bring up signals at level crossing tool dialog
getLETools().setSignalsAtLevelXing(leToolBarPanel.signalIconEditor, leToolBarPanel.signalFrame);
});
// set signals at throat-to-throat turnouts
jmi = new JMenuItem(Bundle.getMessage("SignalsAtTToTTurnout") + "...");
jmi.setToolTipText(Bundle.getMessage("SignalsAtTToTTurnoutToolTip"));
toolsMenu.add(jmi);
jmi.addActionListener((ActionEvent event) -> {
// bring up signals at throat-to-throat turnouts tool dialog
getLETools().setSignalsAtThroatToThroatTurnouts(leToolBarPanel.signalIconEditor, leToolBarPanel.signalFrame);
});
// set signals at 3-way turnout
jmi = new JMenuItem(Bundle.getMessage("SignalsAt3WayTurnout") + "...");
jmi.setToolTipText(Bundle.getMessage("SignalsAt3WayTurnoutToolTip"));
toolsMenu.add(jmi);
jmi.addActionListener((ActionEvent event) -> {
// bring up signals at 3-way turnout tool dialog
getLETools().setSignalsAt3WayTurnout(leToolBarPanel.signalIconEditor, leToolBarPanel.signalFrame);
});
jmi = new JMenuItem(Bundle.getMessage("SignalsAtSlip") + "...");
jmi.setToolTipText(Bundle.getMessage("SignalsAtSlipToolTip"));
toolsMenu.add(jmi);
jmi.addActionListener((ActionEvent event) -> {
// bring up signals at throat-to-throat turnouts tool dialog
getLETools().setSignalsAtSlip(leToolBarPanel.signalIconEditor, leToolBarPanel.signalFrame);
});
jmi = new JMenuItem(Bundle.getMessage("EntryExitTitle") + "...");
jmi.setToolTipText(Bundle.getMessage("EntryExitToolTip"));
toolsMenu.add(jmi);
jmi.addActionListener((ActionEvent event) -> {
if (addEntryExitPairAction == null) {
addEntryExitPairAction = new AddEntryExitPairAction("ENTRY EXIT", LayoutEditor.this);
}
addEntryExitPairAction.actionPerformed(event);
});
// if (true) { // TODO: disable for production
// jmi = new JMenuItem("GEORGE");
// toolsMenu.add(jmi);
// jmi.addActionListener((ActionEvent event) -> {
// // do GEORGE stuff here!
// });
// }
} // setupToolsMenu
/**
* get the toolbar side
*
* @return the side where to put the tool bar
*/
public ToolBarSide getToolBarSide() {
return toolBarSide;
}
/**
* set the tool bar side
*
* @param newToolBarSide on which side to put the toolbar
*/
public void setToolBarSide(ToolBarSide newToolBarSide) {
// null if edit toolbar is not setup yet...
if (!newToolBarSide.equals(toolBarSide)) {
toolBarSide = newToolBarSide;
InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setProperty(getWindowFrameRef(), "toolBarSide", toolBarSide.getName()));
toolBarSideTopButton.setSelected(toolBarSide.equals(ToolBarSide.eTOP));
toolBarSideLeftButton.setSelected(toolBarSide.equals(ToolBarSide.eLEFT));
toolBarSideBottomButton.setSelected(toolBarSide.equals(ToolBarSide.eBOTTOM));
toolBarSideRightButton.setSelected(toolBarSide.equals(ToolBarSide.eRIGHT));
toolBarSideFloatButton.setSelected(toolBarSide.equals(ToolBarSide.eFLOAT));
setupToolBar(); // re-layout all the toolbar items
if (toolBarSide.equals(ToolBarSide.eFLOAT)) {
if (editToolBarContainerPanel != null) {
editToolBarContainerPanel.setVisible(false);
}
if (floatEditHelpPanel != null) {
floatEditHelpPanel.setVisible(isEditable() && getShowHelpBar());
}
} else {
if (floatingEditToolBoxFrame != null) {
deletefloatingEditToolBoxFrame();
}
editToolBarContainerPanel.setVisible(isEditable());
if (getShowHelpBar()) {
helpBarPanel.setVisible(isEditable());
// not sure why... but this is the only way I could
// get everything to layout correctly
// when the helpbar is visible...
boolean editMode = isEditable();
setAllEditable(!editMode);
setAllEditable(editMode);
}
}
wideToolBarCheckBoxMenuItem.setEnabled(
toolBarSide.equals(ToolBarSide.eTOP)
|| toolBarSide.equals(ToolBarSide.eBOTTOM));
}
} // setToolBarSide
//
//
//
private void setToolBarWide(boolean newToolBarIsWide) {
// null if edit toolbar not setup yet...
if (leToolBarPanel.toolBarIsWide != newToolBarIsWide) {
leToolBarPanel.toolBarIsWide = newToolBarIsWide;
wideToolBarCheckBoxMenuItem.setSelected(leToolBarPanel.toolBarIsWide);
InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> {
// Note: since prefs default to false and we want wide to be the default
// we invert it and save it as thin
prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".toolBarThin", !leToolBarPanel.toolBarIsWide);
});
setupToolBar(); // re-layout all the toolbar items
if (getShowHelpBar()) {
// not sure why, but this is the only way I could
// get everything to layout correctly
// when the helpbar is visible...
boolean editMode = isEditable();
setAllEditable(!editMode);
setAllEditable(editMode);
} else {
helpBarPanel.setVisible(isEditable() && getShowHelpBar());
}
}
} // setToolBarWide
//
//
//
@SuppressWarnings("deprecation") // getMenuShortcutKeyMask()
private void setupZoomMenu(@Nonnull JMenuBar menuBar) {
zoomMenu.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("MenuZoomMnemonic")));
menuBar.add(zoomMenu);
ButtonGroup zoomButtonGroup = new ButtonGroup();
int primary_modifier = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
// add zoom choices to menu
JMenuItem zoomInItem = new JMenuItem(Bundle.getMessage("ZoomIn"));
zoomInItem.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("zoomInMnemonic")));
String zoomInAccelerator = Bundle.getMessage("zoomInAccelerator");
// log.debug("zoomInAccelerator: " + zoomInAccelerator);
zoomInItem.setAccelerator(KeyStroke.getKeyStroke(stringsToVTCodes.get(zoomInAccelerator), primary_modifier));
zoomMenu.add(zoomInItem);
zoomInItem.addActionListener((ActionEvent event) -> setZoom(getZoom() * 1.1));
JMenuItem zoomOutItem = new JMenuItem(Bundle.getMessage("ZoomOut"));
zoomOutItem.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("zoomOutMnemonic")));
String zoomOutAccelerator = Bundle.getMessage("zoomOutAccelerator");
// log.debug("zoomOutAccelerator: " + zoomOutAccelerator);
zoomOutItem.setAccelerator(KeyStroke.getKeyStroke(stringsToVTCodes.get(zoomOutAccelerator), primary_modifier));
zoomMenu.add(zoomOutItem);
zoomOutItem.addActionListener((ActionEvent event) -> setZoom(getZoom() / 1.1));
JMenuItem zoomFitItem = new JMenuItem(Bundle.getMessage("ZoomToFit"));
zoomMenu.add(zoomFitItem);
zoomFitItem.addActionListener((ActionEvent event) -> zoomToFit());
zoomMenu.addSeparator();
// add zoom choices to menu
zoomMenu.add(zoom025Item);
zoom025Item.addActionListener((ActionEvent event) -> setZoom(0.25));
zoomButtonGroup.add(zoom025Item);
zoomMenu.add(zoom05Item);
zoom05Item.addActionListener((ActionEvent event) -> setZoom(0.5));
zoomButtonGroup.add(zoom05Item);
zoomMenu.add(zoom075Item);
zoom075Item.addActionListener((ActionEvent event) -> setZoom(0.75));
zoomButtonGroup.add(zoom075Item);
String zoomNoneAccelerator = Bundle.getMessage("zoomNoneAccelerator");
// log.debug("zoomNoneAccelerator: " + zoomNoneAccelerator);
noZoomItem.setAccelerator(KeyStroke.getKeyStroke(stringsToVTCodes.get(zoomNoneAccelerator), primary_modifier));
zoomMenu.add(noZoomItem);
noZoomItem.addActionListener((ActionEvent event) -> setZoom(1.0));
zoomButtonGroup.add(noZoomItem);
zoomMenu.add(zoom15Item);
zoom15Item.addActionListener((ActionEvent event) -> setZoom(1.5));
zoomButtonGroup.add(zoom15Item);
zoomMenu.add(zoom20Item);
zoom20Item.addActionListener((ActionEvent event) -> setZoom(2.0));
zoomButtonGroup.add(zoom20Item);
zoomMenu.add(zoom30Item);
zoom30Item.addActionListener((ActionEvent event) -> setZoom(3.0));
zoomButtonGroup.add(zoom30Item);
zoomMenu.add(zoom40Item);
zoom40Item.addActionListener((ActionEvent event) -> setZoom(4.0));
zoomButtonGroup.add(zoom40Item);
zoomMenu.add(zoom50Item);
zoom50Item.addActionListener((ActionEvent event) -> setZoom(5.0));
zoomButtonGroup.add(zoom50Item);
zoomMenu.add(zoom60Item);
zoom60Item.addActionListener((ActionEvent event) -> setZoom(6.0));
zoomButtonGroup.add(zoom60Item);
zoomMenu.add(zoom70Item);
zoom70Item.addActionListener((ActionEvent event) -> setZoom(7.0));
zoomButtonGroup.add(zoom70Item);
zoomMenu.add(zoom80Item);
zoom80Item.addActionListener((ActionEvent event) -> setZoom(8.0));
zoomButtonGroup.add(zoom80Item);
// note: because this LayoutEditor object was just instantiated its
// zoom attribute is 1.0; if it's being instantiated from an XML file
// that has a zoom attribute for this object then setZoom will be
// called after this method returns and we'll select the appropriate
// menu item then.
noZoomItem.setSelected(true);
// Note: We have to invoke this stuff later because _targetPanel is not setup yet
SwingUtilities.invokeLater(() -> {
// get the window specific saved zoom user preference
InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> {
Object zoomProp = prefsMgr.getProperty(getWindowFrameRef(), "zoom");
log.debug(
"{} zoom is {}", getWindowFrameRef(), zoomProp);
if (zoomProp
!= null) {
setZoom((Double) zoomProp);
}
}
);
// get the scroll bars from the scroll pane
JScrollPane scrollPane = getPanelScrollPane();
if (scrollPane != null) {
JScrollBar hsb = scrollPane.getHorizontalScrollBar();
JScrollBar vsb = scrollPane.getVerticalScrollBar();
// Increase scroll bar unit increments!!!
vsb.setUnitIncrement(gContext.getGridSize());
hsb.setUnitIncrement(gContext.getGridSize());
// add scroll bar adjustment listeners
vsb.addAdjustmentListener(this::scrollBarAdjusted);
hsb.addAdjustmentListener(this::scrollBarAdjusted);
// remove all mouse wheel listeners
mouseWheelListeners = scrollPane.getMouseWheelListeners();
for (MouseWheelListener mwl : mouseWheelListeners) {
scrollPane.removeMouseWheelListener(mwl);
}
// add my mouse wheel listener
// (so mouseWheelMoved (below) will be called)
scrollPane.addMouseWheelListener(this);
}
});
} // setupZoomMenu
private MouseWheelListener[] mouseWheelListeners;
// scroll bar listener to update x & y coordinates in toolbar on scroll
public void scrollBarAdjusted(AdjustmentEvent event) {
// log.warn("scrollBarAdjusted");
if (isEditable()) {
// get the location of the mouse
PointerInfo mpi = MouseInfo.getPointerInfo();
Point mouseLoc = mpi.getLocation();
// convert to target panel coordinates
SwingUtilities.convertPointFromScreen(mouseLoc, getTargetPanel());
// correct for scaling...
double theZoom = getZoom();
xLoc = (int) (mouseLoc.getX() / theZoom);
yLoc = (int) (mouseLoc.getY() / theZoom);
dLoc = new Point2D.Double(xLoc, yLoc);
leToolBarPanel.setLocationText(dLoc);
}
adjustClip();
}
private void adjustScrollBars() {
// log.info("adjustScrollBars()");
// This is the bounds of what's on the screen
JScrollPane scrollPane = getPanelScrollPane();
Rectangle scrollBounds = scrollPane.getViewportBorderBounds();
// log.info(" getViewportBorderBounds: {}", MathUtil.rectangle2DToString(scrollBounds));
// this is the size of the entire scaled layout panel
Dimension targetPanelSize = getTargetPanelSize();
// log.info(" getTargetPanelSize: {}", MathUtil.dimensionToString(targetPanelSize));
// double scale = getZoom();
// determine the relative position of the current horizontal scrollbar
JScrollBar horScroll = scrollPane.getHorizontalScrollBar();
double oldX = horScroll.getValue();
double oldMaxX = horScroll.getMaximum();
double ratioX = (oldMaxX < 1) ? 0 : oldX / oldMaxX;
// calculate the new X maximum and value
int panelWidth = (int) (targetPanelSize.getWidth());
int scrollWidth = (int) scrollBounds.getWidth();
int newMaxX = Math.max(panelWidth - scrollWidth, 0);
int newX = (int) (newMaxX * ratioX);
horScroll.setMaximum(newMaxX);
horScroll.setValue(newX);
// determine the relative position of the current vertical scrollbar
JScrollBar vertScroll = scrollPane.getVerticalScrollBar();
double oldY = vertScroll.getValue();
double oldMaxY = vertScroll.getMaximum();
double ratioY = (oldMaxY < 1) ? 0 : oldY / oldMaxY;
// calculate the new X maximum and value
int tempPanelHeight = (int) (targetPanelSize.getHeight());
int tempScrollHeight = (int) scrollBounds.getHeight();
int newMaxY = Math.max(tempPanelHeight - tempScrollHeight, 0);
int newY = (int) (newMaxY * ratioY);
vertScroll.setMaximum(newMaxY);
vertScroll.setValue(newY);
// log.info("w: {}, x: {}, h: {}, y: {}", "" + newMaxX, "" + newX, "" + newMaxY, "" + newY);
adjustClip();
}
private void adjustClip() {
// log.info("adjustClip()");
// This is the bounds of what's on the screen
JScrollPane scrollPane = getPanelScrollPane();
Rectangle scrollBounds = scrollPane.getViewportBorderBounds();
// log.info(" ViewportBorderBounds: {}", MathUtil.rectangle2DToString(scrollBounds));
JScrollBar horScroll = scrollPane.getHorizontalScrollBar();
int scrollX = horScroll.getValue();
JScrollBar vertScroll = scrollPane.getVerticalScrollBar();
int scrollY = vertScroll.getValue();
Rectangle2D newClipRect = MathUtil.offset(
scrollBounds,
scrollX - scrollBounds.getMinX(),
scrollY - scrollBounds.getMinY());
newClipRect = MathUtil.scale(newClipRect, 1.0 / getZoom());
newClipRect = MathUtil.granulize(newClipRect, 1.0); // round to nearest pixel
layoutEditorComponent.setClip(newClipRect);
redrawPanel();
}
@Override
public void mouseWheelMoved(@Nonnull MouseWheelEvent event) {
// log.warn("mouseWheelMoved");
if (event.isAltDown()) {
// get the mouse position from the event and convert to target panel coordinates
Component component = (Component) event.getSource();
Point eventPoint = event.getPoint();
JComponent targetPanel = getTargetPanel();
Point2D mousePoint = SwingUtilities.convertPoint(component, eventPoint, targetPanel);
// get the old view port position
JScrollPane scrollPane = getPanelScrollPane();
JViewport viewPort = scrollPane.getViewport();
Point2D viewPosition = viewPort.getViewPosition();
// convert from oldZoom (scaled) coordinates to image coordinates
double zoom = getZoom();
Point2D imageMousePoint = MathUtil.divide(mousePoint, zoom);
Point2D imageViewPosition = MathUtil.divide(viewPosition, zoom);
// compute the delta (in image coordinates)
Point2D imageDelta = MathUtil.subtract(imageMousePoint, imageViewPosition);
// compute how much to change zoom
double amount = Math.pow(1.1, event.getScrollAmount());
if (event.getWheelRotation() < 0.0) {
// reciprocal for zoom out
amount = 1.0 / amount;
}
// set the new zoom
double newZoom = setZoom(zoom * amount);
// recalulate the amount (in case setZoom didn't zoom as much as we wanted)
amount = newZoom / zoom;
// convert the old delta to the new
Point2D newImageDelta = MathUtil.divide(imageDelta, amount);
// calculate the new view position (in image coordinates)
Point2D newImageViewPosition = MathUtil.subtract(imageMousePoint, newImageDelta);
// convert from image coordinates to newZoom (scaled) coordinates
Point2D newViewPosition = MathUtil.multiply(newImageViewPosition, newZoom);
// don't let origin go negative
newViewPosition = MathUtil.max(newViewPosition, MathUtil.zeroPoint2D);
// log.info("mouseWheelMoved: newViewPos2D: {}", newViewPosition);
// set new view position
viewPort.setViewPosition(MathUtil.point2DToPoint(newViewPosition));
} else {
JScrollPane scrollPane = getPanelScrollPane();
if (scrollPane != null) {
if (scrollPane.getVerticalScrollBar().isVisible()) {
// Redispatch the event to the original MouseWheelListeners
for (MouseWheelListener mwl : mouseWheelListeners) {
mwl.mouseWheelMoved(event);
}
} else {
// proprogate event to ancestor
Component ancestor = SwingUtilities.getAncestorOfClass(JScrollPane.class,
scrollPane);
if (ancestor != null) {
MouseWheelEvent mwe = new MouseWheelEvent(
ancestor,
event.getID(),
event.getWhen(),
event.getModifiersEx(),
event.getX(),
event.getY(),
event.getXOnScreen(),
event.getYOnScreen(),
event.getClickCount(),
event.isPopupTrigger(),
event.getScrollType(),
event.getScrollAmount(),
event.getWheelRotation());
ancestor.dispatchEvent(mwe);
}
}
}
}
}
//
// select the apropreate zoom menu item based on the zoomFactor
//
private void selectZoomMenuItem(double zoomFactor) {
// this will put zoomFactor on 100% increments
//(so it will more likely match one of these values)
int newZoomFactor = (int) MathUtil.granulize(zoomFactor, 100);
// int newZoomFactor = ((int) Math.round(zoomFactor)) * 100;
noZoomItem.setSelected(newZoomFactor == 100);
zoom20Item.setSelected(newZoomFactor == 200);
zoom30Item.setSelected(newZoomFactor == 300);
zoom40Item.setSelected(newZoomFactor == 400);
zoom50Item.setSelected(newZoomFactor == 500);
zoom60Item.setSelected(newZoomFactor == 600);
zoom70Item.setSelected(newZoomFactor == 700);
zoom80Item.setSelected(newZoomFactor == 800);
// this will put zoomFactor on 50% increments
//(so it will more likely match one of these values)
// newZoomFactor = ((int) (zoomFactor * 2)) * 50;
newZoomFactor = (int) MathUtil.granulize(zoomFactor, 50);
zoom05Item.setSelected(newZoomFactor == 50);
zoom15Item.setSelected(newZoomFactor == 150);
// this will put zoomFactor on 25% increments
//(so it will more likely match one of these values)
// newZoomFactor = ((int) (zoomFactor * 4)) * 25;
newZoomFactor = (int) MathUtil.granulize(zoomFactor, 25);
zoom025Item.setSelected(newZoomFactor == 25);
zoom075Item.setSelected(newZoomFactor == 75);
}
/**
* setZoom
*
* @param zoomFactor the amount to scale
* @return the new scale amount (not necessarily the same as zoomFactor)
*/
public double setZoom(double zoomFactor) {
double newZoom = MathUtil.pin(zoomFactor, minZoom, maxZoom);
selectZoomMenuItem(newZoom);
if (!MathUtil.equals(newZoom, getPaintScale())) {
log.debug("zoom: {}", zoomFactor);
// setPaintScale(newZoom); //<<== don't call; messes up scrollbars
_paintScale = newZoom; // just set paint scale directly
resetTargetSize(); // calculate new target panel size
adjustScrollBars(); // and adjust the scrollbars ourselves
// adjustClip();
leToolBarPanel.zoomLabel.setText(String.format("x%1$,.2f", newZoom));
// save the window specific saved zoom user preference
InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setProperty(getWindowFrameRef(), "zoom", zoomFactor));
}
return getPaintScale();
}
/**
* getZoom
*
* @return the zooming scale
*/
public double getZoom() {
return getPaintScale();
}
/**
* getMinZoom
*
* @return the minimum zoom scale
*/
public double getMinZoom() {
return minZoom;
}
/**
* getMaxZoom
*
* @return the maximum zoom scale
*/
public double getMaxZoom() {
return maxZoom;
}
//
// TODO: make this public? (might be useful!)
//
private Rectangle2D calculateMinimumLayoutBounds() {
// calculate a union of the bounds of everything on the layout
Rectangle2D result = new Rectangle2D.Double();
// combine all (onscreen) Components into a list of list of Components
List<List<? extends Component>> listOfListsOfComponents = new ArrayList<>();
listOfListsOfComponents.add(backgroundImage);
listOfListsOfComponents.add(sensorImage);
listOfListsOfComponents.add(signalHeadImage);
listOfListsOfComponents.add(markerImage);
listOfListsOfComponents.add(labelImage);
listOfListsOfComponents.add(clocks);
listOfListsOfComponents.add(multiSensors);
listOfListsOfComponents.add(signalList);
listOfListsOfComponents.add(memoryLabelList);
listOfListsOfComponents.add(globalVariableLabelList);
listOfListsOfComponents.add(blockContentsLabelList);
listOfListsOfComponents.add(sensorList);
listOfListsOfComponents.add(signalMastList);
// combine their bounds
for (List<? extends Component> listOfComponents : listOfListsOfComponents) {
for (Component o : listOfComponents) {
if (result.isEmpty()) {
result = o.getBounds();
} else {
result = result.createUnion(o.getBounds());
}
}
}
for (LayoutTrackView ov : getLayoutTrackViews()) {
if (result.isEmpty()) {
result = ov.getBounds();
} else {
result = result.createUnion(ov.getBounds());
}
}
for (LayoutShape o : layoutShapes) {
if (result.isEmpty()) {
result = o.getBounds();
} else {
result = result.createUnion(o.getBounds());
}
}
// put a grid size margin around it
result = MathUtil.inset(result, gContext.getGridSize() * gContext.getGridSize2nd() / -2.0);
return result;
}
/**
* resize panel bounds
*
* @param forceFlag if false only grow bigger
* @return the new (?) panel bounds
*/
private Rectangle2D resizePanelBounds(boolean forceFlag) {
Rectangle2D panelBounds = getPanelBounds();
Rectangle2D layoutBounds = calculateMinimumLayoutBounds();
// make sure it includes the origin
layoutBounds.add(MathUtil.zeroPoint2D);
if (forceFlag) {
panelBounds = layoutBounds;
} else {
panelBounds.add(layoutBounds);
}
// don't let origin go negative
panelBounds = panelBounds.createIntersection(MathUtil.zeroToInfinityRectangle2D);
// log.info("resizePanelBounds: {}", MathUtil.rectangle2DToString(panelBounds));
setPanelBounds(panelBounds);
return panelBounds;
}
private double zoomToFit() {
Rectangle2D layoutBounds = resizePanelBounds(true);
// calculate the bounds for the scroll pane
JScrollPane scrollPane = getPanelScrollPane();
Rectangle2D scrollBounds = scrollPane.getViewportBorderBounds();
// don't let origin go negative
scrollBounds = scrollBounds.createIntersection(MathUtil.zeroToInfinityRectangle2D);
// calculate the horzontial and vertical scales
double scaleWidth = scrollPane.getWidth() / layoutBounds.getWidth();
double scaleHeight = scrollPane.getHeight() / layoutBounds.getHeight();
// set the new zoom to the smallest of the two
double result = setZoom(Math.min(scaleWidth, scaleHeight));
// set the new zoom (return value may be different)
result = setZoom(result);
// calculate new scroll bounds
scrollBounds = MathUtil.scale(layoutBounds, result);
// don't let origin go negative
scrollBounds = scrollBounds.createIntersection(MathUtil.zeroToInfinityRectangle2D);
// make sure it includes the origin
scrollBounds.add(MathUtil.zeroPoint2D);
// and scroll to it
scrollPane.scrollRectToVisible(MathUtil.rectangle2DToRectangle(scrollBounds));
return result;
}
private Point2D windowCenter() {
// Returns window's center coordinates converted to layout space
// Used for initial setup of turntables and reporters
return MathUtil.divide(MathUtil.center(getBounds()), getZoom());
}
private void setupMarkerMenu(@Nonnull JMenuBar menuBar) {
JMenu markerMenu = new JMenu(Bundle.getMessage("MenuMarker"));
markerMenu.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("MenuMarkerMnemonic")));
menuBar.add(markerMenu);
markerMenu.add(new AbstractAction(Bundle.getMessage("AddLoco") + "...") {
@Override
public void actionPerformed(ActionEvent event) {
locoMarkerFromInput();
}
});
markerMenu.add(new AbstractAction(Bundle.getMessage("AddLocoRoster") + "...") {
@Override
public void actionPerformed(ActionEvent event) {
locoMarkerFromRoster();
}
});
markerMenu.add(new AbstractAction(Bundle.getMessage("RemoveMarkers")) {
@Override
public void actionPerformed(ActionEvent event) {
removeMarkers();
}
});
}
private void setupDispatcherMenu(@Nonnull JMenuBar menuBar) {
JMenu dispMenu = new JMenu(Bundle.getMessage("MenuDispatcher"));
dispMenu.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("MenuDispatcherMnemonic")));
dispMenu.add(new JMenuItem(new DispatcherAction(Bundle.getMessage("MenuItemOpen"))));
menuBar.add(dispMenu);
JMenuItem newTrainItem = new JMenuItem(Bundle.getMessage("MenuItemNewTrain"));
dispMenu.add(newTrainItem);
newTrainItem.addActionListener((ActionEvent event) -> {
if (InstanceManager.getDefault(TransitManager.class).getNamedBeanSet().isEmpty()) {
// Inform the user that there are no Transits available, and don't open the window
JmriJOptionPane.showMessageDialog(
null,
ResourceBundle.getBundle("jmri.jmrit.dispatcher.DispatcherBundle").
getString("NoTransitsMessage"));
} else {
DispatcherFrame df = InstanceManager.getDefault(DispatcherFrame.class
);
if (!df.getNewTrainActive()) {
df.getActiveTrainFrame().initiateTrain(event, null, null);
df.setNewTrainActive(true);
} else {
df.getActiveTrainFrame().showActivateFrame(null);
}
}
});
menuBar.add(dispMenu);
}
private boolean includedTurnoutSkipped = false;
public boolean isIncludedTurnoutSkipped() {
return includedTurnoutSkipped;
}
public void setIncludedTurnoutSkipped(Boolean boo) {
includedTurnoutSkipped = boo;
}
boolean openDispatcherOnLoad = false;
// TODO: Java standard pattern for boolean getters is "isOpenDispatcherOnLoad()"
public boolean getOpenDispatcherOnLoad() {
return openDispatcherOnLoad;
}
public void setOpenDispatcherOnLoad(Boolean boo) {
openDispatcherOnLoad = boo;
}
/**
* Remove marker icons from panel
*/
@Override
public void removeMarkers() {
for (int i = markerImage.size(); i > 0; i--) {
LocoIcon il = markerImage.get(i - 1);
if ((il != null) && (il.isActive())) {
markerImage.remove(i - 1);
il.remove();
il.dispose();
setDirty();
}
}
super.removeMarkers();
redrawPanel();
}
/**
* Assign the block from the toolbar to all selected layout tracks
*/
private void assignBlockToSelection() {
String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
if (newName == null) {
newName = "";
}
LayoutBlock b = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(newName);
_layoutTrackSelection.forEach((lt) -> lt.setAllLayoutBlocks(b));
}
public boolean translateTrack(float xDel, float yDel) {
Point2D delta = new Point2D.Double(xDel, yDel);
getLayoutTrackViews().forEach((ltv) -> ltv.setCoordsCenter(MathUtil.add(ltv.getCoordsCenter(), delta)));
resizePanelBounds(true);
return true;
}
/**
* scale all LayoutTracks coordinates by the x and y factors.
*
* @param xFactor the amount to scale X coordinates.
* @param yFactor the amount to scale Y coordinates.
* @return true when complete.
*/
public boolean scaleTrack(float xFactor, float yFactor) {
getLayoutTrackViews().forEach((ltv) -> ltv.scaleCoords(xFactor, yFactor));
// update the overall scale factors
gContext.setXScale(gContext.getXScale() * xFactor);
gContext.setYScale(gContext.getYScale() * yFactor);
resizePanelBounds(true);
return true;
}
/**
* loop through all LayoutBlocks and set colors to the default colors from
* this LayoutEditor
*
* @return count of changed blocks
*/
public int setAllTracksToDefaultColors() {
LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class
);
SortedSet<LayoutBlock> lBList = lbm.getNamedBeanSet();
int changed = 0;
for (LayoutBlock lb : lBList) {
lb.setBlockTrackColor(this.getDefaultTrackColorColor());
lb.setBlockOccupiedColor(this.getDefaultOccupiedTrackColorColor());
lb.setBlockExtraColor(this.getDefaultAlternativeTrackColorColor());
changed++;
}
log.info("Track Colors set to default values for {} layoutBlocks.", changed);
return changed;
}
private Rectangle2D undoRect;
private boolean canUndoMoveSelection = false;
private Point2D undoDelta = MathUtil.zeroPoint2D;
/**
* Translate entire layout by x and y amounts.
*
* @param xTranslation horizontal (X) translation value
* @param yTranslation vertical (Y) translation value
*/
public void translate(float xTranslation, float yTranslation) {
// here when all numbers read in - translation if entered
if ((xTranslation != 0.0F) || (yTranslation != 0.0F)) {
Point2D delta = new Point2D.Double(xTranslation, yTranslation);
Rectangle2D selectionRect = getSelectionRect();
// set up undo information
undoRect = MathUtil.offset(selectionRect, delta);
undoDelta = MathUtil.subtract(MathUtil.zeroPoint2D, delta);
canUndoMoveSelection = true;
undoTranslateSelectionMenuItem.setEnabled(canUndoMoveSelection);
// apply translation to icon items within the selection
for (Positionable c : _positionableSelection) {
Point2D newPoint = MathUtil.add(c.getLocation(), delta);
c.setLocation((int) newPoint.getX(), (int) newPoint.getY());
}
for (LayoutTrack lt : _layoutTrackSelection) {
LayoutTrackView ltv = getLayoutTrackView(lt);
ltv.setCoordsCenter(MathUtil.add(ltv.getCoordsCenter(), delta));
}
for (LayoutShape ls : _layoutShapeSelection) {
ls.setCoordsCenter(MathUtil.add(ls.getCoordsCenter(), delta));
}
selectionX = undoRect.getX();
selectionY = undoRect.getY();
selectionWidth = undoRect.getWidth();
selectionHeight = undoRect.getHeight();
resizePanelBounds(false);
setDirty();
redrawPanel();
}
}
/**
* undo the move selection
*/
void undoMoveSelection() {
if (canUndoMoveSelection) {
_positionableSelection.forEach((c) -> {
Point2D newPoint = MathUtil.add(c.getLocation(), undoDelta);
c.setLocation((int) newPoint.getX(), (int) newPoint.getY());
});
_layoutTrackSelection.forEach(
(lt) -> {
LayoutTrackView ltv = getLayoutTrackView(lt);
ltv.setCoordsCenter(MathUtil.add(ltv.getCoordsCenter(), undoDelta));
}
);
_layoutShapeSelection.forEach((ls) -> ls.setCoordsCenter(MathUtil.add(ls.getCoordsCenter(), undoDelta)));
undoRect = MathUtil.offset(undoRect, undoDelta);
selectionX = undoRect.getX();
selectionY = undoRect.getY();
selectionWidth = undoRect.getWidth();
selectionHeight = undoRect.getHeight();
resizePanelBounds(false);
redrawPanel();
canUndoMoveSelection = false;
undoTranslateSelectionMenuItem.setEnabled(canUndoMoveSelection);
}
}
/**
* Rotate selection by 90 degrees clockwise.
*/
public void rotateSelection90() {
Rectangle2D bounds = getSelectionRect();
Point2D center = MathUtil.midPoint(bounds);
for (Positionable positionable : _positionableSelection) {
Rectangle2D cBounds = positionable.getBounds(new Rectangle());
Point2D oldBottomLeft = new Point2D.Double(cBounds.getMinX(), cBounds.getMaxY());
Point2D newTopLeft = MathUtil.rotateDEG(oldBottomLeft, center, 90);
boolean rotateFlag = true;
if (positionable instanceof PositionableLabel) {
PositionableLabel positionableLabel = (PositionableLabel) positionable;
if (positionableLabel.isBackground()) {
rotateFlag = false;
}
}
if (rotateFlag) {
positionable.rotate(positionable.getDegrees() + 90);
positionable.setLocation((int) newTopLeft.getX(), (int) newTopLeft.getY());
}
}
for (LayoutTrack lt : _layoutTrackSelection) {
LayoutTrackView ltv = getLayoutTrackView(lt);
ltv.setCoordsCenter(MathUtil.rotateDEG(ltv.getCoordsCenter(), center, 90));
ltv.rotateCoords(90);
}
for (LayoutShape ls : _layoutShapeSelection) {
ls.setCoordsCenter(MathUtil.rotateDEG(ls.getCoordsCenter(), center, 90));
ls.rotateCoords(90);
}
resizePanelBounds(true);
setDirty();
redrawPanel();
}
/**
* Rotate the entire layout by 90 degrees clockwise.
*/
public void rotateLayout90() {
List<Positionable> positionables = new ArrayList<>(getContents());
positionables.addAll(backgroundImage);
positionables.addAll(blockContentsLabelList);
positionables.addAll(labelImage);
positionables.addAll(memoryLabelList);
positionables.addAll(globalVariableLabelList);
positionables.addAll(sensorImage);
positionables.addAll(sensorList);
positionables.addAll(signalHeadImage);
positionables.addAll(signalList);
positionables.addAll(signalMastList);
// do this to remove duplicates that may be in more than one list
positionables = positionables.stream().distinct().collect(Collectors.toList());
Rectangle2D bounds = getPanelBounds();
Point2D lowerLeft = new Point2D.Double(bounds.getMinX(), bounds.getMaxY());
for (Positionable positionable : positionables) {
Rectangle2D cBounds = positionable.getBounds(new Rectangle());
Point2D newTopLeft = MathUtil.subtract(MathUtil.rotateDEG(positionable.getLocation(), lowerLeft, 90), lowerLeft);
boolean reLocateFlag = true;
if (positionable instanceof PositionableLabel) {
try {
PositionableLabel positionableLabel = (PositionableLabel) positionable;
if (positionableLabel.isBackground()) {
reLocateFlag = false;
}
positionableLabel.rotate(positionableLabel.getDegrees() + 90);
} catch (NullPointerException ex) {
log.warn("previously-ignored NPE", ex);
}
}
if (reLocateFlag) {
try {
positionable.setLocation((int) (newTopLeft.getX() - cBounds.getHeight()), (int) newTopLeft.getY());
} catch (NullPointerException ex) {
log.warn("previously-ignored NPE", ex);
}
}
}
for (LayoutTrackView ltv : getLayoutTrackViews()) {
try {
Point2D newPoint = MathUtil.subtract(MathUtil.rotateDEG(ltv.getCoordsCenter(), lowerLeft, 90), lowerLeft);
ltv.setCoordsCenter(newPoint);
ltv.rotateCoords(90);
} catch (NullPointerException ex) {
log.warn("previously-ignored NPE", ex);
}
}
for (LayoutShape ls : layoutShapes) {
Point2D newPoint = MathUtil.subtract(MathUtil.rotateDEG(ls.getCoordsCenter(), lowerLeft, 90), lowerLeft);
ls.setCoordsCenter(newPoint);
ls.rotateCoords(90);
}
resizePanelBounds(true);
setDirty();
redrawPanel();
}
/**
* align the layout to grid
*/
public void alignLayoutToGrid() {
// align to grid
List<Positionable> positionables = new ArrayList<>(getContents());
positionables.addAll(backgroundImage);
positionables.addAll(blockContentsLabelList);
positionables.addAll(labelImage);
positionables.addAll(memoryLabelList);
positionables.addAll(globalVariableLabelList);
positionables.addAll(sensorImage);
positionables.addAll(sensorList);
positionables.addAll(signalHeadImage);
positionables.addAll(signalList);
positionables.addAll(signalMastList);
// do this to remove duplicates that may be in more than one list
positionables = positionables.stream().distinct().collect(Collectors.toList());
alignToGrid(positionables, getLayoutTracks(), layoutShapes);
}
/**
* align selection to grid
*/
public void alignSelectionToGrid() {
alignToGrid(_positionableSelection, _layoutTrackSelection, _layoutShapeSelection);
}
private void alignToGrid(List<Positionable> positionables, List<LayoutTrack> tracks, List<LayoutShape> shapes) {
for (Positionable positionable : positionables) {
Point2D newLocation = MathUtil.granulize(positionable.getLocation(), gContext.getGridSize());
positionable.setLocation((int) (newLocation.getX()), (int) newLocation.getY());
}
for (LayoutTrack lt : tracks) {
LayoutTrackView ltv = getLayoutTrackView(lt);
ltv.setCoordsCenter(MathUtil.granulize(ltv.getCoordsCenter(), gContext.getGridSize()));
if (lt instanceof LayoutTurntable) {
LayoutTurntable tt = (LayoutTurntable) lt;
LayoutTurntableView ttv = getLayoutTurntableView(tt);
for (LayoutTurntable.RayTrack rt : tt.getRayTrackList()) {
int rayIndex = rt.getConnectionIndex();
ttv.setRayCoordsIndexed(MathUtil.granulize(ttv.getRayCoordsIndexed(rayIndex), gContext.getGridSize()), rayIndex);
}
}
}
for (LayoutShape ls : shapes) {
ls.setCoordsCenter(MathUtil.granulize(ls.getCoordsCenter(), gContext.getGridSize()));
for (int idx = 0; idx < ls.getNumberPoints(); idx++) {
ls.setPoint(idx, MathUtil.granulize(ls.getPoint(idx), gContext.getGridSize()));
}
}
resizePanelBounds(true);
setDirty();
redrawPanel();
}
public void setCurrentPositionAndSize() {
// save current panel location and size
Dimension dim = getSize();
// Compute window size based on LayoutEditor size
gContext.setWindowHeight(dim.height);
gContext.setWindowWidth(dim.width);
// Compute layout size based on LayoutPane size
dim = getTargetPanelSize();
gContext.setLayoutWidth((int) (dim.width / getZoom()));
gContext.setLayoutHeight((int) (dim.height / getZoom()));
adjustScrollBars();
Point pt = getLocationOnScreen();
gContext.setUpperLeftY(pt.x);
gContext.setUpperLeftY(pt.y);
log.debug("setCurrentPositionAndSize Position - {},{} WindowSize - {},{} PanelSize - {},{}", gContext.getUpperLeftX(), gContext.getUpperLeftY(), gContext.getWindowWidth(), gContext.getWindowHeight(), gContext.getLayoutWidth(), gContext.getLayoutHeight());
setDirty();
}
private JRadioButtonMenuItem addButtonGroupMenuEntry(
@Nonnull JMenu inMenu,
ButtonGroup inButtonGroup,
final String inName,
boolean inSelected,
ActionListener inActionListener) {
JRadioButtonMenuItem result = new JRadioButtonMenuItem(inName);
if (inActionListener != null) {
result.addActionListener(inActionListener);
}
if (inButtonGroup != null) {
inButtonGroup.add(result);
}
result.setSelected(inSelected);
inMenu.add(result);
return result;
}
private void addTurnoutCircleSizeMenuEntry(
@Nonnull JMenu inMenu,
@Nonnull String inName,
final int inSize) {
ActionListener a = (ActionEvent event) -> {
if (getTurnoutCircleSize() != inSize) {
setTurnoutCircleSize(inSize);
setDirty();
redrawPanel();
}
};
addButtonGroupMenuEntry(inMenu,
turnoutCircleSizeButtonGroup, inName,
getTurnoutCircleSize() == inSize, a);
}
private void setOptionMenuTurnoutCircleSize() {
String tcs = Integer.toString(getTurnoutCircleSize());
Enumeration<AbstractButton> e = turnoutCircleSizeButtonGroup.getElements();
while (e.hasMoreElements()) {
AbstractButton button = e.nextElement();
String buttonName = button.getText();
button.setSelected(buttonName.equals(tcs));
}
}
@Override
public void setScroll(int state) {
if (isEditable()) {
// In edit mode the scroll bars are always displayed, however we will want to set the scroll for when we exit edit mode
super.setScroll(Editor.SCROLL_BOTH);
_scrollState = state;
} else {
super.setScroll(state);
}
}
/**
* The LE xml load uses the string version of setScroll which went directly to
* Editor. The string version has been added here so that LE can set the scroll
* selection.
* @param value The new scroll value.
*/
@Override
public void setScroll(String value) {
if (value != null) super.setScroll(value);
scrollNoneMenuItem.setSelected(_scrollState == Editor.SCROLL_NONE);
scrollBothMenuItem.setSelected(_scrollState == Editor.SCROLL_BOTH);
scrollHorizontalMenuItem.setSelected(_scrollState == Editor.SCROLL_HORIZONTAL);
scrollVerticalMenuItem.setSelected(_scrollState == Editor.SCROLL_VERTICAL);
}
/**
* Add a layout turntable at location specified
*
* @param pt x,y placement for turntable
*/
public void addTurntable(@Nonnull Point2D pt) {
// get unique name
String name = finder.uniqueName("TUR", ++numLayoutTurntables);
LayoutTurntable lt = new LayoutTurntable(name, this);
LayoutTurntableView ltv = new LayoutTurntableView(lt, pt, this);
addLayoutTrack(lt, ltv);
lt.addRay(0.0);
lt.addRay(90.0);
lt.addRay(180.0);
lt.addRay(270.0);
setDirty();
}
/**
* Allow external trigger of re-drawHidden
*/
@Override
public void redrawPanel() {
repaint();
}
/**
* Allow external set/reset of awaitingIconChange
*/
public void setAwaitingIconChange() {
awaitingIconChange = true;
}
public void resetAwaitingIconChange() {
awaitingIconChange = false;
}
/**
* Allow external reset of dirty bit
*/
public void resetDirty() {
setDirty(false);
savedEditMode = isEditable();
savedPositionable = allPositionable();
savedControlLayout = allControlling();
savedAnimatingLayout = isAnimating();
savedShowHelpBar = getShowHelpBar();
}
/**
* Allow external set of dirty bit
*
* @param val true/false for panelChanged
*/
public void setDirty(boolean val) {
panelChanged = val;
}
@Override
public void setDirty() {
setDirty(true);
}
/**
* Check the dirty state.
*
* @return true if panel has changed
*/
@Override
public boolean isDirty() {
return panelChanged;
}
/*
* Get mouse coordinates and adjust for zoom.
* <p>
* Side effects on xLoc, yLoc and dLoc
*/
@Nonnull
private Point2D calcLocation(JmriMouseEvent event, int dX, int dY) {
xLoc = (int) ((event.getX() + dX) / getZoom());
yLoc = (int) ((event.getY() + dY) / getZoom());
dLoc = new Point2D.Double(xLoc, yLoc);
return dLoc;
}
private Point2D calcLocation(JmriMouseEvent event) {
return calcLocation(event, 0, 0);
}
/**
* Handle a mouse pressed event
* <p>
* Side-effects on _anchorX, _anchorY,_lastX, _lastY, xLoc, yLoc, dLoc,
* selectionActive, xLabel, yLabel
*
* @param event the JmriMouseEvent
*/
@Override
public void mousePressed(JmriMouseEvent event) {
// initialize cursor position
_anchorX = xLoc;
_anchorY = yLoc;
_lastX = _anchorX;
_lastY = _anchorY;
calcLocation(event);
// TODO: Add command-click on nothing to pan view?
if (isEditable()) {
boolean prevSelectionActive = selectionActive;
selectionActive = false;
leToolBarPanel.setLocationText(dLoc);
if (event.isPopupTrigger()) {
if (event.isMetaDown() || event.isAltDown()) {
// if requesting a popup and it might conflict with moving, delay the request to mouseReleased
delayedPopupTrigger = true;
} else {
// no possible conflict with moving, display the popup now
showEditPopUps(event);
}
}
if (event.isMetaDown() || event.isAltDown()) {
// if dragging an item, identify the item for mouseDragging
selectedObject = null;
selectedHitPointType = HitPointType.NONE;
if (findLayoutTracksHitPoint(dLoc)) {
selectedObject = foundTrack;
selectedHitPointType = foundHitPointType;
startDelta = MathUtil.subtract(foundLocation, dLoc);
foundTrack = null;
foundTrackView = null;
} else {
selectedObject = checkMarkerPopUps(dLoc);
if (selectedObject != null) {
selectedHitPointType = HitPointType.MARKER;
startDelta = MathUtil.subtract(((LocoIcon) selectedObject).getLocation(), dLoc);
} else {
selectedObject = checkClockPopUps(dLoc);
if (selectedObject != null) {
selectedHitPointType = HitPointType.LAYOUT_POS_JCOMP;
startDelta = MathUtil.subtract(((PositionableJComponent) selectedObject).getLocation(), dLoc);
} else {
selectedObject = checkMultiSensorPopUps(dLoc);
if (selectedObject != null) {
selectedHitPointType = HitPointType.MULTI_SENSOR;
startDelta = MathUtil.subtract(((MultiSensorIcon) selectedObject).getLocation(), dLoc);
}
}
}
if (selectedObject == null) {
selectedObject = checkSensorIconPopUps(dLoc);
if (selectedObject == null) {
selectedObject = checkSignalHeadIconPopUps(dLoc);
if (selectedObject == null) {
selectedObject = checkLabelImagePopUps(dLoc);
if (selectedObject == null) {
selectedObject = checkSignalMastIconPopUps(dLoc);
}
}
}
if (selectedObject != null) {
selectedHitPointType = HitPointType.LAYOUT_POS_LABEL;
startDelta = MathUtil.subtract(((PositionableLabel) selectedObject).getLocation(), dLoc);
if (selectedObject instanceof MemoryIcon) {
MemoryIcon pm = (MemoryIcon) selectedObject;
if (pm.getPopupUtility().getFixedWidth() == 0) {
startDelta = new Point2D.Double((pm.getOriginalX() - dLoc.getX()),
(pm.getOriginalY() - dLoc.getY()));
}
}
if (selectedObject instanceof GlobalVariableIcon) {
GlobalVariableIcon pm = (GlobalVariableIcon) selectedObject;
if (pm.getPopupUtility().getFixedWidth() == 0) {
startDelta = new Point2D.Double((pm.getOriginalX() - dLoc.getX()),
(pm.getOriginalY() - dLoc.getY()));
}
}
} else {
selectedObject = checkBackgroundPopUps(dLoc);
if (selectedObject != null) {
selectedHitPointType = HitPointType.LAYOUT_POS_LABEL;
startDelta = MathUtil.subtract(((PositionableLabel) selectedObject).getLocation(), dLoc);
} else {
// dragging a shape?
ListIterator<LayoutShape> listIterator = layoutShapes.listIterator(layoutShapes.size());
// hit test in front to back order (reverse order of list)
while (listIterator.hasPrevious()) {
LayoutShape ls = listIterator.previous();
selectedHitPointType = ls.findHitPointType(dLoc, true);
if (LayoutShape.isShapeHitPointType(selectedHitPointType)) {
// log.warn("drag selectedObject: ", lt);
selectedObject = ls; // found one!
beginLocation = dLoc;
currentLocation = beginLocation;
startDelta = MathUtil.zeroPoint2D;
break;
}
}
}
}
}
}
} else if (event.isShiftDown() && leToolBarPanel.trackButton.isSelected() && !event.isPopupTrigger()) {
// starting a Track Segment, check for free connection point
selectedObject = null;
if (findLayoutTracksHitPoint(dLoc, true)) {
// match to a free connection point
beginTrack = foundTrack;
beginHitPointType = foundHitPointType;
beginLocation = foundLocation;
// BUGFIX: prevents initial drawTrackSegmentInProgress to {0, 0}
currentLocation = beginLocation;
} else {
// TODO: auto-add anchor point?
beginTrack = null;
}
} else if (event.isShiftDown() && leToolBarPanel.shapeButton.isSelected() && !event.isPopupTrigger()) {
// adding or extending a shape
selectedObject = null; // assume we're adding...
for (LayoutShape ls : layoutShapes) {
selectedHitPointType = ls.findHitPointType(dLoc, true);
if (HitPointType.isShapePointOffsetHitPointType(selectedHitPointType)) {
// log.warn("extend selectedObject: ", lt);
selectedObject = ls; // nope, we're extending
beginLocation = dLoc;
currentLocation = beginLocation;
break;
}
}
} else if (!event.isShiftDown() && !event.isControlDown() && !event.isPopupTrigger()) {
// check if controlling a turnout in edit mode
selectedObject = null;
if (allControlling()) {
checkControls(false);
}
// initialize starting selection - cancel any previous selection rectangle
selectionActive = true;
selectionX = dLoc.getX();
selectionY = dLoc.getY();
selectionWidth = 0.0;
selectionHeight = 0.0;
}
if (prevSelectionActive) {
redrawPanel();
}
} else if (allControlling()
&& !event.isMetaDown() && !event.isPopupTrigger()
&& !event.isAltDown() && !event.isShiftDown() && !event.isControlDown()) {
// not in edit mode - check if mouse is on a turnout (using wider search range)
selectedObject = null;
checkControls(true);
} else if ((event.isMetaDown() || event.isAltDown())
&& !event.isShiftDown() && !event.isControlDown()) {
// not in edit mode - check if moving a marker if there are any
selectedObject = checkMarkerPopUps(dLoc);
if (selectedObject != null) {
selectedHitPointType = HitPointType.MARKER;
startDelta = MathUtil.subtract(((LocoIcon) selectedObject).getLocation(), dLoc);
}
} else if (event.isPopupTrigger() && !event.isShiftDown()) {
// not in edit mode - check if a marker popup menu is being requested
LocoIcon lo = checkMarkerPopUps(dLoc);
if (lo != null) {
delayedPopupTrigger = true;
}
}
if (!event.isPopupTrigger()) {
List<Positionable> selections = getSelectedItems(event);
if (!selections.isEmpty()) {
selections.get(0).doMousePressed(event);
}
}
requestFocusInWindow();
} // mousePressed
// this is a method to iterate over a list of lists of items
// calling the predicate tester.test on each one
// all matching items are then added to the resulting List
// note: currently unused; commented out to avoid findbugs warning
// private static List testEachItemInListOfLists(
// @Nonnull List<List> listOfListsOfObjects,
// @Nonnull Predicate<Object> tester) {
// List result = new ArrayList<>();
// for (List<Object> listOfObjects : listOfListsOfObjects) {
// List<Object> l = listOfObjects.stream().filter(o -> tester.test(o)).collect(Collectors.toList());
// result.addAll(l);
// }
// return result;
//}
// this is a method to iterate over a list of lists of items
// calling the predicate tester.test on each one
// and return the first one that matches
// TODO: make this public? (it is useful! ;-)
// note: currently unused; commented out to avoid findbugs warning
// private static Object findFirstMatchingItemInListOfLists(
// @Nonnull List<List> listOfListsOfObjects,
// @Nonnull Predicate<Object> tester) {
// Object result = null;
// for (List listOfObjects : listOfListsOfObjects) {
// Optional<Object> opt = listOfObjects.stream().filter(o -> tester.test(o)).findFirst();
// if (opt.isPresent()) {
// result = opt.get();
// break;
// }
// }
// return result;
//}
/**
* Called by {@link #mousePressed} to determine if the mouse click was in a
* turnout control location. If so, update selectedHitPointType and
* selectedObject for use by {@link #mouseReleased}.
* <p>
* If there's no match, selectedObject is set to null and
* selectedHitPointType is left referring to the results of the checking the
* last track on the list.
* <p>
* Refers to the current value of {@link #getLayoutTracks()} and
* {@link #dLoc}.
*
* @param useRectangles set true to use rectangle; false for circles.
*/
private void checkControls(boolean useRectangles) {
selectedObject = null; // deliberate side-effect
for (LayoutTrackView theTrackView : getLayoutTrackViews()) {
selectedHitPointType = theTrackView.findHitPointType(dLoc, useRectangles); // deliberate side-effect
if (HitPointType.isControlHitType(selectedHitPointType)) {
selectedObject = theTrackView.getLayoutTrack(); // deliberate side-effect
return;
}
}
}
// This is a geometric search, and should be done with views.
// Hence this form is inevitably temporary.
//
private boolean findLayoutTracksHitPoint(
@Nonnull Point2D loc, boolean requireUnconnected) {
return findLayoutTracksHitPoint(loc, requireUnconnected, null);
}
// This is a geometric search, and should be done with views.
// Hence this form is inevitably temporary.
//
// optional parameter requireUnconnected
private boolean findLayoutTracksHitPoint(@Nonnull Point2D loc) {
return findLayoutTracksHitPoint(loc, false, null);
}
/**
* Internal (private) method to find the track closest to a point, with some
* modifiers to the search. The {@link #foundTrack} and
* {@link #foundHitPointType} members are set from the search.
* <p>
* This is a geometric search, and should be done with views. Hence this
* form is inevitably temporary.
*
* @param loc Point to search from
* @param requireUnconnected forwarded to {@link #getLayoutTrackView}; if
* true, return only free connections
* @param avoid Don't return this track, keep searching. Note
* that {@Link #selectedObject} is also always
* avoided automatically
* @returns true if values of {@link #foundTrack} and
* {@link #foundHitPointType} correct; note they may have changed even if
* false is returned.
*/
private boolean findLayoutTracksHitPoint(@Nonnull Point2D loc,
boolean requireUnconnected, @CheckForNull LayoutTrack avoid) {
boolean result = false; // assume failure (pessimist!)
foundTrack = null;
foundTrackView = null;
foundHitPointType = HitPointType.NONE;
Optional<LayoutTrack> opt = getLayoutTracks().stream().filter(layoutTrack -> { // != means can't (yet) loop over Views
if ((layoutTrack != avoid) && (layoutTrack != selectedObject)) {
foundHitPointType = getLayoutTrackView(layoutTrack).findHitPointType(loc, false, requireUnconnected);
}
return (HitPointType.NONE != foundHitPointType);
}).findFirst();
LayoutTrack layoutTrack = null;
if (opt.isPresent()) {
layoutTrack = opt.get();
}
if (layoutTrack != null) {
foundTrack = layoutTrack;
foundTrackView = this.getLayoutTrackView(layoutTrack);
// get screen coordinates
foundLocation = foundTrackView.getCoordsForConnectionType(foundHitPointType);
/// foundNeedsConnect = isDisconnected(foundHitPointType);
result = true;
}
return result;
}
private TrackSegment checkTrackSegmentPopUps(@Nonnull Point2D loc) {
assert loc != null;
TrackSegment result = null;
// NOTE: Rather than calculate all the hit rectangles for all
// the points below and test if this location is in any of those
// rectangles just create a hit rectangle for the location and
// see if any of the points below are in it instead...
Rectangle2D r = layoutEditorControlCircleRectAt(loc);
// check Track Segments, if any
for (TrackSegmentView tsv : getTrackSegmentViews()) {
if (r.contains(tsv.getCentreSeg())) {
result = tsv.getTrackSegment();
break;
}
}
return result;
}
private PositionableLabel checkBackgroundPopUps(@Nonnull Point2D loc) {
assert loc != null;
PositionableLabel result = null;
// check background images, if any
for (int i = backgroundImage.size() - 1; i >= 0; i--) {
PositionableLabel b = backgroundImage.get(i);
Rectangle2D r = b.getBounds();
if (r.contains(loc)) {
result = b;
break;
}
}
return result;
}
private SensorIcon checkSensorIconPopUps(@Nonnull Point2D loc) {
assert loc != null;
SensorIcon result = null;
// check sensor images, if any
for (int i = sensorImage.size() - 1; i >= 0; i--) {
SensorIcon s = sensorImage.get(i);
Rectangle2D r = s.getBounds();
if (r.contains(loc)) {
result = s;
}
}
return result;
}
private SignalHeadIcon checkSignalHeadIconPopUps(@Nonnull Point2D loc) {
assert loc != null;
SignalHeadIcon result = null;
// check signal head images, if any
for (int i = signalHeadImage.size() - 1; i >= 0; i--) {
SignalHeadIcon s = signalHeadImage.get(i);
Rectangle2D r = s.getBounds();
if (r.contains(loc)) {
result = s;
break;
}
}
return result;
}
private SignalMastIcon checkSignalMastIconPopUps(@Nonnull Point2D loc) {
assert loc != null;
SignalMastIcon result = null;
// check signal head images, if any
for (int i = signalMastList.size() - 1; i >= 0; i--) {
SignalMastIcon s = signalMastList.get(i);
Rectangle2D r = s.getBounds();
if (r.contains(loc)) {
result = s;
break;
}
}
return result;
}
private PositionableLabel checkLabelImagePopUps(@Nonnull Point2D loc) {
assert loc != null;
PositionableLabel result = null;
int level = 0;
for (int i = labelImage.size() - 1; i >= 0; i--) {
PositionableLabel s = labelImage.get(i);
double x = s.getX();
double y = s.getY();
double w = 10.0;
double h = 5.0;
if (s.isIcon() || s.isRotated() || s.getPopupUtility().getOrientation() != PositionablePopupUtil.HORIZONTAL) {
w = s.maxWidth();
h = s.maxHeight();
} else if (s.isText()) {
h = s.getFont().getSize();
w = (h * 2 * (s.getText().length())) / 3;
}
Rectangle2D r = new Rectangle2D.Double(x, y, w, h);
if (r.contains(loc)) {
if (s.getDisplayLevel() >= level) {
// Check to make sure that we are returning the highest level label.
result = s;
level = s.getDisplayLevel();
}
}
}
return result;
}
private AnalogClock2Display checkClockPopUps(@Nonnull Point2D loc) {
assert loc != null;
AnalogClock2Display result = null;
// check clocks, if any
for (int i = clocks.size() - 1; i >= 0; i--) {
AnalogClock2Display s = clocks.get(i);
Rectangle2D r = s.getBounds();
if (r.contains(loc)) {
result = s;
break;
}
}
return result;
}
private MultiSensorIcon checkMultiSensorPopUps(@Nonnull Point2D loc) {
assert loc != null;
MultiSensorIcon result = null;
// check multi sensor icons, if any
for (int i = multiSensors.size() - 1; i >= 0; i--) {
MultiSensorIcon s = multiSensors.get(i);
Rectangle2D r = s.getBounds();
if (r.contains(loc)) {
result = s;
break;
}
}
return result;
}
private LocoIcon checkMarkerPopUps(@Nonnull Point2D loc) {
assert loc != null;
LocoIcon result = null;
// check marker icons, if any
for (int i = markerImage.size() - 1; i >= 0; i--) {
LocoIcon l = markerImage.get(i);
Rectangle2D r = l.getBounds();
if (r.contains(loc)) {
// mouse was pressed in marker icon
result = l;
break;
}
}
return result;
}
private LayoutShape checkLayoutShapePopUps(@Nonnull Point2D loc) {
assert loc != null;
LayoutShape result = null;
for (LayoutShape ls : layoutShapes) {
selectedHitPointType = ls.findHitPointType(loc, true);
if (LayoutShape.isShapeHitPointType(selectedHitPointType)) {
result = ls;
break;
}
}
return result;
}
/**
* Get the coordinates for the connection type of the specified LayoutTrack
* or subtype.
* <p>
* This uses the current LayoutEditor object to map a LayoutTrack (no
* coordinates) object to _a_ specific LayoutTrackView object in the current
* LayoutEditor i.e. window. This allows the same model object in two
* windows, but not twice in a single window.
* <p>
* This is temporary, and needs to go away as the LayoutTrack doesn't
* logically have position; just the LayoutTrackView does, and multiple
* LayoutTrackViews can refer to one specific LayoutTrack.
*
* @param trk the object (LayoutTrack subclass)
* @param connectionType the type of connection
* @return the coordinates for the connection type of the specified object
*/
@Nonnull
public Point2D getCoords(@Nonnull LayoutTrack trk, HitPointType connectionType) {
assert trk != null;
return getCoords(getLayoutTrackView(trk), connectionType);
}
/**
* Get the coordinates for the connection type of the specified
* LayoutTrackView or subtype.
*
* @param trkv the object (LayoutTrackView subclass)
* @param connectionType the type of connection
* @return the coordinates for the connection type of the specified object
*/
@Nonnull
public Point2D getCoords(@Nonnull LayoutTrackView trkv, HitPointType connectionType) {
assert trkv != null;
return trkv.getCoordsForConnectionType(connectionType);
}
@Override
public void mouseReleased(JmriMouseEvent event) {
super.setToolTip(null);
// initialize mouse position
calcLocation(event);
// if alt modifier is down invert the snap to grid behaviour
snapToGridInvert = event.isAltDown();
if (isEditable()) {
leToolBarPanel.setLocationText(dLoc);
// released the mouse with shift down... see what we're adding
if (!event.isPopupTrigger() && !event.isMetaDown() && event.isShiftDown()) {
currentPoint = new Point2D.Double(xLoc, yLoc);
if (snapToGridOnAdd != snapToGridInvert) {
// this snaps the current point to the grid
currentPoint = MathUtil.granulize(currentPoint, gContext.getGridSize());
xLoc = (int) currentPoint.getX();
yLoc = (int) currentPoint.getY();
leToolBarPanel.setLocationText(currentPoint);
}
if (leToolBarPanel.turnoutRHButton.isSelected()) {
addLayoutTurnout(LayoutTurnout.TurnoutType.RH_TURNOUT);
} else if (leToolBarPanel.turnoutLHButton.isSelected()) {
addLayoutTurnout(LayoutTurnout.TurnoutType.LH_TURNOUT);
} else if (leToolBarPanel.turnoutWYEButton.isSelected()) {
addLayoutTurnout(LayoutTurnout.TurnoutType.WYE_TURNOUT);
} else if (leToolBarPanel.doubleXoverButton.isSelected()) {
addLayoutTurnout(LayoutTurnout.TurnoutType.DOUBLE_XOVER);
} else if (leToolBarPanel.rhXoverButton.isSelected()) {
addLayoutTurnout(LayoutTurnout.TurnoutType.RH_XOVER);
} else if (leToolBarPanel.lhXoverButton.isSelected()) {
addLayoutTurnout(LayoutTurnout.TurnoutType.LH_XOVER);
} else if (leToolBarPanel.levelXingButton.isSelected()) {
addLevelXing();
} else if (leToolBarPanel.layoutSingleSlipButton.isSelected()) {
addLayoutSlip(LayoutSlip.TurnoutType.SINGLE_SLIP);
} else if (leToolBarPanel.layoutDoubleSlipButton.isSelected()) {
addLayoutSlip(LayoutSlip.TurnoutType.DOUBLE_SLIP);
} else if (leToolBarPanel.endBumperButton.isSelected()) {
addEndBumper();
} else if (leToolBarPanel.anchorButton.isSelected()) {
addAnchor();
} else if (leToolBarPanel.edgeButton.isSelected()) {
addEdgeConnector();
} else if (leToolBarPanel.trackButton.isSelected()) {
if ((beginTrack != null) && (foundTrack != null)
&& (beginTrack != foundTrack)) {
addTrackSegment();
_targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
beginTrack = null;
foundTrack = null;
foundTrackView = null;
} else if (leToolBarPanel.multiSensorButton.isSelected()) {
startMultiSensor();
} else if (leToolBarPanel.sensorButton.isSelected()) {
addSensor();
} else if (leToolBarPanel.signalButton.isSelected()) {
addSignalHead();
} else if (leToolBarPanel.textLabelButton.isSelected()) {
addLabel();
} else if (leToolBarPanel.memoryButton.isSelected()) {
addMemory();
} else if (leToolBarPanel.globalVariableButton.isSelected()) {
addGlobalVariable();
} else if (leToolBarPanel.blockContentsButton.isSelected()) {
addBlockContents();
} else if (leToolBarPanel.iconLabelButton.isSelected()) {
addIcon();
} else if (leToolBarPanel.logixngButton.isSelected()) {
addLogixNGIcon();
} else if (leToolBarPanel.audioButton.isSelected()) {
addAudioIcon();
} else if (leToolBarPanel.shapeButton.isSelected()) {
LayoutShape ls = (LayoutShape) selectedObject;
if (ls == null) {
ls = addLayoutShape(currentPoint);
} else {
ls.addPoint(currentPoint, selectedHitPointType.shapePointIndex());
}
unionToPanelBounds(ls.getBounds());
_targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
} else if (leToolBarPanel.signalMastButton.isSelected()) {
addSignalMast();
} else {
log.warn("No item selected in panel edit mode");
}
// resizePanelBounds(false);
selectedObject = null;
redrawPanel();
} else if ((event.isPopupTrigger() || delayedPopupTrigger) && !isDragging) {
selectedObject = null;
selectedHitPointType = HitPointType.NONE;
whenReleased = event.getWhen();
showEditPopUps(event);
} else if ((selectedObject != null) && (selectedHitPointType == HitPointType.TURNOUT_CENTER)
&& allControlling() && (!event.isMetaDown() && !event.isAltDown()) && !event.isPopupTrigger()
&& !event.isShiftDown() && !event.isControlDown()) {
// controlling turnouts, in edit mode
LayoutTurnout t = (LayoutTurnout) selectedObject;
t.toggleTurnout();
} else if ((selectedObject != null) && ((selectedHitPointType == HitPointType.SLIP_LEFT)
|| (selectedHitPointType == HitPointType.SLIP_RIGHT))
&& allControlling() && (!event.isMetaDown() && !event.isAltDown()) && !event.isPopupTrigger()
&& !event.isShiftDown() && !event.isControlDown()) {
// controlling slips, in edit mode
LayoutSlip sl = (LayoutSlip) selectedObject;
sl.toggleState(selectedHitPointType);
} else if ((selectedObject != null) && (HitPointType.isTurntableRayHitType(selectedHitPointType))
&& allControlling() && (!event.isMetaDown() && !event.isAltDown()) && !event.isPopupTrigger()
&& !event.isShiftDown() && !event.isControlDown()) {
// controlling turntable, in edit mode
LayoutTurntable t = (LayoutTurntable) selectedObject;
t.setPosition(selectedHitPointType.turntableTrackIndex());
} else if ((selectedObject != null) && ((selectedHitPointType == HitPointType.TURNOUT_CENTER)
|| (selectedHitPointType == HitPointType.SLIP_CENTER)
|| (selectedHitPointType == HitPointType.SLIP_LEFT)
|| (selectedHitPointType == HitPointType.SLIP_RIGHT))
&& allControlling() && (event.isMetaDown() && !event.isAltDown())
&& !event.isShiftDown() && !event.isControlDown() && isDragging) {
// We just dropped a turnout (or slip)... see if it will connect to anything
hitPointCheckLayoutTurnouts((LayoutTurnout) selectedObject);
} else if ((selectedObject != null) && (selectedHitPointType == HitPointType.POS_POINT)
&& allControlling() && (event.isMetaDown())
&& !event.isShiftDown() && !event.isControlDown() && isDragging) {
// We just dropped a PositionablePoint... see if it will connect to anything
PositionablePoint p = (PositionablePoint) selectedObject;
if ((p.getConnect1() == null) || (p.getConnect2() == null)) {
checkPointOfPositionable(p);
}
}
if ((leToolBarPanel.trackButton.isSelected()) && (beginTrack != null) && (foundTrack != null)) {
// user let up shift key before releasing the mouse when creating a track segment
_targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
beginTrack = null;
foundTrack = null;
foundTrackView = null;
redrawPanel();
}
createSelectionGroups();
} else if ((selectedObject != null) && (selectedHitPointType == HitPointType.TURNOUT_CENTER)
&& allControlling() && !event.isMetaDown() && !event.isAltDown() && !event.isPopupTrigger()
&& !event.isShiftDown() && (!delayedPopupTrigger)) {
// controlling turnout out of edit mode
LayoutTurnout t = (LayoutTurnout) selectedObject;
if (useDirectTurnoutControl) {
t.setState(Turnout.CLOSED);
} else {
t.toggleTurnout();
}
} else if ((selectedObject != null) && ((selectedHitPointType == HitPointType.SLIP_LEFT)
|| (selectedHitPointType == HitPointType.SLIP_RIGHT))
&& allControlling() && !event.isMetaDown() && !event.isAltDown() && !event.isPopupTrigger()
&& !event.isShiftDown() && (!delayedPopupTrigger)) {
// controlling slip out of edit mode
LayoutSlip sl = (LayoutSlip) selectedObject;
sl.toggleState(selectedHitPointType);
} else if ((selectedObject != null) && (HitPointType.isTurntableRayHitType(selectedHitPointType))
&& allControlling() && !event.isMetaDown() && !event.isAltDown() && !event.isPopupTrigger()
&& !event.isShiftDown() && (!delayedPopupTrigger)) {
// controlling turntable out of edit mode
LayoutTurntable t = (LayoutTurntable) selectedObject;
t.setPosition(selectedHitPointType.turntableTrackIndex());
} else if ((event.isPopupTrigger() || delayedPopupTrigger) && (!isDragging)) {
// requesting marker popup out of edit mode
LocoIcon lo = checkMarkerPopUps(dLoc);
if (lo != null) {
showPopUp(lo, event);
} else {
if (findLayoutTracksHitPoint(dLoc)) {
// show popup menu
switch (foundHitPointType) {
case TURNOUT_CENTER: {
if (useDirectTurnoutControl) {
LayoutTurnout t = (LayoutTurnout) foundTrack;
t.setState(Turnout.THROWN);
} else {
foundTrackView.showPopup(event);
}
break;
}
case LEVEL_XING_CENTER:
case SLIP_RIGHT:
case SLIP_LEFT: {
foundTrackView.showPopup(event);
break;
}
default: {
break;
}
}
}
AnalogClock2Display c = checkClockPopUps(dLoc);
if (c != null) {
showPopUp(c, event);
} else {
SignalMastIcon sm = checkSignalMastIconPopUps(dLoc);
if (sm != null) {
showPopUp(sm, event);
} else {
PositionableLabel im = checkLabelImagePopUps(dLoc);
if (im != null) {
showPopUp(im, event);
}
}
}
}
}
if (!event.isPopupTrigger() && !isDragging) {
List<Positionable> selections = getSelectedItems(event);
if (!selections.isEmpty()) {
selections.get(0).doMouseReleased(event);
whenReleased = event.getWhen();
}
}
// train icon needs to know when moved
if (event.isPopupTrigger() && isDragging) {
List<Positionable> selections = getSelectedItems(event);
if (!selections.isEmpty()) {
selections.get(0).doMouseDragged(event);
}
}
if (selectedObject != null) {
// An object was selected, deselect it
prevSelectedObject = selectedObject;
selectedObject = null;
}
// clear these
beginTrack = null;
foundTrack = null;
foundTrackView = null;
delayedPopupTrigger = false;
if (isDragging) {
resizePanelBounds(true);
isDragging = false;
}
requestFocusInWindow();
} // mouseReleased
public void addPopupItems(@Nonnull JPopupMenu popup, @Nonnull JmriMouseEvent event) {
List<LayoutTrack> tracks = getLayoutTracks().stream().filter(layoutTrack -> { // != means can't (yet) loop over Views
HitPointType hitPointType = getLayoutTrackView(layoutTrack).findHitPointType(dLoc, false, false);
return (HitPointType.NONE != hitPointType);
}).collect(Collectors.toList());
List<Positionable> selections = getSelectedItems(event);
if ((tracks.size() > 1) || (selections.size() > 1)) {
JMenu iconsBelowMenu = new JMenu(Bundle.getMessage("MenuItemIconsBelow"));
JMenuItem mi = new JMenuItem(Bundle.getMessage("MenuItemIconsBelow_InfoNotInOrder"));
mi.setEnabled(false);
iconsBelowMenu.add(mi);
if (tracks.size() > 1) {
for (int i=0; i < tracks.size(); i++) {
LayoutTrack t = tracks.get(i);
iconsBelowMenu.add(new AbstractAction(Bundle.getMessage(
"LayoutTrackTypeAndName", t.getTypeName(), t.getName())) {
@Override
public void actionPerformed(ActionEvent e) {
LayoutTrackView ltv = getLayoutTrackView(t);
ltv.showPopup(event);
}
});
}
}
if (selections.size() > 1) {
for (int i=0; i < selections.size(); i++) {
Positionable pos = selections.get(i);
iconsBelowMenu.add(new AbstractAction(Bundle.getMessage(
"PositionableTypeAndName", pos.getTypeString(), pos.getNameString())) {
@Override
public void actionPerformed(ActionEvent e) {
showPopUp(pos, event, new ArrayList<>());
}
});
}
}
popup.addSeparator();
popup.add(iconsBelowMenu);
}
}
private void showEditPopUps(@Nonnull JmriMouseEvent event) {
if (findLayoutTracksHitPoint(dLoc)) {
if (HitPointType.isBezierHitType(foundHitPointType)) {
getTrackSegmentView((TrackSegment) foundTrack).showBezierPopUp(event, foundHitPointType);
} else if (HitPointType.isTurntableRayHitType(foundHitPointType)) {
LayoutTurntable t = (LayoutTurntable) foundTrack;
if (t.isTurnoutControlled()) {
LayoutTurntableView ltview = getLayoutTurntableView((LayoutTurntable) foundTrack);
ltview.showRayPopUp(event, foundHitPointType.turntableTrackIndex());
}
} else if (HitPointType.isPopupHitType(foundHitPointType)) {
foundTrackView.showPopup(event);
} else if (HitPointType.isTurnoutHitType(foundHitPointType)) {
// don't curently have edit popup for these
} else {
log.warn("Unknown foundPointType:{}", foundHitPointType);
}
} else {
do {
TrackSegment ts = checkTrackSegmentPopUps(dLoc);
if (ts != null) {
TrackSegmentView tsv = getTrackSegmentView(ts);
tsv.showPopup(event);
break;
}
SensorIcon s = checkSensorIconPopUps(dLoc);
if (s != null) {
showPopUp(s, event);
break;
}
LocoIcon lo = checkMarkerPopUps(dLoc);
if (lo != null) {
showPopUp(lo, event);
break;
}
SignalHeadIcon sh = checkSignalHeadIconPopUps(dLoc);
if (sh != null) {
showPopUp(sh, event);
break;
}
AnalogClock2Display c = checkClockPopUps(dLoc);
if (c != null) {
showPopUp(c, event);
break;
}
MultiSensorIcon ms = checkMultiSensorPopUps(dLoc);
if (ms != null) {
showPopUp(ms, event);
break;
}
PositionableLabel lb = checkLabelImagePopUps(dLoc);
if (lb != null) {
showPopUp(lb, event);
break;
}
PositionableLabel b = checkBackgroundPopUps(dLoc);
if (b != null) {
showPopUp(b, event);
break;
}
SignalMastIcon sm = checkSignalMastIconPopUps(dLoc);
if (sm != null) {
showPopUp(sm, event);
break;
}
LayoutShape ls = checkLayoutShapePopUps(dLoc);
if (ls != null) {
ls.showShapePopUp(event, selectedHitPointType);
break;
}
} while (false);
}
}
/**
* Select the menu items to display for the Positionable's popup.
* @param p the item containing or requiring the context menu
* @param event the event triggering the menu
*/
public void showPopUp(@Nonnull Positionable p, @Nonnull JmriMouseEvent event) {
assert p != null;
if (!((Component) p).isVisible()) {
return; // component must be showing on the screen to determine its location
}
JPopupMenu popup = new JPopupMenu();
if (p.isEditable()) {
JMenuItem jmi;
if (showAlignPopup()) {
setShowAlignmentMenu(popup);
popup.add(new AbstractAction(Bundle.getMessage("ButtonDelete")) {
@Override
public void actionPerformed(ActionEvent event) {
deleteSelectedItems();
}
});
} else {
if (p.doViemMenu()) {
String objectType = p.getClass().getName();
objectType = objectType.substring(objectType.lastIndexOf('.') + 1);
jmi = popup.add(objectType);
jmi.setEnabled(false);
jmi = popup.add(p.getNameString());
jmi.setEnabled(false);
if (p.isPositionable()) {
setShowCoordinatesMenu(p, popup);
}
setDisplayLevelMenu(p, popup);
setPositionableMenu(p, popup);
}
boolean popupSet = false;
popupSet |= p.setRotateOrthogonalMenu(popup);
popupSet |= p.setRotateMenu(popup);
popupSet |= p.setScaleMenu(popup);
if (popupSet) {
popup.addSeparator();
popupSet = false;
}
popupSet |= p.setEditIconMenu(popup);
popupSet |= p.setTextEditMenu(popup);
PositionablePopupUtil util = p.getPopupUtility();
if (util != null) {
util.setFixedTextMenu(popup);
util.setTextMarginMenu(popup);
util.setTextBorderMenu(popup);
util.setTextFontMenu(popup);
util.setBackgroundMenu(popup);
util.setTextJustificationMenu(popup);
util.setTextOrientationMenu(popup);
popup.addSeparator();
util.propertyUtil(popup);
util.setAdditionalEditPopUpMenu(popup);
popupSet = true;
}
if (popupSet) {
popup.addSeparator();
// popupSet = false;
}
p.setDisableControlMenu(popup);
setShowAlignmentMenu(popup);
// for Positionables with unique settings
p.showPopUp(popup);
setShowToolTipMenu(p, popup);
setRemoveMenu(p, popup);
if (p.doViemMenu()) {
setHiddenMenu(p, popup);
setEmptyHiddenMenu(p, popup);
setEditIdMenu(p, popup);
setEditClassesMenu(p, popup);
popup.addSeparator();
setLogixNGPositionableMenu(p, popup);
}
}
} else {
p.showPopUp(popup);
PositionablePopupUtil util = p.getPopupUtility();
if (util != null) {
util.setAdditionalViewPopUpMenu(popup);
}
}
addPopupItems(popup, event);
popup.show((Component) p, p.getWidth() / 2 + (int) ((getZoom() - 1.0) * p.getX()),
p.getHeight() / 2 + (int) ((getZoom() - 1.0) * p.getY()));
/*popup.show((Component)pt, event.getX(), event.getY());*/
}
private long whenReleased = 0; // used to identify event that was popup trigger
private boolean awaitingIconChange = false;
@Override
public void mouseClicked(@Nonnull JmriMouseEvent event) {
// initialize mouse position
calcLocation(event);
// if alt modifier is down invert the snap to grid behaviour
snapToGridInvert = event.isAltDown();
if (!event.isMetaDown() && !event.isPopupTrigger() && !event.isAltDown()
&& !awaitingIconChange && !event.isShiftDown() && !event.isControlDown()) {
List<Positionable> selections = getSelectedItems(event);
if (!selections.isEmpty()) {
selections.get(0).doMouseClicked(event);
}
} else if (event.isPopupTrigger() && (whenReleased != event.getWhen())) {
if (isEditable()) {
selectedObject = null;
selectedHitPointType = HitPointType.NONE;
showEditPopUps(event);
} else {
LocoIcon lo = checkMarkerPopUps(dLoc);
if (lo != null) {
showPopUp(lo, event);
}
}
}
if (event.isControlDown() && !event.isPopupTrigger()) {
if (findLayoutTracksHitPoint(dLoc)) {
switch (foundHitPointType) {
case POS_POINT:
case TURNOUT_CENTER:
case LEVEL_XING_CENTER:
case SLIP_LEFT:
case SLIP_RIGHT:
case TURNTABLE_CENTER: {
amendSelectionGroup(foundTrack);
break;
}
default: {
break;
}
}
} else {
PositionableLabel s = checkSensorIconPopUps(dLoc);
if (s != null) {
amendSelectionGroup(s);
} else {
PositionableLabel sh = checkSignalHeadIconPopUps(dLoc);
if (sh != null) {
amendSelectionGroup(sh);
} else {
PositionableLabel ms = checkMultiSensorPopUps(dLoc);
if (ms != null) {
amendSelectionGroup(ms);
} else {
PositionableLabel lb = checkLabelImagePopUps(dLoc);
if (lb != null) {
amendSelectionGroup(lb);
} else {
PositionableLabel b = checkBackgroundPopUps(dLoc);
if (b != null) {
amendSelectionGroup(b);
} else {
PositionableLabel sm = checkSignalMastIconPopUps(dLoc);
if (sm != null) {
amendSelectionGroup(sm);
} else {
LayoutShape ls = checkLayoutShapePopUps(dLoc);
if (ls != null) {
amendSelectionGroup(ls);
}
}
}
}
}
}
}
}
} else if ((selectionWidth == 0) || (selectionHeight == 0)) {
clearSelectionGroups();
}
requestFocusInWindow();
}
private void checkPointOfPositionable(@Nonnull PositionablePoint p) {
assert p != null;
TrackSegment t = p.getConnect1();
if (t == null) {
t = p.getConnect2();
}
// Nothing connected to this bit of track so ignore
if (t == null) {
return;
}
beginTrack = p;
beginHitPointType = HitPointType.POS_POINT;
PositionablePointView pv = getPositionablePointView(p);
Point2D loc = pv.getCoordsCenter();
if (findLayoutTracksHitPoint(loc, true, p)) {
switch (foundHitPointType) {
case POS_POINT: {
PositionablePoint p2 = (PositionablePoint) foundTrack;
if ((p2.getType() == PositionablePoint.PointType.ANCHOR) && p2.setTrackConnection(t)) {
if (t.getConnect1() == p) {
t.setNewConnect1(p2, foundHitPointType);
} else {
t.setNewConnect2(p2, foundHitPointType);
}
p.removeTrackConnection(t);
if ((p.getConnect1() == null) && (p.getConnect2() == null)) {
removePositionablePoint(p);
}
}
break;
}
case TURNOUT_A:
case TURNOUT_B:
case TURNOUT_C:
case TURNOUT_D:
case SLIP_A:
case SLIP_B:
case SLIP_C:
case SLIP_D:
case LEVEL_XING_A:
case LEVEL_XING_B:
case LEVEL_XING_C:
case LEVEL_XING_D: {
try {
if (foundTrack.getConnection(foundHitPointType) == null) {
foundTrack.setConnection(foundHitPointType, t, HitPointType.TRACK);
if (t.getConnect1() == p) {
t.setNewConnect1(foundTrack, foundHitPointType);
} else {
t.setNewConnect2(foundTrack, foundHitPointType);
}
p.removeTrackConnection(t);
if ((p.getConnect1() == null) && (p.getConnect2() == null)) {
removePositionablePoint(p);
}
}
} catch (JmriException e) {
log.debug("Unable to set location");
}
break;
}
default: {
if (HitPointType.isTurntableRayHitType(foundHitPointType)) {
LayoutTurntable tt = (LayoutTurntable) foundTrack;
int ray = foundHitPointType.turntableTrackIndex();
if (tt.getRayConnectIndexed(ray) == null) {
tt.setRayConnect(t, ray);
if (t.getConnect1() == p) {
t.setNewConnect1(tt, foundHitPointType);
} else {
t.setNewConnect2(tt, foundHitPointType);
}
p.removeTrackConnection(t);
if ((p.getConnect1() == null) && (p.getConnect2() == null)) {
removePositionablePoint(p);
}
}
} else {
log.debug("No valid point, so will quit");
return;
}
break;
}
}
redrawPanel();
if (t.getLayoutBlock() != null) {
getLEAuxTools().setBlockConnectivityChanged();
}
}
beginTrack = null;
}
// We just dropped a turnout... see if it will connect to anything
private void hitPointCheckLayoutTurnouts(@Nonnull LayoutTurnout lt) {
beginTrack = lt;
LayoutTurnoutView ltv = getLayoutTurnoutView(lt);
if (lt.getConnectA() == null) {
if (lt instanceof LayoutSlip) {
beginHitPointType = HitPointType.SLIP_A;
} else {
beginHitPointType = HitPointType.TURNOUT_A;
}
dLoc = ltv.getCoordsA();
hitPointCheckLayoutTurnoutSubs(dLoc);
}
if (lt.getConnectB() == null) {
if (lt instanceof LayoutSlip) {
beginHitPointType = HitPointType.SLIP_B;
} else {
beginHitPointType = HitPointType.TURNOUT_B;
}
dLoc = ltv.getCoordsB();
hitPointCheckLayoutTurnoutSubs(dLoc);
}
if (lt.getConnectC() == null) {
if (lt instanceof LayoutSlip) {
beginHitPointType = HitPointType.SLIP_C;
} else {
beginHitPointType = HitPointType.TURNOUT_C;
}
dLoc = ltv.getCoordsC();
hitPointCheckLayoutTurnoutSubs(dLoc);
}
if ((lt.getConnectD() == null) && (lt.isTurnoutTypeXover() || lt.isTurnoutTypeSlip())) {
if (lt instanceof LayoutSlip) {
beginHitPointType = HitPointType.SLIP_D;
} else {
beginHitPointType = HitPointType.TURNOUT_D;
}
dLoc = ltv.getCoordsD();
hitPointCheckLayoutTurnoutSubs(dLoc);
}
beginTrack = null;
foundTrack = null;
foundTrackView = null;
}
private void hitPointCheckLayoutTurnoutSubs(@Nonnull Point2D dLoc) {
assert dLoc != null;
if (findLayoutTracksHitPoint(dLoc, true)) {
switch (foundHitPointType) {
case POS_POINT: {
PositionablePoint p2 = (PositionablePoint) foundTrack;
if (((p2.getConnect1() == null) && (p2.getConnect2() != null))
|| ((p2.getConnect1() != null) && (p2.getConnect2() == null))) {
TrackSegment t = p2.getConnect1();
if (t == null) {
t = p2.getConnect2();
}
if (t == null) {
return;
}
LayoutTurnout lt = (LayoutTurnout) beginTrack;
try {
if (lt.getConnection(beginHitPointType) == null) {
lt.setConnection(beginHitPointType, t, HitPointType.TRACK);
p2.removeTrackConnection(t);
if (t.getConnect1() == p2) {
t.setNewConnect1(lt, beginHitPointType);
} else {
t.setNewConnect2(lt, beginHitPointType);
}
removePositionablePoint(p2);
}
if (t.getLayoutBlock() != null) {
getLEAuxTools().setBlockConnectivityChanged();
}
} catch (JmriException e) {
log.debug("Unable to set location");
}
}
break;
}
case TURNOUT_A:
case TURNOUT_B:
case TURNOUT_C:
case TURNOUT_D:
case SLIP_A:
case SLIP_B:
case SLIP_C:
case SLIP_D: {
LayoutTurnout ft = (LayoutTurnout) foundTrack;
addTrackSegment();
if ((ft.getTurnoutType() == LayoutTurnout.TurnoutType.RH_TURNOUT) || (ft.getTurnoutType() == LayoutTurnout.TurnoutType.LH_TURNOUT)) {
rotateTurnout(ft);
}
// Assign a block to the new zero length track segment.
((LayoutTurnoutView) foundTrackView).setTrackSegmentBlock(foundHitPointType, true);
break;
}
default: {
log.warn("Unexpected foundPointType {} in hitPointCheckLayoutTurnoutSubs", foundHitPointType);
break;
}
}
}
}
private void rotateTurnout(@Nonnull LayoutTurnout t) {
assert t != null;
LayoutTurnoutView tv = getLayoutTurnoutView(t);
LayoutTurnout be = (LayoutTurnout) beginTrack;
LayoutTurnoutView bev = getLayoutTurnoutView(be);
if (((beginHitPointType == HitPointType.TURNOUT_A) && ((be.getConnectB() != null) || (be.getConnectC() != null)))
|| ((beginHitPointType == HitPointType.TURNOUT_B) && ((be.getConnectA() != null) || (be.getConnectC() != null)))
|| ((beginHitPointType == HitPointType.TURNOUT_C) && ((be.getConnectB() != null) || (be.getConnectA() != null)))) {
return;
}
if ((be.getTurnoutType() != LayoutTurnout.TurnoutType.RH_TURNOUT) && (be.getTurnoutType() != LayoutTurnout.TurnoutType.LH_TURNOUT)) {
return;
}
Point2D c, diverg, xy2;
if ((foundHitPointType == HitPointType.TURNOUT_C) && (beginHitPointType == HitPointType.TURNOUT_C)) {
c = tv.getCoordsA();
diverg = tv.getCoordsB();
xy2 = MathUtil.subtract(c, diverg);
} else if ((foundHitPointType == HitPointType.TURNOUT_C)
&& ((beginHitPointType == HitPointType.TURNOUT_A) || (beginHitPointType == HitPointType.TURNOUT_B))) {
c = tv.getCoordsCenter();
diverg = tv.getCoordsC();
if (beginHitPointType == HitPointType.TURNOUT_A) {
xy2 = MathUtil.subtract(bev.getCoordsB(), bev.getCoordsA());
} else {
xy2 = MathUtil.subtract(bev.getCoordsA(), bev.getCoordsB());
}
} else if (foundHitPointType == HitPointType.TURNOUT_B) {
c = tv.getCoordsA();
diverg = tv.getCoordsB();
switch (beginHitPointType) {
case TURNOUT_B:
xy2 = MathUtil.subtract(bev.getCoordsA(), bev.getCoordsB());
break;
case TURNOUT_A:
xy2 = MathUtil.subtract(bev.getCoordsB(), bev.getCoordsA());
break;
case TURNOUT_C:
default:
xy2 = MathUtil.subtract(bev.getCoordsCenter(), bev.getCoordsC());
break;
}
} else if (foundHitPointType == HitPointType.TURNOUT_A) {
c = tv.getCoordsA();
diverg = tv.getCoordsB();
switch (beginHitPointType) {
case TURNOUT_A:
xy2 = MathUtil.subtract(bev.getCoordsA(), bev.getCoordsB());
break;
case TURNOUT_B:
xy2 = MathUtil.subtract(bev.getCoordsB(), bev.getCoordsA());
break;
case TURNOUT_C:
default:
xy2 = MathUtil.subtract(bev.getCoordsC(), bev.getCoordsCenter());
break;
}
} else {
return;
}
Point2D xy = MathUtil.subtract(diverg, c);
double radius = Math.toDegrees(Math.atan2(xy.getY(), xy.getX()));
double eRadius = Math.toDegrees(Math.atan2(xy2.getY(), xy2.getX()));
bev.rotateCoords(radius - eRadius);
Point2D conCord = bev.getCoordsA();
Point2D tCord = tv.getCoordsC();
if (foundHitPointType == HitPointType.TURNOUT_B) {
tCord = tv.getCoordsB();
}
if (foundHitPointType == HitPointType.TURNOUT_A) {
tCord = tv.getCoordsA();
}
switch (beginHitPointType) {
case TURNOUT_A:
conCord = bev.getCoordsA();
break;
case TURNOUT_B:
conCord = bev.getCoordsB();
break;
case TURNOUT_C:
conCord = bev.getCoordsC();
break;
default:
break;
}
xy = MathUtil.subtract(conCord, tCord);
Point2D offset = MathUtil.subtract(bev.getCoordsCenter(), xy);
bev.setCoordsCenter(offset);
}
public List<Positionable> _positionableSelection = new ArrayList<>();
public List<LayoutTrack> _layoutTrackSelection = new ArrayList<>();
public List<LayoutShape> _layoutShapeSelection = new ArrayList<>();
@Nonnull
public List<Positionable> getPositionalSelection() {
return _positionableSelection;
}
@Nonnull
public List<LayoutTrack> getLayoutTrackSelection() {
return _layoutTrackSelection;
}
@Nonnull
public List<LayoutShape> getLayoutShapeSelection() {
return _layoutShapeSelection;
}
private void createSelectionGroups() {
Rectangle2D selectionRect = getSelectionRect();
getContents().forEach((o) -> {
if (selectionRect.contains(o.getLocation())) {
log.trace("found item o of class {}", o.getClass());
if (!_positionableSelection.contains(o)) {
_positionableSelection.add(o);
}
}
});
getLayoutTracks().forEach((lt) -> {
LayoutTrackView ltv = getLayoutTrackView(lt);
Point2D center = ltv.getCoordsCenter();
if (selectionRect.contains(center)) {
if (!_layoutTrackSelection.contains(lt)) {
_layoutTrackSelection.add(lt);
}
}
});
assignBlockToSelectionMenuItem.setEnabled(!_layoutTrackSelection.isEmpty());
layoutShapes.forEach((ls) -> {
if (selectionRect.intersects(ls.getBounds())) {
if (!_layoutShapeSelection.contains(ls)) {
_layoutShapeSelection.add(ls);
}
}
});
redrawPanel();
}
public void clearSelectionGroups() {
selectionActive = false;
_positionableSelection.clear();
_layoutTrackSelection.clear();
assignBlockToSelectionMenuItem.setEnabled(false);
_layoutShapeSelection.clear();
}
private boolean noWarnGlobalDelete = false;
private void deleteSelectedItems() {
if (!noWarnGlobalDelete) {
int selectedValue = JmriJOptionPane.showOptionDialog(this,
Bundle.getMessage("Question6"), Bundle.getMessage("WarningTitle"),
JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
new Object[]{Bundle.getMessage("ButtonYes"),
Bundle.getMessage("ButtonNo"),
Bundle.getMessage("ButtonYesPlus")},
Bundle.getMessage("ButtonNo"));
// array position 1, ButtonNo or Dialog closed.
if (selectedValue == 1 || selectedValue == JmriJOptionPane.CLOSED_OPTION ) {
return; // return without creating if "No" response
}
if (selectedValue == 2) { // array positio 2, ButtonYesPlus
// Suppress future warnings, and continue
noWarnGlobalDelete = true;
}
}
_positionableSelection.forEach(this::remove);
_layoutTrackSelection.forEach((lt) -> {
if (lt instanceof PositionablePoint) {
boolean oldWarning = noWarnPositionablePoint;
noWarnPositionablePoint = true;
removePositionablePoint((PositionablePoint) lt);
noWarnPositionablePoint = oldWarning;
} else if (lt instanceof LevelXing) {
boolean oldWarning = noWarnLevelXing;
noWarnLevelXing = true;
removeLevelXing((LevelXing) lt);
noWarnLevelXing = oldWarning;
} else if (lt instanceof LayoutSlip) {
boolean oldWarning = noWarnSlip;
noWarnSlip = true;
removeLayoutSlip((LayoutSlip) lt);
noWarnSlip = oldWarning;
} else if (lt instanceof LayoutTurntable) {
boolean oldWarning = noWarnTurntable;
noWarnTurntable = true;
removeTurntable((LayoutTurntable) lt);
noWarnTurntable = oldWarning;
} else if (lt instanceof LayoutTurnout) { //<== this includes LayoutSlips
boolean oldWarning = noWarnLayoutTurnout;
noWarnLayoutTurnout = true;
removeLayoutTurnout((LayoutTurnout) lt);
noWarnLayoutTurnout = oldWarning;
}
});
layoutShapes.removeAll(_layoutShapeSelection);
clearSelectionGroups();
redrawPanel();
}
private void amendSelectionGroup(@Nonnull Positionable p) {
assert p != null;
if (_positionableSelection.contains(p)) {
_positionableSelection.remove(p);
} else {
_positionableSelection.add(p);
}
redrawPanel();
}
public void amendSelectionGroup(@Nonnull LayoutTrack p) {
assert p != null;
if (_layoutTrackSelection.contains(p)) {
_layoutTrackSelection.remove(p);
} else {
_layoutTrackSelection.add(p);
}
assignBlockToSelectionMenuItem.setEnabled(!_layoutTrackSelection.isEmpty());
redrawPanel();
}
public void amendSelectionGroup(@Nonnull LayoutShape ls) {
assert ls != null;
if (_layoutShapeSelection.contains(ls)) {
_layoutShapeSelection.remove(ls);
} else {
_layoutShapeSelection.add(ls);
}
redrawPanel();
}
public void alignSelection(boolean alignX) {
Point2D minPoint = MathUtil.infinityPoint2D;
Point2D maxPoint = MathUtil.zeroPoint2D;
Point2D sumPoint = MathUtil.zeroPoint2D;
int cnt = 0;
for (Positionable comp : _positionableSelection) {
if (!getFlag(Editor.OPTION_POSITION, comp.isPositionable())) {
continue; // skip non-positionables
}
Point2D p = MathUtil.pointToPoint2D(comp.getLocation());
minPoint = MathUtil.min(minPoint, p);
maxPoint = MathUtil.max(maxPoint, p);
sumPoint = MathUtil.add(sumPoint, p);
cnt++;
}
for (LayoutTrack lt : _layoutTrackSelection) {
LayoutTrackView ltv = getLayoutTrackView(lt);
Point2D p = ltv.getCoordsCenter();
minPoint = MathUtil.min(minPoint, p);
maxPoint = MathUtil.max(maxPoint, p);
sumPoint = MathUtil.add(sumPoint, p);
cnt++;
}
for (LayoutShape ls : _layoutShapeSelection) {
Point2D p = ls.getCoordsCenter();
minPoint = MathUtil.min(minPoint, p);
maxPoint = MathUtil.max(maxPoint, p);
sumPoint = MathUtil.add(sumPoint, p);
cnt++;
}
Point2D avePoint = MathUtil.divide(sumPoint, cnt);
int aveX = (int) avePoint.getX();
int aveY = (int) avePoint.getY();
for (Positionable comp : _positionableSelection) {
if (!getFlag(Editor.OPTION_POSITION, comp.isPositionable())) {
continue; // skip non-positionables
}
if (alignX) {
comp.setLocation(aveX, comp.getY());
} else {
comp.setLocation(comp.getX(), aveY);
}
}
_layoutTrackSelection.forEach((lt) -> {
LayoutTrackView ltv = getLayoutTrackView(lt);
if (alignX) {
ltv.setCoordsCenter(new Point2D.Double(aveX, ltv.getCoordsCenter().getY()));
} else {
ltv.setCoordsCenter(new Point2D.Double(ltv.getCoordsCenter().getX(), aveY));
}
});
_layoutShapeSelection.forEach((ls) -> {
if (alignX) {
ls.setCoordsCenter(new Point2D.Double(aveX, ls.getCoordsCenter().getY()));
} else {
ls.setCoordsCenter(new Point2D.Double(ls.getCoordsCenter().getX(), aveY));
}
});
redrawPanel();
}
private boolean showAlignPopup() {
return ((!_positionableSelection.isEmpty())
|| (!_layoutTrackSelection.isEmpty())
|| (!_layoutShapeSelection.isEmpty()));
}
/**
* Offer actions to align the selected Positionable items either
* Horizontally (at average y coord) or Vertically (at average x coord).
*
* @param popup the JPopupMenu to add alignment menu to
* @return true if alignment menu added
*/
public boolean setShowAlignmentMenu(@Nonnull JPopupMenu popup) {
if (showAlignPopup()) {
JMenu edit = new JMenu(Bundle.getMessage("EditAlignment"));
edit.add(new AbstractAction(Bundle.getMessage("AlignX")) {
@Override
public void actionPerformed(ActionEvent event) {
alignSelection(true);
}
});
edit.add(new AbstractAction(Bundle.getMessage("AlignY")) {
@Override
public void actionPerformed(ActionEvent event) {
alignSelection(false);
}
});
popup.add(edit);
return true;
}
return false;
}
@Override
public void keyPressed(@Nonnull KeyEvent event) {
if (event.getKeyCode() == KeyEvent.VK_DELETE) {
deleteSelectedItems();
return;
}
double deltaX = returnDeltaPositionX(event);
double deltaY = returnDeltaPositionY(event);
if ((deltaX != 0) || (deltaY != 0)) {
selectionX += deltaX;
selectionY += deltaY;
Point2D delta = new Point2D.Double(deltaX, deltaY);
_positionableSelection.forEach((c) -> {
Point2D newPoint = c.getLocation();
if ((c instanceof MemoryIcon) && (c.getPopupUtility().getFixedWidth() == 0)) {
MemoryIcon pm = (MemoryIcon) c;
newPoint = new Point2D.Double(pm.getOriginalX(), pm.getOriginalY());
}
newPoint = MathUtil.add(newPoint, delta);
newPoint = MathUtil.max(MathUtil.zeroPoint2D, newPoint);
c.setLocation(MathUtil.point2DToPoint(newPoint));
});
_layoutTrackSelection.forEach((lt) -> {
LayoutTrackView ltv = getLayoutTrackView(lt);
Point2D newPoint = MathUtil.add(ltv.getCoordsCenter(), delta);
newPoint = MathUtil.max(MathUtil.zeroPoint2D, newPoint);
getLayoutTrackView(lt).setCoordsCenter(newPoint);
});
_layoutShapeSelection.forEach((ls) -> {
Point2D newPoint = MathUtil.add(ls.getCoordsCenter(), delta);
newPoint = MathUtil.max(MathUtil.zeroPoint2D, newPoint);
ls.setCoordsCenter(newPoint);
});
redrawPanel();
return;
}
getLayoutEditorToolBarPanel().keyPressed(event);
}
private double returnDeltaPositionX(@Nonnull KeyEvent event) {
double result = 0.0;
double amount = event.isShiftDown() ? 5.0 : 1.0;
switch (event.getKeyCode()) {
case KeyEvent.VK_LEFT: {
result = -amount;
break;
}
case KeyEvent.VK_RIGHT: {
result = +amount;
break;
}
default: {
break;
}
}
return result;
}
private double returnDeltaPositionY(@Nonnull KeyEvent event) {
double result = 0.0;
double amount = event.isShiftDown() ? 5.0 : 1.0;
switch (event.getKeyCode()) {
case KeyEvent.VK_UP: {
result = -amount;
break;
}
case KeyEvent.VK_DOWN: {
result = +amount;
break;
}
default: {
break;
}
}
return result;
}
int _prevNumSel = 0;
@Override
public void mouseMoved(@Nonnull JmriMouseEvent event) {
// initialize mouse position
calcLocation(event);
// if alt modifier is down invert the snap to grid behaviour
snapToGridInvert = event.isAltDown();
if (isEditable()) {
leToolBarPanel.setLocationText(dLoc);
}
List<Positionable> selections = getSelectedItems(event);
Positionable selection = null;
int numSel = selections.size();
if (numSel > 0) {
selection = selections.get(0);
}
if ((selection != null) && (selection.getDisplayLevel() > Editor.BKG) && selection.showToolTip()) {
showToolTip(selection, event);
} else if (_targetPanel.getCursor() != Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)) {
super.setToolTip(null);
}
if (numSel != _prevNumSel) {
redrawPanel();
_prevNumSel = numSel;
}
if (findLayoutTracksHitPoint(dLoc)) {
// log.debug("foundTrack: {}", foundTrack);
if (HitPointType.isControlHitType(foundHitPointType)) {
_targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
setTurnoutTooltip();
} else {
_targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
}
foundTrack = null;
foundHitPointType = HitPointType.NONE;
} else {
_targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
} // mouseMoved
private void setTurnoutTooltip() {
if (foundTrackView instanceof LayoutTurnoutView) {
var ltv = (LayoutTurnoutView) foundTrackView;
var lt = ltv.getLayoutTurnout();
if (lt.showToolTip()) {
var tt = lt.getToolTip();
if (tt != null) {
tt.setText(lt.getNameString());
var coords = ltv.getCoordsCenter();
int offsetY = (int) (getTurnoutCircleSize() * SIZE);
tt.setLocation((int) coords.getX(), (int) coords.getY() + offsetY);
setToolTip(tt);
}
}
}
}
public void setAllShowLayoutTurnoutToolTip(boolean state) {
log.debug("setAllShowLayoutTurnoutToolTip: {}", state);
for (LayoutTurnout lt : getLayoutTurnoutsAndSlips()) {
lt.setShowToolTip(state);
}
}
private boolean isDragging = false;
@Override
public void mouseDragged(@Nonnull JmriMouseEvent event) {
// initialize mouse position
calcLocation(event);
// ignore this event if still at the original point
if ((!isDragging) && (xLoc == getAnchorX()) && (yLoc == getAnchorY())) {
return;
}
// if alt modifier is down invert the snap to grid behaviour
snapToGridInvert = event.isAltDown();
// process this mouse dragged event
if (isEditable()) {
leToolBarPanel.setLocationText(dLoc);
}
currentPoint = MathUtil.add(dLoc, startDelta);
// don't allow negative placement, objects could become unreachable
currentPoint = MathUtil.max(currentPoint, MathUtil.zeroPoint2D);
if ((selectedObject != null) && (event.isMetaDown() || event.isAltDown())
&& (selectedHitPointType == HitPointType.MARKER)) {
// marker moves regardless of editMode or positionable
PositionableLabel pl = (PositionableLabel) selectedObject;
pl.setLocation((int) currentPoint.getX(), (int) currentPoint.getY());
isDragging = true;
redrawPanel();
return;
}
if (isEditable()) {
if ((selectedObject != null) && event.isMetaDown() && allPositionable()) {
if (snapToGridOnMove != snapToGridInvert) {
// this snaps currentPoint to the grid
currentPoint = MathUtil.granulize(currentPoint, gContext.getGridSize());
xLoc = (int) currentPoint.getX();
yLoc = (int) currentPoint.getY();
leToolBarPanel.setLocationText(currentPoint);
}
if ((!_positionableSelection.isEmpty())
|| (!_layoutTrackSelection.isEmpty())
|| (!_layoutShapeSelection.isEmpty())) {
Point2D lastPoint = new Point2D.Double(_lastX, _lastY);
Point2D offset = MathUtil.subtract(currentPoint, lastPoint);
Point2D newPoint;
for (Positionable c : _positionableSelection) {
if ((c instanceof MemoryIcon) && (c.getPopupUtility().getFixedWidth() == 0)) {
MemoryIcon pm = (MemoryIcon) c;
newPoint = new Point2D.Double(pm.getOriginalX(), pm.getOriginalY());
} else {
newPoint = c.getLocation();
}
newPoint = MathUtil.add(newPoint, offset);
// don't allow negative placement, objects could become unreachable
newPoint = MathUtil.max(newPoint, MathUtil.zeroPoint2D);
c.setLocation(MathUtil.point2DToPoint(newPoint));
}
for (LayoutTrack lt : _layoutTrackSelection) {
LayoutTrackView ltv = getLayoutTrackView(lt);
Point2D center = ltv.getCoordsCenter();
newPoint = MathUtil.add(center, offset);
// don't allow negative placement, objects could become unreachable
newPoint = MathUtil.max(newPoint, MathUtil.zeroPoint2D);
getLayoutTrackView(lt).setCoordsCenter(newPoint);
}
for (LayoutShape ls : _layoutShapeSelection) {
Point2D center = ls.getCoordsCenter();
newPoint = MathUtil.add(center, offset);
// don't allow negative placement, objects could become unreachable
newPoint = MathUtil.max(newPoint, MathUtil.zeroPoint2D);
ls.setCoordsCenter(newPoint);
}
_lastX = xLoc;
_lastY = yLoc;
} else {
switch (selectedHitPointType) {
case POS_POINT:
case TURNOUT_CENTER:
case LEVEL_XING_CENTER:
case SLIP_LEFT:
case SLIP_RIGHT:
case TURNTABLE_CENTER: {
getLayoutTrackView((LayoutTrack) selectedObject).setCoordsCenter(currentPoint);
isDragging = true;
break;
}
case TURNOUT_A: {
getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsA(currentPoint);
break;
}
case TURNOUT_B: {
getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsB(currentPoint);
break;
}
case TURNOUT_C: {
getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsC(currentPoint);
break;
}
case TURNOUT_D: {
getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsD(currentPoint);
break;
}
case LEVEL_XING_A: {
getLevelXingView((LevelXing) selectedObject).setCoordsA(currentPoint);
break;
}
case LEVEL_XING_B: {
getLevelXingView((LevelXing) selectedObject).setCoordsB(currentPoint);
break;
}
case LEVEL_XING_C: {
getLevelXingView((LevelXing) selectedObject).setCoordsC(currentPoint);
break;
}
case LEVEL_XING_D: {
getLevelXingView((LevelXing) selectedObject).setCoordsD(currentPoint);
break;
}
case SLIP_A: {
getLayoutSlipView((LayoutSlip) selectedObject).setCoordsA(currentPoint);
break;
}
case SLIP_B: {
getLayoutSlipView((LayoutSlip) selectedObject).setCoordsB(currentPoint);
break;
}
case SLIP_C: {
getLayoutSlipView((LayoutSlip) selectedObject).setCoordsC(currentPoint);
break;
}
case SLIP_D: {
getLayoutSlipView((LayoutSlip) selectedObject).setCoordsD(currentPoint);
break;
}
case LAYOUT_POS_LABEL:
case MULTI_SENSOR: {
PositionableLabel pl = (PositionableLabel) selectedObject;
if (pl.isPositionable()) {
pl.setLocation((int) currentPoint.getX(), (int) currentPoint.getY());
isDragging = true;
}
break;
}
case LAYOUT_POS_JCOMP: {
PositionableJComponent c = (PositionableJComponent) selectedObject;
if (c.isPositionable()) {
c.setLocation((int) currentPoint.getX(), (int) currentPoint.getY());
isDragging = true;
}
break;
}
case TRACK_CIRCLE_CENTRE: {
TrackSegmentView tv = getTrackSegmentView((TrackSegment) selectedObject);
tv.reCalculateTrackSegmentAngle(currentPoint.getX(), currentPoint.getY());
break;
}
default: {
if (HitPointType.isBezierHitType(foundHitPointType)) {
int index = selectedHitPointType.bezierPointIndex();
getTrackSegmentView((TrackSegment) selectedObject).setBezierControlPoint(currentPoint, index);
} else if ((selectedHitPointType == HitPointType.SHAPE_CENTER)) {
((LayoutShape) selectedObject).setCoordsCenter(currentPoint);
} else if (HitPointType.isShapePointOffsetHitPointType(selectedHitPointType)) {
int index = selectedHitPointType.shapePointIndex();
((LayoutShape) selectedObject).setPoint(index, currentPoint);
} else if (HitPointType.isTurntableRayHitType(selectedHitPointType)) {
LayoutTurntable turn = (LayoutTurntable) selectedObject;
LayoutTurntableView turnView = getLayoutTurntableView(turn);
turnView.setRayCoordsIndexed(currentPoint.getX(), currentPoint.getY(),
selectedHitPointType.turntableTrackIndex());
}
break;
}
}
}
} else if ((beginTrack != null)
&& event.isShiftDown()
&& leToolBarPanel.trackButton.isSelected()) {
// dragging from first end of Track Segment
currentLocation = new Point2D.Double(xLoc, yLoc);
boolean needResetCursor = (foundTrack != null);
if (findLayoutTracksHitPoint(currentLocation, true)) {
// have match to free connection point, change cursor
_targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
} else if (needResetCursor) {
_targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
} else if (event.isShiftDown()
&& leToolBarPanel.shapeButton.isSelected() && (selectedObject != null)) {
// dragging from end of shape
currentLocation = new Point2D.Double(xLoc, yLoc);
} else if (selectionActive && !event.isShiftDown() && !event.isMetaDown()) {
selectionWidth = xLoc - selectionX;
selectionHeight = yLoc - selectionY;
}
redrawPanel();
} else {
Rectangle r = new Rectangle(event.getX(), event.getY(), 1, 1);
((JComponent) event.getSource()).scrollRectToVisible(r);
} // if (isEditable())
} // mouseDragged
@Override
public void mouseEntered(@Nonnull JmriMouseEvent event) {
_targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
/**
* Add an Anchor point.
*/
public void addAnchor() {
addAnchor(currentPoint);
}
@Nonnull
public PositionablePoint addAnchor(@Nonnull Point2D p) {
assert p != null;
// get unique name
String name = finder.uniqueName("A", ++numAnchors);
// create object
PositionablePoint o = new PositionablePoint(name,
PositionablePoint.PointType.ANCHOR, this);
PositionablePointView pv = new PositionablePointView(o, p, this);
addLayoutTrack(o, pv);
setDirty();
return o;
}
/**
* Add an End Bumper point.
*/
public void addEndBumper() {
// get unique name
String name = finder.uniqueName("EB", ++numEndBumpers);
// create object
PositionablePoint o = new PositionablePoint(name,
PositionablePoint.PointType.END_BUMPER, this);
PositionablePointView pv = new PositionablePointView(o, currentPoint, this);
addLayoutTrack(o, pv);
setDirty();
}
/**
* Add an Edge Connector point.
*/
public void addEdgeConnector() {
// get unique name
String name = finder.uniqueName("EC", ++numEdgeConnectors);
// create object
PositionablePoint o = new PositionablePoint(name,
PositionablePoint.PointType.EDGE_CONNECTOR, this);
PositionablePointView pv = new PositionablePointView(o, currentPoint, this);
addLayoutTrack(o, pv);
setDirty();
}
/**
* Add a Track Segment
*/
public void addTrackSegment() {
// get unique name
String name = finder.uniqueName("T", ++numTrackSegments);
// create object
newTrack = new TrackSegment(name, beginTrack, beginHitPointType,
foundTrack, foundHitPointType,
leToolBarPanel.mainlineTrack.isSelected(), this);
TrackSegmentView tsv = new TrackSegmentView(
newTrack,
this
);
addLayoutTrack(newTrack, tsv);
setDirty();
// link to connected objects
setLink(beginTrack, beginHitPointType, newTrack, HitPointType.TRACK);
setLink(foundTrack, foundHitPointType, newTrack, HitPointType.TRACK);
// check on layout block
String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
if (newName == null) {
newName = "";
}
LayoutBlock b = provideLayoutBlock(newName);
if (b != null) {
newTrack.setLayoutBlock(b);
getLEAuxTools().setBlockConnectivityChanged();
// check on occupancy sensor
String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName();
if (sensorName == null) {
sensorName = "";
}
if (!sensorName.isEmpty()) {
if (!validateSensor(sensorName, b, this)) {
b.setOccupancySensorName("");
} else {
leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor());
}
}
newTrack.updateBlockInfo();
}
}
/**
* Add a Level Crossing
*/
public void addLevelXing() {
// get unique name
String name = finder.uniqueName("X", ++numLevelXings);
// create object
LevelXing o = new LevelXing(name, this);
LevelXingView ov = new LevelXingView(o, currentPoint, this);
addLayoutTrack(o, ov);
setDirty();
// check on layout block
String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
if (newName == null) {
newName = "";
}
LayoutBlock b = provideLayoutBlock(newName);
if (b != null) {
o.setLayoutBlockAC(b);
o.setLayoutBlockBD(b);
// check on occupancy sensor
String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName();
if (sensorName == null) {
sensorName = "";
}
if (!sensorName.isEmpty()) {
if (!validateSensor(sensorName, b, this)) {
b.setOccupancySensorName("");
} else {
leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor());
}
}
}
}
/**
* Add a LayoutSlip
*
* @param type the slip type
*/
public void addLayoutSlip(LayoutTurnout.TurnoutType type) {
// get the rotation entry
double rot = 0.0;
String s = leToolBarPanel.rotationComboBox.getEditor().getItem().toString().trim();
if (s.isEmpty()) {
rot = 0.0;
} else {
try {
rot = Double.parseDouble(s);
} catch (NumberFormatException e) {
JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error3") + " "
+ e, Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
return;
}
}
// get unique name
String name = finder.uniqueName("SL", ++numLayoutSlips);
// create object
LayoutSlip o;
LayoutSlipView ov;
switch (type) {
case DOUBLE_SLIP:
LayoutDoubleSlip lds = new LayoutDoubleSlip(name, this);
o = lds;
ov = new LayoutDoubleSlipView(lds, currentPoint, rot, this);
break;
case SINGLE_SLIP:
LayoutSingleSlip lss = new LayoutSingleSlip(name, this);
o = lss;
ov = new LayoutSingleSlipView(lss, currentPoint, rot, this);
break;
default:
log.error("can't create slip {} with type {}", name, type);
return; // without creating
}
addLayoutTrack(o, ov);
setDirty();
// check on layout block
String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
if (newName == null) {
newName = "";
}
LayoutBlock b = provideLayoutBlock(newName);
if (b != null) {
ov.setLayoutBlock(b);
// check on occupancy sensor
String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName();
if (sensorName == null) {
sensorName = "";
}
if (!sensorName.isEmpty()) {
if (!validateSensor(sensorName, b, this)) {
b.setOccupancySensorName("");
} else {
leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor());
}
}
}
String turnoutName = leToolBarPanel.turnoutNameComboBox.getSelectedItemDisplayName();
if (turnoutName == null) {
turnoutName = "";
}
if (validatePhysicalTurnout(turnoutName, this)) {
// turnout is valid and unique.
o.setTurnout(turnoutName);
if (o.getTurnout().getSystemName().equals(turnoutName)) {
leToolBarPanel.turnoutNameComboBox.setSelectedItem(o.getTurnout());
}
} else {
o.setTurnout("");
leToolBarPanel.turnoutNameComboBox.setSelectedItem(null);
leToolBarPanel.turnoutNameComboBox.setSelectedIndex(-1);
}
turnoutName = leToolBarPanel.extraTurnoutNameComboBox.getSelectedItemDisplayName();
if (turnoutName == null) {
turnoutName = "";
}
if (validatePhysicalTurnout(turnoutName, this)) {
// turnout is valid and unique.
o.setTurnoutB(turnoutName);
if (o.getTurnoutB().getSystemName().equals(turnoutName)) {
leToolBarPanel.extraTurnoutNameComboBox.setSelectedItem(o.getTurnoutB());
}
} else {
o.setTurnoutB("");
leToolBarPanel.extraTurnoutNameComboBox.setSelectedItem(null);
leToolBarPanel.extraTurnoutNameComboBox.setSelectedIndex(-1);
}
}
/**
* Add a Layout Turnout
*
* @param type the turnout type
*/
public void addLayoutTurnout(LayoutTurnout.TurnoutType type) {
// get the rotation entry
double rot = 0.0;
String s = leToolBarPanel.rotationComboBox.getEditor().getItem().toString().trim();
if (s.isEmpty()) {
rot = 0.0;
} else {
try {
rot = Double.parseDouble(s);
} catch (NumberFormatException e) {
JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error3") + " "
+ e, Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
return;
}
}
// get unique name
String name = finder.uniqueName("TO", ++numLayoutTurnouts);
// create object - check all types, although not clear all actually reach here
LayoutTurnout o;
LayoutTurnoutView ov;
switch (type) {
case RH_TURNOUT:
LayoutRHTurnout lrht = new LayoutRHTurnout(name, this);
o = lrht;
ov = new LayoutRHTurnoutView(lrht, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
break;
case LH_TURNOUT:
LayoutLHTurnout llht = new LayoutLHTurnout(name, this);
o = llht;
ov = new LayoutLHTurnoutView(llht, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
break;
case WYE_TURNOUT:
LayoutWye lw = new LayoutWye(name, this);
o = lw;
ov = new LayoutWyeView(lw, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
break;
case DOUBLE_XOVER:
LayoutDoubleXOver ldx = new LayoutDoubleXOver(name, this);
o = ldx;
ov = new LayoutDoubleXOverView(ldx, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
break;
case RH_XOVER:
LayoutRHXOver lrx = new LayoutRHXOver(name, this);
o = lrx;
ov = new LayoutRHXOverView(lrx, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
break;
case LH_XOVER:
LayoutLHXOver llx = new LayoutLHXOver(name, this);
o = llx;
ov = new LayoutLHXOverView(llx, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
break;
case DOUBLE_SLIP:
LayoutDoubleSlip lds = new LayoutDoubleSlip(name, this);
o = lds;
ov = new LayoutDoubleSlipView(lds, currentPoint, rot, this);
log.error("Found SINGLE_SLIP in addLayoutTurnout for element {}", name);
break;
case SINGLE_SLIP:
LayoutSingleSlip lss = new LayoutSingleSlip(name, this);
o = lss;
ov = new LayoutSingleSlipView(lss, currentPoint, rot, this);
log.error("Found SINGLE_SLIP in addLayoutTurnout for element {}", name);
break;
default:
log.error("can't create LayoutTrack {} with type {}", name, type);
return; // without creating
}
addLayoutTrack(o, ov);
setDirty();
// check on layout block
String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
if (newName == null) {
newName = "";
}
LayoutBlock b = provideLayoutBlock(newName);
if (b != null) {
ov.setLayoutBlock(b);
// check on occupancy sensor
String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName();
if (sensorName == null) {
sensorName = "";
}
if (!sensorName.isEmpty()) {
if (!validateSensor(sensorName, b, this)) {
b.setOccupancySensorName("");
} else {
leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor());
}
}
}
// set default continuing route Turnout State
o.setContinuingSense(Turnout.CLOSED);
// check on a physical turnout
String turnoutName = leToolBarPanel.turnoutNameComboBox.getSelectedItemDisplayName();
if (turnoutName == null) {
turnoutName = "";
}
if (validatePhysicalTurnout(turnoutName, this)) {
// turnout is valid and unique.
o.setTurnout(turnoutName);
if (o.getTurnout().getSystemName().equals(turnoutName)) {
leToolBarPanel.turnoutNameComboBox.setSelectedItem(o.getTurnout());
}
} else {
o.setTurnout("");
leToolBarPanel.turnoutNameComboBox.setSelectedItem(null);
leToolBarPanel.turnoutNameComboBox.setSelectedIndex(-1);
}
}
/**
* Validates that a physical turnout exists and is unique among Layout
* Turnouts Returns true if valid turnout was entered, false otherwise
*
* @param inTurnoutName the (system or user) name of the turnout
* @param inOpenPane the pane over which to show dialogs (null to
* suppress dialogs)
* @return true if valid
*/
public boolean validatePhysicalTurnout(
@Nonnull String inTurnoutName,
@CheckForNull Component inOpenPane) {
// check if turnout name was entered
if (inTurnoutName.isEmpty()) {
// no turnout entered
return false;
}
// check that the unique turnout name corresponds to a defined physical turnout
Turnout t = InstanceManager.turnoutManagerInstance().getTurnout(inTurnoutName);
if (t == null) {
// There is no turnout corresponding to this name
if (inOpenPane != null) {
JmriJOptionPane.showMessageDialog(inOpenPane,
MessageFormat.format(Bundle.getMessage("Error8"), inTurnoutName),
Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
}
return false;
}
log.debug("validatePhysicalTurnout('{}')", inTurnoutName);
boolean result = true; // assume success (optimist!)
// ensure that this turnout is unique among Layout Turnouts in this Layout
for (LayoutTurnout lt : getLayoutTurnouts()) {
t = lt.getTurnout();
if (t != null) {
String sname = t.getSystemName();
String uname = t.getUserName();
log.debug("{}: Turnout tested '{}' and '{}'.", lt.getName(), sname, uname);
if ((sname.equals(inTurnoutName))
|| ((uname != null) && (uname.equals(inTurnoutName)))) {
result = false;
break;
}
}
// Only check for the second turnout if the type is a double cross over
// otherwise the second turnout is used to throw an additional turnout at
// the same time.
if (lt.isTurnoutTypeXover()) {
t = lt.getSecondTurnout();
if (t != null) {
String sname = t.getSystemName();
String uname = t.getUserName();
log.debug("{}: 2nd Turnout tested '{}' and '{}'.", lt.getName(), sname, uname);
if ((sname.equals(inTurnoutName))
|| ((uname != null) && (uname.equals(inTurnoutName)))) {
result = false;
break;
}
}
}
}
if (result) { // only need to test slips if we haven't failed yet...
// ensure that this turnout is unique among Layout slips in this Layout
for (LayoutSlip sl : getLayoutSlips()) {
t = sl.getTurnout();
if (t != null) {
String sname = t.getSystemName();
String uname = t.getUserName();
log.debug("{}: slip Turnout tested '{}' and '{}'.", sl.getName(), sname, uname);
if ((sname.equals(inTurnoutName))
|| ((uname != null) && (uname.equals(inTurnoutName)))) {
result = false;
break;
}
}
t = sl.getTurnoutB();
if (t != null) {
String sname = t.getSystemName();
String uname = t.getUserName();
log.debug("{}: slip Turnout B tested '{}' and '{}'.", sl.getName(), sname, uname);
if ((sname.equals(inTurnoutName))
|| ((uname != null) && (uname.equals(inTurnoutName)))) {
result = false;
break;
}
}
}
}
if (result) { // only need to test Turntable turnouts if we haven't failed yet...
// ensure that this turntable turnout is unique among turnouts in this Layout
for (LayoutTurntable tt : getLayoutTurntables()) {
for (LayoutTurntable.RayTrack ray : tt.getRayTrackList()) {
t = ray.getTurnout();
if (t != null) {
String sname = t.getSystemName();
String uname = t.getUserName();
log.debug("{}: Turntable turnout tested '{}' and '{}'.", ray.getTurnoutName(), sname, uname);
if ((sname.equals(inTurnoutName))
|| ((uname != null) && (uname.equals(inTurnoutName)))) {
result = false;
break;
}
}
}
}
}
if (!result && (inOpenPane != null)) {
JmriJOptionPane.showMessageDialog(inOpenPane,
MessageFormat.format(Bundle.getMessage("Error4"), inTurnoutName),
Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
}
return result;
}
/**
* link the 'from' object and type to the 'to' object and type
*
* @param fromObject the object to link from
* @param fromPointType the object type to link from
* @param toObject the object to link to
* @param toPointType the object type to link to
*/
public void setLink(@Nonnull LayoutTrack fromObject, HitPointType fromPointType,
@Nonnull LayoutTrack toObject, HitPointType toPointType) {
switch (fromPointType) {
case POS_POINT: {
if ((toPointType == HitPointType.TRACK) && (fromObject instanceof PositionablePoint)) {
((PositionablePoint) fromObject).setTrackConnection((TrackSegment) toObject);
} else {
log.error("Attempt to link a non-TRACK connection ('{}')to a Positionable Point ('{}')",
toObject.getName(), fromObject.getName());
}
break;
}
case TURNOUT_A:
case TURNOUT_B:
case TURNOUT_C:
case TURNOUT_D:
case SLIP_A:
case SLIP_B:
case SLIP_C:
case SLIP_D:
case LEVEL_XING_A:
case LEVEL_XING_B:
case LEVEL_XING_C:
case LEVEL_XING_D: {
try {
fromObject.setConnection(fromPointType, toObject, toPointType);
} catch (JmriException e) {
// ignore (log.error in setConnection method)
}
break;
}
case TRACK: {
// should never happen, Track Segment links are set in ctor
log.error("Illegal request to set a Track Segment link");
break;
}
default: {
if (HitPointType.isTurntableRayHitType(fromPointType) && (fromObject instanceof LayoutTurntable)) {
if (toObject instanceof TrackSegment) {
((LayoutTurntable) fromObject).setRayConnect((TrackSegment) toObject,
fromPointType.turntableTrackIndex());
} else {
log.warn("setLink found expected toObject type {} with fromPointType {} fromObject type {}",
toObject.getClass(), fromPointType, fromObject.getClass(), new Exception("traceback"));
}
} else {
log.warn("setLink found expected fromObject type {} with fromPointType {} toObject type {}",
fromObject.getClass(), fromPointType, toObject.getClass(), new Exception("traceback"));
}
break;
}
}
}
/**
* Return a layout block with the entered name, creating a new one if
* needed. Note that the entered name becomes the user name of the
* LayoutBlock, and a system name is automatically created by
* LayoutBlockManager if needed.
* <p>
* If the block name is a system name, then the user will have to supply a
* user name for the block.
* <p>
* Some, but not all, errors pop a Swing error dialog in addition to
* logging.
*
* @param inBlockName the entered name
* @return the provided LayoutBlock
*/
public LayoutBlock provideLayoutBlock(@Nonnull String inBlockName) {
LayoutBlock result = null; // assume failure (pessimist!)
LayoutBlock newBlk = null; // assume failure (pessimist!)
if (inBlockName.isEmpty()) {
// nothing entered, try autoAssign
if (autoAssignBlocks) {
newBlk = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock();
if (null == newBlk) {
log.error("provideLayoutBlock: Failure to auto-assign for empty LayoutBlock name");
}
} else {
log.debug("provideLayoutBlock: no name given and not assigning auto block names");
}
} else {
// check if this Layout Block already exists
result = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(inBlockName);
if (result == null) { //(no)
// The combo box name can be either a block system name or a block user name
Block checkBlock = InstanceManager.getDefault(BlockManager.class).getBlock(inBlockName);
if (checkBlock == null) {
log.error("provideLayoutBlock: The block name '{}' does not return a block.", inBlockName);
} else {
String checkUserName = checkBlock.getUserName();
if (checkUserName != null && checkUserName.equals(inBlockName)) {
// Go ahead and use the name for the layout block
newBlk = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock(null, inBlockName);
if (newBlk == null) {
log.error("provideLayoutBlock: Failure to create new LayoutBlock '{}'.", inBlockName);
}
} else {
// Appears to be a system name, request a user name
String blkUserName = (String)JmriJOptionPane.showInputDialog(getTargetFrame(),
Bundle.getMessage("BlkUserNameMsg"),
Bundle.getMessage("BlkUserNameTitle"),
JmriJOptionPane.PLAIN_MESSAGE, null, null, "");
if (blkUserName != null && !blkUserName.isEmpty()) {
// Verify the user name
Block checkDuplicate = InstanceManager.getDefault(BlockManager.class).getByUserName(blkUserName);
if (checkDuplicate != null) {
JmriJOptionPane.showMessageDialog(getTargetFrame(),
Bundle.getMessage("BlkUserNameInUse", blkUserName),
Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
} else {
// OK to use as a block user name
checkBlock.setUserName(blkUserName);
newBlk = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock(null, blkUserName);
if (newBlk == null) {
log.error("provideLayoutBlock: Failure to create new LayoutBlock '{}' with a new user name.", blkUserName);
}
}
}
}
}
}
}
// if we created a new block
if (newBlk != null) {
// initialize the new block
// log.debug("provideLayoutBlock :: Init new block {}", inBlockName);
newBlk.initializeLayoutBlock();
newBlk.initializeLayoutBlockRouting();
newBlk.setBlockTrackColor(defaultTrackColor);
newBlk.setBlockOccupiedColor(defaultOccupiedTrackColor);
newBlk.setBlockExtraColor(defaultAlternativeTrackColor);
result = newBlk;
}
if (result != null) {
// set both new and previously existing block
result.addLayoutEditor(this);
result.incrementUse();
setDirty();
}
return result;
}
/**
* Validates that the supplied occupancy sensor name corresponds to an
* existing sensor and is unique among all blocks. If valid, returns true
* and sets the block sensor name in the block. Else returns false, and does
* nothing to the block.
*
* @param sensorName the sensor name to validate
* @param blk the LayoutBlock in which to set it
* @param openFrame the frame (Component) it is in
* @return true if sensor is valid
*/
public boolean validateSensor(
@Nonnull String sensorName,
@Nonnull LayoutBlock blk,
@Nonnull Component openFrame) {
boolean result = false; // assume failure (pessimist!)
// check if anything entered
if (!sensorName.isEmpty()) {
// get a validated sensor corresponding to this name and assigned to block
if (blk.getOccupancySensorName().equals(sensorName)) {
result = true;
} else {
Sensor s = blk.validateSensor(sensorName, openFrame);
result = (s != null); // if sensor returned result is true.
}
}
return result;
}
/**
* Return a layout block with the given name if one exists. Registers this
* LayoutEditor with the layout block. This method is designed to be used
* when a panel is loaded. The calling method must handle whether the use
* count should be incremented.
*
* @param blockID the given name
* @return null if blockID does not already exist
*/
public LayoutBlock getLayoutBlock(@Nonnull String blockID) {
// check if this Layout Block already exists
LayoutBlock blk = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(blockID);
if (blk == null) {
log.error("LayoutBlock '{}' not found when panel loaded", blockID);
return null;
}
blk.addLayoutEditor(this);
return blk;
}
/**
* Remove object from all Layout Editor temporary lists of items not part of
* track schematic
*
* @param s the object to remove
* @return true if found
*/
private boolean remove(@Nonnull Object s) {
boolean found = false;
if (backgroundImage.contains(s)) {
backgroundImage.remove(s);
found = true;
}
if (memoryLabelList.contains(s)) {
memoryLabelList.remove(s);
found = true;
}
if (globalVariableLabelList.contains(s)) {
globalVariableLabelList.remove(s);
found = true;
}
if (blockContentsLabelList.contains(s)) {
blockContentsLabelList.remove(s);
found = true;
}
if (multiSensors.contains(s)) {
multiSensors.remove(s);
found = true;
}
if (clocks.contains(s)) {
clocks.remove(s);
found = true;
}
if (labelImage.contains(s)) {
labelImage.remove(s);
found = true;
}
if (sensorImage.contains(s) || sensorList.contains(s)) {
Sensor sensor = ((SensorIcon) s).getSensor();
if (sensor != null) {
if (removeAttachedBean((sensor))) {
sensorImage.remove(s);
sensorList.remove(s);
found = true;
} else {
return false;
}
}
}
if (signalHeadImage.contains(s) || signalList.contains(s)) {
SignalHead head = ((SignalHeadIcon) s).getSignalHead();
if (head != null) {
if (removeAttachedBean((head))) {
signalHeadImage.remove(s);
signalList.remove(s);
found = true;
} else {
return false;
}
}
}
if (signalMastList.contains(s)) {
SignalMast mast = ((SignalMastIcon) s).getSignalMast();
if (mast != null) {
if (removeAttachedBean((mast))) {
signalMastList.remove(s);
found = true;
} else {
return false;
}
}
}
super.removeFromContents((Positionable) s);
if (found) {
setDirty();
redrawPanel();
}
return found;
}
@Override
public boolean removeFromContents(@Nonnull Positionable l) {
return remove(l);
}
private String findBeanUsage(@Nonnull NamedBean bean) {
PositionablePoint pe;
PositionablePoint pw;
LayoutTurnout lt;
LevelXing lx;
LayoutSlip ls;
boolean found = false;
StringBuilder sb = new StringBuilder();
String msgKey = "DeleteReference"; // NOI18N
String beanKey = "None"; // NOI18N
String beanValue = bean.getDisplayName();
if (bean instanceof SignalMast) {
beanKey = "BeanNameSignalMast"; // NOI18N
if (InstanceManager.getDefault(SignalMastLogicManager.class).isSignalMastUsed((SignalMast) bean)) {
SignalMastLogic sml = InstanceManager.getDefault(
SignalMastLogicManager.class).getSignalMastLogic((SignalMast) bean);
if ((sml != null) && sml.useLayoutEditor(sml.getDestinationList().get(0))) {
msgKey = "DeleteSmlReference"; // NOI18N
}
}
} else if (bean instanceof Sensor) {
beanKey = "BeanNameSensor"; // NOI18N
} else if (bean instanceof SignalHead) {
beanKey = "BeanNameSignalHead"; // NOI18N
}
if (!beanKey.equals("None")) { // NOI18N
sb.append(Bundle.getMessage(msgKey, Bundle.getMessage(beanKey), beanValue));
}
if ((pw = finder.findPositionablePointByWestBoundBean(bean)) != null) {
TrackSegment t1 = pw.getConnect1();
TrackSegment t2 = pw.getConnect2();
if (t1 != null) {
if (t2 != null) {
sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName())); // NOI18N
sb.append(Bundle.getMessage("DeleteAtPoint2", t2.getBlockName())); // NOI18N
} else {
sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName())); // NOI18N
}
}
found = true;
}
if ((pe = finder.findPositionablePointByEastBoundBean(bean)) != null) {
TrackSegment t1 = pe.getConnect1();
TrackSegment t2 = pe.getConnect2();
if (t1 != null) {
if (t2 != null) {
sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName())); // NOI18N
sb.append(Bundle.getMessage("DeleteAtPoint2", t2.getBlockName())); // NOI18N
} else {
sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName())); // NOI18N
}
}
found = true;
}
if ((lt = finder.findLayoutTurnoutByBean(bean)) != null) {
sb.append(Bundle.getMessage("DeleteAtOther", Bundle.getMessage("BeanNameTurnout"), lt.getTurnoutName())); // NOI18N
found = true;
}
if ((lx = finder.findLevelXingByBean(bean)) != null) {
sb.append(Bundle.getMessage("DeleteAtOther", Bundle.getMessage("LevelCrossing"), lx.getId())); // NOI18N
found = true;
}
if ((ls = finder.findLayoutSlipByBean(bean)) != null) {
sb.append(Bundle.getMessage("DeleteAtOther", Bundle.getMessage("Slip"), ls.getTurnoutName())); // NOI18N
found = true;
}
if (!found) {
return null;
}
return sb.toString();
}
/**
* NX Sensors, Signal Heads and Signal Masts can be attached to positional
* points, turnouts and level crossings. If an attachment exists, present an
* option to cancel the remove action, remove the attachement or retain the
* attachment.
*
* @param bean The named bean to be removed.
* @return true if OK to remove the related icon.
*/
private boolean removeAttachedBean(@Nonnull NamedBean bean) {
String usage = findBeanUsage(bean);
if (usage != null) {
usage = String.format("<html>%s</html>", usage);
int selectedValue = JmriJOptionPane.showOptionDialog(this,
usage, Bundle.getMessage("WarningTitle"),
JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
new Object[]{Bundle.getMessage("ButtonYes"),
Bundle.getMessage("ButtonNo"),
Bundle.getMessage("ButtonCancel")},
Bundle.getMessage("ButtonYes"));
if (selectedValue == 1 ) { // array pos 1, No
return true; // return leaving the references in place but allow the icon to be deleted.
}
// array pos 2, cancel or Dialog closed
if (selectedValue == 2 || selectedValue == JmriJOptionPane.CLOSED_OPTION ) {
return false; // do not delete the item
}
if (bean instanceof Sensor) {
// Additional actions for NX sensor pairs
return getLETools().removeSensorAssignment((Sensor) bean);
} else {
removeBeanRefs(bean);
}
}
return true;
}
private void removeBeanRefs(@Nonnull NamedBean bean) {
PositionablePoint pe;
PositionablePoint pw;
LayoutTurnout lt;
LevelXing lx;
LayoutSlip ls;
if ((pw = finder.findPositionablePointByWestBoundBean(bean)) != null) {
pw.removeBeanReference(bean);
}
if ((pe = finder.findPositionablePointByEastBoundBean(bean)) != null) {
pe.removeBeanReference(bean);
}
if ((lt = finder.findLayoutTurnoutByBean(bean)) != null) {
lt.removeBeanReference(bean);
}
if ((lx = finder.findLevelXingByBean(bean)) != null) {
lx.removeBeanReference(bean);
}
if ((ls = finder.findLayoutSlipByBean(bean)) != null) {
ls.removeBeanReference(bean);
}
}
private boolean noWarnPositionablePoint = false;
/**
* Remove a PositionablePoint -- an Anchor or an End Bumper.
*
* @param o the PositionablePoint to remove
* @return true if removed
*/
public boolean removePositionablePoint(@Nonnull PositionablePoint o) {
// First verify with the user that this is really wanted, only show message if there is a bit of track connected
if ((o.getConnect1() != null) || (o.getConnect2() != null)) {
if (!noWarnPositionablePoint) {
int selectedValue = JmriJOptionPane.showOptionDialog(this,
Bundle.getMessage("Question2"), Bundle.getMessage("WarningTitle"),
JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
new Object[]{Bundle.getMessage("ButtonYes"),
Bundle.getMessage("ButtonNo"),
Bundle.getMessage("ButtonYesPlus")},
Bundle.getMessage("ButtonNo"));
// array position 1, ButtonNo , or Dialog Closed.
if (selectedValue == 1 || selectedValue == JmriJOptionPane.CLOSED_OPTION ) {
return false; // return without creating if "No" response
}
if (selectedValue == 2) { // array position 2, ButtonYesPlus
// Suppress future warnings, and continue
noWarnPositionablePoint = true;
}
}
// remove from selection information
if (selectedObject == o) {
selectedObject = null;
}
if (prevSelectedObject == o) {
prevSelectedObject = null;
}
// remove connections if any
TrackSegment t1 = o.getConnect1();
TrackSegment t2 = o.getConnect2();
if (t1 != null) {
removeTrackSegment(t1);
}
if (t2 != null) {
removeTrackSegment(t2);
}
// delete from array
}
return removeLayoutTrackAndRedraw(o);
}
private boolean noWarnLayoutTurnout = false;
/**
* Remove a LayoutTurnout
*
* @param o the LayoutTurnout to remove
* @return true if removed
*/
public boolean removeLayoutTurnout(@Nonnull LayoutTurnout o) {
// First verify with the user that this is really wanted
if (!noWarnLayoutTurnout) {
int selectedValue = JmriJOptionPane.showOptionDialog(this,
Bundle.getMessage("Question1r"), Bundle.getMessage("WarningTitle"),
JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
new Object[]{Bundle.getMessage("ButtonYes"),
Bundle.getMessage("ButtonNo"),
Bundle.getMessage("ButtonYesPlus")},
Bundle.getMessage("ButtonNo"));
// return without removing if array position 1 "No" response or Dialog closed
if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) {
return false;
}
if (selectedValue == 2 ) { // ButtonYesPlus in array position 2
// Suppress future warnings, and continue
noWarnLayoutTurnout = true;
}
}
// remove from selection information
if (selectedObject == o) {
selectedObject = null;
}
if (prevSelectedObject == o) {
prevSelectedObject = null;
}
// remove connections if any
TrackSegment t = (TrackSegment) o.getConnectA();
if (t != null) {
substituteAnchor(getLayoutTurnoutView(o).getCoordsA(), o, t);
}
t = (TrackSegment) o.getConnectB();
if (t != null) {
substituteAnchor(getLayoutTurnoutView(o).getCoordsB(), o, t);
}
t = (TrackSegment) o.getConnectC();
if (t != null) {
substituteAnchor(getLayoutTurnoutView(o).getCoordsC(), o, t);
}
t = (TrackSegment) o.getConnectD();
if (t != null) {
substituteAnchor(getLayoutTurnoutView(o).getCoordsD(), o, t);
}
// decrement Block use count(s)
LayoutBlock b = o.getLayoutBlock();
if (b != null) {
b.decrementUse();
}
if (o.isTurnoutTypeXover() || o.isTurnoutTypeSlip()) {
LayoutBlock b2 = o.getLayoutBlockB();
if ((b2 != null) && (b2 != b)) {
b2.decrementUse();
}
LayoutBlock b3 = o.getLayoutBlockC();
if ((b3 != null) && (b3 != b) && (b3 != b2)) {
b3.decrementUse();
}
LayoutBlock b4 = o.getLayoutBlockD();
if ((b4 != null) && (b4 != b)
&& (b4 != b2) && (b4 != b3)) {
b4.decrementUse();
}
}
return removeLayoutTrackAndRedraw(o);
}
private void substituteAnchor(@Nonnull Point2D loc,
@Nonnull LayoutTrack o, @Nonnull TrackSegment t) {
PositionablePoint p = addAnchor(loc);
if (t.getConnect1() == o) {
t.setNewConnect1(p, HitPointType.POS_POINT);
}
if (t.getConnect2() == o) {
t.setNewConnect2(p, HitPointType.POS_POINT);
}
p.setTrackConnection(t);
}
private boolean noWarnLevelXing = false;
/**
* Remove a Level Crossing
*
* @param o the LevelXing to remove
* @return true if removed
*/
public boolean removeLevelXing(@Nonnull LevelXing o) {
// First verify with the user that this is really wanted
if (!noWarnLevelXing) {
int selectedValue = JmriJOptionPane.showOptionDialog(this,
Bundle.getMessage("Question3r"), Bundle.getMessage("WarningTitle"),
JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
new Object[]{Bundle.getMessage("ButtonYes"),
Bundle.getMessage("ButtonNo"),
Bundle.getMessage("ButtonYesPlus")},
Bundle.getMessage("ButtonNo"));
// array position 1 Button No, or Dialog closed.
if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) {
return false;
}
if (selectedValue == 2 ) { // array position 2 ButtonYesPlus
// Suppress future warnings, and continue
noWarnLevelXing = true;
}
}
// remove from selection information
if (selectedObject == o) {
selectedObject = null;
}
if (prevSelectedObject == o) {
prevSelectedObject = null;
}
// remove connections if any
LevelXingView ov = getLevelXingView(o);
TrackSegment t = (TrackSegment) o.getConnectA();
if (t != null) {
substituteAnchor(ov.getCoordsA(), o, t);
}
t = (TrackSegment) o.getConnectB();
if (t != null) {
substituteAnchor(ov.getCoordsB(), o, t);
}
t = (TrackSegment) o.getConnectC();
if (t != null) {
substituteAnchor(ov.getCoordsC(), o, t);
}
t = (TrackSegment) o.getConnectD();
if (t != null) {
substituteAnchor(ov.getCoordsD(), o, t);
}
// decrement block use count if any blocks in use
LayoutBlock lb = o.getLayoutBlockAC();
if (lb != null) {
lb.decrementUse();
}
LayoutBlock lbx = o.getLayoutBlockBD();
if ((lbx != null) && (lb != null) && (lbx != lb)) {
lb.decrementUse();
}
return removeLayoutTrackAndRedraw(o);
}
private boolean noWarnSlip = false;
/**
* Remove a slip
*
* @param o the LayoutSlip to remove
* @return true if removed
*/
public boolean removeLayoutSlip(@Nonnull LayoutTurnout o) {
if (!(o instanceof LayoutSlip)) {
return false;
}
// First verify with the user that this is really wanted
if (!noWarnSlip) {
int selectedValue = JmriJOptionPane.showOptionDialog(this,
Bundle.getMessage("Question5r"), Bundle.getMessage("WarningTitle"),
JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
new Object[]{Bundle.getMessage("ButtonYes"),
Bundle.getMessage("ButtonNo"),
Bundle.getMessage("ButtonYesPlus")},
Bundle.getMessage("ButtonNo"));
// return without removing if array position 1 "No" response or Dialog closed
if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) {
return false;
}
if (selectedValue == 2 ) { // ButtonYesPlus in array position 2
// Suppress future warnings, and continue
noWarnSlip = true;
}
}
LayoutTurnoutView ov = getLayoutTurnoutView(o);
// remove from selection information
if (selectedObject == o) {
selectedObject = null;
}
if (prevSelectedObject == o) {
prevSelectedObject = null;
}
// remove connections if any
TrackSegment t = (TrackSegment) o.getConnectA();
if (t != null) {
substituteAnchor(ov.getCoordsA(), o, t);
}
t = (TrackSegment) o.getConnectB();
if (t != null) {
substituteAnchor(ov.getCoordsB(), o, t);
}
t = (TrackSegment) o.getConnectC();
if (t != null) {
substituteAnchor(ov.getCoordsC(), o, t);
}
t = (TrackSegment) o.getConnectD();
if (t != null) {
substituteAnchor(ov.getCoordsD(), o, t);
}
// decrement block use count if any blocks in use
LayoutBlock lb = o.getLayoutBlock();
if (lb != null) {
lb.decrementUse();
}
return removeLayoutTrackAndRedraw(o);
}
private boolean noWarnTurntable = false;
/**
* Remove a Layout Turntable
*
* @param o the LayoutTurntable to remove
* @return true if removed
*/
public boolean removeTurntable(@Nonnull LayoutTurntable o) {
// First verify with the user that this is really wanted
if (!noWarnTurntable) {
int selectedValue = JmriJOptionPane.showOptionDialog(this,
Bundle.getMessage("Question4r"), Bundle.getMessage("WarningTitle"),
JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
new Object[]{Bundle.getMessage("ButtonYes"),
Bundle.getMessage("ButtonNo"),
Bundle.getMessage("ButtonYesPlus")},
Bundle.getMessage("ButtonNo"));
// return without removing if array position 1 "No" response or Dialog closed
if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) {
return false;
}
if (selectedValue == 2 ) { // ButtonYesPlus in array position 2
// Suppress future warnings, and continue
noWarnTurntable = true;
}
}
// remove from selection information
if (selectedObject == o) {
selectedObject = null;
}
if (prevSelectedObject == o) {
prevSelectedObject = null;
}
// remove connections if any
LayoutTurntableView ov = getLayoutTurntableView(o);
for (int j = 0; j < o.getNumberRays(); j++) {
TrackSegment t = ov.getRayConnectOrdered(j);
if (t != null) {
substituteAnchor(ov.getRayCoordsIndexed(j), o, t);
}
}
return removeLayoutTrackAndRedraw(o);
}
/**
* Remove a Track Segment
*
* @param o the TrackSegment to remove
*/
public void removeTrackSegment(@Nonnull TrackSegment o) {
// save affected blocks
LayoutBlock block1 = null;
LayoutBlock block2 = null;
LayoutBlock block = o.getLayoutBlock();
// remove any connections
HitPointType type = o.getType1();
if (type == HitPointType.POS_POINT) {
PositionablePoint p = (PositionablePoint) (o.getConnect1());
if (p != null) {
p.removeTrackConnection(o);
if (p.getConnect1() != null) {
block1 = p.getConnect1().getLayoutBlock();
} else if (p.getConnect2() != null) {
block1 = p.getConnect2().getLayoutBlock();
}
}
} else {
block1 = getAffectedBlock(o.getConnect1(), type);
disconnect(o.getConnect1(), type);
}
type = o.getType2();
if (type == HitPointType.POS_POINT) {
PositionablePoint p = (PositionablePoint) (o.getConnect2());
if (p != null) {
p.removeTrackConnection(o);
if (p.getConnect1() != null) {
block2 = p.getConnect1().getLayoutBlock();
} else if (p.getConnect2() != null) {
block2 = p.getConnect2().getLayoutBlock();
}
}
} else {
block2 = getAffectedBlock(o.getConnect2(), type);
disconnect(o.getConnect2(), type);
}
// delete from array
removeLayoutTrack(o);
// update affected blocks
if (block != null) {
// decrement Block use count
block.decrementUse();
getLEAuxTools().setBlockConnectivityChanged();
block.updatePaths();
}
if ((block1 != null) && (block1 != block)) {
block1.updatePaths();
}
if ((block2 != null) && (block2 != block) && (block2 != block1)) {
block2.updatePaths();
}
//
setDirty();
redrawPanel();
}
private void disconnect(@Nonnull LayoutTrack o, HitPointType type) {
switch (type) {
case TURNOUT_A:
case TURNOUT_B:
case TURNOUT_C:
case TURNOUT_D:
case SLIP_A:
case SLIP_B:
case SLIP_C:
case SLIP_D:
case LEVEL_XING_A:
case LEVEL_XING_B:
case LEVEL_XING_C:
case LEVEL_XING_D: {
try {
o.setConnection(type, null, HitPointType.NONE);
} catch (JmriException e) {
// ignore (log.error in setConnection method)
}
break;
}
default: {
if (HitPointType.isTurntableRayHitType(type)) {
((LayoutTurntable) o).setRayConnect(null, type.turntableTrackIndex());
}
break;
}
}
}
/**
* Depending on the given type, and the real class of the given LayoutTrack,
* determine the connected LayoutTrack. This provides a variable-indirect
* form of e.g. trk.getLayoutBlockC() for example. Perhaps "Connected Block"
* captures the idea better, but that method name is being used for
* something else.
*
*
* @param track The track who's connected blocks are being examined
* @param type This point to check for connected blocks, i.e. TURNOUT_B
* @return The block at a particular point on the track object, or null if
* none.
*/
// Temporary - this should certainly be a LayoutTrack method.
public LayoutBlock getAffectedBlock(@Nonnull LayoutTrack track, HitPointType type) {
LayoutBlock result = null;
switch (type) {
case TURNOUT_A:
case SLIP_A: {
if (track instanceof LayoutTurnout) {
LayoutTurnout lt = (LayoutTurnout) track;
result = lt.getLayoutBlock();
}
break;
}
case TURNOUT_B:
case SLIP_B: {
if (track instanceof LayoutTurnout) {
LayoutTurnout lt = (LayoutTurnout) track;
result = lt.getLayoutBlockB();
}
break;
}
case TURNOUT_C:
case SLIP_C: {
if (track instanceof LayoutTurnout) {
LayoutTurnout lt = (LayoutTurnout) track;
result = lt.getLayoutBlockC();
}
break;
}
case TURNOUT_D:
case SLIP_D: {
if (track instanceof LayoutTurnout) {
LayoutTurnout lt = (LayoutTurnout) track;
result = lt.getLayoutBlockD();
}
break;
}
case LEVEL_XING_A:
case LEVEL_XING_C: {
if (track instanceof LevelXing) {
LevelXing lx = (LevelXing) track;
result = lx.getLayoutBlockAC();
}
break;
}
case LEVEL_XING_B:
case LEVEL_XING_D: {
if (track instanceof LevelXing) {
LevelXing lx = (LevelXing) track;
result = lx.getLayoutBlockBD();
}
break;
}
case TRACK: {
if (track instanceof TrackSegment) {
TrackSegment ts = (TrackSegment) track;
result = ts.getLayoutBlock();
}
break;
}
default: {
log.warn("Unhandled track type: {}", type);
break;
}
}
return result;
}
/**
* Add a sensor indicator to the Draw Panel
*/
void addSensor() {
String newName = leToolBarPanel.sensorComboBox.getSelectedItemDisplayName();
if (newName == null) {
newName = "";
}
if (newName.isEmpty()) {
JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error10"),
Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
return;
}
SensorIcon l = new SensorIcon(new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif",
"resources/icons/smallschematics/tracksegments/circuit-error.gif"), this);
l.setIcon("SensorStateActive", leToolBarPanel.sensorIconEditor.getIcon(0));
l.setIcon("SensorStateInactive", leToolBarPanel.sensorIconEditor.getIcon(1));
l.setIcon("BeanStateInconsistent", leToolBarPanel.sensorIconEditor.getIcon(2));
l.setIcon("BeanStateUnknown", leToolBarPanel.sensorIconEditor.getIcon(3));
l.setSensor(newName);
l.setDisplayLevel(Editor.SENSORS);
leToolBarPanel.sensorComboBox.setSelectedItem(l.getSensor());
setNextLocation(l);
try {
putItem(l); // note: this calls unionToPanelBounds & setDirty()
} catch (Positionable.DuplicateIdException e) {
// This should never happen
log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
}
}
public void putSensor(@Nonnull SensorIcon l) {
l.updateSize();
l.setDisplayLevel(Editor.SENSORS);
try {
putItem(l); // note: this calls unionToPanelBounds & setDirty()
} catch (Positionable.DuplicateIdException e) {
// This should never happen
log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
}
}
/**
* Add a signal head to the Panel
*/
void addSignalHead() {
// check for valid signal head entry
String newName = leToolBarPanel.signalHeadComboBox.getSelectedItemDisplayName();
if (newName == null) {
newName = "";
}
SignalHead mHead = null;
if (!newName.isEmpty()) {
mHead = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(newName);
/*if (mHead == null)
mHead = InstanceManager.getDefault(SignalHeadManager.class).getByUserName(newName);
else */
leToolBarPanel.signalHeadComboBox.setSelectedItem(mHead);
}
if (mHead == null) {
// There is no signal head corresponding to this name
JmriJOptionPane.showMessageDialog(this,
MessageFormat.format(Bundle.getMessage("Error9"), newName),
Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
return;
}
// create and set up signal icon
SignalHeadIcon l = new SignalHeadIcon(this);
l.setSignalHead(newName);
l.setIcon("SignalHeadStateRed", leToolBarPanel.signalIconEditor.getIcon(0));
l.setIcon("SignalHeadStateFlashingRed", leToolBarPanel.signalIconEditor.getIcon(1));
l.setIcon("SignalHeadStateYellow", leToolBarPanel.signalIconEditor.getIcon(2));
l.setIcon("SignalHeadStateFlashingYellow", leToolBarPanel.signalIconEditor.getIcon(3));
l.setIcon("SignalHeadStateGreen", leToolBarPanel.signalIconEditor.getIcon(4));
l.setIcon("SignalHeadStateFlashingGreen", leToolBarPanel.signalIconEditor.getIcon(5));
l.setIcon("SignalHeadStateDark", leToolBarPanel.signalIconEditor.getIcon(6));
l.setIcon("SignalHeadStateHeld", leToolBarPanel.signalIconEditor.getIcon(7));
l.setIcon("SignalHeadStateLunar", leToolBarPanel.signalIconEditor.getIcon(8));
l.setIcon("SignalHeadStateFlashingLunar", leToolBarPanel.signalIconEditor.getIcon(9));
unionToPanelBounds(l.getBounds());
setNextLocation(l);
setDirty();
putSignal(l);
}
public void putSignal(@Nonnull SignalHeadIcon l) {
l.updateSize();
l.setDisplayLevel(Editor.SIGNALS);
try {
putItem(l); // note: this calls unionToPanelBounds & setDirty()
} catch (Positionable.DuplicateIdException e) {
// This should never happen
log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
}
}
@CheckForNull
SignalHead getSignalHead(@Nonnull String name) {
SignalHead sh = InstanceManager.getDefault(SignalHeadManager.class).getBySystemName(name);
if (sh == null) {
sh = InstanceManager.getDefault(SignalHeadManager.class).getByUserName(name);
}
if (sh == null) {
log.warn("did not find a SignalHead named {}", name);
}
return sh;
}
public boolean containsSignalHead(@CheckForNull SignalHead head) {
if (head != null) {
for (SignalHeadIcon h : signalList) {
if (h.getSignalHead() == head) {
return true;
}
}
}
return false;
}
public void removeSignalHead(@CheckForNull SignalHead head) {
if (head != null) {
for (SignalHeadIcon h : signalList) {
if (h.getSignalHead() == head) {
signalList.remove(h);
h.remove();
h.dispose();
setDirty();
redrawPanel();
break;
}
}
}
}
void addSignalMast() {
// check for valid signal head entry
String newName = leToolBarPanel.signalMastComboBox.getSelectedItemDisplayName();
if (newName == null) {
newName = "";
}
SignalMast mMast = null;
if (!newName.isEmpty()) {
mMast = InstanceManager.getDefault(SignalMastManager.class).getSignalMast(newName);
leToolBarPanel.signalMastComboBox.setSelectedItem(mMast);
}
if (mMast == null) {
// There is no signal head corresponding to this name
JmriJOptionPane.showMessageDialog(this,
MessageFormat.format(Bundle.getMessage("Error9"), newName),
Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
return;
}
// create and set up signal icon
SignalMastIcon l = new SignalMastIcon(this);
l.setSignalMast(newName);
unionToPanelBounds(l.getBounds());
setNextLocation(l);
setDirty();
putSignalMast(l);
}
public void putSignalMast(@Nonnull SignalMastIcon l) {
l.updateSize();
l.setDisplayLevel(Editor.SIGNALS);
try {
putItem(l); // note: this calls unionToPanelBounds & setDirty()
} catch (Positionable.DuplicateIdException e) {
// This should never happen
log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
}
}
SignalMast getSignalMast(@Nonnull String name) {
SignalMast sh = InstanceManager.getDefault(SignalMastManager.class).getBySystemName(name);
if (sh == null) {
sh = InstanceManager.getDefault(SignalMastManager.class).getByUserName(name);
}
if (sh == null) {
log.warn("did not find a SignalMast named {}", name);
}
return sh;
}
public boolean containsSignalMast(@Nonnull SignalMast mast) {
for (SignalMastIcon h : signalMastList) {
if (h.getSignalMast() == mast) {
return true;
}
}
return false;
}
/**
* Add a label to the Draw Panel
*/
void addLabel() {
String labelText = leToolBarPanel.textLabelTextField.getText();
labelText = (labelText != null) ? labelText.trim() : "";
if (labelText.isEmpty()) {
JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11"),
Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
return;
}
PositionableLabel l = super.addLabel(labelText);
unionToPanelBounds(l.getBounds());
setDirty();
l.setForeground(defaultTextColor);
}
@Override
public void putItem(@Nonnull Positionable l) throws Positionable.DuplicateIdException {
super.putItem(l);
if (l instanceof SensorIcon) {
sensorImage.add((SensorIcon) l);
sensorList.add((SensorIcon) l);
} else if (l instanceof LocoIcon) {
markerImage.add((LocoIcon) l);
} else if (l instanceof SignalHeadIcon) {
signalHeadImage.add((SignalHeadIcon) l);
signalList.add((SignalHeadIcon) l);
} else if (l instanceof SignalMastIcon) {
signalMastList.add((SignalMastIcon) l);
} else if (l instanceof MemoryIcon) {
memoryLabelList.add((MemoryIcon) l);
} else if (l instanceof GlobalVariableIcon) {
globalVariableLabelList.add((GlobalVariableIcon) l);
} else if (l instanceof BlockContentsIcon) {
blockContentsLabelList.add((BlockContentsIcon) l);
} else if (l instanceof AnalogClock2Display) {
clocks.add((AnalogClock2Display) l);
} else if (l instanceof MultiSensorIcon) {
multiSensors.add((MultiSensorIcon) l);
}
if (l instanceof PositionableLabel) {
if (((PositionableLabel) l).isBackground()) {
backgroundImage.add((PositionableLabel) l);
} else {
labelImage.add((PositionableLabel) l);
}
}
unionToPanelBounds(l.getBounds(new Rectangle()));
setDirty();
}
/**
* Add a memory label to the Draw Panel
*/
void addMemory() {
String memoryName = leToolBarPanel.textMemoryComboBox.getSelectedItemDisplayName();
if (memoryName == null) {
memoryName = "";
}
if (memoryName.isEmpty()) {
JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11a"),
Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
return;
}
MemoryIcon l = new MemoryIcon(" ", this);
l.setMemory(memoryName);
Memory xMemory = l.getMemory();
if (xMemory != null) {
String uname = xMemory.getDisplayName();
if (!uname.equals(memoryName)) {
// put the system name in the memory field
leToolBarPanel.textMemoryComboBox.setSelectedItem(xMemory);
}
}
setNextLocation(l);
l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
l.setDisplayLevel(Editor.LABELS);
l.setForeground(defaultTextColor);
unionToPanelBounds(l.getBounds());
try {
putItem(l); // note: this calls unionToPanelBounds & setDirty()
} catch (Positionable.DuplicateIdException e) {
// This should never happen
log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
}
}
void addGlobalVariable() {
String globalVariableName = leToolBarPanel.textGlobalVariableComboBox.getSelectedItemDisplayName();
if (globalVariableName == null) {
globalVariableName = "";
}
if (globalVariableName.isEmpty()) {
JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11c"),
Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
return;
}
GlobalVariableIcon l = new GlobalVariableIcon(" ", this);
l.setGlobalVariable(globalVariableName);
GlobalVariable xGlobalVariable = l.getGlobalVariable();
if (xGlobalVariable != null) {
String uname = xGlobalVariable.getDisplayName();
if (!uname.equals(globalVariableName)) {
// put the system name in the memory field
leToolBarPanel.textGlobalVariableComboBox.setSelectedItem(xGlobalVariable);
}
}
setNextLocation(l);
l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
l.setDisplayLevel(Editor.LABELS);
l.setForeground(defaultTextColor);
unionToPanelBounds(l.getBounds());
try {
putItem(l); // note: this calls unionToPanelBounds & setDirty()
} catch (Positionable.DuplicateIdException e) {
// This should never happen
log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
}
}
void addBlockContents() {
String newName = leToolBarPanel.blockContentsComboBox.getSelectedItemDisplayName();
if (newName == null) {
newName = "";
}
if (newName.isEmpty()) {
JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11b"),
Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
return;
}
BlockContentsIcon l = new BlockContentsIcon(" ", this);
l.setBlock(newName);
Block xMemory = l.getBlock();
if (xMemory != null) {
String uname = xMemory.getDisplayName();
if (!uname.equals(newName)) {
// put the system name in the memory field
leToolBarPanel.blockContentsComboBox.setSelectedItem(xMemory);
}
}
setNextLocation(l);
l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
l.setDisplayLevel(Editor.LABELS);
l.setForeground(defaultTextColor);
try {
putItem(l); // note: this calls unionToPanelBounds & setDirty()
} catch (Positionable.DuplicateIdException e) {
// This should never happen
log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
}
}
/**
* Add a Reporter Icon to the panel.
*
* @param reporter the reporter icon to add.
* @param xx the horizontal location.
* @param yy the vertical location.
*/
public void addReporter(@Nonnull Reporter reporter, int xx, int yy) {
ReporterIcon l = new ReporterIcon(this);
l.setReporter(reporter);
l.setLocation(xx, yy);
l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
l.setDisplayLevel(Editor.LABELS);
unionToPanelBounds(l.getBounds());
try {
putItem(l); // note: this calls unionToPanelBounds & setDirty()
} catch (Positionable.DuplicateIdException e) {
// This should never happen
log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
}
}
/**
* Add an icon to the target
*/
void addIcon() {
PositionableLabel l = new PositionableLabel(leToolBarPanel.iconEditor.getIcon(0), this);
setNextLocation(l);
l.setDisplayLevel(Editor.ICONS);
unionToPanelBounds(l.getBounds());
l.updateSize();
try {
putItem(l); // note: this calls unionToPanelBounds & setDirty()
} catch (Positionable.DuplicateIdException e) {
// This should never happen
log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
}
}
/**
* Add a LogixNG icon to the target
*/
void addLogixNGIcon() {
LogixNGIcon l = new LogixNGIcon(leToolBarPanel.logixngEditor.getIcon(0), this);
setNextLocation(l);
l.setDisplayLevel(Editor.ICONS);
unionToPanelBounds(l.getBounds());
l.updateSize();
try {
putItem(l); // note: this calls unionToPanelBounds & setDirty()
} catch (Positionable.DuplicateIdException e) {
// This should never happen
log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
}
}
/**
* Add a LogixNG icon to the target
*/
void addAudioIcon() {
String audioName = leToolBarPanel.textAudioComboBox.getSelectedItemDisplayName();
if (audioName == null) {
audioName = "";
}
if (audioName.isEmpty()) {
JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11d"),
Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
return;
}
AudioIcon l = new AudioIcon(leToolBarPanel.audioEditor.getIcon(0), this);
l.setAudio(audioName);
Audio xAudio = l.getAudio();
if (xAudio != null) {
String uname = xAudio.getDisplayName();
if (!uname.equals(audioName)) {
// put the system name in the memory field
leToolBarPanel.textAudioComboBox.setSelectedItem(xAudio);
}
}
setNextLocation(l);
l.setDisplayLevel(Editor.ICONS);
unionToPanelBounds(l.getBounds());
l.updateSize();
try {
putItem(l); // note: this calls unionToPanelBounds & setDirty()
} catch (Positionable.DuplicateIdException e) {
// This should never happen
log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
}
}
/**
* Add a loco marker to the target
*/
@Override
public LocoIcon addLocoIcon(@Nonnull String name) {
LocoIcon l = new LocoIcon(this);
Point2D pt = windowCenter();
if (selectionActive) {
pt = MathUtil.midPoint(getSelectionRect());
}
l.setLocation((int) pt.getX(), (int) pt.getY());
putLocoIcon(l, name);
l.setPositionable(true);
unionToPanelBounds(l.getBounds());
return l;
}
@Override
public void putLocoIcon(@Nonnull LocoIcon l, @Nonnull String name) {
super.putLocoIcon(l, name);
markerImage.add(l);
unionToPanelBounds(l.getBounds());
}
private JFileChooser inputFileChooser = null;
/**
* Add a background image
*/
public void addBackground() {
if (inputFileChooser == null) {
inputFileChooser = new jmri.util.swing.JmriJFileChooser(
String.format("%s%sresources%sicons",
System.getProperty("user.dir"),
File.separator,
File.separator));
inputFileChooser.setFileFilter(new FileNameExtensionFilter("Graphics Files", "gif", "jpg", "png"));
}
inputFileChooser.rescanCurrentDirectory();
int retVal = inputFileChooser.showOpenDialog(this);
if (retVal != JFileChooser.APPROVE_OPTION) {
return; // give up if no file selected
}
// NamedIcon icon = new NamedIcon(inputFileChooser.getSelectedFile().getPath(),
// inputFileChooser.getSelectedFile().getPath());
String name = inputFileChooser.getSelectedFile().getPath();
// convert to portable path
name = FileUtil.getPortableFilename(name);
// setup icon
PositionableLabel o = super.setUpBackground(name);
backgroundImage.add(o);
unionToPanelBounds(o.getBounds());
setDirty();
}
// there is no way to call this; could that
// private boolean remove(@Nonnull Object s)
// is being used instead.
//
///**
// * Remove a background image from the list of background images
// *
// * @param b PositionableLabel to remove
// */
//private void removeBackground(@Nonnull PositionableLabel b) {
// if (backgroundImage.contains(b)) {
// backgroundImage.remove(b);
// setDirty();
// }
//}
/**
* add a layout shape to the list of layout shapes
*
* @param p Point2D where the shape should be
* @return the LayoutShape
*/
@Nonnull
private LayoutShape addLayoutShape(@Nonnull Point2D p) {
// get unique name
String name = finder.uniqueName("S", getLayoutShapes().size() + 1);
// create object
LayoutShape o = new LayoutShape(name, p, this);
layoutShapes.add(o);
unionToPanelBounds(o.getBounds());
setDirty();
return o;
}
/**
* Remove a layout shape from the list of layout shapes
*
* @param s the LayoutShape to add
* @return true if added
*/
public boolean removeLayoutShape(@Nonnull LayoutShape s) {
boolean result = false;
if (layoutShapes.contains(s)) {
layoutShapes.remove(s);
setDirty();
result = true;
redrawPanel();
}
return result;
}
/**
* Invoke a window to allow you to add a MultiSensor indicator to the target
*/
private int multiLocX;
private int multiLocY;
void startMultiSensor() {
multiLocX = xLoc;
multiLocY = yLoc;
if (leToolBarPanel.multiSensorFrame == null) {
// create a common edit frame
leToolBarPanel.multiSensorFrame = new MultiSensorIconFrame(this);
leToolBarPanel.multiSensorFrame.initComponents();
leToolBarPanel.multiSensorFrame.pack();
}
leToolBarPanel.multiSensorFrame.setVisible(true);
}
// Invoked when window has new multi-sensor ready
public void addMultiSensor(@Nonnull MultiSensorIcon l) {
l.setLocation(multiLocX, multiLocY);
try {
putItem(l); // note: this calls unionToPanelBounds & setDirty()
} catch (Positionable.DuplicateIdException e) {
// This should never happen
log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
}
leToolBarPanel.multiSensorFrame.dispose();
leToolBarPanel.multiSensorFrame = null;
}
/**
* Set object location and size for icon and label object as it is created.
* Size comes from the preferredSize; location comes from the fields where
* the user can spec it.
*
* @param obj the positionable object.
*/
@Override
public void setNextLocation(@Nonnull Positionable obj) {
obj.setLocation(xLoc, yLoc);
}
//
// singleton (one per-LayoutEditor) accessors
//
private ConnectivityUtil conTools = null;
@Nonnull
public ConnectivityUtil getConnectivityUtil() {
if (conTools == null) {
conTools = new ConnectivityUtil(this);
}
return conTools;
}
private LayoutEditorTools tools = null;
@Nonnull
public LayoutEditorTools getLETools() {
if (tools == null) {
tools = new LayoutEditorTools(this);
}
return tools;
}
private LayoutEditorAuxTools auxTools = null;
@Override
@Nonnull
public LayoutEditorAuxTools getLEAuxTools() {
if (auxTools == null) {
auxTools = new LayoutEditorAuxTools(this);
}
return auxTools;
}
private LayoutEditorChecks layoutEditorChecks = null;
@Nonnull
public LayoutEditorChecks getLEChecks() {
if (layoutEditorChecks == null) {
layoutEditorChecks = new LayoutEditorChecks(this);
}
return layoutEditorChecks;
}
/**
* Invoked by DeletePanel menu item Validate user intent before deleting
*/
@Override
public boolean deletePanel() {
if (canDeletePanel()) {
// verify deletion
if (!super.deletePanel()) {
return false; // return without deleting if "No" response
}
clearLayoutTracks();
return true;
}
return false;
}
/**
* Check for conditions that prevent a delete.
* <ul>
* <li>The panel has active edge connector links</li>
* <li>The panel is used by EntryExit</li>
* </ul>
* @return true if ok to delete
*/
public boolean canDeletePanel() {
var messages = new ArrayList<String>();
var points = getPositionablePoints();
for (PositionablePoint point : points) {
if (point.getType() == PositionablePoint.PointType.EDGE_CONNECTOR) {
var panelName = point.getLinkedEditorName();
if (!panelName.isEmpty()) {
messages.add(Bundle.getMessage("ActiveEdgeConnector", point.getId(), point.getLinkedEditorName()));
}
}
}
var entryExitPairs = InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class);
if (!entryExitPairs.getNxSource(this).isEmpty()) {
messages.add(Bundle.getMessage("ActiveEntryExit"));
}
if (!messages.isEmpty()) {
StringBuilder msg = new StringBuilder(Bundle.getMessage("PanelRelationshipsError"));
for (String message : messages) {
msg.append(message);
}
JmriJOptionPane.showMessageDialog(null,
msg.toString(),
Bundle.getMessage("ErrorTitle"), // NOI18N
JmriJOptionPane.ERROR_MESSAGE);
}
return messages.isEmpty();
}
/**
* Control whether target panel items are editable. Does this by invoking
* the {@link Editor#setAllEditable} function of the parent class. This also
* controls the relevant pop-up menu items (which are the primary way that
* items are edited).
*
* @param editable true for editable.
*/
@Override
public void setAllEditable(boolean editable) {
int restoreScroll = _scrollState;
super.setAllEditable(editable);
if (toolBarSide.equals(ToolBarSide.eFLOAT)) {
if (editable) {
createfloatingEditToolBoxFrame();
createFloatingHelpPanel();
} else {
deletefloatingEditToolBoxFrame();
}
} else {
editToolBarContainerPanel.setVisible(editable);
}
setShowHidden(editable);
if (editable) {
setScroll(Editor.SCROLL_BOTH);
_scrollState = restoreScroll;
} else {
setScroll(_scrollState);
}
// these may not be set up yet...
if (helpBarPanel != null) {
if (toolBarSide.equals(ToolBarSide.eFLOAT)) {
if (floatEditHelpPanel != null) {
floatEditHelpPanel.setVisible(isEditable() && getShowHelpBar());
}
} else {
helpBarPanel.setVisible(editable && getShowHelpBar());
}
}
awaitingIconChange = false;
editModeCheckBoxMenuItem.setSelected(editable);
redrawPanel();
}
/**
* Control whether panel items are positionable. Markers are always
* positionable.
*
* @param state true for positionable.
*/
@Override
public void setAllPositionable(boolean state) {
super.setAllPositionable(state);
markerImage.forEach((p) -> p.setPositionable(true));
}
/**
* Control whether target panel items are controlling layout items. Does
* this by invoke the {@link Positionable#setControlling} function of each
* item on the target panel. This also controls the relevant pop-up menu
* items.
*
* @param state true for controlling.
*/
public void setTurnoutAnimation(boolean state) {
if (animationCheckBoxMenuItem.isSelected() != state) {
animationCheckBoxMenuItem.setSelected(state);
}
if (animatingLayout != state) {
animatingLayout = state;
redrawPanel();
}
}
public boolean isAnimating() {
return animatingLayout;
}
public boolean getScroll() {
// deprecated but kept to allow opening files
// on version 2.5.1 and earlier
return _scrollState != Editor.SCROLL_NONE;
}
// public Color getDefaultBackgroundColor() {
// return defaultBackgroundColor;
// }
public String getDefaultTrackColor() {
return ColorUtil.colorToColorName(defaultTrackColor);
}
/**
*
* Getter defaultTrackColor.
*
* @return block default color as Color
*/
@Nonnull
public Color getDefaultTrackColorColor() {
return defaultTrackColor;
}
@Nonnull
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it")
public String getDefaultOccupiedTrackColor() {
return ColorUtil.colorToColorName(defaultOccupiedTrackColor);
}
/**
*
* Getter defaultOccupiedTrackColor.
*
* @return block default occupied color as Color
*/
@Nonnull
public Color getDefaultOccupiedTrackColorColor() {
return defaultOccupiedTrackColor;
}
@Nonnull
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it")
public String getDefaultAlternativeTrackColor() {
return ColorUtil.colorToColorName(defaultAlternativeTrackColor);
}
/**
*
* Getter defaultAlternativeTrackColor.
*
* @return block default alternative color as Color
*/
@Nonnull
public Color getDefaultAlternativeTrackColorColor() {
return defaultAlternativeTrackColor;
}
@Nonnull
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it")
public String getDefaultTextColor() {
return ColorUtil.colorToColorName(defaultTextColor);
}
@Nonnull
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it")
public String getTurnoutCircleColor() {
return ColorUtil.colorToColorName(turnoutCircleColor);
}
@Nonnull
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it")
public String getTurnoutCircleThrownColor() {
return ColorUtil.colorToColorName(turnoutCircleThrownColor);
}
public boolean isTurnoutFillControlCircles() {
return turnoutFillControlCircles;
}
public int getTurnoutCircleSize() {
return turnoutCircleSize;
}
public boolean isTurnoutDrawUnselectedLeg() {
return turnoutDrawUnselectedLeg;
}
public String getLayoutName() {
return layoutName;
}
// TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
public boolean getShowHelpBar() {
return showHelpBar;
}
// TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
public boolean getDrawGrid() {
return drawGrid;
}
// TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
public boolean getSnapOnAdd() {
return snapToGridOnAdd;
}
// TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
public boolean getSnapOnMove() {
return snapToGridOnMove;
}
// TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
public boolean getAntialiasingOn() {
return antialiasingOn;
}
public boolean isDrawLayoutTracksLabel() {
return drawLayoutTracksLabel;
}
// TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
public boolean getHighlightSelectedBlock() {
return highlightSelectedBlockFlag;
}
// TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
public boolean getTurnoutCircles() {
return turnoutCirclesWithoutEditMode;
}
// TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
public boolean getTooltipsNotEdit() {
return tooltipsWithoutEditMode;
}
// TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
public boolean getTooltipsInEdit() {
return tooltipsInEditMode;
}
// TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
public boolean getAutoBlockAssignment() {
return autoAssignBlocks;
}
public void setLayoutDimensions(int windowWidth, int windowHeight, int windowX, int windowY, int panelWidth, int panelHeight) {
setLayoutDimensions(windowWidth, windowHeight, windowX, windowY, panelWidth, panelHeight, false);
}
public void setLayoutDimensions(int windowWidth, int windowHeight, int windowX, int windowY, int panelWidth, int panelHeight, boolean merge) {
gContext.setUpperLeftX(windowX);
gContext.setUpperLeftY(windowY);
setLocation(gContext.getUpperLeftX(), gContext.getUpperLeftY());
gContext.setWindowWidth(windowWidth);
gContext.setWindowHeight(windowHeight);
setSize(windowWidth, windowHeight);
Rectangle2D panelBounds = new Rectangle2D.Double(0.0, 0.0, panelWidth, panelHeight);
if (merge) {
panelBounds.add(calculateMinimumLayoutBounds());
}
setPanelBounds(panelBounds);
}
@Nonnull
public Rectangle2D getPanelBounds() {
return new Rectangle2D.Double(0.0, 0.0, gContext.getLayoutWidth(), gContext.getLayoutHeight());
}
public void setPanelBounds(@Nonnull Rectangle2D newBounds) {
// don't let origin go negative
newBounds = newBounds.createIntersection(MathUtil.zeroToInfinityRectangle2D);
if (!getPanelBounds().equals(newBounds)) {
gContext.setLayoutWidth((int) newBounds.getWidth());
gContext.setLayoutHeight((int) newBounds.getHeight());
resetTargetSize();
}
log.debug("setPanelBounds(({})", newBounds);
}
private void resetTargetSize() {
int newTargetWidth = (int) (gContext.getLayoutWidth() * getZoom());
int newTargetHeight = (int) (gContext.getLayoutHeight() * getZoom());
Dimension targetPanelSize = getTargetPanelSize();
int oldTargetWidth = (int) targetPanelSize.getWidth();
int oldTargetHeight = (int) targetPanelSize.getHeight();
if ((newTargetWidth != oldTargetWidth) || (newTargetHeight != oldTargetHeight)) {
setTargetPanelSize(newTargetWidth, newTargetHeight);
adjustScrollBars();
}
}
// this will grow the panel bounds based on items added to the layout
@Nonnull
public Rectangle2D unionToPanelBounds(@Nonnull Rectangle2D bounds) {
Rectangle2D result = getPanelBounds();
// make room to expand
Rectangle2D b = MathUtil.inset(bounds, gContext.getGridSize() * gContext.getGridSize2nd() / -2.0);
// don't let origin go negative
b = b.createIntersection(MathUtil.zeroToInfinityRectangle2D);
result.add(b);
setPanelBounds(result);
return result;
}
/**
* @param color value to set the default track color to.
*/
public void setDefaultTrackColor(@Nonnull Color color) {
defaultTrackColor = color;
JmriColorChooser.addRecentColor(color);
}
/**
* @param color value to set the default occupied track color to.
*/
public void setDefaultOccupiedTrackColor(@Nonnull Color color) {
defaultOccupiedTrackColor = color;
JmriColorChooser.addRecentColor(color);
}
/**
* @param color value to set the default alternate track color to.
*/
public void setDefaultAlternativeTrackColor(@Nonnull Color color) {
defaultAlternativeTrackColor = color;
JmriColorChooser.addRecentColor(color);
}
/**
* @param color new color for turnout circle.
*/
public void setTurnoutCircleColor(@CheckForNull Color color) {
if (color == null) {
turnoutCircleColor = getDefaultTrackColorColor();
} else {
turnoutCircleColor = color;
JmriColorChooser.addRecentColor(color);
}
}
/**
* @param color new color for turnout circle.
*/
public void setTurnoutCircleThrownColor(@CheckForNull Color color) {
if (color == null) {
turnoutCircleThrownColor = getDefaultTrackColorColor();
} else {
turnoutCircleThrownColor = color;
JmriColorChooser.addRecentColor(color);
}
}
/**
* Should only be invoked on the GUI (Swing) thread.
*
* @param state true to fill in turnout control circles, else false.
*/
@InvokeOnGuiThread
public void setTurnoutFillControlCircles(boolean state) {
if (turnoutFillControlCircles != state) {
turnoutFillControlCircles = state;
turnoutFillControlCirclesCheckBoxMenuItem.setSelected(turnoutFillControlCircles);
}
}
public void setTurnoutCircleSize(int size) {
// this is an int
turnoutCircleSize = size;
// these are doubles
circleRadius = SIZE * size;
circleDiameter = 2.0 * circleRadius;
setOptionMenuTurnoutCircleSize();
}
/**
* Should only be invoked on the GUI (Swing) thread.
*
* @param state true to draw unselected legs, else false.
*/
@InvokeOnGuiThread
public void setTurnoutDrawUnselectedLeg(boolean state) {
if (turnoutDrawUnselectedLeg != state) {
turnoutDrawUnselectedLeg = state;
turnoutDrawUnselectedLegCheckBoxMenuItem.setSelected(turnoutDrawUnselectedLeg);
}
}
/**
* @param color value to set the default text color to.
*/
public void setDefaultTextColor(@Nonnull Color color) {
defaultTextColor = color;
JmriColorChooser.addRecentColor(color);
}
/**
* @param color value to set the panel background to.
*/
public void setDefaultBackgroundColor(@Nonnull Color color) {
defaultBackgroundColor = color;
JmriColorChooser.addRecentColor(color);
}
public void setLayoutName(@Nonnull String name) {
layoutName = name;
}
/**
* Should only be invoked on the GUI (Swing) thread.
*
* @param state true to show the help bar, else false.
*/
@InvokeOnGuiThread // due to the setSelected call on a possibly-visible item
public void setShowHelpBar(boolean state) {
if (showHelpBar != state) {
showHelpBar = state;
// these may not be set up yet...
if (showHelpCheckBoxMenuItem != null) {
showHelpCheckBoxMenuItem.setSelected(showHelpBar);
}
if (toolBarSide.equals(ToolBarSide.eFLOAT)) {
if (floatEditHelpPanel != null) {
floatEditHelpPanel.setVisible(isEditable() && showHelpBar);
}
} else {
if (helpBarPanel != null) {
helpBarPanel.setVisible(isEditable() && showHelpBar);
}
}
InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".showHelpBar", showHelpBar));
}
}
/**
* Should only be invoked on the GUI (Swing) thread.
*
* @param state true to show the draw grid, else false.
*/
@InvokeOnGuiThread
public void setDrawGrid(boolean state) {
if (drawGrid != state) {
drawGrid = state;
showGridCheckBoxMenuItem.setSelected(drawGrid);
}
}
/**
* Should only be invoked on the GUI (Swing) thread.
*
* @param state true to set snap to grid on add, else false.
*/
@InvokeOnGuiThread
public void setSnapOnAdd(boolean state) {
if (snapToGridOnAdd != state) {
snapToGridOnAdd = state;
snapToGridOnAddCheckBoxMenuItem.setSelected(snapToGridOnAdd);
}
}
/**
* Should only be invoked on the GUI (Swing) thread.
*
* @param state true to set snap on move, else false.
*/
@InvokeOnGuiThread
public void setSnapOnMove(boolean state) {
if (snapToGridOnMove != state) {
snapToGridOnMove = state;
snapToGridOnMoveCheckBoxMenuItem.setSelected(snapToGridOnMove);
}
}
/**
* Should only be invoked on the GUI (Swing) thread.
*
* @param state true to set anti-aliasing flag on, else false.
*/
@InvokeOnGuiThread
public void setAntialiasingOn(boolean state) {
if (antialiasingOn != state) {
antialiasingOn = state;
// this may not be set up yet...
if (antialiasingOnCheckBoxMenuItem != null) {
antialiasingOnCheckBoxMenuItem.setSelected(antialiasingOn);
}
InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".antialiasingOn", antialiasingOn));
}
}
/**
*
* @param state true to set anti-aliasing flag on, else false.
*/
public void setDrawLayoutTracksLabel(boolean state) {
if (drawLayoutTracksLabel != state) {
drawLayoutTracksLabel = state;
// this may not be set up yet...
if (drawLayoutTracksLabelCheckBoxMenuItem != null) {
drawLayoutTracksLabelCheckBoxMenuItem.setSelected(drawLayoutTracksLabel);
}
InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".drawLayoutTracksLabel", drawLayoutTracksLabel));
}
}
// enable/disable using the "Extra" color to highlight the selected block
public void setHighlightSelectedBlock(boolean state) {
if (highlightSelectedBlockFlag != state) {
highlightSelectedBlockFlag = state;
// this may not be set up yet...
if (leToolBarPanel.highlightBlockCheckBox != null) {
leToolBarPanel.highlightBlockCheckBox.setSelected(highlightSelectedBlockFlag);
}
InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".highlightSelectedBlock", highlightSelectedBlockFlag));
// thread this so it won't break the AppVeyor checks
ThreadingUtil.newThread(() -> {
if (highlightSelectedBlockFlag) {
// use the "Extra" color to highlight the selected block
if (!highlightBlockInComboBox(leToolBarPanel.blockIDComboBox)) {
highlightBlockInComboBox(leToolBarPanel.blockContentsComboBox);
}
} else {
// undo using the "Extra" color to highlight the selected block
Block block = leToolBarPanel.blockIDComboBox.getSelectedItem();
highlightBlock(null);
leToolBarPanel.blockIDComboBox.setSelectedItem(block);
}
}).start();
}
}
//
// highlight the block selected by the specified combo Box
//
public boolean highlightBlockInComboBox(@Nonnull NamedBeanComboBox<Block> inComboBox) {
return highlightBlock(inComboBox.getSelectedItem());
}
/**
* highlight the specified block
*
* @param inBlock the block
* @return true if block was highlighted
*/
public boolean highlightBlock(@CheckForNull Block inBlock) {
boolean result = false; // assume failure (pessimist!)
if (leToolBarPanel.blockIDComboBox.getSelectedItem() != inBlock) {
leToolBarPanel.blockIDComboBox.setSelectedItem(inBlock);
}
LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class
);
Set<Block> l = leToolBarPanel.blockIDComboBox.getManager().getNamedBeanSet();
for (Block b : l) {
LayoutBlock lb = lbm.getLayoutBlock(b);
if (lb != null) {
boolean enable = ((inBlock != null) && b.equals(inBlock));
lb.setUseExtraColor(enable);
result |= enable;
}
}
return result;
}
/**
* highlight the specified layout block
*
* @param inLayoutBlock the layout block
* @return true if layout block was highlighted
*/
public boolean highlightLayoutBlock(@Nonnull LayoutBlock inLayoutBlock) {
return highlightBlock(inLayoutBlock.getBlock());
}
public void setTurnoutCircles(boolean state) {
if (turnoutCirclesWithoutEditMode != state) {
turnoutCirclesWithoutEditMode = state;
if (turnoutCirclesOnCheckBoxMenuItem != null) {
turnoutCirclesOnCheckBoxMenuItem.setSelected(turnoutCirclesWithoutEditMode);
}
}
}
public void setAutoBlockAssignment(boolean boo) {
if (autoAssignBlocks != boo) {
autoAssignBlocks = boo;
if (autoAssignBlocksCheckBoxMenuItem != null) {
autoAssignBlocksCheckBoxMenuItem.setSelected(autoAssignBlocks);
}
}
}
public void setTooltipsNotEdit(boolean state) {
if (tooltipsWithoutEditMode != state) {
tooltipsWithoutEditMode = state;
setTooltipSubMenu();
setTooltipsAlwaysOrNever();
}
}
public void setTooltipsInEdit(boolean state) {
if (tooltipsInEditMode != state) {
tooltipsInEditMode = state;
setTooltipSubMenu();
setTooltipsAlwaysOrNever();
}
}
private void setTooltipsAlwaysOrNever() {
tooltipsAlwaysOrNever = ((tooltipsInEditMode && tooltipsWithoutEditMode) ||
(!tooltipsInEditMode && !tooltipsWithoutEditMode));
}
private void setTooltipSubMenu() {
if (tooltipNoneMenuItem != null) {
tooltipNoneMenuItem.setSelected((!tooltipsInEditMode) && (!tooltipsWithoutEditMode));
tooltipAlwaysMenuItem.setSelected((tooltipsInEditMode) && (tooltipsWithoutEditMode));
tooltipInEditMenuItem.setSelected((tooltipsInEditMode) && (!tooltipsWithoutEditMode));
tooltipNotInEditMenuItem.setSelected((!tooltipsInEditMode) && (tooltipsWithoutEditMode));
}
}
// accessor routines for turnout size parameters
public void setTurnoutBX(double bx) {
turnoutBX = bx;
setDirty();
}
public double getTurnoutBX() {
return turnoutBX;
}
public void setTurnoutCX(double cx) {
turnoutCX = cx;
setDirty();
}
public double getTurnoutCX() {
return turnoutCX;
}
public void setTurnoutWid(double wid) {
turnoutWid = wid;
setDirty();
}
public double getTurnoutWid() {
return turnoutWid;
}
public void setXOverLong(double lg) {
xOverLong = lg;
setDirty();
}
public double getXOverLong() {
return xOverLong;
}
public void setXOverHWid(double hwid) {
xOverHWid = hwid;
setDirty();
}
public double getXOverHWid() {
return xOverHWid;
}
public void setXOverShort(double sh) {
xOverShort = sh;
setDirty();
}
public double getXOverShort() {
return xOverShort;
}
// reset turnout sizes to program defaults
private void resetTurnoutSize() {
turnoutBX = LayoutTurnout.turnoutBXDefault;
turnoutCX = LayoutTurnout.turnoutCXDefault;
turnoutWid = LayoutTurnout.turnoutWidDefault;
xOverLong = LayoutTurnout.xOverLongDefault;
xOverHWid = LayoutTurnout.xOverHWidDefault;
xOverShort = LayoutTurnout.xOverShortDefault;
setDirty();
}
public void setDirectTurnoutControl(boolean boo) {
useDirectTurnoutControl = boo;
useDirectTurnoutControlCheckBoxMenuItem.setSelected(useDirectTurnoutControl);
}
// TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
public boolean getDirectTurnoutControl() {
return useDirectTurnoutControl;
}
// final initialization routine for loading a LayoutEditor
public void setConnections() {
getLayoutTracks().forEach((lt) -> lt.setObjects(this));
getLEAuxTools().initializeBlockConnectivity();
log.debug("Initializing Block Connectivity for {}", getLayoutName());
// reset the panel changed bit
resetDirty();
}
// these are convenience methods to return rectangles
// to use when (hit point-in-rect testing
//
// compute the control point rect at inPoint
public @Nonnull
Rectangle2D layoutEditorControlRectAt(@Nonnull Point2D inPoint) {
return new Rectangle2D.Double(inPoint.getX() - SIZE,
inPoint.getY() - SIZE, SIZE2, SIZE2);
}
// compute the turnout circle control rect at inPoint
public @Nonnull
Rectangle2D layoutEditorControlCircleRectAt(@Nonnull Point2D inPoint) {
return new Rectangle2D.Double(inPoint.getX() - circleRadius,
inPoint.getY() - circleRadius, circleDiameter, circleDiameter);
}
/**
* Special internal class to allow drawing of layout to a JLayeredPane This
* is the 'target' pane where the layout is displayed
*/
@Override
public void paintTargetPanel(@Nonnull Graphics g) {
// Nothing to do here
// All drawing has been moved into LayoutEditorComponent
// which calls draw.
// This is so the layout is drawn at level three
// (above or below the Positionables)
}
// get selection rectangle
@Nonnull
public Rectangle2D getSelectionRect() {
double selX = Math.min(selectionX, selectionX + selectionWidth);
double selY = Math.min(selectionY, selectionY + selectionHeight);
return new Rectangle2D.Double(selX, selY,
Math.abs(selectionWidth), Math.abs(selectionHeight));
}
// set selection rectangle
public void setSelectionRect(@Nonnull Rectangle2D selectionRect) {
// selectionRect = selectionRect.createIntersection(MathUtil.zeroToInfinityRectangle2D);
selectionX = selectionRect.getX();
selectionY = selectionRect.getY();
selectionWidth = selectionRect.getWidth();
selectionHeight = selectionRect.getHeight();
// There's already code in the super class (Editor) to draw
// the selection rect... We just have to set _selectRect
_selectRect = MathUtil.rectangle2DToRectangle(selectionRect);
selectionRect = MathUtil.scale(selectionRect, getZoom());
JComponent targetPanel = getTargetPanel();
Rectangle targetRect = targetPanel.getVisibleRect();
// this will make it the size of the targetRect
// (effectively centering it onscreen)
Rectangle2D selRect2D = MathUtil.inset(selectionRect,
(selectionRect.getWidth() - targetRect.getWidth()) / 2.0,
(selectionRect.getHeight() - targetRect.getHeight()) / 2.0);
// don't let the origin go negative
selRect2D = selRect2D.createIntersection(MathUtil.zeroToInfinityRectangle2D);
Rectangle selRect = MathUtil.rectangle2DToRectangle(selRect2D);
if (!targetRect.contains(selRect)) {
targetPanel.scrollRectToVisible(selRect);
}
clearSelectionGroups();
selectionActive = true;
createSelectionGroups();
// redrawPanel(); // createSelectionGroups already calls this
}
public void setSelectRect(Rectangle rectangle) {
_selectRect = rectangle;
}
/*
// TODO: This compiles but I can't get the syntax correct to pass the (sub-)class
public List<LayoutTrack> getLayoutTracksOfClass(@Nonnull Class<LayoutTrack> layoutTrackClass) {
return getLayoutTracks().stream()
.filter(item -> item instanceof PositionablePoint)
.filter(layoutTrackClass::isInstance)
//.map(layoutTrackClass::cast) // TODO: Do we need this? if not dead-code-strip
.collect(Collectors.toList());
}
// TODO: This compiles but I can't get the syntax correct to pass the array of (sub-)classes
public List<LayoutTrack> getLayoutTracksOfClasses(@Nonnull List<Class<? extends LayoutTrack>> layoutTrackClasses) {
return getLayoutTracks().stream()
.filter(o -> layoutTrackClasses.contains(o.getClass()))
.collect(Collectors.toList());
}
// TODO: This compiles but I can't get the syntax correct to pass the (sub-)class
public List<LayoutTrack> getLayoutTracksOfClass(@Nonnull Class<? extends LayoutTrack> layoutTrackClass) {
return getLayoutTracksOfClasses(new ArrayList<>(Arrays.asList(layoutTrackClass)));
}
public List<PositionablePoint> getPositionablePoints() {
return getLayoutTracksOfClass(PositionablePoint);
}
*/
@Override
public @Nonnull
Stream<LayoutTrack> getLayoutTracksOfClass(Class<? extends LayoutTrack> layoutTrackClass) {
return getLayoutTracks().stream()
.filter(layoutTrackClass::isInstance)
.map(layoutTrackClass::cast);
}
@Override
public @Nonnull
Stream<LayoutTrackView> getLayoutTrackViewsOfClass(Class<? extends LayoutTrackView> layoutTrackViewClass) {
return getLayoutTrackViews().stream()
.filter(layoutTrackViewClass::isInstance)
.map(layoutTrackViewClass::cast);
}
@Override
public @Nonnull
List<PositionablePointView> getPositionablePointViews() {
return getLayoutTrackViewsOfClass(PositionablePointView.class)
.map(PositionablePointView.class::cast)
.collect(Collectors.toCollection(ArrayList::new));
}
@Override
public @Nonnull
List<PositionablePoint> getPositionablePoints() {
return getLayoutTracksOfClass(PositionablePoint.class)
.map(PositionablePoint.class::cast)
.collect(Collectors.toCollection(ArrayList::new));
}
public @Nonnull
List<LayoutSlipView> getLayoutSlipViews() {
return getLayoutTrackViewsOfClass(LayoutSlipView.class)
.map(LayoutSlipView.class::cast)
.collect(Collectors.toCollection(ArrayList::new));
}
@Override
public @Nonnull
List<LayoutSlip> getLayoutSlips() {
return getLayoutTracksOfClass(LayoutSlip.class)
.map(LayoutSlip.class::cast)
.collect(Collectors.toCollection(ArrayList::new));
}
@Override
public @Nonnull
List<TrackSegmentView> getTrackSegmentViews() {
return getLayoutTrackViewsOfClass(TrackSegmentView.class)
.map(TrackSegmentView.class::cast)
.collect(Collectors.toCollection(ArrayList::new));
}
@Override
public @Nonnull
List<TrackSegment> getTrackSegments() {
return getLayoutTracksOfClass(TrackSegment.class)
.map(TrackSegment.class::cast)
.collect(Collectors.toCollection(ArrayList::new));
}
public @Nonnull
List<LayoutTurnoutView> getLayoutTurnoutViews() { // this specifically does not include slips
return getLayoutTrackViews().stream() // next line excludes LayoutSlips
.filter((o) -> (!(o instanceof LayoutSlipView) && (o instanceof LayoutTurnoutView)))
.map(LayoutTurnoutView.class::cast)
.collect(Collectors.toCollection(ArrayList::new));
}
@Override
public @Nonnull
List<LayoutTurnout> getLayoutTurnouts() { // this specifically does not include slips
return getLayoutTracks().stream() // next line excludes LayoutSlips
.filter((o) -> (!(o instanceof LayoutSlip) && (o instanceof LayoutTurnout)))
.map(LayoutTurnout.class::cast)
.collect(Collectors.toCollection(ArrayList::new));
}
@Override
public @Nonnull
List<LayoutTurntable> getLayoutTurntables() {
return getLayoutTracksOfClass(LayoutTurntable.class)
.map(LayoutTurntable.class::cast)
.collect(Collectors.toCollection(ArrayList::new));
}
public @Nonnull
List<LayoutTurntableView> getLayoutTurntableViews() {
return getLayoutTrackViewsOfClass(LayoutTurntableView.class)
.map(LayoutTurntableView.class::cast)
.collect(Collectors.toCollection(ArrayList::new));
}
@Override
public @Nonnull
List<LevelXing> getLevelXings() {
return getLayoutTracksOfClass(LevelXing.class)
.map(LevelXing.class::cast)
.collect(Collectors.toCollection(ArrayList::new));
}
@Override
public @Nonnull
List<LevelXingView> getLevelXingViews() {
return getLayoutTrackViewsOfClass(LevelXingView.class)
.map(LevelXingView.class::cast)
.collect(Collectors.toCollection(ArrayList::new));
}
/**
* Read-only access to the list of LayoutTrack family objects. The returned
* list will throw UnsupportedOperationException if you attempt to modify
* it.
*
* @return unmodifiable copy of layout track list.
*/
@Override
@Nonnull
final public List<LayoutTrack> getLayoutTracks() {
return Collections.unmodifiableList(layoutTrackList);
}
public @Nonnull
List<LayoutTurnoutView> getLayoutTurnoutAndSlipViews() {
return getLayoutTrackViewsOfClass(LayoutTurnoutView.class
)
.map(LayoutTurnoutView.class::cast)
.collect(Collectors.toCollection(ArrayList::new));
}
@Override
public @Nonnull
List<LayoutTurnout> getLayoutTurnoutsAndSlips() {
return getLayoutTracksOfClass(LayoutTurnout.class
)
.map(LayoutTurnout.class::cast)
.collect(Collectors.toCollection(ArrayList::new));
}
/**
* Read-only access to the list of LayoutTrackView family objects. The
* returned list will throw UnsupportedOperationException if you attempt to
* modify it.
*
* @return unmodifiable copy of track views.
*/
@Override
@Nonnull
final public List<LayoutTrackView> getLayoutTrackViews() {
return Collections.unmodifiableList(layoutTrackViewList);
}
private final List<LayoutTrack> layoutTrackList = new ArrayList<>();
private final List<LayoutTrackView> layoutTrackViewList = new ArrayList<>();
private final Map<LayoutTrack, LayoutTrackView> trkToView = new HashMap<>();
private final Map<LayoutTrackView, LayoutTrack> viewToTrk = new HashMap<>();
// temporary
@Override
final public LayoutTrackView getLayoutTrackView(LayoutTrack trk) {
LayoutTrackView lv = trkToView.get(trk);
if (lv == null) {
log.warn("No View found for {} class {}", trk, trk.getClass());
throw new IllegalArgumentException("No View found: " + trk.getClass());
}
return lv;
}
// temporary
@Override
final public LevelXingView getLevelXingView(LevelXing xing) {
LayoutTrackView lv = trkToView.get(xing);
if (lv == null) {
log.warn("No View found for {} class {}", xing, xing.getClass());
throw new IllegalArgumentException("No View found: " + xing.getClass());
}
if (lv instanceof LevelXingView) {
return (LevelXingView) lv;
} else {
log.error("wrong type {} {} found {}", xing, xing.getClass(), lv);
}
throw new IllegalArgumentException("Wrong type: " + xing.getClass());
}
// temporary
@Override
final public LayoutTurnoutView getLayoutTurnoutView(LayoutTurnout to) {
LayoutTrackView lv = trkToView.get(to);
if (lv == null) {
log.warn("No View found for {} class {}", to, to.getClass());
throw new IllegalArgumentException("No View found: " + to);
}
if (lv instanceof LayoutTurnoutView) {
return (LayoutTurnoutView) lv;
} else {
log.error("wrong type {} {} found {}", to, to.getClass(), lv);
}
throw new IllegalArgumentException("Wrong type: " + to.getClass());
}
// temporary
@Override
final public LayoutTurntableView getLayoutTurntableView(LayoutTurntable to) {
LayoutTrackView lv = trkToView.get(to);
if (lv == null) {
log.warn("No View found for {} class {}", to, to.getClass());
throw new IllegalArgumentException("No matching View found: " + to);
}
if (lv instanceof LayoutTurntableView) {
return (LayoutTurntableView) lv;
} else {
log.error("wrong type {} {} found {}", to, to.getClass(), lv);
}
throw new IllegalArgumentException("Wrong type: " + to.getClass());
}
// temporary
final public LayoutSlipView getLayoutSlipView(LayoutSlip to) {
LayoutTrackView lv = trkToView.get(to);
if (lv == null) {
log.warn("No View found for {} class {}", to, to.getClass());
throw new IllegalArgumentException("No matching View found: " + to);
}
if (lv instanceof LayoutSlipView) {
return (LayoutSlipView) lv;
} else {
log.error("wrong type {} {} found {}", to, to.getClass(), lv);
}
throw new IllegalArgumentException("Wrong type: " + to.getClass());
}
// temporary
@Override
final public TrackSegmentView getTrackSegmentView(TrackSegment to) {
LayoutTrackView lv = trkToView.get(to);
if (lv == null) {
log.warn("No View found for {} class {}", to, to.getClass());
throw new IllegalArgumentException("No matching View found: " + to);
}
if (lv instanceof TrackSegmentView) {
return (TrackSegmentView) lv;
} else {
log.error("wrong type {} {} found {}", to, to.getClass(), lv);
}
throw new IllegalArgumentException("Wrong type: " + to.getClass());
}
// temporary
@Override
final public PositionablePointView getPositionablePointView(PositionablePoint to) {
LayoutTrackView lv = trkToView.get(to);
if (lv == null) {
log.warn("No View found for {} class {}", to, to.getClass());
throw new IllegalArgumentException("No matching View found: " + to);
}
if (lv instanceof PositionablePointView) {
return (PositionablePointView) lv;
} else {
log.error("wrong type {} {} found {}", to, to.getClass(), lv);
}
throw new IllegalArgumentException("Wrong type: " + to.getClass());
}
/**
* Add a LayoutTrack and LayoutTrackView to the list of LayoutTrack family
* objects.
*
* @param trk the layout track to add.
*/
@Override
final public void addLayoutTrack(@Nonnull LayoutTrack trk, @Nonnull LayoutTrackView v) {
log.trace("addLayoutTrack {}", trk);
if (layoutTrackList.contains(trk)) {
log.warn("LayoutTrack {} already being maintained", trk.getName());
}
layoutTrackList.add(trk);
layoutTrackViewList.add(v);
trkToView.put(trk, v);
viewToTrk.put(v, trk);
unionToPanelBounds(v.getBounds()); // temporary - this should probably _not_ be in the topological part
}
/**
* If item present, delete from the list of LayoutTracks and force a dirty
* redraw.
*
* @param trk the layout track to remove and redraw.
* @return true is item was deleted and a redraw done.
*/
final public boolean removeLayoutTrackAndRedraw(@Nonnull LayoutTrack trk) {
log.trace("removeLayoutTrackAndRedraw {}", trk);
if (layoutTrackList.contains(trk)) {
removeLayoutTrack(trk);
setDirty();
redrawPanel();
log.trace("removeLayoutTrackAndRedraw present {}", trk);
return true;
}
log.trace("removeLayoutTrackAndRedraw absent {}", trk);
return false;
}
/**
* If item present, delete from the list of LayoutTracks and force a dirty
* redraw.
*
* @param trk the layout track to remove.
*/
@Override
final public void removeLayoutTrack(@Nonnull LayoutTrack trk) {
log.trace("removeLayoutTrack {}", trk);
layoutTrackList.remove(trk);
LayoutTrackView v = trkToView.get(trk);
layoutTrackViewList.remove(v);
trkToView.remove(trk);
viewToTrk.remove(v);
}
/**
* Clear the list of layout tracks. Not intended for general use.
* <p>
*/
private void clearLayoutTracks() {
layoutTrackList.clear();
layoutTrackViewList.clear();
trkToView.clear();
viewToTrk.clear();
}
@Override
public @Nonnull
List<LayoutShape> getLayoutShapes() {
return layoutShapes;
}
public void sortLayoutShapesByLevel() {
layoutShapes.sort((lhs, rhs) -> {
// -1 == less than, 0 == equal, +1 == greater than
return Integer.signum(lhs.getLevel() - rhs.getLevel());
});
}
/**
* {@inheritDoc}
* <p>
* This implementation is temporary, using the on-screen points from the
* LayoutTrackViews via @{link LayoutEditor#getCoords}.
*/
@Override
public int computeDirection(LayoutTrack trk1, HitPointType h1, LayoutTrack trk2, HitPointType h2) {
return Path.computeDirection(
getCoords(trk1, h1),
getCoords(trk2, h2)
);
}
@Override
public int computeDirectionToCenter(@Nonnull LayoutTrack trk1, @Nonnull HitPointType h1, @Nonnull PositionablePoint p) {
return Path.computeDirection(
getCoords(trk1, h1),
getPositionablePointView(p).getCoordsCenter()
);
}
@Override
public int computeDirectionFromCenter(@Nonnull PositionablePoint p, @Nonnull LayoutTrack trk1, @Nonnull HitPointType h1) {
return Path.computeDirection(
getPositionablePointView(p).getCoordsCenter(),
getCoords(trk1, h1)
);
}
@Override
public boolean showAlignPopup(@Nonnull Positionable l) {
return false;
}
@Override
public void showToolTip(
@Nonnull Positionable selection,
@Nonnull JmriMouseEvent event) {
ToolTip tip = selection.getToolTip();
tip.setLocation(selection.getX() + selection.getWidth() / 2, selection.getY() + selection.getHeight());
setToolTip(tip);
}
@Override
public void addToPopUpMenu(
@Nonnull NamedBean nb,
@Nonnull JMenuItem item,
int menu) {
if ((nb == null) || (item == null)) {
return;
}
List<?> theList = null;
if (nb instanceof Sensor) {
theList = sensorList;
} else if (nb instanceof SignalHead) {
theList = signalList;
} else if (nb instanceof SignalMast) {
theList = signalMastList;
} else if (nb instanceof Block) {
theList = blockContentsLabelList;
} else if (nb instanceof Memory) {
theList = memoryLabelList;
} else if (nb instanceof GlobalVariable) {
theList = globalVariableLabelList;
}
if (theList != null) {
for (Object o : theList) {
PositionableLabel si = (PositionableLabel) o;
if ((si.getNamedBean() == nb) && (si.getPopupUtility() != null)) {
if (menu != Editor.VIEWPOPUPONLY) {
si.getPopupUtility().addEditPopUpMenu(item);
}
if (menu != Editor.EDITPOPUPONLY) {
si.getPopupUtility().addViewPopUpMenu(item);
}
}
}
} else if (nb instanceof Turnout) {
for (LayoutTurnoutView ltv : getLayoutTurnoutAndSlipViews()) {
if (ltv.getTurnout().equals(nb)) {
if (menu != Editor.VIEWPOPUPONLY) {
ltv.addEditPopUpMenu(item);
}
if (menu != Editor.EDITPOPUPONLY) {
ltv.addViewPopUpMenu(item);
}
}
}
}
}
@Override
public @Nonnull
String toString() {
return String.format("LayoutEditor: %s", getLayoutName());
}
@Override
public void vetoableChange(
@Nonnull PropertyChangeEvent evt)
throws PropertyVetoException {
NamedBean nb = (NamedBean) evt.getOldValue();
if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N
StringBuilder message = new StringBuilder();
message.append(Bundle.getMessage("VetoInUseLayoutEditorHeader", toString())); // NOI18N
message.append("<ul>");
boolean found = false;
if (nb instanceof SignalHead) {
if (containsSignalHead((SignalHead) nb)) {
found = true;
message.append("<li>");
message.append(Bundle.getMessage("VetoSignalHeadIconFound"));
message.append("</li>");
}
LayoutTurnout lt = finder.findLayoutTurnoutByBean(nb);
if (lt != null) {
message.append("<li>");
message.append(Bundle.getMessage("VetoSignalHeadAssignedToTurnout", lt.getTurnoutName()));
message.append("</li>");
}
PositionablePoint p = finder.findPositionablePointByBean(nb);
if (p != null) {
message.append("<li>");
// Need to expand to get the names of blocks
message.append(Bundle.getMessage("VetoSignalHeadAssignedToPoint"));
message.append("</li>");
}
LevelXing lx = finder.findLevelXingByBean(nb);
if (lx != null) {
message.append("<li>");
// Need to expand to get the names of blocks
message.append(Bundle.getMessage("VetoSignalHeadAssignedToLevelXing"));
message.append("</li>");
}
LayoutSlip ls = finder.findLayoutSlipByBean(nb);
if (ls != null) {
message.append("<li>");
message.append(Bundle.getMessage("VetoSignalHeadAssignedToLayoutSlip", ls.getTurnoutName()));
message.append("</li>");
}
} else if (nb instanceof Turnout) {
LayoutTurnout lt = finder.findLayoutTurnoutByBean(nb);
if (lt != null) {
found = true;
message.append("<li>");
message.append(Bundle.getMessage("VetoTurnoutIconFound"));
message.append("</li>");
}
for (LayoutTurnout t : getLayoutTurnouts()) {
if (t.getLinkedTurnoutName() != null) {
String uname = nb.getUserName();
if (nb.getSystemName().equals(t.getLinkedTurnoutName())
|| ((uname != null) && uname.equals(t.getLinkedTurnoutName()))) {
found = true;
message.append("<li>");
message.append(Bundle.getMessage("VetoLinkedTurnout", t.getTurnoutName()));
message.append("</li>");
}
}
if (nb.equals(t.getSecondTurnout())) {
found = true;
message.append("<li>");
message.append(Bundle.getMessage("VetoSecondTurnout", t.getTurnoutName()));
message.append("</li>");
}
}
LayoutSlip ls = finder.findLayoutSlipByBean(nb);
if (ls != null) {
found = true;
message.append("<li>");
message.append(Bundle.getMessage("VetoSlipIconFound", ls.getDisplayName()));
message.append("</li>");
}
for (LayoutTurntable lx : getLayoutTurntables()) {
if (lx.isTurnoutControlled()) {
for (int i = 0; i < lx.getNumberRays(); i++) {
if (nb.equals(lx.getRayTurnout(i))) {
found = true;
message.append("<li>");
message.append(Bundle.getMessage("VetoRayTurntableControl", lx.getId()));
message.append("</li>");
break;
}
}
}
}
}
if (nb instanceof SignalMast) {
if (containsSignalMast((SignalMast) nb)) {
message.append("<li>");
message.append("As an Icon");
message.append("</li>");
found = true;
}
String foundelsewhere = findBeanUsage(nb);
if (foundelsewhere != null) {
message.append(foundelsewhere);
found = true;
}
}
if (nb instanceof Sensor) {
int count = 0;
for (SensorIcon si : sensorList) {
if (nb.equals(si.getNamedBean())) {
count++;
found = true;
}
}
if (count > 0) {
message.append("<li>");
message.append(String.format("As an Icon %s times", count));
message.append("</li>");
}
String foundelsewhere = findBeanUsage(nb);
if (foundelsewhere != null) {
message.append(foundelsewhere);
found = true;
}
}
if (nb instanceof Memory) {
for (MemoryIcon si : memoryLabelList) {
if (nb.equals(si.getMemory())) {
found = true;
message.append("<li>");
message.append(Bundle.getMessage("VetoMemoryIconFound"));
message.append("</li>");
}
}
}
if (nb instanceof GlobalVariable) {
for (GlobalVariableIcon si : globalVariableLabelList) {
if (nb.equals(si.getGlobalVariable())) {
found = true;
message.append("<li>");
message.append(Bundle.getMessage("VetoGlobalVariableIconFound"));
message.append("</li>");
}
}
}
if (found) {
message.append("</ul>");
message.append(Bundle.getMessage("VetoReferencesWillBeRemoved")); // NOI18N
throw new PropertyVetoException(message.toString(), evt);
}
} else if ("DoDelete".equals(evt.getPropertyName())) { // NOI18N
if (nb instanceof SignalHead) {
removeSignalHead((SignalHead) nb);
removeBeanRefs(nb);
}
if (nb instanceof Turnout) {
LayoutTurnout lt = finder.findLayoutTurnoutByBean(nb);
if (lt != null) {
lt.setTurnout("");
}
for (LayoutTurnout t : getLayoutTurnouts()) {
if (t.getLinkedTurnoutName() != null) {
if (t.getLinkedTurnoutName().equals(nb.getSystemName())
|| ((nb.getUserName() != null) && t.getLinkedTurnoutName().equals(nb.getUserName()))) {
t.setLinkedTurnoutName("");
}
}
if (nb.equals(t.getSecondTurnout())) {
t.setSecondTurnout("");
}
}
for (LayoutSlip sl : getLayoutSlips()) {
if (nb.equals(sl.getTurnout())) {
sl.setTurnout("");
}
if (nb.equals(sl.getTurnoutB())) {
sl.setTurnoutB("");
}
}
for (LayoutTurntable lx : getLayoutTurntables()) {
if (lx.isTurnoutControlled()) {
for (int i = 0; i < lx.getNumberRays(); i++) {
if (nb.equals(lx.getRayTurnout(i))) {
lx.setRayTurnout(i, null, NamedBean.UNKNOWN);
}
}
}
}
}
if (nb instanceof SignalMast) {
removeBeanRefs(nb);
if (containsSignalMast((SignalMast) nb)) {
Iterator<SignalMastIcon> icon = signalMastList.iterator();
while (icon.hasNext()) {
SignalMastIcon i = icon.next();
if (i.getSignalMast().equals(nb)) {
icon.remove();
super.removeFromContents(i);
}
}
setDirty();
redrawPanel();
}
}
if (nb instanceof Sensor) {
removeBeanRefs(nb);
Iterator<SensorIcon> icon = sensorImage.iterator();
while (icon.hasNext()) {
SensorIcon i = icon.next();
if (nb.equals(i.getSensor())) {
icon.remove();
super.removeFromContents(i);
}
}
setDirty();
redrawPanel();
}
if (nb instanceof Memory) {
Iterator<MemoryIcon> icon = memoryLabelList.iterator();
while (icon.hasNext()) {
MemoryIcon i = icon.next();
if (nb.equals(i.getMemory())) {
icon.remove();
super.removeFromContents(i);
}
}
}
if (nb instanceof GlobalVariable) {
Iterator<GlobalVariableIcon> icon = globalVariableLabelList.iterator();
while (icon.hasNext()) {
GlobalVariableIcon i = icon.next();
if (nb.equals(i.getGlobalVariable())) {
icon.remove();
super.removeFromContents(i);
}
}
}
}
}
// private void rename(String inFrom, String inTo) {
//
// }
@Override
public void dispose() {
if (leToolBarPanel.sensorFrame != null) {
leToolBarPanel.sensorFrame.dispose();
leToolBarPanel.sensorFrame = null;
}
if (leToolBarPanel.signalFrame != null) {
leToolBarPanel.signalFrame.dispose();
leToolBarPanel.signalFrame = null;
}
if (leToolBarPanel.iconFrame != null) {
leToolBarPanel.iconFrame.dispose();
leToolBarPanel.iconFrame = null;
}
super.dispose();
}
// package protected
class TurnoutComboBoxPopupMenuListener implements PopupMenuListener {
private final NamedBeanComboBox<Turnout> comboBox;
private final List<Turnout> currentTurnouts;
public TurnoutComboBoxPopupMenuListener(NamedBeanComboBox<Turnout> comboBox, List<Turnout> currentTurnouts) {
this.comboBox = comboBox;
this.currentTurnouts = currentTurnouts;
}
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent event) {
// This method is called before the popup menu becomes visible.
log.debug("PopupMenuWillBecomeVisible");
Set<Turnout> l = new HashSet<>();
comboBox.getManager().getNamedBeanSet().forEach((turnout) -> {
if (!currentTurnouts.contains(turnout)) {
if (!validatePhysicalTurnout(turnout.getDisplayName(), null)) {
l.add(turnout);
}
}
});
comboBox.setExcludedItems(l);
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent event) {
// This method is called before the popup menu becomes invisible
log.debug("PopupMenuWillBecomeInvisible");
}
@Override
public void popupMenuCanceled(PopupMenuEvent event) {
// This method is called when the popup menu is canceled
log.debug("PopupMenuCanceled");
}
}
/**
* Create a listener that will exclude turnouts that are present in the
* current panel.
*
* @param comboBox The NamedBeanComboBox that contains the turnout list.
* @return A PopupMenuListener
*/
public TurnoutComboBoxPopupMenuListener newTurnoutComboBoxPopupMenuListener(NamedBeanComboBox<Turnout> comboBox) {
return new TurnoutComboBoxPopupMenuListener(comboBox, new ArrayList<>());
}
/**
* Create a listener that will exclude turnouts that are present in the
* current panel. The list of current turnouts are not excluded.
*
* @param comboBox The NamedBeanComboBox that contains the turnout
* list.
* @param currentTurnouts The turnouts to be left in the turnout list.
* @return A PopupMenuListener
*/
public TurnoutComboBoxPopupMenuListener newTurnoutComboBoxPopupMenuListener(NamedBeanComboBox<Turnout> comboBox, List<Turnout> currentTurnouts) {
return new TurnoutComboBoxPopupMenuListener(comboBox, currentTurnouts);
}
List<NamedBeanUsageReport> usageReport;
@Override
public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
usageReport = new ArrayList<>();
if (bean != null) {
usageReport = super.getUsageReport(bean);
// LE Specific checks
// Turnouts
findTurnoutUsage(bean);
// Check A, EB, EC for sensors, masts, heads
findPositionalUsage(bean);
// Level Crossings
findXingWhereUsed(bean);
// Track segments
findSegmentWhereUsed(bean);
}
return usageReport;
}
void findTurnoutUsage(NamedBean bean) {
for (LayoutTurnout turnout : getLayoutTurnoutsAndSlips()) {
String data = getUsageData(turnout);
if (bean.equals(turnout.getTurnout())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnout", data));
}
if (bean.equals(turnout.getSecondTurnout())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnout2", data));
}
if (isLBLockUsed(bean, turnout.getLayoutBlock())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data));
}
if (turnout.hasEnteringDoubleTrack()) {
if (isLBLockUsed(bean, turnout.getLayoutBlockB())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data));
}
if (isLBLockUsed(bean, turnout.getLayoutBlockC())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data));
}
if (isLBLockUsed(bean, turnout.getLayoutBlockD())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data));
}
}
if (bean.equals(turnout.getSensorA())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data));
}
if (bean.equals(turnout.getSensorB())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data));
}
if (bean.equals(turnout.getSensorC())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data));
}
if (bean.equals(turnout.getSensorD())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data));
}
if (bean.equals(turnout.getSignalAMast())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data));
}
if (bean.equals(turnout.getSignalBMast())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data));
}
if (bean.equals(turnout.getSignalCMast())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data));
}
if (bean.equals(turnout.getSignalDMast())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data));
}
if (bean.equals(turnout.getSignalA1())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
}
if (bean.equals(turnout.getSignalA2())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
}
if (bean.equals(turnout.getSignalA3())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
}
if (bean.equals(turnout.getSignalB1())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
}
if (bean.equals(turnout.getSignalB2())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
}
if (bean.equals(turnout.getSignalC1())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
}
if (bean.equals(turnout.getSignalC2())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
}
if (bean.equals(turnout.getSignalD1())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
}
if (bean.equals(turnout.getSignalD2())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
}
}
}
void findPositionalUsage(NamedBean bean) {
for (PositionablePoint point : getPositionablePoints()) {
String data = getUsageData(point);
if (bean.equals(point.getEastBoundSensor())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSensor", data));
}
if (bean.equals(point.getWestBoundSensor())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSensor", data));
}
if (bean.equals(point.getEastBoundSignalHead())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalHead", data));
}
if (bean.equals(point.getWestBoundSignalHead())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalHead", data));
}
if (bean.equals(point.getEastBoundSignalMast())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalMast", data));
}
if (bean.equals(point.getWestBoundSignalMast())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalMast", data));
}
}
}
void findSegmentWhereUsed(NamedBean bean) {
for (TrackSegment segment : getTrackSegments()) {
if (isLBLockUsed(bean, segment.getLayoutBlock())) {
String data = getUsageData(segment);
usageReport.add(new NamedBeanUsageReport("LayoutEditorSegmentBlock", data));
}
}
}
void findXingWhereUsed(NamedBean bean) {
for (LevelXing xing : getLevelXings()) {
String data = getUsageData(xing);
if (isLBLockUsed(bean, xing.getLayoutBlockAC())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorXingBlock", data));
}
if (isLBLockUsed(bean, xing.getLayoutBlockBD())) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorXingBlock", data));
}
if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTA)) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data));
}
if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTB)) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data));
}
if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTC)) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data));
}
if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTD)) {
usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data));
}
}
}
String getUsageData(LayoutTrack track) {
LayoutTrackView trackView = getLayoutTrackView(track);
Point2D point = trackView.getCoordsCenter();
if (trackView instanceof TrackSegmentView) {
TrackSegmentView segmentView = (TrackSegmentView) trackView;
point = new Point2D.Double(segmentView.getCentreSegX(), segmentView.getCentreSegY());
}
return String.format("%s :: x=%d, y=%d",
track.getClass().getSimpleName(),
Math.round(point.getX()),
Math.round(point.getY()));
}
boolean isLBLockUsed(NamedBean bean, LayoutBlock lblock) {
boolean result = false;
if (lblock != null) {
if (bean.equals(lblock.getBlock())) {
result = true;
}
}
return result;
}
boolean isUsedInXing(NamedBean bean, LevelXing xing, LevelXing.Geometry point) {
boolean result = false;
if (bean.equals(xing.getSensor(point))) {
result = true;
}
if (bean.equals(xing.getSignalHead(point))) {
result = true;
}
if (bean.equals(xing.getSignalMast(point))) {
result = true;
}
return result;
}
// initialize logging
private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutEditor.class);
}