package net.pterodactylus.sonitus.gui;
+import static javax.swing.BorderFactory.createCompoundBorder;
+import static javax.swing.BorderFactory.createEmptyBorder;
import static javax.swing.BorderFactory.createEtchedBorder;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
import java.util.Collection;
-import java.util.List;
+import java.util.EventListener;
+import java.util.logging.Logger;
+import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
+import javax.swing.UIManager;
+import javax.swing.event.EventListenerList;
-import net.pterodactylus.sonitus.data.Controlled;
-import net.pterodactylus.sonitus.data.Filter;
+import net.pterodactylus.sonitus.data.ControlledComponent;
+import net.pterodactylus.sonitus.data.Metadata;
+import net.pterodactylus.sonitus.data.MetadataListener;
import net.pterodactylus.sonitus.data.Pipeline;
import net.pterodactylus.sonitus.data.Sink;
import net.pterodactylus.sonitus.data.Source;
-import com.google.common.collect.Lists;
-
/**
* {@link JPanel} that displays all components of a {@link Pipeline}.
*
*/
public class PipelinePanel extends JPanel {
+ /** The logger. */
+ private static final Logger logger = Logger.getLogger(PipelinePanel.class.getName());
+
/** The pipeline being displayed. */
private final Pipeline pipeline;
+ /** The component hover listeners. */
+ private final EventListenerList componentSelectionListeners = new EventListenerList();
+
+ /** The currently selected component. */
+ private JComponent selectedComponent;
+
/**
* Creates a new pipeline panel displaying the given pipeline.
*
}
//
+ // LISTENER MANAGEMENT
+ //
+
+ /**
+ * Adds the given component selection listener to this panel.
+ *
+ * @param componentSelectionListener
+ * The component selection listener to add
+ */
+ public void addComponentHoverListener(ComponentSelectionListener componentSelectionListener) {
+ componentSelectionListeners.add(ComponentSelectionListener.class, componentSelectionListener);
+ }
+
+ //
// PRIVATE METHODS
//
/* count all sinks. */
int sinkCount = 0;
- List<Source> sources = Lists.newArrayList(pipeline.source());
- while (!sources.isEmpty()) {
- Collection<Sink> sinks = pipeline.sinks(sources.remove(0));
- for (Sink sink : sinks) {
- /* only count real sinks, everything else is filter. */
- if (sink instanceof Filter) {
- sources.add((Filter) sink);
- } else {
- sinkCount++;
- }
+ for (ControlledComponent component : pipeline.components()) {
+ if (!(component instanceof Source)) {
+ logger.finest(String.format("%s is not a Source, skipping.", component.name()));
+ sinkCount++;
+ continue;
+ }
+ Collection<Sink> sinks = pipeline.sinks((Source) component);
+ logger.finest(String.format("%s has %d sinks: %s", component.name(), sinks.size(), sinks));
+ if (sinks.isEmpty()) {
+ sinkCount++;
}
}
}
/* paint all components recursively. */
- addControlled(pipeline.source(), 0, 0, gridCellCount);
+ addControlled(pipeline.source(), 0, 0, gridCellCount, null);
}
/**
* Displays the given component.
*
- * @param controlled
+ * @param controlledComponent
* The component to add this panel.
* @param level
* The level at which to show the component (the source is level {@code 0})
* @param width
* The width of the component in grid cells
*/
- private void addControlled(Controlled controlled, int level, int position, int width) {
+ private void addControlled(final ControlledComponent controlledComponent, int level, int position, int width, ControlledComponent parentComponent) {
/* create a GUI component that displays the component. */
- JLabel sourceLabel = new JLabel(controlled.name());
- sourceLabel.setBorder(createEtchedBorder());
+ final JPanel componentPanel = createComponentPanel(controlledComponent, parentComponent);
+ componentPanel.addMouseListener(new MouseAdapter() {
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ for (Component component : getComponents()) {
+ component.setBackground(UIManager.getColor("Panel.background"));
+ }
+ for (ComponentSelectionListener componentSelectionListener : componentSelectionListeners.getListeners(ComponentSelectionListener.class)) {
+ componentPanel.setBackground(Color.LIGHT_GRAY);
+ componentSelectionListener.componentSelected(controlledComponent);
+ }
+ selectedComponent = componentPanel;
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent mouseEvent) {
+ if (componentPanel != selectedComponent) {
+ componentPanel.setBackground(Color.white);
+ }
+ }
+
+ @Override
+ public void mouseExited(MouseEvent mouseEvent) {
+ if (componentPanel != selectedComponent) {
+ componentPanel.setBackground(UIManager.getColor("Panel.background"));
+ }
+ }
+ });
/* show component. */
- add(sourceLabel, new GridBagConstraints(position, level, width, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
+ add(componentPanel, new GridBagConstraints(position, level, width, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
/* if the component does not have connected sinks, exit here. */
- if (!(controlled instanceof Source)) {
+ if (!(controlledComponent instanceof Source)) {
+ add(new JPanel(), new GridBagConstraints(position, 999, width, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
return;
}
/* iterate over the component’s sinks. */
- Collection<Sink> sinks = pipeline.sinks((Source) controlled);
- int sinkWidth = width / sinks.size();
- int sinkIndex = 0;
- for (Sink connectedSink : sinks) {
- /* distribute all sinks evenly below this source. */
- addControlled(connectedSink, level + 1, position + sinkIndex * sinkWidth, sinkWidth);
- sinkIndex++;
+ Collection<Sink> sinks = pipeline.sinks((Source) controlledComponent);
+ if (!sinks.isEmpty()) {
+ int sinkWidth = width / sinks.size();
+ int sinkIndex = 0;
+ for (Sink connectedSink : sinks) {
+ /* distribute all sinks evenly below this source. */
+ addControlled(connectedSink, level + 1, position + sinkIndex * sinkWidth, sinkWidth, controlledComponent);
+ sinkIndex++;
+ }
+ }
+ }
+
+ /**
+ * Creates a panel displaying a single component.
+ *
+ * @param controlledComponent
+ * The component to display
+ * @return The created panel
+ */
+ private static JPanel createComponentPanel(final ControlledComponent controlledComponent, final ControlledComponent parentComponent) {
+ JPanel componentPanel = new JPanel(new BorderLayout(12, 12));
+ componentPanel.setBorder(createCompoundBorder(createEtchedBorder(), createEmptyBorder(0, 4, 0, 3)));
+ componentPanel.add(new JLabel(controlledComponent.name()), BorderLayout.WEST);
+ final JLabel titleLabel = new JLabel(controlledComponent.metadata().fullTitle());
+ titleLabel.setFont(titleLabel.getFont().deriveFont(titleLabel.getFont().getSize2D() * 0.8f));
+ componentPanel.add(titleLabel, BorderLayout.EAST);
+ if (parentComponent != null) {
+ titleLabel.setVisible(!parentComponent.metadata().fullTitle().equals(controlledComponent.metadata().fullTitle()));
}
+ controlledComponent.addMetadataListener(new MetadataListener() {
+
+ @Override
+ public void metadataUpdated(ControlledComponent component, Metadata metadata) {
+ titleLabel.setText(metadata.fullTitle());
+ titleLabel.setVisible((parentComponent == null) || !parentComponent.metadata().fullTitle().equals(metadata.fullTitle()));
+ }
+ });
+ return componentPanel;
+ }
+
+ /**
+ * Interface for objects that want to be notified if the user moves the mouse
+ * cursor over a controlled component.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+ public static interface ComponentSelectionListener extends EventListener {
+
+ /**
+ * Notifies the listener that the mouse is now over the given controlled
+ * component.
+ *
+ * @param controlledComponent
+ * The controlled component now under the mouse
+ */
+ void componentSelected(ControlledComponent controlledComponent);
+
}
}