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; } }