001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 * Created on Oct 6, 2009
021 * Author: Andreas Prlic
022 *
023 */
024
025package org.biojava.nbio.structure.align.gui.jmol;
026
027import java.awt.Color;
028import java.awt.Dimension;
029import java.awt.Graphics;
030import java.awt.Rectangle;
031import java.awt.event.ActionEvent;
032import java.awt.event.ActionListener;
033import java.io.BufferedInputStream;
034import java.io.IOException;
035import java.io.InputStream;
036import java.io.PipedInputStream;
037import java.io.PipedOutputStream;
038import java.text.DecimalFormat;
039import java.util.List;
040
041import javax.swing.JComboBox;
042
043import org.biojava.nbio.structure.Atom;
044import org.biojava.nbio.structure.Calc;
045import org.biojava.nbio.structure.Group;
046import org.biojava.nbio.structure.Structure;
047import org.biojava.nbio.structure.StructureTools;
048import org.biojava.nbio.structure.align.gui.JPrintPanel;
049import org.biojava.nbio.structure.domain.LocalProteinDomainParser;
050import org.biojava.nbio.structure.domain.pdp.Domain;
051import org.biojava.nbio.structure.domain.pdp.Segment;
052import org.biojava.nbio.structure.gui.util.color.ColorUtils;
053import org.biojava.nbio.structure.io.mmtf.MmtfActions;
054import org.biojava.nbio.structure.jama.Matrix;
055import org.biojava.nbio.structure.scop.ScopDatabase;
056import org.biojava.nbio.structure.scop.ScopDomain;
057import org.biojava.nbio.structure.scop.ScopInstallation;
058import org.jmol.adapter.smarter.SmarterJmolAdapter;
059import org.jmol.api.JmolAdapter;
060import org.jmol.api.JmolStatusListener;
061import org.jmol.api.JmolViewer;
062import org.jmol.util.LoggerInterface;
063import org.slf4j.Logger;
064import org.slf4j.LoggerFactory;
065
066
067public class JmolPanel
068extends JPrintPanel
069implements ActionListener
070{
071        private static final Logger logger = LoggerFactory.getLogger(JmolPanel.class);
072
073        private static final long serialVersionUID = -3661941083797644242L;
074
075        private JmolViewer viewer;
076        private JmolAdapter adapter;
077        JmolStatusListener statusListener;
078        final Dimension currentSize = new Dimension();
079        final Rectangle rectClip = new Rectangle();
080
081        Structure structure;
082
083        public JmolPanel() {
084                super();
085                statusListener = new MyJmolStatusListener();
086                adapter = new SmarterJmolAdapter();
087                JmolLoggerAdapter jmolLogger = new JmolLoggerAdapter(LoggerFactory.getLogger(org.jmol.api.JmolViewer.class));
088                org.jmol.util.Logger.setLogger(jmolLogger);
089                org.jmol.util.Logger.setLogLevel( jmolLogger.getLogLevel() );
090                viewer = JmolViewer.allocateViewer(this,
091                                adapter,
092                                null,null,null,null,
093                                statusListener);
094
095        }
096
097        @Override
098        public void paint(Graphics g) {
099
100                getSize(currentSize);
101                g.getClipBounds(rectClip);
102                viewer.renderScreenImage(g, currentSize, rectClip);
103        }
104
105        public void evalString(String rasmolScript){
106
107                viewer.evalString(rasmolScript);
108
109        }
110
111        public void openStringInline(String pdbFile){
112                viewer.openStringInline(pdbFile);
113
114        }
115        public JmolViewer getViewer() {
116                return viewer;
117        }
118
119        public JmolAdapter getAdapter(){
120                return adapter;
121        }
122
123        public JmolStatusListener getStatusListener(){
124                return statusListener;
125        }
126        public void executeCmd(String rasmolScript) {
127                viewer.evalString(rasmolScript);
128        }
129
130        public void setStructure(final Structure s, boolean useMmtf) {
131
132                this.structure = s;
133
134                if (useMmtf) {
135                        try (
136                                        PipedOutputStream out = new PipedOutputStream();
137                                        // Viewer requires a BufferedInputStream for reflection
138                                        InputStream in = new BufferedInputStream(new PipedInputStream(out));
139                                        ) {
140                                new Thread((Runnable)() -> {
141                                        try {
142                                                MmtfActions.writeToOutputStream(s,out);
143                                        } catch (Exception e) {
144                                                logger.error("Error generating MMTF output for {}",
145                                                                s.getStructureIdentifier()==null ? s.getStructureIdentifier().getIdentifier() : s.getName(), e);
146                                        }
147                                }).start();
148                                viewer.openReader(null, in);
149                        } catch (IOException e) {
150                                logger.error("Error transfering {} to Jmol",
151                                                s.getStructureIdentifier()==null ? s.getStructureIdentifier().getIdentifier() : s.getName(), e);
152                        }
153                } else {
154                        // Use mmCIF format
155                        String serialized = s.toMMCIF();
156                        viewer.openStringInline(serialized);
157
158                }
159
160                evalString("save STATE state_1");
161
162        }
163
164        public void setStructure(final Structure s) {
165                // Set the default to MMCIF (until issue #629 is fixed)
166                setStructure(s, false);
167        }
168
169        /** assign a custom color to the Jmol chains command.
170         *
171         */
172        public void jmolColorByChain(){
173                String script =
174                                "function color_by_chain(objtype, color_list) {"+ String.format("%n") +
175                                ""+ String.format("%n") +
176                                "                if (color_list) {"+ String.format("%n") +
177                                "                  if (color_list.type == \"string\") {"+ String.format("%n") +
178                                "                    color_list = color_list.split(\",\").trim();"+ String.format("%n") +
179                                "                  }"+ String.format("%n") +
180                                "                } else {"+ String.format("%n") +
181                                "                  color_list = [\"104BA9\",\"AA00A2\",\"C9F600\",\"FFA200\",\"284A7E\",\"7F207B\",\"9FB82E\",\"BF8B30\",\"052D6E\",\"6E0069\",\"83A000\",\"A66A00\",\"447BD4\",\"D435CD\",\"D8FA3F\",\"FFBA40\",\"6A93D4\",\"D460CF\",\"E1FA71\",\"FFCC73\"];"+ String.format("%n") +
182                                "                }"+ String.format("%n") +
183
184                                "                var cmd2 = \"\";"+ String.format("%n") +
185
186                                "                if (!objtype) {"+ String.format("%n") +
187                                "                  var type_list  = [ \"backbone\",\"cartoon\",\"dots\",\"halo\",\"label\",\"meshribbon\",\"polyhedra\",\"rocket\",\"star\",\"strand\",\"strut\",\"trace\"];"+ String.format("%n") +
188                                "                  cmd2 = \"color \" + type_list.join(\" none; color \") + \" none;\";"+ String.format("%n") +
189                                "                  objtype = \"atoms\";"+ String.format("%n") +
190
191                                "                }"+ String.format("%n") +
192
193                                "                var chain_list  = script(\"show chain\").trim().lines;"+ String.format("%n") +
194                                "                var chain_count = chain_list.length;"+ String.format("%n") +
195
196                                "                var color_count = color_list.length;"+ String.format("%n") +
197                                "                var sel = {selected};"+ String.format("%n") +
198                                "                var cmds = \"\";"+ String.format("%n") +
199
200
201                                "                for (var chain_number=1; chain_number<=chain_count; chain_number++) {"+ String.format("%n") +
202                                "                  // remember, Jmol arrays start with 1, but % can return 0"+ String.format("%n") +
203                                "                  cmds += \"select sel and :\" + chain_list[chain_number] + \";color \" + objtype + \" [x\" + color_list[(chain_number-1) % color_count + 1] + \"];\" + cmd2;"+ String.format("%n") +
204                                "                }"+ String.format("%n") +
205                                "                script INLINE @{cmds + \"select sel\"}"+ String.format("%n") +
206                                "}";
207
208                executeCmd(script);
209        }
210
211        /** The user selected one of the Combo boxes...
212         *
213         * @param event an ActionEvent
214         */
215        @Override
216        public void actionPerformed(ActionEvent event) {
217
218                Object mysource = event.getSource();
219
220                if ( ! (mysource instanceof JComboBox )) {
221                        super.actionPerformed(event);
222                        return;
223                }
224
225                @SuppressWarnings("unchecked")
226                JComboBox<String> source = (JComboBox<String>) event.getSource();
227                String value = source.getSelectedItem().toString();
228                evalString("save selection; ");
229
230                String selectLigand = "select ligand;wireframe 0.16;spacefill 0.5; color cpk ;";
231
232                if ( value.equals("Cartoon")){
233                        String script = "hide null; select all;  spacefill off; wireframe off; backbone off;" +
234                                        " cartoon on; " +
235                                        " select ligand; wireframe 0.16;spacefill 0.5; color cpk; " +
236                                        " select *.FE; spacefill 0.7; color cpk ; " +
237                                        " select *.CU; spacefill 0.7; color cpk ; " +
238                                        " select *.ZN; spacefill 0.7; color cpk ; " +
239                                        " select all; ";
240                        this.executeCmd(script);
241                } else if (value.equals("Backbone")){
242                        String script = "hide null; select all; spacefill off; wireframe off; backbone 0.4;" +
243                                        " cartoon off; " +
244                                        " select ligand; wireframe 0.16;spacefill 0.5; color cpk; " +
245                                        " select *.FE; spacefill 0.7; color cpk ; " +
246                                        " select *.CU; spacefill 0.7; color cpk ; " +
247                                        " select *.ZN; spacefill 0.7; color cpk ; " +
248                                        " select all; ";
249                        this.executeCmd(script);
250                } else if (value.equals("CPK")){
251                        String script = "hide null; select all; spacefill off; wireframe off; backbone off;" +
252                                        " cartoon off; cpk on;" +
253                                        " select ligand; wireframe 0.16;spacefill 0.5; color cpk; " +
254                                        " select *.FE; spacefill 0.7; color cpk ; " +
255                                        " select *.CU; spacefill 0.7; color cpk ; " +
256                                        " select *.ZN; spacefill 0.7; color cpk ; " +
257                                        " select all; ";
258                        this.executeCmd(script);
259
260                } else if (value.equals("Ligands")){
261                        this.executeCmd("restrict ligand; cartoon off; wireframe on;  display selected;");
262                } else if (value.equals("Ligands and Pocket")){
263                        this.executeCmd(" select within (6.0,ligand); cartoon off; wireframe on; backbone off; display selected; ");
264                } else if ( value.equals("Ball and Stick")){
265                        String script = "hide null; restrict not water;  wireframe 0.2; spacefill 25%;" +
266                                        " cartoon off; backbone off; " +
267                                        " select ligand; wireframe 0.16; spacefill 0.5; color cpk; " +
268                                        " select *.FE; spacefill 0.7; color cpk ; " +
269                                        " select *.CU; spacefill 0.7; color cpk ; " +
270                                        " select *.ZN; spacefill 0.7; color cpk ; " +
271                                        " select all; ";
272                        this.executeCmd(script);
273                } else if ( value.equals("By Chain")){
274                        jmolColorByChain();
275                        String script = "hide null; select all;set defaultColors Jmol; color_by_chain(\"cartoon\"); color_by_chain(\"\"); " + selectLigand + "; select all; ";
276                        this.executeCmd(script);
277                } else if ( value.equals("Rainbow")) {
278                        this.executeCmd("hide null; select all; set defaultColors Jmol; color group; color cartoon group; " + selectLigand + "; select all; " );
279                } else if ( value.equals("Secondary Structure")){
280                        this.executeCmd("hide null; select all; set defaultColors Jmol; color structure; color cartoon structure;" + selectLigand + "; select all; " );
281
282                } else if ( value.equals("By Element")){
283                        this.executeCmd("hide null; select all; set defaultColors Jmol; color cpk; color cartoon cpk; " + selectLigand + "; select all; ");
284                } else if ( value.equals("By Amino Acid")){
285                        this.executeCmd("hide null; select all; set defaultColors Jmol; color amino; color cartoon amino; " + selectLigand + "; select all; " );
286                } else if ( value.equals("Hydrophobicity") ){
287                        this.executeCmd("hide null; set defaultColors Jmol; select hydrophobic; color red; color cartoon red; select not hydrophobic ; color blue ; color cartoon blue; "+ selectLigand+"; select all; ");
288                } else if ( value.equals("Suggest Domains")){
289                        colorByPDP();
290                } else if ( value.equals("Show SCOP Domains")){
291                        colorBySCOP();
292                }
293                evalString("restore selection; ");
294        }
295
296        private void colorBySCOP() {
297
298                if ( structure == null)
299                        return;
300
301                String pdbId = structure.getPDBCode();
302                if ( pdbId == null)
303                        return;
304                ScopDatabase scop = new ScopInstallation();
305
306                List<ScopDomain> domains = scop.getDomainsForPDB(pdbId);
307                if ( domains == null) {
308                        System.err.println("No SCOP domains found for " + pdbId);
309                        return;
310                }
311                int i = -1;
312                for ( ScopDomain domain : domains){
313                        i++;
314                        if ( i >= ColorUtils.colorWheel.length)
315                                i = 0;
316                        Color c1 = ColorUtils.colorWheel[i];
317                        List<String>ranges = domain.getRanges();
318
319                        for (String range : ranges){
320                                logger.debug(range);
321                                String[] spl = range.split(":");
322                                String script = " select  ";
323                                if ( spl.length > 1 )
324                                        script += spl[1]+":"+spl[0] +"/1;";
325                                else
326                                        script += "*" + spl[0]+"/1;";
327                                script += " color [" + c1.getRed() + ","+c1.getGreen() + "," +c1.getBlue()+"];";
328                                script += " color cartoon [" + c1.getRed() + ","+c1.getGreen() + "," +c1.getBlue()+"] ;";
329                                logger.debug(script);
330                                evalString(script);
331
332                        }
333                }
334
335
336        }
337
338        private void colorByPDP() {
339                logger.debug("colorByPDP");
340                if ( structure == null)
341                        return;
342
343                try {
344                        Atom[] ca = StructureTools.getRepresentativeAtomArray(structure);
345                        List<Domain> domains = LocalProteinDomainParser.suggestDomains(ca);
346                        int i = -1;
347                        for ( Domain dom : domains){
348                                i++;
349                                if ( i > ColorUtils.colorWheel.length)
350                                        i = 0;
351                                //System.out.println("DOMAIN:" + i + " size:" + dom.size + " " +  dom.score);
352                                List<Segment> segments = dom.getSegments();
353                                Color c1 = ColorUtils.colorWheel[i];
354                                //float fraction = 0f;
355                                for ( Segment s : segments){
356                                        //System.out.println("   Segment: " + s);
357                                        //Color c1 = ColorUtils.rotateHue(c, fraction);
358                                        //      fraction += 1.0/(float)segments.size();
359                                        int start = s.getFrom();
360                                        int end = s.getTo();
361                                        Group startG = ca[start].getGroup();
362                                        Group endG = ca[end].getGroup();
363                                        logger.debug("   Segment: " +startG.getResidueNumber() +":" + startG.getChainId() + " - " + endG.getResidueNumber()+":"+endG.getChainId() + " " + s);
364                                        String j1 = startG.getResidueNumber()+"";
365                                        String j2 = endG.getResidueNumber()+":"+endG.getChainId();
366                                        String script = " select  " +j1 +"-" +j2 +"/1;";
367                                        script += " color [" + c1.getRed() + ","+c1.getGreen() + "," +c1.getBlue()+"];";
368                                        script += " color cartoon [" + c1.getRed() + ","+c1.getGreen() + "," +c1.getBlue()+"] ;";
369                                        logger.debug(script);
370                                        evalString(script);
371                                }
372
373                        }
374                } catch (Exception e){
375                        e.printStackTrace();
376                }
377        }
378
379        public void rotateJmol(Matrix jmolRotation) {
380
381                if ( jmolRotation != null) {
382                        double[] zyz = Calc.getZYZEuler(jmolRotation);
383                        DecimalFormat df = new DecimalFormat("0.##");
384
385                        String script = "reset; rotate z "
386                                        + df.format(zyz[0])
387                                        + "; rotate y "
388                                        + df.format(zyz[1])
389                                        +"; rotate z "
390                                        + df.format(zyz[2])+";";
391
392                        executeCmd(script);
393
394                }
395        }
396
397        /** Clean up this instance for garbage collection, to avoid memory leaks...
398         *
399         */
400        public void destroy(){
401
402                executeCmd("zap;");
403                structure = null;
404
405                viewer = null;
406                adapter = null;
407        }
408
409        public static class JmolLoggerAdapter implements LoggerInterface {
410                private Logger slf;
411                public JmolLoggerAdapter(Logger slf) {
412                        this.slf=slf;
413                }
414                public int getLogLevel() {
415                        if( slf.isTraceEnabled() )
416                                return org.jmol.util.Logger.LEVEL_MAX;
417                        if( slf.isDebugEnabled() )
418                                return org.jmol.util.Logger.LEVEL_DEBUG;
419                        if( slf.isInfoEnabled() )
420                                return org.jmol.util.Logger.LEVEL_INFO;
421                        if( slf.isWarnEnabled() )
422                                return org.jmol.util.Logger.LEVEL_WARN;
423                        if( slf.isErrorEnabled() )
424                                return org.jmol.util.Logger.LEVEL_ERROR;
425                        throw new IllegalStateException("Unknown SLF4J error level");
426                }
427                @Override
428                public void debug(String txt) {
429                        slf.debug(txt);
430                }
431                @Override
432                public void info(String txt) {
433                        slf.info(txt);
434                }
435                @Override
436                public void warn(String txt) {
437                        slf.warn(txt);
438                }
439                @Override
440                public void warnEx(String txt, Throwable e) {
441                        slf.warn(txt,e);
442                }
443                @Override
444                public void error(String txt) {
445                        slf.error(txt);
446                }
447                @Override
448                public void errorEx(String txt, Throwable e) {
449                        slf.error(txt,e);
450                }
451                @Override
452                public void fatal(String txt) {
453                        slf.error(txt);
454                }
455                @Override
456                public void fatalEx(String txt, Throwable e) {
457                        slf.error(txt,e);
458                }
459        }
460}