workcraft/workcraft

View on GitHub
workcraft/CircuitPlugin/src/org/workcraft/plugins/circuit/tools/CycleAnalyserTool.java

Summary

Maintainability
C
1 day
Test Coverage
package org.workcraft.plugins.circuit.tools;

import org.workcraft.Framework;
import org.workcraft.dom.Node;
import org.workcraft.dom.visual.HitMan;
import org.workcraft.dom.visual.SizeHelper;
import org.workcraft.dom.visual.VisualComponent;
import org.workcraft.dom.visual.VisualModel;
import org.workcraft.exceptions.OperationCancelledException;
import org.workcraft.gui.controls.FlatHeaderRenderer;
import org.workcraft.gui.events.GraphEditorMouseEvent;
import org.workcraft.gui.layouts.WrapLayout;
import org.workcraft.gui.tools.*;
import org.workcraft.interop.Format;
import org.workcraft.plugins.builtin.settings.AnalysisDecorationSettings;
import org.workcraft.plugins.circuit.*;
import org.workcraft.plugins.circuit.interop.SdcFormat;
import org.workcraft.plugins.circuit.utils.PathbreakSerialiserUtils;
import org.workcraft.plugins.circuit.utils.*;
import org.workcraft.types.Pair;
import org.workcraft.utils.*;
import org.workcraft.workspace.WorkspaceEntry;

import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.function.Function;

public class CycleAnalyserTool extends AbstractGraphEditorTool {

    private static final String WARNING_SYMBOL = Character.toString((char) 0x26A0);
    private static final String WARNING_PREFIX = WARNING_SYMBOL + ' ';

    private CycleState cycleState = null;
    private final DriverPinTable driverPinTable = new DriverPinTable();
    private final DrivenPinTable drivenPinTable = new DrivenPinTable();

    private JPanel panel;
    private JButton writeConstraintsButton;

    @Override
    public JPanel getControlsPanel(final GraphEditor editor) {
        if (panel != null) {
            return panel;
        }
        panel = new JPanel(new BorderLayout());
        panel.add(getLegendControlsPanel(), BorderLayout.NORTH);
        panel.add(getBreakControlsPanel(editor), BorderLayout.CENTER);
        panel.add(getScanControlsPanel(editor), BorderLayout.SOUTH);
        panel.setPreferredSize(new Dimension(0, 0));
        return panel;
    }

    private JPanel getLegendControlsPanel() {
        ColorLegendTable colorLegendTable = new ColorLegendTable(Arrays.asList(
                Pair.of(AnalysisDecorationSettings.getDontTouchColor(), "Don't touch zero delay"),
                Pair.of(AnalysisDecorationSettings.getProblemColor(), "On a cycle"),
                Pair.of(AnalysisDecorationSettings.getFixerColor(), "Path breaker"),
                Pair.of(AnalysisDecorationSettings.getClearColor(), "Not on any cycle")
        ));

        JPanel legendPanel = new JPanel(new BorderLayout());
        legendPanel.setBorder(GuiUtils.getTitledBorder("<html><b>Highlight legend</b></html>"));
        legendPanel.add(colorLegendTable, BorderLayout.CENTER);
        return legendPanel;
    }

    private JPanel getBreakControlsPanel(final GraphEditor editor) {

        JButton tagPathBreakerSelfloopPinsButton = GuiUtils.createIconButton(
                "images/circuit-cycle-selfloop_pins.svg",
                "Path breaker all self-loops",
                l -> changePathBreaker(editor, CycleUtils::tagPathBreakerSelfloopPins));

        JButton tagPathBreakerAutoAppendButton = GuiUtils.createIconButton(
                "images/circuit-cycle-auto_append.svg",
                "Auto-append path breaker pins as necessary to complete cycle breaking",
                l -> changePathBreaker(editor, CycleUtils::tagPathBreakerAutoAppend));

        JButton tagPathBreakerAutoDiscardButton = GuiUtils.createIconButton(
                "images/circuit-cycle-auto_discard.svg",
                "Auto-discard path breaker pins that are redundant for cycle breaking",
                l -> changePathBreaker(editor, CycleUtils::tagPathBreakerAutoDiscard));

        JButton tagPathBreakerClearAllButton = GuiUtils.createIconButton(
                "images/circuit-cycle-clear_all.svg",
                "Clear all path breaker pins",
                l -> changePathBreaker(editor, CycleUtils::tagPathBreakerClearAll));

        JPanel buttonPanel = new JPanel();
        buttonPanel.add(tagPathBreakerSelfloopPinsButton);
        buttonPanel.add(tagPathBreakerAutoAppendButton);
        buttonPanel.add(tagPathBreakerAutoDiscardButton);
        buttonPanel.add(tagPathBreakerClearAllButton);
        GuiUtils.setButtonPanelLayout(buttonPanel, tagPathBreakerClearAllButton.getPreferredSize());

        JPanel controlPanel = new JPanel(new WrapLayout());
        controlPanel.add(buttonPanel);

        JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
                new JScrollPane(driverPinTable), new JScrollPane(drivenPinTable));

        splitPane.setOneTouchExpandable(true);
        splitPane.setResizeWeight(0.5);

        JPanel forcePanel = new JPanel(new BorderLayout());
        forcePanel.add(controlPanel, BorderLayout.NORTH);
        forcePanel.add(splitPane, BorderLayout.CENTER);
        return forcePanel;
    }

    private void changePathBreaker(final GraphEditor editor, Function<Circuit, Collection<? extends Contact>> func) {
        WorkspaceEntry we = editor.getWorkspaceEntry();
        we.captureMemento();
        Circuit circuit = (Circuit) editor.getModel().getMathModel();
        Collection<? extends Contact> changedContacts = func.apply(circuit);
        if (changedContacts.isEmpty()) {
            we.uncaptureMemento();
        } else {
            we.saveMemento();
        }
        editor.requestFocus();
    }

    private JPanel getScanControlsPanel(final GraphEditor editor) {
        JButton insertTestableGatesButton = new JButton("<html><center>Insert<br><small>TBUF/TINV</small></center></html>");
        insertTestableGatesButton.addActionListener(l -> insertTestableGates(editor));
        insertTestableGatesButton.setToolTipText("Insert testable buffers/inverters for path breaker components");

        JButton insertScanButton = new JButton("<html><center>Insert<br><small>SCAN</small></center></html>");
        insertScanButton.addActionListener(l -> insertScan(editor));
        insertScanButton.setToolTipText("Insert scan for path breaker components");

        writeConstraintsButton = new JButton("<html><center>Write<br><small>SDC...</small></center></html>");
        writeConstraintsButton.addActionListener(l -> writeConstraints(editor));
        writeConstraintsButton.setToolTipText("<html>Write <i>set_disable_timing</i> constraints for <b>input pin</b> path breakers</html>");

        JPanel scanPanel = new JPanel(new WrapLayout());
        scanPanel.add(insertTestableGatesButton);
        scanPanel.add(insertScanButton);
        scanPanel.add(writeConstraintsButton);
        return scanPanel;
    }

    private void insertTestableGates(GraphEditor editor) {
        VisualCircuit circuit = (VisualCircuit) editor.getModel();
        editor.getWorkspaceEntry().saveMemento();
        ScanUtils.insertTestableGates(circuit);
        editor.requestFocus();
    }

    private void insertScan(GraphEditor editor) {
        WorkspaceEntry we = editor.getWorkspaceEntry();
        VisualCircuit circuit = WorkspaceUtils.getAs(we, VisualCircuit.class);
        we.captureMemento();
        boolean isModified = ScanUtils.insertScan(circuit);
        if (isModified) {
            we.saveMemento();
        }
        we.uncaptureMemento();
        editor.requestFocus();
    }

    private void writeConstraints(final GraphEditor editor) {
        File file = new File(editor.getWorkspaceEntry().getFileName());
        Format format = SdcFormat.getInstance();
        JFileChooser fc = DialogUtils.createFileSaver("Save path breaker SDC constraints", file, format);
        try {
            file = DialogUtils.chooseValidSaveFileOrCancel(fc, format);
            Circuit circuit = WorkspaceUtils.getAs(editor.getWorkspaceEntry(), Circuit.class);
            LogUtils.logInfo(ExportUtils.getExportMessage(circuit, file, "Writing path breaker constraints for the circuit "));
            try (FileOutputStream out = new FileOutputStream(file)) {
                PathbreakSerialiserUtils.write(circuit, out);
            } catch (IOException e) {
                LogUtils.logError("Could not write into file '" + file.getAbsolutePath() + "'");
            }
            Framework.getInstance().setLastDirectory(fc.getCurrentDirectory());
        } catch (OperationCancelledException e) {
            // Operation cancelled by the user
        }
    }

    @Override
    public String getLabel() {
        return "Cycle analyser";
    }

    @Override
    public int getHotKeyCode() {
        return KeyEvent.VK_Y;
    }

    @Override
    public Icon getIcon() {
        return GuiUtils.createIconFromSVG("images/circuit-tool-cycle_analysis.svg");
    }

    @Override
    public Cursor getCursor(boolean menuKeyDown, boolean shiftKeyDown, boolean altKeyDown) {
        return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
    }

    @Override
    public String getHintText(final GraphEditor editor) {
        return "Click on a contact or a gate to toggle its path breaker state.";
    }

    @Override
    public void activated(final GraphEditor editor) {
        super.activated(editor);
        Circuit circuit = (Circuit) editor.getModel().getMathModel();
        CircuitUtils.correctInitialState(circuit);
        updateState(editor);
        editor.getWorkspaceEntry().addObserver(e -> updateState(editor));
    }

    @Override
    public void deactivated(final GraphEditor editor) {
        super.deactivated(editor);
        cycleState = null;
    }

    @Override
    public void setPermissions(final GraphEditor editor) {
        WorkspaceEntry we = editor.getWorkspaceEntry();
        we.setCanModify(true);
        we.setCanSelect(false);
        we.setCanCopy(false);
    }

    private void updateState(final GraphEditor editor) {
        Circuit circuit = (Circuit) editor.getModel().getMathModel();
        cycleState = new CycleState(circuit);
        driverPinTable.refresh();
        drivenPinTable.refresh();
        // Enable write SDC constraints button if there are path breaker input pins
        boolean hasDrivenPathBreaker = !cycleState.getBreakerDrivenPins().isEmpty();
        writeConstraintsButton.setEnabled(hasDrivenPathBreaker);
    }

    @Override
    public void mousePressed(GraphEditorMouseEvent e) {
        boolean processed = false;
        GraphEditor editor = e.getEditor();
        VisualModel model = e.getModel();
        if (e.getButton() == MouseEvent.BUTTON1) {
            Node deepestNode = HitMan.hitDeepest(e.getPosition(), model.getRoot(),
                    node -> (node instanceof VisualFunctionComponent) || (node instanceof VisualContact));

            VisualContact contact = null;
            if (deepestNode instanceof VisualContact) {
                contact = (VisualContact) deepestNode;
            } else if (deepestNode instanceof VisualCircuitComponent) {
                VisualFunctionComponent component = (VisualFunctionComponent) deepestNode;
                contact = component.getMainVisualOutput();

            }
            if ((contact != null) && contact.isPin() && !contact.isZeroDelayDriver()) {
                FunctionContact mathContact = ((VisualFunctionContact) contact).getReferencedComponent();
                editor.getWorkspaceEntry().saveMemento();
                mathContact.setPathBreaker(!mathContact.getPathBreaker());
                processed = true;
            }
        }
        if (!processed) {
            super.mousePressed(e);
        }
    }

    @Override
    public Decorator getDecorator(final GraphEditor editor) {
        return node -> {
            Node mathNode = null;
            if (node instanceof VisualComponent) {
                mathNode = ((VisualComponent) node).getReferencedComponent();
            }

            if (mathNode != null) {
                if (mathNode instanceof Contact) {
                    return getContactDecoration((Contact) mathNode);
                }
                if (mathNode instanceof FunctionComponent) {
                    return getComponentDecoration((FunctionComponent) mathNode);
                }
            }
            return null;
        };
    }

    private Color getCycleStatusColor(Contact contact) {
        return contact.isZeroDelayDriver() ? AnalysisDecorationSettings.getDontTouchColor()
                : cycleState.isInCycle(contact) ? AnalysisDecorationSettings.getProblemColor()
                : contact.getPathBreaker() ? AnalysisDecorationSettings.getFixerColor()
                : contact.isPin() ? AnalysisDecorationSettings.getClearColor() : null;
    }

    private Decoration getContactDecoration(Contact contact) {
        final Color color = getCycleStatusColor(contact);

        return new Decoration() {
            @Override
            public Color getColorisation() {
                return color;
            }

            @Override
            public Color getBackground() {
                return color;
            }
        };
    }

    private Decoration getComponentDecoration(FunctionComponent component) {
        final Color color = component.getIsZeroDelay() ? AnalysisDecorationSettings.getDontTouchColor()
                : cycleState.isInCycle(component) ? AnalysisDecorationSettings.getProblemColor()
                : ScanUtils.hasPathBreakerOutput(component) ? AnalysisDecorationSettings.getFixerColor()
                : AnalysisDecorationSettings.getClearColor();

        return new Decoration() {
            @Override
            public Color getColorisation() {
                return color;
            }

            @Override
            public Color getBackground() {
                return color;
            }
        };
    }

    private class DriverPinTable extends JTable {

        DriverPinTable() {
            setModel(new DriverPinTableModel());
            setFocusable(false);
            setRowSelectionAllowed(false);
            setRowHeight(SizeHelper.getComponentHeightFromFont(getFont()));
            setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
            getTableHeader().setDefaultRenderer(new FlatHeaderRenderer(false));
            getTableHeader().setReorderingAllowed(false);
            setDefaultRenderer(Object.class, new PinTableCellRenderer());
            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    Contact contact = cycleState.getDriverPin(getSelectedRow());
                    if (contact != null) {
                        contact.setPathBreaker(!contact.getPathBreaker());
                    }
                }
            });
        }

        public void refresh() {
            int cyclePinCount = cycleState.getCycleDriverPins().size();
            int breakerPinCount = cycleState.getBreakerDriverPins().size();
            int clearPinCount = cycleState.getDriverPinCount() - cyclePinCount - breakerPinCount;
            String header = StatsUtils.getHtmlStatsHeader("Output pins",
                    cyclePinCount, null, breakerPinCount, clearPinCount);

            GuiUtils.setColumnHeader(this, 0, header);

            final Color color = !cycleState.getCycleDriverPins().isEmpty() ? AnalysisDecorationSettings.getProblemColor()
                    : !cycleState.getBreakerDriverPins().isEmpty() ? AnalysisDecorationSettings.getFixerColor()
                    : AnalysisDecorationSettings.getClearColor();

            getTableHeader().setBackground(color);
            GuiUtils.refreshTable(this);
        }
    }

    private class DriverPinTableModel extends AbstractTableModel {
        @Override
        public int getColumnCount() {
            return 1;
        }

        @Override
        public int getRowCount() {
            return cycleState.getDriverPinCount();
        }

        @Override
        public Object getValueAt(int row, int column) {
            return cycleState.getDriverPin(row);
        }
    }

    private class DrivenPinTable extends JTable {

        DrivenPinTable() {
            setModel(new DrivenPinTableModel());
            setFocusable(false);
            setRowSelectionAllowed(false);
            setRowHeight(SizeHelper.getComponentHeightFromFont(getFont()));
            setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
            getTableHeader().setReorderingAllowed(false);
            getTableHeader().setDefaultRenderer(new FlatHeaderRenderer(false));
            setDefaultRenderer(Object.class, new PinTableCellRenderer());
            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    Contact contact = cycleState.getDrivenPin(getSelectedRow());
                    if (contact != null) {
                        contact.setPathBreaker(!contact.getPathBreaker());
                    }
                }
            });
        }

        public void refresh() {
            int cyclePinCount = cycleState.getCycleDrivenPins().size();
            int breakerPinCount = cycleState.getBreakerDrivenPins().size();
            int clearPinCount = cycleState.getDrivenPinCount() - cyclePinCount - breakerPinCount;
            String header = StatsUtils.getHtmlStatsHeader("Input pins",
                    cyclePinCount, null, breakerPinCount, clearPinCount);

            GuiUtils.setColumnHeader(this, 0, header);

            final Color color = !cycleState.getCycleDrivenPins().isEmpty() ? AnalysisDecorationSettings.getProblemColor()
                    : !cycleState.getBreakerDrivenPins().isEmpty() ? AnalysisDecorationSettings.getFixerColor()
                    : AnalysisDecorationSettings.getClearColor();

            getTableHeader().setBackground(color);
            GuiUtils.refreshTable(this);
        }
    }

    private class DrivenPinTableModel extends AbstractTableModel {
        @Override
        public int getColumnCount() {
            return 1;
        }

        @Override
        public int getRowCount() {
            return cycleState.getDrivenPinCount();
        }

        @Override
        public Object getValueAt(int row, int column) {
            return cycleState.getDrivenPin(row);
        }
    }

    private class PinTableCellRenderer implements TableCellRenderer {

        private final JLabel label = new JLabel() {
            @Override
            public void paint(Graphics g) {
                g.setColor(getBackground());
                g.fillRect(0, 0, getWidth(), getHeight());
                super.paint(g);
            }
        };

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int col) {

            JLabel result = null;
            if (value instanceof FunctionContact) {
                FunctionContact contact = (FunctionContact) value;
                Color color = ColorUtils.colorise(GuiUtils.getTableCellBackgroundColor(), getCycleStatusColor(contact));
                label.setBackground(color);

                String ref = cycleState.getContactReference(contact);
                String text = cycleState.isRedundantPathBreaker(contact) ? WARNING_PREFIX + ref : ref;
                label.setText(text);

                boolean fits = GuiUtils.getLabelTextWidth(label) < GuiUtils.getTableColumnTextWidth(table, col);
                label.setToolTipText(fits ? null : label.getText());
                label.setBorder(GuiUtils.getTableCellBorder());
                result = label;
            }
            return result;
        }
    }

}