1 /* ContigTool.java
2  *
3  * created: 2005
4  *
5  * This file is part of Artemis
6  *
7  * Copyright(C) 2005  Genome Research Limited
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or(at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22  *
23  */
24 
25 package uk.ac.sanger.artemis.components;
26 
27 import uk.ac.sanger.artemis.Options;
28 import uk.ac.sanger.artemis.FeatureVector;
29 import uk.ac.sanger.artemis.Feature;
30 import uk.ac.sanger.artemis.io.Range;
31 import uk.ac.sanger.artemis.editor.MultiLineToolTipUI;
32 import uk.ac.sanger.artemis.SelectionChangeListener;
33 import uk.ac.sanger.artemis.SelectionChangeEvent;
34 import uk.ac.sanger.artemis.Selection;
35 
36 import java.awt.*;
37 import java.awt.event.*;
38 
39 import java.util.Vector;
40 
41 import java.awt.geom.RoundRectangle2D;
42 
43 import javax.swing.BorderFactory;
44 import javax.swing.ImageIcon;
45 import javax.swing.JLabel;
46 import javax.swing.JMenu;
47 import javax.swing.JMenuItem;
48 import javax.swing.JPanel;
49 import javax.swing.JPopupMenu;
50 import javax.swing.JScrollPane;
51 import javax.swing.border.Border;
52 import java.awt.datatransfer.*;
53 import java.awt.dnd.*;
54 
55 public class ContigTool extends JPanel
56        implements DragGestureListener, DropTargetListener,
57                   DragSourceListener, Autoscroll,
58                   SelectionChangeListener
59 {
60   private static final long serialVersionUID = 1L;
61   private FeatureVector contig_features;
62   private FeatureDisplay feature_display;
63   private Selection selection;
64 
65   private int scale  = 1000;
66   private int xbound = 50;
67   private int length = xbound*2;
68 
69   /** pop up menu */
70   private JPopupMenu popup = new JPopupMenu();
71 
72   private int highlight_drop_base = -1;
73   /** AutoScroll margin */
74   private static final int AUTOSCROLL_MARGIN = 45;
75   /** used by AutoScroll method */
76   private Insets autoscrollInsets = new Insets( 0, 0, 0, 0 );
77 
78   /** status label */
79   final private JLabel status_line = new JLabel("");
80 
ContigTool(final FeatureVector contig_features, final FeatureDisplay feature_display, final JScrollPane jsp, final Selection selection)81   public ContigTool(final FeatureVector contig_features,
82                     final FeatureDisplay feature_display,
83                     final JScrollPane jsp,
84                     final Selection selection)
85   {
86     super();
87     this.contig_features = contig_features;
88     this.feature_display = feature_display;
89     this.selection       = selection;
90 
91     setFocusable(true); // required for KeyEvent to work
92     MultiLineToolTipUI.initialize();
93     setToolTipText("");   //enable tooltip display
94 
95     /*for(int i=0; i<contig_features.size(); i++)
96     {
97       final Range this_feature_range = contig_features.elementAt(i).getMaxRawRange();
98       length += ((this_feature_range.getEnd() - this_feature_range.getStart())/scale);
99     }*/
100 
101     length += contig_features.elementAt(0).getStrand().getSequenceLength() / scale;
102 
103 
104     Dimension dim = new Dimension(length, 20);
105     setPreferredSize(dim);
106 
107     DragSource dragSource = DragSource.getDefaultDragSource();
108 
109     dragSource.createDefaultDragGestureRecognizer(
110        this,                             // component where drag originates
111        DnDConstants.ACTION_COPY_OR_MOVE, // actions
112        this);                            // drag gesture recognizer
113 
114     setDropTarget(new DropTarget(this,this));
115 
116     getSelection().addSelectionChangeListener(this);
117 
118     addKeyListener(new KeyAdapter()
119     {
120       public void keyPressed(final KeyEvent event)
121       {
122         // this is done so that menu shortcuts don't cause each action to be
123         // performed twice
124         if(event.getModifiers() != 0)
125           return;
126 
127         switch(event.getKeyCode())
128         {
129           case KeyEvent.VK_UP:
130             goToNext(true);
131             repaint();
132             break;
133           case KeyEvent.VK_DOWN:
134             goToNext(false);
135             repaint();
136             break;
137           default:
138             break;
139         }
140       }
141     });
142 
143 
144     addMouseListener(new MouseAdapter()
145     {
146       public void mouseReleased(MouseEvent event)
147       {
148         if(event.isPopupTrigger())
149         {
150           popup.show(event.getComponent(),
151                   event.getX(), event.getY());
152           return;
153         }
154 
155         FeatureVector contig_features = ContigTool.this.contig_features;
156         if(event.getClickCount() == 1 &&
157            event.getID() == MouseEvent.MOUSE_RELEASED)
158         {
159           Point p = event.getPoint();
160           for(int i=0; i<contig_features.size(); i++)
161           {
162             final Feature feature = contig_features.elementAt(i);
163             final Range this_feature_range = feature.getMaxRawRange();
164 
165             int xstart = xbound + this_feature_range.getStart()/scale;
166             int xend   = xbound + this_feature_range.getEnd()/scale;
167 
168             if(p.x >= xstart && p.x <= xend)
169             {
170               if(getSelection().contains(feature))
171                 getSelection().remove(feature);
172               else
173               {
174                 clearSelection();
175 
176                 String tt = this_feature_range.getStart()+".."+
177                             this_feature_range.getEnd();
178 
179                 if(feature.getIDString() != null)
180                   tt = tt + ", " + feature.getIDString();
181 
182                 status_line.setText(tt);
183 
184                 getSelection().add(feature);
185               }
186             }
187           }
188           repaint();
189         }
190       }
191 
192       public void mousePressed(MouseEvent event)
193       {
194         if(event.isPopupTrigger())
195         {
196           popup.show(event.getComponent(),
197                   event.getX(), event.getY());
198           return;
199         }
200       }
201     });
202 
203     JMenu zoomIn = new JMenu("Zoom In");
204     popup.add(zoomIn);
205     // set popup menu items
206     JMenuItem zoomIn5 = new JMenuItem("x 1/5");
207     zoomIn.add(zoomIn5);
208     zoomIn5.addActionListener(new ActionListener()
209     {
210       public void actionPerformed(ActionEvent event)
211       {
212         zoomIn(5,jsp);
213       }
214     });
215 
216     JMenuItem zoomIn10 = new JMenuItem("x 1/10");
217     zoomIn.add(zoomIn10);
218     zoomIn10.addActionListener(new ActionListener()
219     {
220       public void actionPerformed(ActionEvent event)
221       {
222         zoomIn(10,jsp);
223       }
224     });
225 
226 
227     JMenu zoomOut = new JMenu("Zoom Out");
228     popup.add(zoomOut);
229     JMenuItem zoomOut5 = new JMenuItem("x5");
230     zoomOut.add(zoomOut5);
231     zoomOut5.addActionListener(new ActionListener()
232     {
233       public void actionPerformed(ActionEvent event)
234       {
235         scale = scale * 5;
236         adjustSize(jsp);
237         repaint();
238       }
239     });
240 
241     JMenuItem zoomOut10 = new JMenuItem("x10");
242     zoomOut.add(zoomOut10);
243     zoomOut10.addActionListener(new ActionListener()
244     {
245       public void actionPerformed(ActionEvent event)
246       {
247         scale = scale * 10;
248         adjustSize(jsp);
249         repaint();
250       }
251     });
252 
253     // set up status bar
254     status_line.setFont(Options.getOptions().getFont());
255     final FontMetrics fm =
256       this.getFontMetrics(status_line.getFont());
257 
258     final int font_height = fm.getHeight()+10;
259 
260     status_line.setMinimumSize(new Dimension(100, font_height));
261     status_line.setPreferredSize(new Dimension(100, font_height));
262 
263     Border loweredbevel = BorderFactory.createLoweredBevelBorder();
264     Border raisedbevel = BorderFactory.createRaisedBevelBorder();
265     Border compound = BorderFactory.createCompoundBorder(raisedbevel,loweredbevel);
266     status_line.setBorder(compound);
267   }
268 
zoomIn(final int factor, final JScrollPane jsp)269   private void zoomIn(final int factor, final JScrollPane jsp)
270   {
271     if(scale < factor)
272     {
273       scale = 1;
274       return;
275     }
276 
277     scale = scale / factor;
278     adjustSize(jsp);
279     repaint();
280   }
281 
282   /**
283    *
284    * Clear all selected features
285    *
286    **/
clearSelection()287   private void clearSelection()
288   {
289     FeatureVector selected_features = getSelection().getAllFeatures();
290 
291     for(int i=0; i<selected_features.size(); i++)
292     {
293       Feature this_feature = selected_features.elementAt(i);
294       getSelection().remove(this_feature);
295     }
296   }
297 
getSelection()298   private Selection getSelection()
299   {
300     return selection;
301   }
302 
303   /**
304    *  Implementation of the SelectionChangeListener interface.  We listen to
305    *  SelectionChange events so that we can update the list to reflect the
306    *  current selection.
307    **/
selectionChanged(SelectionChangeEvent event)308   public void selectionChanged(SelectionChangeEvent event)
309   {
310     if(!isVisible())
311       return;
312 
313     // don't bother with events we sent ourself
314     if(event.getSource() == this)
315       return;
316 
317     // if the selected range changes we don't care
318     if(getSelection().getMarkerRange() != null &&
319        event.getType() == SelectionChangeEvent.OBJECT_CHANGED)
320       return;
321 
322     repaint();
323   }
324 
325   /**
326    *
327    * Select the next feature in the sequence.
328    *
329    */
goToNext(boolean up)330   private void goToNext(boolean up)
331   {
332     if(getSelection().getSelectedFeatures().size() != 1)
333       return;
334 
335     final Feature curr_feature = getSelection().getSelectedFeatures().elementAt(0);
336     final Range curr_feature_range = curr_feature.getMaxRawRange();
337     int start = curr_feature_range.getStart();
338     int end   = curr_feature_range.getEnd();
339 
340     if(up && start == 1)
341       return;
342 
343     for(int i=0; i<contig_features.size(); i++)
344     {
345       final Feature feature = contig_features.elementAt(i);
346       final Range this_feature_range = feature.getMaxRawRange();
347       if(up && this_feature_range.getEnd() == start-1)
348       {
349         clearSelection();
350         getSelection().add(feature);
351         return;
352       }
353       else if(!up && this_feature_range.getStart() == end+1)
354       {
355         clearSelection();
356         getSelection().add(feature);
357         return;
358       }
359     }
360   }
361 
362   /**
363    *
364    * Used when changing the scale / magnification.
365    * @param jsp  scrollpane to reset viewport size
366    *
367    */
adjustSize(final JScrollPane jsp)368   private void adjustSize(final JScrollPane jsp)
369   {
370     length = xbound*2;
371     length += contig_features.elementAt(0).getStrand().getSequenceLength() / scale;
372     setPreferredSize(new Dimension(length, 60));
373     jsp.revalidate();
374   }
375 
376   /**
377    *
378    * Get the status bar.
379    * @return status label
380    *
381    */
getStatusBar()382   protected JLabel getStatusBar()
383   {
384     return status_line;
385   }
386 
387   /**
388    *
389    * Override paintComponent()
390    *
391    */
paintComponent(Graphics g)392   protected void paintComponent(Graphics g)
393   {
394     Graphics2D g2 = (Graphics2D)g;
395     super.paintComponent(g2);
396 
397     setFont(Options.getOptions().getFont());
398     final FontMetrics fm =
399       this.getFontMetrics(getFont());
400 
401     for(int i=0; i<contig_features.size(); i++)
402     {
403       final Feature feature = contig_features.elementAt(i);
404       final Range this_feature_range = feature.getMaxRawRange();
405       Color colour = feature.getColour();
406       if(colour == null)
407         colour = Color.white;
408 
409       int xstart = xbound + this_feature_range.getStart()/scale;
410       int xend   = xbound + this_feature_range.getEnd()/scale;
411 
412       RoundRectangle2D e = new RoundRectangle2D.Float(xstart, 10, xend-xstart,
413                                                       20, 0, 10);
414       GradientPaint gp = new GradientPaint(xstart, 10, colour,
415                                            xstart, 10+10, Color.white, true);
416       g2.setPaint(gp);
417       g2.fill(e);
418     }
419 
420     BasicStroke stroke  = (BasicStroke)g2.getStroke();
421     BasicStroke stroke1 = new BasicStroke(1.f);
422     BasicStroke stroke2 = new BasicStroke(2.f);
423     g2.setColor(Color.black);
424 
425     FeatureVector selected_features = getSelection().getSelectedFeatures();
426     // draw feature outline
427     for(int i=0; i<contig_features.size(); i++)
428     {
429       final Feature feature = contig_features.elementAt(i);
430       final Range this_feature_range = feature.getMaxRawRange();
431       int xstart = xbound + this_feature_range.getStart()/scale;
432       int xend   = xbound + this_feature_range.getEnd()/scale;
433 
434       if(selected_features.contains(feature))
435         g2.setStroke(stroke2);
436       else
437         g2.setStroke(stroke1);
438 
439       g2.drawRect(xstart, 10, xend-xstart, 20);
440 
441       final String label_or_gene = feature.getIDString();
442       final Shape saved_clip = g.getClip();
443       g2.setColor(Color.black);
444       g2.setClip(xstart, 10, xend-xstart, 20);
445       g2.drawString(label_or_gene, xstart,
446                     10 + fm.getMaxAscent() + 1);
447       g2.setClip(saved_clip);
448     }
449 
450     g2.setStroke(stroke);
451     if(highlight_drop_base > 0)
452     {
453       g2.setColor(Color.red);
454       final int draw_x_position = xbound + highlight_drop_base/scale;
455       g.drawLine(draw_x_position, 0,
456                  draw_x_position, 100);
457     }
458   }
459 
460   /**
461    *
462    * Determine the tool tip to display
463    * @param e    mouse event
464    * @return     tool tip
465    *
466    */
getToolTipText(MouseEvent e)467   public String getToolTipText(MouseEvent e)
468   {
469     Point loc = e.getPoint();
470     int pos = loc.x*scale - xbound;
471     int first;
472     int last;
473 
474     for(int i = 0; i < contig_features.size(); i++)
475     {
476       final Feature this_feature = contig_features.elementAt(i);
477       first = this_feature.getRawFirstBase();
478       last  = this_feature.getRawLastBase();
479       if(pos >= first && pos <=last)
480       {
481         return first+".."+last;
482       }
483     }
484     return null;
485   }
486 
487 
488 
489 ////////////////////
490 // DRAG AND DROP
491 ////////////////////
492 
493   /**
494    *
495    * Given a point find the nearest start/stop of a feature and
496    * set highlight_drop_base.
497    *
498    */
getNearestFeatureEnd(Point loc)499   private void getNearestFeatureEnd(Point loc)
500   {
501     final int base_pos = (loc.x-50)*scale;
502     int first;
503     int last;
504     final Vector contig_keys = FeatureDisplay.getContigKeys();
505 
506     for(int i = 0; i < contig_features.size(); i++)
507     {
508       final Feature this_feature = contig_features.elementAt(i);
509 
510       if(contig_keys.contains(this_feature.getKey()))
511       {
512         first = this_feature.getRawFirstBase();
513         last  = this_feature.getRawLastBase();
514 
515         if( Math.abs(first - base_pos) < Math.abs(base_pos - highlight_drop_base) )
516           highlight_drop_base = first;
517         if( Math.abs(last - base_pos) < Math.abs(base_pos - highlight_drop_base) )
518           highlight_drop_base = last+1;
519       }
520     }
521   }
522 
523 // drop
drop(DropTargetDropEvent e)524   public void drop(DropTargetDropEvent e)
525   {
526     //Transferable t = e.getTransferable();
527     if(e.isDataFlavorSupported(DataFlavor.stringFlavor))
528     {
529       FeatureVector selected_features = getSelection().getSelectedFeatures();
530       feature_display.reorder(highlight_drop_base,
531                               selected_features.elementAt(0));   // rearrange contigs
532 
533       // reset status bar
534       final Range this_feature_range = selected_features.elementAt(0).getMaxRawRange();
535       String tt = this_feature_range.getStart()+".."+
536                   this_feature_range.getEnd();
537 
538       if(selected_features.elementAt(0).getIDString() != null)
539         tt = tt + ", " + selected_features.elementAt(0).getIDString();
540 
541       status_line.setText(tt);
542       repaint();
543     }
544     highlight_drop_base = -1;
545   }
546 
dragExit(DropTargetEvent e)547   public void dragExit(DropTargetEvent e)
548   {
549     highlight_drop_base = -1;
550   }
551 
dropActionChanged(DropTargetDragEvent e)552   public void dropActionChanged(DropTargetDragEvent e) {}
553 
dragOver(DropTargetDragEvent e)554   public void dragOver(DropTargetDragEvent e)
555   {
556     if(e.isDataFlavorSupported(DataFlavor.stringFlavor))
557     {
558       Point ploc = e.getLocation();
559       getNearestFeatureEnd(ploc);
560       repaint();
561     }
562     else
563       e.rejectDrag();
564   }
565 
dragEnter(DropTargetDragEvent e)566   public void dragEnter(DropTargetDragEvent e)
567   {
568     if(e.isDataFlavorSupported(DataFlavor.stringFlavor))
569       e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
570   }
571 
572 // drag source
dragGestureRecognized(DragGestureEvent e)573   public void dragGestureRecognized(DragGestureEvent e)
574   {
575     // ignore if mouse popup trigger
576     InputEvent ie = e.getTriggerEvent();
577     if(ie instanceof MouseEvent)
578       if(((MouseEvent)ie).isPopupTrigger())
579         return;
580 
581     final Vector contig_keys = FeatureDisplay.getContigKeys();
582 
583     FeatureVector selected_features = getSelection().getSelectedFeatures();
584     if(selected_features.size() == 1 &&
585        contig_keys.contains(selected_features.elementAt(0).getKey()))
586     {
587       ClassLoader cl = this.getClass().getClassLoader();
588       ImageIcon icon = new ImageIcon(cl.getResource("images/icon.gif"));
589       final Image icon_image = icon.getImage();
590 
591       //TransferableContig tcontig = new TransferableContig(selected_features.elementAt(0));
592       StringSelection name = new StringSelection(selected_features.elementAt(0).getGeneName());
593 
594       e.startDrag(DragSource.DefaultCopyDrop,     // cursor
595                   icon_image, new Point(-1, -1),
596                  (Transferable)name,              // transferable data
597                                        this);     // drag source listener
598     }
599   }
600 
dragDropEnd(DragSourceDropEvent e)601   public void dragDropEnd(DragSourceDropEvent e) {}
dragEnter(DragSourceDragEvent e)602   public void dragEnter(DragSourceDragEvent e) {}
603 
dragExit(DragSourceEvent e)604   public void dragExit(DragSourceEvent e)
605   {
606 //  highlight_drop_base = -1;
607   }
dragOver(DragSourceDragEvent e)608   public void dragOver(DragSourceDragEvent e) {}
dropActionChanged(DragSourceDragEvent e)609   public void dropActionChanged(DragSourceDragEvent e) {}
610 
611 
612 ////////////////////
613 // AUTO SCROLLING //
614 ////////////////////
615   /**
616    *
617    * Handles the auto scrolling of the JTree.
618    * @param location The location of the mouse.
619    *
620    */
autoscroll( Point location )621   public void autoscroll( Point location )
622   {
623     int top = 0, left = 0, bottom = 0, right = 0;
624     Dimension size = getSize();
625     Rectangle rect = getVisibleRect();
626     int bottomEdge = rect.y + rect.height;
627     int rightEdge = rect.x + rect.width;
628     if( location.y - rect.y < AUTOSCROLL_MARGIN && rect.y > 0 )
629       top = AUTOSCROLL_MARGIN;
630     if( location.x - rect.x < AUTOSCROLL_MARGIN && rect.x > 0 )
631       left = AUTOSCROLL_MARGIN;
632     if( bottomEdge - location.y < AUTOSCROLL_MARGIN && bottomEdge < size.height )
633       bottom = AUTOSCROLL_MARGIN;
634     if( rightEdge - location.x < AUTOSCROLL_MARGIN && rightEdge < size.width )
635       right = AUTOSCROLL_MARGIN;
636     rect.x += right - left;
637     rect.y += bottom - top;
638     scrollRectToVisible( rect );
639   }
640 
641   /**
642    *
643    * Gets the insets used for the autoscroll.
644    * @return The insets.
645    *
646    */
getAutoscrollInsets()647   public Insets getAutoscrollInsets()
648   {
649     Dimension size = getSize();
650     Rectangle rect = getVisibleRect();
651     autoscrollInsets.top = rect.y + AUTOSCROLL_MARGIN;
652     autoscrollInsets.left = rect.x + AUTOSCROLL_MARGIN;
653     autoscrollInsets.bottom = size.height - (rect.y+rect.height) + AUTOSCROLL_MARGIN;
654     autoscrollInsets.right  = size.width - (rect.x+rect.width) + AUTOSCROLL_MARGIN;
655     return autoscrollInsets;
656   }
657 
setScale(int scale)658   protected void setScale(int scale)
659   {
660     this.scale = scale;
661   }
662 
getScale()663   protected int getScale()
664   {
665     return scale;
666   }
667 
668   /**
669    *
670    * Popup listener
671    *
672    */
673   class PopupListener extends MouseAdapter
674   {
mousePressed(MouseEvent e)675     public void mousePressed(MouseEvent e)
676     {
677       maybeShowPopup(e);
678     }
679 
mouseReleased(MouseEvent e)680     public void mouseReleased(MouseEvent e)
681     {
682       maybeShowPopup(e);
683     }
684 
maybeShowPopup(MouseEvent e)685     private void maybeShowPopup(MouseEvent e)
686     {
687       if(e.isPopupTrigger())
688         popup.show(e.getComponent(),
689                 e.getX(), e.getY());
690     }
691   }
692 
693 }
694