package org.chesmapper.view.gui;
import java.awt.Color;
import java.awt.Dimension;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;
import org.chesmapper.map.main.PropHandler;
import org.chesmapper.map.main.Settings;
import org.chesmapper.view.cluster.ClusteringImpl;
import org.chesmapper.view.cluster.Compound;
import org.chesmapper.view.gui.MainPanel.JmolPanel;
import org.jmol.export.dialog.Dialog;
import org.jmol.viewer.Viewer;
import org.mg.javalib.gui.ResolutionPanel;
import org.mg.javalib.util.DoubleArraySummary;
import org.mg.javalib.util.FileUtil;
import org.mg.javalib.util.SwingUtil;
import org.mg.javalib.util.Vector3fUtil;
public class View
{
private Viewer viewer;
GUIControler guiControler;
public static View instance;
ViewControler viewControler;
private ClusteringImpl clustering;
public boolean antialiasOn = false;
private static Zoomable zoomedTo;
public static enum AnimationSpeed
{
SLOW, FAST
}
private View(Viewer viewer, GUIControler guiControler, ViewControler viewControler, final ClusteringImpl clustering)
{
this.viewer = viewer;
this.guiControler = guiControler;
this.viewControler = viewControler;
this.clustering = clustering;
clustering.addListener(new PropertyChangeListener()
{
@Override
public void propertyChange(PropertyChangeEvent evt)
{
if (evt.getPropertyName().equals(ClusteringImpl.CLUSTER_REMOVED)
|| evt.getPropertyName().equals(ClusteringImpl.CLUSTER_MODIFIED)
|| evt.getPropertyName().equals(ClusteringImpl.CLUSTER_CLEAR))
{
medianDiameter = null;
for (Compound m : spheresForCompound)
if (!clustering.getCompounds(true).contains(m))
hideSphere(m);
}
}
});
viewer.script("set disablePopupMenu on");
viewer.script("set minPixelSelRadius 30");
setAntialiasOn(viewControler.isAntialiasEnabled());
hideHydrogens(viewControler.isHideHydrogens());
}
public static View init(JmolPanel jmolPanel, GUIControler guiControler, ViewControler viewControler,
ClusteringImpl clustering)
{
instance = new View((Viewer) jmolPanel.getViewer(), guiControler, viewControler, clustering);
return instance;
}
public synchronized void setAntialiasOn(boolean antialias)
{
this.antialiasOn = antialias;
if (antialias)
viewer.script("set antialiasDisplay ON");
else
viewer.script("set antialiasDisplay OFF");
}
public boolean isAntialiasOn()
{
return antialiasOn;
}
public synchronized void setSpinEnabled(boolean spinEnabled, int speed)
{
if (spinEnabled)
{
System.out.println("spinning at " + speed);
viewer.evalString("set spinx 0");
viewer.evalString("set spiny " + speed);
viewer.evalString("set spinz 0");
viewer.evalString("spin on");
}
else
{
viewer.evalString("spin off");
}
}
public synchronized void centerAt(Zoomable zoomable)
{
zoomTo(zoomable, null, null, true);
}
public synchronized void zoomTo(Zoomable zoomable, AnimationSpeed speed)
{
zoomTo(zoomable, speed, null);
}
private void checkNoAWT()
{
SwingUtil.checkNoAWTEventThread();
if (guiControler.isVisible() && !guiControler.isBlocked())
throw new IllegalStateException("animation running, gui should be blocked");
}
public synchronized void zoomTo(final Zoomable zoomable, final AnimationSpeed speed, Boolean superimposed)
{
zoomTo(zoomable, speed, superimposed, false);
}
private synchronized void zoomTo(final Zoomable zoomable, final AnimationSpeed speed, Boolean superimposed,
boolean onlyCentering)
{
if (superimposed == null)
superimposed = zoomable.isSuperimposed();
final float diameter = Math.max(5.0f, zoomable.getDiameter(superimposed));
final Vector3f center = zoomable.getCenter(superimposed);
// Settings.LOGGER.warn("zoom to " + zoomable);
// Settings.LOGGER.warn("Superimposed " + superimposed);
// Settings.LOGGER.warn("Center " + center);
// Settings.LOGGER.warn("Diameter " + diameter);
// Settings.LOGGER.warn("Rot radius " + viewer.getRotationRadius());
// old way of zooming
// int zoom = (int) ((1200 / (10 / viewer.getRotationRadius())) / diameter);
// Settings.LOGGER.warn("zoom " + zoom);
// zoom = (int) Math.max(5, zoom);
// Settings.LOGGER.warn("zoom A " + zoom);
// much better, adjust the rotation radius instead
viewer.setRotationRadius(diameter / 10.0f, true);
//Settings.LOGGER.warn("Rot radius A " + viewer.getRotationRadius());
int zoom = 10;
if (isAnimated() && !onlyCentering)
{
checkNoAWT();
final int finalZoom = zoom;
boolean setAntialiasBackOn = false;
if (viewControler.isAntialiasEnabled() && antialiasOn)
{
setAntialiasBackOn = true;
setAntialiasOn(false);
}
String cmd = "zoomto " + (speed == AnimationSpeed.SLOW ? 0.66 : 0.33) + " " + Vector3fUtil.toString(center)
+ " " + finalZoom;
// Settings.LOGGER.warn("XX zoom> " + cmd);
viewer.scriptWait(cmd);
System.out.println("resetting navigtion point");
//viewer.setBooleanProperty("windowCentered", false);
viewer.setBooleanProperty("windowCentered", true);
if (setAntialiasBackOn)
setAntialiasOn(true);
zoomedTo = zoomable;
}
else
{
if (!onlyCentering)
{
String cmd = "zoomto 0 " + Vector3fUtil.toString(center) + " " + zoom;
viewer.scriptWait(cmd);
}
else
{
// cannot use the setNewRotationCenter method
// problem is that it calculates a new setRotationRadius and uses the old zoom factor 10
// viewer.setNewRotationCenter(new Point3f(zoomable.getCenter(superimposed)));
viewer.setRotationCenterWithoutRadius(new Point3f(zoomable.getCenter(superimposed)));
}
zoomedTo = zoomable;
}
}
public Zoomable getZoomTarget()
{
return zoomedTo;
}
public synchronized static String convertColor(Color col)
{
return "[" + col.getRed() + ", " + col.getGreen() + ", " + col.getBlue() + "]";
}
public synchronized static String convertPos(Point3f p)
{
return "{" + p.x + " " + p.y + ", " + p.z + "}";
}
public synchronized void setBackground(Color col)
{
viewer.script("background " + convertColor(col));
}
public synchronized int findNearestAtomIndex(int x, int y)
{
return viewer.findNearestAtomIndexFixed(x, y);
}
public synchronized int getAtomCompoundIndex(int atomIndex)
{
return viewer.getAtomModelIndex(atomIndex);
}
public synchronized void clearSelection()
{
viewer.clearSelection();
}
public synchronized void select(BitSet bitSet)
{
// Settings.LOGGER.warn("XX> selecting bitset with " + bitSet.cardinality() + " atoms, bitset: " + bitSet);
viewer.select(bitSet, false, null, false);
// Settings.LOGGER.warn("XX> " + viewer.getAtomSetCenter(bitSet));
}
private void evalScript(String script)
{
if (script.matches("(?i).*hide.*") || script.matches("(?i).*subset.*") || script.matches("(?i).*display.*"))
throw new Error("use wrap methods");
}
public synchronized void scriptWait(String script)
{
evalScript(script);
// Settings.LOGGER.warn("XX wait> " + script);
viewer.scriptWait(script);
}
public synchronized void selectAll()
{
viewer.scriptWait("select not hidden");
}
public synchronized void hide(BitSet bs)
{
// Settings.LOGGER.warn("XX> hide bitset with " + bs.cardinality() + " atoms, bitset: " + bs);
viewer.select(bs, false, null, false);
hideSelected();
}
public synchronized void hideSelected()
{
// Settings.LOGGER.warn("XX> select selected OR hidden; hide selected");
viewer.scriptWait("select selected OR hidden; hide selected");
}
public synchronized void display(BitSet bs)
{
// Settings.LOGGER.warn("XX> display bitset with " + bs.cardinality() + " atoms, bitset: " + bs);
viewer.select(bs, false, null, false);
viewer.scriptWait("select (not hidden) OR selected; select not selected; hide selected");
}
public synchronized BitSet getCompoundBitSet(int modelIndex)
{
return viewer.getModelUndeletedAtomsBitSet(modelIndex);
}
public synchronized String getCompoundNumberDotted(int i)
{
return viewer.getModelNumberDotted(i);
}
public synchronized Point3f getAtomSetCenter(BitSet bitSet)
{
return viewer.getAtomSetCenter(bitSet);
}
HashSet<Compound> spheresForCompound = new HashSet<Compound>();
public double sphereSize = 0.5;
public double sphereTranslucency = 0.5;
public synchronized void hideSphere(Compound m)
{
if (spheresForCompound.contains(m))
{
String id = "sphere" + m.getJmolIndex();
scriptWait("ellipsoid ID " + id + " color translucent 1.0");
scriptWait("ellipsoid ID " + id + "_2 color translucent 1.0");
}
}
public Float medianDiameter = null;
private synchronized double medianDiameter()
{
if (medianDiameter == null)
{
List<Float> d = new ArrayList<Float>();
for (Compound m : clustering.getCompounds(true))
d.add(m.getDiameter());
medianDiameter = (float) DoubleArraySummary.create(d).getMedian();
}
return medianDiameter;
}
private synchronized void updateSpherePosition(Compound m)
{
if (spheresForCompound.contains(m))
{
String id = "sphere" + m.getJmolIndex();
// BoxInfo info = viewer.getBoxInfo(m.getBitSet(), 1.0F);
// Point3f center = info.getBoundBoxCenter();
// Vector3f corner = info.getBoundBoxCornerVector();
// scriptWait("ellipsoid ID " + id + " AXES {" + Math.max(corner.x * sphereSize, 1.0) + " 0 0} {0 "
// + Math.max(corner.y * sphereSize, 1.0) + " 0} {0 0 " + Math.max(corner.z * sphereSize, 1.0)
// + "}");
// scriptWait("ellipsoid ID " + id + " center " + convertPos(center));
double mSize = medianDiameter() + ((m.getDiameter() - medianDiameter()) * 0.25);
mSize = Math.min(8.0, mSize);
double size = Math.max(1.0, mSize * 0.5 * (0.1 + 0.9 * sphereSize));
scriptWait("ellipsoid ID " + id + " AXES {" + size + " 0 0} {0 " + size + " 0} {0 0 " + size + "}");
scriptWait("ellipsoid ID " + id + " center " + convertPos(getAtomSetCenter(m.getBitSet())));
scriptWait("ellipsoid ID " + id + "_2 AXES {" + size * 1.5 + " 0 0} {0 " + size * 1.5 + " 0} {0 0 " + size
* 0.55 + "}");
scriptWait("ellipsoid ID " + id + "_2 center " + convertPos(getAtomSetCenter(m.getBitSet())));
// Point3f c = getAtomSetCenter(m.getBitSet());
// Point3f p1 = new Point3f(c.x, c.y - (float) size * 1.5f, c.z + (float) size * 1.5f);
// Point3f p2 = new Point3f(c.x, c.y + (float) size * 1.5f, c.z - (float) size * 1.5f);
// Point3f p3 = new Point3f(c.x, c.y, c.z);
// scriptWait("draw ID " + id + "P plane " + convertPos(p1) + " " + convertPos(p2) + " " + convertPos(p3));
}
}
public synchronized void showSphere(Compound m, boolean showLastHighlightColor, boolean updateSizeAndPos)
{
String id = "sphere" + m.getJmolIndex();
if (!spheresForCompound.contains(m) || updateSizeAndPos)
{
spheresForCompound.add(m);
updateSpherePosition(m);
}
double trans = 0.0 + 0.8 * sphereTranslucency;
switch (m.getTranslucency())
{
case ModerateWeak:
trans = 0.2 + 0.65 * sphereTranslucency;
break;
case ModerateStrong:
trans = 0.4 + 0.5 * sphereTranslucency;
break;
case Strong:
trans = 0.6 + 0.35 * sphereTranslucency;
break;
case None:
//do nothing
}
scriptWait("ellipsoid ID " + id + " " + m.getHighlightColorString() + " color translucent " + trans);
if (showLastHighlightColor && m.getLastHighlightColorString() != null)
scriptWait("ellipsoid ID " + id + "_2 " + m.getLastHighlightColorString() + " color translucent "
+ Math.max(0, (trans - 0.1)));
else
scriptWait("ellipsoid ID " + id + "_2 color translucent 1.0");
}
public synchronized void zap(boolean b, boolean c, boolean d)
{
viewer.zap(b, c, d);
}
public synchronized void loadCompoundFromFile(String s, String filename, String s2[], Object reader, boolean b,
Hashtable<String, Object> t, StringBuffer sb, int i)
{
viewer.loadModelFromFile(s, filename, s2, reader, b, t, sb, i);
}
public synchronized int getCompoundCount()
{
return viewer.getModelCount();
}
public synchronized void setAtomCoordRelative(Vector3f c, BitSet bitSet)
{
viewer.setAtomCoordRelative(c, bitSet);
}
public synchronized void setAtomCoordRelative(final List<Vector3f> c, final List<BitSet> bitSet,
final AnimationSpeed overlapAnim)
{
if (isAnimated() && c.size() > 1)
{
checkNoAWT();
int n = (overlapAnim == AnimationSpeed.SLOW) ? 24 : 10;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < bitSet.size(); j++)
{
Vector3f v = new Vector3f(c.get(j));
v.scale(1 / (float) n);
viewer.setAtomCoordRelative(v, bitSet.get(j));
}
viewer.scriptWait("delay 0.01");
}
}
else
{
for (int i = 0; i < bitSet.size(); i++)
viewer.setAtomCoordRelative(c.get(i), bitSet.get(i));
}
}
public synchronized void setAtomProperty(BitSet bitSet, int temperature, int v, float v2, String string, float f[],
String s[])
{
viewer.setAtomProperty(bitSet, temperature, v, v2, string, f, s);
}
public synchronized int getAtomCountInCompound(int index)
{
return viewer.getAtomCountInModel(index);
}
public synchronized BitSet getSmartsMatch(String smarts, BitSet bitSet)
{
BitSet b = viewer.getSmartsMatch(smarts, bitSet);
if (b == null)
{
Settings.LOGGER.warn("jmol did not like: " + smarts + " " + bitSet);
return new BitSet();
}
else
return b;
}
HashSet<String> animSuspend = new HashSet<String>();
public synchronized void suspendAnimation(String key)
{
if (animSuspend.contains(key))
throw new Error("already suspended animation for: " + key);
animSuspend.add(key);
}
public synchronized void proceedAnimation(String key)
{
if (!animSuspend.contains(key))
throw new Error("use suspend first for " + key);
animSuspend.remove(key);
}
public synchronized boolean isAnimated()
{
return guiControler.isVisible() && animSuspend.size() == 0;
}
public synchronized void hideHydrogens(boolean b)
{
scriptWait("set showHydrogens " + (b ? "FALSE" : "TRUE"));
}
public float getDiameter(BitSet bitSet)
{
List<Vector3f> points = new ArrayList<Vector3f>();
for (int i = 0; i < bitSet.size(); i++)
if (bitSet.get(i))
points.add(new Vector3f(viewer.getAtomPoint3f(i)));
Vector3f[] a = new Vector3f[points.size()];
return Vector3fUtil.maxDist(points.toArray(a));
}
// public List<Vector3f> getCenterAndAxes(BitSet bitSet)
// {
// BoxInfo info = viewer.getBoxInfo(bitSet, 1.0F);
// Point3f center = info.getBoundBoxCenter();
// Vector3f corner = info.getBoundBoxCornerVector();
//
//// System.out.println(info);
//// System.out.println(corner);
//// System.out.println(ArrayUtil.toString(info.getBboxVertices()));
//
// return null;
// }
HashMap<Dimension, Dimension> cachedResolutions = new HashMap<Dimension, Dimension>();
/**
* Copied from org.openscience.jmol.app.jmolpanel.JmolPanel
*/
public void exportImage()
{
Dimension resScreen = new Dimension(viewer.getScreenWidth(), viewer.getScreenHeight());
Dimension resCached = cachedResolutions.get(resScreen);
if (resCached == null)
resCached = resScreen;
Dimension resSelected = ResolutionPanel.getResuloution(Settings.TOP_LEVEL_FRAME, "Select Image Resolution",
(int) resCached.getWidth(), (int) resCached.getHeight());
if (resSelected == null)
return;
cachedResolutions.put(resScreen, resSelected);
int qualityJPG = -1;
int qualityPNG = -1;
String imageType = null;
String[] imageChoices = { "JPEG", "PNG", "GIF", "PPM", "PDF" };
String[] imageExtensions = { "jpg", "png", "gif", "ppm", "pdf" };
Dialog sd = new Dialog();
String dir = PropHandler.get("image-export-dir");
if (dir == null)
dir = System.getProperty("user.home");
String name = dir + File.separator + "ches-mapper-image.jpg";
String fileName = sd.getImageFileNameFromDialog(viewer, name, imageType, imageChoices, imageExtensions,
qualityJPG, qualityPNG);
if (fileName == null)
return;
PropHandler.put("image-export-dir", FileUtil.getParent(fileName));
PropHandler.storeProperties();
qualityJPG = sd.getQuality("JPG");
qualityPNG = sd.getQuality("PNG");
String sType = imageType = sd.getType();
if (sType == null)
{
// file type changer was not touched
sType = fileName;
int i = sType.lastIndexOf(".");
if (i < 0)
return; // make no assumptions - require a type by extension
sType = sType.substring(i + 1).toUpperCase();
}
Settings.LOGGER.info((String) viewer.createImage(fileName, sType, null, sd.getQuality(sType),
resSelected.width, resSelected.height));
}
private int getFirstCarbonAtom(BitSet bs)
{
//System.out.println(empty.cardinality());
int firstAtom = -1;
int firstCarbon = -1;
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1))
{
//System.out.println(i + " " + viewer.getAtomInfo(i));
if (firstAtom == -1)
firstAtom = i;
if (viewer.getAtomInfo(i).matches("^C[0-9].*"))
{
firstCarbon = i;
break;
}
}
if (firstCarbon == -1)
firstCarbon = firstAtom;
// System.out.println("atom to select: " + viewer.getAtomInfo(firstCarbon));
return firstCarbon;
}
public BitSet getDotModeHideBitSet(BitSet bs)
{
BitSet set = new BitSet(bs.length());
set.or(bs);
set.clear(getFirstCarbonAtom(bs));
return set;
}
public BitSet getDotModeDisplayBitSet(BitSet bs)
{
BitSet set = new BitSet(bs.length());
set.set(getFirstCarbonAtom(bs));
return set;
}
public synchronized void selectFirstCarbonAtom(BitSet bs)
{
BitSet sel = new BitSet(bs.length());
sel.set(getFirstCarbonAtom(bs));
select(sel);
}
}