/******************************************************************************* * Copyright (c) 2007, 2010 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.diagram.elements; import gnu.trove.map.TIntObjectMap; import gnu.trove.map.TMap; import gnu.trove.map.hash.THashMap; import gnu.trove.map.hash.TIntObjectHashMap; import gnu.trove.procedure.TObjectProcedure; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.List; import org.simantics.datatypes.literal.Vec2d; import org.simantics.db.layer0.variable.RVI; import org.simantics.diagram.profile.MonitorTextGridResult; import org.simantics.scenegraph.ExportableWidget.OutputWidget; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.g2d.G2DSceneGraph; import org.simantics.scenegraph.g2d.events.EventTypes; import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent; import org.simantics.scenegraph.g2d.events.MouseEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent; import org.simantics.scenegraph.g2d.nodes.Decoration; import org.simantics.scenegraph.utils.NodeUtil; import org.simantics.scl.runtime.function.Function1; /** * TODO: does not work remotely, e.g. rowIds */ @OutputWidget("textGrid") public class TextGridNode extends G2DParentNode implements Decoration { public static class A extends TextNode { MonitorTextGridResult cache = null; private static final long serialVersionUID = -4519849713591842241L; public MonitorTextGridResult getCache() { return cache; } public void setCache(MonitorTextGridResult cache) { this.cache = cache; } } private static final int CACHED_COLUMNS = 4; private static final int CACHED_ROWS = 10; private static Cell[][] cache; static { cache = new Cell[CACHED_ROWS][]; for (int row = 0; row < CACHED_ROWS; ++row) { cache[row] = new Cell[CACHED_COLUMNS]; for (int col = 0; col < CACHED_COLUMNS; ++col) { cache[row][col] = new Cell(col+1, row+1); } } } private static String makeId(int x, int y) { return x + "#" + y; } static class Cell { public final int x; public final int y; public Cell(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object object) { if (this == object) return true; else if (object == null) return false; else if (Cell.class != object.getClass()) return false; Cell p = (Cell)object; return x == p.x && y == p.y; } @Override public int hashCode() { return x *31 + y; } @Override public String toString() { return makeId(x, y); } public static Cell make(int x, int y) { if (x >= 1 && x <= CACHED_COLUMNS && y >= 1 && y <= CACHED_ROWS) return cache[y-1][x-1]; return new Cell(x,y); } } private static final long serialVersionUID = 7015425802228571055L; private TMap nodes = new THashMap(); private TIntObjectMap rowIds = new TIntObjectHashMap<>(); private boolean up = true; static class MaxY implements TObjectProcedure { int max = 0; @Override public boolean execute(Cell key) { if (key.y > max) max = key.y; return true; } } private int computeRows() { MaxY maxy = new MaxY(); nodes.forEachKey(maxy); return maxy.max; } private List peekRowCells(int y) { ArrayList row = new ArrayList(4); for (Cell key : nodes.keySet()) if (key.y == y) row.add(key); return row; } public void setUp(boolean up) { this.up = up; } public A get(int x, int y) { Cell p = Cell.make(x, y); A node = nodes.get(p); if(node == null) { //System.out.println(" create(" + x + "," + y + ")"); node = getOrCreateNode(p.toString(), A.class); node.setZIndex(x + (y-1)*100); nodes.put(p, node); } else { //System.out.println(" get(" + x + "," + y + ")"); } return node; } public MonitorTextGridResult getCache(int x, int y) { return get(x,y).getCache(); } public void setCache(int x, int y, MonitorTextGridResult cache) { get(x,y).setCache(cache); } public void setTransform(int x, int y, AffineTransform transform) { get(x,y).setTransform(transform); dragBegin = null; currentDrag = null; } public void setHorizontalAlignment(int x, int y, byte horizontalAlignment) { get(x,y).setHorizontalAlignment(horizontalAlignment); } public void setVerticalAlignment(int x, int y, byte verticalAlignment) { get(x,y).setVerticalAlignment(verticalAlignment); } public void setForceEventListening(int x, int y, boolean force) { get(x,y).setForceEventListening(force); } public void setEditable(int x, int y, boolean editable) { get(x,y).setEditable(editable); } public void setTextListener(int x, int y, ITextListener listener) { get(x,y).setTextListener(listener); } public void setInputValidator(int x, int y, Function1 validator) { get(x,y).setValidator(validator); } public void setTranslator(Function1 translator) { this.translator = translator; } public void setContentFilter(int x, int y, ITextContentFilter filter) { get(x,y).setContentFilter(filter); } public void setRVI(int x, int y, RVI rvi) { get(x,y).setRVI(rvi); } public void setBackgroundColor(int x, int y, Color color) { get(x,y).setBackgroundColor(color); } @SyncField("font") public void setFont(int x, int y, Font font) { get(x,y).setFont(font); } @SyncField("text") public void setText(int x, int y, String text) { get(x,y).setText(text); } @SyncField("text") public void setPending(int x, int y, boolean pending) { get(x,y).setPending(pending); } @SyncField("color") public void setColor(int x, int y, Color color) { get(x,y).setColor(color); } public void removeRow(int y) { List row = peekRowCells(y); if (row.isEmpty()) return; //System.out.println("removeRow(" + y + "): removing " + row.size() + " cells"); for (Cell cell : row) { nodes.remove(cell); removeNode(cell.toString()); } } public void setRowId(int y, String id) { rowIds.put(y, id); } public String getRowId(int y) { return rowIds.get(y); } @Override public void render(Graphics2D g2d) { Vec2d delta = getDelta(FACTOR); if(delta != null) setTransform(AffineTransform.getTranslateInstance(delta.x, delta.y-2.1*computeRows()*(up ? 1.0 : 0.0))); else setTransform(AffineTransform.getTranslateInstance(0, -2.1*computeRows()*(up ? 1.0 : 0.0))); super.render(g2d); } @Override public Rectangle2D getBoundsInLocal(boolean b) { return null; } @Override public Rectangle2D getBoundsInLocal() { return null; } @Override public Rectangle2D getBounds() { return null; } @Override public int getEventMask() { return EventTypes.MouseDragBeginMask | EventTypes.KeyPressedMask; } private static boolean isEventDummy(MouseDragBegin e) { if (e.controlPosition.distance(0, 0) == 0 && e.screenPosition.distance(0, 0) == 0 && e.buttons == 0) { return true; } else { return false; } } private boolean dragging = false; private Point2D dragBegin = null; private Point2D currentDrag = null; private Function1 translator = null; private static double FACTOR = 1.0; private static double FACTOR2 = 7.0; private Vec2d getDelta(double factor) { if(dragBegin != null && currentDrag != null) { double dx = factor * (currentDrag.getX() - dragBegin.getX()); double dy = factor * (currentDrag.getY() - dragBegin.getY()); return new Vec2d(dx, dy); } else { return null; } } @Override protected boolean keyPressed(KeyPressedEvent e) { if (dragging && e.keyCode == java.awt.event.KeyEvent.VK_ESCAPE) { dragBegin = null; currentDrag = null; dragging = false; repaint(); return true; } return false; } protected boolean mouseMoved(MouseMovedEvent event) { if (dragging) { currentDrag = NodeUtil.worldToLocal(this, event.controlPosition, new Point2D.Double()); repaint(); } return false; } protected boolean hitTest(MouseEvent event, double tolerance) { Rectangle2D bounds = super.getBoundsInLocal(false); if(bounds == null) return false; Point2D localPos = NodeUtil.worldToLocal(this, event.controlPosition, new Point2D.Double()); double x = localPos.getX(); double y = localPos.getY()+2.1*computeRows()*(up ? 1.0 : 0.0); boolean hit = bounds.contains(x, y); return hit; } @Override protected boolean mouseDragged(MouseDragBegin e) { // Get rid of dummy events from dragGestureRecognized if (isEventDummy(e)) { return false; } G2DSceneGraph sg = NodeUtil.getRootNode(this); Boolean b = sg.getGlobalProperty(G2DSceneGraph.IGNORE_FOCUS, false); if(!b) return false; if(!e.hasAnyButton(MouseEvent.LEFT_MASK) || e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) || !hitTest(e, 0.0)) return false; dragBegin = NodeUtil.worldToLocal(this, e.controlPosition, new Point2D.Double()); dragging = true; return true; } protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) { if(!dragging) return false; Vec2d delta = getDelta(FACTOR2); if(delta != null && translator != null) { translator.apply(delta); } else { dragBegin = null; currentDrag = null; } dragging = false; return false; } @Override public void init() { super.init(); addEventHandler(this); } @Override public void cleanup() { removeEventHandler(this); super.cleanup(); } }