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 24.05.2004
021 * @author Andreas Prlic
022 *
023 */
024package org.biojava.nbio.structure.align.gui.jmol;
025
026import org.biojava.nbio.structure.*;
027import org.biojava.nbio.structure.align.gui.AlignmentGui;
028import org.biojava.nbio.structure.align.gui.DisplayAFP;
029import org.biojava.nbio.structure.align.gui.MenuCreator;
030import org.biojava.nbio.structure.align.model.AFPChain;
031import org.biojava.nbio.structure.align.model.AfpChainWriter;
032import org.biojava.nbio.structure.align.util.AlignmentTools;
033import org.biojava.nbio.structure.align.util.AtomCache;
034import org.biojava.nbio.structure.align.util.ResourceManager;
035import org.biojava.nbio.structure.align.util.UserConfiguration;
036import org.biojava.nbio.structure.align.webstart.AligUIManager;
037import org.biojava.nbio.structure.gui.util.color.ColorUtils;
038import org.biojava.nbio.structure.jama.Matrix;
039
040import javax.swing.*;
041import javax.swing.event.ChangeEvent;
042import javax.swing.event.ChangeListener;
043
044import java.awt.*;
045import java.awt.event.*;
046import java.io.StringWriter;
047import java.util.ArrayList;
048import java.util.Arrays;
049import java.util.Hashtable;
050import java.util.List;
051
052/**
053 * A class that provides a simple GUI for Jmol
054 *
055 * @author Andreas Prlic
056 * @since 1.6
057 *
058 */
059public class StructureAlignmentJmol extends AbstractAlignmentJmol implements ChangeListener {
060
061        private Atom[] ca1;
062        private Atom[] ca2;
063        private AFPChain afpChain;
064
065        private static final String LIGAND_DISPLAY_SCRIPT = ResourceManager.getResourceManager("ce")
066                        .getString("default.ligand.jmol.script");
067
068        public static void main(String[] args) {
069
070                try {
071
072                        UserConfiguration config = new UserConfiguration();
073                        AtomCache cache = new AtomCache(config);
074
075                        Structure struc = cache.getStructure("5pti");
076
077                        StructureAlignmentJmol jmolPanel = new StructureAlignmentJmol(null, null, null);
078
079                        jmolPanel.setStructure(struc);
080
081                        // send some RASMOL style commands to Jmol
082                        jmolPanel.evalString("select * ; color chain;");
083                        jmolPanel.evalString("select *; spacefill off; wireframe off; backbone 0.4;  ");
084
085                } catch (Exception e) {
086                        e.printStackTrace();
087                }
088        }
089
090        public StructureAlignmentJmol() {
091                // don;t have an afpChain, set it to null...
092                this(null, null, null);
093        }
094
095        public StructureAlignmentJmol(AFPChain afpChain, Atom[] ca1, Atom[] ca2) {
096
097                AligUIManager.setLookAndFeel();
098
099                nrOpenWindows++;
100                jmolPanel = new JmolPanel();
101
102                frame = new JFrame();
103
104                JMenuBar menu = MenuCreator.initJmolMenu(frame,this, afpChain, null);
105
106                frame.setJMenuBar(menu);
107                //frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
108                this.afpChain = afpChain;
109                this.ca1 = ca1;
110                this.ca2 = ca2;
111
112                frame.addWindowListener( new WindowAdapter()
113                {
114
115                        @Override
116                        public void windowClosing(WindowEvent e) {
117
118                                nrOpenWindows--;
119
120                                destroy();
121
122                                if ( nrOpenWindows > 0) {
123
124                                        frame.dispose();
125                                }
126                                else  {
127                                        // check if AlignmentGUI is visible..
128
129                                        AlignmentGui gui = AlignmentGui.getInstanceNoVisibilityChange();
130                                        if ( gui.isVisible()) {
131                                                frame.dispose();
132                                                gui.requestFocus();
133                                        } else {
134                                                System.exit(0);
135                                        }
136                                }
137                        }
138                });
139
140                Container contentPane = frame.getContentPane();
141
142                Box vBox = Box.createVerticalBox();
143
144                //try {
145
146
147
148                jmolPanel.addMouseMotionListener(this);
149                jmolPanel.addMouseListener(this);
150
151                //              } catch (ClassNotFoundException e){
152                //                      e.printStackTrace();
153                //                      System.err.println("Could not find Jmol in classpath, please install first. http://www.jmol.org");
154                //                      return;
155                //              }
156                jmolPanel.setPreferredSize(new Dimension(DEFAULT_WIDTH,DEFAULT_HEIGHT));
157                vBox.add(jmolPanel);
158
159
160                /// USER SCRIPTING COMMAND
161                JTextField field = new JTextField();
162
163                field.setMaximumSize(new Dimension(Short.MAX_VALUE,30));
164                field.setText(COMMAND_LINE_HELP);
165                RasmolCommandListener listener = new RasmolCommandListener(jmolPanel,field) ;
166
167                field.addActionListener(listener);
168                field.addMouseListener(listener);
169                field.addKeyListener(listener);
170                vBox.add(field);
171
172
173                /// COMBO BOXES
174                Box hBox1 = Box.createHorizontalBox();
175                hBox1.add(Box.createGlue());
176
177                String[] styles = new String[] { "Cartoon", "Backbone", "CPK", "Ball and Stick", "Ligands","Ligands and Pocket"};
178                JComboBox style = new JComboBox(styles);
179
180                hBox1.setMaximumSize(new Dimension(Short.MAX_VALUE,30));
181
182                hBox1.add(new JLabel("Style"));
183                hBox1.add(style);
184                vBox.add(hBox1);
185                contentPane.add(vBox);
186
187                style.addActionListener(jmolPanel);
188
189                String[] colorModes = new String[] { "Secondary Structure", "By Chain", "Rainbow", "By Element", "By Amino Acid", "Hydrophobicity" ,"Suggest Domains" , "Show SCOP Domains"};
190                JComboBox colors = new JComboBox(colorModes);
191                colors.addActionListener(jmolPanel);
192                hBox1.add(Box.createGlue());
193                hBox1.add(new JLabel("Color"));
194                hBox1.add(colors);
195
196
197                // CHeck boxes
198                Box hBox2 = Box.createHorizontalBox();
199                hBox2.setMaximumSize(new Dimension(Short.MAX_VALUE,30));
200
201                JButton resetDisplay = new JButton("Reset Display");
202
203                resetDisplay.addActionListener(new ActionListener() {
204
205                        @Override
206                        public void actionPerformed(ActionEvent e) {
207                                System.out.println("reset!!");
208                                jmolPanel.executeCmd("restore STATE state_1");
209
210                        }
211                });
212
213                hBox2.add(resetDisplay);
214                hBox2.add(Box.createGlue());
215
216
217                JCheckBox toggleSelection = new JCheckBox("Show Selection");
218                toggleSelection.addItemListener(
219                                new ItemListener() {
220
221                                        @Override
222                                        public void itemStateChanged(ItemEvent e) {
223                                                boolean showSelection = (e.getStateChange() == ItemEvent.SELECTED);
224
225                                                if (showSelection){
226                                                        jmolPanel.executeCmd("set display selected");
227                                                } else {
228                                                        jmolPanel.executeCmd("set display off");
229                                                }
230
231                                        }
232                                }
233                                );
234
235
236
237                hBox2.add(toggleSelection);
238
239                hBox2.add(Box.createGlue());
240                vBox.add(hBox2);
241
242
243                // ZOOM SLIDER
244                Box hBox3 = Box.createHorizontalBox();
245                hBox3.setMaximumSize(new Dimension(Short.MAX_VALUE,30));
246
247                JLabel sliderLabel = new JLabel("Zoom");
248
249                hBox3.add(Box.createGlue());
250                hBox3.add(sliderLabel);
251
252                JSlider zoomSlider = new JSlider(JSlider.HORIZONTAL,0,500,100);
253
254                zoomSlider.addChangeListener(this);
255
256                zoomSlider.setMajorTickSpacing(100);
257                zoomSlider.setPaintTicks(true);
258
259                Hashtable<Integer, JLabel> labelTable = new Hashtable<Integer, JLabel>();
260                labelTable.put(new Integer(0),new JLabel("0%"));
261                labelTable.put(new Integer(100),new JLabel("100%"));
262                labelTable.put(new Integer(200),new JLabel("200%"));
263                labelTable.put(new Integer(300),new JLabel("300%"));
264                labelTable.put(new Integer(400),new JLabel("400%"));
265                labelTable.put(new Integer(500),new JLabel("500%"));
266
267                zoomSlider.setLabelTable(labelTable);
268                zoomSlider.setPaintLabels(true);
269
270                hBox3.add(zoomSlider);
271                hBox3.add(Box.createGlue());
272
273                // SPIN CHECKBOX
274                JCheckBox toggleSpin = new JCheckBox("Spin");
275                toggleSpin.addItemListener(
276                                new ItemListener() {
277
278                                        @Override
279                                        public void itemStateChanged(ItemEvent e) {
280                                                boolean spinOn = (e.getStateChange() == ItemEvent.SELECTED);
281
282                                                if (spinOn){
283                                                        jmolPanel.executeCmd("spin ON");
284                                                } else {
285                                                        jmolPanel.executeCmd("spin OFF");
286                                                }
287
288                                        }
289                                }
290                                );
291
292
293                hBox3.add(toggleSpin);
294                hBox3.add(Box.createGlue());
295
296                vBox.add(hBox3);
297
298
299                // STATUS DISPLAY
300
301                Box hBox = Box.createHorizontalBox();
302
303                status = new JTextField();
304                status.setBackground(Color.white);
305                status.setEditable(false);
306                status.setMaximumSize(new Dimension(Short.MAX_VALUE,30));
307                status.setPreferredSize(new Dimension(DEFAULT_WIDTH / 2,30));
308                status.setMinimumSize(new Dimension(DEFAULT_WIDTH / 2,30));
309                hBox.add(status);
310                text = new JTextField();
311                text.setBackground(Color.white);
312                text.setMaximumSize(new Dimension(Short.MAX_VALUE,30));
313                text.setPreferredSize(new Dimension(DEFAULT_WIDTH / 2,30));
314                text.setMinimumSize(new Dimension(DEFAULT_WIDTH / 2,30));
315                text.setText("Display of Atom info");
316                text.setEditable(false);
317                hBox.add(text);
318
319                vBox.add(hBox);
320
321
322
323                contentPane.add(vBox);
324                MyJmolStatusListener li = (MyJmolStatusListener) jmolPanel.getStatusListener();
325                li.setTextField(status);
326                frame.pack();
327                frame.setVisible(true);
328
329
330                // init coordinates
331
332                initCoords();
333
334                resetDisplay();
335
336        }
337
338        @Override
339        protected void initCoords() {
340                try {
341                        if (ca1 == null || ca2 == null) {
342                                if (structure != null)
343                                        setStructure(structure);
344                                else {
345                                        // System.err.println("could not find anything to
346                                        // display!");
347                                        return;
348                                }
349                        }
350                        Structure artificial = AlignmentTools.getAlignedStructure(ca1, ca2);
351                        PDBHeader header = new PDBHeader();
352                        String title = afpChain.getAlgorithmName() + " V." + afpChain.getVersion() + " : " + afpChain.getName1()
353                                        + " vs. " + afpChain.getName2();
354                        header.setTitle(title);
355                        artificial.setPDBHeader(header);
356                        setStructure(artificial);
357                } catch (StructureException e) {
358                        e.printStackTrace();
359                }
360        }
361
362        @Override
363        public void destroy() {
364                super.destroy();
365                afpChain = null;
366                ca1 = null;
367                ca2 = null;
368        }
369
370        @Override
371        public void actionPerformed(ActionEvent e) {
372                String cmd = e.getActionCommand();
373                if (cmd.equals(MenuCreator.TEXT_ONLY)) {
374                        if (afpChain == null) {
375                                System.err.println("Currently not viewing an alignment!");
376                                return;
377                        }
378                        // Clone the AFPChain to not override the FatCat numbers in alnsymb
379                        AFPChain textAFP = (AFPChain) afpChain.clone();
380                        String result = AfpChainWriter.toWebSiteDisplay(textAFP, ca1, ca2);
381
382                        DisplayAFP.showAlignmentImage(afpChain, result);
383
384                } else if (cmd.equals(MenuCreator.PAIRS_ONLY)) {
385                        if (afpChain == null) {
386                                System.err.println("Currently not viewing an alignment!");
387                                return;
388                        }
389                        String result = AfpChainWriter.toAlignedPairs(afpChain, ca1, ca2);
390
391                        DisplayAFP.showAlignmentImage(afpChain, result);
392
393                } else if (cmd.equals(MenuCreator.ALIGNMENT_PANEL)) {
394                        if (afpChain == null) {
395                                System.err.println("Currently not viewing an alignment!");
396                                return;
397                        }
398                        try {
399                                DisplayAFP.showAlignmentPanel(afpChain, ca1, ca2, this);
400                        } catch (Exception e1) {
401                                e1.printStackTrace();
402                                return;
403                        }
404
405                } else if (cmd.equals(MenuCreator.FATCAT_TEXT)) {
406                        if (afpChain == null) {
407                                System.err.println("Currently not viewing an alignment!");
408                                return;
409                        }
410                        String result = afpChain.toFatcat(ca1, ca2);
411                        result += AFPChain.newline;
412                        result += afpChain.toRotMat();
413                        DisplayAFP.showAlignmentImage(afpChain, result);
414                }
415        }
416
417        public static String getJmolString(AFPChain afpChain, Atom[] ca1, Atom[] ca2) {
418
419                if (afpChain.getBlockNum() > 1) {
420                        return getMultiBlockJmolScript(afpChain, ca1, ca2);
421                }
422
423                StringBuffer j = new StringBuffer();
424                j.append(DEFAULT_SCRIPT);
425
426                // now color the equivalent residues ...
427                StringBuffer sel = new StringBuffer();
428                List<String> pdb1 = DisplayAFP.getPDBresnum(0, afpChain, ca1);
429                sel.append("select ");
430                int pos = 0;
431                for (String res : pdb1) {
432                        if (pos > 0)
433                                sel.append(",");
434                        pos++;
435
436                        sel.append(res);
437                        sel.append("/1");
438                }
439                if (pos == 0)
440                        sel.append("none");
441                sel.append(";");
442                sel.append("backbone 0.6 ;   color orange;");
443                sel.append("select */2; color lightgrey; model 2; ");
444                // jmol.evalString("select */2; color lightgrey; model 2; ");
445                List<String> pdb2 = DisplayAFP.getPDBresnum(1, afpChain, ca2);
446                sel.append("select ");
447                pos = 0;
448                for (String res : pdb2) {
449                        if (pos > 0)
450                                sel.append(",");
451                        pos++;
452
453                        sel.append(res);
454                        sel.append("/2");
455                }
456                if (pos == 0)
457                        sel.append("none");
458                sel.append("; backbone 0.6 ;   color cyan;");
459                // System.out.println(sel);
460                j.append(sel);
461                // now show both models again.
462                j.append("model 0;  ");
463                j.append(LIGAND_DISPLAY_SCRIPT);
464                // color [object] cpk , set defaultColors Jmol , set defaultColors
465                // Rasmol
466
467                // and now select the aligned residues...
468                StringBuffer buf = new StringBuffer("select ");
469                int count = 0;
470                for (String res : pdb1) {
471                        if (count > 0)
472                                buf.append(",");
473                        buf.append(res);
474                        buf.append("/1");
475                        count++;
476                }
477
478                for (String res : pdb2) {
479                        buf.append(",");
480                        buf.append(res);
481                        buf.append("/2");
482                }
483                // buf.append("; set display selected;");
484
485                j.append(buf);
486
487                return j.toString();
488        }
489
490        public static String getJmolScript4Block(AFPChain afpChain, Atom[] ca1, Atom[] ca2, int blockNr) {
491                int blockNum = afpChain.getBlockNum();
492
493                if (blockNr >= blockNum)
494                        return DEFAULT_SCRIPT;
495
496                int[] optLen = afpChain.getOptLen();
497                int[][][] optAln = afpChain.getOptAln();
498
499                if (optLen == null)
500                        return DEFAULT_SCRIPT;
501
502                StringWriter jmol = new StringWriter();
503                jmol.append(DEFAULT_SCRIPT);
504
505                jmol.append("select */2; color lightgrey; model 2; ");
506
507                printJmolScript4Block(ca1, ca2, blockNum, optLen, optAln, jmol, blockNr);
508
509                jmol.append("model 0;  ");
510                jmol.append(LIGAND_DISPLAY_SCRIPT);
511                // System.out.println(jmol);
512                return jmol.toString();
513
514        }
515
516        private static String getMultiBlockJmolScript(AFPChain afpChain, Atom[] ca1, Atom[] ca2) {
517
518                int blockNum = afpChain.getBlockNum();
519                int[] optLen = afpChain.getOptLen();
520                int[][][] optAln = afpChain.getOptAln();
521
522                if (optLen == null)
523                        return DEFAULT_SCRIPT;
524
525                StringWriter jmol = new StringWriter();
526                jmol.append(DEFAULT_SCRIPT);
527
528                jmol.append("select */2; color lightgrey; model 2; ");
529
530                for (int bk = 0; bk < blockNum; bk++) {
531
532                        printJmolScript4Block(ca1, ca2, blockNum, optLen, optAln, jmol, bk);
533                }
534                jmol.append("model 0;  ");
535
536                jmol.append(LIGAND_DISPLAY_SCRIPT);
537                // System.out.println(jmol);
538                return jmol.toString();
539
540        }
541
542        private static void printJmolScript4Block(Atom[] ca1, Atom[] ca2, int blockNum, int[] optLen, int[][][] optAln,
543                        StringWriter jmol, int bk) {
544                // the block nr determines the color...
545                int colorPos = bk;
546
547                Color c1;
548                Color c2;
549                // If the colors for the block are specified in AFPChain use them,
550                // otherwise the default ones are calculated
551
552                if (colorPos > ColorUtils.colorWheel.length) {
553                        colorPos = ColorUtils.colorWheel.length % colorPos;
554                }
555
556                Color end1 = ColorUtils.rotateHue(ColorUtils.orange, (1.0f / 24.0f) * blockNum);
557                Color end2 = ColorUtils.rotateHue(ColorUtils.cyan, (1.0f / 24.0f) * (blockNum + 1));
558
559                c1 = ColorUtils.getIntermediate(ColorUtils.orange, end1, blockNum, bk);
560                c2 = ColorUtils.getIntermediate(ColorUtils.cyan, end2, blockNum, bk);
561
562                List<String> pdb1 = new ArrayList<String>();
563                List<String> pdb2 = new ArrayList<String>();
564                for (int i = 0; i < optLen[bk]; i++) {
565                        ///
566                        int pos1 = optAln[bk][0][i];
567                        pdb1.add(JmolTools.getPdbInfo(ca1[pos1]));
568                        int pos2 = optAln[bk][1][i];
569                        pdb2.add(JmolTools.getPdbInfo(ca2[pos2]));
570                }
571
572                // and now select the aligned residues...
573                StringBuffer buf = new StringBuffer("select ");
574                int count = 0;
575                for (String res : pdb1) {
576                        if (count > 0)
577                                buf.append(",");
578                        buf.append(res);
579                        buf.append("/1");
580                        count++;
581                }
582
583                buf.append("; backbone 0.6 ; color [" + c1.getRed() + "," + c1.getGreen() + "," + c1.getBlue() + "]; select ");
584
585                count = 0;
586                for (String res : pdb2) {
587                        if (count > 0)
588                                buf.append(",");
589
590                        buf.append(res);
591                        buf.append("/2");
592                        count++;
593                }
594                // buf.append("; set display selected;");
595
596                buf.append("; backbone 0.6 ; color [" + c2.getRed() + "," + c2.getGreen() + "," + c2.getBlue() + "];");
597
598                // now color this block:
599                jmol.append(buf);
600        }
601
602        @Override
603        public void resetDisplay() {
604
605                if (afpChain != null && ca1 != null && ca2 != null) {
606                        String script = getJmolString(afpChain, ca1, ca2);
607                        // System.out.println(script);
608                        evalString(script);
609                        jmolPanel.evalString("save STATE state_1");
610                }
611        }
612
613        @Override
614        public List<Matrix> getDistanceMatrices() {
615                if (afpChain == null)
616                        return null;
617                else
618                        return Arrays.asList(afpChain.getDisTable1(), afpChain.getDisTable2());
619        }
620
621        @Override
622        public void stateChanged(ChangeEvent e) {
623                JSlider source = (JSlider) e.getSource();
624                if (!source.getValueIsAdjusting()) {
625                        int zoomValue = (int) source.getValue();
626                        jmolPanel.executeCmd("zoom " + zoomValue);
627                }
628        }
629
630
631}