JAL-2388 Working hidden regions hide/show in desktop
[jalview.git] / src / jalview / appletgui / ScalePanel.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.ColumnSelection;
24 import jalview.datamodel.HiddenColumns;
25 import jalview.datamodel.SequenceGroup;
26 import jalview.renderer.ScaleRenderer;
27 import jalview.renderer.ScaleRenderer.ScaleMark;
28 import jalview.util.MessageManager;
29
30 import java.awt.Color;
31 import java.awt.FontMetrics;
32 import java.awt.Graphics;
33 import java.awt.MenuItem;
34 import java.awt.Panel;
35 import java.awt.PopupMenu;
36 import java.awt.event.ActionEvent;
37 import java.awt.event.ActionListener;
38 import java.awt.event.InputEvent;
39 import java.awt.event.MouseEvent;
40 import java.awt.event.MouseListener;
41 import java.awt.event.MouseMotionListener;
42 import java.util.List;
43
44 public class ScalePanel extends Panel implements MouseMotionListener,
45         MouseListener
46 {
47
48   protected int offy = 4;
49
50   public int width;
51
52   protected AlignViewport av;
53
54   AlignmentPanel ap;
55
56   boolean stretchingGroup = false;
57
58   int min; // used by mouseDragged to see if user
59
60   int max; // used by mouseDragged to see if user
61
62   boolean mouseDragging = false;
63
64   int[] reveal;
65
66   public ScalePanel(AlignViewport av, AlignmentPanel ap)
67   {
68     setLayout(null);
69     this.av = av;
70     this.ap = ap;
71
72     addMouseListener(this);
73     addMouseMotionListener(this);
74
75   }
76
77   @Override
78   public void mousePressed(MouseEvent evt)
79   {
80     int x = (evt.getX() / av.getCharWidth()) + av.getRanges().getStartRes();
81     final int res;
82
83     if (av.hasHiddenColumns())
84     {
85       res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(x);
86     }
87     else
88     {
89       res = x;
90     }
91
92     min = res;
93     max = res;
94     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
95     {
96       rightMouseButtonPressed(evt, res);
97     }
98     else
99     {
100       leftMouseButtonPressed(evt, res);
101     }
102   }
103
104   /**
105    * Handles left mouse button pressed (selection / clear selections)
106    * 
107    * @param evt
108    * @param res
109    */
110   protected void leftMouseButtonPressed(MouseEvent evt, final int res)
111   {
112     if (!evt.isControlDown() && !evt.isShiftDown())
113     {
114       av.getColumnSelection().clear();
115     }
116
117     av.getColumnSelection().addElement(res);
118     SequenceGroup sg = new SequenceGroup();
119     for (int i = 0; i < av.getAlignment().getSequences().size(); i++)
120     {
121       sg.addSequence(av.getAlignment().getSequenceAt(i), false);
122     }
123
124     sg.setStartRes(res);
125     sg.setEndRes(res);
126     av.setSelectionGroup(sg);
127
128     if (evt.isShiftDown())
129     {
130       int min = Math.min(av.getColumnSelection().getMin(), res);
131       int max = Math.max(av.getColumnSelection().getMax(), res);
132       for (int i = min; i < max; i++)
133       {
134         av.getColumnSelection().addElement(i);
135       }
136       sg.setStartRes(min);
137       sg.setEndRes(max);
138     }
139     ap.paintAlignment(true);
140     av.sendSelection();
141   }
142
143   /**
144    * Handles right mouse button press. If pressed in a selected column, opens
145    * context menu for 'Hide Columns'. If pressed on a hidden columns marker,
146    * opens context menu for 'Reveal / Reveal All'. Else does nothing.
147    * 
148    * @param evt
149    * @param res
150    */
151   protected void rightMouseButtonPressed(MouseEvent evt, final int res)
152   {
153     PopupMenu pop = new PopupMenu();
154     if (reveal != null)
155     {
156       MenuItem item = new MenuItem(MessageManager.getString("label.reveal"));
157       item.addActionListener(new ActionListener()
158       {
159         @Override
160         public void actionPerformed(ActionEvent e)
161         {
162           av.showColumn(reveal[0]);
163           reveal = null;
164           ap.paintAlignment(true);
165           if (ap.overviewPanel != null)
166           {
167             ap.overviewPanel.updateOverviewImage();
168           }
169           av.sendSelection();
170         }
171       });
172       pop.add(item);
173
174       if (av.getAlignment().getHiddenColumns().hasManyHiddenColumns())
175       {
176         item = new MenuItem(MessageManager.getString("action.reveal_all"));
177         item.addActionListener(new ActionListener()
178         {
179           @Override
180           public void actionPerformed(ActionEvent e)
181           {
182             av.showAllHiddenColumns();
183             reveal = null;
184             ap.paintAlignment(true);
185             if (ap.overviewPanel != null)
186             {
187               ap.overviewPanel.updateOverviewImage();
188             }
189             av.sendSelection();
190           }
191         });
192         pop.add(item);
193       }
194       this.add(pop);
195       pop.show(this, evt.getX(), evt.getY());
196     }
197     else if (av.getColumnSelection().contains(res))
198     {
199       MenuItem item = new MenuItem(
200               MessageManager.getString("label.hide_columns"));
201       item.addActionListener(new ActionListener()
202       {
203         @Override
204         public void actionPerformed(ActionEvent e)
205         {
206           av.hideColumns(res, res);
207           if (av.getSelectionGroup() != null
208                   && av.getSelectionGroup().getSize() == av.getAlignment()
209                           .getHeight())
210           {
211             av.setSelectionGroup(null);
212           }
213
214           ap.paintAlignment(true);
215           if (ap.overviewPanel != null)
216           {
217             ap.overviewPanel.updateOverviewImage();
218           }
219           av.sendSelection();
220         }
221       });
222       pop.add(item);
223       this.add(pop);
224       pop.show(this, evt.getX(), evt.getY());
225     }
226   }
227
228   @Override
229   public void mouseReleased(MouseEvent evt)
230   {
231     mouseDragging = false;
232
233     int res = (evt.getX() / av.getCharWidth())
234             + av.getRanges().getStartRes();
235
236     if (res > av.getAlignment().getWidth())
237     {
238       res = av.getAlignment().getWidth() - 1;
239     }
240
241     if (av.hasHiddenColumns())
242     {
243       res = av.getAlignment().getHiddenColumns()
244               .adjustForHiddenColumns(res);
245     }
246
247     if (!stretchingGroup)
248     {
249       ap.paintAlignment(false);
250
251       return;
252     }
253
254     SequenceGroup sg = av.getSelectionGroup();
255
256     if (res > sg.getStartRes())
257     {
258       sg.setEndRes(res);
259     }
260     else if (res < sg.getStartRes())
261     {
262       sg.setStartRes(res);
263     }
264
265     stretchingGroup = false;
266     ap.paintAlignment(false);
267     av.sendSelection();
268   }
269
270   /**
271    * Action on dragging the mouse in the scale panel is to expand or shrink the
272    * selection group range (including any hidden columns that it spans)
273    * 
274    * @param evt
275    */
276   @Override
277   public void mouseDragged(MouseEvent evt)
278   {
279     mouseDragging = true;
280     ColumnSelection cs = av.getColumnSelection();
281
282     int res = (evt.getX() / av.getCharWidth())
283             + av.getRanges().getStartRes();
284     res = Math.max(0, res);
285     res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(res);
286     res = Math.min(res, av.getAlignment().getWidth() - 1);
287     min = Math.min(res, min);
288     max = Math.max(res, max);
289
290     SequenceGroup sg = av.getSelectionGroup();
291     if (sg != null)
292     {
293       stretchingGroup = true;
294       cs.stretchGroup(res, sg, min, max);
295       ap.paintAlignment(false);
296     }
297   }
298
299   @Override
300   public void mouseEntered(MouseEvent evt)
301   {
302     if (mouseDragging)
303     {
304       ap.seqPanel.scrollCanvas(null);
305     }
306   }
307
308   @Override
309   public void mouseExited(MouseEvent evt)
310   {
311     if (mouseDragging)
312     {
313       ap.seqPanel.scrollCanvas(evt);
314     }
315   }
316
317   @Override
318   public void mouseClicked(MouseEvent evt)
319   {
320
321   }
322
323   @Override
324   public void mouseMoved(MouseEvent evt)
325   {
326     if (!av.hasHiddenColumns())
327     {
328       return;
329     }
330
331     int res = (evt.getX() / av.getCharWidth())
332             + av.getRanges().getStartRes();
333
334     res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(res);
335
336     reveal = null;
337     for (int[] region : av.getAlignment().getHiddenColumns()
338             .getHiddenRegions())
339     {
340       if (res + 1 == region[0] || res - 1 == region[1])
341       {
342         reveal = region;
343         break;
344       }
345     }
346
347     repaint();
348   }
349
350   @Override
351   public void update(Graphics g)
352   {
353     paint(g);
354   }
355
356   @Override
357   public void paint(Graphics g)
358   {
359     drawScale(g, av.getRanges().getStartRes(), av.getRanges().getEndRes(),
360             getSize().width,
361             getSize().height);
362   }
363
364   // scalewidth will normally be screenwidth,
365   public void drawScale(Graphics gg, int startx, int endx, int width,
366           int height)
367   {
368     gg.setFont(av.getFont());
369     // Fill in the background
370     gg.setColor(Color.white);
371     gg.fillRect(0, 0, width, height);
372     gg.setColor(Color.black);
373
374     // Fill the selected columns
375     ColumnSelection cs = av.getColumnSelection();
376     HiddenColumns hidden = av.getAlignment().getHiddenColumns();
377     int avCharWidth = av.getCharWidth();
378     int avcharHeight = av.getCharHeight();
379     if (cs != null)
380     {
381       gg.setColor(new Color(220, 0, 0));
382       boolean hasHiddenColumns = hidden.hasHiddenColumns();
383       for (int sel : cs.getSelected())
384       {
385         // TODO: JAL-2001 - provide a fast method to list visible selected in a
386         // given range
387         if (hasHiddenColumns)
388         {
389           if (hidden.isVisible(sel))
390           {
391             sel = hidden.findColumnPosition(sel);
392           }
393           else
394           {
395             continue;
396           }
397         }
398
399         if ((sel >= startx) && (sel <= endx))
400         {
401           gg.fillRect((sel - startx) * avCharWidth, 0, avCharWidth,
402                   getSize().height);
403         }
404       }
405     }
406
407     // Draw the scale numbers
408     gg.setColor(Color.black);
409
410     int maxX = 0;
411     List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
412             endx);
413
414     FontMetrics fm = gg.getFontMetrics(av.getFont());
415     int y = avcharHeight;
416     int yOf = fm.getDescent();
417     y -= yOf;
418     for (ScaleMark mark : marks)
419     {
420       boolean major = mark.major;
421       int mpos = mark.column; // (i - startx - 1)
422       String mstring = mark.text;
423       if (mstring != null)
424       {
425         if (mpos * avCharWidth > maxX)
426         {
427           gg.drawString(mstring, mpos * avCharWidth, y);
428           maxX = (mpos + 2) * avCharWidth + fm.stringWidth(mstring);
429         }
430       }
431       if (major)
432       {
433         gg.drawLine((mpos * avCharWidth) + (avCharWidth / 2), y + 2,
434                 (mpos * avCharWidth) + (avCharWidth / 2), y + (yOf * 2));
435       }
436       else
437       {
438         gg.drawLine((mpos * avCharWidth) + (avCharWidth / 2), y + yOf,
439                 (mpos * avCharWidth) + (avCharWidth / 2), y + (yOf * 2));
440       }
441     }
442
443     if (av.hasHiddenColumns())
444     {
445       gg.setColor(Color.blue);
446       int res;
447       if (av.getShowHiddenMarkers())
448       {
449         int widthx = 1 + endx - startx;
450         for (int i = 0; i < hidden.getHiddenRegions().size(); i++)
451         {
452
453           res = hidden.findHiddenRegionPosition(i) - startx;
454
455           if (res < 0 || res > widthx)
456           {
457             continue;
458           }
459
460           gg.fillPolygon(new int[] {
461               -1 + res * avCharWidth - avcharHeight / 4,
462               -1 + res * avCharWidth + avcharHeight / 4,
463               -1 + res * avCharWidth }, new int[] { y, y, y + 2 * yOf }, 3);
464         }
465       }
466     }
467   }
468
469 }