*******************************************************************************/\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
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
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
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
*/\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
*/\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
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
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
* @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
\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
* 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
// 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
@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
\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
}\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
\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
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
\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
\r
@ServerSide\r
protected void fireTextEditingCancelled() {\r
- valid = true;\r
+ setState(STATE_VALID);\r
\r
if (deactivateEdit()) {\r
if (textListener != null)\r
\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
}\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
\r
}\r
\r
- xOffsetIsDirty = false;\r
+ clearState(STATE_X_OFFSET_IS_DIRTY);\r
\r
}\r
\r
// 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
\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
\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
case KeyEvent.VK_ESCAPE:\r
text = textBeforeEdit;\r
resetCaches();\r
- editing = false;\r
+ clearState(STATE_EDITING);\r
fireTextEditingCancelled();\r
return true;\r
\r
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
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
} \r
} else {\r
hoverClick = 0;\r
- if (editing) {\r
+ if (hasState(STATE_EDITING)) {\r
fireTextEditingEnded();\r
}\r
}\r
\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
@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
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
@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