/*
* Copyright 2011 Uwe Krueger.
*
* 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.mandelsoft.mand.tool;
import com.mandelsoft.io.AbstractFile;
import java.awt.Container;
import java.awt.Rectangle;
import java.awt.Font;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.mandelsoft.mand.MandelData;
import com.mandelsoft.mand.MandelInfo;
import com.mandelsoft.mand.MandelName;
import com.mandelsoft.mand.QualifiedMandelName;
import com.mandelsoft.mand.util.MandUtils;
import com.mandelsoft.mand.util.MandelList;
import com.mandelsoft.mand.util.MandelListFolder;
import com.mandelsoft.swing.BufferedComponent.RectModifiedEvent;
import com.mandelsoft.swing.BufferedComponent.RectModifiedEventListener;
import com.mandelsoft.swing.BufferedComponent.VisibleRect;
import com.mandelsoft.swing.DataField;
import com.mandelsoft.swing.GBC;
import com.mandelsoft.swing.GBCPanel;
import com.mandelsoft.swing.NumberField;
import com.mandelsoft.swing.TextField;
import com.mandelsoft.swing.Utils;
import com.mandelsoft.swing.WindowControlAction;
import java.awt.Component;
/**
*
* @author Uwe Krueger
*/
public abstract class MandelAreaViewDialog extends MandelDialog {
static public boolean isReadonly(MandelWindowAccess owner, AbstractFile f,
QualifiedMandelName name)
{
return (f!=null&&!f.isFile()) ||
(name!=null && owner.getEnvironment().isReadonly(name.getLabel()) ||
owner.getEnvironment().isReadonly());
}
///////////////////////////////////////////////////////////////////////
// View Panel
///////////////////////////////////////////////////////////////////////
public class MandelAreaView extends GBCPanel {
protected QualifiedMandelName qname;
protected VisibleRect rect;
private boolean inupdate;
private MandelInfo info; // core data
protected MandelData data; // optional information
private boolean change;
protected boolean readonlyMode;
private JLabel label;
private int[] row;
private int col;
private int maxcol=0;
private PropertyChangeListener updateListener;
protected NumberField limitfield;
protected JTextField infofield;
protected JButton showbutton;
public MandelAreaView(QualifiedMandelName name, MandelInfo info, boolean change,
boolean readonly)
{
this.change=change&&!readonly;
this.readonlyMode=readonly;
this.info=info;
this.updateListener=new UpdateListener();
setup();
setName(name);
}
public MandelAreaView(QualifiedMandelName name, MandelData data, boolean change,
boolean readonly)
{
this.change=change&&!readonly;
this.readonlyMode=readonly;
this.qname=name;
this.data=data;
this.info=data.getInfo();
this.updateListener=new UpdateListener();
setup();
setName(name);
}
public MandelAreaView(boolean change)
{
this(null, new MandelInfo(), change, !change);
}
@Override
protected void panelUnbound()
{
super.panelUnbound();
if (rect!=null) {
rect.discard();
}
}
public void setName(QualifiedMandelName name)
{
if (name==null) return;
String text;
qname=name;
if (name.isRoot()) {
text="Root Area";
}
else {
text="Area "+name.getName();
}
setName(text);
if (altspec!=null) altspec.setName(text);
if (modtimes!=null) modtimes.setName(text);
if (tags!=null) tags.setName(text);
label.setText(text);
}
protected void _setRect(VisibleRect rect)
{
this.rect=rect;
}
public void setInfo(String name, MandelInfo info)
{
data=null;
setName(name);
_setInfo(info);
}
protected void _setInfo(MandelInfo info)
{
this.info.setInfo(info);
updateFields();
}
public void setInfo(MandelInfo info)
{
data=null;
_setInfo(info);
}
public void setData(String name, MandelData data)
{
this.data=data;
setName(name);
_setInfo(data.getInfo());
}
public void setData(MandelData data)
{
this.data=data;
_setInfo(data.getInfo());
}
public MandelInfo getInfo()
{
return info;
}
public MandelData getData()
{
return data;
}
public QualifiedMandelName getQualifiedName()
{
return qname;
}
public boolean isChangeable()
{
return change;
}
@Override
protected void adjustBorderArea(Rectangle rect)
{
rect.setRect(rect.getX()*2, rect.getY()+1,
rect.getWidth()*2, rect.getHeight());
}
protected void setup()
{
JSeparator sep;
col=0;
row=new int[col+1];
row[col]=1;
setupFields();
add(label=new JLabel(getName()),
new GBC(0, 0, (maxcol+1)*2, 1).setLayout(GBC.HORIZONTAL,
GBC.CENTER).
setBottomInset(10));
label.setHorizontalAlignment(JLabel.CENTER);
label.setFont(label.getFont().deriveFont(Font.BOLD,
label.getFont().getSize()+2));
setupButtonPanel();
//Put the panels in this panel, labels on left,
//text fields on right.
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
}
protected void setCol(int col)
{
this.col=col;
if (col>maxcol) {
int[] old=row;
int i;
row=new int[col+1];
for (i=0; i<old.length; i++) {
row[i]=old[i];
}
for (; i<=col; i++) {
row[i]=1;
}
maxcol=col;
}
}
protected int getCol()
{
return col;
}
protected int getRow(int col)
{
if (col>maxcol) return 1;
return row[col]-1;
}
protected int getRow()
{
return getRow(col);
}
protected void skipRows(int n)
{
while (n>0) {
n--;
JLabel d1=new JLabel("");
JLabel d2=new JLabel("");
//d1.setVisible(false);
//d2.setVisible(false);
addField(d1,d2);
}
}
protected int getGridRowFieldCol(int col)
{
if (col>maxcol) return 1;
return row[col];
}
protected int getGridRowForGridCol(int col)
{
if (col/2>maxcol) return 1;
return row[col/2];
}
protected int getMaxGridRow()
{
int max=0;
for (int i=0; i<row.length; i++) {
if (row[i]>max) max=row[i];
}
return max;
}
protected int getMaxGridCol()
{
return row.length*2+1;
}
protected void setMaxGridRow(int max)
{
for (int i=0; i<row.length; i++) {
if (row[i]<max) row[i]=max;
}
}
protected void addSeparator()
{
JSeparator sep=new JSeparator();
sep.setName("sep");
add(sep, new GBC(col*2, row[col]++, 2, 1).setWeight(100, 0).
setLayout(GBC.BOTH, GBC.CENTER).
setInsets(10, 0, 10, 0));
}
protected JTextField createField(String name, String prop)
{
return createField(name, prop, change);
}
protected JTextField createField(String name, String prop, boolean change)
{
return createField(name, prop, change, null);
}
protected JTextField createField(String name, String prop, boolean change,
DataField field)
{
BeanAccess access=new BeanAccess(info, prop);
Class type=access.getType();
if (!access.hasGetter()) {
throw new IllegalArgumentException("no getter found for "+prop+" in "+
info.getClass());
}
if (String.class==type) {
field=createTextField((TextField)field,
(ValueAccess<String>)access);
}
else {
if (!Number.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("illegal type for "+prop+" in "+
info.getClass());
}
field=createNumberField((NumberField)field,
(ValueAccess<Number>)access);
}
return setupField(field, name, change, access);
}
private DataField<String> createTextField(TextField field,
ValueAccess<String> access)
{
if (field==null) field=new TextField(access.getValue());
else field.setDataValue(access.getValue());
return field;
}
private DataField<Number> createNumberField(NumberField field,
ValueAccess<Number> access)
{
if (field==null) field=new NumberField(access.getValue());
else field.setDataValue(access.getValue());
field.setEnforceCorrectValue(true);
return field;
}
// protected JTextField createField(String name, boolean change,
// ValueAccess<Number> access)
// {
// return setupField(new NumberField(access.getValue()),
// name, change, access);
//
// }
private Map<DataField<?>, ValueAccess<?>> fields=
new HashMap<DataField<?>, ValueAccess<?>>();
private <T> JTextField setupField(DataField<T> dfield, String name,
boolean change,
ValueAccess<T> up)
{ JTextField field=(JTextField)dfield;
field.setEditable(change);
//field.setEnabled(change);
field.setHorizontalAlignment(JTextField.TRAILING);
field.setColumns(field_length);
if (up!=null) {
field.addActionListener(up);
}
field.addPropertyChangeListener("value", updateListener);
if (!change) {
field.setBorder(null);
}
setupField(field, name);
if (up!=null) fields.put(dfield, up);
return field;
}
protected JTextField createInfoField(String name, String value)
{
return createInfoField(name, value, field_length);
}
protected JTextField createInfoField(String name, String value, int len)
{
JTextField field=new JTextField(value);
field.setEditable(change);
field.setColumns(len);
field.setBorder(null);
field.setHorizontalAlignment(JTextField.RIGHT);
setupField(field, name);
return field;
}
private static final int LEN_1=60;
private static final int LEN_2=15;
private int field_length=LEN_1;
protected void updateFieldLength(int len)
{
if (field_length==len) return;
field_length=len;
for (Component c:getComponents()) {
if (c instanceof JTextField) {
((JTextField)c).setColumns(len);
}
}
}
protected void addField(JLabel label, JComponent field)
{
label.setLabelFor(field);
label.setHorizontalAlignment(JLabel.LEFT);
add(label, new GBC(col*2, row[col]).setWeight(0, 0).setAnchor(GBC.WEST));
add(field,
new GBC(col*2+1, row[col]++).setWeight(100, 10).setLeftInset(10));
if (col>0 && field_length==LEN_1) updateFieldLength(LEN_2);
}
protected void setupField(JTextField field, String name)
{
addField(new JLabel(name),field);
}
protected void updateFields()
{
setAltSpec();
setModTimes();
setTags();
for (DataField f:fields.keySet()) {
ValueAccess acc=fields.get(f);
f.setDataValue(acc.getValue());
}
}
protected JPanel buttons;
protected void newButtonPanel()
{
int row=getMaxGridRow()+1;
setMaxGridRow(row);
buttons=new JPanel();
add(buttons, GBC(0, row).setSpanW(getMaxGridCol()+1));
}
protected void setupButtonPanel()
{
newButtonPanel();
setupButtons();
}
protected JButton createButton(String name, String tip, ActionListener l)
{
JButton b=new JButton(name);
if (tip!=null) b.setToolTipText(tip);
b.addActionListener(l);
buttons.add(b);
return b;
}
protected void setupButtons()
{
modtimes=new ModTimesAction();
JButton b=modtimes_button=new JButton(modtimes);
b.setToolTipText("Show modification times");
buttons.add(b);
altspec=new AltSpecAction();
b=new JButton(altspec);
b.setToolTipText("Show alternate coordinate format");
buttons.add(b);
tags=new TagsAction();
b=tags_button=new JButton(tags);
b.setToolTipText("Show area tags");
buttons.add(b);
modtimes_button.setVisible(data!=null);
}
////////////////////////////////////////////////////////////////////////
// Sub Spec Windows
////////////////////////////////////////////////////////////////////////
private abstract class SubSpecAction extends WindowControlAction {
private MandelSpecDialog spec;
public SubSpecAction(String label)
{
super(null, label);
}
public void setInfo(MandelInfo info)
{
if (spec!=null) spec.setInfo(info);
}
public void setData(MandelData data)
{
if (spec!=null) spec.setData(data);
}
public void setName(String name)
{
if (spec!=null) spec.setName(name);
}
@Override
protected Window createWindow(Window owner)
{
if (owner==null) {
Container c=MandelAreaView.this;
while (c.getParent()!=null && !(c instanceof Window)) {
c=c.getParent();
//System.out.println("C: "+c);
}
owner=(Window)c;
}
spec=createDialog(owner,MandelAreaView.this.getName(),change);
setInfo(getInfo());
spec.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e)
{
MandelSpecDialog s=(MandelSpecDialog)e.getSource();
inupdate=true;
s.updateInfo(getInfo());
updateFields();
inupdate=false;
}
});
return spec;
}
protected abstract MandelSpecDialog createDialog(Window owner, String name,
boolean change);
}
////////////////////////////////////////////////////////////////////////
// modufication time sub window
private ModTimesAction modtimes;
private JButton modtimes_button;
private void setModTimes()
{
if (modtimes!=null) {
if (getData()!=null) {
modtimes.setData(getData());
modtimes_button.setVisible(true);
}
else {
modtimes.setEnabled(true); // close window and
modtimes_button.setVisible(false); // hide active button
}
}
}
private class ModTimesAction extends SubSpecAction {
public ModTimesAction()
{
super("Modification Times");
}
@Override
protected MandelSpecDialog createDialog(Window owner, String name,
boolean change)
{
MandelSpecDialog spec=new ModTimes(owner,name);
spec.setData(MandelAreaView.this.getData());
return spec;
}
}
////////////////////////////////////////////////////////////////////////
// tags sub window
private TagsAction tags;
private JButton tags_button;
private void setTags()
{
if (tags!=null) {
if (getInfo()!=null) {
tags.setInfo(getInfo());
tags_button.setVisible(true);
}
else {
tags.setEnabled(true); // close window and
tags_button.setVisible(false); // hide active button
}
}
}
private class TagsAction extends SubSpecAction {
public TagsAction()
{
super("Tags");
}
@Override
protected MandelSpecDialog createDialog(Window owner, String name,
boolean change)
{
return new TagSpec(owner,name,!readonlyMode);
}
}
////////////////////////////////////////////////////////////////////////
// alternate specification sub window
private AltSpecAction altspec;
private void setAltSpec()
{
if (altspec!=null) altspec.setInfo(getInfo());
}
private class AltSpecAction extends SubSpecAction {
public AltSpecAction()
{
super("Alt. Spec");
}
@Override
protected MandelSpecDialog createDialog(Window owner, String name,
boolean change)
{
return new AltSpec(owner,name,change);
}
}
////////////////////////////////////////////////////////////////////
// prepare show
////////////////////////////////////////////////////////////////////
private class ModifiedListener implements RectModifiedEventListener {
public void rectModified(RectModifiedEvent e)
{
MandelInfo info=getInfo();
System.out.println("info is "+info);
updateInfo(info,rect._getRect());
MandUtils.round(info);
setInfo(info);
}
}
protected String getRectLabel()
{
QualifiedMandelName n=getQualifiedName();
if (n!=null) return n.toString();
return getTitle();
}
protected void addShowButton(String help, boolean subst)
{
showbutton=createButton("Show", help, new ShowAction(subst));
}
private class ShowAction implements ActionListener {
private boolean subst;
public ShowAction(boolean subst)
{
this.subst=subst;
}
public void actionPerformed(ActionEvent e)
{
if (rect==null) {
String label=getRectLabel();
_setRect(getMandelWindowAccess().getMandelImagePane().getImagePane().
createRect(label,label));
rect.addRectModifiedEventListener(new ModifiedListener());
rect.setFixed(!isChangeable());
}
// getMandelWindowAccess().getMandelImagePane().hideSubRects();
rect.activate(subst);
updateSlave();
rect.setVisible(true);
}
}
////////////////////////////////////////////////////////////////////
protected void setupFields()
{
createField("area center X", "XM");
createField("area center Y", "YM");
createField("dimension X", "DX");
createField("dimension Y", "DY");
limitfield=(NumberField)createField("iteration limit", "LimitIt");
//addSeparator();
createField("image width", "RX");
createField("image height", "RY");
createField("location hint", "Location").setEditable(!readonlyMode);
if (qname!=null) {
infofield=createInfoField("info", getInfoString());
}
else {
skipRows(1);
}
addBorder(0, 7, 1, 2);
addBorder(0, 5, 1, 2);
addBorder(0, 0, 1, 5);
}
protected String getInfoString()
{
return MandelAreaViewDialog.this.getInfoString(getEnvironment(),qname);
}
protected void updateSlave()
{
System.out.println("update slave");
if (qname!=null && infofield!=null) infofield.setText(getInfoString());
if (rect!=null) updateRect(rect,getInfo());
}
protected void updateRect(VisibleRect rect, MandelInfo info)
{
getMandelWindowAccess().getMandelImagePane().updateRect(rect,info);
}
synchronized public void updateInfo(MandelInfo info, Rectangle rect)
{
getMandelWindowAccess().getMandelImagePane().updateInfo(info, rect);
}
class UpdateListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent evt)
{ // only way to assure listener order
// cannot add value acces listener as property change listener
ValueAccess acc=fields.get((DataField)evt.getSource());
if (acc!=null) acc.propertyChange(evt);
if (!inupdate) {
setAltSpec();
}
updateSlave();
}
}
}
public static String getInfoString(ToolEnvironment env,
QualifiedMandelName qname)
{
if (qname==null) return "";
StringBuilder sb=new StringBuilder();
if (env.getFavorites()!=null) {
if (contains(env.getFavorites().getRoot(), qname.getMandelName())) {
sb.append("Favorite");
}
}
if (env.getTodos()!=null) {
if (contains(env.getTodos().getRoot(), qname.getMandelName())) {
if (sb.length()!=0) sb.append(", ");
sb.append("Todo");
}
}
if (env.getAreas()!=null) {
if (contains(env.getAreas(), qname.getMandelName())) {
if (sb.length()!=0) sb.append(", ");
sb.append("Key area");
}
}
return sb.toString();
}
static private boolean contains(MandelList list, MandelName name)
{
for (QualifiedMandelName n:list) {
if (name.equals(n.getMandelName())) return true;
}
return false;
}
static private boolean contains(MandelListFolder folder, MandelName name)
{
for (QualifiedMandelName n:folder.allentries()) {
if (name.equals(n.getMandelName())) return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//// Dialog
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
private MandelAreaView view;
public MandelAreaViewDialog(MandelWindowAccess owner, String title)
{
super(owner, title);
}
public MandelAreaViewDialog(MandelWindowAccess owner, String title,
QualifiedMandelName name, Object info,
boolean change, boolean readonly)
{
super(owner, title);
setup(name, info, change, readonly);
setVisible(true);
}
public MandelAreaViewDialog(MandelWindowAccess owner, String title,
boolean change, boolean readonly)
{
super(owner, title);
setup(null, new MandelInfo(), change, readonly);
}
protected void setup(QualifiedMandelName name, Object info,
boolean change, boolean readonly)
{
view=createView(name, info, change, readonly);
add(view);
pack();
this.setResizable(false);
}
protected MandelAreaView createView(QualifiedMandelName name, Object info,
boolean change, boolean readonly)
{
return new MandelAreaView(name, (MandelInfo)info, change, readonly);
}
protected MandelAreaView getView()
{
return view;
}
protected JDialog getDialog()
{
return this;
}
public void setInfo(String name, MandelInfo info)
{
getView().setInfo(name, info);
}
public void setData(String name, MandelData data)
{
getView().setData(name, data);
}
public MandelInfo getInfo()
{
return getView().getInfo();
}
///////////////////////////////////////////////////////////////////////
// utilitiy classes
///////////////////////////////////////////////////////////////////////
static protected abstract class ValueAccess<T>
implements ActionListener, PropertyChangeListener {
public void actionPerformed(ActionEvent e)
{
//System.out.println("action "+e);
DataField<T> f=(DataField<T>)e.getSource();
setValue(f.getDataValue());
}
public void propertyChange(PropertyChangeEvent e)
{
String propertyName=e.getPropertyName();
System.out.println("CHANGE "+propertyName+"="+e.getOldValue()+
"->"+e.getNewValue());
setValue((T)e.getNewValue());
}
public abstract void setValue(T n);
public abstract T getValue();
}
protected static class BeanAccess<T> extends ValueAccess<T> {
private String prop;
private Object bean;
private Method setter;
private Method getter;
private Class type;
public BeanAccess(Object bean, String prop)
{
this.bean=bean;
this.prop=prop;
String mname="get"+prop;
for (Method m:bean.getClass().getMethods()) {
if (m.getName().equals(mname)) {
Class<?>[] params=m.getParameterTypes();
if (params==null||params.length==0) {
Class<?> r=mapType(m.getReturnType());
if (r!=null&&(Number.class.isAssignableFrom(r)||
String.class.isAssignableFrom(r))) {
getter=m;
type=r;
}
}
}
}
mname="set"+prop;
if (type!=null) {
//System.out.println("trying to get setter for "+prop+": "+type);
try {
Method m=bean.getClass().getMethod(mname, new Class[]{type});
setter=checkSetter(m);
}
catch (Exception ex) {
// ignore
}
}
if (setter==null) {
for (Method m:bean.getClass().getMethods()) {
if (m.getName().equals(mname)) {
//System.out.println("checking "+m);
setter=checkSetter(m);
if (setter!=null) break;
}
}
}
}
public String getProperty()
{ return prop;
}
protected Method checkSetter(Method m)
{
//System.out.println("checking "+m);
Class<?>[] params=m.getParameterTypes();
if (params!=null) {
if (params.length==1) {
if (params[0]==type) return m;
Class t=mapType(params[0]);
if (Number.class.isAssignableFrom(t)) {
if (type==null) {
type=t;
}
return m;
}
else {
//System.out.println("no assignment "+params[0]+"/"+t);
}
}
}
return null;
}
private Class mapType(Class t)
{
if (t==double.class) {
t=Double.class;
}
else if (t==float.class) {
t=Float.class;
}
else if (t==long.class) {
t=Long.class;
}
else if (t==int.class) {
t=Integer.class;
}
else if (t==short.class) {
t=Short.class;
}
else if (t==byte.class) {
t=Byte.class;
}
return t;
}
public Class getType()
{
return type;
}
public boolean hasGetter()
{
return getter!=null;
}
public boolean hasSetter()
{
return setter!=null;
}
public void setValue(T n)
{
System.out.println("setting "+prop+" to "+n);
if (setter==null) {
throw new IllegalArgumentException("no setter for property "+prop);
}
try {
setter.invoke(bean,
new Object[]{Utils.convertValueToValueClass(n, type)});
}
catch (Exception ex) {
throw new IllegalArgumentException("cannot access property "+setter);
}
}
public T getValue()
{
if (getter==null) {
throw new IllegalArgumentException("no getter for property "+prop);
}
try {
return (T)getter.invoke(bean, new Object[]{});
}
catch (Exception ex) {
throw new IllegalArgumentException("cannot access property "+setter);
}
}
}
}