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