package nodebox.client; import nodebox.client.port.*; import nodebox.node.Node; import nodebox.node.Port; import nodebox.ui.PaneView; import nodebox.ui.Theme; import javax.swing.*; import java.awt.*; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.ArrayList; public class PortView extends JComponent implements PaneView, PortControl.OnValueChangeListener { private static Logger logger = Logger.getLogger("nodebox.client.PortView"); private static final Map<Port.Widget, Class> CONTROL_MAP; // At this width, the label background lines out with the pane header divider. public static final int LABEL_WIDTH = 114; static { CONTROL_MAP = new HashMap<Port.Widget, Class>(); CONTROL_MAP.put(Port.Widget.ANGLE, FloatControl.class); CONTROL_MAP.put(Port.Widget.COLOR, ColorControl.class); CONTROL_MAP.put(Port.Widget.FILE, FileControl.class); CONTROL_MAP.put(Port.Widget.FLOAT, FloatControl.class); CONTROL_MAP.put(Port.Widget.FONT, FontControl.class); CONTROL_MAP.put(Port.Widget.GRADIENT, null); CONTROL_MAP.put(Port.Widget.IMAGE, ImageControl.class); CONTROL_MAP.put(Port.Widget.INT, IntControl.class); CONTROL_MAP.put(Port.Widget.MENU, MenuControl.class); CONTROL_MAP.put(Port.Widget.PASSWORD, PasswordControl.class); CONTROL_MAP.put(Port.Widget.SEED, IntControl.class); CONTROL_MAP.put(Port.Widget.DATA, DataControl.class); CONTROL_MAP.put(Port.Widget.STRING, StringControl.class); CONTROL_MAP.put(Port.Widget.TEXT, StringControl.class); // TODO TextControl CONTROL_MAP.put(Port.Widget.TOGGLE, ToggleControl.class); CONTROL_MAP.put(Port.Widget.POINT, PointControl.class); } private final NodeBoxDocument document; private final PortPane pane; private JPanel controlPanel; private Map<String, PortControl> controlMap = new HashMap<String, PortControl>(); public PortView(PortPane pane, NodeBoxDocument document) { this.pane = pane; this.document = document; setLayout(new BorderLayout()); controlPanel = new ControlPanel(new GridBagLayout()); // controlPanel = new JPanel(new GridBagLayout()); //controlPanel.setOpaque(false); //controlPanel.setBackground(Theme.getInstance().getParameterViewBackgroundColor()); JScrollPane scrollPane = new JScrollPane(controlPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); scrollPane.setBorder(BorderFactory.createEmptyBorder()); add(scrollPane, BorderLayout.CENTER); } public NodeBoxDocument getDocument() { return document; } public Node getActiveNode() { Node activeNode = document.getActiveNode(); return activeNode != null ? activeNode : document.getActiveNetwork(); } /** * Fully rebuild the port view. */ public void updateAll() { Node activeNode = getActiveNode(); if (activeNode == null) { pane.setHeaderTitle("Ports"); } else { pane.setHeaderTitle(activeNode.getName()); } rebuildInterface(); validate(); repaint(); } /** * The port was updated, either its metadata, expression or value. * <p/> * Rebuild the interface for this port. * * @param port The updated port. */ public void updatePort(Port port) { // TODO More granular rebuild. rebuildInterface(); } /** * The value for a port was changed. * <p/> * Display the new value in the port's control UI. * * @param portName The changed port. * @param value The new port value. */ public void updatePortValue(String portName, Object value) { PortControl control = getControlForPort(portName); if (control != null && control.isVisible()) { control.setValueForControl(value); } } /** * Check the enabled state of all Parameters and sync the port rows accordingly. */ public void updateEnabledState() { for (Component c : controlPanel.getComponents()) if (c instanceof PortRow) { PortRow row = (PortRow) c; if (row.isEnabled() != row.getPort().isEnabled()) { row.setEnabled(row.getPort().isEnabled()); } } } private void rebuildInterface() { controlPanel.removeAll(); controlMap.clear(); Node node = getActiveNode(); if (node == null) return; int rowIndex = 0; ArrayList<String> portNames = new ArrayList<String>(); String activeNodePath = document.getActiveNodePath(); for (Port p : node.getInputs()) portNames.add(p.getName()); for (String portName : portNames) { Port p = node.getInput(portName); // Hide ports with names that start with an underscore. if (portName.startsWith("_") || p.getName().startsWith("_")) continue; // Hide ports whose values can't be persisted. if (p.isCustomType()) continue; // Hide ports that accept lists. if (p.hasListRange()) continue; Class widgetClass = CONTROL_MAP.get(p.getWidget()); JComponent control; if (getDocument().isConnected(portName)) { control = new JLabel("<connected>"); control.setMinimumSize(new Dimension(10, 35)); control.setFont(Theme.SMALL_FONT); control.setForeground(Theme.TEXT_DISABLED_COLOR); } else if (widgetClass != null) { control = (JComponent) constructControl(widgetClass, activeNodePath, p); ((PortControl) control).setValueChangeListener(this); ((PortControl) control).setDisplayName(portName); controlMap.put(portName, (PortControl) control); } else { control = new JLabel(" "); } GridBagConstraints rowConstraints = new GridBagConstraints(); rowConstraints.gridx = 0; rowConstraints.gridy = rowIndex; rowConstraints.fill = GridBagConstraints.HORIZONTAL; rowConstraints.weightx = 1.0; PortRow portRow = new PortRow(getDocument(), portName, control); portRow.setEnabled(p.isEnabled()); controlPanel.add(portRow, rowConstraints); rowIndex++; } if (rowIndex == 0) { JLabel noPorts = new JLabel("No ports"); noPorts.setFont(Theme.SMALL_BOLD_FONT); noPorts.setForeground(Theme.TEXT_NORMAL_COLOR); controlPanel.add(noPorts); } JLabel filler = new JLabel(); GridBagConstraints fillerConstraints = new GridBagConstraints(); fillerConstraints.gridx = 0; fillerConstraints.gridy = rowIndex; fillerConstraints.fill = GridBagConstraints.BOTH; fillerConstraints.weighty = 1.0; fillerConstraints.gridwidth = GridBagConstraints.REMAINDER; controlPanel.add(filler, fillerConstraints); revalidate(); } @SuppressWarnings("unchecked") private PortControl constructControl(Class controlClass, String activeNodePath, Port p) { try { Constructor constructor = controlClass.getConstructor(String.class, Port.class); return (PortControl) constructor.newInstance(activeNodePath, p); } catch (Exception e) { logger.log(Level.SEVERE, "Cannot construct control", e); throw new AssertionError("Cannot construct control:" + e); } } public PortControl getControlForPort(String portName) { return controlMap.get(portName); } public void onValueChange(String nodePath, String portName, Object newValue) { document.setValue(nodePath, portName, newValue); } private class ControlPanel extends JPanel { private ControlPanel(LayoutManager layout) { super(layout); } @Override protected void paintComponent(Graphics g) { if (getActiveNode() == null) { Rectangle clip = g.getClipBounds(); g.setColor(new Color(196, 196, 196)); g.fillRect(clip.x, clip.y, clip.width, clip.height); } else { int height = getHeight(); int width = getWidth(); g.setColor(Theme.PORT_LABEL_BACKGROUND); g.fillRect(0, 0, LABEL_WIDTH - 3, height); g.setColor(new Color(146, 146, 146)); g.fillRect(LABEL_WIDTH - 3, 0, 1, height); g.setColor(new Color(133, 133, 133)); g.fillRect(LABEL_WIDTH - 2, 0, 1, height); g.setColor(new Color(112, 112, 112)); g.fillRect(LABEL_WIDTH - 1, 0, 1, height); g.setColor(Theme.PORT_VALUE_BACKGROUND); g.fillRect(LABEL_WIDTH, 0, width - LABEL_WIDTH, height); } } } }