/* ========================
* JSynoptic : a free Synoptic editor
* ========================
*
* Project Info: http://jsynoptic.sourceforge.net/index.html
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* (C) Copyright 2001-2003, by :
* Corporate:
* Astrium SAS
* EADS CRC
* Individual:
* Nicolas Brodu
* Jean-Baptiste Lièvremont
*
* $Id: TextShape.java,v 1.67 2009/01/19 17:24:08 ogor Exp $
*
* Changes
* -------
* 13-Oct-2003 : Initial version (NB);
*
*/
package jsynoptic.builtin;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ResourceBundle;
import java.util.TimeZone;
import java.util.Vector;
import javax.swing.undo.CompoundEdit;
import jsynoptic.base.DataSourceConsumer;
import jsynoptic.builtin.ui.TextPropertiesPanel;
import jsynoptic.ui.JSynoptic;
import jsynoptic.ui.JSynopticBatch;
import simtools.data.DataException;
import simtools.data.DataInfo;
import simtools.data.DataSource;
import simtools.data.DataSourcePool;
import simtools.data.EndNotificationListener;
import simtools.data.UnsupportedOperation;
import simtools.diagram.DiagramSelection;
import simtools.shapes.AbstractShape;
import simtools.shapes.undo.PropertyChangeEdit;
import simtools.ui.ColorMapper;
import simtools.ui.CustomizedLocale;
import simtools.ui.FontChooserPanel;
import simtools.ui.GenericMapper;
import simtools.ui.JPropertiesPanel;
import simtools.ui.MapperListener;
import simtools.ui.ResourceFinder;
import simtools.ui.TextMapper;
import simtools.util.PrintfFormat;
/**
* This shape displays some texts, and fits it to its dimensions.
*/
public class TextShape extends Abstract2DShape implements MapperListener,
DataSourceConsumer {
static final long serialVersionUID = -6782484932282488103L;
public static ResourceBundle resources = ResourceFinder.get(TextShape.class);
public static final int FORMAT_STRING = 0;
public static final int FORMAT_DECIMAL = 1;
public static final int FORMAT_SCIENTIFIC = 2;
public static final int FORMAT_HEXADECIMAL = 3;
public static final int FORMAT_OCTAL = 4;
public static final int FORMAT_BINARY = 5;
public static final int FORMAT_MAPPER = 6;
public static final int FORMAT_DATE = 7;
public static final int FORMAT_TIME = 8;
public static final int FORMAT_DATE_TIME = 9;
public static final int FORMAT_PRINTF = 10;
public static final int FORMAT_USE_DATA_SOURCE = 11;
public static final int MIN_WIDTH = 40;
public static final int MIN_HEIGHT = 20;
public static final int MARGINX = 5;
public static final int MARGINY = 1;
// Default values (can be overwriten using user file configuration)
public static boolean DEFAULT_FIXED_FONT = true;
public static boolean DEFAULT_FIXED_FRAME = false;
public static int DEFAUT_CHAR_NUMBER = 5;
static public int DEFAULT_TEXT_SIZE = 36;
static public String DEFAULT_TEXT_FONT = "Monospaced";
static public int DEFAULT_FORMAT = FORMAT_STRING;
static public boolean DEFAULT_DISPLAY_NAME = false;
static public boolean DEFAULT_DISPLAY_VALUE = true;
static public boolean DEFAULT_DISPLAY_UNIT = false;
static public boolean DEFAULT_DISPLAY_COMMENTS = false;
static public String dateFormatPattern = "MM/dd/yyyy";
static public String shortTimeFormatPattern = "HH:mm:ss.SSS";
static public String longTimeFormatPattern = "MM/dd/yyyy HH:mm:ss.SSS";
protected int format;
protected String printfFormat = null;
protected transient PrintfFormat printfFormater = null;
protected NumberFormat decimalFormatter;
protected NumberFormat scientificFormatter;
public static boolean groupTextDigits = false;
/**
* @deprecated
*/
private DateFormat dateFormatter = null; // no more used : backward
// compatibility
/**
* @deprecated
*/
private DateFormat timeFormatter = null; // no more used : backward
// compatibility
protected DateFormat dateTimeFormatter;
public static String timeZone;
protected TextMapper mapper;
/**
* @deprecated use printf format instead
*/
protected String displayPattern = null;
protected Boolean displayDsName;
protected Boolean displayDsValue;
protected Boolean displayDsUnit;
protected Boolean displayDsComments;
protected String text;
protected transient DataSource source;
protected transient long sourceIndex;
protected Color textColor = Color.black;
protected transient Color textDynamicColor = null;
protected ColorMapper textMapper;
protected transient DataSource textMapperSource;
protected transient long textMapperIndex;
protected static Object referenceMutex = new Object(); // used to
// synchronize when
// defining
// reference fields
// in the first
// constructor
protected static Graphics referenceGraphics = null;
// We keep _x and _y to 0 so the frame around the text field can be aligned
// on the grid
// But we need a delta for the text origin
protected transient int baseline;
// Do fitText() on reload => get baseline and font
protected transient Font currentFont;
protected transient FontMetrics referenceFontMetrics = null;
protected transient Font referenceFont = null;
// For data source notifications
protected transient boolean dirtyText = false;
protected transient boolean dirtyColor = false;
protected transient boolean dirtyState = false;
protected String fontName;
protected int fontSize;
protected boolean boldFont;
protected boolean italicFont;
/**
* When the font is locked, the text never auto-adjusts to the size
*
* Default is true
*/
protected boolean lockedFont;
/**
* When the frame is locked, - the text never auto-adjusts to the size - The
* frame is determinated according to a number of characters to display, and
* cannot be rezized
*
* Default is false
*/
protected boolean lockedFrame;
/**
* When frame is locked, number of charcter to dispay in frame
*/
protected Integer charNumber;
/**
* When margin is enabled, the text is not framed directly with the bounding
* box, but has margins with MARGINX and MARGINY values Default is true
*/
protected boolean marginEnabled = true;
/**
* when the margin is not enabled, it is necessary to multiply the size of
* the font in Y to remove the line spacing, so that the text can be framed
* directly with the bounding box
*/
protected transient final double fontYScaleFactor = 1.34;
/** used to initialize the reference fields at class creation */
static {
Component c;
if (JSynoptic.gui != null) {
c = JSynoptic.gui.getOwner();
} else if (JSynopticBatch.batch != null) {
c = (Component) JSynopticBatch.batch.getPrintable(0);
} else {
c = null;
}
Graphics g = (c != null) ? c.createImage(100, 100).getGraphics() : new BufferedImage(100, 100,
BufferedImage.TYPE_INT_RGB).createGraphics();
referenceGraphics = g;
}
/**
* @param printfFormat
* the string value to apply to printf format
*/
public void setPrintfFormat(String printfFormat) {
this.printfFormat = printfFormat;
if (printfFormat != null) {
try {
printfFormater = new PrintfFormat(printfFormat);
return;
} catch (IllegalArgumentException e) {
}
}
printfFormater = null;
}
/**
* @param format
*/
protected void setFormat(int f) {
format = f;
if ((format < 0) || ((format == FORMAT_MAPPER) && (mapper == null))) {
format = FORMAT_STRING;
} else if (format == FORMAT_DATE) {
dateTimeFormatter = new SimpleDateFormat(dateFormatPattern);
} else if (format == FORMAT_TIME) {
dateTimeFormatter = new SimpleDateFormat(shortTimeFormatPattern);
} else if (format == FORMAT_DATE_TIME) {
dateTimeFormatter = new SimpleDateFormat(longTimeFormatPattern);
}
if ((format == FORMAT_DATE) || (format == FORMAT_TIME) || (format == FORMAT_DATE_TIME)) {
dateTimeFormatter.setTimeZone(TimeZone.getTimeZone(timeZone));
}
}
/**
* @return the current text displayed by this shape
*/
public String getText() {
return text;
}
/**
* Set current text. In case frame is locked, keep only specifed first
* characters
*
* @param text
*/
public void setText(String text) {
this.text = text;
}
protected Object getSourceValue() throws DataException {
if (source != null) {
return source.getValue(sourceIndex);
}
return null;
}
/**
* Gets a new text value for this shape. The current formatter will be used
* if the object is a Number
*
* @param o.
* Any object. If this is a Number, it will be
* formatted.Otherwise, toString() is used.
* @return the computed new text
*/
public String formatSourceValue(Object o) {
String ret;
if ((format == FORMAT_MAPPER) && (mapper != null)) {
ret = mapper.getString(o);
if ((ret == null) || (ret.equals(""))) {
ret = " ";
}
return ret;
}
if (((format == FORMAT_PRINTF) || (format == FORMAT_USE_DATA_SOURCE)) && (printfFormater != null)) {
try {
ret = printfFormater.sprintf(o);
return ret;
} catch (IllegalArgumentException e) {
ret = resources.getString("IncompatiblePrintfFormater");
}
return ret;
}
if (o == null) {
ret = " ";
return ret;
}
if (!(o instanceof Number)) {
ret = o.toString();
return ret;
}
Number n = (Number) o;
switch (format) {
case FORMAT_DECIMAL:
ret = decimalFormatter.format(n);
break;
case FORMAT_SCIENTIFIC:
ret = scientificFormatter.format(n);
break;
case FORMAT_HEXADECIMAL:
ret = "0x" + Long.toHexString(n.longValue()).toUpperCase();
break;
case FORMAT_OCTAL:
ret = "0" + Long.toOctalString(n.longValue());
break;
case FORMAT_BINARY:
ret = Long.toBinaryString(n.longValue());
break;
case FORMAT_DATE:
case FORMAT_TIME:
case FORMAT_DATE_TIME:
ret = dateTimeFormatter.format(new Date(n.longValue()));
break;
default:
case FORMAT_STRING:
ret = n.toString();
}
return ret;
}
/*
* (non-Javadoc)
*
* @see jsynoptic.builtin.Abstract1DShape#getDelegateShape()
*/
protected Shape getDelegateShape() {
return new Rectangle(_ox, _oy - _h, _w, _h);
}
protected void init() {
// Frame
drawColor = null; // no frame
// Dynamic options
displayDsName = new Boolean(DEFAULT_DISPLAY_NAME);
displayDsValue = new Boolean(DEFAULT_DISPLAY_VALUE);
displayDsUnit = new Boolean(DEFAULT_DISPLAY_UNIT);
displayDsComments = new Boolean(DEFAULT_DISPLAY_COMMENTS);
// Decimal and scientific formaters
decimalFormatter = NumberFormat.getNumberInstance(CustomizedLocale.get());
decimalFormatter.setGroupingUsed(groupTextDigits); // Diable grouping
// format whaterver
// used Locale
scientificFormatter = NumberFormat.getNumberInstance(CustomizedLocale.get());
if (scientificFormatter instanceof DecimalFormat) {
((DecimalFormat) scientificFormatter).applyPattern("0.000E0");
}
scientificFormatter.setGroupingUsed(groupTextDigits); // Diable
// grouping
// format
// whaterver
// used Locale
// Format
setFormat(DEFAULT_FORMAT);
if ((format == FORMAT_PRINTF) || (format == FORMAT_USE_DATA_SOURCE)) {
setPrintfFormat(printfFormat);
}
// Size
setLockedFont(DEFAULT_FIXED_FONT); // default behaviour since version
// 1.6
setLockedFrame(DEFAULT_FIXED_FRAME); // since version 2.6
setCharNumber(new Integer(DEFAUT_CHAR_NUMBER)); // since version 2.6
}
public TextShape(String text, int width, int height) {
this(text, 0, 0, width, height);
}
public TextShape(String text, int x, int y, int width, int height) {
this(text, x, y, width, height, true);
}
public TextShape(String text, int x, int y, int width, int height, boolean fitBounds) {
super(x, y, width, height);
setText(text);
init(); // Set all text properties
updateFont();
if (fitBounds){
fitBounds();
}
fitText();
}
public boolean setFont(Font f) {
if (f.equals(referenceFont)) {
return false;
}
fontName = f.getName();
fontSize = f.getSize();
boldFont = f.isBold();
italicFont = f.isItalic();
referenceFont = new Font(fontName, f.getStyle(), fontSize);
referenceFontMetrics = referenceGraphics.getFontMetrics(referenceFont);
currentFont = referenceFont;
return true;
}
public void setLockedFont(boolean lockedFont) {
this.lockedFont = lockedFont;
if (lockedFont) {
lockedFrame = false;
}
}
public void setLockedFrame(boolean lockedFrame) {
this.lockedFrame = lockedFrame;
if (lockedFrame) {
lockedFont = false;
}
}
protected void setCharNumber(Integer charNumber) {
this.charNumber = charNumber;
}
protected boolean canResize() {
return (!lockedFrame && super.canResize());
}
public void updateFont() {
if (fontName == null) {
fontName = DEFAULT_TEXT_FONT; // backward compatibility
fontSize = DEFAULT_TEXT_SIZE;
italicFont = false;
boldFont = false;
}
currentFont = new Font(fontName, (boldFont ? Font.BOLD : 0) + (italicFont ? Font.ITALIC : 0), fontSize);
referenceFont = currentFont;
referenceFontMetrics = referenceGraphics.getFontMetrics(referenceFont);
}
/* (non-Javadoc)
* @see jsynoptic.builtin.Abstract1DShape#setDelegateEndNotificationListener(simtools.data.EndNotificationListener)
*/
public void setDelegateEndNotificationListener(EndNotificationListener denl) {
if (source != null) {
source.removeEndNotificationListener(delegateEndNotificationListener);
source.addEndNotificationListener(denl);
}
if (textMapperSource != null) {
textMapperSource.removeEndNotificationListener(delegateEndNotificationListener);
textMapperSource.addEndNotificationListener(denl);
}
super.setDelegateEndNotificationListener(denl);
}
// Benefits from parent draw, add our own text
protected void drawHook(Graphics2D g, boolean shapeDrawn) {
if (!shapeDrawn) {
return;
}
Object textRenderingHint = g.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
AbstractShape.ANTI_ALIASING ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
// Prevent the text to get out of bounds even if it should not according
// to java doc
Rectangle oldclip = g.getClipBounds();
g.setClip(_ox, _oy - _h, _w, _h);
Color c = getTextColor();
if (c != null) {
g.setColor(c);
}
Font oldFont = g.getFont();
g.setFont(currentFont);
String displayedText = text;
if (lockedFrame && (text != null)) {
int lastIndex = (charNumber.intValue() < text.length()) ? charNumber.intValue() : text.length();
displayedText = text.substring(0, lastIndex);
}
if (marginEnabled) {
g.drawString(displayedText, _ox + MARGINX, _oy - baseline - MARGINY);
} else {// margin not enabled
g.drawString(displayedText, _ox, _oy - baseline);
}
g.setFont(oldFont);
g.setClip(oldclip);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, textRenderingHint);
}
/**
* Set dimensions and fits the text to the new dimensions.
*/
public void setDimensions(int width, int height) {
_w = width;
_h = height;
if (_w < MIN_SIZE) {
_w = MIN_SIZE;
}
if (_h < MIN_SIZE) {
_h = MIN_SIZE;
}
ratio = (double) _w / (double) _h;
fitText();
}
/**
* Resize uses an increment for width and height. Public by side effect, do
* not call. Use setDimensions instead.
*/
public void resize(int dx, int dy) {
super.resize(dx, dy); // from Abstract1DShape
fitText();
}
/**
* Used to make the bounds best fit the current size text size
*/
public void fitBounds() {
if (lockedFrame || lockedFont) {
currentFont = referenceFont;
int h=50, w=150;
if (lockedFrame) { // Compute the bounding needed to display
// specified number of characters
h = referenceFontMetrics.getMaxAscent() + referenceFontMetrics.getMaxDescent();
w = referenceFontMetrics.stringWidth("W") * charNumber.intValue(); // Don't
// use
// getMaxAdvance()
// because wacky characters
// in fonts.
} else if (text != null){
h = referenceFontMetrics.getMaxAscent() + referenceFontMetrics.getMaxDescent();
w = referenceFontMetrics.stringWidth(text);
}
if (marginEnabled) {
_w = w + 2 * MARGINX;
_h = h + 2 * MARGINY;
} else {
_w = w;
// divide by fontYScaleFactor to compensate the font line
// spacing
_h = (int) ((double) h / fontYScaleFactor);
}
if (_w < MIN_SIZE) {
_w = MIN_SIZE;
}
if (_h < MIN_SIZE) {
_h = MIN_SIZE;
}
ratio = (double) _w / (double) _h;
computeBaseLine();
if (transform != null) {
updateTransform();
} else {
updateBounds();
}
}
}
/**
* Compute base line used with a not scaled font
*/
protected void computeBaseLine() {
if (marginEnabled) {
baseline = referenceFontMetrics.getMaxDescent();
} else {// margin not enabled
baseline = (int) ((double) referenceFontMetrics.getMaxDescent() / fontYScaleFactor);
}
}
/**
* Used to make the text best fit the current size
*/
public void fitText() {
if (!(lockedFrame || lockedFont)) {
double innerw, innerh;
double scaleX, scaleY;
int referenceHeight = referenceFontMetrics.getMaxAscent() + referenceFontMetrics.getMaxDescent();
int referenceWidth = referenceFontMetrics.stringWidth(text);
if (marginEnabled) {
innerw = _w - 2 * MARGINX;
innerh = _h - 2 * MARGINY;
if ((innerw <= 0) || (innerh <= 0)) {
return;
}
scaleX = innerw / referenceWidth;
scaleY = innerh / referenceHeight;
} else {// margin not enabled
innerw = _w;
innerh = _h;
if ((innerw <= 0) || (innerh <= 0)) {
return;
}
scaleX = innerw / referenceWidth;
// multiplication by fontYScaleFactor to compensate the font
// line spacing
scaleY = (innerh / referenceHeight) * fontYScaleFactor;
}
baseline = (int) (referenceFontMetrics.getMaxDescent() * scaleY);
currentFont = referenceFont.deriveFont(AffineTransform.getScaleInstance(scaleX, scaleY));
}
}
/**
* Updates the current text, by reading the data source value if connected
* to a data source Then, notify the system to update the display
*
* @param readAgainFromSource
* Can be set to false to prevent reading the text from the
* source
*/
public boolean updateText() {
if (source == null) {
return false;
}
String oldText = text;
String newText = "";
if (displayDsName.booleanValue()) {
String name = DataInfo.getLabel(source);
if ((name != null) && !name.equals("")) {
newText += name;
if (displayDsValue.booleanValue()) {
newText += "=";
}
}
}
try {
if (displayDsValue.booleanValue()) {
newText += formatSourceValue(getSourceValue());
}
} catch (DataException e) {
newText += resources.getString("NoValue");
}
if (displayDsUnit.booleanValue()) {
String unit = DataInfo.getUnit(source);
if ((unit != null) && !unit.equals("")) {
newText += " [" + unit + "]";
}
}
if (displayDsComments.booleanValue()) {
String comments = DataInfo.getComment(source);
if ((comments != null) && !comments.equals("")) {
newText += " " + comments;
}
}
setText(newText);
// no change in text => no need to repaint
if (text.equals(oldText)) {
return false;
}
fitText();
return true;
}
public void DataSourceValueChanged(DataSource ds, long minIndex, long maxIndex) {
// Change the text to display if the source match (we also listen to
// mapper sources)
if (ds.equals(source)) {
// We care only about our index value change
if ((sourceIndex >= minIndex) && (sourceIndex <= maxIndex)) {
dirtyText = true;
}
}
// now check if this is our mapper source
if (ds.equals(textMapperSource)) {
// We care only about our index value change
if ((textMapperIndex >= minIndex) && (textMapperIndex <= maxIndex)) {
dirtyColor = true;
}
}
super.DataSourceValueChanged(ds, minIndex, maxIndex);
}
/*
* (non-Javadoc)
*
* @see simtools.data.DataSourceListener#DataSourceIndexRangeChanged(simtools.data.DataSource,
* long, long)
*/
public void DataSourceIndexRangeChanged(DataSource ds, long startIndex, long lastIndex) {
// Change the text to display if the source match (we also listen to
// mapper sources)
if (ds.equals(source)) {
sourceIndex = lastIndex;
dirtyText = true;
}
// mind our own business
if (ds.equals(this.textMapperSource)) {
textMapperIndex = lastIndex;
dirtyColor = true;
}
super.DataSourceIndexRangeChanged(ds, startIndex, lastIndex);
}
/*
* (non-Javadoc)
*
* @see simtools.data.DataSourceListener#DataSourceReplaced(simtools.data.DataSource,
* simtools.data.DataSource)
*/
public void DataSourceReplaced(DataSource oldData, DataSource newData) {
if (oldData == source) {
source = newData;
if (source != null) {
source.addListener(this);
source.addEndNotificationListener(delegateEndNotificationListener);
try {
sourceIndex = source.getLastIndex();
} catch (UnsupportedOperation uo1) {
sourceIndex = 0;
}
dirtyText = true;
}
oldData.removeListener(this);
oldData.removeEndNotificationListener(delegateEndNotificationListener);
}
if (oldData == textMapperSource) {
textMapperSource = newData;
if (textMapperSource != null) {
textMapperSource.addListener(this);
textMapperSource.addEndNotificationListener(delegateEndNotificationListener);
try {
textMapperIndex = textMapperSource.getLastIndex();
} catch (UnsupportedOperation uo1) {
textMapperIndex = 0;
}
dirtyText = true;
}
oldData.removeListener(this);
oldData.removeEndNotificationListener(delegateEndNotificationListener);
}
super.DataSourceReplaced(oldData, newData);
}
public Color getTextColor(){
return (textDynamicColor!=null) ? textDynamicColor : textColor;
}
/*
* (non-Javadoc)
*
* @see simtools.data.EndNotificationListener#notificationEnd(java.lang.Object)
*/
public void notificationEnd(Object referer) {
if (dirtyText) {
dirty |= updateText();
dirtyText = false;
}
if (dirtyColor) {
Color c = null;
if (textMapper != null) {
c = (Color) textMapper.getPaint(textMapperSource, textMapperIndex);
}
// dirty field to true only if necessary
if (c == null) {
dirty |= (textDynamicColor != null);
} else {
dirty |= !c.equals(textDynamicColor);
}
textDynamicColor = c;
dirtyColor = false;
}
super.notificationEnd(referer);
}
/* (non-Javadoc)
* @see simtools.shapes.AbstractShape#subscribeToDataNotifications()
*/
public void processShapeRestoring(){
if (source != null) {
source.addListener(this);
source.addEndNotificationListener(delegateEndNotificationListener);
try {
sourceIndex = source.getLastIndex();
} catch (UnsupportedOperation uo1) {
sourceIndex = 0;
}
}
if (textMapperSource != null) {
textMapperSource.addListener(this);
textMapperSource.addEndNotificationListener(delegateEndNotificationListener);
try {
textMapperIndex = textMapperSource.getLastIndex();
} catch (UnsupportedOperation uo1) {
textMapperIndex = 0;
}
}
super.processShapeRestoring();
}
/* (non-Javadoc)
* @see simtools.shapes.AbstractShape#unsubscribeToDataNotifications()
*/
public void processShapeRemoving(){
if (source != null) {
source.removeListener(this);
source.removeEndNotificationListener(delegateEndNotificationListener);
}
if (textMapperSource != null) {
textMapperSource.removeListener(this);
textMapperSource.removeEndNotificationListener(delegateEndNotificationListener);
}
super.processShapeRemoving();
}
public String[] getActions(double x, double y, Object o, int context) {
if (context == MOUSE_OVER_CONTEXT) {
return null;
}
if (context == MOUSE_OUT_CONTEXT) {
return null;
}
if (context == MOUSE_PRESSED_CONTEXT) {
return null;
}
Vector v = new Vector();
String[] actions = super.getActions(x, y, o, context);
if (actions != null) {
for (int i = 0; i < actions.length; ++i) {
v.add(actions[i]);
}
}
if (context == EDITOR_CONTEXT) {
if ((o instanceof DataSource) && (canAddDataSource(((DataSource) o)))) {
v.add(resources.getString("SetSource"));
}
}
return (String[]) v.toArray(new String[v.size()]);
}
public void setSource(DataSource ds) {
if (source != null) {
source.removeListener(this);
source.removeEndNotificationListener(delegateEndNotificationListener);
}
source = ds;
if (source == null) {
return;
}
source.addListener(this);
source.addEndNotificationListener(delegateEndNotificationListener);
try {
sourceIndex = source.computeLastIndex();
} catch (UnsupportedOperation e) {
try {
sourceIndex = source.getLastIndex();
} catch (UnsupportedOperation e1) {
try {
sourceIndex = source.getStartIndex();
} catch (UnsupportedOperation e2) {
sourceIndex = 0;
}
}
}
if ((format == FORMAT_PRINTF) || (format == FORMAT_USE_DATA_SOURCE)) {
setPrintfFormat(DataInfo.getPrintfFormat(ds));
}
updateText();
}
/*
* (non-Javadoc)
*
* @see jsynoptic.base.SelectionContextualActionProvider#getCollectiveActions()
*/
public LinkedHashMap getCollectiveActions(DiagramSelection sel, double x, double y, Object o, int context) {
LinkedHashMap res = super.getCollectiveActions(sel, x, y, o, context);
res.put(resources.getString("AdjustSize"), null);
// Body action
for (int i = 0; i < FontChooserPanel.SIZES.length; i++) {
res.put(resources.getString("Body") + ";" + FontChooserPanel.SIZES[i], null);
}
// Font action
for (int i = 0; i < AbstractShape.FONT_NAMES.length; i++) {
res.put(resources.getString("Font") + ";" + AbstractShape.FONT_NAMES[i], null);
}
if (context == EDITOR_CONTEXT) {
if ((o instanceof DataSource) && (canAddDataSource(((DataSource) o)))) {
res.put(resources.getString("SetSource"), null);
}
}
return res;
}
/*
* (non-Javadoc)
*
* @see jsynoptic.base.ContextualActionProvider#doAction(double, double,
* java.lang.Object, java.lang.String)
*/
public boolean doAction(double x, double y, Object o, String action, CompoundEdit undoableEdit) {
if (action.equals(resources.getString("SetSource"))) {
// Save old params
CompoundEdit ce = new CompoundEdit();
HashMap oldValues = new HashMap();
oldValues.put("TEXT_SOURCE", source);
oldValues.put("TEXT", getText());
setSource((DataSource) o);
// Notify changes
Iterator it = oldValues.keySet().iterator();
while (it.hasNext()) {
String n = (String) it.next();
ce.addEdit(new PropertyChangeEdit(this, n, oldValues.get(n), getPropertyValue(n)));
}
ce.end();
if (undoableEdit != null) {
undoableEdit.addEdit(ce);
}
return true;
}
if (action.startsWith(resources.getString("Font"))) {
// Save old params
CompoundEdit ce = new CompoundEdit();
HashMap oldValues = new HashMap();
oldValues.put("FONT", currentFont);
// Make changes
fontName = action.substring(resources.getString("Font").length() + 1);
updateFont();
Rectangle bounds = getBounds();
fitBounds();
// Notify changes
Iterator it = oldValues.keySet().iterator();
while (it.hasNext()) {
String n = (String) it.next();
ce.addEdit(new PropertyChangeEdit(this, n, oldValues.get(n), getPropertyValue(n)));
}
ce.end();
if (undoableEdit != null) {
undoableEdit.addEdit(ce);
}
// Repaint
bounds.add(getBounds());
notifyChange(bounds);
return true;
}
if (action.startsWith(resources.getString("Body"))) {
// Save old params
CompoundEdit ce = new CompoundEdit();
HashMap oldValues = new HashMap();
oldValues.put("FONT", currentFont);
// Make changes
fontSize = new Integer(action.substring(resources.getString("Body").length() + 1)).intValue();
updateFont();
Rectangle bounds = getBounds();
fitBounds();
// Notify changes
Iterator it = oldValues.keySet().iterator();
while (it.hasNext()) {
String n = (String) it.next();
ce.addEdit(new PropertyChangeEdit(this, n, oldValues.get(n), getPropertyValue(n)));
}
ce.end();
if (undoableEdit != null) {
undoableEdit.addEdit(ce);
}
// Repaint
bounds.add(getBounds());
notifyChange(bounds);
return true;
}
if (action.equals(resources.getString("AdjustSize"))) {
CompoundEdit ce = new CompoundEdit();
HashMap oldValues = new HashMap();
oldValues.put("WIDTH", getPropertyValue("WIDTH"));
oldValues.put("HEIGHT", getPropertyValue("HEIGHT"));
Rectangle bounds = getBounds();
fitBounds();
Iterator it = oldValues.keySet().iterator();
while (it.hasNext()) {
String n = (String) it.next();
ce.addEdit(new PropertyChangeEdit(this, n, oldValues.get(n), getPropertyValue(n)));
}
ce.end();
if (undoableEdit != null) {
undoableEdit.addEdit(ce);
}
bounds.add(getBounds());
notifyChange(bounds);
return true;
}
return super.doAction(x, y, o, action, undoableEdit);
}
/* (non-Javadoc)
* @see jsynoptic.builtin.Abstract2DShape#getPanel(java.util.List)
*/
public JPropertiesPanel getPanel(List properties) {
if (properties == null){
return null;
}
if (properties.containsAll(Arrays.asList(new TextShapePropertiesNames().getPropertyNames()))){
return new TextPropertiesPanel(true, Builtin.resources.getString("Text"));
} else {
return super.getPanel(properties);
}
}
/* (non-Javadoc)
* @see simtools.shapes.AbstractShape#getShapeName()
*/
public String getShapeName(){
return Builtin.resources.getString("Text");
}
// Take care of serialisation. Special handling for datasources
private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
out.defaultWriteObject();
DataSourcePool.global.writeDataSource(out, textMapperSource);
DataSourcePool.global.writeDataSource(out, source);
}
protected void synchronizeFormat() {
// backward compatibility
if ((format == FORMAT_DATE) && (dateFormatter != null)) {
dateTimeFormatter = dateFormatter;
}
if ((format == FORMAT_TIME) && (timeFormatter != null)) {
dateTimeFormatter = timeFormatter;
}
dateFormatter = null; // no more used
timeFormatter = null; // no more used
}
private void readObject(java.io.ObjectInputStream in) throws java.lang.ClassNotFoundException, java.io.IOException {
in.defaultReadObject();
if (displayDsName == null) {
displayDsName = new Boolean(false);
}
if (displayDsValue == null) {
displayDsValue = new Boolean(true);
}
if (displayDsUnit == null) {
displayDsUnit = new Boolean(false);
}
if (displayDsComments == null) {
displayDsComments = new Boolean(false);
}
if (charNumber == null) {
charNumber = new Integer(DEFAUT_CHAR_NUMBER); // since version 2.6
}
// Create decimal and scientific formaters
if (decimalFormatter == null) {
decimalFormatter = NumberFormat.getNumberInstance(CustomizedLocale.get());
}
decimalFormatter.setGroupingUsed(groupTextDigits); // Diable grouping
// format whaterver
// used Locale
if (scientificFormatter == null) {
scientificFormatter = NumberFormat.getNumberInstance(CustomizedLocale.get());
if (scientificFormatter instanceof DecimalFormat) {
((DecimalFormat) scientificFormatter).applyPattern("0.000E0");
}
}
scientificFormatter.setGroupingUsed(groupTextDigits); // Diable
// grouping
// format
// whaterver
// used Locale
synchronizeFormat();
// Set format regarding current defined formats
setFormat(format);
if (mapper != null) {
mapper = TextMapper.updateTextMapper(mapper);
}
if (mapper != null) {
mapper.addListener(this);
}
// Objects using this feature should handle this in their own readObject
textMapperSource = DataSourcePool.global.readDataSource(in);
if (textMapperSource != null) {
textMapperSource.addListener(this);
textMapperSource.addEndNotificationListener(delegateEndNotificationListener);
try {
textMapperIndex = textMapperSource.getLastIndex();
} catch (UnsupportedOperation uo1) {
textMapperIndex = 0;
}
}
if (textMapper != null) {
textMapper = ColorMapper.updateColorMapper(textMapper);
}
if ((textMapper != null) && (textMapperSource != null)) {
textDynamicColor = (Color) textMapper.getPaint(textMapperSource, textMapperIndex);
} else {
textDynamicColor = null;
}
updateFont();
computeBaseLine();
source = DataSourcePool.global.readDataSource(in);
setPrintfFormat(printfFormat);
if (source != null) {
source.addListener(this);
source.addEndNotificationListener(delegateEndNotificationListener);
try {
sourceIndex = source.computeLastIndex();
} catch (UnsupportedOperation e) {
try {
sourceIndex = source.getLastIndex();
} catch (UnsupportedOperation e1) {
try {
sourceIndex = source.getStartIndex();
} catch (UnsupportedOperation e2) {
sourceIndex = 0;
}
}
}
updateText();
}
fitText();
}
/*
* (non-Javadoc)
*
* @see simtools.shapes.AbstractShape#cloneShape()
*/
protected AbstractShape cloneShape() {
TextShape ts = (TextShape) super.cloneShape();
if (ts.source != null) {
ts.source.addEndNotificationListener(ts.delegateEndNotificationListener);
ts.source.addListener(ts);
}
if (ts.textMapperSource != null) {
ts.textMapperSource.addEndNotificationListener(ts.delegateEndNotificationListener);
ts.textMapperSource.addListener(ts);
}
ts.decimalFormatter = (decimalFormatter == null) ? null : (NumberFormat) decimalFormatter.clone();
ts.scientificFormatter = (scientificFormatter == null) ? null : (NumberFormat) scientificFormatter.clone();
ts.dateFormatter = null; // no more used : backward compatibility
ts.timeFormatter = null; // no more used : backward compatibility
ts.dateTimeFormatter = (dateTimeFormatter == null) ? null : (DateFormat) dateTimeFormatter.clone();
ts.updateFont();
ts.fitText();
return ts;
}
/*
* (non-Javadoc)
*
* @see simtools.ui.MapperListener#mappingChanged(simtools.ui.GenericMapper)
*/
public void mappingChanged(GenericMapper mapper) {
if (mapper.equals(this.mapper)) {
updateText();
}
notifyChange();
}
/*
* (non-Javadoc)
*
* @see simtools.shapes.AbstractShape#notifyChange(java.awt.Rectangle)
*/
protected synchronized void notifyChange(Rectangle changedArea) {
// Override this for delegate end notification listener to know if this
// object was
// dirty or not.
// The delegate should set dirtyState to false before calling our end
// notification handler
// If it was dirty, this function is called by Abstract1DShape. If not,
// no call
// The the delegate can check if the dirty state has changed.
dirtyState = true;
super.notifyChange(changedArea);
}
/**
* @return Returns the dirtyState.
*/
public boolean isDirtyState() {
return dirtyState;
}
/**
* @param dirtyState
* The dirtyState to set.
*/
public void setDirtyState(boolean dirtyState) {
this.dirtyState = dirtyState;
}
/*
* (non-Javadoc)
*
* @see jsynoptic.base.DataSourceConsumer#addDataSource(simtools.data.DataSource)
*/
public boolean addDataSource(DataSource d) {
setSource(d);
notifyChange();
return true;
}
/*
* (non-Javadoc)
*
* @see jsynoptic.base.DataSourceConsumer#canAddDataSource(simtools.data.DataSource)
*/
public boolean canAddDataSource(DataSource d) {
return true;
}
public Object getPropertyValue(String name) {
Object res = super.getPropertyValue(name);
if (name.equalsIgnoreCase("TEXT")) {
res = getText();
} else if (name.equalsIgnoreCase("TEXT_COLOR")) {
res = textColor;
} else if (name.equalsIgnoreCase("DISPLAY_NAME")) {
res = displayDsName;
} else if (name.equalsIgnoreCase("DISPLAY_VALUE")) {
res = displayDsValue;
} else if (name.equalsIgnoreCase("DISPLAY_UNIT")) {
res = displayDsUnit;
} else if (name.equalsIgnoreCase("DISPLAY_COMMENTS")) {
res = displayDsComments;
} else if (name.equalsIgnoreCase("TEXT_SOURCE")) {
res = source;
} else if (name.equalsIgnoreCase("VALUE_FORMAT")) {
res = new Integer(format);
} else if (name.equalsIgnoreCase("FORMAT_MAPPER")) {
res = mapper;
} else if (name.equalsIgnoreCase("DECIMAL_DIGITS")) {
long l = decimalFormatter.getMaximumFractionDigits();
if (format == FORMAT_SCIENTIFIC) {
l = scientificFormatter.getMaximumFractionDigits();
}
res = new Integer((int) l);
} else if (name.equalsIgnoreCase("VALUE_PRINTF_FORMAT")) {
res = printfFormat;
} else if (name.equalsIgnoreCase("TEXT_MAPPER")) {
res = textMapper;
} else if (name.equalsIgnoreCase("TEXT_MAPPER_SOURCE")) {
res = textMapperSource;
} else if (name.equalsIgnoreCase("ENABLE_MARGIN")) {
res = new Boolean(marginEnabled);
} else if (name.equalsIgnoreCase("FONT_LOCK")) {
res = new Boolean(lockedFont);
} else if (name.equalsIgnoreCase("FONT")) {
res = currentFont;
} else if (name.equalsIgnoreCase("FRAME_LOCK")) {
res = new Boolean(lockedFrame);
} else if (name.equalsIgnoreCase("CHAR_NUMBER")) {
res = charNumber;
}
return res;
}
public void setPropertyValue(String name, Object value) {
// Let text shape manage width and heigth bounds when these properties
// have to be updated
if (!(name.equalsIgnoreCase("WIDTH")) && !(name.equalsIgnoreCase("HEIGHT"))
&& !(name.equalsIgnoreCase("ALLOW_RESIZE"))) {
super.setPropertyValue(name, value);
if (name.equalsIgnoreCase("TEXT")) {
if (value instanceof String) {
if (value != null) {
setText((String) value);
}
}
} else if (name.equalsIgnoreCase("TEXT_COLOR")) {
if (value instanceof Color) {
textColor = (Color) value;
} else {
textColor = null;
}
} else if (name.equalsIgnoreCase("DISPLAY_NAME")) {
if (value instanceof Boolean) {
displayDsName = ((Boolean) value);
}
} else if (name.equalsIgnoreCase("DISPLAY_VALUE")) {
if (value instanceof Boolean) {
displayDsValue = ((Boolean) value);
}
} else if (name.equalsIgnoreCase("DISPLAY_UNIT")) {
if (value instanceof Boolean) {
displayDsUnit = ((Boolean) value);
}
} else if (name.equalsIgnoreCase("DISPLAY_COMMENTS")) {
if (value instanceof Boolean) {
displayDsComments = ((Boolean) value);
}
} else if (name.equalsIgnoreCase("VALUE_FORMAT")) {
if (value instanceof Integer) {
setFormat(((Integer) value).intValue());
updateText();
}
} else if (name.equalsIgnoreCase("FORMAT_MAPPER")) {
if (mapper != null) {
mapper.removeListener(this);
}
if (value instanceof TextMapper) {
mapper = (TextMapper) value;
mapper.addListener(this);
} else {
mapper = null;
}
} else if (name.equalsIgnoreCase("TEXT_SOURCE")) {
setSource((DataSource) value);
} else if (name.equalsIgnoreCase("DECIMAL_DIGITS")) {
if (value instanceof Integer) {
int l = ((Integer) value).intValue();
if (format == FORMAT_DECIMAL) {
decimalFormatter.setMaximumFractionDigits(l);
} else if (format == FORMAT_SCIENTIFIC) {
if (scientificFormatter instanceof DecimalFormat) {
String p = "0.";
for (int i = 0; i < l; ++i) {
p += "0";
}
p += "E0";
((DecimalFormat) scientificFormatter).applyPattern(p);
}
}
}
} else if (name.equalsIgnoreCase("VALUE_PRINTF_FORMAT")) {
if ((format == FORMAT_PRINTF) || (format == FORMAT_USE_DATA_SOURCE)) {
setPrintfFormat((String) value);
updateText();
}
} else if (name.equalsIgnoreCase("TEXT_MAPPER")) {
if (value instanceof ColorMapper) {
textMapper = (ColorMapper) value;
} else {
textMapper = null;
}
} else if (name.equalsIgnoreCase("TEXT_MAPPER_SOURCE")) {
if (textMapperSource != null) {
textMapperSource.removeListener(TextShape.this);
textMapperSource.removeEndNotificationListener(delegateEndNotificationListener);
}
if (value instanceof DataSource) {
textMapperSource = (DataSource) value;
try {
textMapperIndex = textMapperSource.getLastIndex();
textMapperIndex = textMapperSource.getLastIndex();
} catch (UnsupportedOperation e) {
textMapperIndex = 0;
}
textMapperSource.addListener(this);
textMapperSource.addEndNotificationListener(delegateEndNotificationListener);
} else {
textMapperSource = null;
}
if ((textMapper != null) && (textMapperSource != null)) {
textDynamicColor = (Color) textMapper.getPaint(textMapperSource, textMapperIndex);
} else {
textDynamicColor = null;
}
} else if (name.equalsIgnoreCase("FONT_LOCK")) {
if (value instanceof Boolean) {
boolean nv = ((Boolean) value).booleanValue();
if (nv != lockedFont) {
setLockedFont(nv);
if (lockedFont) {
fitBounds(); // adjust size to use a locked font
}
}
}
} else if (name.equalsIgnoreCase("FRAME_LOCK")) {
if (value instanceof Boolean) {
boolean nv = ((Boolean) value).booleanValue();
if (nv != lockedFrame) {
setLockedFrame(nv);
if (lockedFrame) {
fitBounds(); // adjust size to use a locked frame
}
}
}
} else if (name.equalsIgnoreCase("CHAR_NUMBER")) {
if (value instanceof Integer) {
Integer ncn = (Integer) value;
if (!ncn.equals(this.charNumber)) {
setCharNumber((Integer) value);
if (lockedFrame) {
updateText();
setText(getText()); // Set text to keep only fisrt caracters
fitBounds(); // adjust size to use a locked font
}
}
}
} else if (name.equalsIgnoreCase("ENABLE_MARGIN")) {
if (value instanceof Boolean) {
boolean nv = ((Boolean) value).booleanValue();
if (nv != marginEnabled) {
marginEnabled = nv;
fitBounds();
}
}
} else if (name.equalsIgnoreCase("FONT")) {
if (value instanceof Font) {
if (!value.equals(referenceFont)) {
setFont((Font) value);
fitBounds();
}
}
}
fitText(); // If frame or font are not locked, fit text to bounds
}
}
public String[] getPropertyNames() {
if (_propertyNames == null) {
_propertyNames = new TextShapePropertiesNames().getPropertyNames();
}
return _propertyNames;
}
public static class TextShapePropertiesNames extends Abstract2DShapePropertiesNames {
private static transient String[] props = new String[] {
"TEXT",
"TEXT_COLOR",
"DISPLAY_NAME",
"DISPLAY_VALUE",
"DISPLAY_UNIT",
"DISPLAY_COMMENTS",
"TEXT_SOURCE",
"FORMAT_MAPPER",
"VALUE_FORMAT",
"DECIMAL_DIGITS",
"VALUE_PRINTF_FORMAT",
"TEXT_MAPPER",
"TEXT_MAPPER_SOURCE",
"FONT_LOCK",
"FRAME_LOCK",
"CHAR_NUMBER",
"FONT",
"ENABLE_MARGIN", };
public TextShapePropertiesNames() {
super();
for (int i = 0; i < props.length; i++) {
propertyNames.add(props[i]);
}
}
}
}