2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.Sequence;
25 import jalview.datamodel.SequenceFeature;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.gui.SeqPanel.MousePos;
29 import jalview.io.SequenceAnnotationReport;
30 import jalview.util.MessageManager;
31 import jalview.util.Platform;
32 import jalview.viewmodel.AlignmentViewport;
33 import jalview.viewmodel.ViewportRanges;
35 import java.awt.BorderLayout;
36 import java.awt.event.MouseEvent;
37 import java.awt.event.MouseListener;
38 import java.awt.event.MouseMotionListener;
39 import java.awt.event.MouseWheelEvent;
40 import java.awt.event.MouseWheelListener;
41 import java.util.List;
43 import javax.swing.JPanel;
44 import javax.swing.JPopupMenu;
45 import javax.swing.SwingUtilities;
46 import javax.swing.ToolTipManager;
49 * This panel hosts alignment sequence ids and responds to mouse clicks on them,
50 * as well as highlighting ids matched by a search from the Find menu.
55 public class IdPanel extends JPanel
56 implements MouseListener, MouseMotionListener, MouseWheelListener
58 private IdCanvas idCanvas;
60 protected AlignmentViewport av;
62 protected AlignmentPanel alignPanel;
64 ScrollThread scrollThread = null;
73 boolean mouseDragging = false;
75 private final SequenceAnnotationReport seqAnnotReport;
78 * Creates a new IdPanel object.
83 public IdPanel(AlignViewport av, AlignmentPanel parent)
87 setIdCanvas(new IdCanvas(av));
88 linkImageURL = getClass().getResource("/images/link.gif").toString();
89 seqAnnotReport = new SequenceAnnotationReport(linkImageURL);
90 setLayout(new BorderLayout());
91 add(getIdCanvas(), BorderLayout.CENTER);
92 addMouseListener(this);
93 addMouseMotionListener(this);
94 addMouseWheelListener(this);
95 ToolTipManager.sharedInstance().registerComponent(this);
99 * Responds to mouse movement by setting tooltip text for the sequence id
100 * under the mouse (or possibly annotation label, when in wrapped mode)
105 public void mouseMoved(MouseEvent e)
107 SeqPanel sp = alignPanel.getSeqPanel();
108 MousePos pos = sp.findMousePosition(e);
109 if (pos.isOverAnnotation())
112 * mouse is over an annotation label in wrapped mode
114 AlignmentAnnotation[] anns = av.getAlignment()
115 .getAlignmentAnnotation();
116 AlignmentAnnotation annotation = anns[pos.annotationIndex];
117 setToolTipText(AnnotationLabels.getTooltip(annotation));
118 alignPanel.alignFrame.setStatus(
119 AnnotationLabels.getStatusMessage(annotation, anns));
123 int seq = Math.max(0, pos.seqIndex);
124 if (seq < av.getAlignment().getHeight())
126 SequenceI sequence = av.getAlignment().getSequenceAt(seq);
127 StringBuilder tip = new StringBuilder(64);
128 tip.append(sequence.getDisplayId(true)).append(" ");
129 seqAnnotReport.createTooltipAnnotationReport(tip, sequence,
130 av.isShowDBRefs(), av.isShowNPFeats(), sp.seqCanvas.fr);
131 setToolTipText(JvSwingUtils.wrapTooltip(true, tip.toString()));
133 StringBuilder text = new StringBuilder();
134 text.append("Sequence ").append(String.valueOf(seq + 1))
136 .append(sequence.getName());
137 alignPanel.alignFrame.setStatus(text.toString());
143 * Responds to a mouse drag by selecting the sequences under the dragged
149 public void mouseDragged(MouseEvent e)
151 mouseDragging = true;
153 MousePos pos = alignPanel.getSeqPanel().findMousePosition(e);
154 if (pos.isOverAnnotation())
156 // mouse is over annotation label in wrapped mode
160 int seq = Math.max(0, pos.seqIndex);
164 selectSeqs(lastid - 1, seq);
166 else if (seq > lastid)
168 selectSeqs(lastid + 1, seq);
172 alignPanel.paintAlignment(false, false);
176 * Response to the mouse wheel by scrolling the alignment panel.
179 public void mouseWheelMoved(MouseWheelEvent e)
182 double wheelRotation = e.getPreciseWheelRotation();
183 if (wheelRotation > 0)
187 av.getRanges().scrollRight(true);
191 av.getRanges().scrollUp(false);
194 else if (wheelRotation < 0)
198 av.getRanges().scrollRight(false);
202 av.getRanges().scrollUp(true);
208 * Handle a mouse click event. Currently only responds to a double-click. The
209 * action is to try to open a browser window at a URL that searches for the
210 * selected sequence id. The search URL is configured in Preferences |
211 * Connections | URL link from Sequence ID. For example:
213 * http://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$SEQUENCE_ID$
218 public void mouseClicked(MouseEvent e)
221 * Ignore single click. Ignore 'left' click followed by 'right' click (user
222 * selects a row then its pop-up menu).
224 if (e.getClickCount() < 2 || SwingUtilities.isRightMouseButton(e))
226 // reinstate isRightMouseButton check to ignore mouse-related popup events
227 // note - this does nothing on default MacBookPro force-trackpad config!
231 MousePos pos = alignPanel.getSeqPanel().findMousePosition(e);
232 int seq = pos.seqIndex;
233 if (pos.isOverAnnotation() || seq < 0)
238 String id = av.getAlignment().getSequenceAt(seq).getName();
239 String url = Preferences.sequenceUrlLinks.getPrimaryUrl(id);
243 jalview.util.BrowserLauncher.openURL(url);
244 } catch (Exception ex)
246 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
247 MessageManager.getString("label.web_browser_not_found_unix"),
248 MessageManager.getString("label.web_browser_not_found"),
249 JvOptionPane.WARNING_MESSAGE);
250 ex.printStackTrace();
261 public void mouseEntered(MouseEvent e)
263 if (scrollThread != null)
265 scrollThread.stopScrolling();
276 public void mouseExited(MouseEvent e)
278 if (av.getWrapAlignment())
283 if (mouseDragging && (e.getY() < 0)
284 && (av.getRanges().getStartSeq() > 0))
286 scrollThread = new ScrollThread(true);
289 if (mouseDragging && (e.getY() >= getHeight())
290 && (av.getAlignment().getHeight() > av.getRanges().getEndSeq()))
292 scrollThread = new ScrollThread(false);
297 * Respond to a mouse press. Does nothing for (left) double-click as this is
298 * handled by mouseClicked().
300 * Right mouse down - construct and show context menu.
302 * Ctrl-down or Shift-down - add to or expand current selection group if there
305 * Mouse down - select this sequence.
310 public void mousePressed(MouseEvent e)
312 if (e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(e))
317 MousePos pos = alignPanel.getSeqPanel().findMousePosition(e);
319 if (e.isPopupTrigger()) // Mac reports this in mousePressed
321 showPopupMenu(e, pos);
326 * defer right-mouse click handling to mouseReleased on Windows
327 * (where isPopupTrigger() will answer true)
328 * NB isRightMouseButton is also true for Cmd-click on Mac
330 if (SwingUtilities.isRightMouseButton(e) && !Platform.isAMac())
335 if ((av.getSelectionGroup() == null)
336 || (!jalview.util.Platform.isControlDown(e) && !e.isShiftDown()
337 && av.getSelectionGroup() != null))
339 av.setSelectionGroup(new SequenceGroup());
340 av.getSelectionGroup().setStartRes(0);
341 av.getSelectionGroup().setEndRes(av.getAlignment().getWidth() - 1);
344 if (e.isShiftDown() && (lastid != -1))
346 selectSeqs(lastid, pos.seqIndex);
350 selectSeq(pos.seqIndex);
353 av.isSelectionGroupChanged(true);
355 alignPanel.paintAlignment(false, false);
359 * Build and show the popup-menu at the right-click mouse position
363 void showPopupMenu(MouseEvent e, MousePos pos)
365 if (pos.isOverAnnotation())
367 showAnnotationMenu(e, pos);
371 Sequence sq = (Sequence) av.getAlignment().getSequenceAt(pos.seqIndex);
374 * build a new links menu based on the current links
375 * and any non-positional features
377 List<SequenceFeature> features = null;
380 List<String> nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
381 features = sq.getFeatures().getNonPositionalFeatures();
382 for (SequenceFeature sf : features)
384 if (sf.links != null)
386 nlinks.addAll(sf.links);
391 PopupMenu pop = new PopupMenu(alignPanel, sq, features,
392 Preferences.getGroupURLLinks());
393 pop.show(this, e.getX(), e.getY());
397 * On right mouse click on a Consensus annotation label, shows a limited popup
398 * menu, with options to configure the consensus calculation and rendering.
402 * @see AnnotationLabels#showPopupMenu(MouseEvent)
404 void showAnnotationMenu(MouseEvent e, MousePos pos)
406 if (pos.annotationIndex == -1)
410 AlignmentAnnotation[] anns = this.av.getAlignment()
411 .getAlignmentAnnotation();
412 if (anns == null || pos.annotationIndex >= anns.length)
416 AlignmentAnnotation ann = anns[pos.annotationIndex];
417 if (!ann.label.contains("Consensus"))
422 JPopupMenu pop = new JPopupMenu(
423 MessageManager.getString("label.annotations"));
424 AnnotationLabels.addConsensusMenuOptions(this.alignPanel, ann, pop);
425 pop.show(this, e.getX(), e.getY());
429 * Toggle whether the sequence is part of the current selection group.
433 void selectSeq(int seq)
437 SequenceI pickedSeq = av.getAlignment().getSequenceAt(seq);
438 av.getSelectionGroup().addOrRemove(pickedSeq, false);
442 * Add contiguous rows of the alignment to the current selection group. Does
443 * nothing if there is no selection group.
448 void selectSeqs(int start, int end)
450 if (av.getSelectionGroup() == null)
455 if (end >= av.getAlignment().getHeight())
457 end = av.getAlignment().getHeight() - 1;
470 for (int i = start; i <= end; i++)
472 av.getSelectionGroup().addSequence(av.getAlignment().getSequenceAt(i),
478 * Respond to mouse released. Refreshes the display and triggers broadcast of
479 * the new selection group to any listeners.
484 public void mouseReleased(MouseEvent e)
486 if (scrollThread != null)
488 scrollThread.stopScrolling();
490 MousePos pos = alignPanel.getSeqPanel().findMousePosition(e);
492 mouseDragging = false;
493 PaintRefresher.Refresh(this, av.getSequenceSetId());
494 // always send selection message when mouse is released
497 if (e.isPopupTrigger()) // Windows reports this in mouseReleased
499 showPopupMenu(e, pos);
504 * Highlight sequence ids that match the given list, and if necessary scroll
505 * to the start sequence of the list.
509 public void highlightSearchResults(List<SequenceI> list)
511 getIdCanvas().setHighlighted(list);
513 if (list == null || list.isEmpty())
518 int index = av.getAlignment().findIndex(list.get(0));
520 // do we need to scroll the panel?
521 if ((av.getRanges().getStartSeq() > index)
522 || (av.getRanges().getEndSeq() < index))
524 av.getRanges().setStartSeq(index);
528 public IdCanvas getIdCanvas()
533 public void setIdCanvas(IdCanvas idCanvas)
535 this.idCanvas = idCanvas;
539 * Performs scrolling of the visible alignment up or down, adding newly
540 * visible sequences to the current selection
542 class ScrollThread extends Thread
544 private boolean running = false;
549 * Constructor for a thread that scrolls either up or down
553 public ScrollThread(boolean up)
556 setName("IdPanel$ScrollThread$" + String.valueOf(up));
561 * Sets a flag to stop the scrolling
563 public void stopScrolling()
569 * Scrolls the alignment either up or down, one row at a time, adding newly
570 * visible sequences to the current selection. Speed is limited to a maximum
571 * of ten rows per second. The thread exits when the end of the alignment is
572 * reached or a flag is set to stop it.
581 ViewportRanges ranges = IdPanel.this.av.getRanges();
582 if (ranges.scrollUp(up))
584 int toSeq = up ? ranges.getStartSeq() : ranges.getEndSeq();
585 int fromSeq = toSeq < lastid ? lastid - 1 : lastid + 1;
586 IdPanel.this.selectSeqs(fromSeq, toSeq);
593 * scroll did nothing - reached limit of visible alignment
598 alignPanel.paintAlignment(false, false);
603 } catch (Exception ex)