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 "AS IS", 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