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 at May 26, 2008
021 */
022package org.biojava.nbio.structure.gui;
023
024import org.biojava.nbio.structure.*;
025import org.biojava.nbio.structure.align.StructurePairAligner;
026import org.biojava.nbio.structure.align.pairwise.AlternativeAlignment;
027import org.biojava.nbio.structure.gui.events.AlignmentPositionListener;
028import org.biojava.nbio.structure.gui.util.AlignedPosition;
029import org.biojava.nbio.structure.gui.util.SequenceMouseListener;
030import org.biojava.nbio.structure.gui.util.SequenceScalePanel;
031import org.biojava.nbio.structure.io.PDBFileReader;
032
033import javax.swing.*;
034import javax.swing.event.ChangeEvent;
035import javax.swing.event.ChangeListener;
036import java.awt.*;
037import java.awt.event.WindowAdapter;
038import java.awt.event.WindowEvent;
039import java.util.ArrayList;
040import java.util.List;
041//import org.slf4j.Logger;
042//import org.slf4j.LoggerFactory;
043
044/** A sequence display that can show the results of a protein structure alignment.
045 *
046 * @author Andreas Prlic
047 * @since 1.7
048 */
049public class SequenceDisplay extends JPanel implements ChangeListener {
050
051        /**
052         *
053         */
054        private static final long serialVersionUID = -1829252532712454236L;
055
056        Structure structure1;
057        Structure structure2;
058        AlternativeAlignment alig;
059        StructurePairAligner structurePairAligner;
060        SequenceScalePanel panel1;
061        SequenceScalePanel panel2;
062
063        JSlider  residueSizeSlider;
064        JLabel  percentageDisplay;
065
066        int[] idx1;
067        int[] idx2;
068
069        /** the maximum value that the scale can get
070         *
071         */
072        public static final int MAX_SCALE               = 10;
073
074        //private static final Logger logger = LoggerFactory.getLogger(SequenceDisplay.class);
075
076        List<AlignedPosition> apos;
077
078        float scale;
079        SequenceMouseListener mouseListener1;
080        SequenceMouseListener mouseListener2;
081
082
083        JLabel label1;
084        JLabel label2;
085
086        public static void main(String[] args){
087
088                try {
089                        PDBFileReader pdbr = new PDBFileReader();
090
091                        //String pdb1 = "1crl";
092                        //String pdb2 = "1ede";
093
094                        String pdb1 = "1buz";
095                        String pdb2 = "5pti";
096
097
098                        // NO NEED TO DO CHANGE ANYTHING BELOW HERE...
099
100                        StructurePairAligner sc = new StructurePairAligner();
101
102
103                        // step1 : read molecules
104
105                        System.out.println("aligning " + pdb1 + " vs. " + pdb2);
106
107                        Structure s1 = pdbr.getStructureById(pdb1);
108                        Structure s2 = pdbr.getStructureById(pdb2);
109
110                        // step 2 : do the calculations
111                        sc.align(s1,s2);
112
113
114                        AlternativeAlignment[] aligs = sc.getAlignments();
115                        SequenceDisplay displ = new SequenceDisplay(sc);
116                        displ.setStructure1(s1);
117                        displ.setStructure2(s2);
118                        displ.setAlternativeAlignment(aligs[0]);
119
120                        displ.updateDisplay();
121
122                        JFrame frame = new JFrame("Sequences for AlternativeAlignment ["+0+"]");
123
124                        frame.getContentPane().add(displ);
125
126                        frame.pack();
127                        frame.setVisible(true);
128                        frame.addWindowListener(new WindowAdapter(){
129                                @Override
130                                public void windowClosing(WindowEvent e){
131                                        JFrame f = (JFrame) e.getSource();
132                                        f.setVisible(false);
133                                        f.dispose();
134                                }
135
136
137
138                        });
139
140                } catch (Exception e){
141                        e.printStackTrace();
142                }
143
144        }
145
146        public SequenceDisplay(StructurePairAligner structurePairAligner){
147                super();
148
149
150
151                structure1 = null;
152                structure2 = null;
153                alig = null;
154                this.structurePairAligner = structurePairAligner;
155                panel1 = new SequenceScalePanel(1);
156                panel2 = new SequenceScalePanel(2);
157
158                mouseListener1 = new SequenceMouseListener(this);
159
160
161                panel1.addMouseListener(mouseListener1);
162                panel1.addMouseMotionListener(mouseListener1);
163
164
165                mouseListener2 = new SequenceMouseListener(this);
166                panel2.addMouseListener(mouseListener2);
167                panel2.addMouseMotionListener(mouseListener2);
168
169                //SequenceMouseListener ml = new SequenceMouseListener(this);
170                //this.addMouseListener(ml);
171                //this.addMouseMotionListener(ml);
172
173                Box vBox = Box.createVerticalBox();
174
175
176                Box hBox1 = Box.createHorizontalBox();
177                Box hBox2 = Box.createHorizontalBox();
178
179                label1 = new JLabel();
180                hBox1.add(label1);
181
182                label2 = new JLabel();
183                hBox2.add(label2);
184
185
186                hBox1.add(panel1);
187                hBox2.add(panel2);
188
189                vBox.add(hBox1);
190                vBox.add(hBox2);
191
192
193                int RES_MIN  = 1;
194                int RES_MAX  = 100;
195                int RES_INIT = 100;
196                residueSizeSlider = new JSlider(JSlider.HORIZONTAL,
197                                RES_MIN, RES_MAX, RES_INIT);
198                residueSizeSlider.setInverted(true);
199                //residueSizeSlider.setMajorTickSpacing(5);
200                //residueSizeSlider.setMinorTickSpacing(2);
201                residueSizeSlider.setPaintTicks(false);
202                residueSizeSlider.setPaintLabels(false);
203                residueSizeSlider.addChangeListener(this);
204                //residueSizeSlider.setPreferredSize(new Dimension(100,15));
205
206                percentageDisplay =  new JLabel("100 %");
207
208                Box hBox = Box.createHorizontalBox();
209                hBox.setBackground(Color.white);
210                hBox.add(Box.createHorizontalGlue());
211                hBox.add(residueSizeSlider);
212                hBox.add(percentageDisplay);
213                hBox.add(Box.createHorizontalGlue());
214
215                //vBox.add(hBox);
216
217                JScrollPane scroll = new JScrollPane(vBox);
218
219                //scroll.setPreferredSize(new Dimension(500,160));
220
221                Box vBox2 = Box.createVerticalBox();
222                vBox2.add(scroll);
223                vBox2.add(hBox);
224
225                //vBox2.setPreferredSize(new Dimension(500,160));
226                //vBox2.setSize(new Dimension(500,160));
227                //vBox2.setMinimumSize(new Dimension(500,160));
228                //vBox2.setMaximumSize(new Dimension(500,160));
229                this.setPreferredSize(new Dimension(500,100));
230
231                this.add(vBox2);
232
233                this.setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
234
235                apos = new ArrayList<AlignedPosition>();
236        }
237
238        public void clearListeners(){
239
240                mouseListener1.clearListeners();
241                mouseListener2.clearListeners();
242
243        }
244        public void addAlignmentPositionListener(AlignmentPositionListener li){
245                mouseListener1.addAlignmentPositionListener(li);
246                mouseListener2.addAlignmentPositionListener(li);
247        }
248
249        public StructurePairAligner getStructurePairAligner() {
250                return structurePairAligner;
251        }
252
253        public void setStructurePairAligner(StructurePairAligner structurePairAligner) {
254                this.structurePairAligner = structurePairAligner;
255        }
256        /** get the identical position in the alignment
257         *
258         * @return identical positions for structure1
259         */
260        public int[] getIdx1() {
261                return idx1;
262        }
263
264        /** set the identical positions in the alignment
265         *
266         * @param idx identical positions for structure1
267         */
268        private void setIdx1(int[] idx) {
269                this.idx1 = idx;
270
271        }
272        /** get the identical position in the alignment
273         *
274         * @return identical positions for structure2
275         */
276        public int[] getIdx2() {
277                return idx2;
278        }
279
280        /** set the identical positions in the alignment
281         *
282         * @param idx identical positions for structure2
283         */
284        private void setIdx2(int[] idx) {
285                this.idx2 = idx;
286
287        }
288
289
290
291        private void buildAligMap(){
292                apos.clear();
293
294                int gap   = 0;
295                int gpos1 = 0;
296                int gpos2 = 0;
297
298                for (int pos = 0 ; pos < idx1.length ; pos ++){
299
300                        int p1 = idx1[pos];
301                        int p2 = idx2[pos];
302
303                        int end = Math.max(p1,p2);
304
305                        //System.out.println("p1: " + p1 + " p2: " + p2 );
306                        // fill up  gaps...
307                        for (;gap<end;gap++){
308
309                                AlignedPosition m = new AlignedPosition();
310                                if ( gpos1 < p1){
311                                        m.setPos1(gpos1);
312
313                                        gpos1++;
314                                }
315                                if ( gpos2 < p2){
316                                        m.setPos2(gpos2);
317
318                                        gpos2++;
319                                }
320                                m.setEquivalent(AlignedPosition.NOT_ALIGNED);
321
322                                //System.out.println(m + " => " + end);
323                                apos.add(m);
324                        }
325
326                        // add this aligned position
327                        AlignedPosition m = new AlignedPosition();
328
329                        m.setPos1(p1);
330                        m.setPos2(p2);
331                        m.setEquivalent(AlignedPosition.EQUIVALENT);
332
333                        //System.out.println(m);
334                        apos.add(m);
335                        gpos1++;
336                        gpos2++;
337                        gap++;
338
339                }
340
341                //System.out.println(apos);
342
343        }
344
345
346        private void setAtoms(Structure s, SequenceScalePanel panel){
347                if ( structurePairAligner == null){
348                        System.err.println("StructurePairAligner has not been set");
349                        return;
350                }
351                Atom[] ca1 = structurePairAligner.getAlignmentAtoms(s);
352                Chain c = new ChainImpl();
353                c.setId("1");
354                for (Atom atom : ca1) {
355
356                        Group g = atom.getGroup();
357
358                        Chain parentChain = g.getChain();
359
360                        c.addGroup(g);
361                        // hack for Jmol?
362                        g.setChain(parentChain);
363                }
364                panel.setChain(c);
365
366        }
367
368        //TODO: add a method to allow the display if the structure alignment
369        // has been called with setting the atoms directly
370
371
372
373        public void setStructure1(Structure structure){
374                this.structure1 = structure;
375                if ( structure != null) {
376                        setAtoms(structure1,panel1);
377                        label1.setText(structure.getPDBCode());
378                        label1.repaint();
379                }
380
381
382        }
383        public void setStructure2(Structure structure){
384                this.structure2 = structure;
385                if ( structure != null){
386                        setAtoms(structure2,panel2);
387                        label2.setText(structure.getPDBCode());
388                        label2.repaint();
389                }
390        }
391
392        public void setAlternativeAlignment(AlternativeAlignment alig){
393                this.alig  = alig;
394                this.setIdx1(alig.getIdx1());
395                this.setIdx2(alig.getIdx2());
396
397                buildAligMap();
398
399                panel1.setAligMap(apos);
400                panel2.setAligMap(apos);
401
402                updateDisplay();
403
404
405        }
406        public List<AlignedPosition> getAligMap(){
407                return apos;
408        }
409
410        @Override
411        public void stateChanged(ChangeEvent e) {
412
413                JSlider source = (JSlider)e.getSource();
414                //if (!source.getValueIsAdjusting()) {
415
416                int residueSize = source.getValue();
417                calcScale(residueSize);
418
419                updatePercentageDisplay();
420
421
422
423                this.repaint();
424                this.revalidate();
425
426                //this.updateUI();
427                //int width = getTotalWidth();
428                //int height = getTotalHeight();
429                //Dimension d = new Dimension(width,height);
430                //logger.info("setting preferred size" + width + " " + height);
431                //this.setPreferredSize(d);
432                //this.setSize(d);
433                // }
434
435
436        }
437        public void updateDisplay(){
438
439                int residueSize = residueSizeSlider.getValue();
440                calcScale(residueSize);
441                updatePercentageDisplay();
442                this.repaint();
443                this.revalidate();
444
445        }
446        private void updatePercentageDisplay(){
447                int perc = residueSizeSlider.getValue();
448                percentageDisplay.setText(perc+ " %");
449        }
450
451        private int getMaxSequenceLength(){
452                int l1 = panel1.getChain().getAtomGroups(GroupType.AMINOACID).size();
453                int l2 = panel2.getChain().getAtomGroups(GroupType.AMINOACID).size();
454                if ( l1 > l2)
455                        return l1;
456                else return l2;
457        }
458
459
460
461        /** calculate the float that is used for display.
462         * 1 * scale = size of 1 amino acid (in pixel).
463         * maximum @see MAX_SCALE
464         * @param zoomFactor
465         * @return a float that is the display "scale" - an internal value required for paintin.
466         * user should only interact with the zoomfactor ...
467         */
468        private float getScaleForZoom(int zoomFactor){
469
470                if ( zoomFactor > 100)
471                        zoomFactor = 100;
472                if ( zoomFactor < 1)
473                        zoomFactor = 1;
474
475
476                int DEFAULT_X_START
477                = SequenceScalePanel.DEFAULT_X_START;
478                int DEFAULT_X_RIGHT_BORDER = SequenceScalePanel.DEFAULT_X_RIGHT_BORDER;
479
480                int seqLength = getMaxSequenceLength();
481                // the maximum width depends on the size of the parent Component
482
483                int width=getWidth();
484
485
486                float s = width / (float) ( seqLength + DEFAULT_X_START + DEFAULT_X_RIGHT_BORDER );
487                //logger.info("scale for 100% " + s + " " + seqLength + " " + zoomFactor);
488
489                s = (100) * s / ( zoomFactor * 1.0f) ;
490
491                if ( s > MAX_SCALE)
492                        s = MAX_SCALE;
493
494                //logger.info("but changed to " + s);
495                return s;
496        }
497
498        /** a value of 100 means that the whole sequence should be displayed in the current visible window
499         * a factor of 1 means that one amino acid shoud be drawn as big as possible
500         *
501         * @param zoomFactor - a value between 1 and 100
502         *
503         *
504         */
505        public void calcScale(int zoomFactor){
506
507                float s = getScaleForZoom(zoomFactor);
508                scale = s;
509                //logger.info("calc scale zoom:"+zoomFactor+ " s: " + s);
510                panel1.setScale(s);
511                panel2.setScale(s);
512                panel1.repaint();
513                panel2.repaint();
514
515
516                //return scale;
517
518        }
519
520        public float getScale(){
521                return scale;
522        }
523
524}
525