42ddf9e735c7eb9863f6bb5b9d3b3e28d0518c8d
[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                 System.out.println(sinkCount);
110
111                 /* get number of maximum horizontal grid cells. */
112                 int gridCellCount = 1;
113                 for (int n = 2; n <= sinkCount; ++n) {
114                         gridCellCount *= n;
115                 }
116
117                 /* paint all components recursively. */
118                 addControlled(pipeline.source(), 0, 0, gridCellCount, null);
119         }
120
121         /**
122          * Displays the given component.
123          *
124          * @param controlledComponent
125          *              The component to add this panel.
126          * @param level
127          *              The level at which to show the component (the source is level {@code 0})
128          * @param position
129          *              The position at which to display the component
130          * @param width
131          *              The width of the component in grid cells
132          */
133         private void addControlled(final ControlledComponent controlledComponent, int level, int position, int width, ControlledComponent parentComponent) {
134                 /* create a GUI component that displays the component. */
135                 JPanel componentPanel = createComponentPanel(controlledComponent, parentComponent);
136                 componentPanel.addMouseListener(new MouseAdapter() {
137
138                         @Override
139                         public void mouseEntered(MouseEvent mouseEvent) {
140                                 for (ComponentHoverListener componentHoverListener : componentHoverListeners.getListeners(ComponentHoverListener.class)) {
141                                         componentHoverListener.componentEntered(controlledComponent);
142                                 }
143                         }
144                 });
145
146                 /* show component. */
147                 add(componentPanel, new GridBagConstraints(position, level, width, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
148
149                 /* if the component does not have connected sinks, exit here. */
150                 if (!(controlledComponent instanceof Source)) {
151                         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));
152                         return;
153                 }
154
155                 /* iterate over the component’s sinks. */
156                 Collection<Sink> sinks = pipeline.sinks((Source) controlledComponent);
157                 if (!sinks.isEmpty()) {
158                         int sinkWidth = width / sinks.size();
159                         int sinkIndex = 0;
160                         for (Sink connectedSink : sinks) {
161                                 /* distribute all sinks evenly below this source. */
162                                 addControlled(connectedSink, level + 1, position + sinkIndex * sinkWidth, sinkWidth, controlledComponent);
163                                 sinkIndex++;
164                         }
165                 }
166         }
167
168         /**
169          * Creates a panel displaying a single component.
170          *
171          * @param controlledComponent
172          *              The component to display
173          * @return The created panel
174          */
175         private static JPanel createComponentPanel(final ControlledComponent controlledComponent, final ControlledComponent parentComponent) {
176                 JPanel componentPanel = new JPanel(new BorderLayout(12, 12));
177                 componentPanel.setBorder(createCompoundBorder(createEtchedBorder(), createEmptyBorder(0, 4, 0, 3)));
178                 componentPanel.add(new JLabel(controlledComponent.name()), BorderLayout.WEST);
179                 final JLabel titleLabel = new JLabel(controlledComponent.metadata().title());
180                 titleLabel.setFont(titleLabel.getFont().deriveFont(titleLabel.getFont().getSize2D() * 0.8f));
181                 componentPanel.add(titleLabel, BorderLayout.EAST);
182                 if (parentComponent != null) {
183                         titleLabel.setVisible(!parentComponent.metadata().title().equals(controlledComponent.metadata().title()));
184                         parentComponent.addMetadataListener(new MetadataListener() {
185
186                                 @Override
187                                 public void metadataUpdated(ControlledComponent component, Metadata metadata) {
188                                         titleLabel.setText(metadata.title());
189                                         titleLabel.setVisible(!controlledComponent.metadata().title().equals(metadata.title()));
190                                 }
191                         });
192                 }
193                 controlledComponent.addMetadataListener(new MetadataListener() {
194
195                         @Override
196                         public void metadataUpdated(ControlledComponent component, Metadata metadata) {
197                                 titleLabel.setText(metadata.title());
198                                 titleLabel.setVisible((parentComponent == null) || !parentComponent.metadata().title().equals(metadata.title()));
199                         }
200                 });
201                 return componentPanel;
202         }
203
204         /**
205          * Interface for objects that want to be notified if the user moves the mouse
206          * cursor over a controlled component.
207          *
208          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
209          */
210         public static interface ComponentHoverListener extends EventListener {
211
212                 /**
213                  * Notifies the listener that the mouse is now over the given controlled
214                  * component.
215                  *
216                  * @param controlledComponent
217                  *              The controlled component now under the mouse
218                  */
219                 void componentEntered(ControlledComponent controlledComponent);
220
221         }
222
223 }