View Javadoc

1   /* OpenLogViewer
2    *
3    * Copyright 2011
4    *
5    * This file is part of the OpenLogViewer project.
6    *
7    * OpenLogViewer software is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU General Public License as published by
9    * the Free Software Foundation, either version 3 of the License, or
10   * (at your option) any later version.
11   *
12   * OpenLogViewer software is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Public License for more details.
16   *
17   * You should have received a copy of the GNU General Public License
18   * along with any OpenLogViewer software.  If not, see http://www.gnu.org/licenses/
19   *
20   * I ask that if you make any changes to this file you fork the code on github.com!
21   *
22   */
23  package org.diyefi.openlogviewer.graphing;
24  
25  import java.awt.Dimension;
26  import java.awt.event.ActionEvent;
27  import java.awt.event.ActionListener;
28  import java.awt.event.ComponentEvent;
29  import java.awt.event.ComponentListener;
30  import java.awt.event.InputEvent;
31  import java.awt.event.KeyEvent;
32  import java.awt.event.KeyListener;
33  import java.awt.event.MouseEvent;
34  import java.awt.event.MouseListener;
35  import java.awt.event.MouseMotionListener;
36  import java.awt.event.MouseWheelEvent;
37  import java.awt.event.MouseWheelListener;
38  
39  import javax.swing.JPanel;
40  import javax.swing.Timer;
41  
42  import org.diyefi.openlogviewer.OpenLogViewer;
43  import org.diyefi.openlogviewer.genericlog.GenericLog;
44  
45  public class EntireGraphingPanel extends JPanel implements ActionListener, MouseMotionListener, MouseListener, MouseWheelListener, KeyListener, ComponentListener {
46  	private static final long serialVersionUID = 1L;
47  	public static final int LEFT_OFFSCREEN_POINTS_ZOOMED_IN = 0;
48  	public static final int RIGHT_OFFSCREEN_POINTS_ZOOMED_IN = 3;
49  	public static final int LEFT_OFFSCREEN_POINTS_ZOOMED_OUT = 2;
50  	public static final int RIGHT_OFFSCREEN_POINTS_ZOOMED_OUT = 2;
51  
52  	private MultiGraphLayeredPane multiGraph;
53  	private GraphPositionPanel graphPositionPanel;
54  	private double graphPosition;
55  	private int graphSize;
56  	private boolean playing;
57  	private boolean wasPlaying;
58  	private Timer playTimer;
59  	private Timer flingTimer;
60  	private boolean dragging;
61  	private boolean flinging;
62  	private long thePastMouseDragged;
63  	private long thePastLeftArrow;
64  	private long thePastRightArrow;
65  	private int scrollAcceleration;
66  	private int prevDragXCoord;
67  	private int flingInertia;
68  	private int zoom;
69  	private boolean zoomedOutBeyondOneToOne;
70  	private int oldComponentWidth;
71  
72  	public EntireGraphingPanel() {
73  		super();
74  		init();
75  	}
76  
77  	private void init() {
78  		this.setName("Graphing Panel");
79  		this.setLayout(new java.awt.BorderLayout());
80  		multiGraph = new MultiGraphLayeredPane();
81  		multiGraph.setPreferredSize(new Dimension(600, 400));
82  		this.add(multiGraph, java.awt.BorderLayout.CENTER);
83  		graphPositionPanel = new GraphPositionPanel();
84  		graphPositionPanel.setPreferredSize(new Dimension(600, 20));
85  		this.add(graphPositionPanel, java.awt.BorderLayout.SOUTH);
86  		zoom = 1;
87  		zoomedOutBeyondOneToOne = false;
88  		oldComponentWidth = this.getWidth();
89  		resetGraphPosition();
90  		setGraphSize(0);
91  		playing = false;
92  		wasPlaying = false;
93  		playTimer = new Timer(10, this);
94  		playTimer.setInitialDelay(0);
95  		flingTimer = new Timer(10, this);
96  		flingTimer.setInitialDelay(0);
97  		addMouseListener(this);
98  		addMouseMotionListener(this);
99  		addMouseWheelListener(this);
100 		addMouseListener(multiGraph.getInfoPanel());
101 		addMouseMotionListener(multiGraph.getInfoPanel());
102 		stopDragging();
103 		stopFlinging();
104 		thePastMouseDragged = System.currentTimeMillis();
105 		thePastLeftArrow = System.currentTimeMillis();
106 		thePastRightArrow = System.currentTimeMillis();
107 		scrollAcceleration = 0;
108 	}
109 
110 	public final void actionPerformed(final ActionEvent e) {
111 		if (playing && graphPosition < getGraphPositionMax()) {
112 			if (zoomedOutBeyondOneToOne) {
113 				moveGraphPosition(zoom);
114 			} else {
115 				moveGraphPosition(1);
116 			}
117 		} else if ((flinging && graphPosition < getGraphPositionMax()) && (graphPosition > getGraphPositionMin())) {
118 			if (flingInertia == 0) {
119 				stopFlinging();
120 			} else {
121 				moveEntireGraphingPanel(flingInertia);
122 				if (flingInertia > 0) {
123 					flingInertia--;
124 				} else {
125 					flingInertia++;
126 				}
127 			}
128 		}
129 	}
130 
131 	public final MultiGraphLayeredPane getMultiGraphLayeredPane() {
132 		return multiGraph;
133 	}
134 
135 	public final GraphPositionPanel getGraphPositionPanel() {
136 		return graphPositionPanel;
137 	}
138 
139 	public final void setLog(final GenericLog genLog) {
140 		playing = false;
141 		resetGraphPosition();
142 		multiGraph.setLog(genLog);
143 		graphPositionPanel.setLog(genLog);
144 	}
145 
146 
147 	/**
148 	 * The tightest the user should be allowed to zoom in.
149 	 */
150 	private int getTightestZoom() {
151 		return this.getWidth() - 1;
152 	}
153 
154 	/**
155 	 * The widest the user should be allowed to zoom out.
156 	 */
157 	private int getWidestZoom() {
158 		return (multiGraph.graphSize() / 2);
159 	}
160 
161 	/**
162 	 * Zoom in by one. This control zooms finer than the coarse zoom control.
163 	 * This assumes you are zooming in on the data centered in the screen.
164 	 * If you need to zoom in on a different location then you must move
165 	 * the graph accordingly.
166 	 */
167 	public final void zoomIn() {
168 		final double graphWidth = this.getWidth();
169 		double move = 0;
170 
171 		if (zoomedOutBeyondOneToOne) {
172 			if (zoom == 2) {
173 				zoomedOutBeyondOneToOne = false;
174 			}
175 			zoom--;
176 			move = graphWidth / (double) (zoom * 2);
177 		} else if (zoom < getTightestZoom()) {
178 			move = graphWidth / (double) (zoom * 2);
179 			zoom++;
180 		}
181 
182 		moveEntireGraphingPanel(move);
183 	}
184 
185 	/**
186 	 * Zoom in using steps larger the further away from 1:1 you are.
187 	 * This assumes you are zooming in on the data centered in the screen.
188 	 * If you need to zoom in on a different location then you must use
189 	 * zoomIn() repeatedly coupled with a move each time.
190 	 */
191 	public final void zoomInCoarse() {
192 		final int zoomAmount = (int) Math.sqrt(zoom);
193 		for (int i = 0; i < zoomAmount; i++) {
194 			zoomIn();
195 		}
196 	}
197 
198 	/**
199 	 * Zoom out by one. This control zooms finer than the coarse zoom control.
200 	 * This assumes you are zooming out from the data centered in the screen.
201 	 * If you need to zoom out from a different location then you must move
202 	 * the graph accordingly.
203 	 */
204 	public final void zoomOut() {
205 		final double graphWidth = this.getWidth();
206 		double move = 0;
207 
208 		if (!zoomedOutBeyondOneToOne) {
209 			if (zoom == 1) {
210 				zoomedOutBeyondOneToOne = true;
211 				zoom = 2;
212 				move = graphWidth / (double) (zoom * 2);
213 			} else {
214 				move = graphWidth / (double) (zoom * 2);
215 				zoom--;
216 			}
217 		} else if (zoom < getWidestZoom()) {
218 			zoom++;
219 			move = graphWidth / (double) (zoom * 2);
220 		}
221 
222 		moveEntireGraphingPanel(-move);
223 	}
224 
225 	/**
226 	 * Zoom out using steps larger the further away from 1:1 you are.
227 	 * This assumes you are zooming out with the data centered in the screen.
228 	 * If you need to zoom out on a different location then you must use
229 	 * zoomOut() repeatedly coupled with a move each time.
230 	 */
231 	public final void zoomOutCoarse() {
232 		final int zoomAmount = (int) Math.sqrt(zoom);
233 		for (int i = 0; i < zoomAmount; i++) {
234 			zoomOut();
235 		}
236 	}
237 
238 	/**
239 	 * Zoom the graph so that if it is centered, then the
240 	 * entire graph will fit within the display. Usually
241 	 * this will result in ultimately zooming out, but if the
242 	 * graph is small enough and/or the display is large enough
243 	 * then zooming in will be more appropriate.
244 	 *
245 	 * If the graph will fit perfectly inside the display
246 	 * then it will be sized down one more time so that
247 	 * there is always at least 4 pixels of blank space to
248 	 * the left and right of the graph so the user will
249 	 * know they are seeing the entire graph trace.
250 	 */
251 	private void zoomGraphToFit(final int dataPointsToFit) {
252 		final int graphWindowWidth = this.getWidth() - 8; //Remove 4 pixels per side.
253 		int dataPointsThatFitInDisplay = 0;
254 		if (zoomedOutBeyondOneToOne) {
255 			dataPointsThatFitInDisplay = graphWindowWidth * zoom;
256 		} else {
257 			dataPointsThatFitInDisplay =  graphWindowWidth / zoom;
258 		}
259 
260 		// Zoom in until the data no longer fits in the display.
261 		while (dataPointsToFit < dataPointsThatFitInDisplay && zoom != getTightestZoom()) {
262 			zoomIn();
263 			if (zoomedOutBeyondOneToOne) {
264 				dataPointsThatFitInDisplay = graphWindowWidth * zoom;
265 			} else {
266 				dataPointsThatFitInDisplay =  graphWindowWidth / zoom;
267 			}
268 		}
269 
270 		// Zoom out one or more times until the data just fits in the display.
271 		while (dataPointsToFit > dataPointsThatFitInDisplay && zoom != getWidestZoom()) {
272 			zoomOut();
273 			if (zoomedOutBeyondOneToOne) {
274 				dataPointsThatFitInDisplay = graphWindowWidth * zoom;
275 			} else {
276 				dataPointsThatFitInDisplay =  graphWindowWidth / zoom;
277 			}
278 		}
279 	}
280 
281 	public boolean isZoomedOutBeyondOneToOne() {
282 		return zoomedOutBeyondOneToOne;
283 	}
284 
285 	public final void play() {
286 		if (playing) {
287 			pause();
288 		} else {
289 			playing = true;
290 			stopDragging();
291 			stopFlinging();
292 			playTimer.start();
293 		}
294 	}
295 
296 	public final void pause() {
297 		playing = false;
298 		playTimer.stop();
299 		stopDragging();
300 		stopFlinging();
301 	}
302 
303 	/**
304 	 * Increases the speed of the graph by 1 ms until 0, at which speed cannot be advanced any further and will essentially update as fast as possible.
305 	 */
306 	public final void fastForward() {
307 		final int currentDelay = playTimer.getDelay();
308 		if (currentDelay > 0) {
309 			playTimer.setDelay(currentDelay - 1);
310 		}
311 	}
312 
313 	public final void eject() {
314 		resetGraphPosition();
315 	}
316 
317 	public final void stop() {
318 		playing = false;
319 		wasPlaying = false;
320 		playTimer.stop();
321 		resetGraphPosition();
322 	}
323 
324 	/**
325 	 * Slows the speed of playback by 1 ms
326 	 */
327 	public final void slowDown() {
328 		final int currentDelay = playTimer.getDelay();
329 		playTimer.setDelay(currentDelay + 1);
330 	}
331 
332 	public final void fling() {
333 		flinging = true;
334 		flingTimer.start();
335 	}
336 
337 	private double getGraphPositionMin() {
338 		double min = 0.0;
339 		if (zoomedOutBeyondOneToOne) {
340 			min = -((this.getWidth() - 1) * zoom);
341 		} else {
342 			min = -(((double) this.getWidth() - 1.0) / (double) zoom);
343 		}
344 		return min;
345 	}
346 
347 	public final double getGraphPosition() {
348 		return graphPosition;
349 	}
350 
351 	private int getGraphPositionMax() {
352 		if (zoom == getWidestZoom()) {
353 			int size = graphSize - (LEFT_OFFSCREEN_POINTS_ZOOMED_OUT * zoom);
354 			if (size < 0) {
355 				size = 0;
356 			}
357 			return size;
358 		}
359 		return graphSize;
360 	}
361 
362 	public final void setGraphPosition(final double newPos) {
363 		graphPosition = newPos;
364 		repaint();
365 	}
366 
367 	/**
368 	 * How many available data records we are dealing with.
369 	 */
370 	public final void setGraphSize(final int newGraphSize) {
371 		graphSize = newGraphSize;
372 		if (graphSize > 0) {
373 			centerGraphPosition(0, graphSize);
374 			zoomGraphToFit(graphSize);
375 		}
376 	}
377 
378 	/**
379 	 * Move the graph to the right so that only one valid
380 	 * data point shows on the right-most part of the display.
381 	 */
382 	private void resetGraphPosition() {
383 		setGraphPosition(getGraphPositionMin());
384 	}
385 
386 	/**
387 	 * Move the graph to the center of the two provided graph positions
388 	 * so that there are equal data points to the left and to the right.
389 	 *
390 	 * Right now the method is expecting to get integer data points as
391 	 * it should be impossible to select fractions of a data point.
392 	 */
393 	private void centerGraphPosition(final int beginPosition, final int endPosition) {
394 		final int halfScreen = this.getWidth() / 2;
395 		double pointsThatFitInHalfScreen = 0;
396 		if (zoomedOutBeyondOneToOne) {
397 			pointsThatFitInHalfScreen = halfScreen * zoom;
398 		} else {
399 			pointsThatFitInHalfScreen = halfScreen / zoom;
400 		}
401 		final int distanceBetweenPositions = endPosition - beginPosition;
402 		final double halfwayBetweenTwoPositions = distanceBetweenPositions / 2;
403 		final double centerPosition = (beginPosition + halfwayBetweenTwoPositions) - pointsThatFitInHalfScreen;
404 		setGraphPosition(centerPosition);
405 	}
406 
407 	/**
408 	 * Move the graph to the left so that only one valid
409 	 * data point shows on the left-most part of the display.
410 	 */
411 	private void goToLastGraphPosition() {
412 		setGraphPosition(getGraphPositionMax());
413 	}
414 
415 	public final boolean isPlaying() {
416 		return playing;
417 	}
418 
419 	public final int getZoom() {
420 		return zoom;
421 	}
422 
423 	/**
424 	 * Take the current graph position and move amount positions forward.
425 	 */
426 	private void moveGraphPosition(final double amount) {
427 		final double newPos = graphPosition + amount;
428 		if (newPos > getGraphPositionMax()) {
429 			goToLastGraphPosition();
430 		} else if (newPos < getGraphPositionMin()) {
431 			resetGraphPosition();
432 		} else {
433 			setGraphPosition(newPos);
434 		}
435 	}
436 
437 	/**
438 	 * Move the graph position to newPosition where newPosition is dictated by
439 	 * an x screen coordinate.
440 	 */
441 	private void moveEntireGraphingPanel(final double newPosition) {
442 		double move = -1.0;
443 		if (zoomedOutBeyondOneToOne) {
444 			move = newPosition * zoom;
445 		} else {
446 			move = newPosition / zoom;
447 		}
448 		if (graphPosition + move < getGraphPositionMax()) {
449 			if (graphPosition + move < getGraphPositionMin()) {
450 				resetGraphPosition();
451 			} else {
452 				moveGraphPosition(move);
453 			}
454 		} else {
455 			goToLastGraphPosition();
456 		}
457 	}
458 
459 	private void stopDragging() {
460 		dragging = false;
461 		prevDragXCoord = -1;
462 	}
463 
464 	private void stopFlinging() {
465 		flinging = false;
466 		flingInertia = 0;
467 	}
468 
469 	// Mouse listener functionality
470 	@Override
471 	public final void mouseClicked(final MouseEvent e) {
472 		if (!dragging) {
473 			final int half = this.getWidth() / 2;
474 			moveEntireGraphingPanel(e.getX() - half);
475 		} else {
476 			stopDragging();
477 			stopFlinging();
478 		}
479 	}
480 
481 	@Override
482 	public final void mouseDragged(final MouseEvent e) {
483 		dragging = true;
484 		final int xMouseCoord = e.getX();
485 		if ((prevDragXCoord > 0) && (prevDragXCoord != xMouseCoord)) {
486 			moveEntireGraphingPanel(prevDragXCoord - xMouseCoord);
487 			flingInertia = ((prevDragXCoord - xMouseCoord) * 2);
488 			thePastMouseDragged = System.currentTimeMillis();
489 		}
490 		prevDragXCoord = xMouseCoord;
491 	}
492 
493 	@Override
494 	public final void mouseMoved(final MouseEvent e) {
495 		// What should be here?
496 		// Ben says eventually there might be stuff here, and it is required implementation for the MouseMovementListener interface.
497 		// Fred says thanks! :-)
498 	}
499 
500 	@Override
501 	public final void mouseEntered(final MouseEvent e) {
502 		// What should be here?
503 		// Ben says eventually there might be stuff here, and it is required implementation for the MouseMovementListener interface.
504 		// Fred says thanks! :-)
505 	}
506 
507 	@Override
508 	public final void mouseExited(final MouseEvent e) {
509 		// What should be here?
510 		// Ben says eventually there might be stuff here, and it is required implementation for the MouseMovementListener interface.
511 		// Fred says thanks! :-)
512 	}
513 
514 	@Override
515 	public final void mousePressed(final MouseEvent e) {
516 		wasPlaying = playing;
517 		if (playing) {
518 			pause();
519 		}
520 
521 		stopDragging();
522 		stopFlinging();
523 	}
524 
525 	@Override
526 	public final void mouseReleased(final MouseEvent e) {
527 		stopDragging();
528 
529 		final long now = System.currentTimeMillis();
530 		if ((now - thePastMouseDragged) > 50) {
531 			stopFlinging(); // If over 50 milliseconds since dragging then don't fling
532 		}
533 
534 		if (flingInertia != 0) {
535 			fling();
536 		}
537 
538 		if (wasPlaying) {
539 			play();
540 		}
541 	}
542 
543 	@Override
544 	public final void mouseWheelMoved(final MouseWheelEvent e) {
545 		final int xMouseCoord = e.getX();
546 		final double center = this.getWidth() / 2.0;
547 		final int notches = e.getWheelRotation();
548 		double move = 0;
549 		final int zoomAmount = (int)Math.sqrt(zoom);
550 		if (notches < 0) {
551 			for (int i = 0; i < zoomAmount; i++) {
552 				if (zoomedOutBeyondOneToOne) {
553 					move = (xMouseCoord - center) / (zoom - 1.0);
554 				} else {
555 					move = (xMouseCoord - center) / zoom;
556 				}
557 				if (!(!zoomedOutBeyondOneToOne && zoom == getTightestZoom())) {
558 					zoomIn();
559 					moveEntireGraphingPanel(move);
560 				}
561 			}
562 		} else {
563 			for (int i = 0; i < zoomAmount; i++) {
564 				if (zoomedOutBeyondOneToOne || zoom == 1) {
565 					move = -(xMouseCoord - center) / (zoom + 1.0);
566 				} else {
567 					move = -(xMouseCoord - center) / zoom;
568 				}
569 				if (!(zoomedOutBeyondOneToOne && zoom == getWidestZoom())) {
570 					zoomOut();
571 					moveEntireGraphingPanel(move);
572 				}
573 			}
574 		}
575 	}
576 
577 	// Key listener functionality
578 	@Override
579 	public final void keyPressed(final KeyEvent e) {
580 		switch (e.getKeyCode()) {
581 			// Play key binding
582 			case KeyEvent.VK_SPACE: {
583 				play();
584 				break;
585 			}
586 
587 			// Enter full screen key binding
588 			case KeyEvent.VK_ENTER: {
589 				if (e.getModifiers() == InputEvent.ALT_MASK
590 						&& e.getKeyLocation() == KeyEvent.KEY_LOCATION_STANDARD) {
591 					OpenLogViewer.getInstance().enterFullScreen();
592 				}
593 				break;
594 			}
595 
596 			// Exit full screen key binding
597 			case KeyEvent.VK_ESCAPE: {
598 				OpenLogViewer.getInstance().exitFullScreen();
599 				break;
600 			}
601 
602 			// Toggle full screen key binding
603 			case KeyEvent.VK_F11: {
604 				OpenLogViewer.getInstance().toggleFullScreen();
605 				break;
606 			}
607 
608 			// Home key binding
609 			case KeyEvent.VK_HOME: {
610 				resetGraphPosition();
611 				break;
612 			}
613 
614 			// End key binding
615 			case KeyEvent.VK_END: {
616 				goToLastGraphPosition();
617 				break;
618 			}
619 
620 			// Scroll left key bindings
621 			case KeyEvent.VK_PAGE_UP: {
622 				//Big scroll
623 				moveEntireGraphingPanel(-(this.getWidth() * 0.75));
624 				break;
625 			}
626 
627 			case KeyEvent.VK_LEFT:
628 			case KeyEvent.VK_KP_LEFT: {
629 				int localZoom = zoom;
630 				if(zoomedOutBeyondOneToOne){
631 					localZoom = 1;
632 				}
633 				if (e.getModifiers() == InputEvent.CTRL_MASK) {
634 					//Big scroll
635 					moveEntireGraphingPanel(-(this.getWidth() * 0.75));
636 				} else {
637 					final long now = System.currentTimeMillis();
638 					final long delay = now - thePastLeftArrow;
639 					if (delay < 50) {
640 						scrollAcceleration++;
641 						moveEntireGraphingPanel(-localZoom - (scrollAcceleration * localZoom));
642 					} else {
643 						scrollAcceleration = 0;
644 						moveEntireGraphingPanel(-localZoom);
645 					}
646 					thePastLeftArrow = System.currentTimeMillis();
647 				}
648 				break;
649 			}
650 
651 			// Scroll right key bindings
652 			case KeyEvent.VK_PAGE_DOWN: {
653 				//Big scroll
654 				moveEntireGraphingPanel(this.getWidth() * 0.75);
655 				break;
656 			}
657 
658 			case KeyEvent.VK_RIGHT:
659 			case KeyEvent.VK_KP_RIGHT: {
660 				int localZoom = zoom;
661 				if(zoomedOutBeyondOneToOne){
662 					localZoom = 1;
663 				}
664 				if (e.getModifiers() == InputEvent.CTRL_MASK) {
665 					//Big scroll
666 					moveEntireGraphingPanel(this.getWidth() * 0.75);
667 				} else {
668 					final long now = System.currentTimeMillis();
669 					final long delay = now - thePastRightArrow;
670 					if (delay < 50) {
671 						scrollAcceleration++;
672 						moveEntireGraphingPanel(localZoom + (scrollAcceleration * localZoom));
673 					} else {
674 						scrollAcceleration = 0;
675 						moveEntireGraphingPanel(localZoom);
676 					}
677 					thePastRightArrow = System.currentTimeMillis();
678 				}
679 				break;
680 			}
681 
682 			// Zoom in key bindings
683 			case KeyEvent.VK_UP:
684 			case KeyEvent.VK_KP_UP: {
685 				zoomInCoarse();
686 				break;
687 			}
688 
689 			case KeyEvent.VK_ADD: {
690 				if (e.getModifiers() == InputEvent.CTRL_MASK) {
691 					zoomInCoarse();
692 				}
693 				break;
694 			}
695 
696 			// Zoom out key bindings
697 			case KeyEvent.VK_DOWN:
698 			case KeyEvent.VK_KP_DOWN: {
699 				zoomOutCoarse();
700 				break;
701 			}
702 
703 			case KeyEvent.VK_SUBTRACT: {
704 				if (e.getModifiers() == InputEvent.CTRL_MASK) {
705 					zoomOutCoarse();
706 				}
707 				break;
708 			}
709 		}
710 	}
711 
712 	@Override
713 	public final void keyReleased(final KeyEvent e) {
714 		// What should be here?
715 		// Ben says eventually there might be stuff here, and it is required implementation for the KeyListener interface.
716 		// Fred says thanks! :-)
717 	}
718 
719 	@Override
720 	public final void keyTyped(final KeyEvent e) {
721 		// What should be here?
722 		// Ben says eventually there might be stuff here, and it is required implementation for the KeyListener interface.
723 		// Fred says thanks! :-)
724 	}
725 
726 	@Override
727 	public void componentHidden(final ComponentEvent e) {
728 		// Ben says eventually there might be stuff here, and it is required implementation for the ComponentListener interface.
729 		// Fred says thanks! :-)
730 	}
731 
732 	// Call resize event handler because the Mac sort of treats resizing as a move
733 	@Override
734 	public void componentMoved(final ComponentEvent e) {
735 		componentResized(e);
736 	}
737 
738 	@Override
739 	public void componentResized(final ComponentEvent e) {
740 		int newWidth = this.getWidth();
741 		if (newWidth != oldComponentWidth) {
742 			double move = 0.0;
743 			int amount = newWidth - oldComponentWidth;
744 			if (zoomedOutBeyondOneToOne) {
745 				move = -(amount * zoom);
746 			} else {
747 				move = -((double) amount / (double) zoom);
748 			}
749 			move /= 2.0;
750 			oldComponentWidth = newWidth;
751 			moveGraphPosition(move);
752 		}
753 	}
754 
755 	@Override
756 	public void componentShown(final ComponentEvent e) {
757 		// Ben says eventually there might be stuff here, and it is required implementation for the ComponentListener interface.
758 		// Fred says thanks! :-)
759 	}
760 }