]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/elements/EditorState.java
Editing of texts inside SVG elements
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / elements / EditorState.java
diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/EditorState.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/EditorState.java
new file mode 100644 (file)
index 0000000..0c13750
--- /dev/null
@@ -0,0 +1,358 @@
+package org.simantics.diagram.elements;
+
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+
+import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
+
+import com.kitfox.svg.Group;
+import com.kitfox.svg.Line;
+import com.kitfox.svg.Rect;
+import com.kitfox.svg.SVGDiagram;
+import com.kitfox.svg.SVGException;
+import com.kitfox.svg.Text;
+import com.kitfox.svg.Tspan;
+import com.kitfox.svg.animation.AnimationElement;
+
+/**
+ * @author Antti Villberg
+ * @since 1.31.0
+ */
+class EditorState {
+
+       enum ModificationClass {
+               SINGLE_INSERT, AREA_INSERT,SINGLE_DELETE,AREA_DELETE,NO_EDIT
+       }
+
+       EditorStateStatic base;
+       ModificationClass modificationClass = ModificationClass.NO_EDIT;
+       int caretPosition = -1;
+       int selectionOtherPosition = -1;
+       String currentText = null;
+
+       private String selectedText() {
+               if(editModeHasSelection()) {
+                       int min = Math.min(caretPosition, selectionOtherPosition);
+                       int max = Math.max(caretPosition, selectionOtherPosition);
+                       return currentText.substring(min, max);
+               }
+               return null;
+       }
+
+       private boolean editModeHasSelection() {
+               return selectionOtherPosition != -1;
+       }
+
+       private void editModeClearSelection() {
+               selectionOtherPosition = -1;
+       }
+
+       private void deleteCurrentSelection() {
+               int min = Math.min(caretPosition, selectionOtherPosition);
+               int max = Math.max(caretPosition, selectionOtherPosition);
+               currentText = currentText.substring(0, min) + currentText.substring(max, currentText.length());
+               caretPosition = min;
+               editModeClearSelection();
+       }
+
+       public void applyEditMode(SVGDiagram diagram) throws SVGException {
+
+               Text text = (Text)diagram.getElement(base.textElementId);
+               Tspan span = (Tspan)text.getContent().get(0);
+
+               // Measure the X-dimensions of the font - append TERM_STRING to account for trailing whitespace
+               span.setText(currentText + EditorStateManager.TERM_STRING);
+               text.rebuild();
+               diagram.updateTime(0);
+               double textWidth = text.getBoundingBox().getWidth() - base.termStringWidth;
+
+               // Measure the caret position
+               span.setText(currentText.substring(0, caretPosition) + EditorStateManager.TERM_STRING);
+               text.rebuild();
+               diagram.updateTime(0);
+               double caretRectWidth = text.getBoundingBox().getWidth() - base.termStringWidth;
+
+               double selectionOtherWidth = 0;
+               if(selectionOtherPosition != -1) {
+                       span.setText(currentText.substring(0, selectionOtherPosition) +  EditorStateManager.TERM_STRING);
+                       text.rebuild();
+                       diagram.updateTime(0);
+                       selectionOtherWidth = text.getBoundingBox().getWidth() - base.termStringWidth;
+               }
+
+
+               // Finally the actual text
+               span.setText(currentText);
+               text.rebuild();
+               diagram.updateTime(0);
+               Rectangle2D finalBB = text.getBoundingBox();
+
+               Group group = (Group)text.getParent();
+               Line line = new Line();
+               try { 
+
+                       group.removeChild(text);
+
+                       double xPadding = 0;
+
+                       double minY = (base.verticalDimensions.getMinY()-1);
+                       double height = (base.verticalDimensions.getHeight()+2);
+
+                       Rect rect = new Rect();
+                       rect.addAttribute("x", AnimationElement.AT_XML, "" + (finalBB.getMinX()-xPadding));
+                       rect.addAttribute("y", AnimationElement.AT_XML, "" + minY);
+                       rect.addAttribute("width", AnimationElement.AT_XML, "" + (textWidth+xPadding));
+                       rect.addAttribute("height", AnimationElement.AT_XML, "" + height);
+                       rect.addAttribute("fill", AnimationElement.AT_XML, "#ccc");
+                       group.loaderAddChild(null, rect);
+
+                       double caretX = finalBB.getMinX() + caretRectWidth;
+
+                       if(selectionOtherPosition != -1) {
+                               double selectionX = finalBB.getMinX() + selectionOtherWidth;
+                               Rect selection = new Rect();
+                               if(selectionOtherPosition < caretPosition) {
+                                       selection.addAttribute("x", AnimationElement.AT_XML, "" + selectionX);
+                                       selection.addAttribute("y", AnimationElement.AT_XML, "" + minY);
+                                       selection.addAttribute("width", AnimationElement.AT_XML, "" + (caretX-selectionX));
+                                       selection.addAttribute("height", AnimationElement.AT_XML, "" + height);
+                                       selection.addAttribute("fill", AnimationElement.AT_XML, "#888");
+                               } else {
+                                       selection.addAttribute("x", AnimationElement.AT_XML, "" + caretX);
+                                       selection.addAttribute("y", AnimationElement.AT_XML, "" + minY);
+                                       selection.addAttribute("width", AnimationElement.AT_XML, "" + (selectionX-caretX));
+                                       selection.addAttribute("height", AnimationElement.AT_XML, "" + height);
+                                       selection.addAttribute("fill", AnimationElement.AT_XML, "#888");
+                               }
+                               group.loaderAddChild(null, selection);
+                       }
+
+                       line.addAttribute("x1", AnimationElement.AT_XML, "" + caretX);
+                       line.addAttribute("x2", AnimationElement.AT_XML, "" + caretX);
+                       line.addAttribute("y1", AnimationElement.AT_XML, "" + (base.verticalDimensions.getMinY()-1));
+                       line.addAttribute("y2", AnimationElement.AT_XML, "" + (base.verticalDimensions.getMaxY()+1));
+                       line.addAttribute("stroke", AnimationElement.AT_XML, "black");
+                       line.addAttribute("stroke-width", AnimationElement.AT_XML, "0.5");
+                       group.loaderAddChild(null, line);
+
+                       group.loaderAddChild(null, text);
+
+               } finally {
+
+               }
+
+               diagram.updateTime(0);
+
+       }
+
+       boolean keyPressed(EditorStateManager esm, KeyPressedEvent e) {
+               boolean result = keyPressedInternal(esm, e);
+               if(selectionOtherPosition == caretPosition)
+                       editModeClearSelection();
+               return result;
+       }
+
+       private void performDelete() {
+               if(editModeHasSelection()) {
+                       deleteCurrentSelection();
+                       modificationClass = ModificationClass.AREA_DELETE;
+               } else {
+                       if(caretPosition < currentText.length()) {
+                               currentText = currentText.substring(0, caretPosition) + currentText.substring(caretPosition+1, currentText.length());
+                       }
+                       modificationClass = ModificationClass.SINGLE_DELETE;
+               }
+       }
+
+       private void performCopy() {
+               String selection = selectedText();
+               if(selection == null) return;
+               Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+               clipboard.setContents(new StringSelection(selection), null);
+       }
+
+       boolean keyPressedInternal(EditorStateManager esm, KeyPressedEvent e) {
+
+               if(e.keyCode == java.awt.event.KeyEvent.VK_BACK_SPACE) {
+                       if(editModeHasSelection()) {
+                               deleteCurrentSelection();
+                               modificationClass = ModificationClass.AREA_DELETE;
+                       } else {
+                               if(caretPosition > 0) {
+                                       currentText = currentText.substring(0, caretPosition-1) + currentText.substring(caretPosition, currentText.length());
+                                       caretPosition--;
+                               }
+                               modificationClass = ModificationClass.SINGLE_DELETE;
+                       }
+               } else if (java.awt.event.KeyEvent.VK_DELETE == e.keyCode) {
+                       performDelete();
+               } else if (java.awt.event.KeyEvent.VK_C == e.keyCode && e.isControlDown()) {
+                       performCopy();
+                       return false;
+               } else if (java.awt.event.KeyEvent.VK_X == e.keyCode && e.isControlDown()) {
+                       performCopy();
+                       performDelete();
+               } else if (java.awt.event.KeyEvent.VK_V == e.keyCode && e.isControlDown()) {
+                       Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+                       DataFlavor dataFlavor = DataFlavor.stringFlavor;
+                       if (clipboard.isDataFlavorAvailable(dataFlavor)) {
+                               try {
+                                       String text = clipboard.getData(dataFlavor).toString();
+                                       if(editModeHasSelection())
+                                               deleteCurrentSelection();
+
+                                       currentText = currentText.substring(0, caretPosition) + text + currentText.substring(caretPosition, currentText.length());
+                                       caretPosition += text.length();
+                                       modificationClass = ModificationClass.AREA_INSERT;
+                               } catch (UnsupportedFlavorException | IOException e1) {
+                               }
+                       } else {
+                               return false;
+                       }
+               } else if (java.awt.event.KeyEvent.VK_A == e.keyCode && e.isControlDown()) {
+                       caretPosition = 0;
+                       selectionOtherPosition = currentText.length();
+               } else if (java.awt.event.KeyEvent.VK_Z == e.keyCode && e.isControlDown()) {
+                       esm.undo();
+                       return false;
+               } else if (java.awt.event.KeyEvent.VK_Y == e.keyCode && e.isControlDown()) {
+                       esm.redo();
+                       return false;
+               } else if (java.awt.event.KeyEvent.VK_ESCAPE == e.keyCode) {
+                       esm.deactivateEdit();
+               } else if (java.awt.event.KeyEvent.VK_LEFT == e.keyCode) {
+                       if(!e.isShiftDown() && editModeHasSelection()) {
+                               if(selectionOtherPosition < caretPosition) {
+                                       caretPosition = selectionOtherPosition;
+                               }
+                               editModeClearSelection();
+                       } else {
+                               if(e.isShiftDown() && !editModeHasSelection()) {
+                                       selectionOtherPosition = caretPosition;
+                               }
+                               if(caretPosition > 0) {
+                                       caretPosition--;
+                               }
+                       }
+               } else if (java.awt.event.KeyEvent.VK_RIGHT == e.keyCode) {
+                       if(!e.isShiftDown() && editModeHasSelection()) {
+                               if(selectionOtherPosition > caretPosition) {
+                                       caretPosition = selectionOtherPosition;
+                               }
+                               editModeClearSelection();
+                       } else {
+                               if(e.isShiftDown() && !editModeHasSelection()) {
+                                       selectionOtherPosition = caretPosition;
+                               }
+                               if(caretPosition < currentText.length()) {
+                                       caretPosition++;
+                               }
+                       }
+               } else if (java.awt.event.KeyEvent.VK_END == e.keyCode) {
+                       if(e.isShiftDown()) {
+                               if(!editModeHasSelection()) {
+                                       selectionOtherPosition = caretPosition;
+                               }
+                       } else {
+                               editModeClearSelection();
+                       }
+                       caretPosition = currentText.length();
+               } else if (java.awt.event.KeyEvent.VK_HOME == e.keyCode) {
+                       if(e.isShiftDown()) {
+                               if(!editModeHasSelection()) {
+                                       selectionOtherPosition = caretPosition;
+                               }
+                       } else {
+                               editModeClearSelection();
+                       }
+                       caretPosition = 0;
+               } else if (java.awt.event.KeyEvent.VK_ENTER == e.keyCode) {
+                       esm.applyEdit();
+                       esm.deactivateEdit();
+               } else if(isAllowedCharacter(e)) {
+                       if(editModeHasSelection())
+                               deleteCurrentSelection();
+                       currentText = currentText.substring(0, caretPosition) + e.character + currentText.substring(caretPosition, currentText.length());
+                       caretPosition++;
+                       modificationClass = ModificationClass.SINGLE_INSERT;
+               } else {
+                       return false;
+               }
+
+               esm.paint();
+
+               return true;
+
+       }
+
+       void replace(EditorState other) {
+               base = other.base;
+               caretPosition = other.caretPosition;
+               currentText = other.currentText;
+               selectionOtherPosition = other.selectionOtherPosition;
+       }
+
+       boolean shouldReplace(EditorState comparedTo) {
+               return modificationClass.equals(comparedTo.modificationClass);
+       }
+
+       EditorState copy() {
+               EditorState result = new EditorState();
+               result.replace(this);
+               return result;
+       }
+
+       private boolean isAllowedCharacter(KeyPressedEvent e) {
+               char c = e.character;
+               if (c == 65535 || Character.getType(c) == Character.CONTROL) {
+                       return false;
+               }
+               return true;
+       }
+
+       @Override
+       public int hashCode() {
+               final int prime = 31;
+               int result = 1;
+               result = prime * result + ((base == null) ? 0 : base.hashCode());
+               result = prime * result + caretPosition;
+               result = prime * result + ((currentText == null) ? 0 : currentText.hashCode());
+               result = prime * result + ((modificationClass == null) ? 0 : modificationClass.hashCode());
+               result = prime * result + selectionOtherPosition;
+               return result;
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (this == obj)
+                       return true;
+               if (obj == null)
+                       return false;
+               if (getClass() != obj.getClass())
+                       return false;
+               EditorState other = (EditorState) obj;
+               if (base == null) {
+                       if (other.base != null)
+                               return false;
+               } else if (!base.equals(other.base))
+                       return false;
+               if (caretPosition != other.caretPosition)
+                       return false;
+               if (currentText == null) {
+                       if (other.currentText != null)
+                               return false;
+               } else if (!currentText.equals(other.currentText))
+                       return false;
+               if (modificationClass != other.modificationClass)
+                       return false;
+               if (selectionOtherPosition != other.selectionOtherPosition)
+                       return false;
+               return true;
+       }
+
+}
\ No newline at end of file