/*
* This file is part of MoleculeViewer.
*
* MoleculeViewer 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 3 of the License, or
* (at your option) any later version.
*
* MoleculeViewer 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 MoleculeViewer. If not, see <http://www.gnu.org/licenses/>.
*/
package astex.thinlet;
import astex.*;
import astex.generic.*;
import thinlet.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
public class ThinletUI extends Thinlet implements WindowListener,
MoleculeRendererListener,
RendererEventListener {
public MoleculeViewer moleculeViewer = null;
public MoleculeRenderer moleculeRenderer = null;
private HashSet<String> initialised = new HashSet<String>(11);
/** Main entry point for scripting execution via thinlet. */
private void execute(Object component){
String init = (String)getProperty(component, "init");
if(init != null && !initialised.contains(init)){
initialised.add(init);
init = preprocess(component, init);
execute(init);
}
final String commands[] = { "precommand", "command", "postcommand" };
for(int i = 0; i < commands.length; i++){
String command = (String)getProperty(component, commands[i]);
if(command == null && !"table".equals(getClass(component))){
if("checkbox".equals(getClass(component))){
final boolean selected = getBoolean(component, "selected");
if(selected){
command = (String)getProperty(component, "commandon");
}else{
command = (String)getProperty(component, "commandoff");
}
}else if("tabbedpane".equals(getClass(component))){
component = getSelectedItem(component);
if(getProperty(component, "command") != null){
execute(component);
}
return;
// this next block was commented out - why?
// must have caused some other effect, restrict it to a textarea
}else if("textarea".equals(getClass(component))){
command = getString(component, "text");
}
}else if(command != null && ("table".equals(getClass(component)) || "tree".equals(getClass(component))) && i == 1){
// only preprocess the pertable command
final Object rows[] = getSelectedItems(component);
final StringBuilder wholeCommand = new StringBuilder();
for(int r = 0; r < rows.length; r++){
wholeCommand.append(preprocess(rows[r], command));
}
// do this per row...
command = wholeCommand.toString();
}
if(command != null){
command = preprocess(component, command);
execute(command);
}else{
if(i == 1){
Log.error("no command or text to execute");
}
}
}
}
/** Actually execute a command and repaint. */
private void execute(String command){
if(!command.endsWith(";")){
command += ";";
}
moleculeRenderer.execute(command);
moleculeViewer.dirtyRepaint();
}
/**
* get the cell from the selected row that has
* the specified columnName property.
*/
private String getCellValueWithName(Object table, final String name){
Object row = null;
if("row".equals(getClass(table))){
// it was a row really...
row = table;
table = getParent(row);
}
final Object header = getWidget(table, "header");
int columnId = -1;
for(int i = 0; i < getCount(header); i++){
final Object column = getItem(header, i);
final String text = getString(column, "text");
if(text != null && text.equalsIgnoreCase(name)){
columnId = i;
break;
}
}
if(columnId == -1){
System.out.println("ERROR: table has no column called " + name);
}
if(row == null){
// the original object was a table so we need to get the row
// XXX will this ever actually get called this way?
row = getSelectedItem(table);
}
final Object cell = getItem(row, columnId);
return getString(cell, "text");
}
private String getValue(Object component, final String s){
if("table".equals(getClass(component)) || "row".equals(getClass(component))){
final String field = s.substring(1);
final String value = getCellValueWithName(component, field);
return value;
}
if("$o".equals(s)){
return getBoolean(component, "selected") ? "on" : "off";
}
if("$b".equals(s)){
return getBoolean(component, "selected") ? "true" : "false";
}
if("$t".equals(s)){
return getString(component, "text");
}
if("$d".equals(s)){
return Integer.toString(getInteger(component, "value"));
}
if("$V".equals(s)){
return Double.toString(getInteger(component, "value") * 0.1);
}
if("$v".equals(s)){
return Double.toString(getInteger(component, "value") * 0.01);
}
if("$c".equals(s)){
final Color color = getColor(component, "background");
return Color32.format(color.getRGB());
}
if("$C".equals(s)){
final Color color = getColor(component, "background");
return Color32.formatNoQuotes(color.getRGB());
}
if("$f".equals(s)){
final Color color = getColor(component, "foreground");
return Color32.formatNoQuotes(color.getRGB());
}
if("$n".equals(s)){
if("combobox".equals(getClass(component))){
component = getSelectedItem(component);
}
return getString(component, "name");
}
if("$h".equals(s)){
double hsv[] = { 0.0, 1.0, 1.0 };
hsv[0] = (double) getInteger(component, "value");
final int c = Color32.hsv2packed(hsv);
return Color32.format(c);
}
final String property = (String)getProperty(component, s.substring(1));
if(property != null){
return property;
}
System.out.println("invalid attribute " + s);
return null;
}
private String preprocess(final Object component, final String origCommand){
return preprocess(component, origCommand, true);
}
private String preprocess(final Object component, final String origCommand, final boolean replacePipe){
String command = origCommand;
if(replacePipe){
command = origCommand.replace('|', ';');
}
final StringBuilder newCommand = new StringBuilder(origCommand.length());
try {
// now do the values from other objects.
final int len = command.length();
for(int i = 0; i < len; i++){
if(command.charAt(i) == '$'){
Object comp = component;
String attribute = null;
if(command.charAt(++i) == '{'){
final StringBuilder sb = new StringBuilder(16);
while(command.charAt(++i) != '}'){
sb.append(command.charAt(i));
}
final String bits[] = FILE.split(sb.toString(), ".");
if(bits.length != 2){
System.out.println("no . character");
System.out.println(origCommand);
}else{
// this should cache the object name...
comp = findComponent(bits[0]);
attribute = "$" + bits[1];
if(comp == null){
System.out.println("couldn't find object " + bits[0]);
}
}
}else{
attribute = "$" + command.charAt(i);
}
String value = null;
if(comp == getParent(component)){
// if the named table was our parent then
// we just go for the row instead
value = getValue(component, attribute);
}else if("tree".equals(getClass(comp))){
value = getValue(getSelectedItem(comp), attribute);
}else{
value = getValue(comp, attribute);
}
if(value == null){
// just append what was there...
newCommand.append(attribute);
}else{
newCommand.append(value);
}
}else{
newCommand.append(command.charAt(i));
}
}
}catch(Exception e){
System.out.println("error processing command: " + origCommand);
System.out.println("exception " + e);
return null;
}
return newCommand.toString();
}
public void windowClosing(final WindowEvent e){
close(e.getWindow());
}
public void windowActivated(final WindowEvent e){ }
public void windowClosed(final WindowEvent e){ }
public void windowDeactivated(final WindowEvent e){ }
public void windowDeiconified(final WindowEvent e){ }
public void windowIconified(final WindowEvent e){ }
public void windowOpened(final WindowEvent e){ }
private void close(final Window window){
window.setVisible(false);
window.dispose();
}
public boolean destroy(){
return false;
}
private void setContent(final String xml){
try{
add(parse(xml));
}catch(Exception e){
System.out.println("Exception: " + e);
}
}
public ThinletUI(final MoleculeViewer mv, final String xml){
this(mv);
setContent(xml);
}
private ThinletUI(final MoleculeViewer mv){
System.out.println("Thinlet GUI toolkit - www.thinlet.com");
System.out.println("Copyright (C) 2002-2003 Robert Bajzat (robert.bajzat@thinlet.com)");
moleculeViewer = mv;
moleculeRenderer = mv.getMoleculeRenderer();
moleculeRenderer.addMoleculeRendererListener(this);
moleculeRenderer.renderer.addRendererEventListener(this);
setFont(new Font("Arial", Font.PLAIN, 12));
}
public String readTemplate(final String xmlFile) {
try {
final FILE f = FILE.open(xmlFile);
final InputStream fis = f.getInputStream();
final StringBuilder sb = new StringBuilder(1024);
int c = 0;
while((c = fis.read()) != -1){
sb.append((char)c);
}
fis.close();
return sb.toString();
}catch(Exception e){
System.out.println("exception opening template " + e);
return null;
}
}
/** MoleculeRendererListener interface. */
/** A molecule was added. */
public void moleculeAdded(final MoleculeRenderer renderer, final Molecule molecule){
final Object moleculeTree = findComponent("molecule_tree");
addMolecule(moleculeTree, molecule);
if(molecule.getMoleculeType() != Molecule.SymmetryMolecule){
final Object resnode = findComponent("residuelist");
final Object atomnode = findComponent("atomlist");
if(resnode != null){
populateResidues(resnode, atomnode);
}
}
}
private void addMolecule(final Object tree, final Molecule mol){
if(tree != null){
final Object node = createNode(mol.getName(), false, mol);
final int chainCount = mol.getChainCount();
for(int c = 0; c < chainCount; c++){
final Chain chain = mol.getChain(c);
String name = chain.getName();
name = name.replace(' ', 'X');
final Object chainNode = createNode(name, false, chain);
final Object dummyNode = createNode("dummy", false, null);
add(chainNode, dummyNode);
add(node, chainNode);
}
add(tree, node);
repaint();
}else{
System.out.println("couldn't find molecule tree");
}
}
/** A molecule was removed. */
public void moleculeRemoved(final MoleculeRenderer renderer, final Molecule molecule){
removeMolecule(molecule);
}
private void removeMolecule(final Molecule molecule){
final Object moleculeTree = find("molecule_tree");
final Object molNode = find(moleculeTree, molecule.getName());
remove(molNode);
if(molecule.getMoleculeType() != Molecule.SymmetryMolecule){
final Object resnode = findComponent("residuelist");
final Object atomnode = findComponent("atomlist");
if(resnode != null){
populateResidues(resnode, atomnode);
}
}
}
private void populateResidues(final Object resnode, final Object atomnode){
removeAll(resnode);
removeAll(atomnode);
final TreeSet<String> resnames = new TreeSet<String>();
final TreeSet<String> atomnames = new TreeSet<String>();
for(int m = 0; m < moleculeRenderer.getMoleculeCount(); m++){
final Molecule mol = moleculeRenderer.getMolecule(m);
for(int c = 0; c < mol.getChainCount(); c++){
final Chain chain = mol.getChain(c);
for(int r = 0; r < chain.getResidueCount(); r++){
final Residue res = chain.getResidue(r);
final String resname = res.getName();
if(!resnames.contains(resname)){
resnames.add(resname);
}
for(int a = 0; a < res.getAtomCount(); a++){
final Atom atom = res.getAtom(a);
final String atomname = atom.getAtomLabel();
if(!atomnames.contains(atomname)){
atomnames.add(atomname);
}
}
}
}
}
// residue names
char lastChar = 0;
Object folder = null;
for(String name: resnames){
final char c = name.charAt(0);
if(c != lastChar){
folder = create("node");
setString(folder, "text", String.valueOf(c));
setBoolean(folder, "expanded", false);
add(resnode, folder);
lastChar = c;
}
final Object node = create("node");
setString(node, "text", name);
putProperty(node, "selection", "name '" + name + "'");
add(folder, node);
}
// atom names
lastChar = 0;
folder = null;
for(String name: atomnames){
final char c = name.charAt(0);
if(c != lastChar){
folder = create("node");
setString(folder, "text", String.valueOf(c));
setBoolean(folder, "expanded", false);
add(atomnode, folder);
lastChar = c;
}
final Object node = create("node");
setString(node, "text", name);
putProperty(node, "selection", "atom '" + name + "'");
add(folder, node);
}
}
public void genericAdded(final MoleculeRenderer renderer, final Generic generic){
System.out.println("generic added " + generic);
if(generic instanceof Distance){
final Object list = findComponent("distance_list");
addDistance(list, (Distance)generic);
}
}
private void addDistance(final Object list, final Distance distance){
if(list == null){
return;
}
final String name = distance.getString(Generic.Name, "generic");
final String itemString = "<item name=\"" + name + "\" text=\"" + name + "\"/>";
final Object item = safeParse(itemString);
putProperty(item, "reference", distance);
add(list, item);
}
private String getColorString(final int rgb){
return "#" + Integer.toHexString(rgb|0xff000000).substring(2);
}
private String getColorString2(final int rgb){
return "0x" + Integer.toHexString(rgb|0xff000000).substring(2);
}
public void genericRemoved(final MoleculeRenderer renderer, final Generic generic){
System.out.println("generic removed " + generic);
final Object list = findComponent("distance_list");
final Object item = find(list, (String)generic.get(Generic.Name, "generic"));
remove(item);
}
/** A map was added. */
public void mapAdded(final MoleculeRenderer renderer, final astex.Map map){
System.out.println("mapAdded " + map);
final Object mapComponent = findComponent("map_panel");
addMap(mapComponent, map);
}
/** A map was removed. */
public void mapRemoved(final MoleculeRenderer renderer, final astex.Map map){
System.out.println("mapRemoved " + map);
final Object mapComponent = findComponent("map_panel");
final Object mapObject = find(mapComponent, map.getName());
remove(mapObject);
addMap(mapComponent, map);
}
private void addMap(final Object component, final astex.Map map){
String mapTemplate = readTemplate("/astex/thinlet/maptemplate.xml.properties");
for(int i = 0; i < astex.Map.MaximumContourLevels; i++){
String contourTemplate = readTemplate("/astex/thinlet/contourtemplate.xml.properties");
final int color = map.getContourColor(i);
final String scolor = getColorString(color);
contourTemplate = Util.replace(contourTemplate, "%contour", Integer.toString(i));
contourTemplate = Util.replace(contourTemplate, "%color" + i, scolor);
contourTemplate = Util.replace(contourTemplate, "%display" + i, Boolean.toString(map.getContourDisplayed(i)));
String solid = "false";
if(map.getContourStyle(i) == astex.Map.Surface){
solid = "true";
}
contourTemplate = Util.replace(contourTemplate, "%solid" + i, solid);
contourTemplate = Util.replace(contourTemplate, "%level" + i, Double.toString(map.getContourLevel(i)));
final Tmesh contourObject =
moleculeRenderer.getContourGraphicalObject(map, i);
contourTemplate = Util.replace(contourTemplate, "%width" + i, Double.toString(contourObject.getLineWidth()));
mapTemplate = Util.replace(mapTemplate, "%c" + i, contourTemplate);
}
mapTemplate = Util.replace(mapTemplate, "%n", map.getName());
// stupid, stupid, stupid, needs doing in loop above
mapTemplate = Util.replace(mapTemplate, "%max", Double.toString(Math.max(map.getContourLevel(0), map.getContourLevel(1))));
mapTemplate = Util.replace(mapTemplate, "%min", Double.toString(Math.min(map.getContourLevel(0), map.getContourLevel(1))));
mapTemplate = Util.replace(mapTemplate, "%level2", Double.toString(map.getContourLevel(2)));
mapTemplate = Util.replace(mapTemplate, "%color0", getColorString2(map.getContourColor(0)));
mapTemplate = Util.replace(mapTemplate, "%color1", getColorString2(map.getContourColor(1)));
mapTemplate = Util.replace(mapTemplate, "%color2", getColorString2(map.getContourColor(2)));
add(component, safeParse(mapTemplate));
}
/** An atom was selected. */
public void atomSelected(final MoleculeRenderer renderer, final Atom atom){
if(atom == null){
final Object moleculeTree = findComponent("molecule_tree");
final Object items[] = getSelectedItems(moleculeTree);
for(Object item: items){
setBoolean(item, "selected", false);
}
repaint();
}
}
public boolean handleRendererEvent(final RendererEvent re){
if(re.getType() == RendererEvent.Type.ObjectAdded){
final Tmesh tmesh = (Tmesh)re.getItem();
final Object objectList = findComponent("object_list");
addObject(objectList, tmesh);
}else if(re.getType() == RendererEvent.Type.ObjectRemoved){
final Tmesh tmesh = (Tmesh)re.getItem();
remove(find(tmesh.getName()));
}else if(re.getType() == RendererEvent.Type.FrontClipMoved){
final Double d = (Double)re.getItem();
if(d != null){
final double val = d.doubleValue();
final Object clip = findComponent("frontclip");
if(clip != null){
setString(clip, "text", String.format("%.1f", val));
}
}
}else if(re.getType() == RendererEvent.Type.BackClipMoved){
final Double d = (Double)re.getItem();
if(d != null){
final double val = d.doubleValue();
final Object clip = findComponent("backclip");
if(clip != null){
setString(clip, "text", String.format("%.1f", val));
}
}
}
return true;
}
private void addObject(final Object component, final Tmesh object){
if(component != null){
String objectString =
readTemplate("/astex/thinlet/objecttemplate.xml.properties");
objectString = Util.replace(objectString, "%n", object.getName());
objectString = Util.replace(objectString, "%c", Color32.formatNoQuotes(object.getColor()));
add(component, safeParse(objectString));
}
}
private Object createNode(final String name, final boolean expanded, final Object ref){
final String nodeString =
"<node text=\"" + name + "\" name=\"" + name + "\" expanded=\"" + expanded + "\" font=\"courier\"/>";
final Object node = safeParse(nodeString);
putProperty(node, "reference", ref);
return node;
}
public Object safeParse(final String xml){
try {
return parse(new StringReader(xml));
}catch(Exception e){
System.out.println("error parsing xml: " + xml);
e.printStackTrace();
return null;
}
}
public ThinletUI(){
}
/** Hashtable of component names to objects. */
private HashMap<String, Object> components = null;
/**
* Look up a cached object name.
* Don't use this if the object may change.
*/
private Object findComponent(final String name){
if(components == null){
components = new HashMap<String, Object>(11);
}
Object component = components.get(name);
if(component == null){
component = shallowFind(name);
if(component == null){
find(name);
}
if(component == null){
System.out.println("WARNING: couldn't find thinlet component " + name);
}else{
components.put(name, component);
}
}
return component;
}
}