/* ========================
* 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
*
* $Id: TextArrayShape.java,v 1.30 2009/01/08 15:23:40 ogor Exp $
*
* Changes
* -------
* 16-Oct-2003 : Initial version (NB);
*
*/
package jsynoptic.builtin;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ResourceBundle;
import java.util.Vector;
import javax.swing.undo.CompoundEdit;
import jsynoptic.base.ContextualActionProvider;
import jsynoptic.base.DataSourceConsumer;
import jsynoptic.base.Linkable;
import jsynoptic.builtin.ui.TextArrayPropertiesPanel;
import jsynoptic.ui.JSynoptic;
import jsynoptic.ui.LongAction;
import simtools.data.DataSource;
import simtools.data.DataSourceCollection;
import simtools.data.EndNotificationListener;
import simtools.diagram.Resizable;
import simtools.shapes.AbstractShape;
import simtools.shapes.ui.AbstractShapePropertiesDialogBox;
import simtools.ui.JPropertiesPanel;
import simtools.ui.ResourceFinder;
/**
* An array of text shapes
*/
public class TextArrayShape extends AbstractShape implements Resizable, ContextualActionProvider, Linkable,
EndNotificationListener, DataSourceConsumer {
static final long serialVersionUID = 1320844024842187479L;
public static ResourceBundle resources = ResourceFinder.get(TextArrayShape.class);
// Linkable shape
protected String link;
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
/**
* @return Returns the cells.
*/
public Vector getCells() {
return cells;
}
/**
* If true, this shapes auto-resizes when its content changes. Default is
* false
*/
// protected boolean autoresize; // deprecated : delete it ?
/** The array cells are of type TextShape */
protected Vector cells;
protected boolean forceSameFont = false;
/**
* For subclasses to override. Does nothing
*/
protected TextArrayShape() {
super();
}
public TextArrayShape(String text, int width, int height) {
this(text, 0, 0, width, height);
}
/**
* Construct a new text array with the given size, and only one cell with
* the given text.
*/
public TextArrayShape(String text, int ox, int oy, int width, int height) {
super(ox, oy);
cells = new Vector();
TextShape ts = createTextShape(text, width, height);
cells.add(ts);
ts.setDelegateEndNotificationListener(this);
// TextArray width shall depend on cells width values
_w = ts.getBounds().width;
if (_w < TextShape.MIN_WIDTH) {
_w = TextShape.MIN_WIDTH;
}
_h = height;
if (_h < TextShape.MIN_HEIGHT) {
_h = TextShape.MIN_HEIGHT;
}
_y = height;
ts.setAnchor(_ox, _oy + _y);
}
/*
* (non-Javadoc)
*
* @see simtools.shapes.AbstractShape#translate(int, int)
*/
public void translate(int dx, int dy) {
super.translate(dx, dy);
for (Iterator it = cells.iterator(); it.hasNext();) {
((TextShape) it.next()).translate(dx, dy);
}
}
/*
* (non-Javadoc)
*
* @see simtools.shapes.AbstractShape#setAnchor(int, int)
*/
public void setAnchor(int ox, int oy) {
super.setAnchor(ox, oy);
positionCells();
}
/*
* (non-Javadoc)
*
* @see simtools.shapes.AbstractShape#draw(java.awt.Graphics2D)
*/
public void draw(Graphics2D g) {
for (Iterator it = cells.iterator(); it.hasNext();) {
((TextShape) it.next()).draw(g);
}
}
/**
* Unify the fonts of all the text shapes, so they have a common font.
*/
public void unifyFonts() {
if (forceSameFont) {
double minx = Double.POSITIVE_INFINITY;
double miny = Double.POSITIVE_INFINITY;
double innerw = _w - 2 * TextShape.MARGINX;
double innerh = _h / cells.size() - 2 * TextShape.MARGINY;
if ((innerw <= 0) || (innerh <= 0)) {
return;
}
TextShape tsmin = null;
// Get min size so all fonts will fit
for (int i = 0; i < cells.size(); ++i) {
TextShape ts = (TextShape) cells.get(i);
double scaleX = innerw / ts.referenceFontMetrics.stringWidth(ts.getText());
double scaleY = innerh
/ (ts.referenceFontMetrics.getMaxAscent() + ts.referenceFontMetrics.getMaxDescent());
minx = Math.min(scaleX, minx);
if (scaleY < miny) {
tsmin = ts;
miny = scaleY;
}
}
if (tsmin == null) {
return;
}
int baseline = (int) (tsmin.referenceFontMetrics.getMaxDescent() * innerh)
/ (tsmin.referenceFontMetrics.getMaxAscent() + tsmin.referenceFontMetrics.getMaxDescent());
Font commonFont = tsmin.referenceFont.deriveFont(AffineTransform.getScaleInstance(minx, miny));
// Apply min size to all
for (int i = 0; i < cells.size(); ++i) {
TextShape ts = (TextShape) cells.get(i);
ts.setFont(commonFont);
ts.baseline = baseline;
ts.setLockedFont(true);
}
}
}
/**
* This function supposes dimensions are correct, it will not check that
* Also, it does not notify changes.
*/
protected void positionCells() {
int cellHeight = _h / cells.size();
for (int i = 0; i < cells.size(); ++i) {
TextShape ts = (TextShape) cells.get(i);
ts.setAnchor(_ox, _oy + (i + 1) * cellHeight);
ts.setDimensions(_w, cellHeight);
}
}
/*
* (non-Javadoc)
*
* @see simtools.diagram.Resizable#resize(int, int)
*/
public void resize(int dx, int dy) {
_w += dx;
_h += dy;
if (_w < TextShape.MIN_WIDTH) {
_w = TextShape.MIN_WIDTH;
}
// round _h to the nearest multiple of the number of cells so each cell
// has equal height
int cellHeight = (int) Math.rint((double) _h / (double) cells.size());
if (cellHeight < TextShape.MIN_HEIGHT) {
cellHeight = TextShape.MIN_HEIGHT;
}
_h = cellHeight * cells.size();
_oy += _y - _h;
_y = _h;
positionCells();
}
/*
* (non-Javadoc)
*
* @see jsynoptic.base.ContextualActionProvider#getActions(double, double,
* java.lang.Object, int)
*/
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;
}
if (context == EDITOR_CONTEXT) {
}
Vector v = new Vector();
v.add(resources.getString("Properties..."));
v.add(resources.getString("NewCell"));
if (o instanceof DataSource && canAddDataSource(((DataSource) o))) {
for (int i = 0; i < cells.size(); ++i) {
v.add(resources.getString("SetSourceForCell") + (i + 1));
}
v.add(resources.getString("AddSourceAsCell") + (cells.size() + 1));
}
if (o instanceof DataSourceCollection) {
v.add(resources.getString("SetOneCellPerSource"));
v.add(resources.getString("CreateNewCellsForEachSource"));
}
return (String[]) v.toArray(new String[v.size()]);
}
/*
* (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) {
//TODO make those actions undoable
if (action.equals(resources.getString("Properties..."))) {
new LongAction(LongAction.LONG_ACTION_SHAPE, null, this) {
protected void doAction() {
JPropertiesPanel panel = createPanel();
/**
* Only one shape properties dialog box can be displayed at
* once When user opens a dialog box properties while
* another one is still displayed: this last dislaog box is
* closed
*/
Point oldLocation = null;
if (currentDialogBox != null) {
oldLocation = currentDialogBox.getBounds().getLocation();
currentDialogBox.dispose();
}
ArrayList shapes = new ArrayList();
shapes.add(TextArrayShape.this);
AbstractShapePropertiesDialogBox optionDialog = new AbstractShapePropertiesDialogBox(JSynoptic.gui
.getOwner(), panel, panel.getShapeName() + resources.getString("propertiesTitle"),
shapes , JSynoptic.gui.getActiveComponent());
if (oldLocation != null) {
optionDialog.setLocation(oldLocation);
}
currentDialogBox = optionDialog;
// Show dialog box
optionDialog.setVisible(true);
}
}.start();
return true;
}
if (action.equals(resources.getString("NewCell"))) {
int cellSize = _h / cells.size();
TextShape ts = new TextShape(Builtin.resources.getString("Text"), _w, cellSize);
ts.setDelegateEndNotificationListener(this);
cells.add(ts);
_h += cellSize;
_y = _h;
positionCells();
unifyFonts();
notifyChange();
return true;
}
if (action.startsWith(resources.getString("AddSourceAsCell"))) {
return addDataSource((DataSource) o);
}
if (action.startsWith(resources.getString("SetSourceForCell"))) {
int i = (int) Long.parseLong(action.substring(resources.getString("SetSourceForCell").length()));
((TextShape) cells.get(i - 1)).setSource((DataSource) o);
notifyChange();
return true;
}
if (action.equals(resources.getString("SetOneCellPerSource"))) {
DataSourceCollection dsc = (DataSourceCollection) o;
int cellSize = _h / cells.size();
for (Iterator it = cells.iterator(); it.hasNext();) {
TextShape ts = (TextShape) it.next();
ts.setDelegateEndNotificationListener(ts); // remove ourselves
// as listener
}
Vector newCells = new Vector();
for (int i = 0; i < dsc.size(); ++i) {
TextShape ts = new TextShape("", _w, cellSize);
ts.setDelegateEndNotificationListener(this);
ts.setSource((DataSource) dsc.get(i));
newCells.add(ts);
}
cells = newCells;
Rectangle bounds = getBounds();
_h = cellSize * cells.size();
_y = _h;
positionCells();
bounds.add(getBounds()); // enclose largest array to update()
notifyChange(bounds);
return true;
}
if (action.equals(resources.getString("CreateNewCellsForEachSource"))) {
DataSourceCollection dsc = (DataSourceCollection) o;
int cellSize = _h / cells.size();
for (int i = 0; i < dsc.size(); ++i) {
TextShape ts = new TextShape("", _w, cellSize);
ts.setDelegateEndNotificationListener(this);
ts.setSource((DataSource) dsc.get(i));
cells.add(ts);
}
_h = cellSize * cells.size();
_y = _h;
positionCells();
notifyChange();
return true;
}
return false;
}
/*
* (non-Javadoc)
*
* @see jsynoptic.builtin.Abstract1DShape#createPanel()
*/
public JPropertiesPanel createPanel() {
return new TextArrayPropertiesPanel(Builtin.resources.getString("TextArray"), true);
}
/* (non-Javadoc)
* @see simtools.shapes.AbstractShape#getShapeName()
*/
public String getShapeName(){
return Builtin.resources.getString("TextArray");
}
/*
* (non-Javadoc)
*
* @see jsynoptic.base.ContextualActionProvider#canDoAction(double, double,
* java.lang.Object, java.lang.String, int)
*/
public boolean canDoAction(double x, double y, Object o, String action, int context) {
return true;
}
/**
* By overwriting this method, it is possible to create different kind of text cells
* @param name
* @param width
* @param height
* @return a text shape belonging to text array
*/
protected TextShape createTextShape(String name, int width, int height) {
return new TextShape(name, width, height);
}
/*
* (non-Javadoc)
*
* @see jsynoptic.base.DataSourceConsumer#addDataSource(simtools.data.DataSource)
*/
public boolean addDataSource(DataSource d) {
int cellSize = _h / cells.size();
TextShape ts = createTextShape(Builtin.resources.getString("Text"), _w, cellSize);
ts.setDelegateEndNotificationListener(this);
ts.addDataSource(d);
cells.add(ts);
_h += cellSize;
_y = _h;
positionCells();
unifyFonts();
notifyChange();
return true;
}
public void setCells(Vector cellsProperties) {
int cellHeight = _h / cells.size();
if (cellHeight < TextShape.MIN_HEIGHT) {
cellHeight = TextShape.MIN_HEIGHT;
}
cells.clear();
_h = 0;
_w = 0;
for (int i = 0; i < cellsProperties.size(); i++) {
TextShape ts = createTextShape(Builtin.resources.getString("Text"), _w, cellHeight);
ts.setDelegateEndNotificationListener(this);
Vector cellProperties = (Vector) cellsProperties.get(i);
// Apply properties to each cells
for (int j = 0; j < cellProperties.size(); j++) {
Object[] property = (Object[]) cellProperties.get(j);
String pname = (String) property[0];
Object value = property[1];
ts.setPropertyValue(pname, value);
}
// Update text array width only when cell width is highter
int width = ts.getBounds().width;
if (width > _w) {
_w = width;
}
cells.add(ts);
_h += cellHeight;
}
_y = _h;
positionCells();
unifyFonts();
notifyChange();
}
/*
* (non-Javadoc)
*
* @see jsynoptic.base.DataSourceConsumer#canAddDataSource(simtools.data.DataSource)
*/
public boolean canAddDataSource(DataSource d) {
return true;
}
/*
* (non-Javadoc)
*
* @see simtools.shapes.AbstractShape#cloneShape()
*/
protected AbstractShape cloneShape() {
TextArrayShape tas = (TextArrayShape) super.cloneShape();
tas.cells = new Vector();
for (Iterator it = cells.iterator(); it.hasNext();) {
tas.cells.add(((TextShape) it.next()).cloneShape());
((TextShape) tas.cells.lastElement()).setDelegateEndNotificationListener(tas);
}
tas.positionCells();
tas.unifyFonts();
return tas;
}
// Restore unique font at de-serialization.
private void readObject(java.io.ObjectInputStream in) throws java.lang.ClassNotFoundException, java.io.IOException {
in.defaultReadObject();
if (forceSameFont) {
unifyFonts();
}
for (Iterator it = cells.iterator(); it.hasNext();) {
TextShape ts = (TextShape) it.next();
ts.setDelegateEndNotificationListener(this);
}
}
/*
* (non-Javadoc)
*
* @see simtools.shapes.AbstractShape#setPropertyValue(java.lang.String,
* java.lang.Object)
*/
public void setPropertyValue(String name, Object value) {
super.setPropertyValue(name, value);
if (name.equalsIgnoreCase("TEXT_ARRAY_CELL_LIST")) {
if (value instanceof Vector) {
setCells((Vector) value);
}
} else if (name.equalsIgnoreCase("SAME_FONT")) {
boolean b = ((Boolean) value).booleanValue();
if (b != forceSameFont) {
forceSameFont = b;
unifyFonts();
}
}
}
public Object getPropertyValue(String name) {
Object res = super.getPropertyValue(name);
if (name.equalsIgnoreCase("TEXT_ARRAY_CELL_LIST")) {
Vector cellsProperties = new Vector();
// For each cell, give an array containing cell properties
for (int i = 0; i < cells.size(); i++) {
TextShape textShape = (TextShape) cells.get(i);
Vector cellProperties = new Vector();
String[] props = textShape.getPropertyNames();
if (props != null) {
for (int j = 0; j < props.length; j++) {
// Each property has a name and a value
Object[] property = new Object[2];
String pname = props[j];
Object value = textShape.getPropertyValue(pname);
property[0] = pname;
property[1] = value;
cellProperties.add(property);
}
}
cellsProperties.add(cellProperties);
}
res = cellsProperties;
} else if (name.equalsIgnoreCase("SAME_FONT")) {
res = new Boolean(forceSameFont);
}
return res;
}
/*
* (non-Javadoc)
*
* @see simtools.data.EndNotificationListener#notificationEnd(java.lang.Object)
*/
public void notificationEnd(Object referer) {
// We receive notification for all the text shapes. This keep the end
// notification event
// unique as this object is registred by all shapes thanks to
// setDelegateEndNotificationListener
Rectangle dirtyRectangle = null;
for (Iterator it = cells.iterator(); it.hasNext();) {
TextShape ts = (TextShape) it.next();
ts.setDirtyState(false);
// This won't repaint as this shape is not connected to any shape
// listener
// Also, there are tests in the shape so if it hasn't changed,
// nothing happens
ts.notificationEnd(referer);
// But the dirty field would be updated
if (ts.isDirtyState()) {
if (dirtyRectangle == null) {
dirtyRectangle = ts.getBounds();
} else {
dirtyRectangle.add(ts.getBounds());
}
}
}
if (forceSameFont) {
unifyFonts();
}
notifyChange(dirtyRectangle);
}
/* (non-Javadoc)
* @see simtools.shapes.AbstractShape#subscribeToDataNotifications()
*/
public void processShapeRestoring(){
for (Iterator it = cells.iterator(); it.hasNext();) {
TextShape ts = (TextShape) it.next();
ts.processShapeRestoring();
}
super.processShapeRestoring();
}
/* (non-Javadoc)
* @see simtools.shapes.AbstractShape#unsubscribeToDataNotifications()
*/
public void processShapeRemoving(){
for (Iterator it = cells.iterator(); it.hasNext();) {
TextShape ts = (TextShape) it.next();
ts.processShapeRemoving();
}
super.processShapeRemoving();
}
public String[] getPropertyNames() {
if (_propertyNames == null) {
_propertyNames = new TextArrayShapePropertiesNames().getPropertyNames();
}
return _propertyNames;
}
public static class TextArrayShapePropertiesNames extends AbstractShapePropertiesNames {
private static transient String[] props = new String[] { "TEXT_ARRAY_CELL_LIST", "SAME_FONT" };
public TextArrayShapePropertiesNames() {
super();
for (int i = 0; i < props.length; i++) {
propertyNames.add(props[i]);
}
}
}
}