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