+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