001package jmri.jmrix.openlcb.swing.networktree;
002
003import java.awt.Dimension;
004import javax.swing.JTree;
005import javax.swing.event.TreeSelectionEvent;
006import javax.swing.tree.DefaultMutableTreeNode;
007
008import jmri.*;
009import jmri.jmrix.can.CanListener;
010import jmri.jmrix.can.CanMessage;
011import jmri.jmrix.can.CanReply;
012import jmri.jmrix.can.CanSystemConnectionMemo;
013import jmri.jmrix.can.swing.CanPanelInterface;
014import jmri.jmrix.openlcb.swing.ClientActions;
015import jmri.util.JmriJFrame;
016import jmri.util.swing.JmriPanel;
017
018import org.openlcb.Connection;
019import org.openlcb.MimicNodeStore;
020import org.openlcb.NodeID;
021import org.openlcb.OlcbInterface;
022import org.openlcb.SimpleNodeIdent;
023import org.openlcb.implementations.MemoryConfigurationService;
024import org.openlcb.swing.memconfig.MemConfigDescriptionPane;
025import org.openlcb.swing.memconfig.MemConfigReadWritePane;
026import org.openlcb.swing.networktree.NodeTreeRep;
027import org.openlcb.swing.networktree.TreePane;
028
029/**
030 * Frame displaying tree of OpenLCB nodes.
031 * <p>
032 * This uses a {@link CanSystemConnectionMemo} for access to various 
033 * org.openlcb.*
034 * OpenLCB context objects from the 
035 * <a href="https://github.com/openlcb/OpenLCB_Java">OpenLCB_Java project</a>.
036 * The {@link org.openlcb.MimicNodeStore} fills out the tree of known nodes.
037 * When requested to configure a node, that node's CDI is loaded
038 * and presented using a {@link org.openlcb.swing.networktree.TreePane}.
039 *
040 * @author Bob Jacobsen Copyright (C) 2009, 2010, 2012, 2024
041 */
042public class NetworkTreePane extends JmriPanel implements CanListener, CanPanelInterface {
043
044    public NetworkTreePane() {
045        super();
046    }
047
048    private transient CanSystemConnectionMemo memo;
049
050    @Override
051    public void initContext(Object context) {
052        if (context instanceof CanSystemConnectionMemo) {
053            initComponents((CanSystemConnectionMemo) context);
054        }
055    }
056
057    @Override
058    public void initComponents(CanSystemConnectionMemo memo) {
059        this.memo = memo;
060
061        memo.getTrafficController().addCanListener(this);
062
063        // add GUI components
064        setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS));
065
066        treePane = new TreePane(){
067            UserPreferencesManager pref = jmri.InstanceManager.getDefault(UserPreferencesManager.class);
068            String sortPreferenceName = NetworkTreePane.class.getName() + ".selectedSortOrder";
069
070            @Override
071            public void initComponents(MimicNodeStore store, final Connection connection,
072                            final NodeID node, final NodeTreeRep.SelectionKeyLoader loader) {
073                super.initComponents(store, connection, node, loader);
074                // finally handle sort-by JComboBox preferences WITHOUT setting preferences
075                var name = pref.getProperty(this.getClass().getName(), sortPreferenceName);
076                if (name == null) name = "BY_NAME";
077                SortOrder order;
078                try {
079                    order = SortOrder.valueOf((String)name);
080                } catch (IllegalArgumentException e) {
081                    order = SortOrder.BY_NAME;
082                }
083                super.setSortOrder(order);
084                // and do it a little later to make sure the table has been shown
085                final var localOrder = order;
086                jmri.util.ThreadingUtil.runOnLayoutDelayed( () -> {
087                    super.setSortOrder(localOrder);
088                }, 750
089                );
090            }
091            
092            // This overrides setOrder to preserve the order
093            @Override
094            public void setSortOrder(SortOrder order) {
095                pref.setProperty(this.getClass().getName(), sortPreferenceName, order.name());
096                super.setSortOrder(order);
097            }
098        };
099        treePane.setPreferredSize(new Dimension(300, 300));
100
101        treePane.initComponents(memo.get(MimicNodeStore.class), memo.get(Connection.class), memo.get(NodeID.class), new ActionLoader(memo)
102        );
103        add(treePane);
104
105        treePane.addTreeSelectionListener((TreeSelectionEvent e) -> {
106            JTree tree = (JTree) e.getSource();
107            DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
108
109            if (node == null) {
110                return;
111            }
112
113            if (node.getUserObject() instanceof NodeTreeRep.SelectionKey) {
114                ((NodeTreeRep.SelectionKey) node.getUserObject()).select(node);
115            }
116        });
117    }
118
119    TreePane treePane;
120
121    @Override
122    public String getTitle() {
123        if (memo != null) {
124            return (memo.getUserName() + " Network Tree");
125        }
126        return "LCC / OpenLCB Network Tree";
127    }
128
129    @Override
130    public void dispose() {
131        memo.getTrafficController().removeCanListener(this);
132    }
133
134    @Override
135    public synchronized void message(CanMessage l) {  // receive a message and log it
136    }
137
138    @Override
139    public synchronized void reply(CanReply l) {  // receive a reply and log it
140    }
141
142    //private final static Logger log = LoggerFactory.getLogger(NetworkTreePane.class);
143
144    /**
145     * Nested class to open specific windows when proper tree element is picked.
146     */
147    private static class ActionLoader extends NodeTreeRep.SelectionKeyLoader {
148
149        private final ClientActions actions;
150
151        ActionLoader(CanSystemConnectionMemo memo) {
152            OlcbInterface iface = memo.get(OlcbInterface.class);
153            actions = new ClientActions(iface, memo);
154            this.store = iface.getNodeStore();
155            this.mcs = iface.getMemoryConfigurationService();
156        }
157
158        final MimicNodeStore store;
159        final MemoryConfigurationService mcs;
160
161        @Override
162        public NodeTreeRep.SelectionKey cdiKey(String name, NodeID node) {
163            return new NodeTreeRep.SelectionKey(name, node) {
164                @Override
165                public void select(DefaultMutableTreeNode rep) {
166                    MimicNodeStore.NodeMemo memo = store.findNode(node);
167                    SimpleNodeIdent ident = memo.getSimpleNodeIdent();
168                    StringBuilder description = new StringBuilder();
169                    if (ident.getUserName() != null) {
170                        description.append(ident.getUserName());
171                    }
172                    if (ident.getUserDesc() != null && ident.getUserDesc().length() > 0) {
173                        if (description.length() > 0) {
174                            description.append(" - ");
175                        }
176                        description.append(ident.getUserDesc());
177                    }
178                    if (description.length() == 0) {
179                        if (ident.getMfgName() != null && ident.getMfgName().length() > 0) {
180                            description.append(ident.getMfgName());
181                        }
182                        if (ident.getModelName() != null && ident.getModelName().length() > 0) {
183                            if (description.length() > 0) {
184                                description.append(" - ");
185                            }
186                            description.append(ident.getModelName());
187                        }
188                    }
189                    if (description.length() == 0) {
190                        description.append(node.toString());
191                    } else {
192                        description.append(" (");
193                        description.append(node.toString());
194                        description.append(")");
195                    }
196                    openCdiPane(node, description.toString());
197                }
198            };
199        }
200
201        @Override
202        public NodeTreeRep.SelectionKey configurationKey(String name, NodeID node) {
203            return new NodeTreeRep.SelectionKey(name, node) {
204                @Override
205                public void select(DefaultMutableTreeNode rep) {
206                    openConfigurePane(node);
207                }
208            };
209        }
210
211        void openConfigurePane(NodeID node) {
212            {
213                JmriJFrame f = new JmriJFrame();
214                f.setTitle("Configuration Capabilities " + node);
215                MemConfigDescriptionPane mc = new MemConfigDescriptionPane(node, store, mcs);
216                f.add(mc);
217                mc.initComponents();
218                f.pack();
219                f.setVisible(true);
220            }
221            {
222                JmriJFrame f = new JmriJFrame();
223                f.setTitle("Configuration R/W Tool " + node);
224                MemConfigReadWritePane mc = new MemConfigReadWritePane(node, store, mcs);
225                f.add(mc);
226                mc.initComponents();
227                f.pack();
228                f.setVisible(true);
229            }
230        }
231
232        public void openCdiPane(final NodeID destNode, String description) {
233            actions.openCdiWindow(destNode, description);
234        }
235    }
236
237}