001    package org.LiveGraph.gui;
002    
003    import java.awt.BorderLayout;
004    import java.awt.Color;
005    import java.awt.Dimension;
006    import java.awt.Graphics;
007    import java.awt.GridLayout;
008    import java.awt.Point;
009    import java.awt.event.MouseAdapter;
010    import java.awt.event.MouseEvent;
011    import java.awt.event.MouseMotionAdapter;
012    import java.awt.event.WindowAdapter;
013    import java.awt.event.WindowEvent;
014    import java.awt.geom.Point2D;
015    import java.util.ArrayList;
016    import java.util.Collections;
017    import java.util.Iterator;
018    import java.util.List;
019    
020    import javax.swing.BorderFactory;
021    import javax.swing.JFrame;
022    import javax.swing.JLabel;
023    import javax.swing.JPanel;
024    import javax.swing.Popup;
025    import javax.swing.PopupFactory;
026    import javax.swing.WindowConstants;
027    import javax.swing.border.EtchedBorder;
028    
029    import org.LiveGraph.LiveGraph;
030    import org.LiveGraph.dataCache.CacheObserver;
031    import org.LiveGraph.dataCache.DataCache;
032    import org.LiveGraph.plot.Plotter;
033    import org.LiveGraph.settings.DataSeriesSettings;
034    import org.LiveGraph.settings.GraphSettings;
035    import org.LiveGraph.settings.ObservableSettings;
036    import org.LiveGraph.settings.SettingsObserver;
037    
038    import com.softnetConsult.utils.swing.SwingTools;
039    
040    
041    /**
042     * The plot window of the application.
043     * 
044     * <p style="font-size:smaller;">This product includes software developed by the
045     *    <strong>LiveGraph</strong> project and its contributors.<br />
046     *    (<a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>)<br />
047     *    Copyright (c) 2007 G. Paperin.<br />
048     *    All rights reserved.
049     * </p>
050     * <p style="font-size:smaller;">File: PlotWindow.java</p> 
051     * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
052     *    without modification, are permitted provided that the following terms and conditions are met:
053     * </p>
054     * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
055     *    acknowledgement of the LiveGraph project and its web-site, the above copyright notice,
056     *    this list of conditions and the following disclaimer.<br />
057     *    2. Redistributions in binary form must reproduce the above acknowledgement of the
058     *    LiveGraph project and its web-site, the above copyright notice, this list of conditions
059     *    and the following disclaimer in the documentation and/or other materials provided with
060     *    the distribution.<br />
061     *    3. All advertising materials mentioning features or use of this software or any derived
062     *    software must display the following acknowledgement:<br />
063     *    <em>This product includes software developed by the LiveGraph project and its
064     *    contributors.<br />(http://www.live-graph.org)</em><br />
065     *    4. All advertising materials distributed in form of HTML pages or any other technology
066     *    permitting active hyper-links that mention features or use of this software or any
067     *    derived software must display the acknowledgment specified in condition 3 of this
068     *    agreement, and in addition, include a visible and working hyper-link to the LiveGraph
069     *    homepage (http://www.live-graph.org).
070     * </p>
071     * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY
072     *    OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
073     *    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL
074     *    THE AUTHORS, CONTRIBUTORS OR COPYRIGHT  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
075     *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  FROM, OUT OF OR
076     *    IN CONNECTION WITH THE SOFTWARE OR THE USE OR  OTHER DEALINGS IN THE SOFTWARE.
077     * </p>
078     * 
079     * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
080     * @version {@value org.LiveGraph.LiveGraph#version}
081     */
082    public class PlotWindow extends JFrame implements CacheObserver, SettingsObserver {
083    
084    private JLabel statusLabel = null;
085    private Plotter plotter = null;
086    private JPanel canvas = null;
087    private List<SeriesHighlightListener> seriesHighlightListeners = null;
088    private boolean highlightDataPoints = true;
089    private Popup hlSerPopup = null; 
090    private List<String> seriesLabels = null;
091    
092    /**
093     * Created and setts up a plotter window.
094     * 
095     * @param plotter The plotter object for this window.
096     */
097    public PlotWindow(Plotter plotter) {
098            super();
099            
100            if (null == plotter)
101                    throw new NullPointerException("Cannot use a null plotter");
102            
103            this.seriesHighlightListeners = new ArrayList<SeriesHighlightListener>();
104            this.plotter = plotter;
105            this.highlightDataPoints = true;
106            if (null != LiveGraph.application().getGraphSettings())
107                    this.highlightDataPoints = LiveGraph.application().getGraphSettings().getHighlightDataPoints();
108            this.seriesLabels = new ArrayList<String>();
109            this.initialize();
110    }
111    
112    /**
113     * This method initializes the window.
114     */
115    private void initialize() {
116            
117            // Window settings:
118            
119            //final PlotWindow PLOT_WIN = this;
120            this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
121            Dimension frameDim = new Dimension(450, 500);
122            this.setPreferredSize(frameDim);
123            this.setBounds(480, 5, frameDim.width, frameDim.height);        
124            this.setTitle("Data plot (LiveGraph)");
125            
126            // Hide-show listener:
127            
128            this.addWindowListener(new WindowAdapter() {
129                    @Override public void windowClosing(WindowEvent e) {
130                            LiveGraph.application().setDisplayPlotWindow(false);
131                    }
132            });
133            
134            // Layout:
135            
136            JPanel panel = null;
137            getContentPane().setLayout(new BorderLayout());
138            
139            
140            // Ststus label:
141            
142            statusLabel = new JLabel();
143            statusLabel.setFont(SwingTools.getPlainFont(statusLabel));
144            
145            panel = new JPanel(new BorderLayout());
146            
147            panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(5, 3, 5, 5),
148                                                                                                               BorderFactory.createEtchedBorder(EtchedBorder.RAISED)));     
149            panel.setPreferredSize(new Dimension(frameDim.width, 30));
150            panel.add(statusLabel, BorderLayout.CENTER);
151            
152            getContentPane().add(panel , BorderLayout.SOUTH);
153            
154            // Plot canvas: 
155            
156            hlSerPopup = null; 
157            canvas = new JPanel() {
158                    @Override public void paint(Graphics g) {
159                            super.paint(g);
160                            plotter.paint(g);
161                    }               
162                    @Override public void setBounds(int x, int y, int width, int height) {
163                            super.setBounds(x, y, width, height);
164                            plotter.setScreenSize(width, height);
165                    }
166            };
167            canvas.setBackground(Color.WHITE);
168            canvas.addMouseListener(new MouseAdapter() {
169                    @Override public void mouseExited(MouseEvent e) {
170                            setStatusMessage("");
171                            if (highlightDataPoints) {
172                                    List<Integer> l = Collections.emptyList();
173                                    notifySeriesHighlightListeners(l);                              
174                            }
175                    }
176                    @Override public void mouseReleased(MouseEvent e) {
177                            if (highlightDataPoints && MouseEvent.BUTTON1 == e.getButton() && null != hlSerPopup) {
178                                    hlSerPopup.hide();
179                                    hlSerPopup = null;
180                            }
181                    }
182                    @Override public void mousePressed(MouseEvent e) {
183                            if (highlightDataPoints && MouseEvent.BUTTON1 == e.getButton())
184                                    showHlSeriesPopup(plotter.highlightAround(e.getPoint()), e.getXOnScreen(), e.getYOnScreen());
185                    }
186            });
187            canvas.addMouseMotionListener(new MouseMotionAdapter() {
188                    @Override public void mouseDragged(MouseEvent e) { this.mouseMoved(e); }
189                    @Override public void mouseMoved(MouseEvent e) {
190                            if (plotter.screenTooSmall()) {
191                                    setStatusMessage("Enlarge this window.");
192                                    return;
193                            }
194                            if (!plotter.getShowAtLeastOneSeries()) {
195                                    setStatusMessage("No data selected for display.");
196                                    return;
197                            } 
198                            Point ep = e.getPoint();
199                            Point2D.Double dp = plotter.screenToDataPoint(ep);
200                            setStatusMessage(String.format("(%.3f, %.3f)", dp.x, dp.y));
201                            if (highlightDataPoints) {
202                                    List<Integer> hlSeries = plotter.highlightAround(ep);
203                                    notifySeriesHighlightListeners(hlSeries);                       
204                                    canvas.repaint();
205                                    if (MouseEvent.MOUSE_DRAGGED == e.getID() && null != hlSerPopup)
206                                            showHlSeriesPopup(hlSeries, e.getXOnScreen(), e.getYOnScreen());
207                            }
208                    }
209            });
210            
211            panel = new JPanel(new BorderLayout());
212            panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(5, 5, 3, 5),
213                                                                                                               BorderFactory.createEtchedBorder(EtchedBorder.RAISED)));
214            panel.add(canvas, BorderLayout.CENTER);
215            getContentPane().add(panel, BorderLayout.CENTER);
216            
217            
218    } // private void initialize()
219    
220    
221    /**
222     * Shows the popup with the labels of highlighted data series.
223     * 
224     * @param hlSeries List of indices of highlighted data series.
225     * @param mx Current mouse position on screen (x).
226     * @param my Current mouse position on screen (y).
227     */
228    private void showHlSeriesPopup(List<Integer> hlSeries, int mx, int my) {
229                    
230            JPanel panel = new JPanel(new GridLayout(hlSeries.size(), 1, 2, 2));
231            panel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
232            JLabel label = null;
233            
234            if (hlSeries.isEmpty()) {
235                    
236                    label = new JLabel("- no data series highlighted -");
237                    panel.add(label);
238                    
239            } else {
240                    for (int s : hlSeries) {
241                            
242                            label = new JLabel(seriesLabels.get(s));
243                            label.setFont(SwingTools.getPlainFont(label));
244                            label.setForeground(LiveGraph.application().getDataSeriesSettings().getColour(s));
245                            panel.add(label);       
246                    }
247            }
248            
249            
250            if (null != hlSerPopup)
251                    hlSerPopup.hide();                                                      
252            hlSerPopup = PopupFactory.getSharedInstance().getPopup(canvas, panel, mx + 15, my + 15);
253            hlSerPopup.show();
254    }
255    
256    /**
257     * Update the status bar message.
258     * @param msg message.
259     */
260    public void setStatusMessage(String msg) {
261            if (null == msg)
262                    return;
263            statusLabel.setText(msg);
264    }
265    
266    /**
267     * Repaints the plot canvas when the cache was updated.
268     */
269    public void cacheEventFired(DataCache cache, CacheEvent event) {
270            
271            switch(event) {
272                    
273                    case UpdateData:
274                            canvas.repaint();
275                            break;
276                            
277                    case UpdateLabels:
278                            seriesLabels.clear();
279                            Iterator<String> labls = cache.iterateDataSeriesLabels();
280                            while (labls.hasNext())
281                                    seriesLabels.add(labls.next());
282                            break;
283                    
284                    default:
285                            break;                  
286            }
287    } 
288    
289    /**
290     * Dispatches settings change events.
291     * @see #settingHasChanged(DataSeriesSettings, String)
292     * @see #settingHasChanged(GraphSettings, String)
293     */
294    public void settingHasChanged(ObservableSettings settings, Object info) {
295            
296            if (null == info || null == settings)
297                    return;
298            
299            if ((settings instanceof DataSeriesSettings) && (info instanceof String)) {
300                    settingHasChanged((DataSeriesSettings) settings, (String) info);
301                    return;
302            }
303            
304            if ((settings instanceof GraphSettings) && (info instanceof String)) {
305                    settingHasChanged((GraphSettings) settings, (String) info);
306                    return;
307            }
308    }
309    
310    /**
311     * Repaints the plot canvas when the data file settings change.
312     * 
313     * @param settings Series settings (not currentluy used).
314     * @param info Event info (not currently used).
315     */
316    public void settingHasChanged(DataSeriesSettings settings, String info) {
317            
318            if (null == info || null == settings)
319                    return;
320            
321            canvas.repaint();
322    }
323    
324    /**
325     * Repaints the plot canvas when the graph settings change.
326     * 
327     * @param settings Series settings (not currentluy used).
328     * @param info Event info (not currently used).
329     */
330    public void settingHasChanged(GraphSettings settings, String info) {
331            
332            if (null == info || null == settings)
333                    return;
334            
335            if (info.equals("HighlightDataPoints") || info.equals("load")) {
336                    highlightDataPoints = settings.getHighlightDataPoints();
337            }
338            
339            canvas.repaint();
340    }
341    
342    /**
343     * Adds a {@code SeriesHighlightListener}.
344     * @param listener A listener.
345     * @return Whether the listener was actually added because it did not exist.
346     */
347    public boolean addSeriesHighlightListeners(SeriesHighlightListener listener) {
348            return seriesHighlightListeners.add(listener);
349    }
350    
351    /**
352     * Removed a {@code SeriesHighlightListener}.
353     * @param listener A listener.
354     * @return Whether the listener was actually removed because it was there.
355     */
356    public boolean removeSeriesHighlightListeners(SeriesHighlightListener listener) {
357            return seriesHighlightListeners.remove(listener);
358    }
359    
360    /**
361     * @return Number of {@code SeriesHighlightListener}s.
362     */
363    public int countSeriesHighlightListeners() {
364            return seriesHighlightListeners.size();
365    }
366    
367    /**
368     * Notifies the listeners about the highlighted series.
369     * @param hlSeries highlighted series indices.
370     */
371    private void notifySeriesHighlightListeners(List<Integer> hlSeries) {
372            for (SeriesHighlightListener listener : seriesHighlightListeners)
373                    listener.highlightSeries(hlSeries);
374    }
375    
376    
377    } // public class PlotWindow