001package jmri.jmrit.symbolicprog; 002 003import java.awt.event.ActionEvent; 004import java.beans.PropertyChangeEvent; 005import java.beans.PropertyChangeListener; 006 007import javax.swing.AbstractAction; 008import javax.swing.JLabel; 009 010import jmri.jmrit.roster.RosterEntry; 011import jmri.jmrit.symbolicprog.tabbedframe.PaneProgFrame; 012import jmri.jmrix.can.CanSystemConnectionMemo; 013import jmri.util.swing.JmriJOptionPane; 014 015import org.openlcb.OlcbInterface; 016import org.openlcb.NodeID; 017import org.openlcb.cdi.impl.ConfigRepresentation; 018 019/** 020 * Action to download the function labels from a TCS CS-105 to a roster entry 021 * 022 * @author Bob Jacobsen Copyright (C) 2003, 2023 023 * @author Dave Heap Copyright (C) 2015 024 */ 025public class TcsDownloadAction extends AbstractAction implements PropertyChangeListener { 026 027 public TcsDownloadAction(String actionName, CvTableModel pModel, VariableTableModel vModel, PaneProgFrame pParent, JLabel pStatus, RosterEntry re) { 028 super(actionName); 029 this.cvTable = pModel; 030 this.vModel = vModel; 031 this.rosterEntry = re; 032 this.frame = pParent; 033 } 034 035 // will this be enabled if created? 036 static public boolean willBeEnabled() { 037 // see if there's an openlcb connection 038 var cscm = getSystemConnectionMemo(); 039 if (cscm == null) { 040 return false; 041 } 042 if (cscm.get(org.openlcb.NodeID.class) == null) { 043 return false; 044 } 045 return true; 046 } 047 048 static CanSystemConnectionMemo getSystemConnectionMemo() { 049 return jmri.InstanceManager.getNullableDefault(CanSystemConnectionMemo.class); 050 } 051 052 PaneProgFrame frame; 053 RosterEntry rosterEntry; 054 CvTableModel cvTable; 055 VariableTableModel vModel; 056 ConfigRepresentation configRep; 057 058 @Override 059 public void actionPerformed(ActionEvent e) { 060 boolean isLong = cvTable.holdsLongAddress(); 061 int addr = cvTable.holdsAddress(); 062 log.debug("computed address is {} long: {}", addr, isLong); 063 064 // Create the train's node ID from current values in GUI 065 byte upperAddressByte = (byte) (isLong ? (192+(addr>>8)) : 0); 066 byte lowerAddressByte = (byte) (addr & 0xFF); 067 var nodeID = new NodeID(new byte[]{6,1,0,0,upperAddressByte, lowerAddressByte}); 068 log.debug("node ID {}", nodeID); 069 070 // check for Node ID already known 071 var nodeStore = getSystemConnectionMemo().get(org.openlcb.MimicNodeStore.class); 072 var nodeMemo = nodeStore.findNode(nodeID); 073 if (nodeMemo == null) { 074 JmriJOptionPane.showMessageDialog(frame, "Entry "+addr+" not found in CS-105, canceling"); 075 return; 076 } 077 078 // get the CD/CDI information 079 configRep = new ConfigRepresentation(getSystemConnectionMemo().get(OlcbInterface.class),nodeID); 080 configRep.addPropertyChangeListener(this); 081 082 } 083 084 @Override 085 public void propertyChange(PropertyChangeEvent event) { 086 switch (event.getPropertyName()) { 087 case ConfigRepresentation.UPDATE_STATE : 088 // Normal. Indicates that the load is proceeding. 089 case ConfigRepresentation.UPDATE_REP : 090 // Normal, CDI is read in, loading caches next 091 return; 092 case ConfigRepresentation.UPDATE_CACHE_COMPLETE : 093 log.debug("CDI read done"); 094 095 // look for values 096 processValuesToGUI(); 097 return; 098 default: 099 log.error("Unexpected PropertyChangeEvent {}", event); 100 return; 101 } 102 } 103 104 /** 105 * Construct and execute a listener that processses 106 * the relevant CDI elements into the Roster and Function Label 107 * GUI elements. 108 */ 109 void processValuesToGUI() { 110 configRep.visit(new ConfigRepresentation.Visitor() { 111 @Override 112 public void visitString(ConfigRepresentation.StringEntry e) { 113 log.trace("String entry {} is {}", e.key, e.getValue()); 114 115 if (e.key.startsWith("Train.User Description")) { 116 log.info("setComment {}", e.getValue()); 117 frame.getRosterPane().setComment(e.getValue()); 118 } else if (e.key.startsWith("Train.Functions")) { 119 int index = getNumberField(e.key); 120 if (index == -1) { 121 log.warn("Unexpected format \"{}\"", e.key); 122 return; 123 } 124 if (e.key.endsWith("Description")) { 125 String value = e.getValue(); 126 if (value==null) { 127 value = ""; 128 } 129 // Display has already written contents 130 // to this. If content here is empty, we defer to that; 131 // if there are content here, overrides what Display wrote. 132 if (!value.isEmpty()) { 133 frame.getFnLabelPane().getLabel(index+1).setText(value); 134 log.trace("Description sets {} {} {}", index, e.getValue(), value); 135 } 136 } else { 137 log.warn("Unexpected content \"{}\"", e.key); 138 } 139 } 140 } 141 142 // TODO: Have to update the Function Pane contents on every change 143 // so that the data is present for viewing and saving 144 @Override 145 public void visitInt(ConfigRepresentation.IntegerEntry e) { 146 log.trace("Integer entry {} is {}", e.key, e.getValue()); 147 148 // is this the last entry? 149 if (e.key.startsWith("Train.Delete From Roster")) { 150 // TODO: This is firing much too soon 151 JmriJOptionPane.showMessageDialog(frame, "Download complete."); 152 } else if (e.key.startsWith("Train.Functions")) { 153 int index = getNumberField(e.key); 154 if (index == -1) { 155 log.warn("Unexpected format \"{}\"", e.key); 156 return; 157 } 158 if (e.key.endsWith(".Momentary")) { 159 boolean lockable = (e.getValue() == 0); 160 frame.getFnLabelPane().getLockable(index+1).setSelected(lockable); 161 } else if (e.key.endsWith(".Consist Behavior")) { 162 // process consist bit 163 // first, see if function variable exists 164 var variable = vModel.findVar("Consist Address Active For F"+(index+1)); 165 if (variable != null) { 166 // it exists, so we transfer that to the consist info 167 int value = (int)e.getValue(); 168 variable.setIntValue(value); 169 log.trace("Set Consist Address Active For F{} to {}", (index+1), value); 170 } else { 171 log.trace("Variable Consist Address Active For F{} not found", (index+1) ); 172 } 173 } else if (e.key.endsWith(".Display")) { 174 // do a reverse lookup and store every time, 175 // will be overwritten by Description if needed 176 var description = TcsImporter.unpackDescription("", ""+e.getValue()); 177 log.trace("Display sets {} {} {}", index, e.getValue(), description); 178 frame.getFnLabelPane().getLabel(index+1).setText(description); 179 } else { 180 log.warn("Unexpected content \"{}\"", e.key); 181 } 182 } 183 } 184 185 @Override 186 public void visitEvent(ConfigRepresentation.EventEntry e) { 187 log.trace("Event entry {} is {}", e.key, e.getValue()); 188 } 189 }); 190 } 191 192 // Extract the number from e.g. Train.Functions(25).Momentary 193 static int getNumberField(String value) { 194 int first = value.indexOf("("); 195 int last = value.indexOf(")"); 196 if (first > 0 && last > 0 && last > first + 1) { 197 var digits = value.substring(first+1, last); 198 return Integer.parseInt(digits); 199 } 200 return -1; 201 } 202 203 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TcsDownloadAction.class); 204 205}