JAL-3161 limit tooltip and status updates to visible columns
[jalview.git] / src / jalview / appletgui / IdPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
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.
11  *  
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.
16  * 
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.
20  */
21 package jalview.appletgui;
22
23 import jalview.datamodel.SequenceFeature;
24 import jalview.datamodel.SequenceGroup;
25 import jalview.datamodel.SequenceI;
26 import jalview.urls.api.UrlProviderFactoryI;
27 import jalview.urls.api.UrlProviderI;
28 import jalview.urls.applet.AppletUrlProviderFactory;
29 import jalview.viewmodel.AlignmentViewport;
30
31 import java.awt.BorderLayout;
32 import java.awt.Panel;
33 import java.awt.event.InputEvent;
34 import java.awt.event.MouseEvent;
35 import java.awt.event.MouseListener;
36 import java.awt.event.MouseMotionListener;
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.List;
40
41 public class IdPanel extends Panel
42         implements MouseListener, MouseMotionListener
43 {
44
45   protected IdCanvas idCanvas;
46
47   protected AlignmentViewport av;
48
49   protected AlignmentPanel alignPanel;
50
51   ScrollThread scrollThread = null;
52
53   int lastid = -1;
54
55   boolean mouseDragging = false;
56
57   UrlProviderI urlProvider = null;
58
59   public IdPanel(AlignViewport viewport, AlignmentPanel parent)
60   {
61     this.av = viewport;
62     alignPanel = parent;
63     idCanvas = new IdCanvas(viewport);
64     setLayout(new BorderLayout());
65     add(idCanvas, BorderLayout.CENTER);
66     idCanvas.addMouseListener(this);
67     idCanvas.addMouseMotionListener(this);
68
69     String label, url;
70     // TODO: add in group link parameter
71
72     // make a list of label,url pairs
73     HashMap<String, String> urlList = new HashMap<>();
74     if (viewport.applet != null)
75     {
76       for (int i = 1; i < 10; i++)
77       {
78         label = viewport.applet.getParameter("linkLabel_" + i);
79         url = viewport.applet.getParameter("linkURL_" + i);
80
81         // only add non-null parameters
82         if (label != null)
83         {
84           urlList.put(label, url);
85         }
86       }
87
88       if (!urlList.isEmpty())
89       {
90         // set default as first entry in list
91         String defaultUrl = viewport.applet.getParameter("linkLabel_1");
92         UrlProviderFactoryI factory = new AppletUrlProviderFactory(
93                 defaultUrl, urlList);
94         urlProvider = factory.createUrlProvider();
95       }
96     }
97   }
98
99   Tooltip tooltip;
100
101   @Override
102   public void mouseMoved(MouseEvent e)
103   {
104     int seq = alignPanel.seqPanel.findSeq(e);
105
106     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
107
108     StringBuffer tooltiptext = new StringBuffer();
109     if (sequence == null)
110     {
111       return;
112     }
113     if (sequence.getDescription() != null)
114     {
115       tooltiptext.append(sequence.getDescription());
116       tooltiptext.append("\n");
117     }
118
119     for (SequenceFeature sf : sequence.getFeatures()
120             .getNonPositionalFeatures())
121     {
122       boolean nl = false;
123       if (sf.getFeatureGroup() != null)
124       {
125         tooltiptext.append(sf.getFeatureGroup());
126         nl = true;
127       }
128       if (sf.getType() != null)
129       {
130         tooltiptext.append(" ");
131         tooltiptext.append(sf.getType());
132         nl = true;
133       }
134       if (sf.getDescription() != null)
135       {
136         tooltiptext.append(" ");
137         tooltiptext.append(sf.getDescription());
138         nl = true;
139       }
140       if (!Float.isNaN(sf.getScore()) && sf.getScore() != 0f)
141       {
142         tooltiptext.append(" Score = ");
143         tooltiptext.append(sf.getScore());
144         nl = true;
145       }
146       if (sf.getStatus() != null && sf.getStatus().length() > 0)
147       {
148         tooltiptext.append(" (");
149         tooltiptext.append(sf.getStatus());
150         tooltiptext.append(")");
151         nl = true;
152       }
153       if (nl)
154       {
155         tooltiptext.append("\n");
156       }
157     }
158
159     if (tooltiptext.length() == 0)
160     {
161       // nothing to display - so clear tooltip if one is visible
162       if (tooltip != null)
163       {
164         tooltip.setVisible(false);
165       }
166       tooltip = null;
167       tooltiptext = null;
168       return;
169     }
170     if (tooltip == null)
171     {
172       tooltip = new Tooltip(
173               sequence.getDisplayId(true) + "\n" + tooltiptext.toString(),
174               idCanvas);
175     }
176     else
177     {
178       tooltip.setTip(
179               sequence.getDisplayId(true) + "\n" + tooltiptext.toString());
180     }
181     tooltiptext = null;
182   }
183
184   @Override
185   public void mouseDragged(MouseEvent e)
186   {
187     mouseDragging = true;
188
189     int seq = Math.max(0, alignPanel.seqPanel.findSeq(e));
190
191     if (seq < lastid)
192     {
193       selectSeqs(lastid - 1, seq);
194     }
195     else if (seq > lastid)
196     {
197       selectSeqs(lastid + 1, seq);
198     }
199
200     lastid = seq;
201     alignPanel.paintAlignment(false, false);
202   }
203
204   @Override
205   public void mouseClicked(MouseEvent e)
206   {
207     if (e.getClickCount() < 2)
208     {
209       return;
210     }
211
212     // get the sequence details
213     int seq = alignPanel.seqPanel.findSeq(e);
214     SequenceI sq = av.getAlignment().getSequenceAt(seq);
215     if (sq == null)
216     {
217       return;
218     }
219     String id = sq.getName();
220
221     // get the default url with the sequence details filled in
222     if (urlProvider == null)
223     {
224       return;
225     }
226     String url = urlProvider.getPrimaryUrl(id);
227     String target = urlProvider.getPrimaryTarget(id);
228     try
229     {
230       alignPanel.alignFrame.showURL(url, target);
231     } catch (Exception ex)
232     {
233       ex.printStackTrace();
234     }
235   }
236
237   @Override
238   public void mouseEntered(MouseEvent e)
239   {
240     if (scrollThread != null)
241     {
242       scrollThread.running = false;
243     }
244   }
245
246   @Override
247   public void mouseExited(MouseEvent e)
248   {
249     if (av.getWrapAlignment())
250     {
251       return;
252     }
253
254     if (mouseDragging && e.getY() < 0 && av.getRanges().getStartSeq() > 0)
255     {
256       scrollThread = new ScrollThread(true);
257     }
258
259     if (mouseDragging && e.getY() >= getSize().height
260             && av.getAlignment().getHeight() > av.getRanges().getEndSeq())
261     {
262       scrollThread = new ScrollThread(false);
263     }
264   }
265
266   @Override
267   public void mousePressed(MouseEvent e)
268   {
269     if (e.getClickCount() > 1)
270     {
271       return;
272     }
273
274     int y = e.getY();
275     if (av.getWrapAlignment())
276     {
277       y -= 2 * av.getCharHeight();
278     }
279
280     int seq = alignPanel.seqPanel.findSeq(e);
281
282     if ((e.getModifiers()
283             & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
284     {
285       SequenceI sq = av.getAlignment().getSequenceAt(seq);
286
287       /*
288        *  build a new links menu based on the current links
289        *  and any non-positional features
290        */
291       List<String> nlinks;
292       if (urlProvider != null)
293       {
294         nlinks = urlProvider.getLinksForMenu();
295       }
296       else
297       {
298         nlinks = new ArrayList<>();
299       }
300
301       for (SequenceFeature sf : sq.getFeatures().getNonPositionalFeatures())
302       {
303         if (sf.links != null)
304         {
305           for (String link : sf.links)
306           {
307             nlinks.add(link);
308           }
309         }
310       }
311
312       APopupMenu popup = new APopupMenu(alignPanel, sq, nlinks);
313       this.add(popup);
314       popup.show(this, e.getX(), e.getY());
315       return;
316     }
317
318     if ((av.getSelectionGroup() == null)
319             || ((!jalview.util.Platform.isControlDown(e)
320                     && !e.isShiftDown()) && av.getSelectionGroup() != null))
321     {
322       av.setSelectionGroup(new SequenceGroup());
323       av.getSelectionGroup().setStartRes(0);
324       av.getSelectionGroup().setEndRes(av.getAlignment().getWidth() - 1);
325     }
326
327     if (e.isShiftDown() && lastid != -1)
328     {
329       selectSeqs(lastid, seq);
330     }
331     else
332     {
333       selectSeq(seq);
334     }
335
336     alignPanel.paintAlignment(false, false);
337   }
338
339   void selectSeq(int seq)
340   {
341     lastid = seq;
342     SequenceI pickedSeq = av.getAlignment().getSequenceAt(seq);
343     av.getSelectionGroup().addOrRemove(pickedSeq, true);
344   }
345
346   void selectSeqs(int start, int end)
347   {
348
349     lastid = start;
350
351     if (end >= av.getAlignment().getHeight())
352     {
353       end = av.getAlignment().getHeight() - 1;
354     }
355
356     if (end < start)
357     {
358       int tmp = start;
359       start = end;
360       end = tmp;
361       lastid = end;
362     }
363     if (av.getSelectionGroup() == null)
364     {
365       av.setSelectionGroup(new SequenceGroup());
366     }
367     for (int i = start; i <= end; i++)
368     {
369       av.getSelectionGroup().addSequence(av.getAlignment().getSequenceAt(i),
370               i == end);
371     }
372
373   }
374
375   @Override
376   public void mouseReleased(MouseEvent e)
377   {
378     if (scrollThread != null)
379     {
380       scrollThread.running = false;
381     }
382
383     if (av.getSelectionGroup() != null)
384     {
385       av.getSelectionGroup().recalcConservation();
386     }
387
388     mouseDragging = false;
389     PaintRefresher.Refresh(this, av.getSequenceSetId());
390     // always send selection message when mouse is released
391     av.sendSelection();
392   }
393
394   public void highlightSearchResults(List<SequenceI> list)
395   {
396     idCanvas.setHighlighted(list);
397
398     if (list == null)
399     {
400       return;
401     }
402
403     int index = av.getAlignment().findIndex(list.get(0));
404
405     // do we need to scroll the panel?
406     if (av.getRanges().getStartSeq() > index
407             || av.getRanges().getEndSeq() < index)
408     {
409       av.getRanges().setStartSeq(index);
410     }
411   }
412
413   // this class allows scrolling off the bottom of the visible alignment
414   class ScrollThread extends Thread
415   {
416     boolean running = false;
417
418     boolean up = true;
419
420     public ScrollThread(boolean isUp)
421     {
422       this.up = isUp;
423       start();
424     }
425
426     public void stopScrolling()
427     {
428       running = false;
429     }
430
431     @Override
432     public void run()
433     {
434       running = true;
435       while (running)
436       {
437         if (av.getRanges().scrollUp(up))
438         {
439           // scroll was ok, so add new sequence to selection
440           int seq = av.getRanges().getStartSeq();
441           if (!up)
442           {
443             seq = av.getRanges().getEndSeq();
444           }
445
446           if (seq < lastid)
447           {
448             selectSeqs(lastid - 1, seq);
449           }
450           else if (seq > lastid && seq < av.getAlignment().getHeight())
451           {
452             selectSeqs(lastid + 1, seq);
453           }
454
455           lastid = seq;
456         }
457         else
458         {
459           running = false;
460         }
461
462         alignPanel.paintAlignment(true, false);
463         try
464         {
465           Thread.sleep(100);
466         } catch (Exception ex)
467         {
468         }
469       }
470     }
471   }
472 }