-/*******************************************************************************\r
- * Copyright (c) 2007, 2011 Association for Decentralized Information Management in\r
- * Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- * VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.trend.impl;\r
-\r
-import java.awt.Cursor;\r
-import java.awt.Toolkit;\r
-import java.awt.datatransfer.Clipboard;\r
-import java.awt.datatransfer.StringSelection;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-import java.awt.print.PrinterException;\r
-import java.io.File;\r
-import java.io.IOException;\r
-\r
-import org.simantics.databoard.accessor.error.AccessorException;\r
-import org.simantics.databoard.util.Bean;\r
-import org.simantics.g2d.canvas.Hints;\r
-import org.simantics.g2d.canvas.ICanvasContext;\r
-import org.simantics.g2d.canvas.IMouseCursorHandle;\r
-import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;\r
-import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
-import org.simantics.g2d.canvas.impl.HintReflection.HintListener;\r
-import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;\r
-import org.simantics.g2d.chassis.ITooltipProvider;\r
-import org.simantics.g2d.participant.TimeParticipant;\r
-import org.simantics.history.HistoryException;\r
-import org.simantics.history.csv.CSVFormatter;\r
-import org.simantics.history.util.ProgressMonitor;\r
-import org.simantics.scenegraph.g2d.IG2DNode;\r
-import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseWheelMovedEvent;\r
-import org.simantics.scenegraph.g2d.events.TimeEvent;\r
-import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
-import org.simantics.scenegraph.g2d.events.command.Commands;\r
-import org.simantics.trend.configuration.ItemPlacement;\r
-import org.simantics.trend.configuration.TrendItem.Renderer;\r
-import org.simantics.trend.util.PrintUtil;\r
-import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
-import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
-import org.simantics.utils.datastructures.hints.IHintObservable;\r
-\r
-public class TrendParticipant extends AbstractCanvasParticipant {\r
-\r
- /**\r
- * Key for storing chart value tip box relative position. Changed by\r
- * {@link TrendParticipant} after user finishes dragging this class around.\r
- * */\r
- public static final Key KEY_VALUE_TIP_BOX_RELATIVE_POS = new KeyOf(Point2D.class);\r
-\r
- /** Key for chart redraw interval in milliseconds (drawn only if dirty) */\r
- public static final Key KEY_TREND_DRAW_INTERVAL = new KeyOf(Long.class);\r
-\r
- /** The time code when trend was last redrawn */\r
- public static final Key KEY_TREND_SHAPE_LAST = new KeyOf(Long.class);\r
-\r
- /** Key for interval time of value scale */\r
- public static final Key KEY_TREND_AUTOSCALE_INTERVAL = new KeyOf(Long.class);\r
-\r
- /** Key for time of last autoscale */\r
- public static final Key KEY_TREND_AUTOSCALE_LAST = new KeyOf(Long.class);\r
-\r
- public static final double KEY_MOVE = 0.33;\r
-\r
- /** Cursor when panning */\r
- public MouseCursors cursors = new MouseCursors();\r
-\r
- @HintListener(Class=Hints.class, Field="KEY_CONTROL_BOUNDS")\r
- public void selectionChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
- trend.shapedirty = true;\r
- setDirty();\r
- }\r
-\r
- TrendNode trend;\r
-\r
- @Dependency TimeParticipant time;\r
-\r
- Grab grab; \r
-\r
- // The single item on which the mouse hovers over is set to this field. (Binary items are selectable). \r
- public ItemNode hoveringItem;\r
-\r
- @Override\r
- public void addedToContext(ICanvasContext ctx) {\r
- super.addedToContext(ctx);\r
- time.registerForEvents(getClass());\r
- }\r
-\r
- public void setTrend(TrendNode node) {\r
- trend = node;\r
- }\r
-\r
- public TrendNode getTrend() {\r
- return trend;\r
- }\r
-\r
-// @SGInit\r
-// public void initSG(G2DParentNode parent) {\r
-// Set<TrendNode> nodes = NodeUtil.collectNodes(parent, TrendNode.class);\r
-// trends.addAll(nodes);\r
-// }\r
-\r
- @SGCleanup\r
- public void cleanupSG() {\r
- }\r
-\r
- protected void updateNode() {\r
-// titleNode.setText(text)\r
-// node.setEnabled(isPaintingEnabled());\r
-// node.setGridColor(getGridColor());\r
-// node.setGridSize(getGridSize());\r
- }\r
-\r
- @EventHandler(priority = 0)\r
- public boolean handleTimeEvent(TimeEvent e) {\r
- //System.out.println("(" + isRemoved() + ") time event: " + e.time + " (" + e.interval + ")");\r
- if (isRemoved()) {\r
- if (time != null)\r
- time.unregisterForEvents(getClass());\r
- return false;\r
- }\r
-\r
- long currentTime = e.time;\r
-\r
- Long drawInterval = getHint(KEY_TREND_DRAW_INTERVAL);\r
- if (drawInterval == null) drawInterval = 200L;\r
- Long lastDrawTime = getHint(KEY_TREND_SHAPE_LAST);\r
- boolean drawIntervalElapsed = lastDrawTime == null || (currentTime>=lastDrawTime+drawInterval);\r
- if (drawIntervalElapsed) {\r
-// System.out.println("elapsed="+drawIntervalElapsed+" currentTime="+currentTime+" lastDraw="+lastDrawTime+", interval="+drawInterval+", nextDrawTime="+(lastDrawTime+drawInterval));\r
-// System.out.println("elapsed="+drawIntervalElapsed+" currentTime="+currentTime+" lastDraw="+lastDrawTime+", interval="+drawInterval);\r
- setHint(KEY_TREND_SHAPE_LAST, currentTime);\r
- trend.shapedirty |= trend.datadirty; \r
- trend.datadirty = false;\r
- }\r
-\r
- // Scale time\r
- Long autoscaleInterval = getHint(KEY_TREND_AUTOSCALE_INTERVAL);\r
- if (autoscaleInterval == null) autoscaleInterval = 1000L;\r
- Long autoscaleTime = getHint(KEY_TREND_AUTOSCALE_LAST);\r
- boolean autoscale = autoscaleTime==null || (currentTime>=autoscaleTime+autoscaleInterval);\r
- if (autoscale) {\r
- boolean l = trend.autoscale(trend.autoscaletime, autoscale);\r
- if (!draggingValueTip(grab)) {\r
- trend.updateValueTipTime();\r
- }\r
- if (l) {\r
- trend.layout();\r
- }\r
-\r
- setHint(KEY_TREND_AUTOSCALE_LAST, currentTime);\r
- }\r
-\r
- // IT IS DRAW TIME\r
- if (trend.shapedirty) {\r
-// System.out.println(currentTime+": Draw time!");\r
- setDirty();\r
- }\r
-\r
- return false;\r
- }\r
-\r
- @EventHandler(priority = 0)\r
- public boolean mouseClick(MouseClickEvent me) {\r
- IG2DNode node = trend.pickNode( me.controlPosition );\r
- \r
- if (me.clickCount==2 && node!=null) {\r
- trend.valueTipTime = trend.prevValueTipTime;\r
- if (node instanceof Plot) {\r
- boolean binaryArea = me.controlPosition.getY()>trend.plot.getY()+trend.plot.analogAreaHeight;\r
- if (binaryArea) {\r
- trend.horizRuler.zoomOut();\r
- } else {\r
- trend.zoomOut();\r
- }\r
- setDirty();\r
- return true;\r
- } else if (node instanceof HorizRuler) {\r
- HorizRuler hr = (HorizRuler) node;\r
- hr.zoomOut();\r
- setDirty();\r
- return true;\r
- } else if (node instanceof VertRuler) {\r
- VertRuler vr = (VertRuler) node;\r
- vr.zoomOut();\r
- setDirty();\r
- return true;\r
- }\r
- }\r
- \r
- // Click ValueTip \r
- if (me.clickCount==1 && me.button==MouseEvent.LEFT_BUTTON && node!=null && node instanceof Plot) {\r
- trend.prevValueTipTime = trend.valueTipTime;\r
- if (trend.valueTipTime == null) {\r
- trend.valueTipTime = Double.isNaN(trend.mouseHoverTime)?null:trend.mouseHoverTime;\r
- trend.valueTipHover = true;\r
- } else {\r
- double valueTipX = trend.horizRuler.toX( trend.valueTipTime );\r
- double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();\r
- boolean hit = x>=valueTipX-TrendLayout.VALUE_TIP_HOVER_SENSITIVITY && x<=valueTipX+TrendLayout.VALUE_TIP_HOVER_SENSITIVITY;\r
- if (hit) trend.valueTipTime = null;\r
- }\r
- setDirty();\r
- }\r
-\r
- /*\r
- // Right click removes value tip\r
- if (me.clickCount==1 && me.button==MouseEvent.RIGHT_BUTTON) {\r
- if (trend.valueTipTime != null) {\r
- trend.valueTipTime = null;\r
- setDirty();\r
- } \r
- }*/\r
-\r
- if (me.clickCount==1 && me.button==MouseEvent.LEFT_BUTTON) {\r
- if (node instanceof VertRuler) {\r
- VertRuler vr = (VertRuler) node;\r
- trend.selectVertRuler( trend.vertRulers.indexOf(vr) );\r
- setDirty();\r
- } else if (node == null || node instanceof TextNode) {\r
- Plot p = trend.plot;\r
- double x = me.controlPosition.getX() - p.getX();\r
- double y = me.controlPosition.getY() - p.getY();\r
- if ( x>=0 && x<=p.getWidth() && y<=0 && y>=-Plot.DIAMOND_SIZE*2) {\r
- // Click hits milestone area\r
- milestoneSearch: for ( Milestone ms : trend.milestones.milestones ) {\r
- double mx = trend.horizRuler.toX( ms.time );\r
- if ( x>=mx-Plot.DIAMOND_SIZE && x<=mx+Plot.DIAMOND_SIZE ) {\r
- ITooltipProvider tp = getContext().getTooltipProvider();\r
- if (tp!=null) {\r
- tp.show(ms.label, ms.description);\r
- }\r
- break milestoneSearch;\r
- }\r
- }\r
- }\r
-\r
- }\r
-\r
- }\r
- return false;\r
- }\r
-\r
- @EventHandler(priority = 0)\r
- public boolean mouseDown(MouseButtonPressedEvent me) {\r
- // Start Box-Zoom\r
- if (me.button==MouseEvent.LEFT_BUTTON) {\r
- IG2DNode node = trend.pickNode( me.controlPosition ); \r
- if (node == null || node instanceof Plot) {\r
-\r
- // Start value box grab\r
- {\r
- double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();\r
- double y = me.controlPosition.getY() - trend.plot.getBounds().getY();\r
- if (trend.plot.valueTipBoxBounds.contains(x, y)) {\r
- //System.out.println("grabbed value box @ " + x + ", " + y);\r
- grab = new Grab();\r
- grab.valueBoxRelPos.setLocation(trend.spec.viewProfile.valueViewPositionX, trend.spec.viewProfile.valueViewPositionY);\r
- grab.valueBoxPos.setFrame(trend.plot.valueTipBoxBounds);\r
- grab.mousepos = new Point2D.Double( me.controlPosition.getX(), me.controlPosition.getY() );\r
- grab.valueBoxGrab = true;\r
- return true;\r
- }\r
- }\r
-\r
- // Start Value-Tip grab\r
- if (trend.valueTipTime!=null) {\r
- double valueTipX = trend.horizRuler.toX( trend.valueTipTime );\r
- double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();\r
- boolean hit = x>=valueTipX-TrendLayout.VALUE_TIP_HOVER_SENSITIVITY && x<=valueTipX+TrendLayout.VALUE_TIP_HOVER_SENSITIVITY; \r
- if (hit) {\r
- grab = new Grab();\r
- grab.mousepos = new Point2D.Double( me.controlPosition.getX(), me.controlPosition.getY() );\r
- grab.sx = trend.horizRuler.unitsPerPixel();\r
- grab.sy = new double[ trend.vertRulers.size() ];\r
- grab.valueTipGrab = true;\r
- return true;\r
- }\r
- }\r
-\r
- // Start Box-Zoom\r
- boolean binaryArea = me.controlPosition.getY()>trend.plot.getY()+trend.plot.analogAreaHeight;\r
- boolean timeZoom = (me.stateMask & MouseEvent.SHIFT_MASK)>0 || binaryArea;\r
- if (trend.selection==null) {\r
- trend.selection = trend.addNode("Selection", SelectionNode.class);\r
- trend.selection.setZIndex( 10 );\r
- trend.selection.start( me.controlPosition, binaryArea, timeZoom );\r
- }\r
- return true;\r
- }\r
- }\r
-\r
- // Start grab\r
- IG2DNode node = trend.pickNode( me.controlPosition );\r
- if ( (me.button==MouseEvent.MIDDLE_BUTTON) && \r
- node!=null && node instanceof Plot) {\r
- //Plot p = (Plot) node;\r
- TrendNode trend = (TrendNode) node.getParent();\r
- boolean shift = (me.stateMask & MouseEvent.SHIFT_MASK)>0;\r
- boolean alt = (me.stateMask & MouseEvent.ALT_MASK)>0;\r
- //boolean analogArea = me.controlPosition.getY() < p.getY()+p.analogAreaHeight;\r
- //boolean binaryArea = me.controlPosition.getY() >= p.getY()+p.analogAreaHeight;\r
- grab = new Grab();\r
- grab.mousepos = new Point2D.Double( me.controlPosition.getX(), me.controlPosition.getY() );\r
- grab.sx = trend.horizRuler.unitsPerPixel();\r
- grab.sy = new double[ trend.vertRulers.size() ];\r
- grab.horiz = !shift || alt;\r
- grab.vert = shift || alt;\r
- if (grab.vert) {\r
- for (int i=0; i<trend.vertRulers.size(); i++)\r
- grab.sy[i] = trend.vertRulers.get(i).unitsPerPixel();\r
- }\r
- Cursor c = grab.horiz ? (grab.vert ? cursors.grab : cursors.grab_horiz) : (grab.vert ? cursors.grab_vert : cursors.grab); \r
- grab.cursor = getContext().getMouseCursorContext().setCursor(me.mouseId, c);\r
- grab.mouseButton = me.button;\r
- grab.plot = true;\r
- trend.horizRuler.translate(0);\r
- setHoverTime(null);\r
- setDirty();\r
- return true;\r
- }\r
- if ( (me.button==MouseEvent.LEFT_BUTTON||me.button==MouseEvent.MIDDLE_BUTTON) && \r
- node!=null && node instanceof HorizRuler) {\r
- HorizRuler hr = (HorizRuler) node;\r
- grab = new Grab();\r
- grab.cursor = getContext().getMouseCursorContext().setCursor(me.mouseId, cursors.grab_horiz);\r
- grab.mousepos = new Point2D.Double();\r
- grab.mousepos.setLocation( me.controlPosition );\r
- grab.sx = hr.unitsPerPixel();\r
- grab.horizRuler = true;\r
- grab.mouseButton = me.button;\r
- grab.horiz = true;\r
- trend.horizRuler.translate(0);\r
- setDirty();\r
- return true;\r
- }\r
- if ( (me.button==MouseEvent.LEFT_BUTTON||me.button==MouseEvent.MIDDLE_BUTTON) && \r
- node!=null && node instanceof VertRuler) {\r
- //VertRuler vr = (VertRuler) node;\r
- grab = new Grab();\r
- grab.cursor = getContext().getMouseCursorContext().setCursor(me.mouseId, cursors.grab_vert);\r
- grab.mousepos = new Point2D.Double();\r
- grab.mousepos.setLocation( me.controlPosition );\r
- grab.sy = new double[ trend.vertRulers.size() ];\r
- for (int i=0; i<trend.vertRulers.size(); i++)\r
- grab.sy[i] = trend.vertRulers.get(i).unitsPerPixel();\r
- grab.mouseButton = me.button;\r
- grab.vertRuler = trend.vertRulers.indexOf(node);\r
- trend.selectVertRuler( grab.vertRuler );\r
- setDirty();\r
- return true;\r
- }\r
- \r
- return false;\r
- }\r
-\r
- @EventHandler(priority = 0)\r
- public boolean mouseUp(MouseButtonReleasedEvent me) {\r
-\r
- // Release value box grab\r
- if (me.button==1 && draggingValueBox(grab)) {\r
- setHint(KEY_VALUE_TIP_BOX_RELATIVE_POS, new Point2D.Double(\r
- trend.spec.viewProfile.valueViewPositionX,\r
- trend.spec.viewProfile.valueViewPositionY));\r
- grab = null;\r
- }\r
-\r
- // Release value tip grab\r
- if (me.button==1 && draggingValueTip(grab)) {\r
- grab = null;\r
- }\r
-\r
- // Release Box-Zoom\r
- if (me.button==1 && trend.selection!=null) {\r
- Rectangle2D rect = trend.selection.rect;\r
- \r
- if (rect.getWidth()>1 && rect.getHeight()>1) { \r
- if (trend.selection.timeZoom) {\r
- trend.horizRuler.zoomIn( rect.getX()-trend.horizRuler.getX(), rect.getWidth() );\r
- } else {\r
- trend.zoomIn( \r
- rect.getX()-trend.plot.getX(), \r
- rect.getY()-trend.plot.getY(),\r
- rect.getWidth(), rect.getHeight(), true, true );\r
- }\r
- trend.layout();\r
- trend.shapedirty = true;\r
- setDirty();\r
- }\r
- trend.selection.delete();\r
- trend.selection = null;\r
-\r
- return true;\r
- }\r
- \r
- if (grab!=null && grab.mouseButton==me.button) {\r
- grab.cursor.remove();\r
- grab = null;\r
- }\r
- return false;\r
- }\r
-\r
- @EventHandler(priority = 0)\r
- public boolean mouseExit(MouseExitEvent me) {\r
- trend.valueTipHover = false;\r
- setHoverTime( null );\r
- return false;\r
- }\r
-\r
- void setHoverTime(Double time) {\r
- if ( time==null && Double.isNaN(trend.mouseHoverTime) ) return;\r
- if ( time!=null && trend.mouseHoverTime==time ) return;\r
- if ( time==null ) {\r
- trend.mouseHoverTime = Double.NaN;\r
- } else {\r
- trend.mouseHoverTime = time;\r
- trend.lastMouseHoverTime = time;\r
- }\r
- setDirty();\r
- }\r
-\r
-// @EventHandler(priority = 0)\r
-// public boolean mouseEnter(MouseExitEvent me) {\r
-// //double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();\r
-// //double time = trend.horizRuler.toTime( x ) - trend.horizRuler.basetime;\r
-// return false;\r
-// }\r
-\r
- @SuppressWarnings("unused")\r
- @EventHandler(priority = 0)\r
- public boolean mouseMoved(MouseMovedEvent me) {\r
-// ITooltipProvider tt = getContext().getTooltipProvider();\r
-// if (tt!=null) {\r
-// tt.setTooltipText("Hello");\r
-// }\r
-\r
- IG2DNode pick = trend.pickNode( me.controlPosition );\r
- \r
- TrackMouseMovement: {\r
- if ( pick == trend.plot ) {\r
- double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();\r
- double time = trend.horizRuler.toTime( x );\r
- double sx = (trend.horizRuler.end-trend.horizRuler.from) / trend.horizRuler.getWidth();\r
- double timeSnapTolerance = sx * 7.0; \r
- boolean shift = (me.stateMask & MouseEvent.SHIFT_MASK)>0;\r
- \r
- // SNAP - When shift is held\r
- if (shift) {\r
- Double snappedTime = null;\r
- try {\r
- snappedTime = trend.snapToValue( time, timeSnapTolerance );\r
- if (snappedTime != null) time = snappedTime;\r
- } catch (HistoryException e) {\r
- } catch (AccessorException e) {\r
- }\r
- }\r
- \r
- setHoverTime(time);\r
- }\r
- }\r
-\r
- // Implement value box moving while dragging.\r
- if (draggingValueBox(grab)) {\r
- Rectangle2D plotBox = trend.plot.getBounds();\r
- Rectangle2D valueBoxPos = grab.valueBoxPos;\r
- Rectangle2D valueBox = trend.plot.valueTipBoxBounds;\r
- double dx = me.controlPosition.getX() - grab.mousepos.getX();\r
- double dy = me.controlPosition.getY() - grab.mousepos.getY();\r
- double margin = Plot.VALUE_TIP_BOX_PLOT_MARGIN;\r
- double maxX = plotBox.getWidth() - margin - valueBox.getWidth();\r
- double maxY = plotBox.getHeight() - margin - valueBox.getHeight();\r
- double boxX = valueBoxPos.getX() + dx;\r
- double boxY = valueBoxPos.getY() + dy;\r
- double pw = plotBox.getWidth() - 2 * margin;\r
- double ph = plotBox.getHeight() - 2 * margin;\r
- double w = pw - valueBox.getWidth();\r
- double h = ph - valueBox.getHeight();\r
- if (w > 0 && h > 0) {\r
- double rx = (boxX - margin) / w;\r
- double ry = (boxY - margin) / h;\r
- rx = Math.max(0, Math.min(rx, 1));\r
- ry = Math.max(0, Math.min(ry, 1));\r
- trend.spec.viewProfile.valueViewPositionX = rx;\r
- trend.spec.viewProfile.valueViewPositionY = ry;\r
- setDirty();\r
- }\r
- return false;\r
- }\r
-\r
- ValueToolTip: if ( pick == trend.plot) {\r
- if (draggingValueTip(grab)) {\r
- // Move grabbed value-tip\r
- trend.valueTipTime = Double.isNaN(trend.mouseHoverTime) ? null : trend.mouseHoverTime;\r
- return false;\r
- } else {\r
- // Track hover color\r
- if (trend.valueTipTime!=null) {\r
- double valueTipX = trend.horizRuler.toX( trend.valueTipTime );\r
- double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();\r
- boolean hit = x>=valueTipX-TrendLayout.VALUE_TIP_HOVER_SENSITIVITY && x<=valueTipX+TrendLayout.VALUE_TIP_HOVER_SENSITIVITY;\r
- trend.valueTipHover = hit;\r
- }\r
- }\r
- }\r
- \r
- // Item pick\r
- ItemPick: {\r
- if ( pick instanceof VertRuler ) {\r
- VertRuler vr = (VertRuler) pick;\r
- hoveringItem = null;\r
- for (ItemNode item : trend.analogItems) {\r
- if (item.item.renderer != Renderer.Analog || item.ruler==vr) {\r
- if ( hoveringItem != null) break;\r
- hoveringItem = item;\r
- }\r
- }\r
- } else if ( pick == null || pick instanceof Plot ) {\r
- hoveringItem = trend.plot.pickItem( me.controlPosition );\r
- } else {\r
- hoveringItem = null;\r
- }\r
- }\r
- \r
- // Box-Zoom\r
- if (trend.selection!=null) {\r
- trend.selection.setEndPoint( me.controlPosition );\r
- setHoverTime(null);\r
- setDirty();\r
- return true;\r
- }\r
- \r
- // Drag axis\r
- if (grab != null) {\r
- double dx = me.controlPosition.getX() - grab.mousepos.x;\r
- double dy = me.controlPosition.getY() - grab.mousepos.y;\r
- grab.mousepos.setLocation(me.controlPosition);\r
- \r
- if (grab.plot) {\r
- if (grab.horiz) {\r
- trend.horizRuler.translate(-dx*grab.sx);\r
- }\r
- if (grab.vert) {\r
- for (int i=0; i<grab.sy.length; i++) {\r
- if (i>=trend.vertRulers.size()) break;\r
- trend.vertRulers.get(i).translate(dy*grab.sy[i]);\r
- }\r
- }\r
- } else if (grab.horizRuler) {\r
- trend.horizRuler.translate(-dx*grab.sx);\r
- } else if (grab.vertRuler>=0) {\r
- if (grab.vertRuler<=trend.vertRulers.size()) {\r
- VertRuler vr = trend.vertRulers.get( grab.vertRuler );\r
- trend.selectVertRuler( grab.vertRuler );\r
- vr.translate(dy*grab.sy[ grab.vertRuler ]);\r
- }\r
- }\r
- trend.layout();\r
- trend.shapedirty = true;\r
- setDirty();\r
- return true;\r
- }\r
- return false;\r
- }\r
-\r
- @EventHandler(priority = 0)\r
- public boolean mouseWheel(MouseWheelMovedEvent me) {\r
- IG2DNode node = trend.pickNode( me.controlPosition );\r
- if (node instanceof Plot || node instanceof HorizRuler || node instanceof VertRuler) {\r
- Point2D pt = node.parentToLocal( me.controlPosition );\r
- boolean shift = (me.stateMask & MouseEvent.SHIFT_MASK)>0;\r
- boolean alt = (me.stateMask & MouseEvent.ALT_MASK)>0;\r
- double pw = trend.plot.getWidth();\r
- double ph = trend.plot.analogAreaHeight;\r
-\r
- double r = Math.pow(0.9, me.wheelRotation);\r
-\r
- double w = r * pw; \r
- double h = r * ph;\r
- double rx = pt.getX() / pw;\r
- double ry = pt.getY() / ph;\r
-\r
- double zx = (pw-w)*rx;\r
- double zy = (ph-h)*ry;\r
-\r
- if (node instanceof Plot) {\r
- TrendNode trend = (TrendNode) node.getParent();\r
- if (shift||alt) {\r
- for (VertRuler vr : trend.vertRulers) {\r
- vr.zoomIn(zy, h);\r
- }\r
- } \r
- if (!shift) {\r
- trend.horizRuler.zoomIn(zx, w);\r
- }\r
- }\r
-\r
- if (node instanceof HorizRuler) {\r
- //HorizRuler hr = (HorizRuler) node;\r
- trend.horizRuler.zoomIn(zx, w);\r
- }\r
-\r
- if (node instanceof VertRuler) {\r
- VertRuler vr = (VertRuler) node;\r
- trend.selectVertRuler( trend.vertRulers.indexOf(vr) );\r
- vr.zoomIn(zy, h);\r
- }\r
- trend.shapedirty = true;\r
- trend.layout();\r
- setDirty();\r
- return true;\r
- }\r
- return false;\r
- }\r
-\r
- @EventHandler(priority = 0)\r
- public boolean handleCommandEvent(CommandEvent e) {\r
- \r
- if (e.command == Commands.CANCEL) {\r
- if (trend.selection != null) {\r
- trend.selection.delete();\r
- trend.selection = null;\r
- setDirty();\r
- return true;\r
- }\r
- \r
- if (grab!=null) {\r
- if (draggingValueBox(grab)) {\r
- trend.spec.viewProfile.valueViewPositionX = grab.valueBoxRelPos.getX();\r
- trend.spec.viewProfile.valueViewPositionY = grab.valueBoxRelPos.getY();\r
- }\r
- if (grab.cursor!=null) grab.cursor.remove();\r
- grab = null;\r
- setDirty();\r
- return true;\r
- }\r
- \r
- if (trend.valueTipTime != null) {\r
- trend.valueTipTime = null;\r
- setDirty();\r
- return true;\r
- }\r
- return false;\r
- }\r
- \r
- if (e.command == Commands.PAN_LEFT) {\r
- double pw = trend.plot.getWidth();\r
- double ph = trend.plot.analogAreaHeight;\r
- double zx = -pw * KEY_MOVE;\r
- double zy = 0;\r
- trend.zoomIn(zx, zy, pw, ph, true, true);\r
- trend.layout();\r
- setDirty();\r
- }\r
- \r
- if (e.command == Commands.PAN_RIGHT) {\r
- double pw = trend.plot.getWidth();\r
- double ph = trend.plot.analogAreaHeight;\r
- double zx = +pw * KEY_MOVE;\r
- double zy = 0;\r
- trend.zoomIn(zx, zy, pw, ph, true, true);\r
- trend.horizRuler.autoscroll = false;\r
- trend.layout();\r
- setDirty();\r
- }\r
- \r
- if (e.command == Commands.PAN_UP) {\r
- double pw = trend.plot.getWidth();\r
- double ph = trend.plot.analogAreaHeight;\r
- double zx = 0;\r
- double zy = -ph * KEY_MOVE;\r
- trend.zoomIn(zx, zy, pw, ph, true, true);\r
- trend.horizRuler.autoscroll = false;\r
- trend.layout();\r
- setDirty();\r
- }\r
- \r
- if (e.command == Commands.PAN_DOWN) {\r
- double pw = trend.plot.getWidth();\r
- double ph = trend.plot.analogAreaHeight;\r
- double zx = 0;\r
- double zy = +ph * KEY_MOVE;\r
- trend.zoomIn(zx, zy, pw, ph, true, true);\r
- trend.horizRuler.autoscroll = false;\r
- trend.layout();\r
- setDirty();\r
- }\r
- \r
- // Zoom out to time window settings (VK_4)\r
- if (e.command.equals(Commands.AUTOSCALE)) {\r
- trend.horizRuler.autoscroll = true;\r
- for (VertRuler vertRuler : trend.vertRulers) vertRuler.autoscroll = true;\r
- trend.zoomOut();\r
- trend.layout();\r
- setDirty();\r
- }\r
- \r
- // Fit all visible (VK_1)\r
- if (e.command.equals(Commands.ZOOM_TO_FIT)) {\r
- trend.readMinMaxFromEnd();\r
- trend.horizRuler.setFromEnd(trend.horizRuler.iFrom, trend.horizRuler.iEnd);\r
- trend.horizRuler.fireListener();\r
- int c = trend.vertRulers.size();\r
- for (int i=0; i<c; i++) {\r
- VertRuler vr = trend.vertRulers.get(c-i-1);\r
- double nMin = vr.iMin;\r
- double nMax = vr.iMax;\r
-\r
- double diff = nMax - nMin;\r
- if (diff==0.0) {\r
- nMin -= 0.5;\r
- nMax += 0.5;\r
- diff = nMax - nMin;\r
- }\r
- double margin = diff*0.02;\r
- \r
- if (trend.itemPlacement == ItemPlacement.Stacked) {\r
- nMin = nMin - (diff)*i - margin;\r
- nMax = nMax + (diff)*(c-i-1) + margin;\r
- \r
- } else {\r
- nMin = vr.iMin - margin;\r
- nMax = vr.iMax + margin;\r
- }\r
- \r
- vr.zoomTo(nMin, nMax);\r
- }\r
- trend.horizRuler.autoscroll = false;\r
- trend.layout();\r
- setDirty();\r
- return true;\r
- }\r
-\r
- // Fit horiz (VK_2)\r
- if (e.command.equals(Commands.ZOOM_TO_FIT_HORIZ)) {\r
- trend.readMinMaxFromEnd();\r
- trend.horizRuler.setFromEnd(trend.horizRuler.iFrom, trend.horizRuler.iEnd);\r
- trend.layout();\r
- trend.horizRuler.autoscroll = false;\r
- setDirty();\r
- return true;\r
- }\r
-\r
- // Fit vert (VK_3)\r
- if (e.command.equals(Commands.ZOOM_TO_FIT_VERT)) {\r
- trend.readMinMaxFromEnd();\r
- int c = trend.vertRulers.size();\r
- for (int i=0; i<c; i++) {\r
- VertRuler vr = trend.vertRulers.get(c-i-1);\r
- double nMin = vr.iMin;\r
- double nMax = vr.iMax;\r
-\r
- double diff = nMax - nMin;\r
- if (diff==0.0) {\r
- nMin -= 0.5;\r
- nMax += 0.5;\r
- diff = nMax - nMin;\r
- }\r
- double margin = diff*0.02;\r
- \r
- if (trend.itemPlacement == ItemPlacement.Stacked) {\r
- nMin = nMin - (diff)*i - margin;\r
- nMax = nMax + (diff)*(c-i-1) + margin;\r
- \r
- } else {\r
- nMin = vr.iMin - margin;\r
- nMax = vr.iMax + margin;\r
- }\r
- \r
- vr.zoomTo(nMin, nMax);\r
- }\r
- trend.layout();\r
- setDirty();\r
- return true;\r
- }\r
- \r
- // +\r
- if (e.command == Commands.ZOOM_IN) {\r
- double pw = trend.plot.getWidth();\r
- double ph = trend.plot.analogAreaHeight;\r
- double za = 3; // Zoom amount\r
- double w = (1. - (0.1*za)) * pw; \r
- double h = (1. - (0.1*za)) * ph;\r
-\r
- double zx = (pw-w)/2;\r
- double zy = (ph-h)/2;\r
- \r
- trend.zoomIn(zx, zy, w, h, true, true);\r
- trend.horizRuler.autoscroll = false;\r
- trend.layout();\r
- setDirty();\r
- return true;\r
- }\r
- \r
- // -\r
- if (e.command == Commands.ZOOM_OUT) {\r
- double pw = trend.plot.getWidth();\r
- double ph = trend.plot.analogAreaHeight;\r
- double za = -3; // Zoom amount\r
- double w = (1. - (0.1*za)) * pw; \r
- double h = (1. - (0.1*za)) * ph;\r
-\r
- double zx = (pw-w)/2;\r
- double zy = (ph-h)/2;\r
- \r
- trend.zoomIn(zx, zy, w, h, true, true);\r
- trend.horizRuler.autoscroll = false;\r
- trend.layout();\r
- setDirty();\r
- return true;\r
- }\r
- \r
- // Print to printer\r
- if (e.command == Commands.PRINT) {\r
- try {\r
- PrintUtil pu = new PrintUtil();\r
- pu.addTrendPage(trend);\r
- pu.print();\r
- } catch (PrinterException e1) {\r
- e1.printStackTrace();\r
- }\r
- return true;\r
- }\r
- \r
- // Print to PDF\r
- if (e.command == Commands.PDFPRINT) {\r
- PrintUtil pu = new PrintUtil();\r
- pu.addTrendPage(trend);\r
- try {\r
- File f = File.createTempFile("Trend", ".pdf");\r
- pu.printPdf(f);\r
- System.out.println("Printed Trend to "+f);\r
- } catch (IOException e1) {\r
- e1.printStackTrace();\r
- }\r
- return true;\r
- }\r
- \r
- // Read visible values into CSV \r
- if (e.command == Commands.COPY || e.command == Commands.EXPORT) {\r
- StringBuilder sb = new StringBuilder(64*1024);\r
- try {\r
- // TODO: fix to use preferences.\r
- CSVFormatter formatter = new CSVFormatter();\r
- formatter.setTimeRange(trend.horizRuler.from, trend.horizRuler.end);\r
- for (ItemNode i : trend.allItems)\r
- {\r
- if (i.item.hidden) continue;\r
- if (i.historyItems==null || i.historyItems.length==0) continue;\r
- Bean bestQualityStream = i.historyItems[0];\r
- String historyItemId = (String) bestQualityStream.getFieldUnchecked("id");\r
- formatter.addItem( trend.historian, historyItemId, i.item.simpleLabel, i.item.variableReference, i.item.unit);\r
- }\r
- formatter.sort();\r
- try {\r
- // TODO: Use a proper user-cancelable progressmonitor\r
- formatter.formulate2(new ProgressMonitor.Stub(), sb);\r
- } catch (IOException e1) {\r
- // sb cannot throw ioexception\r
- }\r
- } catch (HistoryException e1) {\r
- e1.toString();\r
- }\r
- Toolkit toolkit = Toolkit.getDefaultToolkit();\r
- Clipboard clipboard = toolkit.getSystemClipboard();\r
- StringSelection strSel = new StringSelection(sb.toString());\r
- clipboard.setContents(strSel, null);\r
- }\r
- return false;\r
- }\r
- \r
- static class Grab {\r
- int mouseButton;\r
- IMouseCursorHandle cursor;\r
- Point2D.Double mousepos = new Point2D.Double();\r
- boolean plot = false;\r
- boolean horizRuler = false;\r
- boolean vert = false;\r
- boolean horiz = false;\r
- int vertRuler = -1;\r
- double sx = 1;\r
- double[] sy;\r
- boolean valueTipGrab = false;\r
- boolean valueBoxGrab = false;\r
- Point2D valueBoxRelPos = new Point2D.Double(); \r
- Rectangle2D valueBoxPos = new Rectangle2D.Double();\r
- }\r
-\r
- private static boolean draggingValueTip(Grab g) {\r
- return g != null && g.valueTipGrab;\r
- }\r
-\r
- private static boolean draggingValueBox(Grab g) {\r
- return g != null && g.valueBoxGrab;\r
- }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2011 Association for Decentralized Information Management in
+ * Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.trend.impl;
+
+import java.awt.Cursor;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.print.PrinterException;
+import java.io.File;
+import java.io.IOException;
+
+import org.simantics.databoard.accessor.error.AccessorException;
+import org.simantics.databoard.util.Bean;
+import org.simantics.g2d.canvas.Hints;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.IMouseCursorHandle;
+import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
+import org.simantics.g2d.canvas.impl.HintReflection.HintListener;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
+import org.simantics.g2d.chassis.ITooltipProvider;
+import org.simantics.g2d.participant.TimeParticipant;
+import org.simantics.history.HistoryException;
+import org.simantics.history.csv.CSVFormatter;
+import org.simantics.history.util.ProgressMonitor;
+import org.simantics.scenegraph.g2d.IG2DNode;
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
+import org.simantics.scenegraph.g2d.events.MouseEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseWheelMovedEvent;
+import org.simantics.scenegraph.g2d.events.TimeEvent;
+import org.simantics.scenegraph.g2d.events.command.CommandEvent;
+import org.simantics.scenegraph.g2d.events.command.Commands;
+import org.simantics.trend.configuration.ItemPlacement;
+import org.simantics.trend.configuration.TrendItem.Renderer;
+import org.simantics.trend.util.PrintUtil;
+import org.simantics.utils.datastructures.hints.IHintContext.Key;
+import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
+import org.simantics.utils.datastructures.hints.IHintObservable;
+
+public class TrendParticipant extends AbstractCanvasParticipant {
+
+ /**
+ * Key for storing chart value tip box relative position. Changed by
+ * {@link TrendParticipant} after user finishes dragging this class around.
+ * */
+ public static final Key KEY_VALUE_TIP_BOX_RELATIVE_POS = new KeyOf(Point2D.class);
+
+ /** Key for chart redraw interval in milliseconds (drawn only if dirty) */
+ public static final Key KEY_TREND_DRAW_INTERVAL = new KeyOf(Long.class);
+
+ /** The time code when trend was last redrawn */
+ public static final Key KEY_TREND_SHAPE_LAST = new KeyOf(Long.class);
+
+ /** Key for interval time of value scale */
+ public static final Key KEY_TREND_AUTOSCALE_INTERVAL = new KeyOf(Long.class);
+
+ /** Key for time of last autoscale */
+ public static final Key KEY_TREND_AUTOSCALE_LAST = new KeyOf(Long.class);
+
+ public static final double KEY_MOVE = 0.33;
+
+ /** Cursor when panning */
+ public MouseCursors cursors = new MouseCursors();
+
+ @HintListener(Class=Hints.class, Field="KEY_CONTROL_BOUNDS")
+ public void selectionChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
+ trend.shapedirty = true;
+ setDirty();
+ }
+
+ TrendNode trend;
+
+ @Dependency TimeParticipant time;
+
+ Grab grab;
+
+ // The single item on which the mouse hovers over is set to this field. (Binary items are selectable).
+ public ItemNode hoveringItem;
+
+ @Override
+ public void addedToContext(ICanvasContext ctx) {
+ super.addedToContext(ctx);
+ time.registerForEvents(getClass());
+ }
+
+ public void setTrend(TrendNode node) {
+ trend = node;
+ }
+
+ public TrendNode getTrend() {
+ return trend;
+ }
+
+// @SGInit
+// public void initSG(G2DParentNode parent) {
+// Set<TrendNode> nodes = NodeUtil.collectNodes(parent, TrendNode.class);
+// trends.addAll(nodes);
+// }
+
+ @SGCleanup
+ public void cleanupSG() {
+ }
+
+ protected void updateNode() {
+// titleNode.setText(text)
+// node.setEnabled(isPaintingEnabled());
+// node.setGridColor(getGridColor());
+// node.setGridSize(getGridSize());
+ }
+
+ @EventHandler(priority = 0)
+ public boolean handleTimeEvent(TimeEvent e) {
+ //System.out.println("(" + isRemoved() + ") time event: " + e.time + " (" + e.interval + ")");
+ if (isRemoved()) {
+ if (time != null)
+ time.unregisterForEvents(getClass());
+ return false;
+ }
+
+ long currentTime = e.time;
+
+ Long drawInterval = getHint(KEY_TREND_DRAW_INTERVAL);
+ if (drawInterval == null) drawInterval = 200L;
+ Long lastDrawTime = getHint(KEY_TREND_SHAPE_LAST);
+ boolean drawIntervalElapsed = lastDrawTime == null || (currentTime>=lastDrawTime+drawInterval);
+ if (drawIntervalElapsed) {
+// System.out.println("elapsed="+drawIntervalElapsed+" currentTime="+currentTime+" lastDraw="+lastDrawTime+", interval="+drawInterval+", nextDrawTime="+(lastDrawTime+drawInterval));
+// System.out.println("elapsed="+drawIntervalElapsed+" currentTime="+currentTime+" lastDraw="+lastDrawTime+", interval="+drawInterval);
+ setHint(KEY_TREND_SHAPE_LAST, currentTime);
+ trend.shapedirty |= trend.datadirty;
+ trend.datadirty = false;
+ }
+
+ // Scale time
+ Long autoscaleInterval = getHint(KEY_TREND_AUTOSCALE_INTERVAL);
+ if (autoscaleInterval == null) autoscaleInterval = 1000L;
+ Long autoscaleTime = getHint(KEY_TREND_AUTOSCALE_LAST);
+ boolean autoscale = autoscaleTime==null || (currentTime>=autoscaleTime+autoscaleInterval);
+ if (autoscale) {
+ boolean l = trend.autoscale(trend.autoscaletime, autoscale);
+ if (!draggingValueTip(grab)) {
+ trend.updateValueTipTime();
+ }
+ if (l) {
+ trend.layout();
+ }
+
+ setHint(KEY_TREND_AUTOSCALE_LAST, currentTime);
+ }
+
+ // IT IS DRAW TIME
+ if (trend.shapedirty) {
+// System.out.println(currentTime+": Draw time!");
+ setDirty();
+ }
+
+ return false;
+ }
+
+ @EventHandler(priority = 0)
+ public boolean mouseClick(MouseClickEvent me) {
+ IG2DNode node = trend.pickNode( me.controlPosition );
+
+ if (me.clickCount==2 && node!=null) {
+ trend.valueTipTime = trend.prevValueTipTime;
+ if (node instanceof Plot) {
+ boolean binaryArea = me.controlPosition.getY()>trend.plot.getY()+trend.plot.analogAreaHeight;
+ if (binaryArea) {
+ trend.horizRuler.zoomOut();
+ } else {
+ trend.zoomOut();
+ }
+ setDirty();
+ return true;
+ } else if (node instanceof HorizRuler) {
+ HorizRuler hr = (HorizRuler) node;
+ hr.zoomOut();
+ setDirty();
+ return true;
+ } else if (node instanceof VertRuler) {
+ VertRuler vr = (VertRuler) node;
+ vr.zoomOut();
+ setDirty();
+ return true;
+ }
+ }
+
+ // Click ValueTip
+ if (me.clickCount==1 && me.button==MouseEvent.LEFT_BUTTON && node!=null && node instanceof Plot) {
+ trend.prevValueTipTime = trend.valueTipTime;
+ if (trend.valueTipTime == null) {
+ trend.valueTipTime = Double.isNaN(trend.mouseHoverTime)?null:trend.mouseHoverTime;
+ trend.valueTipHover = true;
+ } else {
+ double valueTipX = trend.horizRuler.toX( trend.valueTipTime );
+ double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();
+ boolean hit = x>=valueTipX-TrendLayout.VALUE_TIP_HOVER_SENSITIVITY && x<=valueTipX+TrendLayout.VALUE_TIP_HOVER_SENSITIVITY;
+ if (hit) trend.valueTipTime = null;
+ }
+ setDirty();
+ }
+
+ /*
+ // Right click removes value tip
+ if (me.clickCount==1 && me.button==MouseEvent.RIGHT_BUTTON) {
+ if (trend.valueTipTime != null) {
+ trend.valueTipTime = null;
+ setDirty();
+ }
+ }*/
+
+ if (me.clickCount==1 && me.button==MouseEvent.LEFT_BUTTON) {
+ if (node instanceof VertRuler) {
+ VertRuler vr = (VertRuler) node;
+ trend.selectVertRuler( trend.vertRulers.indexOf(vr) );
+ setDirty();
+ } else if (node == null || node instanceof TextNode) {
+ Plot p = trend.plot;
+ double x = me.controlPosition.getX() - p.getX();
+ double y = me.controlPosition.getY() - p.getY();
+ if ( x>=0 && x<=p.getWidth() && y<=0 && y>=-Plot.DIAMOND_SIZE*2) {
+ // Click hits milestone area
+ milestoneSearch: for ( Milestone ms : trend.milestones.milestones ) {
+ double mx = trend.horizRuler.toX( ms.time );
+ if ( x>=mx-Plot.DIAMOND_SIZE && x<=mx+Plot.DIAMOND_SIZE ) {
+ ITooltipProvider tp = getContext().getTooltipProvider();
+ if (tp!=null) {
+ tp.show(ms.label, ms.description);
+ }
+ break milestoneSearch;
+ }
+ }
+ }
+
+ }
+
+ }
+ return false;
+ }
+
+ @EventHandler(priority = 0)
+ public boolean mouseDown(MouseButtonPressedEvent me) {
+ // Start Box-Zoom
+ if (me.button==MouseEvent.LEFT_BUTTON) {
+ IG2DNode node = trend.pickNode( me.controlPosition );
+ if (node == null || node instanceof Plot) {
+
+ // Start value box grab
+ {
+ double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();
+ double y = me.controlPosition.getY() - trend.plot.getBounds().getY();
+ if (trend.plot.valueTipBoxBounds.contains(x, y)) {
+ //System.out.println("grabbed value box @ " + x + ", " + y);
+ grab = new Grab();
+ grab.valueBoxRelPos.setLocation(trend.spec.viewProfile.valueViewPositionX, trend.spec.viewProfile.valueViewPositionY);
+ grab.valueBoxPos.setFrame(trend.plot.valueTipBoxBounds);
+ grab.mousepos = new Point2D.Double( me.controlPosition.getX(), me.controlPosition.getY() );
+ grab.valueBoxGrab = true;
+ return true;
+ }
+ }
+
+ // Start Value-Tip grab
+ if (trend.valueTipTime!=null) {
+ double valueTipX = trend.horizRuler.toX( trend.valueTipTime );
+ double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();
+ boolean hit = x>=valueTipX-TrendLayout.VALUE_TIP_HOVER_SENSITIVITY && x<=valueTipX+TrendLayout.VALUE_TIP_HOVER_SENSITIVITY;
+ if (hit) {
+ grab = new Grab();
+ grab.mousepos = new Point2D.Double( me.controlPosition.getX(), me.controlPosition.getY() );
+ grab.sx = trend.horizRuler.unitsPerPixel();
+ grab.sy = new double[ trend.vertRulers.size() ];
+ grab.valueTipGrab = true;
+ return true;
+ }
+ }
+
+ // Start Box-Zoom
+ boolean binaryArea = me.controlPosition.getY()>trend.plot.getY()+trend.plot.analogAreaHeight;
+ boolean timeZoom = (me.stateMask & MouseEvent.SHIFT_MASK)>0 || binaryArea;
+ if (trend.selection==null) {
+ trend.selection = trend.addNode("Selection", SelectionNode.class);
+ trend.selection.setZIndex( 10 );
+ trend.selection.start( me.controlPosition, binaryArea, timeZoom );
+ }
+ return true;
+ }
+ }
+
+ // Start grab
+ IG2DNode node = trend.pickNode( me.controlPosition );
+ if ( (me.button==MouseEvent.MIDDLE_BUTTON) &&
+ node!=null && node instanceof Plot) {
+ //Plot p = (Plot) node;
+ TrendNode trend = (TrendNode) node.getParent();
+ boolean shift = (me.stateMask & MouseEvent.SHIFT_MASK)>0;
+ boolean alt = (me.stateMask & MouseEvent.ALT_MASK)>0;
+ //boolean analogArea = me.controlPosition.getY() < p.getY()+p.analogAreaHeight;
+ //boolean binaryArea = me.controlPosition.getY() >= p.getY()+p.analogAreaHeight;
+ grab = new Grab();
+ grab.mousepos = new Point2D.Double( me.controlPosition.getX(), me.controlPosition.getY() );
+ grab.sx = trend.horizRuler.unitsPerPixel();
+ grab.sy = new double[ trend.vertRulers.size() ];
+ grab.horiz = !shift || alt;
+ grab.vert = shift || alt;
+ if (grab.vert) {
+ for (int i=0; i<trend.vertRulers.size(); i++)
+ grab.sy[i] = trend.vertRulers.get(i).unitsPerPixel();
+ }
+ Cursor c = grab.horiz ? (grab.vert ? cursors.grab : cursors.grab_horiz) : (grab.vert ? cursors.grab_vert : cursors.grab);
+ grab.cursor = getContext().getMouseCursorContext().setCursor(me.mouseId, c);
+ grab.mouseButton = me.button;
+ grab.plot = true;
+ trend.horizRuler.translate(0);
+ setHoverTime(null);
+ setDirty();
+ return true;
+ }
+ if ( (me.button==MouseEvent.LEFT_BUTTON||me.button==MouseEvent.MIDDLE_BUTTON) &&
+ node!=null && node instanceof HorizRuler) {
+ HorizRuler hr = (HorizRuler) node;
+ grab = new Grab();
+ grab.cursor = getContext().getMouseCursorContext().setCursor(me.mouseId, cursors.grab_horiz);
+ grab.mousepos = new Point2D.Double();
+ grab.mousepos.setLocation( me.controlPosition );
+ grab.sx = hr.unitsPerPixel();
+ grab.horizRuler = true;
+ grab.mouseButton = me.button;
+ grab.horiz = true;
+ trend.horizRuler.translate(0);
+ setDirty();
+ return true;
+ }
+ if ( (me.button==MouseEvent.LEFT_BUTTON||me.button==MouseEvent.MIDDLE_BUTTON) &&
+ node!=null && node instanceof VertRuler) {
+ //VertRuler vr = (VertRuler) node;
+ grab = new Grab();
+ grab.cursor = getContext().getMouseCursorContext().setCursor(me.mouseId, cursors.grab_vert);
+ grab.mousepos = new Point2D.Double();
+ grab.mousepos.setLocation( me.controlPosition );
+ grab.sy = new double[ trend.vertRulers.size() ];
+ for (int i=0; i<trend.vertRulers.size(); i++)
+ grab.sy[i] = trend.vertRulers.get(i).unitsPerPixel();
+ grab.mouseButton = me.button;
+ grab.vertRuler = trend.vertRulers.indexOf(node);
+ trend.selectVertRuler( grab.vertRuler );
+ setDirty();
+ return true;
+ }
+
+ return false;
+ }
+
+ @EventHandler(priority = 0)
+ public boolean mouseUp(MouseButtonReleasedEvent me) {
+
+ // Release value box grab
+ if (me.button==1 && draggingValueBox(grab)) {
+ setHint(KEY_VALUE_TIP_BOX_RELATIVE_POS, new Point2D.Double(
+ trend.spec.viewProfile.valueViewPositionX,
+ trend.spec.viewProfile.valueViewPositionY));
+ grab = null;
+ }
+
+ // Release value tip grab
+ if (me.button==1 && draggingValueTip(grab)) {
+ grab = null;
+ }
+
+ // Release Box-Zoom
+ if (me.button==1 && trend.selection!=null) {
+ Rectangle2D rect = trend.selection.rect;
+
+ if (rect.getWidth()>1 && rect.getHeight()>1) {
+ if (trend.selection.timeZoom) {
+ trend.horizRuler.zoomIn( rect.getX()-trend.horizRuler.getX(), rect.getWidth() );
+ } else {
+ trend.zoomIn(
+ rect.getX()-trend.plot.getX(),
+ rect.getY()-trend.plot.getY(),
+ rect.getWidth(), rect.getHeight(), true, true );
+ }
+ trend.layout();
+ trend.shapedirty = true;
+ setDirty();
+ }
+ trend.selection.delete();
+ trend.selection = null;
+
+ return true;
+ }
+
+ if (grab!=null && grab.mouseButton==me.button) {
+ grab.cursor.remove();
+ grab = null;
+ }
+ return false;
+ }
+
+ @EventHandler(priority = 0)
+ public boolean mouseExit(MouseExitEvent me) {
+ trend.valueTipHover = false;
+ setHoverTime( null );
+ return false;
+ }
+
+ void setHoverTime(Double time) {
+ if ( time==null && Double.isNaN(trend.mouseHoverTime) ) return;
+ if ( time!=null && trend.mouseHoverTime==time ) return;
+ if ( time==null ) {
+ trend.mouseHoverTime = Double.NaN;
+ } else {
+ trend.mouseHoverTime = time;
+ trend.lastMouseHoverTime = time;
+ }
+ setDirty();
+ }
+
+// @EventHandler(priority = 0)
+// public boolean mouseEnter(MouseExitEvent me) {
+// //double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();
+// //double time = trend.horizRuler.toTime( x ) - trend.horizRuler.basetime;
+// return false;
+// }
+
+ @SuppressWarnings("unused")
+ @EventHandler(priority = 0)
+ public boolean mouseMoved(MouseMovedEvent me) {
+// ITooltipProvider tt = getContext().getTooltipProvider();
+// if (tt!=null) {
+// tt.setTooltipText("Hello");
+// }
+
+ IG2DNode pick = trend.pickNode( me.controlPosition );
+
+ TrackMouseMovement: {
+ if ( pick == trend.plot ) {
+ double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();
+ double time = trend.horizRuler.toTime( x );
+ double sx = (trend.horizRuler.end-trend.horizRuler.from) / trend.horizRuler.getWidth();
+ double timeSnapTolerance = sx * 7.0;
+ boolean shift = (me.stateMask & MouseEvent.SHIFT_MASK)>0;
+
+ // SNAP - When shift is held
+ if (shift) {
+ Double snappedTime = null;
+ try {
+ snappedTime = trend.snapToValue( time, timeSnapTolerance );
+ if (snappedTime != null) time = snappedTime;
+ } catch (HistoryException e) {
+ } catch (AccessorException e) {
+ }
+ }
+
+ setHoverTime(time);
+ }
+ }
+
+ // Implement value box moving while dragging.
+ if (draggingValueBox(grab)) {
+ Rectangle2D plotBox = trend.plot.getBounds();
+ Rectangle2D valueBoxPos = grab.valueBoxPos;
+ Rectangle2D valueBox = trend.plot.valueTipBoxBounds;
+ double dx = me.controlPosition.getX() - grab.mousepos.getX();
+ double dy = me.controlPosition.getY() - grab.mousepos.getY();
+ double margin = Plot.VALUE_TIP_BOX_PLOT_MARGIN;
+ double maxX = plotBox.getWidth() - margin - valueBox.getWidth();
+ double maxY = plotBox.getHeight() - margin - valueBox.getHeight();
+ double boxX = valueBoxPos.getX() + dx;
+ double boxY = valueBoxPos.getY() + dy;
+ double pw = plotBox.getWidth() - 2 * margin;
+ double ph = plotBox.getHeight() - 2 * margin;
+ double w = pw - valueBox.getWidth();
+ double h = ph - valueBox.getHeight();
+ if (w > 0 && h > 0) {
+ double rx = (boxX - margin) / w;
+ double ry = (boxY - margin) / h;
+ rx = Math.max(0, Math.min(rx, 1));
+ ry = Math.max(0, Math.min(ry, 1));
+ trend.spec.viewProfile.valueViewPositionX = rx;
+ trend.spec.viewProfile.valueViewPositionY = ry;
+ setDirty();
+ }
+ return false;
+ }
+
+ ValueToolTip: if ( pick == trend.plot) {
+ if (draggingValueTip(grab)) {
+ // Move grabbed value-tip
+ trend.valueTipTime = Double.isNaN(trend.mouseHoverTime) ? null : trend.mouseHoverTime;
+ return false;
+ } else {
+ // Track hover color
+ if (trend.valueTipTime!=null) {
+ double valueTipX = trend.horizRuler.toX( trend.valueTipTime );
+ double x = me.controlPosition.getX() - trend.horizRuler.getBounds().getX();
+ boolean hit = x>=valueTipX-TrendLayout.VALUE_TIP_HOVER_SENSITIVITY && x<=valueTipX+TrendLayout.VALUE_TIP_HOVER_SENSITIVITY;
+ trend.valueTipHover = hit;
+ }
+ }
+ }
+
+ // Item pick
+ ItemPick: {
+ if ( pick instanceof VertRuler ) {
+ VertRuler vr = (VertRuler) pick;
+ hoveringItem = null;
+ for (ItemNode item : trend.analogItems) {
+ if (item.item.renderer != Renderer.Analog || item.ruler==vr) {
+ if ( hoveringItem != null) break;
+ hoveringItem = item;
+ }
+ }
+ } else if ( pick == null || pick instanceof Plot ) {
+ hoveringItem = trend.plot.pickItem( me.controlPosition );
+ } else {
+ hoveringItem = null;
+ }
+ }
+
+ // Box-Zoom
+ if (trend.selection!=null) {
+ trend.selection.setEndPoint( me.controlPosition );
+ setHoverTime(null);
+ setDirty();
+ return true;
+ }
+
+ // Drag axis
+ if (grab != null) {
+ double dx = me.controlPosition.getX() - grab.mousepos.x;
+ double dy = me.controlPosition.getY() - grab.mousepos.y;
+ grab.mousepos.setLocation(me.controlPosition);
+
+ if (grab.plot) {
+ if (grab.horiz) {
+ trend.horizRuler.translate(-dx*grab.sx);
+ }
+ if (grab.vert) {
+ for (int i=0; i<grab.sy.length; i++) {
+ if (i>=trend.vertRulers.size()) break;
+ trend.vertRulers.get(i).translate(dy*grab.sy[i]);
+ }
+ }
+ } else if (grab.horizRuler) {
+ trend.horizRuler.translate(-dx*grab.sx);
+ } else if (grab.vertRuler>=0) {
+ if (grab.vertRuler<=trend.vertRulers.size()) {
+ VertRuler vr = trend.vertRulers.get( grab.vertRuler );
+ trend.selectVertRuler( grab.vertRuler );
+ vr.translate(dy*grab.sy[ grab.vertRuler ]);
+ }
+ }
+ trend.layout();
+ trend.shapedirty = true;
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ @EventHandler(priority = 0)
+ public boolean mouseWheel(MouseWheelMovedEvent me) {
+ IG2DNode node = trend.pickNode( me.controlPosition );
+ if (node instanceof Plot || node instanceof HorizRuler || node instanceof VertRuler) {
+ Point2D pt = node.parentToLocal( me.controlPosition );
+ boolean shift = (me.stateMask & MouseEvent.SHIFT_MASK)>0;
+ boolean alt = (me.stateMask & MouseEvent.ALT_MASK)>0;
+ double pw = trend.plot.getWidth();
+ double ph = trend.plot.analogAreaHeight;
+
+ double r = Math.pow(0.9, me.wheelRotation);
+
+ double w = r * pw;
+ double h = r * ph;
+ double rx = pt.getX() / pw;
+ double ry = pt.getY() / ph;
+
+ double zx = (pw-w)*rx;
+ double zy = (ph-h)*ry;
+
+ if (node instanceof Plot) {
+ TrendNode trend = (TrendNode) node.getParent();
+ if (shift||alt) {
+ for (VertRuler vr : trend.vertRulers) {
+ vr.zoomIn(zy, h);
+ }
+ }
+ if (!shift) {
+ trend.horizRuler.zoomIn(zx, w);
+ }
+ }
+
+ if (node instanceof HorizRuler) {
+ //HorizRuler hr = (HorizRuler) node;
+ trend.horizRuler.zoomIn(zx, w);
+ }
+
+ if (node instanceof VertRuler) {
+ VertRuler vr = (VertRuler) node;
+ trend.selectVertRuler( trend.vertRulers.indexOf(vr) );
+ vr.zoomIn(zy, h);
+ }
+ trend.shapedirty = true;
+ trend.layout();
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ @EventHandler(priority = 0)
+ public boolean handleCommandEvent(CommandEvent e) {
+
+ if (e.command == Commands.CANCEL) {
+ if (trend.selection != null) {
+ trend.selection.delete();
+ trend.selection = null;
+ setDirty();
+ return true;
+ }
+
+ if (grab!=null) {
+ if (draggingValueBox(grab)) {
+ trend.spec.viewProfile.valueViewPositionX = grab.valueBoxRelPos.getX();
+ trend.spec.viewProfile.valueViewPositionY = grab.valueBoxRelPos.getY();
+ }
+ if (grab.cursor!=null) grab.cursor.remove();
+ grab = null;
+ setDirty();
+ return true;
+ }
+
+ if (trend.valueTipTime != null) {
+ trend.valueTipTime = null;
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ if (e.command == Commands.PAN_LEFT) {
+ double pw = trend.plot.getWidth();
+ double ph = trend.plot.analogAreaHeight;
+ double zx = -pw * KEY_MOVE;
+ double zy = 0;
+ trend.zoomIn(zx, zy, pw, ph, true, true);
+ trend.layout();
+ setDirty();
+ }
+
+ if (e.command == Commands.PAN_RIGHT) {
+ double pw = trend.plot.getWidth();
+ double ph = trend.plot.analogAreaHeight;
+ double zx = +pw * KEY_MOVE;
+ double zy = 0;
+ trend.zoomIn(zx, zy, pw, ph, true, true);
+ trend.horizRuler.autoscroll = false;
+ trend.layout();
+ setDirty();
+ }
+
+ if (e.command == Commands.PAN_UP) {
+ double pw = trend.plot.getWidth();
+ double ph = trend.plot.analogAreaHeight;
+ double zx = 0;
+ double zy = -ph * KEY_MOVE;
+ trend.zoomIn(zx, zy, pw, ph, true, true);
+ trend.horizRuler.autoscroll = false;
+ trend.layout();
+ setDirty();
+ }
+
+ if (e.command == Commands.PAN_DOWN) {
+ double pw = trend.plot.getWidth();
+ double ph = trend.plot.analogAreaHeight;
+ double zx = 0;
+ double zy = +ph * KEY_MOVE;
+ trend.zoomIn(zx, zy, pw, ph, true, true);
+ trend.horizRuler.autoscroll = false;
+ trend.layout();
+ setDirty();
+ }
+
+ // Zoom out to time window settings (VK_4)
+ if (e.command.equals(Commands.AUTOSCALE)) {
+ trend.horizRuler.autoscroll = true;
+ for (VertRuler vertRuler : trend.vertRulers) vertRuler.autoscroll = true;
+ trend.zoomOut();
+ trend.layout();
+ setDirty();
+ }
+
+ // Fit all visible (VK_1)
+ if (e.command.equals(Commands.ZOOM_TO_FIT)) {
+ trend.readMinMaxFromEnd();
+ trend.horizRuler.setFromEnd(trend.horizRuler.iFrom, trend.horizRuler.iEnd);
+ trend.horizRuler.fireListener();
+ int c = trend.vertRulers.size();
+ for (int i=0; i<c; i++) {
+ VertRuler vr = trend.vertRulers.get(c-i-1);
+ double nMin = vr.iMin;
+ double nMax = vr.iMax;
+
+ double diff = nMax - nMin;
+ if (diff==0.0) {
+ nMin -= 0.5;
+ nMax += 0.5;
+ diff = nMax - nMin;
+ }
+ double margin = diff*0.02;
+
+ if (trend.itemPlacement == ItemPlacement.Stacked) {
+ nMin = nMin - (diff)*i - margin;
+ nMax = nMax + (diff)*(c-i-1) + margin;
+
+ } else {
+ nMin = vr.iMin - margin;
+ nMax = vr.iMax + margin;
+ }
+
+ vr.zoomTo(nMin, nMax);
+ }
+ trend.horizRuler.autoscroll = false;
+ trend.layout();
+ setDirty();
+ return true;
+ }
+
+ // Fit horiz (VK_2)
+ if (e.command.equals(Commands.ZOOM_TO_FIT_HORIZ)) {
+ trend.readMinMaxFromEnd();
+ trend.horizRuler.setFromEnd(trend.horizRuler.iFrom, trend.horizRuler.iEnd);
+ trend.layout();
+ trend.horizRuler.autoscroll = false;
+ setDirty();
+ return true;
+ }
+
+ // Fit vert (VK_3)
+ if (e.command.equals(Commands.ZOOM_TO_FIT_VERT)) {
+ trend.readMinMaxFromEnd();
+ int c = trend.vertRulers.size();
+ for (int i=0; i<c; i++) {
+ VertRuler vr = trend.vertRulers.get(c-i-1);
+ double nMin = vr.iMin;
+ double nMax = vr.iMax;
+
+ double diff = nMax - nMin;
+ if (diff==0.0) {
+ nMin -= 0.5;
+ nMax += 0.5;
+ diff = nMax - nMin;
+ }
+ double margin = diff*0.02;
+
+ if (trend.itemPlacement == ItemPlacement.Stacked) {
+ nMin = nMin - (diff)*i - margin;
+ nMax = nMax + (diff)*(c-i-1) + margin;
+
+ } else {
+ nMin = vr.iMin - margin;
+ nMax = vr.iMax + margin;
+ }
+
+ vr.zoomTo(nMin, nMax);
+ }
+ trend.layout();
+ setDirty();
+ return true;
+ }
+
+ // +
+ if (e.command == Commands.ZOOM_IN) {
+ double pw = trend.plot.getWidth();
+ double ph = trend.plot.analogAreaHeight;
+ double za = 3; // Zoom amount
+ double w = (1. - (0.1*za)) * pw;
+ double h = (1. - (0.1*za)) * ph;
+
+ double zx = (pw-w)/2;
+ double zy = (ph-h)/2;
+
+ trend.zoomIn(zx, zy, w, h, true, true);
+ trend.horizRuler.autoscroll = false;
+ trend.layout();
+ setDirty();
+ return true;
+ }
+
+ // -
+ if (e.command == Commands.ZOOM_OUT) {
+ double pw = trend.plot.getWidth();
+ double ph = trend.plot.analogAreaHeight;
+ double za = -3; // Zoom amount
+ double w = (1. - (0.1*za)) * pw;
+ double h = (1. - (0.1*za)) * ph;
+
+ double zx = (pw-w)/2;
+ double zy = (ph-h)/2;
+
+ trend.zoomIn(zx, zy, w, h, true, true);
+ trend.horizRuler.autoscroll = false;
+ trend.layout();
+ setDirty();
+ return true;
+ }
+
+ // Print to printer
+ if (e.command == Commands.PRINT) {
+ try {
+ PrintUtil pu = new PrintUtil();
+ pu.addTrendPage(trend);
+ pu.print();
+ } catch (PrinterException e1) {
+ e1.printStackTrace();
+ }
+ return true;
+ }
+
+ // Print to PDF
+ if (e.command == Commands.PDFPRINT) {
+ PrintUtil pu = new PrintUtil();
+ pu.addTrendPage(trend);
+ try {
+ File f = File.createTempFile("Trend", ".pdf");
+ pu.printPdf(f);
+ System.out.println("Printed Trend to "+f);
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+ return true;
+ }
+
+ // Read visible values into CSV
+ if (e.command == Commands.COPY || e.command == Commands.EXPORT) {
+ StringBuilder sb = new StringBuilder(64*1024);
+ try {
+ // TODO: fix to use preferences.
+ CSVFormatter formatter = new CSVFormatter();
+ formatter.setTimeRange(trend.horizRuler.from, trend.horizRuler.end);
+ for (ItemNode i : trend.allItems)
+ {
+ if (i.item.hidden) continue;
+ if (i.historyItems==null || i.historyItems.length==0) continue;
+ Bean bestQualityStream = i.historyItems[0];
+ String historyItemId = (String) bestQualityStream.getFieldUnchecked("id");
+ formatter.addItem( trend.historian, historyItemId, i.item.simpleLabel, i.item.variableReference, i.item.unit);
+ }
+ formatter.sort();
+ try {
+ // TODO: Use a proper user-cancelable progressmonitor
+ formatter.formulate2(new ProgressMonitor.Stub(), sb);
+ } catch (IOException e1) {
+ // sb cannot throw ioexception
+ }
+ } catch (HistoryException e1) {
+ e1.toString();
+ }
+ Toolkit toolkit = Toolkit.getDefaultToolkit();
+ Clipboard clipboard = toolkit.getSystemClipboard();
+ StringSelection strSel = new StringSelection(sb.toString());
+ clipboard.setContents(strSel, null);
+ }
+ return false;
+ }
+
+ static class Grab {
+ int mouseButton;
+ IMouseCursorHandle cursor;
+ Point2D.Double mousepos = new Point2D.Double();
+ boolean plot = false;
+ boolean horizRuler = false;
+ boolean vert = false;
+ boolean horiz = false;
+ int vertRuler = -1;
+ double sx = 1;
+ double[] sy;
+ boolean valueTipGrab = false;
+ boolean valueBoxGrab = false;
+ Point2D valueBoxRelPos = new Point2D.Double();
+ Rectangle2D valueBoxPos = new Rectangle2D.Double();
+ }
+
+ private static boolean draggingValueTip(Grab g) {
+ return g != null && g.valueTipGrab;
+ }
+
+ private static boolean draggingValueBox(Grab g) {
+ return g != null && g.valueBoxGrab;
+ }
+
+}