]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/elements/TextNode.java
Sync git svn branch with SVN repository r33269.
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / elements / TextNode.java
index 6fb0611f1efb34a99474bda00fcfd0f15bee3941..96cd573cc626860a43d51775d7cb20106ca98a3b 100644 (file)
@@ -11,8 +11,6 @@
  *******************************************************************************/\r
 package org.simantics.diagram.elements;\r
 \r
-import gnu.trove.list.array.TIntArrayList;\r
-\r
 import java.awt.AlphaComposite;\r
 import java.awt.BasicStroke;\r
 import java.awt.Color;\r
@@ -42,6 +40,7 @@ import java.text.AttributedString;
 import java.util.ArrayList;\r
 import java.util.Arrays;\r
 import java.util.Hashtable;\r
+import java.util.List;\r
 \r
 import org.simantics.datatypes.literal.RGB;\r
 import org.simantics.db.layer0.variable.RVI;\r
@@ -62,15 +61,17 @@ import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDoubleClickedEvent;\r
 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;\r
 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
+import org.simantics.scenegraph.g2d.events.NodeEventHandler;\r
 import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
 import org.simantics.scenegraph.g2d.events.command.Commands;\r
-import org.simantics.scenegraph.g2d.events.NodeEventHandler;\r
 import org.simantics.scenegraph.utils.GeometryUtils;\r
 import org.simantics.scenegraph.utils.NodeUtil;\r
 import org.simantics.scl.runtime.function.Function1;\r
 import org.simantics.scl.runtime.function.Function2;\r
 import org.simantics.ui.colors.Colors;\r
 import org.simantics.ui.dnd.LocalObjectTransferable;\r
+import org.simantics.ui.dnd.MultiTransferable;\r
+import org.simantics.ui.dnd.PlaintextTransfer;\r
 import org.simantics.ui.fonts.Fonts;\r
 import org.simantics.utils.threads.AWTThread;\r
 \r
@@ -82,6 +83,8 @@ import com.lowagie.text.pdf.PdfFormField;
 import com.lowagie.text.pdf.PdfWriter;\r
 import com.lowagie.text.pdf.TextField;\r
 \r
+import gnu.trove.list.array.TIntArrayList;\r
+\r
 \r
 /**\r
  * TextNode which supports in-line editing.\r
@@ -129,11 +132,6 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
      */\r
     protected String text = null;\r
 \r
-    /**\r
-     * Tells if this node is still pending for real results or not.\r
-     */\r
-    protected boolean pending = false;\r
-\r
     /**\r
      * The font used to render the {@link #text}.\r
      */\r
@@ -180,12 +178,25 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
      */\r
     protected byte verticalAlignment = 3;\r
 \r
-    protected boolean hover = false;\r
-    boolean editable = false;\r
-    boolean showSelection = true;\r
-    \r
-    \r
-    boolean wrapText = true;\r
+    /**\r
+     * Tells if this node is still pending for real results or not.\r
+     */\r
+    protected static final int STATE_PENDING = (1 << 0);\r
+    protected static final int STATE_HOVER   = (1 << 1);\r
+    protected static final int STATE_EDITABLE = (1 << 2);\r
+    protected static final int STATE_SHOW_SELECTION = (1 << 3);\r
+    protected static final int STATE_WRAP_TEXT = (1 << 4);\r
+    protected transient static final int STATE_EDITING = (1 << 5);\r
+    protected transient static final int STATE_VALID = (1 << 6);\r
+    protected transient static final int STATE_X_OFFSET_IS_DIRTY = (1 << 7);\r
+    protected static final int STATE_ALWAYS_ADD_LISTENERS = (1 << 8);\r
+    protected static final int STATE_LISTENERS_ADDED = (1 << 9);\r
+\r
+    /**\r
+     * A combination of all the STATE_ constants defined in this class,\r
+     * e.g. {@link #STATE_PENDING}.\r
+     */\r
+    protected int state = STATE_SHOW_SELECTION | STATE_WRAP_TEXT | STATE_VALID | STATE_X_OFFSET_IS_DIRTY;\r
 \r
     protected RVI dataRVI = null;\r
 \r
@@ -201,11 +212,6 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
     ITextListener textListener;\r
     ITextContentFilter editContentFilter;\r
 \r
-    transient boolean editing = false;\r
-    transient boolean valid = true;\r
-\r
-    private transient boolean xOffsetIsDirty = true;\r
-\r
     /**\r
      * The renderable line structures parsed from {@link #text} by\r
      * {@link #parseLines(String)}, laid out by\r
@@ -248,12 +254,55 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
         super.cleanup();\r
     }\r
 \r
+    protected boolean hasState(int flags) {\r
+        return (state & flags) == flags;\r
+    }\r
+\r
+    protected void setState(int flags) {\r
+        this.state |= flags;\r
+    }\r
+\r
+    protected void setState(int flags, boolean set) {\r
+        if (set)\r
+            this.state |= flags;\r
+        else\r
+            this.state &= ~flags;\r
+    }\r
+\r
+    protected void clearState(int flags) {\r
+        this.state &= ~flags;\r
+    }\r
+\r
+    protected void setListeners(boolean add) {\r
+        if (add)\r
+            addListeners();\r
+        else\r
+            removeListeners();\r
+    }\r
+\r
     protected void addListeners() {\r
-        addEventHandler(this);\r
+        if (!hasState(STATE_LISTENERS_ADDED)) {\r
+            addEventHandler(this);\r
+            setState(STATE_LISTENERS_ADDED);\r
+        }\r
     }\r
 \r
     protected void removeListeners() {\r
-        removeEventHandler(this);\r
+        if (hasState(STATE_LISTENERS_ADDED)) {\r
+            removeEventHandler(this);\r
+            clearState(STATE_LISTENERS_ADDED);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Set to true to always enable event listening in this TextNode to allow the text node to keep track of hovering, etc. and to allow DnD even when \r
+     * @param force\r
+     */\r
+    public void setForceEventListening(boolean force) {\r
+        setState(STATE_ALWAYS_ADD_LISTENERS, force);\r
+        if (force && !hasState(STATE_EDITABLE)) {\r
+            setListeners(force);\r
+        }\r
     }\r
 \r
     /**\r
@@ -277,11 +326,11 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
      * @return null if no change to edit state was made\r
      */\r
     protected Boolean setEditMode(boolean edit, boolean notify) {\r
-        if (edit && !editable)\r
+        if (edit && !hasState(STATE_EDITABLE))\r
             return null;\r
-        if (editing == edit)\r
+        if (hasState(STATE_EDITING) == edit)\r
             return null;\r
-        this.editing = edit;\r
+        setState(STATE_EDITING, edit);\r
         if (edit) {\r
             caret = text != null ? text.length() : 0;\r
             selectionTail = 0;\r
@@ -298,29 +347,26 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
 \r
     @SyncField({"editable"})\r
     public void setEditable(boolean editable) {\r
-        boolean changed = this.editable != editable;\r
-        this.editable = editable;\r
-        if (editing && !editable)\r
+        boolean changed = hasState(STATE_EDITABLE) != editable;\r
+        setState(STATE_EDITABLE, editable);\r
+        if (hasState(STATE_EDITING) && !editable)\r
             setEditMode(false);\r
-        if (changed) {\r
-            if (editable)\r
-                addListeners();\r
-            else\r
-                removeListeners();\r
+        if (changed && !hasState(STATE_ALWAYS_ADD_LISTENERS)) {\r
+            setListeners(editable);\r
         }\r
     }\r
 \r
     public boolean isEditable() {\r
-        return editable;\r
+        return hasState(STATE_EDITABLE);\r
     }\r
 \r
     public boolean isEditMode() {\r
-        return editing;\r
+        return hasState(STATE_EDITING);\r
     }\r
     \r
     @SyncField({"wrapText"})\r
     public void setWrapText(boolean wrapText) {\r
-        this.wrapText = wrapText;\r
+        setState(STATE_WRAP_TEXT, wrapText);\r
     }\r
     \r
     /**\r
@@ -328,16 +374,16 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
      * the width of the box is fixed\r
      */\r
     public boolean isWrapText() {\r
-        return this.wrapText;\r
+        return hasState(STATE_WRAP_TEXT);\r
     }\r
 \r
     @SyncField({"showSelection"})\r
     public void setShowSelection(boolean showSelection) {\r
-        this.showSelection = showSelection;\r
+        setState(STATE_SHOW_SELECTION, showSelection);\r
     }\r
 \r
     public boolean showsSelection() {\r
-        return showSelection;\r
+        return hasState(STATE_SHOW_SELECTION);\r
     }\r
 \r
     /**\r
@@ -353,7 +399,7 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
         // no value => value\r
         if(this.text == null && text != null) NodeUtil.decreasePending(this);\r
 \r
-        if (editing)\r
+        if (hasState(STATE_EDITING))\r
             return;\r
 \r
         this.text = new String(text != null ? text : "");\r
@@ -399,7 +445,7 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
     @SyncField({"text","caret","selectionTail"})\r
     public void setText(String text) {\r
         //System.out.println("TextNode.setText('" + text + "', " + editing + ")");\r
-        if (editing)\r
+        if (hasState(STATE_EDITING))\r
             return;\r
 \r
         // value => no value\r
@@ -416,9 +462,11 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
 \r
     @SyncField({"pending"})\r
     public void setPending(boolean pending) {\r
-        if(!this.pending && pending) NodeUtil.increasePending(this);\r
-        if(this.pending && !pending) NodeUtil.decreasePending(this);\r
-        this.pending = pending;\r
+        boolean p = hasState(STATE_PENDING);\r
+        if(!p && pending) NodeUtil.increasePending(this);\r
+        if(p && !pending) NodeUtil.decreasePending(this);\r
+        if(p != pending)\r
+            setState(STATE_PENDING, pending);\r
     }\r
 \r
     @SyncField({"fixedWidth"})\r
@@ -448,16 +496,16 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
        }\r
 \r
     public final void synchronizeWrapText(boolean wrap) {\r
-        wrapText = wrap;\r
+        setState(STATE_WRAP_TEXT, wrap);\r
     }\r
 \r
     public boolean isHovering() {\r
-        return hover;\r
+        return hasState(STATE_HOVER);\r
     }\r
 \r
     @SyncField({"hover"})\r
     public void setHover(boolean hover) {\r
-        this.hover = hover;\r
+        setState(STATE_HOVER, hover);\r
         repaint();\r
     }\r
 \r
@@ -564,6 +612,8 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
 \r
         Color color = this.color;\r
         boolean isSelected = NodeUtil.isSelected(this, 1);\r
+        boolean hover = hasState(STATE_HOVER);\r
+        boolean editing = hasState(STATE_EDITING);\r
 \r
         if (!isSelected && hover) {\r
             color = add(color, 120, 120, 120);\r
@@ -634,7 +684,7 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
                                                fieldName.equals("created_by") );\r
         }\r
 \r
-        Color backgroundColor = valid ? this.backgroundColor : Color.red;\r
+        Color backgroundColor = hasState(STATE_VALID) ? this.backgroundColor : Color.red;\r
 \r
         // RENDER\r
         if ( !isPdfField ) {\r
@@ -887,7 +937,7 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
 \r
         if(validator != null) {\r
             String error = validator.apply(text);\r
-            valid = (error == null);\r
+            setState(STATE_VALID, (error == null));\r
         }\r
 \r
         resetCaches();\r
@@ -908,7 +958,7 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
 \r
     @ServerSide\r
     protected void fireTextEditingCancelled() {\r
-        valid = true;\r
+        setState(STATE_VALID);\r
 \r
         if (deactivateEdit()) {\r
             if (textListener != null)\r
@@ -925,9 +975,9 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
 \r
     @ServerSide\r
     public void fireTextEditingEnded() {\r
-        if (!valid) {\r
+        if (!hasState(STATE_VALID)) {\r
             fireTextEditingCancelled();\r
-            valid = true;\r
+            setState(STATE_VALID);\r
             return;\r
         }\r
 \r
@@ -957,13 +1007,13 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
     }\r
 \r
     private void invalidateXOffset() {\r
-        xOffsetIsDirty = true;\r
+        setState(STATE_X_OFFSET_IS_DIRTY);\r
     }\r
 \r
     private void computeEditingXOffset() {\r
 \r
         if(lines == null) return;\r
-        if(!xOffsetIsDirty) return;\r
+        if(!hasState(STATE_X_OFFSET_IS_DIRTY)) return;\r
         if(fixedWidth > 0f) {\r
 \r
             // TODO: implement\r
@@ -979,7 +1029,7 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
 \r
         }\r
 \r
-        xOffsetIsDirty = false;\r
+        clearState(STATE_X_OFFSET_IS_DIRTY);\r
 \r
     }\r
 \r
@@ -1259,7 +1309,7 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
         // Parse & layout (unaligned)\r
         Line[] lines = null;\r
         \r
-        if(wrapText) {\r
+        if(hasState(STATE_WRAP_TEXT)) {\r
             float width = fixedWidth;\r
             if(width <= 0 && targetBounds != null)\r
                 width = (float) (((targetBounds.getWidth() - 2*paddingX)) * scaleRecip);\r
@@ -1545,7 +1595,7 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
 \r
     @Override\r
     protected boolean handleCommand(CommandEvent e) {\r
-        if (!editing)\r
+        if (!hasState(STATE_EDITING))\r
             return false;\r
 \r
         if (Commands.SELECT_ALL.equals(e.command)) {\r
@@ -1557,7 +1607,7 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
 \r
     @Override\r
     protected boolean keyPressed(KeyPressedEvent event) {\r
-        if (!editing)\r
+        if (!hasState(STATE_EDITING))\r
             return false;\r
 \r
         char c = event.character;\r
@@ -1658,7 +1708,7 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
                 case KeyEvent.VK_ESCAPE:\r
                     text = textBeforeEdit;\r
                     resetCaches();\r
-                    editing = false;\r
+                    clearState(STATE_EDITING);\r
                     fireTextEditingCancelled();\r
                     return true;\r
 \r
@@ -1725,7 +1775,7 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
         if (event.button != MouseClickEvent.LEFT_BUTTON)\r
             return false;\r
         \r
-        if (hover) {\r
+        if (hasState(STATE_HOVER)) {\r
                hoverClick++;\r
                if (hoverClick < 2)\r
                        return false;\r
@@ -1734,7 +1784,7 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
             if (ctx == null)\r
                 return false;\r
             IElement e = DiagramNodeUtil.getElement(ctx, this);\r
-            if (!editing) {\r
+            if (!hasState(STATE_EDITING)) {\r
                if (Boolean.TRUE.equals(setEditMode(true))) {\r
                        editActivation = activateEdit(0, e, ctx);\r
                        repaint();\r
@@ -1742,7 +1792,7 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
             } \r
         } else {\r
                hoverClick = 0;\r
-            if (editing) {\r
+            if (hasState(STATE_EDITING)) {\r
                 fireTextEditingEnded();\r
             }\r
         }\r
@@ -1772,13 +1822,13 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
 \r
     @Override\r
     protected boolean mouseButtonPressed(MouseButtonPressedEvent event) {\r
-        if (!editing)\r
+        if (!hasState(STATE_EDITING))\r
             return false;\r
         \r
         Point2D local = controlToLocal( event.controlPosition );\r
         // FIXME: once the event coordinate systems are cleared up, remove this workaround\r
         local = parentToLocal(local);\r
-        if (hover && this.containsLocal(local)) {\r
+        if (hasState(STATE_HOVER) && this.containsLocal(local)) {\r
             setCaret(local, event.isShiftDown());\r
         }\r
         return false;\r
@@ -1787,8 +1837,8 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
     @Override\r
     protected boolean mouseMoved(MouseMovedEvent event) {\r
         boolean hit = hitTest(event, 3.0);\r
-        if (hit != hover) {\r
-            hover = hit;\r
+        if (hit != hasState(STATE_HOVER)) {\r
+            setState(STATE_HOVER, hit);\r
             repaint();\r
         }\r
         return false;\r
@@ -1811,9 +1861,19 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
         if (isHovering()\r
                 && (isControlDown(e) || isShiftDown(e))\r
                 && e.context instanceof NodeEventHandler\r
-                && dataRVI != null)\r
+                && (dataRVI != null || text != null))\r
         {\r
-            e.transferable = new LocalObjectTransferable(dataRVI);\r
+            List<Transferable> trs = new ArrayList<>(2);\r
+            if (dataRVI != null) {\r
+                trs.add(new LocalObjectTransferable(dataRVI));\r
+                trs.add(new PlaintextTransfer(dataRVI.toString()));\r
+            } else if (text != null && !text.isEmpty()) {\r
+                trs.add(new PlaintextTransfer(text));\r
+            }\r
+            if (!trs.isEmpty()) {\r
+                e.transferable = new MultiTransferable(trs);\r
+                return true;\r
+            }\r
         }\r
         return false;\r
     }\r
@@ -1883,7 +1943,7 @@ public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, L
     @Override\r
     public int getEventMask() {\r
         return EventTypes.KeyPressedMask | EventTypes.MouseMovedMask | EventTypes.MouseButtonPressedMask\r
-                | EventTypes.MouseClickMask | EventTypes.CommandMask;\r
+                | EventTypes.MouseClickMask | EventTypes.MouseDragBeginMask | EventTypes.CommandMask;\r
     }\r
 \r
     private MouseEvent lastMouseEvent = null;\r