/*
* JaamSim Discrete Event Simulation
* Copyright (C) 2015 Ausenco Engineering Canada Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jaamsim.Graphics;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.util.ArrayList;
import com.jaamsim.DisplayModels.TextModel;
import com.jaamsim.controllers.RenderManager;
import com.jaamsim.input.BooleanInput;
import com.jaamsim.input.ColourInput;
import com.jaamsim.input.InputAgent;
import com.jaamsim.input.Keyword;
import com.jaamsim.input.StringChoiceInput;
import com.jaamsim.input.StringListInput;
import com.jaamsim.input.ValueInput;
import com.jaamsim.input.Vec3dInput;
import com.jaamsim.math.Transform;
import com.jaamsim.math.Vec3d;
import com.jaamsim.units.DistanceUnit;
import com.jogamp.newt.event.KeyEvent;
/**
* The "TextBasics" object displays text within the 3D model universe.
* @author Harry King
*
*/
public abstract class TextBasics extends DisplayEntity {
@Keyword(description = "The height of the font as displayed in the view window.",
exampleList = {"15 m"})
protected final ValueInput textHeight;
@Keyword(description = "The font to be used for the text.",
exampleList = { "Arial" })
private final StringChoiceInput fontName;
@Keyword(description = "The font styles to be applied to the text, e.g. Bold, Italic. ",
exampleList = { "Bold" })
private final StringListInput fontStyle;
@Keyword(description = "The colour of the text, specified by a colour keyword or RGB values.",
exampleList = { "red", "skyblue", "135 206 235" })
private final ColourInput fontColor;
@Keyword(description = "If TRUE, then a drop shadow appears for the text.",
exampleList = { "TRUE" })
private final BooleanInput dropShadow;
@Keyword(description = "The colour for the drop shadow, specified by a colour keyword or "
+ "RGB values.",
exampleList = { "red", "skyblue", "135 206 235" })
private final ColourInput dropShadowColor;
@Keyword(description = "The { x, y, z } coordinates of the drop shadow's offset, expressed "
+ "as a decimal fraction of the text height.",
exampleList = { "0.1 -0.1 0.001" })
private final Vec3dInput dropShadowOffset;
private boolean editMode = false; // true if the entity is being edited
private String savedText = ""; // saved text after editing is finished
private String editText = ""; // modified text as edited by the user
private int insertPos = 0; // position in the string where new text will be inserted
private int numSelected = 0; // number of characters selected (positive to the right of the insertion position)
{
textHeight = new ValueInput("TextHeight", "Key Inputs", 0.3d);
textHeight.setValidRange(0.0d, Double.POSITIVE_INFINITY);
textHeight.setUnitType(DistanceUnit.class);
this.addInput(textHeight);
fontName = new StringChoiceInput("FontName", "Font", -1);
fontName.setChoices(TextModel.validFontNames);
fontName.setDefaultText("TextModel");
this.addInput(fontName);
fontColor = new ColourInput("FontColour", "Font", ColourInput.BLACK);
fontColor.setDefaultText("TextModel");
this.addInput(fontColor);
this.addSynonym(fontColor, "FontColor");
fontStyle = new StringListInput("FontStyle", "Font", new ArrayList<String>(0));
fontStyle.setValidOptions(TextModel.validStyles);
fontStyle.setCaseSensitive(false);
fontStyle.setDefaultText("TextModel");
this.addInput(fontStyle);
dropShadow = new BooleanInput("DropShadow", "Font", false);
dropShadow.setDefaultText("TextModel");
this.addInput(dropShadow);
dropShadowColor = new ColourInput("DropShadowColour", "Font", ColourInput.BLACK);
dropShadowColor.setDefaultText("TextModel");
this.addInput(dropShadowColor);
this.addSynonym(dropShadowColor, "DropShadowColor");
dropShadowOffset = new Vec3dInput("DropShadowOffset", "Font", null);
dropShadowOffset.setDefaultText("TextModel");
this.addInput(dropShadowOffset);
}
public TextBasics() {}
public void setSavedText(String str) {
savedText = str;
editText = str;
}
public String getEditText() {
return editText;
}
private void deleteSelection() {
if (numSelected == 0)
return;
int start = Math.min(insertPos, insertPos+numSelected);
int end = Math.max(insertPos, insertPos+numSelected);
StringBuilder sb = new StringBuilder(editText);
editText = sb.delete(start, end).toString();
insertPos = start;
numSelected = 0;
}
private void setInsertPosition(int pos, boolean shift) {
if (shift)
numSelected -= pos - insertPos;
else
numSelected = 0;
insertPos = pos;
}
protected void acceptEdits() {
savedText = editText;
editMode = false;
insertPos = editText.length();
numSelected = 0;
}
protected void cancelEdits() {
editMode = false;
editText = savedText;
insertPos = editText.length();
numSelected = 0;
}
private void selectPresentWord() {
// Find the end of the present word
int end = editText.length();
for (int i=insertPos; i<editText.length(); i++) {
if (editText.charAt(i) == ' ') {
end = i + 1;
break;
}
}
// Find the start of the present word
int start = 0;
for (int i=insertPos-1; i>=0; i--) {
if (editText.charAt(i) == ' ') {
start = i + 1;
break;
}
}
// Set the insert position and selection
insertPos = end;
numSelected = start - end;
}
private void copyToClipboard() {
Clipboard clpbrd = Toolkit.getDefaultToolkit().getSystemClipboard();
int start = Math.min(insertPos, insertPos+numSelected);
int end = Math.max(insertPos, insertPos+numSelected);
StringBuilder sb = new StringBuilder(editText);
String copiedText = sb.substring(start, end).toString();
clpbrd.setContents(new StringSelection(copiedText), null);
}
private void pasteFromClipboard() {
Clipboard clpbrd = Toolkit.getDefaultToolkit().getSystemClipboard();
try {
String newText = (String)clpbrd.getData(DataFlavor.stringFlavor);
StringBuilder sb = new StringBuilder(editText);
editText = sb.insert(insertPos, newText).toString();
insertPos += newText.length();
}
catch (Throwable err) {}
}
@Override
public void handleKeyPressed(int keyCode, char keyChar, boolean shift, boolean control, boolean alt) {
if (keyCode == KeyEvent.VK_F2) {
editMode = true;
insertPos = editText.length();
numSelected = 0;
RenderManager.redraw();
return;
}
if (!editMode) {
super.handleKeyPressed(keyCode, keyChar, shift, control, alt);
return;
}
switch (keyCode) {
case KeyEvent.VK_DELETE:
if (numSelected == 0) {
if (insertPos == editText.length())
break;
StringBuilder sb = new StringBuilder(editText);
editText = sb.deleteCharAt(insertPos).toString();
break;
}
deleteSelection();
break;
case KeyEvent.VK_BACK_SPACE:
if (numSelected == 0) {
if (insertPos == 0)
break;
StringBuilder sb = new StringBuilder(editText);
editText = sb.deleteCharAt(insertPos-1).toString();
insertPos--;
break;
}
deleteSelection();
break;
case KeyEvent.VK_LEFT:
if (!shift && !(numSelected == 0)) {
if (numSelected < 0)
setInsertPosition(insertPos + numSelected, shift);
else
setInsertPosition(insertPos, shift);
break;
}
setInsertPosition(Math.max(0, insertPos-1), shift);
break;
case KeyEvent.VK_RIGHT:
if (!shift && !(numSelected == 0)) {
if (numSelected > 0)
setInsertPosition(insertPos + numSelected, shift);
else
setInsertPosition(insertPos, shift);
break;
}
setInsertPosition(Math.min(editText.length(), insertPos+1), shift);
break;
case KeyEvent.VK_HOME:
setInsertPosition(0, shift);
break;
case KeyEvent.VK_END:
setInsertPosition(editText.length(), shift);
break;
case KeyEvent.VK_ENTER:
acceptEdits();
break;
case KeyEvent.VK_ESCAPE:
cancelEdits();
break;
case KeyEvent.VK_C:
if (control) {
copyToClipboard();
break;
}
case KeyEvent.VK_V:
if (control) {
deleteSelection();
pasteFromClipboard();
break;
}
case KeyEvent.VK_X:
if (control) {
copyToClipboard();
deleteSelection();
break;
}
default:
if (keyChar == KeyEvent.VK_UNDEFINED || control)
break;
deleteSelection();
StringBuilder sb = new StringBuilder(editText);
editText = sb.insert(insertPos, keyChar).toString();
insertPos++;
break;
}
RenderManager.redraw();
}
@Override
public void handleKeyReleased(int keyCode, char keyChar, boolean shift, boolean control, boolean alt) {
if (editMode)
return;
super.handleKeyReleased(keyCode, keyChar, shift, control, alt);
}
@Override
public void handleMouseClicked(short count, Vec3d globalCoord) {
if (count > 2)
return;
// Double click starts edit mode
if (count == 2)
editMode = true;
// Set up the transformation from global coordinates to the entity's coordinates
double height = textHeight.getValue();
TextModel tm = (TextModel) displayModelListInput.getValue().get(0);
Vec3d textsize = RenderManager.inst().getRenderedStringSize(tm.getTessFontKey(), height, editText);
Transform trans = getEntityTransForSize(textsize);
// Calculate the entity's coordinates for the mouse click
Vec3d entityCoord = new Vec3d();
trans.multAndTrans(globalCoord, entityCoord);
// Position the insertion point where the text was clicked
double insert = entityCoord.x + 0.5d*textsize.x;
insertPos = RenderManager.inst().getRenderedStringPosition(tm.getTessFontKey(), height, editText, insert);
numSelected = 0;
// Double click selects a whole word
if (count == 2)
selectPresentWord();
}
@Override
public boolean handleDrag(Vec3d currentPt, Vec3d firstPt) {
if (!editMode)
return false;
// Set up the transformation from global coordinates to the entity's coordinates
double height = textHeight.getValue();
TextModel tm = (TextModel) displayModelListInput.getValue().get(0);
Vec3d textsize = RenderManager.inst().getRenderedStringSize(tm.getTessFontKey(), height, editText);
Transform trans = getEntityTransForSize(textsize);
// Calculate the entity's coordinates for the mouse click
Vec3d currentCoord = new Vec3d();
trans.multAndTrans(currentPt, currentCoord);
Vec3d firstCoord = new Vec3d();
trans.multAndTrans(firstPt, firstCoord);
// Set the start and end of highlighting
double insert = currentCoord.x + 0.5d*textsize.x;
double first = firstCoord.x + 0.5d*textsize.x;
insertPos = RenderManager.inst().getRenderedStringPosition(tm.getTessFontKey(), height, editText, insert);
int firstPos = RenderManager.inst().getRenderedStringPosition(tm.getTessFontKey(), height, editText, first);
numSelected = firstPos - insertPos;
return true;
}
@Override
public void handleSelectionLost() {
if (editMode)
acceptEdits();
}
public String getCachedText() {
if (editMode)
return editText;
return savedText;
}
public double getTextHeight() {
return textHeight.getValue().doubleValue();
}
public Vec3d getTextSize() {
double height = textHeight.getValue();
TextModel tm = (TextModel) displayModelListInput.getValue().get(0);
return RenderManager.inst().getRenderedStringSize(tm.getTessFontKey(), height, savedText);
}
public void resizeForText() {
Vec3d textSize = getTextSize();
double length = textSize.x + textSize.y;
double height = 2.0 * textSize.y;
Vec3d newSize = new Vec3d(length, height, 0.0);
InputAgent.apply(this, InputAgent.formatPointInputs("Size", newSize, "m"));
}
public boolean isEditMode() {
return editMode;
}
public int getInsertPosition() {
return insertPos;
}
public int getNumberSelected() {
return numSelected;
}
public StringChoiceInput getFontNameInput() {
return fontName;
}
public StringListInput getFontStyleInput() {
return fontStyle;
}
public ColourInput getFontColorInput() {
return fontColor;
}
public BooleanInput getDropShadowInput() {
return dropShadow;
}
public ColourInput getDropShadowColorInput() {
return dropShadowColor;
}
public Vec3dInput getDropShadowOffsetInput() {
return dropShadowOffset;
}
}