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)
131        {
132                this.structure = s;
133                try (
134                                PipedOutputStream out = new PipedOutputStream();
135                                // Viewer requires a BufferedInputStream for reflection
136                                InputStream in = new BufferedInputStream(new PipedInputStream(out));
137                                ) {
138                        new Thread((Runnable)() -> {
139                                try {
140                                        MmtfActions.writeToOutputStream(s,out);
141                                } catch (Exception e) {
142                                        logger.error("Error generating MMTF output for {}",
143                                                        s.getStructureIdentifier()==null ? s.getStructureIdentifier().getIdentifier() : s.getName(), e);
144                                }
145                        }).start();
146                        viewer.openReader(null, in);
147                } catch (IOException e) {
148                        logger.error("Error transfering {} to Jmol",
149                                        s.getStructureIdentifier()==null ? s.getStructureIdentifier().getIdentifier() : s.getName(), e);
150                }
151
152                evalString("save STATE state_1");
153        }
154
155        /** assign a custom color to the Jmol chains command.
156         *
157         */
158        public void jmolColorByChain(){
159                String script =
160                                "function color_by_chain(objtype, color_list) {"+ String.format("%n") +
161                                ""+ String.format("%n") +
162                                "                if (color_list) {"+ String.format("%n") +
163                                "                  if (color_list.type == \"string\") {"+ String.format("%n") +
164                                "                    color_list = color_list.split(\",\").trim();"+ String.format("%n") +
165                                "                  }"+ String.format("%n") +
166                                "                } else {"+ String.format("%n") +
167                                "                  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") +
168                                "                }"+ String.format("%n") +
169
170                                "                var cmd2 = \"\";"+ String.format("%n") +
171
172                                "                if (!objtype) {"+ String.format("%n") +
173                                "                  var type_list  = [ \"backbone\",\"cartoon\",\"dots\",\"halo\",\"label\",\"meshribbon\",\"polyhedra\",\"rocket\",\"star\",\"strand\",\"strut\",\"trace\"];"+ String.format("%n") +
174                                "                  cmd2 = \"color \" + type_list.join(\" none; color \") + \" none;\";"+ String.format("%n") +
175                                "                  objtype = \"atoms\";"+ String.format("%n") +
176
177                                "                }"+ String.format("%n") +
178
179                                "                var chain_list  = script(\"show chain\").trim().lines;"+ String.format("%n") +
180                                "                var chain_count = chain_list.length;"+ String.format("%n") +
181
182                                "                var color_count = color_list.length;"+ String.format("%n") +
183                                "                var sel = {selected};"+ String.format("%n") +
184                                "                var cmds = \"\";"+ String.format("%n") +
185
186
187                                "                for (var chain_number=1; chain_number<=chain_count; chain_number++) {"+ String.format("%n") +
188                                "                  // remember, Jmol arrays start with 1, but % can return 0"+ String.format("%n") +
189                                "                  cmds += \"select sel and :\" + chain_list[chain_number] + \";color \" + objtype + \" [x\" + color_list[(chain_number-1) % color_count + 1] + \"];\" + cmd2;"+ String.format("%n") +
190                                "                }"+ String.format("%n") +
191                                "                script INLINE @{cmds + \"select sel\"}"+ String.format("%n") +
192                                "}";
193
194                executeCmd(script);
195        }
196
197        /** The user selected one of the Combo boxes...
198         *
199         * @param event an ActionEvent
200         */
201        @Override
202        public void actionPerformed(ActionEvent event) {
203
204                Object mysource = event.getSource();
205
206                if ( ! (mysource instanceof JComboBox )) {
207                        super.actionPerformed(event);
208                        return;
209                }
210
211                @SuppressWarnings("unchecked")
212                JComboBox<String> source = (JComboBox<String>) event.getSource();
213                String value = source.getSelectedItem().toString();
214                evalString("save selection; ");
215
216                String selectLigand = "select ligand;wireframe 0.16;spacefill 0.5; color cpk ;";
217
218                if ( value.equals("Cartoon")){
219                        String script = "hide null; select all;  spacefill off; wireframe off; backbone off;" +
220                                        " cartoon on; " +
221                                        " select ligand; wireframe 0.16;spacefill 0.5; color cpk; " +
222                                        " select *.FE; spacefill 0.7; color cpk ; " +
223                                        " select *.CU; spacefill 0.7; color cpk ; " +
224                                        " select *.ZN; spacefill 0.7; color cpk ; " +
225                                        " select all; ";
226                        this.executeCmd(script);
227                } else if (value.equals("Backbone")){
228                        String script = "hide null; select all; spacefill off; wireframe off; backbone 0.4;" +
229                                        " cartoon off; " +
230                                        " select ligand; wireframe 0.16;spacefill 0.5; color cpk; " +
231                                        " select *.FE; spacefill 0.7; color cpk ; " +
232                                        " select *.CU; spacefill 0.7; color cpk ; " +
233                                        " select *.ZN; spacefill 0.7; color cpk ; " +
234                                        " select all; ";
235                        this.executeCmd(script);
236                } else if (value.equals("CPK")){
237                        String script = "hide null; select all; spacefill off; wireframe off; backbone off;" +
238                                        " cartoon off; cpk on;" +
239                                        " select ligand; wireframe 0.16;spacefill 0.5; color cpk; " +
240                                        " select *.FE; spacefill 0.7; color cpk ; " +
241                                        " select *.CU; spacefill 0.7; color cpk ; " +
242                                        " select *.ZN; spacefill 0.7; color cpk ; " +
243                                        " select all; ";
244                        this.executeCmd(script);
245
246                } else if (value.equals("Ligands")){
247                        this.executeCmd("restrict ligand; cartoon off; wireframe on;  display selected;");
248                } else if (value.equals("Ligands and Pocket")){
249                        this.executeCmd(" select within (6.0,ligand); cartoon off; wireframe on; backbone off; display selected; ");
250                } else if ( value.equals("Ball and Stick")){
251                        String script = "hide null; restrict not water;  wireframe 0.2; spacefill 25%;" +
252                                        " cartoon off; backbone off; " +
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                } else if ( value.equals("By Chain")){
260                        jmolColorByChain();
261                        String script = "hide null; select all;set defaultColors Jmol; color_by_chain(\"cartoon\"); color_by_chain(\"\"); " + selectLigand + "; select all; ";
262                        this.executeCmd(script);
263                } else if ( value.equals("Rainbow")) {
264                        this.executeCmd("hide null; select all; set defaultColors Jmol; color group; color cartoon group; " + selectLigand + "; select all; " );
265                } else if ( value.equals("Secondary Structure")){
266                        this.executeCmd("hide null; select all; set defaultColors Jmol; color structure; color cartoon structure;" + selectLigand + "; select all; " );
267
268                } else if ( value.equals("By Element")){
269                        this.executeCmd("hide null; select all; set defaultColors Jmol; color cpk; color cartoon cpk; " + selectLigand + "; select all; ");
270                } else if ( value.equals("By Amino Acid")){
271                        this.executeCmd("hide null; select all; set defaultColors Jmol; color amino; color cartoon amino; " + selectLigand + "; select all; " );
272                } else if ( value.equals("Hydrophobicity") ){
273                        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; ");
274                } else if ( value.equals("Suggest Domains")){
275                        colorByPDP();
276                } else if ( value.equals("Show SCOP Domains")){
277                        colorBySCOP();
278                }
279                evalString("restore selection; ");
280        }
281
282        private void colorBySCOP() {
283
284                if ( structure == null)
285                        return;
286
287                String pdbId = structure.getPDBCode();
288                if ( pdbId == null)
289                        return;
290                ScopDatabase scop = new ScopInstallation();
291
292                List<ScopDomain> domains = scop.getDomainsForPDB(pdbId);
293                if ( domains == null) {
294                        System.err.println("No SCOP domains found for " + pdbId);
295                        return;
296                }
297                int i = -1;
298                for ( ScopDomain domain : domains){
299                        i++;
300                        if ( i >= ColorUtils.colorWheel.length)
301                                i = 0;
302                        Color c1 = ColorUtils.colorWheel[i];
303                        List<String>ranges = domain.getRanges();
304
305                        for (String range : ranges){
306                                logger.debug(range);
307                                String[] spl = range.split(":");
308                                String script = " select  ";
309                                if ( spl.length > 1 )
310                                        script += spl[1]+":"+spl[0] +"/1;";
311                                else
312                                        script += "*" + spl[0]+"/1;";
313                                script += " color [" + c1.getRed() + ","+c1.getGreen() + "," +c1.getBlue()+"];";
314                                script += " color cartoon [" + c1.getRed() + ","+c1.getGreen() + "," +c1.getBlue()+"] ;";
315                                logger.debug(script);
316                                evalString(script);
317
318                        }
319                }
320
321
322        }
323
324        private void colorByPDP() {
325                logger.debug("colorByPDP");
326                if ( structure == null)
327                        return;
328
329                try {
330                        Atom[] ca = StructureTools.getRepresentativeAtomArray(structure);
331                        List<Domain> domains = LocalProteinDomainParser.suggestDomains(ca);
332                        int i = -1;
333                        for ( Domain dom : domains){
334                                i++;
335                                if ( i > ColorUtils.colorWheel.length)
336                                        i = 0;
337                                //System.out.println("DOMAIN:" + i + " size:" + dom.size + " " +  dom.score);
338                                List<Segment> segments = dom.getSegments();
339                                Color c1 = ColorUtils.colorWheel[i];
340                                //float fraction = 0f;
341                                for ( Segment s : segments){
342                                        //System.out.println("   Segment: " + s);
343                                        //Color c1 = ColorUtils.rotateHue(c, fraction);
344                                        //      fraction += 1.0/(float)segments.size();
345                                        int start = s.getFrom();
346                                        int end = s.getTo();
347                                        Group startG = ca[start].getGroup();
348                                        Group endG = ca[end].getGroup();
349                                        logger.debug("   Segment: " +startG.getResidueNumber() +":" + startG.getChainId() + " - " + endG.getResidueNumber()+":"+endG.getChainId() + " " + s);
350                                        String j1 = startG.getResidueNumber()+"";
351                                        String j2 = endG.getResidueNumber()+":"+endG.getChainId();
352                                        String script = " select  " +j1 +"-" +j2 +"/1;";
353                                        script += " color [" + c1.getRed() + ","+c1.getGreen() + "," +c1.getBlue()+"];";
354                                        script += " color cartoon [" + c1.getRed() + ","+c1.getGreen() + "," +c1.getBlue()+"] ;";
355                                        logger.debug(script);
356                                        evalString(script);
357                                }
358
359                        }
360                } catch (Exception e){
361                        e.printStackTrace();
362                }
363        }
364
365        public void rotateJmol(Matrix jmolRotation) {
366
367                if ( jmolRotation != null) {
368                        double[] zyz = Calc.getZYZEuler(jmolRotation);
369                        DecimalFormat df = new DecimalFormat("0.##");
370
371                        String script = "reset; rotate z "
372                                        + df.format(zyz[0])
373                                        + "; rotate y "
374                                        + df.format(zyz[1])
375                                        +"; rotate z "
376                                        + df.format(zyz[2])+";";
377
378                        executeCmd(script);
379
380                }
381        }
382
383        /** Clean up this instance for garbage collection, to avoid memory leaks...
384         *
385         */
386        public void destroy(){
387
388                executeCmd("zap;");
389                structure = null;
390
391                viewer = null;
392                adapter = null;
393        }
394
395        public static class JmolLoggerAdapter implements LoggerInterface {
396                private Logger slf;
397                public JmolLoggerAdapter(Logger slf) {
398                        this.slf=slf;
399                }
400                public int getLogLevel() {
401                        if( slf.isTraceEnabled() )
402                                return org.jmol.util.Logger.LEVEL_MAX;
403                        if( slf.isDebugEnabled() )
404                                return org.jmol.util.Logger.LEVEL_DEBUG;
405                        if( slf.isInfoEnabled() )
406                                return org.jmol.util.Logger.LEVEL_INFO;
407                        if( slf.isWarnEnabled() )
408                                return org.jmol.util.Logger.LEVEL_WARN;
409                        if( slf.isErrorEnabled() )
410                                return org.jmol.util.Logger.LEVEL_ERROR;
411                        throw new IllegalStateException("Unknown SLF4J error level");
412                }
413                @Override
414                public void debug(String txt) {
415                        slf.debug(txt);
416                }
417                @Override
418                public void info(String txt) {
419                        slf.info(txt);
420                }
421                @Override
422                public void warn(String txt) {
423                        slf.warn(txt);
424                }
425                @Override
426                public void warnEx(String txt, Throwable e) {
427                        slf.warn(txt,e);
428                }
429                @Override
430                public void error(String txt) {
431                        slf.error(txt);
432                }
433                @Override
434                public void errorEx(String txt, Throwable e) {
435                        slf.error(txt,e);
436                }
437                @Override
438                public void fatal(String txt) {
439                        slf.error(txt);
440                }
441                @Override
442                public void fatalEx(String txt, Throwable e) {
443                        slf.error(txt,e);
444                }
445        }
446}