2 * Sonitus - PipelinePanel.java - Copyright © 2013 David Roden
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.pterodactylus.sonitus.gui;
20 import static javax.swing.BorderFactory.createCompoundBorder;
21 import static javax.swing.BorderFactory.createEmptyBorder;
22 import static javax.swing.BorderFactory.createEtchedBorder;
24 import java.awt.BorderLayout;
25 import java.awt.Color;
26 import java.awt.Component;
27 import java.awt.GridBagConstraints;
28 import java.awt.GridBagLayout;
29 import java.awt.Insets;
30 import java.awt.event.MouseAdapter;
31 import java.awt.event.MouseEvent;
32 import java.util.Collection;
33 import java.util.EventListener;
34 import java.util.logging.Logger;
35 import javax.swing.JComponent;
36 import javax.swing.JLabel;
37 import javax.swing.JPanel;
38 import javax.swing.UIManager;
39 import javax.swing.event.EventListenerList;
41 import net.pterodactylus.sonitus.data.ControlledComponent;
42 import net.pterodactylus.sonitus.data.Metadata;
43 import net.pterodactylus.sonitus.data.MetadataListener;
44 import net.pterodactylus.sonitus.data.Pipeline;
45 import net.pterodactylus.sonitus.data.Sink;
46 import net.pterodactylus.sonitus.data.Source;
49 * {@link JPanel} that displays all components of a {@link Pipeline}.
51 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
53 public class PipelinePanel extends JPanel {
56 private static final Logger logger = Logger.getLogger(PipelinePanel.class.getName());
58 /** The pipeline being displayed. */
59 private final Pipeline pipeline;
61 /** The component hover listeners. */
62 private final EventListenerList componentSelectionListeners = new EventListenerList();
64 /** The currently selected component. */
65 private JComponent selectedComponent;
68 * Creates a new pipeline panel displaying the given pipeline.
71 * The pipeline to display
73 public PipelinePanel(Pipeline pipeline) {
74 super(new GridBagLayout());
75 this.pipeline = pipeline;
80 // LISTENER MANAGEMENT
84 * Adds the given component selection listener to this panel.
86 * @param componentSelectionListener
87 * The component selection listener to add
89 public void addComponentHoverListener(ComponentSelectionListener componentSelectionListener) {
90 componentSelectionListeners.add(ComponentSelectionListener.class, componentSelectionListener);
97 /** Update the panel. Needs to be called when the pipeline is changed. */
98 private void updatePanel() {
99 /* clear everything. */
102 /* count all sinks. */
104 for (ControlledComponent component : pipeline.components()) {
105 if (!(component instanceof Source)) {
106 logger.finest(String.format("%s is not a Source, skipping.", component.name()));
110 Collection<Sink> sinks = pipeline.sinks((Source) component);
111 logger.finest(String.format("%s has %d sinks: %s", component.name(), sinks.size(), sinks));
112 if (sinks.isEmpty()) {
117 /* get number of maximum horizontal grid cells. */
118 int gridCellCount = 1;
119 for (int n = 2; n <= sinkCount; ++n) {
123 /* paint all components recursively. */
124 addControlled(pipeline.source(), 0, 0, gridCellCount, null);
128 * Displays the given component.
130 * @param controlledComponent
131 * The component to add this panel.
133 * The level at which to show the component (the source is level {@code 0})
135 * The position at which to display the component
137 * The width of the component in grid cells
139 private void addControlled(final ControlledComponent controlledComponent, int level, int position, int width, ControlledComponent parentComponent) {
140 /* create a GUI component that displays the component. */
141 final JPanel componentPanel = createComponentPanel(controlledComponent, parentComponent);
142 componentPanel.addMouseListener(new MouseAdapter() {
145 public void mouseClicked(MouseEvent e) {
146 for (Component component : getComponents()) {
147 component.setBackground(UIManager.getColor("Panel.background"));
149 for (ComponentSelectionListener componentSelectionListener : componentSelectionListeners.getListeners(ComponentSelectionListener.class)) {
150 componentPanel.setBackground(Color.LIGHT_GRAY);
151 componentSelectionListener.componentSelected(controlledComponent);
153 selectedComponent = componentPanel;
157 public void mouseEntered(MouseEvent mouseEvent) {
158 if (componentPanel != selectedComponent) {
159 componentPanel.setBackground(Color.white);
164 public void mouseExited(MouseEvent mouseEvent) {
165 if (componentPanel != selectedComponent) {
166 componentPanel.setBackground(UIManager.getColor("Panel.background"));
171 /* show component. */
172 add(componentPanel, new GridBagConstraints(position, level, width, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
174 /* if the component does not have connected sinks, exit here. */
175 if (!(controlledComponent instanceof Source)) {
176 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));
180 /* iterate over the component’s sinks. */
181 Collection<Sink> sinks = pipeline.sinks((Source) controlledComponent);
182 if (!sinks.isEmpty()) {
183 int sinkWidth = width / sinks.size();
185 for (Sink connectedSink : sinks) {
186 /* distribute all sinks evenly below this source. */
187 addControlled(connectedSink, level + 1, position + sinkIndex * sinkWidth, sinkWidth, controlledComponent);
194 * Creates a panel displaying a single component.
196 * @param controlledComponent
197 * The component to display
198 * @return The created panel
200 private static JPanel createComponentPanel(final ControlledComponent controlledComponent, final ControlledComponent parentComponent) {
201 JPanel componentPanel = new JPanel(new BorderLayout(12, 12));
202 componentPanel.setBorder(createCompoundBorder(createEtchedBorder(), createEmptyBorder(0, 4, 0, 3)));
203 componentPanel.add(new JLabel(controlledComponent.name()), BorderLayout.WEST);
204 final JLabel titleLabel = new JLabel(controlledComponent.metadata().fullTitle());
205 titleLabel.setFont(titleLabel.getFont().deriveFont(titleLabel.getFont().getSize2D() * 0.8f));
206 componentPanel.add(titleLabel, BorderLayout.EAST);
207 if (parentComponent != null) {
208 titleLabel.setVisible(!parentComponent.metadata().fullTitle().equals(controlledComponent.metadata().fullTitle()));
210 controlledComponent.addMetadataListener(new MetadataListener() {
213 public void metadataUpdated(ControlledComponent component, Metadata metadata) {
214 titleLabel.setText(metadata.fullTitle());
215 titleLabel.setVisible((parentComponent == null) || !parentComponent.metadata().fullTitle().equals(metadata.fullTitle()));
218 return componentPanel;
222 * Interface for objects that want to be notified if the user moves the mouse
223 * cursor over a controlled component.
225 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
227 public static interface ComponentSelectionListener extends EventListener {
230 * Notifies the listener that the mouse is now over the given controlled
233 * @param controlledComponent
234 * The controlled component now under the mouse
236 void componentSelected(ControlledComponent controlledComponent);