001package jmri.jmrit.logixng.tools.swing;
002
003import java.awt.*;
004import java.awt.event.*;
005import java.beans.PropertyChangeEvent;
006import java.beans.PropertyChangeListener;
007import java.util.*;
008import java.util.List;
009import java.util.concurrent.atomic.AtomicBoolean;
010
011import javax.annotation.Nonnull;
012import javax.swing.*;
013import javax.swing.event.TreeModelEvent;
014import javax.swing.event.TreeModelListener;
015import javax.swing.tree.TreePath;
016
017import jmri.jmrit.logixng.FemaleSocket;
018import jmri.InstanceManager;
019import jmri.jmrit.logixng.*;
020import jmri.jmrit.logixng.Module;
021import jmri.jmrit.logixng.implementation.DefaultSymbolTable;
022import jmri.jmrit.logixng.tools.debugger.AbstractDebuggerMaleSocket;
023import jmri.jmrit.logixng.tools.debugger.Debugger;
024import jmri.jmrit.logixng.tools.swing.TreePane.FemaleSocketDecorator;
025import jmri.util.JmriJFrame;
026
027/**
028 * Editor of ConditionalNG
029 *
030 * @author Daniel Bergqvist 2018
031 */
032public final class ConditionalNGDebugger extends JmriJFrame implements PropertyChangeListener {
033
034    private static final int panelWidth = 700;
035    private static final int panelHeight = 500;
036
037    private final Debugger _debugger = InstanceManager.getDefault(Debugger.class);
038    private final JPanel _currentTreePanePanel;
039    private final TreePane _conditionalNG_TreePane;
040    private TreePane _currentTreePane;
041    private final List<TreePane> _treePanes = new ArrayList<>();
042    private final JMenuItem _execute;
043    private final JMenuItem _runItem;
044    private final JMenuItem _stepOverItem;
045    private final JMenuItem _stepIntoItem;
046    private final FemaleSocketDecorator _decorator;
047    private final ConditionalNG _conditionalNG;
048    private Deque<Module> _lastModuleStack = new LinkedList<>();
049    private Module _currentModule;
050    private AbstractDebuggerMaleSocket _currentMaleSocket;
051    private State _currentState = State.None;
052    private boolean _run = false;
053    private MaleSocket _rootSocket;
054    private final JLabel _actionExpressionInfoLabel = new JLabel();
055
056    private final Object _lock = new Object();
057    private boolean _continue = false;
058
059    private final DebuggerSymbolTableModel _symbolTableModel;
060
061    /**
062     * Maintain a list of listeners -- normally only one.
063     */
064    private final List<ConditionalNGEventListener> listenerList = new ArrayList<>();
065
066    /**
067     * This contains a list of commands to be processed by the listener
068     * recipient.
069     */
070    final Map<String, String> logixNGData = new HashMap<>();
071
072    /**
073     * Construct a ConditionalEditor.
074     *
075     * @param conditionalNG the ConditionalNG to be edited
076     */
077    public ConditionalNGDebugger(@Nonnull ConditionalNG conditionalNG) {
078
079        _conditionalNG = conditionalNG;
080
081        _decorator = (FemaleSocket femaleSocket, JPanel panel) -> {
082            if (femaleSocket.isConnected()) {
083                MaleSocket maleSocket = femaleSocket.getConnectedSocket();
084                AbstractDebuggerMaleSocket debugMaleSocket =
085                        (AbstractDebuggerMaleSocket) maleSocket.find(AbstractDebuggerMaleSocket.class);
086                if (debugMaleSocket == null) throw new RuntimeException("AbstractDebuggerMaleSocket is not found");
087                boolean breakpointBefore = debugMaleSocket.getBreakpointBefore();
088                boolean breakpointAfter = debugMaleSocket.getBreakpointAfter();
089                if (breakpointBefore || breakpointAfter) {
090                    JPanel newPanel = new JPanel();
091                    newPanel.setBorder(BorderFactory.createMatteBorder(
092                            breakpointBefore ? 5 : 1,
093                            5,
094                            breakpointAfter ? 5 : 1,
095                            1, Color.red));
096                    newPanel.add(panel);
097                    panel = newPanel;
098                }
099            }
100            if (_currentMaleSocket != null) {
101                Base parent = _currentMaleSocket.getParent();
102                while ((parent != null) && (!(parent instanceof FemaleSocket))) {
103                    parent = parent.getParent();
104                }
105                if (parent == femaleSocket) {
106                    JPanel newPanel = new JPanel();
107                    switch (_currentState) {
108                        case Before:
109                            newPanel.setBorder(BorderFactory.createMatteBorder(8, 5, 1, 1, Color.black));
110                            break;
111                        case After:
112                            newPanel.setBorder(BorderFactory.createMatteBorder(1, 5, 8, 1, Color.black));
113                            break;
114                        default:
115                            // Return without adding a border
116                            return panel;
117                    }
118                    newPanel.add(panel);
119                    return newPanel;
120                }
121            }
122            return panel;
123        };
124
125        _conditionalNG_TreePane = new TreePane(conditionalNG.getFemaleSocket());
126        _conditionalNG_TreePane.initComponents(_decorator);
127
128        _currentTreePanePanel = new JPanel(new CardLayout());
129
130        _currentTreePane = _conditionalNG_TreePane;
131        _currentTreePanePanel.add(_conditionalNG.getSystemName(), _conditionalNG_TreePane);
132        _treePanes.add(_conditionalNG_TreePane);
133
134        // build menu
135        JMenuBar menuBar = new JMenuBar();
136
137        JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile"));
138        JMenuItem closeWindowItem = new JMenuItem(Bundle.getMessage("CloseWindow"));
139        closeWindowItem.addActionListener((ActionEvent e) -> {
140            dispose();
141        });
142        fileMenu.add(closeWindowItem);
143        menuBar.add(fileMenu);
144
145        JMenu debugMenu = new JMenu(Bundle.getMessage("Debug_MenuDebug"));
146
147        _execute = new JMenuItem(Bundle.getMessage("Debug_MenuItem_Execute"));
148        _execute.addActionListener((event) -> { _conditionalNG.execute(); });
149//        _execute.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F7, ActionEvent.CTRL_MASK));
150        debugMenu.add(_execute);
151
152        debugMenu.addSeparator();
153
154        _runItem = new JMenuItem(Bundle.getMessage("Debug_MenuItem_Run"));
155        _runItem.addActionListener((event) -> { doRun(); });
156        _runItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F7, ActionEvent.CTRL_MASK));
157        _runItem.setEnabled(false);
158        debugMenu.add(_runItem);
159
160        _stepOverItem = new JMenuItem(Bundle.getMessage("Debug_MenuItem_StepOver"));
161        _stepOverItem.addActionListener((ActionEvent e) -> { doStepOver(); });
162        _stepOverItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F7, ActionEvent.SHIFT_MASK));
163        _stepOverItem.setEnabled(false);
164        debugMenu.add(_stepOverItem);
165
166        _stepIntoItem = new JMenuItem(Bundle.getMessage("Debug_MenuItem_StepInto"));
167        _stepIntoItem.addActionListener((ActionEvent e) -> { doStepInto(); });
168        _stepIntoItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F7, 0));
169        _stepIntoItem.setEnabled(false);
170        debugMenu.add(_stepIntoItem);
171
172        menuBar.add(debugMenu);
173
174        setJMenuBar(menuBar);
175//        addHelpMenu("package.jmri.jmrit.operations.Operations_Settings", true); // NOI18N
176
177        setTitle((Module)null);
178
179        JPanel actionExpressionInfo = new JPanel();
180        actionExpressionInfo.add(_actionExpressionInfoLabel);
181        JScrollPane actionExpressionInfoScrollPane = new JScrollPane(actionExpressionInfo);
182        actionExpressionInfoScrollPane.setPreferredSize(new Dimension(400, 200));
183
184
185        JTable table = new JTable();
186        _symbolTableModel = new DebuggerSymbolTableModel();
187        table.setModel(_symbolTableModel);
188        JScrollPane variableScrollPane = new JScrollPane(table);
189
190
191        JSplitPane variableSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
192                           actionExpressionInfoScrollPane, variableScrollPane);
193        variableSplitPane.setOneTouchExpandable(true);
194        variableSplitPane.setDividerLocation(50);
195
196
197        JPanel watchPanel = new JPanel();
198        JScrollPane watchScrollPane = new JScrollPane(watchPanel);
199
200        JSplitPane watchSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
201                           variableSplitPane, watchScrollPane);
202        watchSplitPane.setOneTouchExpandable(true);
203        watchSplitPane.setDividerLocation(150);
204
205        // Provide minimum sizes for the two components in the split pane
206        Dimension minimumWatchSize = new Dimension(100, 150);
207        variableScrollPane.setMinimumSize(minimumWatchSize);
208        watchScrollPane.setMinimumSize(minimumWatchSize);
209
210        JSplitPane mainSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
211                           _currentTreePanePanel, watchSplitPane);
212        mainSplitPane.setOneTouchExpandable(true);
213        mainSplitPane.setDividerLocation(150);
214
215        // Provide minimum sizes for the two components in the split pane
216        Dimension minimumMainSize = new Dimension(100, 50);
217        _currentTreePanePanel.setMinimumSize(minimumMainSize);
218        watchSplitPane.setMinimumSize(minimumMainSize);
219
220        // add panels
221        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
222        getContentPane().add(mainSplitPane);
223
224        initMinimumSize(new Dimension(panelWidth, panelHeight));
225
226        _debugger.addPropertyChangeListener(this);
227        _debugger.setBreak(true);
228        _debugger.activateDebugger(conditionalNG);
229
230        PopupMenu popup = new PopupMenu();
231        popup.init();
232    }
233
234    private void setTitle(Module module) {
235        if (module != null) {
236            if (_conditionalNG.getUserName() == null) {
237                if (module.getUserName() == null) {
238                    setTitle(Bundle.getMessage("TitleDebugConditionalNG_Module",
239                            _conditionalNG.getSystemName(),
240                            module.getSystemName()));
241                } else {
242                    setTitle(Bundle.getMessage("TitleDebugConditionalNG_Module",
243                            _conditionalNG.getSystemName(),
244                            module.getSystemName(), module.getUserName()));
245                }
246            } else {
247                if (module.getUserName() == null) {
248                    setTitle(Bundle.getMessage("TitleDebugConditionalNG2_Module",
249                            _conditionalNG.getSystemName(), _conditionalNG.getUserName(),
250                            module.getSystemName()));
251                } else {
252                    setTitle(Bundle.getMessage("TitleDebugConditionalNG2_Module",
253                            _conditionalNG.getSystemName(), _conditionalNG.getUserName(),
254                            module.getSystemName(), module.getUserName()));
255                }
256            }
257        } else {
258            if (_conditionalNG.getUserName() == null) {
259                setTitle(Bundle.getMessage("TitleDebugConditionalNG", _conditionalNG.getSystemName()));
260            } else {
261                setTitle(Bundle.getMessage("TitleDebugConditionalNG2", _conditionalNG.getSystemName(), _conditionalNG.getUserName()));
262            }
263        }
264    }
265
266    private void doStepOver() {
267        AbstractDebuggerMaleSocket maleSocket = _currentMaleSocket;
268        if ((_currentState == State.After) && (_rootSocket == _currentMaleSocket)) {
269            _run = false;
270            _runItem.setEnabled(false);
271        }
272        _currentMaleSocket.setStepInto(false);
273        _currentMaleSocket = null;
274        _currentState = State.None;
275        _stepOverItem.setEnabled(false);
276        _stepIntoItem.setEnabled(false);
277        _currentTreePane.updateTree(maleSocket);
278        synchronized(_lock) {
279            _continue = true;
280            _lock.notify();
281        }
282    }
283
284    private void doStepInto() {
285        AbstractDebuggerMaleSocket maleSocket = _currentMaleSocket;
286        if ((_currentState == State.After) && (_rootSocket == _currentMaleSocket)) {
287            _run = false;
288            _runItem.setEnabled(false);
289        }
290        _currentMaleSocket.setStepInto(true);
291        _currentMaleSocket = null;
292        _currentState = State.None;
293        _stepOverItem.setEnabled(false);
294        _stepIntoItem.setEnabled(false);
295        _currentTreePane.updateTree(maleSocket);
296        synchronized(_lock) {
297            _continue = true;
298            _lock.notify();
299        }
300    }
301
302    private void doRun() {
303        _run = true;
304
305        if (_currentMaleSocket != null) {
306            AbstractDebuggerMaleSocket maleSocket = _currentMaleSocket;
307            if ((_currentState == State.After) && (_rootSocket == _currentMaleSocket)) {
308                _run = false;
309                _runItem.setEnabled(false);
310            }
311            _currentMaleSocket.setStepInto(false);
312            _currentMaleSocket = null;
313            _currentState = State.None;
314            _stepOverItem.setEnabled(false);
315            _stepIntoItem.setEnabled(false);
316            _currentTreePane.updateTree(maleSocket);
317            synchronized(_lock) {
318                _continue = true;
319                _lock.notify();
320            }
321        }
322    }
323
324    public void initMinimumSize(Dimension dimension) {
325        setMinimumSize(dimension);
326        pack();
327        setVisible(true);
328    }
329
330    /** {@inheritDoc} */
331    @Override
332    public void windowClosed(WindowEvent e) {
333        doRun();    // Ensure the ConditionalNG is not waiting for us
334        _debugger.removePropertyChangeListener(this);
335        _debugger.deActivateDebugger();
336        logixNGData.clear();
337        logixNGData.put("Finish", _conditionalNG.getSystemName());  // NOI18N
338        fireLogixNGEvent();
339    }
340
341    public void addLogixNGEventListener(ConditionalNGEventListener listener) {
342        listenerList.add(listener);
343    }
344
345    /**
346     * Notify the listeners to check for new data.
347     */
348    void fireLogixNGEvent() {
349        for (ConditionalNGEventListener l : listenerList) {
350            l.conditionalNGEventOccurred();
351        }
352    }
353
354    @Override
355    public void propertyChange(PropertyChangeEvent evt) {
356        if (Debugger.STEP_BEFORE.equals(evt.getPropertyName())
357                || Debugger.STEP_AFTER.equals(evt.getPropertyName())) {
358
359            String infoString = "";
360            _currentMaleSocket = (AbstractDebuggerMaleSocket) evt.getNewValue();
361            if (_rootSocket == null) _rootSocket = _currentMaleSocket;
362
363//            System.out.format("propertyChange: %s, %s, run: %b, currentState: %s, BP before: %b, BP after: %b%n", evt.getPropertyName(), ((MaleSocket)evt.getNewValue()).getLongDescription(), _run, _currentState.name(), _currentMaleSocket.getBreakpointBefore(), _currentMaleSocket.getBreakpointAfter());
364//            System.out.format("propertyChange: current: %s, root: %s%n", _currentMaleSocket, _rootSocket);
365
366            AtomicBoolean enableMenuItems = new AtomicBoolean(true);
367
368
369            Module module = _currentMaleSocket.getModule();
370
371            // Have we either entered or exited a Module?
372            if (module != _currentModule) {
373                setTitle(module);
374                if ( !_lastModuleStack.isEmpty() && module == _lastModuleStack.peek()) {
375                    _currentTreePanePanel.remove(_currentTreePane);
376                    _treePanes.remove(_treePanes.size()-1);
377                    _currentTreePane = _treePanes.get(_treePanes.size()-1);
378                    if (module != null) {
379                        ((CardLayout)_currentTreePanePanel.getLayout())
380                                .show(_currentTreePanePanel, module.getSystemName());
381                    } else {
382                        ((CardLayout)_currentTreePanePanel.getLayout())
383                                .show(_currentTreePanePanel, _conditionalNG.getSystemName());
384                    }
385                    _lastModuleStack.pop();
386                } else {    // Enter a module
387                    if (module == null) throw new NullPointerException("module is null");
388                    _lastModuleStack.push(_currentModule);
389                    _currentTreePane = new TreePane(module.getRootSocket());
390                    _currentTreePane.initComponents(_decorator);
391                    _treePanes.add(_currentTreePane);
392                    _currentTreePanePanel.add(module.getSystemName(), _currentTreePane);
393                    ((CardLayout)_currentTreePanePanel.getLayout())
394                            .show(_currentTreePanePanel, module.getSystemName());
395                }
396                _currentModule = module;
397            }
398
399
400
401            switch (evt.getPropertyName()) {
402                case Debugger.STEP_BEFORE:
403                    if (!_run || _currentMaleSocket.getBreakpointBefore()) {
404                        _currentState = State.Before;
405                    } else {
406                        _currentState = State.None;
407                    }
408                    infoString = _currentMaleSocket.getBeforeInfo();
409                    break;
410                case Debugger.STEP_AFTER:
411                    if (!_run || _currentMaleSocket.getBreakpointAfter()) {
412                        _currentState = State.After;
413                    } else {
414                        if (_rootSocket == _currentMaleSocket) {
415                            _run = false;
416                            jmri.util.ThreadingUtil.runOnGUIEventually(() -> {
417                                _runItem.setEnabled(false);
418                                _stepOverItem.setEnabled(false);
419                                _stepIntoItem.setEnabled(false);
420                                enableMenuItems.set(false);
421                            });
422                        }
423                        _currentState = State.None;
424                    }
425                    infoString = _currentMaleSocket.getAfterInfo();
426                    break;
427                default:
428                    _currentState = State.None;
429            }
430
431            SymbolTable symbolTable = new DefaultSymbolTable(_conditionalNG.getSymbolTable());
432            String infStr = infoString;
433            jmri.util.ThreadingUtil.runOnGUIEventually(() -> {
434                if (enableMenuItems.get()) {
435                    _runItem.setEnabled(true);
436                    _stepOverItem.setEnabled(true);
437                    _stepIntoItem.setEnabled(true);
438                }
439                _actionExpressionInfoLabel.setText(infStr);
440                _currentTreePane.updateTree(_currentMaleSocket);
441                _symbolTableModel.update(symbolTable);
442            });
443
444//            System.out.format("propertyChange middle: %s, %s, run: %b, currentState: %s%n", evt.getPropertyName(), ((MaleSocket)evt.getNewValue()).getLongDescription(), _run, _currentState.name());
445
446            if (_currentState != State.None) {
447                try {
448                    synchronized(_lock) {
449                        _continue = false;
450                        while (!_continue) _lock.wait();
451                    }
452                } catch (InterruptedException e) {
453                    log.error("LogixNG thread was interrupted: {}", _conditionalNG.getCurrentThread().getThreadName());
454                    Thread.currentThread().interrupt();
455                }
456            }
457
458//            System.out.format("propertyChange done: %s, %s, run: %b, currentState: %s%n", evt.getPropertyName(), ((MaleSocket)evt.getNewValue()).getLongDescription(), _run, _currentState.name());
459        }
460    }
461
462
463    public interface ConditionalNGEventListener extends EventListener {
464
465        public void conditionalNGEventOccurred();
466    }
467
468
469    private static enum State {
470        None,
471        Before,
472        After,
473    }
474
475
476    protected class PopupMenu extends JPopupMenu implements ActionListener {
477
478        private static final String ACTION_COMMAND_BREAKPOINT_BEFORE = "breakpoint_before";
479        private static final String ACTION_COMMAND_BREAKPOINT_AFTER = "breakpoint_after";
480//        private static final String ACTION_COMMAND_EXPAND_TREE = "expandTree";
481
482        private final JTree _tree;
483        private FemaleSocket _currentFemaleSocket;
484        private TreePath _currentPath;
485
486        private JMenuItem menuItemBreakpointBefore;
487        private JMenuItem menuItemBreakpointAfter;
488//        private JMenuItem menuItemExpandTree;
489
490        PopupMenu() {
491            if (_currentTreePane._tree == null) throw new IllegalArgumentException("_tree is null");
492            _tree = _currentTreePane._tree;
493        }
494
495        private void init() {
496            menuItemBreakpointBefore = new JMenuItem(Bundle.getMessage("PopupMenuBreakpointBefore"));
497            menuItemBreakpointBefore.addActionListener(this);
498            menuItemBreakpointBefore.setActionCommand(ACTION_COMMAND_BREAKPOINT_BEFORE);
499            add(menuItemBreakpointBefore);
500            addSeparator();
501            menuItemBreakpointAfter = new JMenuItem(Bundle.getMessage("PopupMenuBreakpointAfter"));
502            menuItemBreakpointAfter.addActionListener(this);
503            menuItemBreakpointAfter.setActionCommand(ACTION_COMMAND_BREAKPOINT_AFTER);
504            add(menuItemBreakpointAfter);
505/*
506            addSeparator();
507            menuItemExpandTree = new JMenuItem(Bundle.getMessage("PopupMenuExpandTree"));
508            menuItemExpandTree.addActionListener(this);
509            menuItemExpandTree.setActionCommand(ACTION_COMMAND_EXPAND_TREE);
510            add(menuItemExpandTree);
511*/
512            setOpaque(true);
513            setLightWeightPopupEnabled(true);
514
515            final PopupMenu popupMenu = this;
516
517            _tree.addMouseListener(
518                    new MouseAdapter() {
519
520                        // On Windows, the popup is opened on mousePressed,
521                        // on some other OS, the popup is opened on mouseReleased
522
523                        @Override
524                        public void mousePressed(MouseEvent e) {
525                            openPopupMenu(e);
526                        }
527
528                        @Override
529                        public void mouseReleased(MouseEvent e) {
530                            openPopupMenu(e);
531                        }
532
533                        private void openPopupMenu(MouseEvent e) {
534                            if (e.isPopupTrigger() && !popupMenu.isVisible()) {
535                                // Get the row the user has clicked on
536                                TreePath path = _tree.getClosestPathForLocation(e.getX(), e.getY());
537                                if (path != null) {
538                                    // Check that the user has clicked on a row.
539                                    Rectangle rect = _tree.getPathBounds(path);
540                                    if ((e.getY() >= rect.y) && (e.getY() <= rect.y + rect.height)) {
541                                        FemaleSocket femaleSocket = (FemaleSocket) path.getLastPathComponent();
542                                        _tree.getLocationOnScreen();
543                                        _tree.getX();
544                                        showPopup(e.getX(), e.getY(), femaleSocket, path);
545                                    }
546                                }
547                            }
548                        }
549                    }
550            );
551        }
552
553        private void showPopup(int x, int y, FemaleSocket femaleSocket, TreePath path) {
554            _currentFemaleSocket = femaleSocket;
555            _currentPath = path;
556
557            boolean isConnected = femaleSocket.isConnected();
558
559            menuItemBreakpointBefore.setEnabled(isConnected);
560            menuItemBreakpointAfter.setEnabled(isConnected);
561
562            show(_tree, x, y);
563        }
564
565        @Override
566        public void actionPerformed(ActionEvent e) {
567            switch (e.getActionCommand()) {
568                case ACTION_COMMAND_BREAKPOINT_BEFORE:
569                    MaleSocket maleSocket1 = _currentFemaleSocket.getConnectedSocket();
570                    AbstractDebuggerMaleSocket debugMaleSocket1 =
571                            (AbstractDebuggerMaleSocket) maleSocket1.find(AbstractDebuggerMaleSocket.class);
572                    if (debugMaleSocket1 == null) throw new RuntimeException("AbstractDebuggerMaleSocket is not found");
573                    // Invert breakpoint setting
574                    debugMaleSocket1.setBreakpointBefore(!debugMaleSocket1.getBreakpointBefore());
575                    for (TreeModelListener l : _currentTreePane.femaleSocketTreeModel.listeners) {
576                        TreeModelEvent tme = new TreeModelEvent(
577                                _currentFemaleSocket,
578                                _currentPath.getPath()
579                        );
580                        l.treeNodesChanged(tme);
581                    }
582                    _currentTreePane._tree.updateUI();
583                    break;
584
585                case ACTION_COMMAND_BREAKPOINT_AFTER:
586                    MaleSocket maleSocket2 = _currentFemaleSocket.getConnectedSocket();
587                    AbstractDebuggerMaleSocket debugMaleSocket2 =
588                            (AbstractDebuggerMaleSocket) maleSocket2.find(AbstractDebuggerMaleSocket.class);
589                    if (debugMaleSocket2 == null) throw new RuntimeException("AbstractDebuggerMaleSocket is not found");
590                    // Invert breakpoint setting
591                    debugMaleSocket2.setBreakpointAfter(!debugMaleSocket2.getBreakpointAfter());
592                    for (TreeModelListener l : _currentTreePane.femaleSocketTreeModel.listeners) {
593                        TreeModelEvent tme = new TreeModelEvent(
594                                _currentFemaleSocket,
595                                _currentPath.getPath()
596                        );
597                        l.treeNodesChanged(tme);
598                    }
599                    _currentTreePane._tree.updateUI();
600                    break;
601/*
602                case ACTION_COMMAND_EXPAND_TREE:
603                    // jtree expand sub tree
604                    // https://stackoverflow.com/questions/15210979/how-do-i-auto-expand-a-jtree-when-setting-a-new-treemodel
605                    // https://www.tutorialspoint.com/how-to-expand-jtree-row-to-display-all-the-nodes-and-child-nodes-in-java
606                    // To expand all rows, do this:
607                    for (int i = 0; i < tree.getRowCount(); i++) {
608                        tree.expandRow(i);
609                    }
610
611                    tree.expandPath(_currentPath);
612                    tree.updateUI();
613                    break;
614*/
615                default:
616                    // Do nothing
617            }
618        }
619    }
620
621
622    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConditionalNGDebugger.class);
623
624}