Remove debug output.
[sonitus.git] / src / main / java / net / pterodactylus / sonitus / gui / PipelinePanel.java
1 /*
2  * Sonitus - PipelinePanel.java - Copyright © 2013 David Roden
3  *
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.
8  *
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.
13  *
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/>.
16  */
17
18 package net.pterodactylus.sonitus.gui;
19
20 import static javax.swing.BorderFactory.createCompoundBorder;
21 import static javax.swing.BorderFactory.createEmptyBorder;
22 import static javax.swing.BorderFactory.createEtchedBorder;
23
24 import java.awt.BorderLayout;
25 import java.awt.GridBagConstraints;
26 import java.awt.GridBagLayout;
27 import java.awt.Insets;
28 import java.awt.event.MouseAdapter;
29 import java.awt.event.MouseEvent;
30 import java.util.Collection;
31 import java.util.EventListener;
32 import java.util.logging.Logger;
33 import javax.swing.JLabel;
34 import javax.swing.JPanel;
35 import javax.swing.event.EventListenerList;
36
37 import net.pterodactylus.sonitus.data.ControlledComponent;
38 import net.pterodactylus.sonitus.data.Metadata;
39 import net.pterodactylus.sonitus.data.MetadataListener;
40 import net.pterodactylus.sonitus.data.Pipeline;
41 import net.pterodactylus.sonitus.data.Sink;
42 import net.pterodactylus.sonitus.data.Source;
43
44 /**
45  * {@link JPanel} that displays all components of a {@link Pipeline}.
46  *
47  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
48  */
49 public class PipelinePanel extends JPanel {
50
51         /** The logger. */
52         private static final Logger logger = Logger.getLogger(PipelinePanel.class.getName());
53
54         /** The pipeline being displayed. */
55         private final Pipeline pipeline;
56
57         /** The component hover listeners. */
58         private final EventListenerList componentHoverListeners = new EventListenerList();
59
60         /**
61          * Creates a new pipeline panel displaying the given pipeline.
62          *
63          * @param pipeline
64          *              The pipeline to display
65          */
66         public PipelinePanel(Pipeline pipeline) {
67                 super(new GridBagLayout());
68                 this.pipeline = pipeline;
69                 updatePanel();
70         }
71
72         //
73         // LISTENER MANAGEMENT
74         //
75
76         /**
77          * Adds the given component hover listener to this panel.
78          *
79          * @param componentHoverListener
80          *              The component hover listener to add
81          */
82         public void addComponentHoverListener(ComponentHoverListener componentHoverListener) {
83                 componentHoverListeners.add(ComponentHoverListener.class, componentHoverListener);
84         }
85
86         //
87         // PRIVATE METHODS
88         //
89
90         /** Update the panel. Needs to be called when the pipeline is changed. */
91         private void updatePanel() {
92                 /* clear everything. */
93                 removeAll();
94
95                 /* count all sinks. */
96                 int sinkCount = 0;
97                 for (ControlledComponent component : pipeline.components()) {
98                         if (!(component instanceof Source)) {
99                                 logger.finest(String.format("%s is not a Source, skipping.", component.name()));
100                                 sinkCount++;
101                                 continue;
102                         }
103                         Collection<Sink> sinks = pipeline.sinks((Source) component);
104                         logger.finest(String.format("%s has %d sinks: %s", component.name(), sinks.size(), sinks));
105                         if (sinks.isEmpty()) {
106                                 sinkCount++;
107                         }
108                 }
109
110                 /* get number of maximum horizontal grid cells. */
111                 int gridCellCount = 1;
112                 for (int n = 2; n <= sinkCount; ++n) {
113                         gridCellCount *= n;
114                 }
115
116                 /* paint all components recursively. */
117                 addControlled(pipeline.source(), 0, 0, gridCellCount, null);
118         }
119
120         /**
121          * Displays the given component.
122          *
123          * @param controlledComponent
124          *              The component to add this panel.
125          * @param level
126          *              The level at which to show the component (the source is level {@code 0})
127          * @param position
128          *              The position at which to display the component
129          * @param width
130          *              The width of the component in grid cells
131          */
132         private void addControlled(final ControlledComponent controlledComponent, int level, int position, int width, ControlledComponent parentComponent) {
133                 /* create a GUI component that displays the component. */
134                 JPanel componentPanel = createComponentPanel(controlledComponent, parentComponent);
135                 componentPanel.addMouseListener(new MouseAdapter() {
136
137                         @Override
138                         public void mouseEntered(MouseEvent mouseEvent) {
139                                 for (ComponentHoverListener componentHoverListener : componentHoverListeners.getListeners(ComponentHoverListener.class)) {
140                                         componentHoverListener.componentEntered(controlledComponent);
141                                 }
142                         }
143                 });
144
145                 /* show component. */
146                 add(componentPanel, new GridBagConstraints(position, level, width, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
147
148                 /* if the component does not have connected sinks, exit here. */
149                 if (!(controlledComponent instanceof Source)) {
150                         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));
151                         return;
152                 }
153
154                 /* iterate over the component’s sinks. */
155                 Collection<Sink> sinks = pipeline.sinks((Source) controlledComponent);
156                 if (!sinks.isEmpty()) {
157                         int sinkWidth = width / sinks.size();
158                         int sinkIndex = 0;
159                         for (Sink connectedSink : sinks) {
160                                 /* distribute all sinks evenly below this source. */
161                                 addControlled(connectedSink, level + 1, position + sinkIndex * sinkWidth, sinkWidth, controlledComponent);
162                                 sinkIndex++;
163                         }
164                 }
165         }
166
167         /**
168          * Creates a panel displaying a single component.
169          *
170          * @param controlledComponent
171          *              The component to display
172          * @return The created panel
173          */
174         private static JPanel createComponentPanel(final ControlledComponent controlledComponent, final ControlledComponent parentComponent) {
175                 JPanel componentPanel = new JPanel(new BorderLayout(12, 12));
176                 componentPanel.setBorder(createCompoundBorder(createEtchedBorder(), createEmptyBorder(0, 4, 0, 3)));
177                 componentPanel.add(new JLabel(controlledComponent.name()), BorderLayout.WEST);
178                 final JLabel titleLabel = new JLabel(controlledComponent.metadata().title());
179                 titleLabel.setFont(titleLabel.getFont().deriveFont(titleLabel.getFont().getSize2D() * 0.8f));
180                 componentPanel.add(titleLabel, BorderLayout.EAST);
181                 if (parentComponent != null) {
182                         titleLabel.setVisible(!parentComponent.metadata().title().equals(controlledComponent.metadata().title()));
183                         parentComponent.addMetadataListener(new MetadataListener() {
184
185                                 @Override
186                                 public void metadataUpdated(ControlledComponent component, Metadata metadata) {
187                                         titleLabel.setText(metadata.title());
188                                         titleLabel.setVisible(!controlledComponent.metadata().title().equals(metadata.title()));
189                                 }
190                         });
191                 }
192                 controlledComponent.addMetadataListener(new MetadataListener() {
193
194                         @Override
195                         public void metadataUpdated(ControlledComponent component, Metadata metadata) {
196                                 titleLabel.setText(metadata.title());
197                                 titleLabel.setVisible((parentComponent == null) || !parentComponent.metadata().title().equals(metadata.title()));
198                         }
199                 });
200                 return componentPanel;
201         }
202
203         /**
204          * Interface for objects that want to be notified if the user moves the mouse
205          * cursor over a controlled component.
206          *
207          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
208          */
209         public static interface ComponentHoverListener extends EventListener {
210
211                 /**
212                  * Notifies the listener that the mouse is now over the given controlled
213                  * component.
214                  *
215                  * @param controlledComponent
216                  *              The controlled component now under the mouse
217                  */
218                 void componentEntered(ControlledComponent controlledComponent);
219
220         }
221
222 }