Pull all interfaces into a single interface: Filter.
[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.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;
40
41 import net.pterodactylus.sonitus.data.Filter;
42 import net.pterodactylus.sonitus.data.Metadata;
43 import net.pterodactylus.sonitus.data.MetadataListener;
44 import net.pterodactylus.sonitus.data.Pipeline;
45
46 /**
47  * {@link JPanel} that displays all filters of a {@link Pipeline}.
48  *
49  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
50  */
51 public class PipelinePanel extends JPanel {
52
53         /** The logger. */
54         private static final Logger logger = Logger.getLogger(PipelinePanel.class.getName());
55
56         /** The pipeline being displayed. */
57         private final Pipeline pipeline;
58
59         /** The filter selection listeners. */
60         private final EventListenerList filterSelectionListeners = new EventListenerList();
61
62         /** The currently selected filter. */
63         private JComponent selectedFilter;
64
65         /**
66          * Creates a new pipeline panel displaying the given pipeline.
67          *
68          * @param pipeline
69          *              The pipeline to display
70          */
71         public PipelinePanel(Pipeline pipeline) {
72                 super(new GridBagLayout());
73                 this.pipeline = pipeline;
74                 updatePanel();
75         }
76
77         //
78         // LISTENER MANAGEMENT
79         //
80
81         /**
82          * Adds the given filter selection listener to this panel.
83          *
84          * @param filterSelectionListener
85          *              The filter selection listener to add
86          */
87         public void addFilterSelectionListener(FilterSelectionListener filterSelectionListener) {
88                 filterSelectionListeners.add(FilterSelectionListener.class, filterSelectionListener);
89         }
90
91         //
92         // PRIVATE METHODS
93         //
94
95         /** Update the panel. Needs to be called when the pipeline is changed. */
96         private void updatePanel() {
97                 /* clear everything. */
98                 removeAll();
99
100                 /* count all filters. */
101                 int sinkCount = 0;
102                 for (Filter filter : pipeline.filters()) {
103                         Collection<Filter> sinks = pipeline.filters(filter);
104                         logger.finest(String.format("%s has %d filters: %s", filter.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 filters recursively. */
117                 addFilter(pipeline.source(), 0, 0, gridCellCount, null);
118         }
119
120         /**
121          * Displays the given filter.
122          *
123          * @param filter
124          *              The filter to add this panel.
125          * @param level
126          *              The level at which to show the filter (the source is level {@code 0})
127          * @param position
128          *              The position at which to display the filter
129          * @param width
130          *              The width of the filter in grid cells
131          */
132         private void addFilter(final Filter filter, int level, int position, int width, Filter parentFilter) {
133                 /* create a GUI component that displays the filter. */
134                 final JPanel filterPanel = createFilterPanel(filter, parentFilter);
135                 filterPanel.addMouseListener(new MouseAdapter() {
136
137                         @Override
138                         public void mouseClicked(MouseEvent e) {
139                                 for (Component component : getComponents()) {
140                                         component.setBackground(UIManager.getColor("Panel.background"));
141                                 }
142                                 for (FilterSelectionListener filterSelectionListener : filterSelectionListeners.getListeners(FilterSelectionListener.class)) {
143                                         filterPanel.setBackground(Color.LIGHT_GRAY);
144                                         filterSelectionListener.filterSelected(filter);
145                                 }
146                                 selectedFilter = filterPanel;
147                         }
148
149                         @Override
150                         public void mouseEntered(MouseEvent mouseEvent) {
151                                 if (filterPanel != selectedFilter) {
152                                         filterPanel.setBackground(Color.white);
153                                 }
154                         }
155
156                         @Override
157                         public void mouseExited(MouseEvent mouseEvent) {
158                                 if (filterPanel != selectedFilter) {
159                                         filterPanel.setBackground(UIManager.getColor("Panel.background"));
160                                 }
161                         }
162                 });
163
164                 /* show filter. */
165                 add(filterPanel, new GridBagConstraints(position, level, width, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
166
167                 /* if the filter does not have connected filters, exit here. */
168                 Collection<Filter> sinks = pipeline.filters(filter);
169                 if (sinks.isEmpty()) {
170                         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));
171                         return;
172                 }
173
174                 /* iterate over the filter’s connected filters. */
175                 if (!sinks.isEmpty()) {
176                         int sinkWidth = width / sinks.size();
177                         int sinkIndex = 0;
178                         for (Filter connectedSink : sinks) {
179                                 /* distribute all filters evenly below this source. */
180                                 addFilter(connectedSink, level + 1, position + sinkIndex * sinkWidth, sinkWidth, filter);
181                                 sinkIndex++;
182                         }
183                 }
184         }
185
186         /**
187          * Creates a panel displaying a single filter.
188          *
189          * @param filter
190          *              The filter to display
191          * @return The created panel
192          */
193         private static JPanel createFilterPanel(final Filter filter, final Filter parentFilter) {
194                 JPanel filterPanel = new JPanel(new BorderLayout(12, 12));
195                 filterPanel.setBorder(createCompoundBorder(createEtchedBorder(), createEmptyBorder(0, 4, 0, 3)));
196                 filterPanel.add(new JLabel(filter.name()), BorderLayout.WEST);
197                 final JLabel titleLabel = new JLabel(filter.metadata().fullTitle());
198                 titleLabel.setFont(titleLabel.getFont().deriveFont(titleLabel.getFont().getSize2D() * 0.8f));
199                 filterPanel.add(titleLabel, BorderLayout.EAST);
200                 if (parentFilter != null) {
201                         titleLabel.setVisible(!parentFilter.metadata().fullTitle().equals(filter.metadata().fullTitle()));
202                 }
203                 filter.addMetadataListener(new MetadataListener() {
204
205                         @Override
206                         public void metadataUpdated(Filter filter, Metadata metadata) {
207                                 titleLabel.setText(metadata.fullTitle());
208                                 titleLabel.setVisible((parentFilter == null) || !parentFilter.metadata().fullTitle().equals(metadata.fullTitle()));
209                         }
210                 });
211                 return filterPanel;
212         }
213
214         /**
215          * Interface for objects that want to be notified if the user moves the mouse
216          * cursor over a filter.
217          *
218          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
219          */
220         public static interface FilterSelectionListener extends EventListener {
221
222                 /**
223                  * Notifies the listener that the mouse is now over the given filter.
224                  *
225                  * @param filter
226                  *              The filter now under the mouse
227                  */
228                 void filterSelected(Filter filter);
229
230         }
231
232 }