]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.trend/src/org/simantics/trend/impl/TrendParticipant.java
Render last known value in time series chart hairline value tip
[simantics/platform.git] / bundles / org.simantics.trend / src / org / simantics / trend / impl / TrendParticipant.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2011 Association for Decentralized Information Management in
3  * Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.trend.impl;
13
14 import java.awt.Cursor;
15 import java.awt.Toolkit;
16 import java.awt.datatransfer.Clipboard;
17 import java.awt.datatransfer.StringSelection;
18 import java.awt.geom.Point2D;
19 import java.awt.geom.Rectangle2D;
20 import java.awt.print.PrinterException;
21 import java.io.File;
22 import java.io.IOException;
23
24 import org.simantics.databoard.accessor.error.AccessorException;
25 import org.simantics.databoard.util.Bean;
26 import org.simantics.g2d.canvas.Hints;
27 import org.simantics.g2d.canvas.ICanvasContext;
28 import org.simantics.g2d.canvas.IMouseCursorHandle;
29 import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
30 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
31 import org.simantics.g2d.canvas.impl.HintReflection.HintListener;
32 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
33 import org.simantics.g2d.chassis.ITooltipProvider;
34 import org.simantics.g2d.participant.TimeParticipant;
35 import org.simantics.history.HistoryException;
36 import org.simantics.history.csv.CSVFormatter;
37 import org.simantics.history.util.ProgressMonitor;
38 import org.simantics.scenegraph.g2d.IG2DNode;
39 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
40 import org.simantics.scenegraph.g2d.events.MouseEvent;
41 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
42 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
43 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
44 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent;
45 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
46 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseWheelMovedEvent;
47 import org.simantics.scenegraph.g2d.events.TimeEvent;
48 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
49 import org.simantics.scenegraph.g2d.events.command.Commands;
50 import org.simantics.trend.configuration.ItemPlacement;
51 import org.simantics.trend.configuration.TrendItem.Renderer;
52 import org.simantics.trend.util.PrintUtil;
53 import org.simantics.utils.datastructures.hints.IHintContext.Key;
54 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
55 import org.simantics.utils.datastructures.hints.IHintObservable;
56
57 public class TrendParticipant extends AbstractCanvasParticipant {
58
59     /**
60      * Key for storing chart value tip box relative position. Changed by
61      * {@link TrendParticipant} after user finishes dragging this class around.
62      * */
63     public static final Key KEY_VALUE_TIP_BOX_RELATIVE_POS = new KeyOf(Point2D.class);
64
65     /** Key for chart redraw interval in milliseconds (drawn only if dirty) */
66     public static final Key KEY_TREND_DRAW_INTERVAL = new KeyOf(Long.class);
67
68     /** The time code when trend was last redrawn */
69     public static final Key KEY_TREND_SHAPE_LAST = new KeyOf(Long.class);
70
71     /** Key for interval time of value scale */
72     public static final Key KEY_TREND_AUTOSCALE_INTERVAL = new KeyOf(Long.class);
73
74     /** Key for time of last autoscale */
75     public static final Key KEY_TREND_AUTOSCALE_LAST = new KeyOf(Long.class);
76
77     public static final double KEY_MOVE = 0.33;
78
79     /** Cursor when panning */
80     public MouseCursors cursors = new MouseCursors();
81
82     @HintListener(Class=Hints.class, Field="KEY_CONTROL_BOUNDS")
83     public void selectionChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
84         trend.shapedirty = true;
85         setDirty();
86     }
87
88     TrendNode trend;
89
90     @Dependency TimeParticipant time;
91
92     Grab grab; 
93
94     // The single item on which the mouse hovers over is set to this field. (Binary items are selectable). 
95     public ItemNode hoveringItem;
96
97     @Override
98     public void addedToContext(ICanvasContext ctx) {
99         super.addedToContext(ctx);
100         time.registerForEvents(getClass());
101     }
102
103     public void setTrend(TrendNode node) {
104         trend = node;
105     }
106
107     public TrendNode getTrend() {
108         return trend;
109     }
110
111 //    @SGInit
112 //    public void initSG(G2DParentNode parent) {
113 //      Set<TrendNode> nodes = NodeUtil.collectNodes(parent, TrendNode.class);
114 //      trends.addAll(nodes);
115 //    }
116
117     @SGCleanup
118     public void cleanupSG() {
119     }
120
121     protected void updateNode() {
122 //      titleNode.setText(text)
123 //        node.setEnabled(isPaintingEnabled());
124 //        node.setGridColor(getGridColor());
125 //        node.setGridSize(getGridSize());
126     }
127
128     @EventHandler(priority = 0)
129     public boolean handleTimeEvent(TimeEvent e) {
130         //System.out.println("(" + isRemoved() + ") time event: " + e.time + " (" + e.interval + ")");
131         if (isRemoved()) {
132             if (time != null)
133                 time.unregisterForEvents(getClass());
134             return false;
135         }
136
137         long currentTime = e.time;
138
139         Long drawInterval = getHint(KEY_TREND_DRAW_INTERVAL);
140         if (drawInterval == null) drawInterval = 200L;
141         Long lastDrawTime = getHint(KEY_TREND_SHAPE_LAST);
142         boolean drawIntervalElapsed = lastDrawTime == null || (currentTime>=lastDrawTime+drawInterval);
143         if (drawIntervalElapsed) {
144 //            System.out.println("elapsed="+drawIntervalElapsed+" currentTime="+currentTime+" lastDraw="+lastDrawTime+", interval="+drawInterval+", nextDrawTime="+(lastDrawTime+drawInterval));
145 //            System.out.println("elapsed="+drawIntervalElapsed+" currentTime="+currentTime+" lastDraw="+lastDrawTime+", interval="+drawInterval);
146             setHint(KEY_TREND_SHAPE_LAST, currentTime);
147             trend.shapedirty |= trend.datadirty; 
148             trend.datadirty = false;
149         }
150
151         // Scale time
152         Long autoscaleInterval = getHint(KEY_TREND_AUTOSCALE_INTERVAL);
153         if (autoscaleInterval == null) autoscaleInterval = 1000L;
154         Long autoscaleTime = getHint(KEY_TREND_AUTOSCALE_LAST);
155         boolean autoscale = autoscaleTime==null || (currentTime>=autoscaleTime+autoscaleInterval);
156         if (autoscale) {
157             boolean l = trend.autoscale(trend.autoscaletime, autoscale);
158             if (!draggingValueTip(grab)) {
159                 trend.updateValueTipTime();
160             }
161             if (l) {
162                 trend.layout();
163             }
164
165             setHint(KEY_TREND_AUTOSCALE_LAST, currentTime);
166         }
167
168         // IT IS DRAW TIME
169         if (trend.shapedirty) {
170 //            System.out.println(currentTime+": Draw time!");
171             setDirty();
172         }
173
174         return false;
175     }
176
177     @EventHandler(priority = 0)
178     public boolean mouseClick(MouseClickEvent me) {
179                 IG2DNode node = trend.pickNode( me.controlPosition );
180                 
181         if (me.clickCount==2 && node!=null) {
182                 trend.valueTipTime = trend.prevValueTipTime;
183                         if (node instanceof Plot) {
184                                 boolean binaryArea = me.controlPosition.getY()>trend.plot.getY()+trend.plot.analogAreaHeight;
185                                 if (binaryArea) {
186                                         trend.horizRuler.zoomOut();
187                                 } else {
188                                         trend.zoomOut();
189                                 }
190                                 setDirty();
191                                 return true;
192                         } else if (node instanceof HorizRuler) {
193                                 HorizRuler hr = (HorizRuler) node;
194                                 hr.zoomOut();
195                                 setDirty();
196                                 return true;
197                         } else if (node instanceof VertRuler) {
198                                 VertRuler vr = (VertRuler) node;
199                                 vr.zoomOut();
200                                 setDirty();
201                                 return true;
202                         }
203         }
204         
205         // Click ValueTip 
206         if (me.clickCount==1 && me.button==MouseEvent.LEFT_BUTTON && node!=null && node instanceof Plot) {
207                 trend.prevValueTipTime = trend.valueTipTime;
208                         if (trend.valueTipTime == null) {
209                         trend.valueTipTime = Double.isNaN(trend.mouseHoverTime)?null:trend.mouseHoverTime;
210                         trend.valueTipHover = true;
211                         } else {
212                                 double valueTipX = trend.horizRuler.toX( trend.valueTipTime );
213                         double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();
214                         boolean hit = x>=valueTipX-TrendLayout.VALUE_TIP_HOVER_SENSITIVITY && x<=valueTipX+TrendLayout.VALUE_TIP_HOVER_SENSITIVITY;
215                         if (hit) trend.valueTipTime = null;
216                         }
217                         setDirty();
218         }
219
220         /*
221         // Right click removes value tip
222         if (me.clickCount==1 && me.button==MouseEvent.RIGHT_BUTTON) {
223                         if (trend.valueTipTime != null) {
224                                 trend.valueTipTime = null;
225                                 setDirty();
226                         }               
227         }*/
228
229         if (me.clickCount==1 && me.button==MouseEvent.LEFT_BUTTON) {
230                         if (node instanceof VertRuler) {
231                                 VertRuler vr = (VertRuler) node;
232                                 trend.selectVertRuler( trend.vertRulers.indexOf(vr) );
233                                 setDirty();
234                         } else if (node == null || node instanceof TextNode) {
235                                 Plot p = trend.plot;
236                                 double x = me.controlPosition.getX() - p.getX();
237                                 double y = me.controlPosition.getY() - p.getY();
238                                 if ( x>=0 && x<=p.getWidth() && y<=0 && y>=-Plot.DIAMOND_SIZE*2) {
239                                         // Click hits milestone area
240                                         milestoneSearch: for ( Milestone ms : trend.milestones.milestones ) {
241                                                 double mx = trend.horizRuler.toX( ms.time );
242                                                 if ( x>=mx-Plot.DIAMOND_SIZE && x<=mx+Plot.DIAMOND_SIZE ) {
243                                                         ITooltipProvider tp = getContext().getTooltipProvider();
244                                                         if (tp!=null) {
245                                                                 tp.show(ms.label, ms.description);
246                                                         }
247                                                         break milestoneSearch;
248                                                 }
249                                         }
250                                 }
251
252                         }
253
254         }
255                 return false;
256     }
257
258     @EventHandler(priority = 0)
259     public boolean mouseDown(MouseButtonPressedEvent me) {
260         // Start Box-Zoom
261         if (me.button==MouseEvent.LEFT_BUTTON) {
262                         IG2DNode node = trend.pickNode( me.controlPosition );   
263                         if (node == null || node instanceof Plot) {
264
265                 // Start value box grab
266                 {
267                     double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();
268                     double y = me.controlPosition.getY() - trend.plot.getBounds().getY();
269                     if (trend.plot.valueTipBoxBounds.contains(x, y)) {
270                         //System.out.println("grabbed value box @ " + x + ", " + y);
271                         grab = new Grab();
272                         grab.valueBoxRelPos.setLocation(trend.spec.viewProfile.valueViewPositionX, trend.spec.viewProfile.valueViewPositionY);
273                         grab.valueBoxPos.setFrame(trend.plot.valueTipBoxBounds);
274                         grab.mousepos = new Point2D.Double( me.controlPosition.getX(), me.controlPosition.getY() );
275                         grab.valueBoxGrab = true;
276                         return true;
277                     }
278                 }
279
280                                 // Start Value-Tip grab
281                                 if (trend.valueTipTime!=null) {
282                                         double valueTipX = trend.horizRuler.toX( trend.valueTipTime );
283                                 double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();
284                                 boolean hit = x>=valueTipX-TrendLayout.VALUE_TIP_HOVER_SENSITIVITY && x<=valueTipX+TrendLayout.VALUE_TIP_HOVER_SENSITIVITY; 
285                                 if (hit) {
286                                         grab = new Grab();
287                                         grab.mousepos = new Point2D.Double( me.controlPosition.getX(), me.controlPosition.getY() );
288                                         grab.sx = trend.horizRuler.unitsPerPixel();
289                                         grab.sy = new double[ trend.vertRulers.size() ];
290                                         grab.valueTipGrab = true;
291                                         return true;
292                                 }
293                                 }
294
295                         // Start Box-Zoom
296                                 boolean binaryArea = me.controlPosition.getY()>trend.plot.getY()+trend.plot.analogAreaHeight;
297                                 boolean timeZoom = (me.stateMask & MouseEvent.SHIFT_MASK)>0 || binaryArea;
298                                 if (trend.selection==null) {
299                                         trend.selection = trend.addNode("Selection", SelectionNode.class);
300                                         trend.selection.setZIndex( 10 );
301                                         trend.selection.start( me.controlPosition, binaryArea, timeZoom );
302                                 }
303                         return true;
304                         }
305         }
306
307         // Start grab
308                 IG2DNode node = trend.pickNode( me.controlPosition );
309         if ( (me.button==MouseEvent.MIDDLE_BUTTON) && 
310                         node!=null && node instanceof Plot) {
311                         //Plot p = (Plot) node;
312                         TrendNode trend = (TrendNode) node.getParent();
313                         boolean shift = (me.stateMask & MouseEvent.SHIFT_MASK)>0;
314                         boolean alt = (me.stateMask & MouseEvent.ALT_MASK)>0;
315                         //boolean analogArea = me.controlPosition.getY() < p.getY()+p.analogAreaHeight;
316                         //boolean binaryArea = me.controlPosition.getY() >= p.getY()+p.analogAreaHeight;
317                 grab = new Grab();
318                 grab.mousepos = new Point2D.Double( me.controlPosition.getX(), me.controlPosition.getY() );
319                 grab.sx = trend.horizRuler.unitsPerPixel();
320                 grab.sy = new double[ trend.vertRulers.size() ];
321                 grab.horiz = !shift || alt;
322                 grab.vert = shift || alt;
323                 if (grab.vert) {
324                         for (int i=0; i<trend.vertRulers.size(); i++)
325                                 grab.sy[i] = trend.vertRulers.get(i).unitsPerPixel();
326                 }
327                 Cursor c = grab.horiz ? (grab.vert ? cursors.grab : cursors.grab_horiz) : (grab.vert ? cursors.grab_vert : cursors.grab); 
328                 grab.cursor = getContext().getMouseCursorContext().setCursor(me.mouseId, c);
329                 grab.mouseButton = me.button;
330                 grab.plot = true;
331                         trend.horizRuler.translate(0);
332                         setHoverTime(null);
333                 setDirty();
334                         return true;
335         }
336                 if ( (me.button==MouseEvent.LEFT_BUTTON||me.button==MouseEvent.MIDDLE_BUTTON) && 
337                                 node!=null && node instanceof HorizRuler) {
338                         HorizRuler hr = (HorizRuler) node;
339                 grab = new Grab();
340                 grab.cursor = getContext().getMouseCursorContext().setCursor(me.mouseId, cursors.grab_horiz);
341                 grab.mousepos = new Point2D.Double();
342                 grab.mousepos.setLocation( me.controlPosition );
343                 grab.sx = hr.unitsPerPixel();
344                 grab.horizRuler = true;
345                 grab.mouseButton = me.button;
346                 grab.horiz = true;
347                         trend.horizRuler.translate(0);
348                 setDirty();
349                         return true;
350                 }
351                 if ( (me.button==MouseEvent.LEFT_BUTTON||me.button==MouseEvent.MIDDLE_BUTTON) && 
352                                 node!=null && node instanceof VertRuler) {
353                         //VertRuler vr = (VertRuler) node;
354                 grab = new Grab();
355                 grab.cursor = getContext().getMouseCursorContext().setCursor(me.mouseId, cursors.grab_vert);
356                 grab.mousepos = new Point2D.Double();
357                 grab.mousepos.setLocation( me.controlPosition );
358                 grab.sy = new double[ trend.vertRulers.size() ];
359                 for (int i=0; i<trend.vertRulers.size(); i++)
360                         grab.sy[i] = trend.vertRulers.get(i).unitsPerPixel();
361                 grab.mouseButton = me.button;
362                 grab.vertRuler = trend.vertRulers.indexOf(node);
363                         trend.selectVertRuler( grab.vertRuler );
364                 setDirty();
365                         return true;
366         }
367         
368         return false;
369     }
370
371     @EventHandler(priority = 0)
372     public boolean mouseUp(MouseButtonReleasedEvent me) {
373
374         // Release value box grab
375         if (me.button==1 && draggingValueBox(grab)) {
376             setHint(KEY_VALUE_TIP_BOX_RELATIVE_POS, new Point2D.Double(
377                     trend.spec.viewProfile.valueViewPositionX,
378                     trend.spec.viewProfile.valueViewPositionY));
379             grab = null;
380         }
381
382         // Release value tip grab
383         if (me.button==1 && draggingValueTip(grab)) {
384             grab = null;
385         }
386
387         // Release Box-Zoom
388         if (me.button==1 && trend.selection!=null) {
389                 Rectangle2D rect = trend.selection.rect;
390                 
391                 if (rect.getWidth()>1 && rect.getHeight()>1) {    
392                         if (trend.selection.timeZoom) {
393                                 trend.horizRuler.zoomIn( rect.getX()-trend.horizRuler.getX(), rect.getWidth() );
394                         } else {
395                                 trend.zoomIn( 
396                                                 rect.getX()-trend.plot.getX(), 
397                                                 rect.getY()-trend.plot.getY(),
398                                                 rect.getWidth(), rect.getHeight(), true, true );
399                         }
400                         trend.layout();
401                         trend.shapedirty = true;
402                         setDirty();
403                 }
404                 trend.selection.delete();
405                 trend.selection = null;
406
407                 return true;
408         }
409         
410         if (grab!=null && grab.mouseButton==me.button) {
411                 grab.cursor.remove();
412                 grab = null;
413         }
414         return false;
415     }
416
417     @EventHandler(priority = 0)
418     public boolean mouseExit(MouseExitEvent me) {
419                 trend.valueTipHover = false;
420         setHoverTime( null );
421         return false;
422     }
423
424     void setHoverTime(Double time) {
425         if ( time==null && Double.isNaN(trend.mouseHoverTime) ) return;
426         if ( time!=null && trend.mouseHoverTime==time ) return;
427         if ( time==null ) {
428                 trend.mouseHoverTime = Double.NaN;
429         } else {
430                 trend.mouseHoverTime = time;
431                 trend.lastMouseHoverTime = time;
432         }
433         setDirty();
434     }
435
436 //    @EventHandler(priority = 0)
437 //    public boolean mouseEnter(MouseExitEvent me) {
438 //      //double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();
439 //      //double time = trend.horizRuler.toTime( x ) - trend.horizRuler.basetime;
440 //      return false;
441 //    }
442
443     @SuppressWarnings("unused")
444     @EventHandler(priority = 0)
445     public boolean mouseMoved(MouseMovedEvent me) {
446 //        ITooltipProvider tt = getContext().getTooltipProvider();
447 //        if (tt!=null) {
448 //            tt.setTooltipText("Hello");
449 //        }
450
451                 IG2DNode pick = trend.pickNode( me.controlPosition );
452         
453                 TrackMouseMovement: {
454                         if ( pick == trend.plot ) {
455                         double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();
456                         double time = trend.horizRuler.toTime( x );
457                         double sx = (trend.horizRuler.end-trend.horizRuler.from) / trend.horizRuler.getWidth();
458                         double timeSnapTolerance = sx * 7.0; 
459                                 boolean shift = (me.stateMask & MouseEvent.SHIFT_MASK)>0;
460                         
461                                 // SNAP - When shift is held
462                         if (shift) {
463                                 Double snappedTime = null;
464                                         try {
465                                                 snappedTime = trend.snapToValue( time, timeSnapTolerance );
466                                                 if (snappedTime != null) time = snappedTime;
467                                         } catch (HistoryException e) {
468                                         } catch (AccessorException e) {
469                                         }
470                         }
471                                 
472                                 setHoverTime(time);
473                         }
474                 }
475
476         // Implement value box moving while dragging.
477         if (draggingValueBox(grab)) {
478             Rectangle2D plotBox = trend.plot.getBounds();
479             Rectangle2D valueBoxPos = grab.valueBoxPos;
480             Rectangle2D valueBox = trend.plot.valueTipBoxBounds;
481             double dx = me.controlPosition.getX() - grab.mousepos.getX();
482             double dy = me.controlPosition.getY() - grab.mousepos.getY();
483             double margin = Plot.VALUE_TIP_BOX_PLOT_MARGIN;
484             double maxX = plotBox.getWidth() - margin - valueBox.getWidth();
485             double maxY = plotBox.getHeight() - margin - valueBox.getHeight();
486             double boxX = valueBoxPos.getX() + dx;
487             double boxY = valueBoxPos.getY() + dy;
488             double pw = plotBox.getWidth() - 2 * margin;
489             double ph = plotBox.getHeight() - 2 * margin;
490             double w = pw - valueBox.getWidth();
491             double h = ph - valueBox.getHeight();
492             if (w > 0 && h > 0) {
493                 double rx = (boxX - margin) / w;
494                 double ry = (boxY - margin) / h;
495                 rx = Math.max(0, Math.min(rx, 1));
496                 ry = Math.max(0, Math.min(ry, 1));
497                 trend.spec.viewProfile.valueViewPositionX = rx;
498                 trend.spec.viewProfile.valueViewPositionY = ry;
499                 setDirty();
500             }
501             return false;
502         }
503
504         ValueToolTip: if ( pick == trend.plot) {
505             if (draggingValueTip(grab)) {
506                                 // Move grabbed value-tip
507                                 trend.valueTipTime = Double.isNaN(trend.mouseHoverTime) ? null : trend.mouseHoverTime;
508                                 return false;
509                         } else {
510                                 // Track hover color
511                                 if (trend.valueTipTime!=null) {
512                                         double valueTipX = trend.horizRuler.toX( trend.valueTipTime );
513                                 double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();
514                                 boolean hit = x>=valueTipX-TrendLayout.VALUE_TIP_HOVER_SENSITIVITY && x<=valueTipX+TrendLayout.VALUE_TIP_HOVER_SENSITIVITY;
515                                 trend.valueTipHover = hit;
516                                 }
517                         }
518         }
519                 
520                 // Item pick
521                 ItemPick: {
522                 if ( pick instanceof VertRuler ) {
523                         VertRuler vr = (VertRuler) pick;
524                         hoveringItem = null;
525                         for (ItemNode item : trend.analogItems) {
526                                 if (item.item.renderer != Renderer.Analog || item.ruler==vr) {
527                                         if ( hoveringItem != null) break;
528                                         hoveringItem = item;
529                                 }
530                         }
531                 } else if ( pick == null || pick instanceof Plot ) {
532                         hoveringItem = trend.plot.pickItem( me.controlPosition );
533                 } else {
534                         hoveringItem = null;
535                 }
536         }
537         
538         // Box-Zoom
539         if (trend.selection!=null) {
540                 trend.selection.setEndPoint( me.controlPosition );
541                 setHoverTime(null);
542                 setDirty();
543                 return true;
544         }
545         
546         // Drag axis
547         if (grab != null) {
548                 double dx = me.controlPosition.getX() - grab.mousepos.x;
549                 double dy = me.controlPosition.getY() - grab.mousepos.y;
550                 grab.mousepos.setLocation(me.controlPosition);
551                 
552                 if (grab.plot) {
553                         if (grab.horiz) {
554                                 trend.horizRuler.translate(-dx*grab.sx);
555                         }
556                         if (grab.vert) {
557                                 for (int i=0; i<grab.sy.length; i++) {
558                                         if (i>=trend.vertRulers.size()) break;
559                                         trend.vertRulers.get(i).translate(dy*grab.sy[i]);
560                                 }
561                         }
562                 } else if (grab.horizRuler) {
563                         trend.horizRuler.translate(-dx*grab.sx);
564                 } else if (grab.vertRuler>=0) {
565                         if (grab.vertRuler<=trend.vertRulers.size()) {
566                                 VertRuler vr = trend.vertRulers.get( grab.vertRuler );
567                                 trend.selectVertRuler( grab.vertRuler );
568                                 vr.translate(dy*grab.sy[ grab.vertRuler ]);
569                         }
570                 }
571                         trend.layout();
572                 trend.shapedirty = true;
573                 setDirty();
574                 return true;
575         }
576         return false;
577     }
578
579     @EventHandler(priority = 0)
580     public boolean mouseWheel(MouseWheelMovedEvent me) {
581                 IG2DNode node = trend.pickNode( me.controlPosition );
582                 if (node instanceof Plot || node instanceof HorizRuler || node instanceof VertRuler) {
583                         Point2D pt = node.parentToLocal( me.controlPosition );
584                         boolean shift = (me.stateMask & MouseEvent.SHIFT_MASK)>0;
585                         boolean alt = (me.stateMask & MouseEvent.ALT_MASK)>0;
586                         double pw = trend.plot.getWidth();
587                         double ph = trend.plot.analogAreaHeight;
588
589                         double r = Math.pow(0.9, me.wheelRotation);
590
591                         double w = r * pw; 
592                         double h = r * ph;
593                         double rx = pt.getX() / pw;
594                         double ry = pt.getY() / ph;
595
596                         double zx = (pw-w)*rx;
597                         double zy = (ph-h)*ry;
598
599                         if (node instanceof Plot) {
600                                 TrendNode trend = (TrendNode) node.getParent();
601                                 if (shift||alt) {
602                                         for (VertRuler vr : trend.vertRulers) {
603                                                 vr.zoomIn(zy, h);
604                                         }
605                                 } 
606                                 if (!shift) {
607                                         trend.horizRuler.zoomIn(zx, w);
608                                 }
609                         }
610
611                         if (node instanceof HorizRuler) {
612                                 //HorizRuler hr = (HorizRuler) node;
613                                 trend.horizRuler.zoomIn(zx, w);
614                         }
615
616                         if (node instanceof VertRuler) {
617                                 VertRuler vr = (VertRuler) node;
618                                 trend.selectVertRuler( trend.vertRulers.indexOf(vr) );
619                                 vr.zoomIn(zy, h);
620                         }
621                         trend.shapedirty = true;
622                         trend.layout();
623                         setDirty();
624                         return true;
625                 }
626         return false;
627     }
628
629     @EventHandler(priority = 0)
630     public boolean handleCommandEvent(CommandEvent e) {
631         
632         if (e.command == Commands.CANCEL) {
633                 if (trend.selection != null) {
634                         trend.selection.delete();
635                         trend.selection = null;
636                         setDirty();
637                         return true;
638                 }
639                 
640                 if (grab!=null) {
641                 if (draggingValueBox(grab)) {
642                     trend.spec.viewProfile.valueViewPositionX = grab.valueBoxRelPos.getX();
643                     trend.spec.viewProfile.valueViewPositionY = grab.valueBoxRelPos.getY();
644                 }
645                         if (grab.cursor!=null) grab.cursor.remove();
646                         grab = null;
647                         setDirty();
648                         return true;
649                 }
650                 
651                 if (trend.valueTipTime != null) {
652                         trend.valueTipTime = null;
653                         setDirty();
654                         return true;
655                 }
656                 return false;
657         }
658         
659         if (e.command == Commands.PAN_LEFT) {
660                         double pw = trend.plot.getWidth();
661                         double ph = trend.plot.analogAreaHeight;
662                         double zx = -pw * KEY_MOVE;
663                         double zy = 0;
664                         trend.zoomIn(zx, zy, pw, ph, true, true);
665                         trend.layout();
666                         setDirty();
667         }
668         
669         if (e.command == Commands.PAN_RIGHT) {
670                         double pw = trend.plot.getWidth();
671                         double ph = trend.plot.analogAreaHeight;
672                         double zx = +pw * KEY_MOVE;
673                         double zy = 0;
674                         trend.zoomIn(zx, zy, pw, ph, true, true);
675                         trend.horizRuler.autoscroll = false;
676                         trend.layout();
677                         setDirty();
678         }
679         
680         if (e.command == Commands.PAN_UP) {
681                         double pw = trend.plot.getWidth();
682                         double ph = trend.plot.analogAreaHeight;
683                         double zx = 0;
684                         double zy = -ph * KEY_MOVE;
685                         trend.zoomIn(zx, zy, pw, ph, true, true);
686                         trend.horizRuler.autoscroll = false;
687                         trend.layout();
688                         setDirty();
689         }
690         
691         if (e.command == Commands.PAN_DOWN) {
692                         double pw = trend.plot.getWidth();
693                         double ph = trend.plot.analogAreaHeight;
694                         double zx = 0;
695                         double zy = +ph * KEY_MOVE;
696                         trend.zoomIn(zx, zy, pw, ph, true, true);
697                         trend.horizRuler.autoscroll = false;
698                         trend.layout();
699                         setDirty();
700         }
701         
702         // Zoom out to time window settings (VK_4)
703         if (e.command.equals(Commands.AUTOSCALE)) {
704                         trend.horizRuler.autoscroll = true;
705                         for (VertRuler vertRuler : trend.vertRulers) vertRuler.autoscroll = true;
706                         trend.zoomOut();
707                         trend.layout();
708                         setDirty();
709         }
710         
711         // Fit all visible (VK_1)
712         if (e.command.equals(Commands.ZOOM_TO_FIT)) {
713                         trend.readMinMaxFromEnd();
714                         trend.horizRuler.setFromEnd(trend.horizRuler.iFrom, trend.horizRuler.iEnd);
715                         trend.horizRuler.fireListener();
716                         int c = trend.vertRulers.size();
717                         for (int i=0; i<c; i++) {
718                                 VertRuler vr = trend.vertRulers.get(c-i-1);
719                                 double nMin = vr.iMin;
720                                 double nMax = vr.iMax;
721
722                                 double diff = nMax - nMin;
723                                 if (diff==0.0) {
724                                         nMin -= 0.5;
725                                         nMax += 0.5;
726                                         diff = nMax - nMin;
727                                 }
728                                 double margin = diff*0.02;
729                                 
730                                 if (trend.itemPlacement == ItemPlacement.Stacked) {
731                                         nMin = nMin - (diff)*i - margin;
732                                         nMax = nMax + (diff)*(c-i-1) + margin;
733                                         
734                                 } else {
735                                         nMin = vr.iMin - margin;
736                                         nMax = vr.iMax + margin;
737                                 }
738                                 
739                                 vr.zoomTo(nMin, nMax);
740                         }
741                         trend.horizRuler.autoscroll = false;
742                         trend.layout();
743                         setDirty();
744                         return true;
745         }
746
747         // Fit horiz (VK_2)
748         if (e.command.equals(Commands.ZOOM_TO_FIT_HORIZ)) {
749                         trend.readMinMaxFromEnd();
750                         trend.horizRuler.setFromEnd(trend.horizRuler.iFrom, trend.horizRuler.iEnd);
751                         trend.layout();
752                         trend.horizRuler.autoscroll = false;
753                         setDirty();
754                         return true;
755         }
756
757         // Fit vert (VK_3)
758         if (e.command.equals(Commands.ZOOM_TO_FIT_VERT)) {
759                         trend.readMinMaxFromEnd();
760                         int c = trend.vertRulers.size();
761                         for (int i=0; i<c; i++) {
762                                 VertRuler vr = trend.vertRulers.get(c-i-1);
763                                 double nMin = vr.iMin;
764                                 double nMax = vr.iMax;
765
766                                 double diff = nMax - nMin;
767                                 if (diff==0.0) {
768                                         nMin -= 0.5;
769                                         nMax += 0.5;
770                                         diff = nMax - nMin;
771                                 }
772                                 double margin = diff*0.02;
773                                 
774                                 if (trend.itemPlacement == ItemPlacement.Stacked) {
775                                         nMin = nMin - (diff)*i - margin;
776                                         nMax = nMax + (diff)*(c-i-1) + margin;
777                                         
778                                 } else {
779                                         nMin = vr.iMin - margin;
780                                         nMax = vr.iMax + margin;
781                                 }
782                                 
783                                 vr.zoomTo(nMin, nMax);
784                         }
785                         trend.layout();
786                         setDirty();
787                         return true;
788         }
789         
790         // +
791         if (e.command == Commands.ZOOM_IN) {
792                         double pw = trend.plot.getWidth();
793                         double ph = trend.plot.analogAreaHeight;
794                         double za = 3; // Zoom amount
795                         double w = (1. - (0.1*za)) * pw; 
796                         double h = (1. - (0.1*za)) * ph;
797
798                         double zx = (pw-w)/2;
799                         double zy = (ph-h)/2;
800                         
801                         trend.zoomIn(zx, zy, w, h, true, true);
802                         trend.horizRuler.autoscroll = false;
803                         trend.layout();
804                         setDirty();
805                         return true;
806         }
807         
808         // -
809         if (e.command == Commands.ZOOM_OUT) {
810                         double pw = trend.plot.getWidth();
811                         double ph = trend.plot.analogAreaHeight;
812                         double za = -3; // Zoom amount
813                         double w = (1. - (0.1*za)) * pw; 
814                         double h = (1. - (0.1*za)) * ph;
815
816                         double zx = (pw-w)/2;
817                         double zy = (ph-h)/2;
818                         
819                         trend.zoomIn(zx, zy, w, h, true, true);
820                         trend.horizRuler.autoscroll = false;
821                         trend.layout();
822                         setDirty();
823                         return true;
824         }
825         
826                 // Print to printer
827                 if (e.command == Commands.PRINT) {
828                         try {
829                                 PrintUtil pu = new PrintUtil();
830                                 pu.addTrendPage(trend);
831                                 pu.print();
832                         } catch (PrinterException e1) {
833                                 e1.printStackTrace();
834                         }
835                         return true;
836                 }
837                 
838                 // Print to PDF
839                 if (e.command == Commands.PDFPRINT) {
840                         PrintUtil pu = new PrintUtil();
841                         pu.addTrendPage(trend);
842                         try {
843                                 File f = File.createTempFile("Trend", ".pdf");
844                                 pu.printPdf(f);
845                                 System.out.println("Printed Trend to "+f);
846                         } catch (IOException e1) {
847                                 e1.printStackTrace();
848                         }
849                         return true;
850                 }
851                 
852                 // Read visible values into CSV 
853                 if (e.command == Commands.COPY || e.command == Commands.EXPORT) {
854                         StringBuilder sb = new StringBuilder(64*1024);
855                         try {
856                                 // TODO: fix to use preferences.
857                                 CSVFormatter formatter = new CSVFormatter();
858                                 formatter.setTimeRange(trend.horizRuler.from, trend.horizRuler.end);
859                                 for (ItemNode i : trend.allItems)
860                                 {
861                                         if (i.item.hidden) continue;
862                                         if (i.historyItems==null || i.historyItems.length==0) continue;
863                                         Bean bestQualityStream = i.historyItems[0];
864                                         String historyItemId = (String) bestQualityStream.getFieldUnchecked("id");
865                                         formatter.addItem( trend.historian, historyItemId, i.item.simpleLabel, i.item.variableReference, i.item.unit);
866                                 }
867                                 formatter.sort();
868                                 try {
869                                     // TODO: Use a proper user-cancelable progressmonitor
870                                         formatter.formulate2(new ProgressMonitor.Stub(), sb);
871                                 } catch (IOException e1) {
872                                         // sb cannot throw ioexception
873                                 }
874                         } catch (HistoryException e1) {
875                                 e1.toString();
876                         }
877                         Toolkit toolkit = Toolkit.getDefaultToolkit();
878                         Clipboard clipboard = toolkit.getSystemClipboard();
879                         StringSelection strSel = new StringSelection(sb.toString());
880                         clipboard.setContents(strSel, null);
881                 }
882                 return false;
883         }
884         
885     static class Grab {
886         int mouseButton;
887         IMouseCursorHandle cursor;
888         Point2D.Double mousepos = new Point2D.Double();
889         boolean plot = false;
890         boolean horizRuler = false;
891         boolean vert = false;
892         boolean horiz = false;
893         int vertRuler = -1;
894         double sx = 1;
895         double[] sy;
896         boolean valueTipGrab = false;
897         boolean valueBoxGrab = false;
898         Point2D valueBoxRelPos = new Point2D.Double(); 
899         Rectangle2D valueBoxPos = new Rectangle2D.Double();
900     }
901
902     private static boolean draggingValueTip(Grab g) {
903         return g != null && g.valueTipGrab;
904     }
905
906     private static boolean draggingValueBox(Grab g) {
907         return g != null && g.valueBoxGrab;
908     }
909
910 }